Files
EA_with_Python/Include/Math/Alglib/interpolation.mqh
T

34262 lines
1.4 MiB
Plaintext

//+------------------------------------------------------------------+
//| interpolation.mqh |
//| Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
//| Copyright 2012-2026, MetaQuotes Ltd. |
//| www.mql5.com |
//+------------------------------------------------------------------+
//| Implementation of ALGLIB library in MetaQuotes Language 5 |
//| |
//| The features of the library include: |
//| - Linear algebra (direct algorithms, EVD, SVD) |
//| - Solving systems of linear and non-linear equations |
//| - Interpolation |
//| - Optimization |
//| - FFT (Fast Fourier Transform) |
//| - Numerical integration |
//| - Linear and nonlinear least-squares fitting |
//| - Ordinary differential equations |
//| - Computation of special functions |
//| - Descriptive statistics and hypothesis testing |
//| - Data analysis - classification, regression |
//| - Implementing linear algebra algorithms, interpolation, etc. |
//| in high-precision arithmetic (using MPFR) |
//| |
//| This file is free software; you can redistribute it and/or |
//| modify it under the terms of the GNU General Public License as |
//| published by the Free Software Foundation (www.fsf.org); either |
//| version 2 of the License, or (at your option) any later version. |
//| |
//| This program is distributed in the hope that it will be useful, |
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
//| GNU General Public License for more details. |
//+------------------------------------------------------------------+
#include "alglibmisc.mqh"
#include "optimization.mqh"
#include "solvers.mqh"
#include "integration.mqh"
//+------------------------------------------------------------------+
//| IDW Buffer |
//+------------------------------------------------------------------+
class CIDWCalcBuffer
{
public:
CRowDouble m_tsdist;
CRowDouble m_tsw;
CRowDouble m_tsyw;
CRowDouble m_x;
CRowDouble m_y;
CMatrixDouble m_tsxy;
CKDTreeRequestBuffer m_requestbuffer;
//--- constructor / destructor
CIDWCalcBuffer(void) {}
~CIDWCalcBuffer(void) {}
//--- copy
void Copy(const CIDWCalcBuffer &obj);
//--- overloading
void operator=(const CIDWCalcBuffer &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CIDWCalcBuffer::Copy(const CIDWCalcBuffer &obj)
{
m_tsdist=obj.m_tsdist;
m_tsw=obj.m_tsw;
m_tsyw=obj.m_tsyw;
m_x=obj.m_x;
m_y=obj.m_y;
m_tsxy=obj.m_tsxy;
m_requestbuffer=obj.m_requestbuffer;
}
//+------------------------------------------------------------------+
//| IDW (Inverse Distance Weighting) model object. |
//+------------------------------------------------------------------+
class CIDWModel
{
public:
int m_algotype;
int m_nlayers;
int m_npoints;
int m_nx;
int m_ny;
double m_lambda0;
double m_lambdadecay;
double m_lambdalast;
double m_r0;
double m_rdecay;
double m_shepardp;
CKDTree m_tree;
CIDWCalcBuffer m_buffer;
//--- arrays
CRowDouble m_globalprior;
CRowDouble m_shepardxy;
//--- constructor / destructor
CIDWModel(void);
~CIDWModel(void) {}
//--- copy
void Copy(const CIDWModel &obj);
//--- overloading
void operator=(const CIDWModel &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CIDWModel::CIDWModel(void)
{
m_algotype=0;
m_nlayers=0;
m_npoints=0;
m_nx=0;
m_ny=0;
m_lambda0=0;
m_lambdadecay=0;
m_lambdalast=0;
m_r0=0;
m_rdecay=0;
m_shepardp=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CIDWModel::Copy(const CIDWModel &obj)
{
//--- copy variables
m_algotype=obj.m_algotype;
m_nlayers=obj.m_nlayers;
m_npoints=obj.m_npoints;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_lambda0=obj.m_lambda0;
m_lambdadecay=obj.m_lambdadecay;
m_lambdalast=obj.m_lambdalast;
m_r0=obj.m_r0;
m_rdecay=obj.m_rdecay;
m_shepardp=obj.m_shepardp;
m_globalprior=obj.m_globalprior;
m_shepardxy=obj.m_shepardxy;
m_tree=obj.m_tree;
m_buffer=obj.m_buffer;
}
//+------------------------------------------------------------------+
//| IDW model. |
//+------------------------------------------------------------------+
class CIDWModelShell
{
private:
CIDWModel m_innerobj;
public:
//--- constructors, destructor
CIDWModelShell(void) {}
CIDWModelShell(CIDWModel &obj) { m_innerobj.Copy(obj); }
~CIDWModelShell(void) {}
//--- method
CIDWModel *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Builder object used to generate IDW (Inverse Distance Weighting) |
//| model. |
//+------------------------------------------------------------------+
struct CIDWBuilder
{
int m_algotype;
int m_nlayers;
int m_npoints;
int m_nx;
int m_ny;
int m_priortermtype;
double m_lambda0;
double m_lambdadecay;
double m_lambdalast;
double m_r0;
double m_rdecay;
double m_shepardp;
CRowInt m_tmptags;
CRowDouble m_priortermval;
CRowDouble m_tmpdist;
CRowDouble m_tmpmean;
CRowDouble m_tmpw;
CRowDouble m_tmpwy;
CRowDouble m_tmpx;
CRowDouble m_xy;
CMatrixDouble m_tmplayers;
CMatrixDouble m_tmpxy;
CKDTree m_tmptree;
//--- constructor / destructor
CIDWBuilder(void);
~CIDWBuilder(void) {}
//--- copy
void Copy(const CIDWBuilder &obj);
//--- overloading
void operator=(const CIDWBuilder &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CIDWBuilder::CIDWBuilder(void)
{
m_algotype=0;
m_nlayers=0;
m_npoints=0;
m_nx=0;
m_ny=0;
m_priortermtype=0;
m_lambda0=0;
m_lambdadecay=0;
m_lambdalast=0;
m_r0=0;
m_rdecay=0;
m_shepardp=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CIDWBuilder::Copy(const CIDWBuilder &obj)
{
m_algotype=obj.m_algotype;
m_nlayers=obj.m_nlayers;
m_npoints=obj.m_npoints;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_priortermtype=obj.m_priortermtype;
m_lambda0=obj.m_lambda0;
m_lambdadecay=obj.m_lambdadecay;
m_lambdalast=obj.m_lambdalast;
m_r0=obj.m_r0;
m_rdecay=obj.m_rdecay;
m_shepardp=obj.m_shepardp;
m_tmptags=obj.m_tmptags;
m_priortermval=obj.m_priortermval;
m_tmpdist=obj.m_tmpdist;
m_tmpmean=obj.m_tmpmean;
m_tmpw=obj.m_tmpw;
m_tmpwy=obj.m_tmpwy;
m_tmpx=obj.m_tmpx;
m_xy=obj.m_xy;
m_tmplayers=obj.m_tmplayers;
m_tmpxy=obj.m_tmpxy;
m_tmptree=obj.m_tmptree;
}
//+------------------------------------------------------------------+
//| IDW fitting report: |
//| rmserror RMS error |
//| avgerror average error |
//| maxerror maximum error |
//| r2 coefficient of determination, |
//| R-squared, 1-RSS/TSS |
//+------------------------------------------------------------------+
struct CIDWReport
{
public:
double m_avgerror;
double m_maxerror;
double m_r2;
double m_rmserror;
//--- constructor / destructor
CIDWReport(void) { ZeroMemory(this); }
~CIDWReport(void) {}
//--- copy
void Copy(const CIDWReport &obj);
//--- overloading
void operator=(const CIDWReport &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CIDWReport::Copy(const CIDWReport &obj)
{
m_avgerror=obj.m_avgerror;
m_maxerror=obj.m_maxerror;
m_r2=obj.m_r2;
m_rmserror=obj.m_rmserror;
}
//+------------------------------------------------------------------+
//| Inverse distance weighting interpolation |
//+------------------------------------------------------------------+
class CIDWInt
{
public:
//--- class constants
static const double m_w0;
static const double m_meps;
static const int m_defaultnlayers;
static const double m_defaultlambda0;
//--- public methods
static void IDWCreateCalcBuffer(CIDWModel &s,CIDWCalcBuffer &buf);
static void IDWBuilderCreate(int nx,int ny,CIDWBuilder &State);
static void IDWBuilderSetNLayers(CIDWBuilder &State,int nlayers);
static void IDWBuilderSetPoints(CIDWBuilder &State,CMatrixDouble &xy,int n);
static void IDWBuilderSetAlgoMSTAB(CIDWBuilder &State,double srad);
static void IDWBuilderSetAlgoTextBookShepard(CIDWBuilder &State,double p);
static void IDWBuilderSetAlgoTextBookModShepard(CIDWBuilder &State,double r);
static void IDWBuilderSetUserTerm(CIDWBuilder &State,double v);
static void IDWBuilderSetConstTerm(CIDWBuilder &State);
static void IDWBuilderSetZeroTerm(CIDWBuilder &State);
static double IDWCalc1(CIDWModel &s,double x0);
static double IDWCalc2(CIDWModel &s,double x0,double x1);
static double IDWCalc3(CIDWModel &s,double x0,double x1,double x2);
static void IDWCalc(CIDWModel &s,CRowDouble &x,CRowDouble &y);
static void IDWCalcBuf(CIDWModel &s,CRowDouble &x,CRowDouble &y);
static void IDWTsCalcBuf(CIDWModel &s,CIDWCalcBuffer &buf,CRowDouble &x,CRowDouble &y);
static void IDWFit(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep);
static void IDWAlloc(CSerializer &s,CIDWModel &model);
static void IDWSerialize(CSerializer &s,CIDWModel &model);
static void CIDWInt::IDWUnserialize(CSerializer &s,CIDWModel &model);
private:
static void CIDWInt::ErrorMetricsViaCalc(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep);
};
//+------------------------------------------------------------------+
//| Initialize constants |
//+------------------------------------------------------------------+
const double CIDWInt::m_w0=1.0;
const double CIDWInt::m_meps=1.0E-50;
const int CIDWInt::m_defaultnlayers=16;
const double CIDWInt::m_defaultlambda0=0.3333;
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to |
//| perform parallel IDW model evaluations (with one IDW model |
//| instance being used from multiple threads, as long as different |
//| threads use different instances of buffer). |
//| This buffer object can be used with IDWTsCalcBuf() function (here|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
//| function which reuses previously allocated output space). |
//| How to use it: |
//| * create IDW model structure or load it from file |
//| * call IDWCreateCalcBuffer(), once per thread working with IDW |
//| model (you should call this function only AFTER model |
//| initialization, see below for more information) |
//| * call IDWTsCalcBuf() from different threads, with each thread |
//| working with its own copy of buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW model |
//| OUTPUT PARAMETERS: |
//| Buf - external buffer. |
//| IMPORTANT: buffer object should be used only with IDW model |
//| object which was used to initialize buffer. Any |
//| attempt to use buffer with different object is |
//| dangerous - you may get memory violation error because|
//| sizes of internal arrays do not fit to dimensions of |
//| the IDW structure. |
//| IMPORTANT: you should call this function only for model which was|
//| built with model builder (or unserialized from file). |
//| Sizes of some internal structures are determined only |
//| after model is built, so buffer object created before |
//| model construction stage will be useless (and any |
//| attempt to use it will result in exception). |
//+------------------------------------------------------------------+
void CIDWInt::IDWCreateCalcBuffer(CIDWModel &s,CIDWCalcBuffer &buf)
{
//--- check
if(!CAp::Assert(s.m_nx>=1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_ny>=1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nlayers>=0,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_algotype>=0,__FUNCTION__+": integrity check failed"))
return;
if(s.m_nlayers>=1 && s.m_algotype!=0)
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_tree,buf.m_requestbuffer);
CApServ::RVectorSetLengthAtLeast(buf.m_x,s.m_nx);
CApServ::RVectorSetLengthAtLeast(buf.m_y,s.m_ny);
CApServ::RVectorSetLengthAtLeast(buf.m_tsyw,s.m_ny*MathMax(s.m_nlayers,1));
CApServ::RVectorSetLengthAtLeast(buf.m_tsw,MathMax(s.m_nlayers,1));
}
//+------------------------------------------------------------------+
//| This subroutine creates builder object used to generate IDW model|
//| from irregularly sampled (scattered) dataset. Multidimensional |
//| scalar/vector-valued are supported. |
//| Builder object is used to fit model to data as follows: |
//| * builder object is created with idwbuildercreate() function |
//| * dataset is added with IDWBuilderSetPoints() function |
//| * one of the modern IDW algorithms is chosen with either: |
//| * IDWBuilderSetAlgoMSTAB() - Multilayer STABilized algorithm|
//| (interpolation). |
//| Alternatively, one of the textbook algorithms can be chosen (not |
//| recommended): |
//| * IDWBuilderSetAlgoTextBookShepard() - textbook Shepard |
//| algorithm |
//| * IDWBuilderSetAlgoTextBookModShepard()- textbook modified |
//| Shepard algorithm |
//| * finally, model construction is performed with IDWFit() |
//| function. |
//| INPUT PARAMETERS: |
//| NX - dimensionality of the argument, NX>=1 |
//| NY - dimensionality of the function being modeled, |
//| NY>=1; NY=1 corresponds to classic scalar function,|
//| NY>=1 corresponds to vector-valued function. |
//| OUTPUT PARAMETERS: |
//| State - builder object |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderCreate(int nx,int ny,CIDWBuilder &State)
{
//--- check
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<=0"))
return;
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<=0"))
return;
//--- We choose reasonable defaults for the algorithm:
//--- * MSTAB algorithm
//--- * 12 layers
//--- * default radius
//--- * default Lambda0
State.m_algotype=2;
State.m_priortermtype=2;
CApServ::RVectorSetLengthAtLeast(State.m_priortermval,ny);
State.m_nlayers=m_defaultnlayers;
State.m_r0=0;
State.m_rdecay=0.5;
State.m_lambda0=m_defaultlambda0;
State.m_lambdalast=0;
State.m_lambdadecay=1.0;
//--- Other parameters, not used but initialized
State.m_shepardp=0;
//--- Initial dataset is empty
State.m_npoints=0;
State.m_nx=nx;
State.m_ny=ny;
}
//+------------------------------------------------------------------+
//| This function changes number of layers used by IDW-MSTAB |
//| algorithm. |
//| The more layers you have, the finer details can be reproduced |
//| with IDW model. The less layers you have, the less memory and CPU|
//| time is consumed by the model. |
//| Memory consumption grows linearly with layers count, running time|
//| grows sub-linearly. |
//| The default number of layers is 16, which allows you to reproduce|
//| details at distance down to SRad/65536. You will rarely need to |
//| change it. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| NLayers - NLayers>=1, the number of layers used by the model.|
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetNLayers(CIDWBuilder &State,int nlayers)
{
//--- check
if(!CAp::Assert(nlayers>=1,__FUNCTION__+": N<1"))
return;
State.m_nlayers=nlayers;
}
//+------------------------------------------------------------------+
//| This function adds dataset to the builder object. |
//| This function overrides results of the previous calls, i.e. |
//| multiple calls of this function will result in only the last set |
//| being added. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| XY - points, array[N,NX+NY]. One row corresponds to one |
//| point in the dataset. First NX elements are |
//| coordinates, next NY elements are function values. |
//| Array may be larger than specified, in this case |
//| only leading [N,NX+NY] elements will be used. |
//| N - number of points in the dataset, N>=0. |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetPoints(CIDWBuilder &State,
CMatrixDouble &xy,int n)
{
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return;
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
return;
if(!CAp::Assert(n==0 || CAp::Cols(xy)>=State.m_nx+State.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,State.m_nx+State.m_ny),__FUNCTION__+": XY contains infinite or NaN values!"))
return;
State.m_npoints=n;
int ew=State.m_nx+State.m_ny;
CApServ::RVectorSetLengthAtLeast(State.m_xy,n*ew);
for(int i=0; i<n; i++)
for(int j=0; j<ew; j++)
State.m_xy.Set(i*ew+j,xy.Get(i,j));
}
//+------------------------------------------------------------------+
//| This function sets IDW model construction algorithm to the |
//| Multilayer Stabilized IDW method (IDW-MSTAB), a latest |
//| incarnation of the inverse distance weighting interpolation which|
//| fixes shortcomings of the original and modified Shepard's |
//| variants. |
//| The distinctive features of IDW-MSTAB are: |
//| 1) exact interpolation is pursued (as opposed to fitting and |
//| noise suppression) |
//| 2) improved robustness when compared with that of other |
//| algorithms: |
//| * MSTAB shows almost no strange fitting artifacts like |
//| ripples and sharp spikes (unlike N-dimensional splines |
//| and HRBFs) |
//| * MSTAB does not return function values far from the |
//| interval spanned by the dataset; say, if all your points |
//| have |f|<=1, you can be sure that model value won't |
//| deviate too much from [-1,+1] |
//| 3) good model construction time competing with that of HRBFs |
//| and bicubic splines |
//| 4) ability to work with any number of dimensions, starting |
//| from NX=1 |
//| The drawbacks of IDW-MSTAB (and all IDW algorithms in general) |
//| are: |
//| 1) dependence of the model evaluation time on the search radius|
//| 2) bad extrapolation properties, models built by this method |
//| are usually conservative in their predictions |
//| Thus, IDW-MSTAB is a good "default" option if you want to perform|
//| scattered multidimensional interpolation. Although it has its |
//| drawbacks, it is easy to use and robust, which makes it a good |
//| first step. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| SRad - initial search radius, SRad>0 is required. A model |
//| value is obtained by "smart" averaging of the |
//| dataset points within search radius. |
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
//| including datasets with non-distinct points. In case |
//| non-distinct points are found, an average value for this |
//| point will be calculated. |
//| NOTE 2: the memory requirements for model storage are |
//| O(NPoints*NLayers). The model construction needs twice |
//| as much memory as model storage. |
//| NOTE 3: by default 16 IDW layers are built which is enough for |
//| most cases. You can change this parameter with |
//| IDWBuilderSetNLayers() method. Larger values may be |
//| necessary if you need to reproduce extrafine details at |
//| distances smaller than SRad/65536. Smaller value may |
//| be necessary if you have to save memory and computing |
//| time, and ready to sacrifice some model quality. |
//| ALGORITHM DESCRIPTION: |
//| ALGLIB implementation of IDW is somewhat similar to the |
//| modified Shepard's method (one with search radius R) but |
//| overcomes several of its drawbacks, namely: |
//| 1) a tendency to show stepwise behavior for uniform datasets|
//| 2) a tendency to show terrible interpolation properties for |
//| highly nonuniform datasets which often arise in |
//| geospatial tasks (function values are densely sampled |
//| across multiple separated "tracks") |
//| IDW-MSTAB method performs several passes over dataset and builds |
//| a sequence of progressively refined IDW models (layers), which |
//| starts from one with largest search radius SRad and continues |
//| to smaller search radii until required number of layers is built.|
//| Highest layers reproduce global behavior of the target function |
//| at larger distances whilst lower layers reproduce fine details at|
//| smaller distances. |
//| Each layer is an IDW model built with following modifications: |
//| * weights go to zero when distance approach to the current |
//| search radius |
//| * an additional regularizing term is added to the distance: |
//| w=1/(d^2+lambda) |
//| * an additional fictional term with unit weight and zero |
//| function value is added in order to promote continuity |
//| properties at the isolated and boundary points |
//| By default, 16 layers is built, which is enough for most cases. |
//| You can change this parameter with IDWBuilderSetNLayers() method.|
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetAlgoMSTAB(CIDWBuilder &State,double srad)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(srad),__FUNCTION__+": SRad is not finite"))
return;
if(!CAp::Assert(srad>0.0,__FUNCTION__+": SRad<=0"))
return;
//--- Set algorithm
State.m_algotype=2;
//--- Set options
State.m_r0=srad;
State.m_rdecay=0.5;
State.m_lambda0=m_defaultlambda0;
State.m_lambdalast=0;
State.m_lambdadecay=1.0;
}
//+------------------------------------------------------------------+
//| This function sets IDW model construction algorithm to the |
//| textbook Shepard's algorithm with custom (user-specified) power |
//| parameter. |
//| IMPORTANT: we do NOT recommend using textbook IDW algorithms |
//| because they have terrible interpolation properties. |
//| Use MSTAB in all cases. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| P - power parameter, P>0; good value to start with is |
//| 2.0 |
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
//| including datasets with non-distinct points. In case |
//| non-distinct points are found, an average value for this |
//| point will be calculated. |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetAlgoTextBookShepard(CIDWBuilder &State,double p)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(p),__FUNCTION__+": P is not finite"))
return;
if(!CAp::Assert(p>0.0,__FUNCTION__+": P<=0"))
return;
//--- Set algorithm and options
State.m_algotype=0;
State.m_shepardp=p;
}
//+------------------------------------------------------------------+
//| This function sets IDW model construction algorithm to the |
//| 'textbook' modified Shepard's algorithm with user-specified |
//| search radius. |
//| IMPORTANT: we do NOT recommend using textbook IDW algorithms |
//| because they have terrible interpolation properties. |
//| Use MSTAB in all cases. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| R - search radius |
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
//| including datasets with non-distinct points. In case |
//| non-distinct points are found, an average value for this |
//| point will be calculated. |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetAlgoTextBookModShepard(CIDWBuilder &State,double r)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(r),__FUNCTION__+": R is not finite"))
return;
if(!CAp::Assert(r>0.0,__FUNCTION__+": R<=0"))
return;
//--- Set algorithm and options
State.m_algotype=1;
State.m_r0=r;
}
//+------------------------------------------------------------------+
//| This function sets prior term (model value at infinity) as |
//| user-specified value. |
//| INPUT PARAMETERS: |
//| S - spline builder |
//| V - value for user-defined prior |
//| NOTE: for vector-valued models all components of the prior are |
//| set to same user-specified value |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetUserTerm(CIDWBuilder &State,double v)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": infinite/NAN value passed"))
return;
State.m_priortermtype=0;
State.m_priortermval.Fill(v);
}
//+------------------------------------------------------------------+
//| This function sets constant prior term (model value at infinity).|
//| Constant prior term is determined as mean value over dataset. |
//| INPUT PARAMETERS: |
//| S - spline builder |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetConstTerm(CIDWBuilder &State)
{
State.m_priortermtype=2;
}
//+------------------------------------------------------------------+
//| This function sets zero prior term (model value at infinity). |
//| INPUT PARAMETERS: |
//| S - spline builder |
//+------------------------------------------------------------------+
void CIDWInt::IDWBuilderSetZeroTerm(CIDWBuilder &State)
{
State.m_priortermtype=3;
}
//+------------------------------------------------------------------+
//| IDW interpolation: scalar target, 1-dimensional argument |
//| NOTE: this function modifies internal temporaries of the IDW |
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
//| parallel model evaluation from the multiple threads, use |
//| IDWTsCalcBuf() with per-thread buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW interpolant built with IDW builder |
//| X0 - argument value |
//| Result: |
//| IDW interpolant S(X0) |
//+------------------------------------------------------------------+
double CIDWInt::IDWCalc1(CIDWModel &s,double x0)
{
//--- check
if(!CAp::Assert(s.m_nx==1,__FUNCTION__+": S.NX<>1"))
return(0);
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
return(0);
s.m_buffer.m_x.Set(0,x0);
//--- function call
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
//--- return result
return(s.m_buffer.m_y[0]);
}
//+------------------------------------------------------------------+
//| IDW interpolation: scalar target, 2-dimensional argument |
//| NOTE: this function modifies internal temporaries of the IDW |
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
//| parallel model evaluation from the multiple threads, use |
//| IDWTsCalcBuf() with per- thread buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW interpolant built with IDW builder |
//| X0, X1 - argument value |
//| Result: |
//| IDW interpolant S(X0,X1) |
//+------------------------------------------------------------------+
double CIDWInt::IDWCalc2(CIDWModel &s,double x0,double x1)
{
//--- check
if(!CAp::Assert(s.m_nx==2,__FUNCTION__+": S.NX<>2"))
return(0);
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": X1 is INF or NAN"))
return(0);
s.m_buffer.m_x.Set(0,x0);
s.m_buffer.m_x.Set(1,x1);
//--- function call
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
//--- return result
return(s.m_buffer.m_y[0]);
}
//+------------------------------------------------------------------+
//| IDW interpolation: scalar target, 3-dimensional argument |
//| NOTE: this function modifies internal temporaries of the IDW |
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
//| parallel model evaluation from the multiple threads, use |
//| IDWTsCalcBuf() with per- thread buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW interpolant built with IDW builder |
//| X0,X1,X2 - argument value |
//| Result: |
//| IDW interpolant S(X0,X1,X2) |
//+------------------------------------------------------------------+
double CIDWInt::IDWCalc3(CIDWModel &s,double x0,double x1,double x2)
{
//--- check
if(!CAp::Assert(s.m_nx==3,__FUNCTION__+": S.NX<>3"))
return(0);
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": X1 is INF or NAN"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": X2 is INF or NAN"))
return(0);
s.m_buffer.m_x.Set(0,x0);
s.m_buffer.m_x.Set(1,x1);
s.m_buffer.m_x.Set(2,x2);
//--- function call
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
//--- return result
return(s.m_buffer.m_y[0]);
}
//+------------------------------------------------------------------+
//| This function calculates values of the IDW model at the given |
//| point. |
//| This is general function which can be used for arbitrary NX |
//| (dimension of the space of arguments) and NY (dimension of the |
//| function itself). However when you have NY=1 you may find more |
//| convenient to use IDWCalc1(), IDWCalc2() or IDWCalc3(). |
//| NOTE: this function modifies internal temporaries of the IDW |
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
//| parallel model evaluation from the multiple threads, use |
//| IDWTsCalcBuf() with per-thread buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is out-parameter and |
//| will be reallocated after call to this function. In|
//| case you want to reuse previously allocated Y, you |
//| may use IDWCalcBuf(), which reallocates Y only when|
//| it is too small. |
//+------------------------------------------------------------------+
void CIDWInt::IDWCalc(CIDWModel &s,CRowDouble &x,CRowDouble &y)
{
y.Resize(0);
//--- function call
IDWTsCalcBuf(s,s.m_buffer,x,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the IDW model at the given |
//| point. |
//| Same as IDWCalc(), but does not reallocate Y when in is large |
//| enough to store function values. |
//| NOTE: this function modifies internal temporaries of the IDW |
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
//| parallel model evaluation from the multiple threads, use |
//| IDWTsCalcBuf() with per-thread buffer object. |
//| INPUT PARAMETERS: |
//| S - IDW model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CIDWInt::IDWCalcBuf(CIDWModel &s,CRowDouble &x,CRowDouble &y)
{
IDWTsCalcBuf(s,s.m_buffer,x,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the IDW model at the given |
//| point, using external buffer object (internal temporaries of IDW |
//| model are not modified). |
//| This function allows to use same IDW model object in different |
//| threads, assuming that different threads use different instances|
//| of the buffer structure. |
//| INPUT PARAMETERS: |
//| S - IDW model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of IDW model with IDWCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CIDWInt::IDWTsCalcBuf(CIDWModel &s,CIDWCalcBuffer &buf,
CRowDouble &x,CRowDouble &y)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
int i=0;
int j=0;
int ew=0;
int k=0;
int layeridx=0;
int npoints=0;
double v=0;
double vv=0;
double f=0;
double p=0;
double r=0;
double eps=0;
double lambdacur=0;
double lambdadecay=0;
double invrdecay=0;
double invr=0;
bool fastcalcpossible=false;
double wf0=0;
double ws0=0;
double wf1=0;
double ws1=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
//--- Avoid spurious compiler warnings
wf0=0;
ws0=0;
wf1=0;
ws1=0;
//--- Allocate output
if(CAp::Len(y)<ny)
y.Resize(ny);
//--- Quick exit for NLayers=0 (no dataset)
if(s.m_nlayers==0)
{
for(j=0; j<ny; j++)
y.Set(j,s.m_globalprior[j]);
return;
}
//--- Textbook Shepard's method
if(s.m_algotype==0)
{
npoints=s.m_npoints;
//--- check
if(!CAp::Assert(npoints>0,__FUNCTION__+": integrity check failed"))
return;
eps=1.0E-50;
ew=nx+ny;
p=s.m_shepardp;
y.Fill(0);
buf.m_tsyw.Fill(eps);
for(i=0; i<npoints; i++)
{
//--- Compute squared distance
v=0;
for(j=0; j<nx; j++)
{
vv=s.m_shepardxy[i*ew+j]-x[j];
v=v+vv*vv;
}
//--- Compute weight (with small regularizing addition)
v=MathPow(v,p*0.5);
v=1/(eps+v);
//--- Accumulate
for(j=0; j<ny; j++)
{
y.Add(j,v*s.m_shepardxy[i*ew+nx+j]);
buf.m_tsyw.Add(j,v);
}
}
for(j=0; j<ny; j++)
y.Set(j,y[j]/buf.m_tsyw[j]+s.m_globalprior[j]);
return;
}
//--- Textbook modified Shepard's method
if(s.m_algotype==1)
{
eps=1.0E-50;
r=s.m_r0;
y.Fill(0);
buf.m_tsyw.Fill(eps);
k=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,x,r,true);
CNearestNeighbor::KDTreeTsQueryResultsXY(s.m_tree,buf.m_requestbuffer,buf.m_tsxy);
CNearestNeighbor::KDTreeTsQueryResultsDistances(s.m_tree,buf.m_requestbuffer,buf.m_tsdist);
for(i=0; i<k; i++)
{
v=buf.m_tsdist[i];
v=(r-v)/(r*v+eps);
v=v*v;
for(j=0; j<ny; j++)
{
y.Add(j,v*buf.m_tsxy.Get(i,nx+j));
buf.m_tsyw.Add(j,v);
}
}
for(j=0; j<ny; j++)
y.Set(j,y[j]/buf.m_tsyw[j]+s.m_globalprior[j]);
return;
}
//--- MSTAB
if(s.m_algotype==2)
{
//--- check
if(!CAp::Assert(m_w0==1.0,__FUNCTION__+": unexpected W0,integrity check failed"))
return;
invrdecay=1.0/s.m_rdecay;
invr=1.0/s.m_r0;
lambdadecay=s.m_lambdadecay;
fastcalcpossible=(ny==1 && s.m_nlayers>=3) && lambdadecay==1.0;
if(fastcalcpossible)
{
//--- Important special case, NY=1, no lambda-decay,
//--- we can perform optimized fast evaluation
wf0=0;
ws0=m_w0;
wf1=0;
ws1=m_w0;
for(j=0; j<s.m_nlayers; j++)
{
buf.m_tsyw.Set(j,0);
buf.m_tsw.Set(j,m_w0);
}
}
else
{
//--- Setup variables for generic evaluation path
for(j=0; j<ny*s.m_nlayers; j++)
buf.m_tsyw.Set(j,0);
for(j=0; j<s.m_nlayers; j++)
buf.m_tsw.Set(j,m_w0);
}
k=CNearestNeighbor::KDTreeTsQueryRNNU(s.m_tree,buf.m_requestbuffer,x,s.m_r0,true);
CNearestNeighbor::KDTreeTsQueryResultsXY(s.m_tree,buf.m_requestbuffer,buf.m_tsxy);
CNearestNeighbor::KDTreeTsQueryResultsDistances(s.m_tree,buf.m_requestbuffer,buf.m_tsdist);
for(i=0; i<k; i++)
{
lambdacur=s.m_lambda0;
vv=buf.m_tsdist[i]*invr;
if(fastcalcpossible)
{
//--- Important special case, fast evaluation possible
v=vv*vv;
v=(1-v)*(1-v)/(v+lambdacur);
f=buf.m_tsxy.Get(i,nx);
wf0=wf0+v*f;
ws0=ws0+v;
vv=vv*invrdecay;
if(vv>=1.0)
continue;
v=vv*vv;
v=(1-v)*(1-v)/(v+lambdacur);
f=buf.m_tsxy.Get(i,nx+1);
wf1=wf1+v*f;
ws1=ws1+v;
vv=vv*invrdecay;
if(vv>=1.0)
continue;
for(layeridx=2; layeridx<s.m_nlayers; layeridx++)
{
if(layeridx==s.m_nlayers-1)
lambdacur=s.m_lambdalast;
v=vv*vv;
v=(1-v)*(1-v)/(v+lambdacur);
f=buf.m_tsxy.Get(i,nx+layeridx);
buf.m_tsyw.Add(layeridx,v*f);
buf.m_tsw.Add(layeridx,v);
vv=vv*invrdecay;
if(vv>=1.0)
break;
}
}
else
{
//--- General case
for(layeridx=0; layeridx<s.m_nlayers; layeridx++)
{
if(layeridx==s.m_nlayers-1)
lambdacur=s.m_lambdalast;
if(vv>=1.0)
break;
v=vv*vv;
v=(1-v)*(1-v)/(v+lambdacur);
for(j=0; j<ny; j++)
{
f=buf.m_tsxy.Get(i,nx+layeridx*ny+j);
buf.m_tsyw.Add(layeridx*ny+j,v*f);
}
buf.m_tsw.Add(layeridx,v);
lambdacur*=lambdadecay;
vv*=invrdecay;
}
}
}
if(fastcalcpossible)
{
//--- Important special case, finalize evaluations
buf.m_tsyw.Set(0,wf0);
buf.m_tsw.Set(0,ws0);
buf.m_tsyw.Set(1,wf1);
buf.m_tsw.Set(1,ws1);
}
for(j=0; j<ny; j++)
y.Set(j,s.m_globalprior[j]);
for(layeridx=0; layeridx<s.m_nlayers; layeridx++)
{
for(j=0; j<ny; j++)
y.Add(j,buf.m_tsyw[layeridx*ny+j]/buf.m_tsw[layeridx]);
}
return;
}
CAp::Assert(false,__FUNCTION__+": unexpected AlgoType");
}
//+------------------------------------------------------------------+
//| This function fits IDW model to the dataset using current IDW |
//| construction algorithm. A model being built and fitting report |
//| are returned. |
//| INPUT PARAMETERS: |
//| State - builder object |
//| OUTPUT PARAMETERS: |
//| Model - an IDW model built with current algorithm |
//| Rep - model fitting report, fields of this structure |
//| contain information about average fitting errors. |
//| NOTE: although IDW-MSTAB algorithm is an interpolation method, |
//| i.e. it tries to fit the model exactly, it can handle |
//| datasets with non-distinct points which can not be fit |
//| exactly; in such cases least-squares fitting is performed. |
//+------------------------------------------------------------------+
void CIDWInt::IDWFit(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep)
{
//--- create variables
int nx=State.m_nx;
int ny=State.m_ny;
int npoints=State.m_npoints;
int i=0;
int i0=0;
int j=0;
int k=0;
int layeridx=0;
int srcidx=0;
double v=0;
double vv=0;
double rcur=0;
double lambdacur=0;
double rss=0;
double tss=0;
//--- Clear report fields
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rep.m_r2=1.0;
//--- Quick exit for empty dataset
if(State.m_npoints==0)
{
model.m_nx=nx;
model.m_ny=ny;
model.m_globalprior=vector<double>::Zeros(ny);
model.m_algotype=0;
model.m_nlayers=0;
model.m_r0=1;
model.m_rdecay=0.5;
model.m_lambda0=0;
model.m_lambdalast=0;
model.m_lambdadecay=1;
model.m_shepardp=2;
model.m_npoints=0;
IDWCreateCalcBuffer(model,model.m_buffer);
return;
}
//--- Compute temporaries which will be required later:
//--- * global mean
//--- check
if(!CAp::Assert(State.m_npoints>0,__FUNCTION__+": integrity check failed"))
return;
State.m_tmpmean=vector<double>::Zeros(ny);
for(i=0; i<npoints; i++)
{
for(j=0; j<ny; j++)
State.m_tmpmean.Add(j,State.m_xy[i*(nx+ny)+nx+j]);
}
State.m_tmpmean/=npoints;
//--- Compute global prior
//--- NOTE: for original Shepard's method it is always mean value
model.m_globalprior=State.m_tmpmean;
if(State.m_algotype!=0)
{
//--- Algorithm is set to one of the "advanced" versions with search
//--- radius which can handle non-mean prior term
if(State.m_priortermtype==0)
{
//--- User-specified prior
model.m_globalprior=State.m_priortermval;
}
if(State.m_priortermtype==3)
{
//--- Zero prior
model.m_globalprior=vector<double>::Zeros(ny);
}
}
//--- Textbook Shepard
if(State.m_algotype==0)
{
//--- Initialize model
model.m_algotype=0;
model.m_nx=nx;
model.m_ny=ny;
model.m_nlayers=1;
model.m_r0=1;
model.m_rdecay=0.5;
model.m_lambda0=0;
model.m_lambdalast=0;
model.m_lambdadecay=1;
model.m_shepardp=State.m_shepardp;
//--- Copy dataset
CApServ::RVectorSetLengthAtLeast(model.m_shepardxy,npoints*(nx+ny));
for(i=0; i<npoints; i++)
{
for(j=0; j<nx; j++)
model.m_shepardxy.Set(i*(nx+ny)+j,State.m_xy[i*(nx+ny)+j]);
for(j=0; j<ny; j++)
model.m_shepardxy.Set(i*(nx+ny)+nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
}
model.m_npoints=npoints;
//--- Prepare internal buffer
//--- Evaluate report fields
IDWCreateCalcBuffer(model,model.m_buffer);
ErrorMetricsViaCalc(State,model,rep);
return;
}
//--- Textbook modified Shepard's method
if(State.m_algotype==1)
{
//--- Initialize model
model.m_algotype=1;
model.m_nx=nx;
model.m_ny=ny;
model.m_nlayers=1;
model.m_r0=State.m_r0;
model.m_rdecay=1;
model.m_lambda0=0;
model.m_lambdalast=0;
model.m_lambdadecay=1;
model.m_shepardp=0;
//--- Build kd-tree search structure
CApServ::RMatrixSetLengthAtLeast(State.m_tmpxy,npoints,nx+ny);
for(i=0; i<npoints; i++)
{
for(j=0; j<nx; j++)
State.m_tmpxy.Set(i,j,State.m_xy[i*(nx+ny)+j]);
for(j=0; j<ny; j++)
State.m_tmpxy.Set(i,nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
}
CNearestNeighbor::KDTreeBuild(State.m_tmpxy,npoints,nx,ny,2,model.m_tree);
//--- Prepare internal buffer
//--- Evaluate report fields
IDWCreateCalcBuffer(model,model.m_buffer);
ErrorMetricsViaCalc(State,model,rep);
return;
}
//--- MSTAB algorithm
if(State.m_algotype==2)
{
//--- check
if(!CAp::Assert(State.m_nlayers>=1,__FUNCTION__+": integrity check failed"))
return;
//--- Initialize model
model.m_algotype=2;
model.m_nx=nx;
model.m_ny=ny;
model.m_nlayers=State.m_nlayers;
model.m_r0=State.m_r0;
model.m_rdecay=0.5;
model.m_lambda0=State.m_lambda0;
model.m_lambdadecay=1.0;
model.m_lambdalast=m_meps;
model.m_shepardp=0;
//--- Build kd-tree search structure,
//--- prepare input residuals for the first layer of the model
CApServ::RMatrixSetLengthAtLeast(State.m_tmpxy,npoints,nx);
CApServ::RMatrixSetLengthAtLeast(State.m_tmplayers,npoints,nx+ny*(State.m_nlayers+1));
CApServ::IVectorSetLengthAtLeast(State.m_tmptags,npoints);
for(i=0; i<npoints; i++)
{
for(j=0; j<nx; j++)
{
v=State.m_xy[i*(nx+ny)+j];
State.m_tmpxy.Set(i,j,v);
State.m_tmplayers.Set(i,j,v);
}
State.m_tmptags.Set(i,i);
for(j=0; j<ny; j++)
State.m_tmplayers.Set(i,nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
}
CNearestNeighbor::KDTreeBuildTagged(State.m_tmpxy,State.m_tmptags,npoints,nx,0,2,State.m_tmptree);
//--- Iteratively build layer by layer
CApServ::RVectorSetLengthAtLeast(State.m_tmpx,nx);
CApServ::RVectorSetLengthAtLeast(State.m_tmpwy,ny);
CApServ::RVectorSetLengthAtLeast(State.m_tmpw,ny);
for(layeridx=0; layeridx<State.m_nlayers; layeridx++)
{
//--- Determine layer metrics
rcur=model.m_r0*MathPow(model.m_rdecay,layeridx);
lambdacur=model.m_lambda0*MathPow(model.m_lambdadecay,layeridx);
if(layeridx==State.m_nlayers-1)
lambdacur=model.m_lambdalast;
//--- For each point compute residual from fitting with current layer
for(i=0; i<npoints; i++)
{
for(j=0; j<nx; j++)
State.m_tmpx.Set(j,State.m_tmplayers.Get(i,j));
k=CNearestNeighbor::KDTreeQueryRNN(State.m_tmptree,State.m_tmpx,rcur,true);
CNearestNeighbor::KDTreeQueryResultsTags(State.m_tmptree,State.m_tmptags);
CNearestNeighbor::KDTreeQueryResultsDistances(State.m_tmptree,State.m_tmpdist);
State.m_tmpwy.Fill(0);
State.m_tmpw.Fill(m_w0);
for(i0=0; i0<k; i0++)
{
vv=State.m_tmpdist[i0]/rcur;
vv=vv*vv;
v=(1-vv)*(1-vv)/(vv+lambdacur);
srcidx=State.m_tmptags[i0];
for(j=0; j<ny; j++)
{
State.m_tmpwy.Add(j,v*State.m_tmplayers.Get(srcidx,nx+layeridx*ny+j));
State.m_tmpw.Add(j,v);
}
}
for(j=0; j<ny; j++)
{
v=State.m_tmplayers.Get(i,nx+layeridx*ny+j);
State.m_tmplayers.Set(i,nx+(layeridx+1)*ny+j,v-State.m_tmpwy[j]/State.m_tmpw[j]);
}
}
}
CNearestNeighbor::KDTreeBuild(State.m_tmplayers,npoints,nx,ny*State.m_nlayers,2,model.m_tree);
//--- Evaluate report fields
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rss=0;
tss=0;
for(i=0; i<npoints; i++)
{
for(j=0; j<ny; j++)
{
v=MathAbs(State.m_tmplayers.Get(i,nx+State.m_nlayers*ny+j));
rep.m_rmserror=rep.m_rmserror+v*v;
rep.m_avgerror=rep.m_avgerror+v;
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
rss=rss+v*v;
tss=tss+CMath::Sqr(State.m_xy[i*(nx+ny)+nx+j]-State.m_tmpmean[j]);
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/(npoints*ny));
rep.m_avgerror=rep.m_avgerror/(npoints*ny);
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
//--- Prepare internal buffer
IDWCreateCalcBuffer(model,model.m_buffer);
return;
}
//--- Unknown algorithm
CAp::Assert(false,__FUNCTION__+": integrity check failed,unexpected algorithm");
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CIDWInt::IDWAlloc(CSerializer &s,CIDWModel &model)
{
//--- Header
s.Alloc_Entry();
//--- Algorithm type and fields which are set for all algorithms
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
CApServ::AllocRealArray(s,model.m_globalprior,-1);
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
//--- Algorithm-specific fields
bool processed=false;
if(model.m_algotype==0)
{
s.Alloc_Entry();
CApServ::AllocRealArray(s,model.m_shepardxy,-1);
processed=true;
}
if(model.m_algotype>0)
{
CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
processed=true;
}
CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization");
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CIDWInt::IDWSerialize(CSerializer &s,CIDWModel &model)
{
//--- Header
s.Serialize_Int(CSCodes::GetIDWSerializationCode());
//--- Algorithm type and fields which are set for all algorithms
s.Serialize_Int(model.m_algotype);
s.Serialize_Int(model.m_nx);
s.Serialize_Int(model.m_ny);
CApServ::SerializeRealArray(s,model.m_globalprior,-1);
s.Serialize_Int(model.m_nlayers);
s.Serialize_Double(model.m_r0);
s.Serialize_Double(model.m_rdecay);
s.Serialize_Double(model.m_lambda0);
s.Serialize_Double(model.m_lambdalast);
s.Serialize_Double(model.m_lambdadecay);
s.Serialize_Double(model.m_shepardp);
//--- Algorithm-specific fields
bool processed=false;
if(model.m_algotype==0)
{
s.Serialize_Int(model.m_npoints);
CApServ::SerializeRealArray(s,model.m_shepardxy,-1);
processed=true;
}
if(model.m_algotype>0)
{
CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
processed=true;
}
CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization");
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CIDWInt::IDWUnserialize(CSerializer &s,CIDWModel &model)
{
//--- Header
int scode=s.Unserialize_Int();
//--- check
if(!CAp::Assert(scode==CSCodes::GetIDWSerializationCode(),__FUNCTION__+": stream header corrupted"))
return;
//--- Algorithm type and fields which are set for all algorithms
model.m_algotype=s.Unserialize_Int();
model.m_nx=s.Unserialize_Int();
model.m_ny=s.Unserialize_Int();
CApServ::UnserializeRealArray(s,model.m_globalprior);
model.m_nlayers=s.Unserialize_Int();
model.m_r0=s.Unserialize_Double();
model.m_rdecay=s.Unserialize_Double();
model.m_lambda0=s.Unserialize_Double();
model.m_lambdalast=s.Unserialize_Double();
model.m_lambdadecay=s.Unserialize_Double();
model.m_shepardp=s.Unserialize_Double();
//
//--- Algorithm-specific fields
//
bool processed=false;
if(model.m_algotype==0)
{
model.m_npoints=s.Unserialize_Int();
CApServ::UnserializeRealArray(s,model.m_shepardxy);
processed=true;
}
if(model.m_algotype>0)
{
CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
processed=true;
}
//--- check
if(!CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization"))
return;
//--- Temporary buffers
IDWCreateCalcBuffer(model,model.m_buffer);
}
//+------------------------------------------------------------------+
//| This function evaluates error metrics for the model using |
//| IDWTsCalcBuf() to calculate model at each point. |
//| NOTE: modern IDW algorithms (MSTAB, MSMOOTH) can generate |
//| residuals during model construction, so they do not need this |
//| function in order to evaluate error metrics. |
//| Following fields of Rep are filled: |
//| * rep.m_rmserror |
//| * rep.m_avgerror |
//| * rep.m_maxerror |
//| * rep.m_r2 |
//+------------------------------------------------------------------+
void CIDWInt::ErrorMetricsViaCalc(CIDWBuilder &State,
CIDWModel &model,
CIDWReport &rep)
{
//--- create variables
int npoints=State.m_npoints;
int nx=State.m_nx;
int ny=State.m_ny;
double v=0;
double vv=0;
double rss=0;
double tss=0;
//--- quick exit
if(npoints==0)
{
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rep.m_r2=1;
return;
}
//--- initialization
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rss=0;
tss=0;
for(int i=0; i<npoints; i++)
{
for(int j=0; j<nx; j++)
model.m_buffer.m_x.Set(j,State.m_xy[i*(nx+ny)+j]);
//--- function call
IDWTsCalcBuf(model,model.m_buffer,model.m_buffer.m_x,model.m_buffer.m_y);
for(int j=0; j<ny; j++)
{
vv=State.m_xy[i*(nx+ny)+nx+j];
v=MathAbs(vv-model.m_buffer.m_y[j]);
rep.m_rmserror=rep.m_rmserror+v*v;
rep.m_avgerror=rep.m_avgerror+v;
rep.m_maxerror=MathMax(rep.m_maxerror,v);
rss=rss+v*v;
tss=tss+CMath::Sqr(vv-State.m_tmpmean[j]);
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/(npoints*ny));
rep.m_avgerror=rep.m_avgerror/(npoints*ny);
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
}
//+------------------------------------------------------------------+
//| Barycentric interpolant. |
//+------------------------------------------------------------------+
class CBarycentricInterpolant
{
public:
//--- variables
int m_n;
double m_sy;
//--- arrays
double m_x[];
double m_y[];
double m_w[];
//--- constructor, destructor
CBarycentricInterpolant(void) { ZeroMemory(this); }
~CBarycentricInterpolant(void) {}
//--- copy
void Copy(CBarycentricInterpolant &obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CBarycentricInterpolant::Copy(CBarycentricInterpolant &obj)
{
//--- copy variables
m_n=obj.m_n;
m_sy=obj.m_sy;
//--- copy arrays
ArrayCopy(m_x,obj.m_x);
ArrayCopy(m_y,obj.m_y);
ArrayCopy(m_w,obj.m_w);
}
//+------------------------------------------------------------------+
//| Barycentric interpolant. |
//+------------------------------------------------------------------+
class CBarycentricInterpolantShell
{
private:
CBarycentricInterpolant m_innerobj;
public:
//--- constructor, destructor
CBarycentricInterpolantShell(void) {}
CBarycentricInterpolantShell(CBarycentricInterpolant &obj) { m_innerobj.Copy(obj); }
~CBarycentricInterpolantShell(void) {}
//--- method
CBarycentricInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Rational interpolation |
//+------------------------------------------------------------------+
class CRatInt
{
public:
static double BarycentricCalc(CBarycentricInterpolant &b,const double t);
static void BarycentricDiff1(CBarycentricInterpolant &b,double t,double &f,double &df);
static void BarycentricDiff2(CBarycentricInterpolant &b,const double t,double &f,double &df,double &d2f);
static void BarycentricLinTransX(CBarycentricInterpolant &b,const double ca,const double cb);
static void BarycentricLinTransY(CBarycentricInterpolant &b,const double ca,const double cb);
static void BarycentricUnpack(CBarycentricInterpolant &b,int &n,double &x[],double &y[],double &w[]);
static void BarycentricBuildXYW(double &x[],double &y[],double &w[],const int n,CBarycentricInterpolant &b);
static void BarycentricBuildFloaterHormann(double &x[],double &y[],const int n,int d,CBarycentricInterpolant &b);
static void BarycentricCopy(CBarycentricInterpolant &b,CBarycentricInterpolant &b2);
private:
static void BarycentricNormalize(CBarycentricInterpolant &b);
};
//+------------------------------------------------------------------+
//| Rational interpolation using barycentric formula |
//| F(t)=SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))|
//| Input parameters: |
//| B - barycentric interpolant built with one of model |
//| building subroutines. |
//| T - interpolation point |
//| Result: |
//| barycentric interpolant F(t) |
//+------------------------------------------------------------------+
double CRatInt::BarycentricCalc(CBarycentricInterpolant &b,const double t)
{
//--- create variables
double s1=0;
double s2=0;
double s=0;
double v=0;
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
return(EMPTY_VALUE);
//--- special case: NaN
if(CInfOrNaN::IsNaN(t))
return(CInfOrNaN::NaN());
//--- special case: N=1
if(b.m_n==1)
return(b.m_sy*b.m_y[0]);
//--- Here we assume that task is normalized,i.m_e.:
//--- 1. abs(Y[i])<=1
//--- 2. abs(W[i])<=1
//--- 3. X[] is ordered
s=MathAbs(t-b.m_x[0]);
for(int i=0; i<=b.m_n-1; i++)
{
v=b.m_x[i];
//--- check
if(v==(double)(t))
return(b.m_sy*b.m_y[i]);
v=MathAbs(t-v);
//--- check
if(v<s)
s=v;
}
//--- change values
s1=0;
s2=0;
//--- calculation
for(int i=0; i<=b.m_n-1; i++)
{
v=s/(t-b.m_x[i]);
v=v*b.m_w[i];
s1=s1+v*b.m_y[i];
s2=s2+v;
}
//--- return result
return(b.m_sy*s1/s2);
}
//+------------------------------------------------------------------+
//| Differentiation of barycentric interpolant: first derivative. |
//| Algorithm used in this subroutine is very robust and should not |
//| fail until provided with values too close to MaxRealNumber |
//| (usually MaxRealNumber/N or greater will overflow). |
//| INPUT PARAMETERS: |
//| B - barycentric interpolant built with one of model |
//| building subroutines. |
//| T - interpolation point |
//| OUTPUT PARAMETERS: |
//| F - barycentric interpolant at T |
//| DF - first derivative |
//+------------------------------------------------------------------+
void CRatInt::BarycentricDiff1(CBarycentricInterpolant &b,double t,
double &f,double &df)
{
//--- create variables
double v=0;
double vv=0;
int i=0;
int k=0;
double n0=0;
double n1=0;
double d0=0;
double d1=0;
double s0=0;
double s1=0;
double xk=0;
double xi=0;
double xmin=0;
double xmax=0;
double xscale1=0;
double xoffs1=0;
double xscale2=0;
double xoffs2=0;
double xprev=0;
//--- initialization
f=0;
df=0;
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
return;
//--- special case: NaN
if(CInfOrNaN::IsNaN(t))
{
//--- change values
f=CInfOrNaN::NaN();
df=CInfOrNaN::NaN();
//--- exit the function
return;
}
//--- special case: N=1
if(b.m_n==1)
{
//--- change values
f=b.m_sy*b.m_y[0];
df=0;
//--- exit the function
return;
}
//--- check
if(b.m_sy==0.0)
{
//--- change values
f=0;
df=0;
//--- exit the function
return;
}
//--- check
if(!CAp::Assert(b.m_sy>0.0,__FUNCTION__+": internal error"))
return;
//--- We assume than N>1 and B.SY>0. Find:
//--- 1. pivot point (X[i] closest to T)
//--- 2. width of interval containing X[i]
v=MathAbs(b.m_x[0]-t);
k=0;
xmin=b.m_x[0];
xmax=b.m_x[0];
//--- calculation
for(i=1; i<=b.m_n-1; i++)
{
vv=b.m_x[i];
//--- check
if(MathAbs(vv-t)<v)
{
v=MathAbs(vv-t);
k=i;
}
//--- change values
xmin=MathMin(xmin,vv);
xmax=MathMax(xmax,vv);
}
//--- pivot point found,calculate dNumerator and dDenominator
xscale1=1/(xmax-xmin);
xoffs1=-(xmin/(xmax-xmin))+1;
xscale2=2;
xoffs2=-3;
t=t*xscale1+xoffs1;
t=t*xscale2+xoffs2;
xk=b.m_x[k];
xk=xk*xscale1+xoffs1;
xk=xk*xscale2+xoffs2;
v=t-xk;
n0=0;
n1=0;
d0=0;
d1=0;
xprev=-2;
//--- calculation
for(i=0; i<b.m_n; i++)
{
//--- change values
xi=b.m_x[i];
xi=xi*xscale1+xoffs1;
xi=xi*xscale2+xoffs2;
//--- check
if(!CAp::Assert(xi>xprev,__FUNCTION__+": points are too close!"))
return;
xprev=xi;
//--- check
if(i!=k)
{
vv=CMath::Sqr(t-xi);
s0=(t-xk)/(t-xi);
s1=(xk-xi)/vv;
}
else
{
s0=1;
s1=0;
}
//--- change values
vv=b.m_w[i]*b.m_y[i];
n0=n0+s0*vv;
n1=n1+s1*vv;
vv=b.m_w[i];
d0=d0+s0*vv;
d1=d1+s1*vv;
}
//--- change values
f=b.m_sy*n0/d0;
df=(n1*d0-n0*d1)/CMath::Sqr(d0);
//--- check
if(df!=0.0)
df=MathSign(df)*MathExp(MathLog(MathAbs(df))+MathLog(b.m_sy)+MathLog(xscale1)+MathLog(xscale2));
}
//+------------------------------------------------------------------+
//| Differentiation of barycentric interpolant: first/second |
//| derivatives. |
//| INPUT PARAMETERS: |
//| B - barycentric interpolant built with one of model |
//| building subroutines. |
//| T - interpolation point |
//| OUTPUT PARAMETERS: |
//| F - barycentric interpolant at T |
//| DF - first derivative |
//| D2F - second derivative |
//| NOTE: this algorithm may fail due to overflow/underflor if used |
//| on data whose values are close to MaxRealNumber or MinRealNumber.|
//| Use more robust BarycentricDiff1() subroutine in such cases. |
//+------------------------------------------------------------------+
void CRatInt::BarycentricDiff2(CBarycentricInterpolant &b,const double t,
double &f,double &df,double &d2f)
{
//--- create variables
double v=0;
double vv=0;
int k=0;
double n0=0;
double n1=0;
double n2=0;
double d0=0;
double d1=0;
double d2=0;
double s0=0;
double s1=0;
double s2=0;
double xk=0;
double xi=0;
//--- initialization
f=0;
df=0;
d2f=0;
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
return;
//--- special case: NaN
if(CInfOrNaN::IsNaN(t))
{
//--- change values
f=CInfOrNaN::NaN();
df=CInfOrNaN::NaN();
d2f=CInfOrNaN::NaN();
//--- exit the function
return;
}
//--- special case: N=1
if(b.m_n==1)
{
//--- change values
f=b.m_sy*b.m_y[0];
df=0;
d2f=0;
//--- exit the function
return;
}
//--- check
if(b.m_sy==0.0)
{
//--- change values
f=0;
df=0;
d2f=0;
//--- exit the function
return;
}
//--- We assume than N>1 and B.SY>0. Find:
//--- 1. pivot point (X[i] closest to T)
//--- 2. width of interval containing X[i]
if(!CAp::Assert(b.m_sy>0.0,__FUNCTION__+": internal error"))
return;
//--- change values
f=0;
df=0;
d2f=0;
v=MathAbs(b.m_x[0]-t);
k=0;
for(int i=1; i<=b.m_n-1; i++)
{
vv=b.m_x[i];
//--- check
if(MathAbs(vv-t)<v)
{
v=MathAbs(vv-t);
k=i;
}
}
//--- pivot point found, calculate dNumerator and dDenominator
xk=b.m_x[k];
v=t-xk;
n0=0;
n1=0;
n2=0;
d0=0;
d1=0;
d2=0;
//--- calculation
for(int i=0; i<=b.m_n-1; i++)
{
//--- check
if(i!=k)
{
xi=b.m_x[i];
vv=CMath::Sqr(t-xi);
s0=(t-xk)/(t-xi);
s1=(xk-xi)/vv;
s2=-(2*(xk-xi)/(vv*(t-xi)));
}
else
{
s0=1;
s1=0;
s2=0;
}
//--- change values
vv=b.m_w[i]*b.m_y[i];
n0=n0+s0*vv;
n1=n1+s1*vv;
n2=n2+s2*vv;
vv=b.m_w[i];
d0=d0+s0*vv;
d1=d1+s1*vv;
d2=d2+s2*vv;
}
//--- change values
f=b.m_sy*n0/d0;
df=b.m_sy*(n1*d0-n0*d1)/CMath::Sqr(d0);
d2f=b.m_sy*((n2*d0-n0*d2)*CMath::Sqr(d0)-(n1*d0-n0*d1)*2*d0*d1)/CMath::Sqr(CMath::Sqr(d0));
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the argument. |
//| INPUT PARAMETERS: |
//| B - rational interpolant in barycentric form |
//| CA, CB - transformation coefficients: x = CA*t + CB |
//| OUTPUT PARAMETERS: |
//| B - transformed interpolant with X replaced by T |
//+------------------------------------------------------------------+
void CRatInt::BarycentricLinTransX(CBarycentricInterpolant &b,
const double ca,const double cb)
{
double v=0;
//--- special case,replace by constant F(CB)
if(ca==0.0)
{
b.m_sy=BarycentricCalc(b,cb);
v=1;
for(int i=0; i<=b.m_n-1; i++)
{
b.m_y[i]=1;
b.m_w[i]=v;
v=-v;
}
//--- exit the function
return;
}
//--- general case: CA<>0
for(int i=0; i<=b.m_n-1; i++)
b.m_x[i]=(b.m_x[i]-cb)/ca;
//--- check
if(ca<0.0)
{
for(int i=0; i<=b.m_n-1; i++)
{
//--- check
if(i<b.m_n-1-i)
{
//--- change values
int j=b.m_n-1-i;
v=b.m_x[i];
b.m_x[i]=b.m_x[j];
b.m_x[j]=v;
v=b.m_y[i];
b.m_y[i]=b.m_y[j];
b.m_y[j]=v;
v=b.m_w[i];
b.m_w[i]=b.m_w[j];
b.m_w[j]=v;
}
else
break;
}
}
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the barycentric|
//| interpolant. |
//| INPUT PARAMETERS: |
//| B - rational interpolant in barycentric form |
//| CA, CB - transformation coefficients: B2(x) = CA*B(x) + CB|
//| OUTPUT PARAMETERS: |
//| B - transformed interpolant |
//+------------------------------------------------------------------+
void CRatInt::BarycentricLinTransY(CBarycentricInterpolant &b,
const double ca,const double cb)
{
double v=0;
//--- calculation
for(int i=0; i<=b.m_n-1; i++)
b.m_y[i]=ca*b.m_sy*b.m_y[i]+cb;
//--- change value
b.m_sy=0;
for(int i=0; i<=b.m_n-1; i++)
b.m_sy=MathMax(b.m_sy,MathAbs(b.m_y[i]));
//--- check
if(b.m_sy>0.0)
{
v=1/b.m_sy;
//--- calculation
for(int i_=0; i_<=b.m_n-1; i_++)
b.m_y[i_]=v*b.m_y[i_];
}
}
//+------------------------------------------------------------------+
//| Extracts X/Y/W arrays from rational interpolant |
//| INPUT PARAMETERS: |
//| B - barycentric interpolant |
//| OUTPUT PARAMETERS: |
//| N - nodes count, N>0 |
//| X - interpolation nodes, array[0..N-1] |
//| F - function values, array[0..N-1] |
//| W - barycentric weights, array[0..N-1] |
//+------------------------------------------------------------------+
void CRatInt::BarycentricUnpack(CBarycentricInterpolant &b,int &n,
double &x[],double &y[],double &w[])
{
double v=0;
//--- initialization
n=b.m_n;
//--- allocation
ArrayResize(x,n);
ArrayResize(y,n);
ArrayResize(w,n);
//--- initialization
v=b.m_sy;
//--- copy
for(int i_=0; i_<n; i_++)
x[i_]=b.m_x[i_];
for(int i_=0; i_<n; i_++)
y[i_]=v*b.m_y[i_];
for(int i_=0; i_<n; i_++)
w[i_]=b.m_w[i_];
}
//+------------------------------------------------------------------+
//| Rational interpolant from X/Y/W arrays |
//| F(t)=SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))|
//| INPUT PARAMETERS: |
//| X - interpolation nodes, array[0..N-1] |
//| F - function values, array[0..N-1] |
//| W - barycentric weights, array[0..N-1] |
//| N - nodes count, N>0 |
//| OUTPUT PARAMETERS: |
//| B - barycentric interpolant built from (X, Y, W) |
//+------------------------------------------------------------------+
void CRatInt::BarycentricBuildXYW(double &x[],double &y[],double &w[],
const int n,CBarycentricInterpolant &b)
{
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": incorrect N!"))
return;
//--- fill X/Y/W
ArrayResize(b.m_x,n);
ArrayResize(b.m_y,n);
ArrayResize(b.m_w,n);
//--- copy
for(int i=0; i<n; i++)
{
b.m_x[i]=x[i];
b.m_y[i]=y[i];
b.m_w[i]=w[i];
}
b.m_n=n;
//--- Normalize
BarycentricNormalize(b);
}
//+------------------------------------------------------------------+
//| Rational interpolant without poles |
//| The subroutine constructs the rational interpolating function |
//| without real poles (see 'Barycentric rational interpolation with |
//| no poles and high rates of approximation', Michael S. Floater. |
//| and Kai Hormann, for more information on this subject). |
//| Input parameters: |
//| X - interpolation nodes, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| N - number of nodes, N>0. |
//| D - order of the interpolation scheme, 0 <= D <= N-1. |
//| D<0 will cause an error. |
//| D>=N it will be replaced with D=N-1. |
//| if you don't know what D to choose, use small value |
//| about 3-5. |
//| Output parameters: |
//| B - barycentric interpolant. |
//| Note: |
//| this algorithm always succeeds and calculates the weights |
//| with close to machine precision. |
//+------------------------------------------------------------------+
void CRatInt::BarycentricBuildFloaterHormann(double &x[],double &y[],
const int n,int d,
CBarycentricInterpolant &b)
{
//--- create variables
double s0=0;
double s=0;
double v=0;
int i=0;
int j=0;
int k=0;
int i_=0;
//--- create arrays
int perm[];
double wtemp[];
double sortrbuf[];
double sortrbuf2[];
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(d>=0,__FUNCTION__+": incorrect D!"))
return;
//--- Prepare
if(d>n-1)
d=n-1;
b.m_n=n;
//--- special case: N=1
if(n==1)
{
//--- allocation
ArrayResize(b.m_x,n);
ArrayResize(b.m_y,n);
ArrayResize(b.m_w,n);
//--- change values
b.m_x[0]=x[0];
b.m_y[0]=y[0];
b.m_w[0]=1;
//--- function call
BarycentricNormalize(b);
//--- exit the function
return;
}
//--- Fill X/Y
ArrayResize(b.m_x,n);
ArrayResize(b.m_y,n);
//--- copy
for(i_=0; i_<n; i_++)
b.m_x[i_]=x[i_];
for(i_=0; i_<n; i_++)
b.m_y[i_]=y[i_];
//--- function call
CTSort::TagSortFastR(b.m_x,b.m_y,sortrbuf,sortrbuf2,n);
//--- Calculate Wk
ArrayResize(b.m_w,n);
s0=1;
for(k=1; k<=d; k++)
s0=-s0;
//--- calculation
for(k=0; k<n; k++)
{
//--- Wk
s=0;
for(i=(int)(MathMax(k-d,0)); i<=MathMin(k,n-1-d); i++)
{
v=1;
for(j=i; j<=i+d; j++)
{
//--- check
if(j!=k)
v=v/MathAbs(b.m_x[k]-b.m_x[j]);
}
s=s+v;
}
b.m_w[k]=s0*s;
//--- Next S0
s0=-s0;
}
//--- Normalize
BarycentricNormalize(b);
}
//+------------------------------------------------------------------+
//| Copying of the barycentric interpolant (for internal use only) |
//| INPUT PARAMETERS: |
//| B - barycentric interpolant |
//| OUTPUT PARAMETERS: |
//| B2 - copy(B1) |
//+------------------------------------------------------------------+
void CRatInt::BarycentricCopy(CBarycentricInterpolant &b,
CBarycentricInterpolant &b2)
{
b2.Copy(b);
}
//+------------------------------------------------------------------+
//| Normalization of barycentric interpolant: |
//| * B.N, B.X, B.Y and B.W are initialized |
//| * B.SY is NOT initialized |
//| * Y[] is normalized, scaling coefficient is stored in B.SY |
//| * W[] is normalized, no scaling coefficient is stored |
//| * X[] is sorted |
//| Internal subroutine. |
//+------------------------------------------------------------------+
void CRatInt::BarycentricNormalize(CBarycentricInterpolant &b)
{
//--- create variables
int j2=0;
double v=0;
//--- create arrays
int p1[];
int p2[];
//--- Normalize task: |Y|<=1,|W|<=1,sort X[]
b.m_sy=0;
for(int i=0; i<=b.m_n-1; i++)
b.m_sy=MathMax(b.m_sy,MathAbs(b.m_y[i]));
//--- check
if(b.m_sy>0.0 && MathAbs(b.m_sy-1)>10*CMath::m_machineepsilon)
{
v=1/b.m_sy;
for(int i_=0; i_<=b.m_n-1; i_++)
b.m_y[i_]=v*b.m_y[i_];
}
//--- change value
v=0;
for(int i=0; i<=b.m_n-1; i++)
v=MathMax(v,MathAbs(b.m_w[i]));
//--- check
if(v>0.0 && MathAbs(v-1)>10*CMath::m_machineepsilon)
{
v=1/v;
for(int i_=0; i_<=b.m_n-1; i_++)
b.m_w[i_]=v*b.m_w[i_];
}
for(int i=0; i<=b.m_n-2; i++)
{
//--- check
if(b.m_x[i+1]<b.m_x[i])
{
//--- function call
CTSort::TagSort(b.m_x,b.m_n,p1,p2);
//--- calculation
for(int j=0; j<=b.m_n-1; j++)
{
j2=p2[j];
v=b.m_y[j];
b.m_y[j]=b.m_y[j2];
b.m_y[j2]=v;
v=b.m_w[j];
b.m_w[j]=b.m_w[j2];
b.m_w[j2]=v;
}
break;
}
}
}
//+------------------------------------------------------------------+
//| Polynomial interpolant |
//+------------------------------------------------------------------+
class CPolInt
{
public:
static void PolynomialBar2Cheb(CBarycentricInterpolant &p,const double a,const double b,double &t[]);
static void PolynomialCheb2Bar(double &t[],const int n,const double a,const double b,CBarycentricInterpolant &p);
static void PolynomialBar2Pow(CBarycentricInterpolant &p,const double c,const double s,double &a[]);
static void PolynomialPow2Bar(double &a[],const int n,const double c,const double s,CBarycentricInterpolant &p);
static void PolynomialBuild(double &cx[],double &cy[],const int n,CBarycentricInterpolant &p);
static void PolynomialBuildEqDist(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
static void PolynomialBuildCheb1(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
static void PolynomialBuildCheb2(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
static double PolynomialCalcEqDist(const double a,const double b,double &f[],const int n,const double t);
static double PolynomialCalcCheb1(const double a,const double b,double &f[],const int n,double t);
static double PolynomialCalcCheb2(const double a,const double b,double &f[],const int n,double t);
};
//+------------------------------------------------------------------+
//| Conversion from barycentric representation to Chebyshev basis. |
//| This function has O(N^2) complexity. |
//| INPUT PARAMETERS: |
//| P - polynomial in barycentric form |
//| A,B - base interval for Chebyshev polynomials (see below) |
//| A<>B |
//| OUTPUT PARAMETERS |
//| T - coefficients of Chebyshev representation; |
//| P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N-1 }, |
//| where Ti - I-th Chebyshev polynomial. |
//| NOTES: |
//| barycentric interpolant passed as P may be either polynomial |
//| obtained from polynomial interpolation/ fitting or rational |
//| function which is NOT polynomial. We can't distinguish |
//| between these two cases, and this algorithm just tries to |
//| work assuming that P IS a polynomial. If not, algorithm will |
//| return results, but they won't have any meaning. |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBar2Cheb(CBarycentricInterpolant &p,
const double a,const double b,
double &t[])
{
double v=0;
//--- create arrays
double vp[];
double vx[];
double tk[];
double tk1[];
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is not finite!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is not finite!"))
return;
//--- check
if(!CAp::Assert(a!=b,__FUNCTION__+": A=B!"))
return;
//--- check
if(!CAp::Assert(p.m_n>0,__FUNCTION__+": P is not correctly initialized barycentric interpolant!"))
return;
//--- Calculate function values on a Chebyshev grid
ArrayResize(vp,p.m_n);
ArrayResize(vx,p.m_n);
for(int i=0; i<=p.m_n-1; i++)
{
vx[i]=MathCos(M_PI*(i+0.5)/p.m_n);
vp[i]=CRatInt::BarycentricCalc(p,0.5*(vx[i]+1)*(b-a)+a);
}
//--- T[0]
ArrayResize(t,p.m_n);
v=0;
for(int i=0; i<=p.m_n-1; i++)
v=v+vp[i];
t[0]=v/p.m_n;
//--- other T's.
//--- NOTES:
//--- 1. TK stores T{k} on VX,TK1 stores T{k-1} on VX
//--- 2. we can do same calculations with fast DCT,but it
//--- * adds dependencies
//--- * still leaves us with O(N^2) algorithm because
//--- preparation of function values is O(N^2) process
if(p.m_n>1)
{
//--- allocation
ArrayResize(tk,p.m_n);
ArrayResize(tk1,p.m_n);
for(int i=0; i<=p.m_n-1; i++)
{
tk[i]=vx[i];
tk1[i]=1;
}
//--- calculation
for(int k=1; k<=p.m_n-1; k++)
{
//--- calculate discrete product of function vector and TK
v=0.0;
for(int i_=0; i_<=p.m_n-1; i_++)
v+=tk[i_]*vp[i_];
t[k]=v/(0.5*p.m_n);
//--- Update TK and TK1
for(int i=0; i<=p.m_n-1; i++)
{
v=2*vx[i]*tk[i]-tk1[i];
tk1[i]=tk[i];
tk[i]=v;
}
}
}
}
//+------------------------------------------------------------------+
//| Conversion from Chebyshev basis to barycentric representation. |
//| This function has O(N^2) complexity. |
//| INPUT PARAMETERS: |
//| T - coefficients of Chebyshev representation; |
//| P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N }, |
//| where Ti - I-th Chebyshev polynomial. |
//| N - number of coefficients: |
//| * if given, only leading N elements of T are used |
//| * if not given, automatically determined from size |
//| of T |
//| A,B - base interval for Chebyshev polynomials (see above) |
//| A<B |
//| OUTPUT PARAMETERS |
//| P - polynomial in barycentric form |
//+------------------------------------------------------------------+
void CPolInt::PolynomialCheb2Bar(double &t[],const int n,const double a,
const double b,CBarycentricInterpolant &p)
{
//--- create variables
double tk=0;
double tk1=0;
double vx=0;
double vy=0;
double v=0;
//--- create array
double y[];
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is not finite!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is not finite!"))
return;
//--- check
if(!CAp::Assert(a!=b,__FUNCTION__+": A=B!"))
return;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
return;
//--- check
if(!CAp::Assert(CAp::Len(t)>=n,__FUNCTION__+": Length(T)<N"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(t,n),__FUNCTION__+": T[] contains INF or NAN"))
return;
//--- Calculate function values on a Chebyshev grid spanning [-1,+1]
ArrayResize(y,n);
for(int i=0; i<n; i++)
{
//--- Calculate value on a grid spanning [-1,+1]
vx=MathCos(M_PI*(i+0.5)/n);
vy=t[0];
tk1=1;
tk=vx;
//--- change values
for(int k=1; k<n; k++)
{
vy=vy+t[k]*tk;
v=2*vx*tk-tk1;
tk1=tk;
tk=v;
}
y[i]=vy;
}
//--- Build barycentric interpolant,map grid from [-1,+1] to [A,B]
PolynomialBuildCheb1(a,b,y,n,p);
}
//+------------------------------------------------------------------+
//| Conversion from barycentric representation to power basis. |
//| This function has O(N^2) complexity. |
//| INPUT PARAMETERS: |
//| P - polynomial in barycentric form |
//| C - offset (see below); 0.0 is used as default value. |
//| S - scale (see below); 1.0 is used as default value. |
//| S<>0. |
//| OUTPUT PARAMETERS |
//| A - coefficients, |
//| P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 } |
//| N - number of coefficients (polynomial degree plus 1) |
//| NOTES: |
//| 1. this function accepts offset and scale, which can be set to |
//| improve numerical properties of polynomial. For example, if |
//| P was obtained as result of interpolation on [-1,+1], you can|
//| set C=0 and S=1 and represent P as sum of 1, x, x^2, x^3 and |
//| so on. In most cases you it is exactly what you need. |
//| However, if your interpolation model was built on [999,1001],|
//| you will see significant growth of numerical errors when |
//| using {1, x, x^2, x^3} as basis. Representing P as sum of 1, |
//| (x-1000), (x-1000)^2, (x-1000)^3 will be better option. Such |
//| representation can be obtained by using 1000.0 as offset |
//| C and 1.0 as scale S. |
//| 2. power basis is ill-conditioned and tricks described above |
//| can't solve this problem completely. This function will |
//| return coefficients in any case, but for N>8 they will become|
//| unreliable. However, N's less than 5 are pretty safe. |
//| 3. barycentric interpolant passed as P may be either polynomial |
//| obtained from polynomial interpolation/ fitting or rational |
//| function which is NOT polynomial. We can't distinguish |
//| between these two cases, and this algorithm just tries to |
//| work assuming that P IS a polynomial. If not, algorithm will |
//| return results, but they won't have any meaning. |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBar2Pow(CBarycentricInterpolant &p,
const double c,const double s,
double &a[])
{
//--- create variables
int i=0;
int k=0;
double e=0;
double d=0;
double v=0;
int i_=0;
//--- create arrays
double vp[];
double vx[];
double tk[];
double tk1[];
double t[];
//--- check
if(!CAp::Assert(CMath::IsFinite(c),__FUNCTION__+": C is not finite!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(s),__FUNCTION__+": S is not finite!"))
return;
//--- check
if(!CAp::Assert(s!=0.0,__FUNCTION__+": S=0!"))
return;
//--- check
if(!CAp::Assert(p.m_n>0,__FUNCTION__+": P is not correctly initialized barycentric interpolant!"))
return;
//--- Calculate function values on a Chebyshev grid
ArrayResize(vp,p.m_n);
ArrayResize(vx,p.m_n);
for(i=0; i<=p.m_n-1; i++)
{
vx[i]=MathCos(M_PI*(i+0.5)/p.m_n);
vp[i]=CRatInt::BarycentricCalc(p,s*vx[i]+c);
}
//--- T[0]
ArrayResize(t,p.m_n);
v=0;
for(i=0; i<=p.m_n-1; i++)
v=v+vp[i];
t[0]=v/p.m_n;
//--- other T's.
//--- NOTES:
//--- 1. TK stores T{k} on VX,TK1 stores T{k-1} on VX
//--- 2. we can do same calculations with fast DCT,but it
//--- * adds dependencies
//--- * still leaves us with O(N^2) algorithm because
//--- preparation of function values is O(N^2) process
if(p.m_n>1)
{
//--- allocation
ArrayResize(tk,p.m_n);
ArrayResize(tk1,p.m_n);
for(i=0; i<=p.m_n-1; i++)
{
tk[i]=vx[i];
tk1[i]=1;
}
//--- calculation
for(k=1; k<=p.m_n-1; k++)
{
//--- calculate discrete product of function vector and TK
v=0.0;
for(i_=0; i_<=p.m_n-1; i_++)
v+=tk[i_]*vp[i_];
t[k]=v/(0.5*p.m_n);
//--- Update TK and TK1
for(i=0; i<=p.m_n-1; i++)
{
v=2*vx[i]*tk[i]-tk1[i];
tk1[i]=tk[i];
tk[i]=v;
}
}
}
//--- Convert from Chebyshev basis to power basis
ArrayResize(a,p.m_n);
for(i=0; i<=p.m_n-1; i++)
a[i]=0;
d=0;
//--- calculation
for(i=0; i<=p.m_n-1; i++)
{
for(k=i; k<=p.m_n-1; k++)
{
e=a[k];
a[k]=0;
//--- check
if(i<=1 && k==i)
a[k]=1;
else
{
//--- check
if(i!=0)
a[k]=2*d;
//--- check
if(k>i+1)
a[k]=a[k]-a[k-2];
}
d=e;
}
//--- change values
d=a[i];
e=0;
k=i;
//--- cycle
while(k<=p.m_n-1)
{
e=e+a[k]*t[k];
k=k+2;
}
a[i]=e;
}
}
//+------------------------------------------------------------------+
//| Conversion from power basis to barycentric representation. |
//| This function has O(N^2) complexity. |
//| INPUT PARAMETERS: |
//| A - coefficients, P(x)=sum { A[i]*((X-C)/S)^i, i=0..N-1 }|
//| N - number of coefficients (polynomial degree plus 1) |
//| * if given, only leading N elements of A are used |
//| * if not given, automatically determined from size |
//| of A |
//| C - offset (see below); 0.0 is used as default value. |
//| S - scale (see below); 1.0 is used as default value. |
//| S<>0. |
//| OUTPUT PARAMETERS |
//| P - polynomial in barycentric form |
//| NOTES: |
//| 1. this function accepts offset and scale, which can be set to |
//| improve numerical properties of polynomial. For example, if |
//| you interpolate on [-1,+1], you can set C=0 and S=1 and |
//| convert from sum of 1, x, x^2, x^3 and so on. In most cases |
//| you it is exactly what you need. |
//| However, if your interpolation model was built on [999,1001],|
//| you will see significant growth of numerical errors when |
//| using {1, x, x^2, x^3} as input basis. Converting from sum |
//| of 1, (x-1000), (x-1000)^2, (x-1000)^3 will be better option |
//| (you have to specify 1000.0 as offset C and 1.0 as scale S). |
//| 2. power basis is ill-conditioned and tricks described above |
//| can't solve this problem completely. This function will |
//| return barycentric model in any case, but for N>8 accuracy |
//| well degrade. However, N's less than 5 are pretty safe. |
//+------------------------------------------------------------------+
void CPolInt::PolynomialPow2Bar(double &a[],const int n,const double c,
const double s,CBarycentricInterpolant &p)
{
//--- create variables
double vx=0;
double vy=0;
double px=0;
//--- create array
double y[];
//--- check
if(!CAp::Assert(CMath::IsFinite(c),__FUNCTION__+": C is not finite!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(s),__FUNCTION__+": S is not finite!"))
return;
//--- check
if(!CAp::Assert(s!=0.0,__FUNCTION__+": S is zero!"))
return;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
return;
//--- check
if(!CAp::Assert(CAp::Len(a)>=n,__FUNCTION__+": Length(A)<N"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(a,n),__FUNCTION__+": A[] contains INF or NAN"))
return;
//--- Calculate function values on a Chebyshev grid spanning [-1,+1]
ArrayResize(y,n);
for(int i=0; i<n; i++)
{
//--- Calculate value on a grid spanning [-1,+1]
vx=MathCos(M_PI*(i+0.5)/n);
vy=a[0];
px=vx;
//--- calculation
for(int k=1; k<n; k++)
{
vy=vy+px*a[k];
px=px*vx;
}
y[i]=vy;
}
//--- Build barycentric interpolant,map grid from [-1,+1] to [A,B]
PolynomialBuildCheb1(c-s,c+s,y,n,p);
}
//+------------------------------------------------------------------+
//| Lagrange intepolant: generation of the model on the general grid.|
//| This function has O(N^2) complexity. |
//| INPUT PARAMETERS: |
//| X - abscissas, array[0..N-1] |
//| Y - function values, array[0..N-1] |
//| N - number of points, N>=1 |
//| OUTPUT PARAMETERS |
//| P - barycentric model which represents Lagrange |
//| interpolant (see ratint unit info and |
//| BarycentricCalc() description for more information). |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBuild(double &cx[],double &cy[],const int n,
CBarycentricInterpolant &p)
{
//--- create variables
double b=0;
double a=0;
double v=0;
double mx=0;
int i_=0;
//--- create arrays
double w[];
double sortrbuf[];
double sortrbuf2[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- function call
CTSort::TagSortFastR(x,y,sortrbuf,sortrbuf2,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- calculate W[j]
//--- multi-pass algorithm is used to avoid overflow
ArrayResize(w,n);
a=x[0];
b=x[0];
for(int j=0; j<n; j++)
{
w[j]=1;
a=MathMin(a,x[j]);
b=MathMax(b,x[j]);
}
//--- calculation
for(int k=0; k<n; k++)
{
//--- W[K] is used instead of 0.0 because
//--- cycle on J does not touch K-th element
//--- and we MUST get maximum from ALL elements
mx=MathAbs(w[k]);
for(int j=0; j<n; j++)
{
//--- check
if(j!=k)
{
v=(b-a)/(x[j]-x[k]);
w[j]=w[j]*v;
mx=MathMax(mx,MathAbs(w[j]));
}
}
//--- check
if(k%5==0)
{
//--- every 5-th run we renormalize W[]
v=1/mx;
for(i_=0; i_<n; i_++)
w[i_]=v*w[i_];
}
}
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
}
//+------------------------------------------------------------------+
//| Lagrange intepolant: generation of the model on equidistant grid.|
//| This function has O(N) complexity. |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| Y - function values at the nodes, array[0..N-1] |
//| N - number of points, N>=1 |
//| for N=1 a constant model is constructed. |
//| OUTPUT PARAMETERS |
//| P - barycentric model which represents Lagrange |
//| interpolant (see ratint unit info and |
//| BarycentricCalc() description for more information). |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBuildEqDist(const double a,const double b,
double &y[],const int n,
CBarycentricInterpolant &p)
{
double v=0;
//--- create arrays
double w[];
double x[];
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return;
//--- check
if(!CAp::Assert((double)(a+(b-a)/n)!=a,__FUNCTION__+": B is too close to A!"))
return;
//--- Special case: N=1
if(n==1)
{
//--- allocation
ArrayResize(x,1);
ArrayResize(w,1);
x[0]=0.5*(b+a);
w[0]=1;
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
//--- exit the function
return;
}
//--- general case
ArrayResize(x,n);
ArrayResize(w,n);
v=1;
//--- calculation
for(int i=0; i<n; i++)
{
w[i]=v;
x[i]=a+(b-a)*i/(n-1);
v=-(v*(n-1-i));
v=v/(i+1);
}
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
}
//+------------------------------------------------------------------+
//| Lagrange intepolant on Chebyshev grid (first kind). |
//| This function has O(N) complexity. |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| Y - function values at the nodes, array[0..N-1], |
//| Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)))|
//| N - number of points, N>=1 |
//| for N=1 a constant model is constructed. |
//| OUTPUT PARAMETERS |
//| P - barycentric model which represents Lagrange |
//| interpolant (see ratint unit info and |
//| BarycentricCalc() description for more information). |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBuildCheb1(const double a,const double b,
double &y[],const int n,
CBarycentricInterpolant &p)
{
//--- create variables
double v=0;
double t=0;
//--- create arrays
double w[];
double x[];
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return;
//--- Special case: N=1
if(n==1)
{
//--- allocation
ArrayResize(x,1);
ArrayResize(w,1);
x[0]=0.5*(b+a);
w[0]=1;
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
//--- exit the function
return;
}
//--- general case
ArrayResize(x,n);
ArrayResize(w,n);
v=1;
//--- calculation
for(int i=0; i<n; i++)
{
t=MathTan(0.5*M_PI*(2*i+1)/(2*n));
w[i]=2*v*t/(1+CMath::Sqr(t));
x[i]=0.5*(b+a)+0.5*(b-a)*(1-CMath::Sqr(t))/(1+CMath::Sqr(t));
v=-v;
}
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
}
//+------------------------------------------------------------------+
//| Lagrange intepolant on Chebyshev grid (second kind). |
//| This function has O(N) complexity. |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| Y - function values at the nodes, array[0..N-1], |
//| Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1))) |
//| N - number of points, N>=1 |
//| for N=1 a constant model is constructed. |
//| OUTPUT PARAMETERS |
//| P - barycentric model which represents Lagrange |
//| interpolant (see ratint unit info and |
//| BarycentricCalc() description for more information). |
//+------------------------------------------------------------------+
void CPolInt::PolynomialBuildCheb2(const double a,const double b,
double &y[],const int n,
CBarycentricInterpolant &p)
{
double v=0;
//--- create arrays
double w[];
double x[];
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return;
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- Special case: N=1
if(n==1)
{
//--- allocation
ArrayResize(x,1);
ArrayResize(w,1);
x[0]=0.5*(b+a);
w[0]=1;
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
//--- exit the function
return;
}
//--- general case
ArrayResize(x,n);
ArrayResize(w,n);
v=1;
//--- calculation
for(int i=0; i<n; i++)
{
//--- check
if(i==0 || i==n-1)
w[i]=v*0.5;
else
w[i]=v;
x[i]=0.5*(b+a)+0.5*(b-a)*MathCos(M_PI*i/(n-1));
v=-v;
}
//--- function call
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
}
//+------------------------------------------------------------------+
//| Fast equidistant polynomial interpolation function with O(N) |
//| complexity |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| F - function values, array[0..N-1] |
//| N - number of points on equidistant grid, N>=1 |
//| for N=1 a constant model is constructed. |
//| T - position where P(x) is calculated |
//| RESULT |
//| value of the Lagrange interpolant at T |
//| IMPORTANT |
//| this function provides fast interface which is not |
//| overflow-safe nor it is very precise. |
//| the best option is to use PolynomialBuildEqDist() or |
//| BarycentricCalc() subroutines unless you are pretty sure that|
//| your data will not result in overflow. |
//+------------------------------------------------------------------+
double CPolInt::PolynomialCalcEqDist(const double a,const double b,
double &f[],const int n,const double t)
{
//--- create variables
double s1=0;
double s2=0;
double v=0;
double threshold=0;
double s=0;
double h=0;
int j=0;
double w=0;
double x=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
return(EMPTY_VALUE);
//--- Special case: T is NAN
if(CInfOrNaN::IsNaN(t))
return(CInfOrNaN::NaN());
//--- Special case: N=1
if(n==1)
return(f[0]);
//--- First,decide: should we use "safe" formula (guarded
//--- against overflow) or fast one?
threshold=MathSqrt(CMath::m_minrealnumber);
j=0;
s=t-a;
//--- calculation
for(int i=1; i<n; i++)
{
x=a+(double)i/(double)(n-1)*(b-a);
//--- check
if(MathAbs(t-x)<MathAbs(s))
{
s=t-x;
j=i;
}
}
//--- check
if(s==0.0)
return(f[j]);
//--- check
if(MathAbs(s)>threshold)
{
//--- use fast formula
j=-1;
s=1.0;
}
//--- Calculate using safe or fast barycentric formula
s1=0;
s2=0;
w=1.0;
h=(b-a)/(n-1);
//--- calculation
for(int i=0; i<n; i++)
{
//--- check
if(i!=j)
{
v=s*w/(t-(a+i*h));
s1=s1+v*f[i];
s2=s2+v;
}
else
{
v=w;
s1=s1+v*f[i];
s2=s2+v;
}
//--- change values
w=-(w*(n-1-i));
w=w/(i+1);
}
//--- return result
return(s1/s2);
}
//+------------------------------------------------------------------+
//| Fast polynomial interpolation function on Chebyshev points (first|
//| kind) with O(N) complexity. |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| F - function values, array[0..N-1] |
//| N - number of points on Chebyshev grid (first kind), |
//| X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)) |
//| for N=1 a constant model is constructed. |
//| T - position where P(x) is calculated |
//| RESULT |
//| value of the Lagrange interpolant at T |
//| IMPORTANT |
//| this function provides fast interface which is not |
//| overflow-safe nor it is very precise |
//| the best option is to use PolIntBuildCheb1() or |
//| BarycentricCalc() subroutines unless you are pretty sure that|
//| your data will not result in overflow. |
//+------------------------------------------------------------------+
double CPolInt::PolynomialCalcCheb1(const double a,const double b,
double &f[],const int n,double t)
{
//--- create variables
double s1=0;
double s2=0;
double v=0;
double threshold=0;
double s=0;
int j=0;
double a0=0;
double delta=0;
double alpha=0;
double beta=0;
double ca=0;
double sa=0;
double tempc=0;
double temps=0;
double x=0;
double w=0;
double p1=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
return(EMPTY_VALUE);
//--- Special case: T is NAN
if(CInfOrNaN::IsNaN(t))
return(CInfOrNaN::NaN());
//--- Special case: N=1
if(n==1)
return(f[0]);
//--- Prepare information for the recurrence formula
//--- used to calculate sin(pi*(2j+1)/(2n+2)) and
//--- cos(pi*(2j+1)/(2n+2)):
//--- A0=pi/(2n+2)
//--- Delta=pi/(n+1)
//--- Alpha=2 sin^2 (Delta/2)
//--- Beta=sin(Delta)
//--- so that sin(..)=sin(A0+j*delta) and cos(..)=cos(A0+j*delta).
//--- Then we use
//--- sin(x+delta)=sin(x) - (alpha*sin(x) - beta*cos(x))
//--- cos(x+delta)=cos(x) - (alpha*cos(x) - beta*sin(x))
//--- to repeatedly calculate sin(..) and cos(..).
threshold=MathSqrt(CMath::m_minrealnumber);
t=(t-0.5*(a+b))/(0.5*(b-a));
a0=M_PI/(2*(n-1)+2);
delta=2*M_PI/(2*(n-1)+2);
alpha=2*CMath::Sqr(MathSin(delta/2));
beta=MathSin(delta);
//--- First, decide: should we use "safe" formula (guarded
//--- against overflow) or fast one?
ca=MathCos(a0);
sa=MathSin(a0);
j=0;
x=ca;
s=t-x;
//--- calculation
for(int i=1; i<n; i++)
{
//--- Next X[i]
temps=sa-(alpha*sa-beta*ca);
tempc=ca-(alpha*ca+beta*sa);
sa=temps;
ca=tempc;
x=ca;
//--- Use X[i]
if(MathAbs(t-x)<MathAbs(s))
{
s=t-x;
j=i;
}
}
//--- check
if(s==0.0)
return(f[j]);
//--- check
if(MathAbs(s)>threshold)
{
//--- use fast formula
j=-1;
s=1.0;
}
//--- Calculate using safe or fast barycentric formula
s1=0;
s2=0;
ca=MathCos(a0);
sa=MathSin(a0);
p1=1.0;
//--- calculation
for(int i=0; i<n; i++)
{
//--- Calculate X[i],W[i]
x=ca;
w=p1*sa;
//--- Proceed
if(i!=j)
{
v=s*w/(t-x);
s1=s1+v*f[i];
s2=s2+v;
}
else
{
v=w;
s1=s1+v*f[i];
s2=s2+v;
}
//--- Next CA,SA,P1
temps=sa-(alpha*sa-beta*ca);
tempc=ca-(alpha*ca+beta*sa);
sa=temps;
ca=tempc;
p1=-p1;
}
//--- return result
return(s1/s2);
}
//+------------------------------------------------------------------+
//| Fast polynomial interpolation function on Chebyshev points |
//| (second kind) with O(N) complexity. |
//| INPUT PARAMETERS: |
//| A - left boundary of [A,B] |
//| B - right boundary of [A,B] |
//| F - function values, array[0..N-1] |
//| N - number of points on Chebyshev grid (second kind), |
//| X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1)) |
//| for N=1 a constant model is constructed. |
//| T - position where P(x) is calculated |
//| RESULT |
//| value of the Lagrange interpolant at T |
//| IMPORTANT |
//| this function provides fast interface which is not |
//| overflow-safe nor it is very precise. |
//| the best option is to use PolIntBuildCheb2() or |
//| BarycentricCalc() subroutines unless you are pretty sure that|
//| your data will not result in overflow. |
//+------------------------------------------------------------------+
double CPolInt::PolynomialCalcCheb2(const double a,const double b,
double &f[],const int n,double t)
{
//--- create variables
double s1=0;
double s2=0;
double v=0;
double threshold=0;
double s=0;
int j=0;
double a0=0;
double delta=0;
double alpha=0;
double beta=0;
double ca=0;
double sa=0;
double tempc=0;
double temps=0;
double x=0;
double w=0;
double p1=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
return(EMPTY_VALUE);
//--- Special case: T is NAN
if(CInfOrNaN::IsNaN(t))
return(CInfOrNaN::NaN());
//--- Special case: N=1
if(n==1)
return(f[0]);
//--- Prepare information for the recurrence formula
//--- used to calculate sin(pi*i/n) and
//--- cos(pi*i/n):
//--- A0=0
//--- Delta=pi/n
//--- Alpha=2 sin^2 (Delta/2)
//--- Beta=sin(Delta)
//--- so that sin(..)=sin(A0+j*delta) and cos(..)=cos(A0+j*delta).
//--- Then we use
//--- sin(x+delta)=sin(x) - (alpha*sin(x) - beta*cos(x))
//--- cos(x+delta)=cos(x) - (alpha*cos(x) - beta*sin(x))
//--- to repeatedly calculate sin(..) and cos(..).
threshold=MathSqrt(CMath::m_minrealnumber);
t=(t-0.5*(a+b))/(0.5*(b-a));
a0=0.0;
delta=M_PI/(n-1);
alpha=2*CMath::Sqr(MathSin(delta/2));
beta=MathSin(delta);
//--- First,decide: should we use "safe" formula (guarded
//--- against overflow) or fast one?
ca=MathCos(a0);
sa=MathSin(a0);
j=0;
x=ca;
s=t-x;
//--- calculation
for(int i=1; i<n; i++)
{
//--- Next X[i]
temps=sa-(alpha*sa-beta*ca);
tempc=ca-(alpha*ca+beta*sa);
sa=temps;
ca=tempc;
x=ca;
//--- Use X[i]
if(MathAbs(t-x)<MathAbs(s))
{
s=t-x;
j=i;
}
}
//--- check
if(s==0.0)
return(f[j]);
//--- check
if(MathAbs(s)>threshold)
{
//--- use fast formula
j=-1;
s=1.0;
}
//--- Calculate using safe or fast barycentric formula
s1=0;
s2=0;
ca=MathCos(a0);
sa=MathSin(a0);
p1=1.0;
//--- calculation
for(int i=0; i<n; i++)
{
//--- Calculate X[i],W[i]
x=ca;
//--- check
if(i==0 || i==n-1)
w=0.5*p1;
else
w=1.0*p1;
//--- Proceed
if(i!=j)
{
v=s*w/(t-x);
s1=s1+v*f[i];
s2=s2+v;
}
else
{
v=w;
s1=s1+v*f[i];
s2=s2+v;
}
//--- Next CA,SA,P1
temps=sa-(alpha*sa-beta*ca);
tempc=ca-(alpha*ca+beta*sa);
sa=temps;
ca=tempc;
p1=-p1;
}
//--- return result
return(s1/s2);
}
//+------------------------------------------------------------------+
//| 1-dimensional spline inteprolant |
//+------------------------------------------------------------------+
class CSpline1DInterpolant
{
public:
//--- variables
bool m_periodic;
int m_n;
int m_k;
int m_continuity;
//--- arrays
double m_x[];
double m_c[];
//--- constructor, destructor
CSpline1DInterpolant(void) { ZeroMemory(this); }
~CSpline1DInterpolant(void) {}
//--- copy
void Copy(CSpline1DInterpolant &obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline1DInterpolant::Copy(CSpline1DInterpolant &obj)
{
//--- copy variables
m_periodic=obj.m_periodic;
m_n=obj.m_n;
m_k=obj.m_k;
m_continuity=obj.m_continuity;
//--- copy arrays
ArrayCopy(m_x,obj.m_x);
ArrayCopy(m_c,obj.m_c);
}
//+------------------------------------------------------------------+
//| 1-dimensional spline inteprolant |
//+------------------------------------------------------------------+
class CSpline1DInterpolantShell
{
private:
CSpline1DInterpolant m_innerobj;
public:
//--- constructors, destructor
CSpline1DInterpolantShell(void) {}
CSpline1DInterpolantShell(CSpline1DInterpolant &obj) { m_innerobj.Copy(obj); }
~CSpline1DInterpolantShell(void) {}
//--- method
CSpline1DInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Spline fitting report: |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| Fields below are filled by obsolete functions (Spline1DFitCubic, |
//| Spline1DFitHermite). Modern fitting functions do NOT fill these |
//| fields: |
//| TaskRCond reciprocal of task's condition number |
//+------------------------------------------------------------------+
class CSpline1DFitReport
{
public:
//--- variables
double m_taskrcond;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
double m_maxerror;
//--- constructor, destructor
CSpline1DFitReport(void) { ZeroMemory(this); }
~CSpline1DFitReport(void) {}
//--- copy
void Copy(const CSpline1DFitReport &obj);
//--- overloading
void operator=(const CSpline1DFitReport &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline1DFitReport::Copy(const CSpline1DFitReport &obj)
{
//--- copy variables
m_taskrcond=obj.m_taskrcond;
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_maxerror=obj.m_maxerror;
}
//+------------------------------------------------------------------+
//| Spline fitting report: |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| Fields below are filled by obsolete functions (Spline1DFitCubic, |
//| Spline1DFitHermite). Modern fitting functions do NOT fill these |
//| fields: |
//| TaskRCond reciprocal of task's condition number |
//+------------------------------------------------------------------+
class CSpline1DFitReportShell
{
private:
CSpline1DFitReport m_innerobj;
public:
//--- constructors, destructor
CSpline1DFitReportShell(void) {}
CSpline1DFitReportShell(CSpline1DFitReport &obj) { m_innerobj.Copy(obj); }
~CSpline1DFitReportShell(void) {}
//--- methods
double GetTaskRCond(void);
void SetTaskRCond(const double d);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetMaxError(void);
void SetMaxError(const double d);
CSpline1DFitReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Returns the value of the variable taskrcond |
//+------------------------------------------------------------------+
double CSpline1DFitReportShell::GetTaskRCond(void)
{
return(m_innerobj.m_taskrcond);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable taskrcond |
//+------------------------------------------------------------------+
void CSpline1DFitReportShell::SetTaskRCond(const double d)
{
m_innerobj.m_taskrcond=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CSpline1DFitReportShell::GetRMSError(void)
{
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CSpline1DFitReportShell::SetRMSError(const double d)
{
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CSpline1DFitReportShell::GetAvgError(void)
{
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CSpline1DFitReportShell::SetAvgError(const double d)
{
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CSpline1DFitReportShell::GetAvgRelError(void)
{
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CSpline1DFitReportShell::SetAvgRelError(const double d)
{
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable maxerror |
//+------------------------------------------------------------------+
double CSpline1DFitReportShell::GetMaxError(void)
{
return(m_innerobj.m_maxerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable maxerror |
//+------------------------------------------------------------------+
void CSpline1DFitReportShell::SetMaxError(const double d)
{
m_innerobj.m_maxerror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CSpline1DFitReport *CSpline1DFitReportShell::GetInnerObj(void)
{
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| 1-dimensional spline interpolation |
//+------------------------------------------------------------------+
class CSpline1D
{
public:
//--- constants
static const double m_lambdareg;
static const double m_cholreg;
//--- public methods
static void Spline1DBuildLinear(double &cx[],double &cy[],const int n,CSpline1DInterpolant &c);
static void Spline1DBuildCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,CSpline1DInterpolant &c);
static void Spline1DGridDiffCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d[]);
static void Spline1DGridDiff2Cubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d1[],double &d2[]);
static void Spline1DConvCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[]);
static void Spline1DConvDiffCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[],double &d2[]);
static void Spline1DConvDiff2Cubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[],double &d2[],double &dd2[]);
static void Spline1DBuildCatmullRom(double &cx[],double &cy[],const int n,const int boundtype,const double tension,CSpline1DInterpolant &c);
static void Spline1DBuildHermite(double &cx[],double &cy[],double &cd[],const int n,CSpline1DInterpolant &c);
static void Spline1DBuildAkima(double &cx[],double &cy[],const int n,CSpline1DInterpolant &c);
static double Spline1DCalc(CSpline1DInterpolant &c,double x);
static void Spline1DDiff(CSpline1DInterpolant &c,double x,double &s,double &ds,double &d2s);
static void Spline1DCopy(CSpline1DInterpolant &c,CSpline1DInterpolant &cc);
static void Spline1DUnpack(CSpline1DInterpolant &c,int &n,CMatrixDouble &tbl);
static void Spline1DLinTransX(CSpline1DInterpolant &c,const double a,const double b);
static void Spline1DLinTransY(CSpline1DInterpolant &c,const double a,const double b);
static double Spline1DIntegrate(CSpline1DInterpolant &c,double x);
static void Spline1DFit(double &x[],double &y[],int n,int m,double lambdans,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void Spline1DBuildMonotone(double &X[],double &Y[],int n,CSpline1DInterpolant &c);
static void Spline1DConvDiffInternal(double &xold[],double &yold[],double &dold[],const int n,double &x2[],const int n2,double &y[],const bool needy,double &d1[],const bool needd1,double &d2[],const bool needd2);
static void HeapSortDPoints(double &x[],double &y[],double &d[],const int n);
static void HeapSortDPoints(CRowDouble &x,CRowDouble &y,CRowDouble &d,const int n);
private:
static void Spline1DGridDiffCubicInternal(double &x[],double &y[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d[],double &a1[],double &a2[],double &a3[],double &b[],double &dt[]);
static void HeapSortPoints(double &x[],double &y[],const int n);
static void HeapSortPPoints(double &x[],double &y[],int &p[],const int n);
static void SolveTridiagonal(double &a[],double &cb[],double &c[],double &cd[],const int n,double &x[]);
static void SolveCyclicTridiagonal(double &a[],double &cb[],double &c[],double &d[],const int n,double &x[]);
static double DiffThreePoint(double t,const double x0,const double f0,double x1,const double f1,double x2,const double f2);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
const double CSpline1D::m_lambdareg=1.0e-9;
const double CSpline1D::m_cholreg=1.0e-12;
//+------------------------------------------------------------------+
//| This subroutine builds linear spline interpolant |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1] |
//| Y - function values, array[0..N-1] |
//| N - points count (optional): |
//| * N>=2 |
//| * if given, only first N points are used to build |
//| spline |
//| * if not given, automatically detected from X/Y |
//| sizes (len(X) must be equal to len(Y)) |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildLinear(double &cx[],double &cy[],
const int n,CSpline1DInterpolant &c)
{
//--- create arrays
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check
if(!CAp::Assert(n>1,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPoints(x,y,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Build
c.m_periodic=false;
c.m_n=n;
c.m_k=3;
//--- allocation
ArrayResize(c.m_x,n);
ArrayResize(c.m_c,4*(n-1));
//--- copy
for(int i=0; i<n; i++)
c.m_x[i]=x[i];
//--- calculation
for(int i=0; i<=n-2; i++)
{
c.m_c[4*i+0]=y[i];
c.m_c[4*i+1]=(y[i+1]-y[i])/(x[i+1]-x[i]);
c.m_c[4*i+2]=0;
c.m_c[4*i+3]=0;
}
}
//+------------------------------------------------------------------+
//| This subroutine builds cubic spline interpolant. |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points are used to |
//| build spline |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildCubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,const int boundrtype,
const double boundr,CSpline1DInterpolant &c)
{
//--- create a variable
int ylen=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double dt[];
double d[];
int p[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Now we've checked and preordered everything,
//--- so we can call internal function to calculate derivatives,
//--- and then build Hermite spline using these derivatives
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
Spline1DBuildHermite(x,y,d,n,c);
//--- check
if(boundltype==-1 || boundrtype==-1)
c.m_periodic=1;
else
c.m_periodic=0;
}
//+------------------------------------------------------------------+
//| This function solves following problem: given table y[] of |
//| function values at nodes x[], it calculates and returns table of |
//| function derivatives d[] (calculated at the same nodes x[]). |
//| This function yields same result as Spline1DBuildCubic() call |
//| followed by sequence of Spline1DDiff() calls, but it can be |
//| several times faster when called for ordered X[] and X2[]. |
//| INPUT PARAMETERS: |
//| X - spline nodes |
//| Y - function values |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points are used |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| OUTPUT PARAMETERS: |
//| D - derivative values at X[] |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. Derivative values are correctly reordered on |
//| return, so D[I] is always equal to S'(X[I]) independently of |
//| points order. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DGridDiffCubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,const int boundrtype,
const double boundr,double &d[])
{
int ylen=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double dt[];
int p[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Now we've checked and preordered everything,
//--- so we can call internal function.
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
//--- Remember that HeapSortPPoints() call?
//--- Now we have to reorder them back.
if(CAp::Len(dt)<n)
ArrayResize(dt,n);
//--- copy
for(int i=0; i<n; i++)
dt[p[i]]=d[i];
for(int i_=0; i_<n; i_++)
d[i_]=dt[i_];
}
//+------------------------------------------------------------------+
//| This function solves following problem: given table y[] of |
//| function values at nodes x[], it calculates and returns tables of|
//| first and second function derivatives d1[] and d2[] (calculated |
//| at the same nodes x[]). |
//| This function yields same result as Spline1DBuildCubic() call |
//| followed by sequence of Spline1DDiff() calls, but it can be |
//| several times faster when called for ordered X[] and X2[]. |
//| INPUT PARAMETERS: |
//| X - spline nodes |
//| Y - function values |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points are used |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| OUTPUT PARAMETERS: |
//| D1 - S' values at X[] |
//| D2 - S'' values at X[] |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. Derivative values are correctly reordered on |
//| return, so D[I] is always equal to S'(X[I]) independently of |
//| points order. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. |
//| However, this subroutine doesn't require you to specify equal |
//| values for the first and last points - it automatically forces |
//| them to be equal by copying Y[first_point] (corresponds to the |
//| leftmost, minimal X[]) to Y[last_point]. However it is |
//| recommended to pass consistent values of Y[], i.e. to make |
//| Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DGridDiff2Cubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,const int boundrtype,
const double boundr,double &d1[],double &d2[])
{
//--- create variables
int i=0;
int ylen=0;
double delta=0;
double delta2=0;
double delta3=0;
double s0=0;
double s1=0;
double s2=0;
double s3=0;
int i_=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double dt[];
int p[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Now we've checked and preordered everything,
//--- so we can call internal function.
//--- After this call we will calculate second derivatives
//--- (manually,by converting to the power basis)
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d1,a1,a2,a3,b,dt);
//--- allocation
ArrayResize(d2,n);
delta=0;
s2=0;
s3=0;
//--- calculation
for(i=0; i<=n-2; i++)
{
//--- We convert from Hermite basis to the power basis.
//--- Si is coefficient before x^i.
//--- Inside this cycle we need just S2,
//--- because we calculate S'' exactly at spline node,
//--- (only x^2 matters at x=0),but after iterations
//--- will be over,we will need other coefficients
//--- to calculate spline value at the last node.
delta=x[i+1]-x[i];
delta2=CMath::Sqr(delta);
delta3=delta*delta2;
s0=y[i];
s1=d1[i];
s2=(3*(y[i+1]-y[i])-2*d1[i]*delta-d1[i+1]*delta)/delta2;
s3=(2*(y[i]-y[i+1])+d1[i]*delta+d1[i+1]*delta)/delta3;
d2[i]=2*s2;
}
d2[n-1]=2*s2+6*s3*delta;
//--- Remember that HeapSortPPoints() call?
//--- Now we have to reorder them back.
if(CAp::Len(dt)<n)
ArrayResize(dt,n);
//--- copy
for(i=0; i<n; i++)
dt[p[i]]=d1[i];
for(i_=0; i_<n; i_++)
d1[i_]=dt[i_];
for(i=0; i<n; i++)
dt[p[i]]=d2[i];
for(i_=0; i_<n; i_++)
d2[i_]=dt[i_];
}
//+------------------------------------------------------------------+
//| This function solves following problem: given table y[] of |
//| function values at old nodes x[] and new nodes x2[], it |
//| calculates and returns table of function values y2[] (calculated |
//| at x2[]). |
//| This function yields same result as Spline1DBuildCubic() call |
//| followed by sequence of Spline1DDiff() calls, but it can be |
//| several times faster when called for ordered X[] and X2[]. |
//| INPUT PARAMETERS: |
//| X - old spline nodes |
//| Y - function values |
//| X2 - new spline nodes |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points from X/Y are |
//| used |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| N2 - new points count: |
//| * N2>=2 |
//| * if given, only first N2 points from X2 are |
//| used |
//| * if not given, automatically detected from |
//| X2 size |
//| OUTPUT PARAMETERS: |
//| F2 - function values at X2[] |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. Function values are correctly reordered on |
//| return, so F2[I] is always equal to S(X2[I]) independently of |
//| points order. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DConvCubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,const int boundrtype,
const double boundr,double &cx2[],
const int n2,double &y2[])
{
//--- create variables
int ylen=0;
double t=0;
double t2=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double d[];
double dt[];
double d1[];
double d2[];
int p[];
int p2[];
double x[];
double y[];
double x2[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(x2,cx2);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(n2>=2,__FUNCTION__+": N2<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
return;
//--- check and sort X/Y
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- set up DT (we will need it below)
ArrayResize(dt,MathMax(n,n2));
//--- sort X2:
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
//--- * if we have periodic problem,wrap points
//--- * sort them,store permutation at P2
if(boundrtype==-1 && boundltype==-1)
{
for(int i=0; i<=n2-1; i++)
{
t=x2[i];
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
x2[i]=t;
}
}
//--- function call
HeapSortPPoints(x2,dt,p2,n2);
//--- Now we've checked and preordered everything,so we:
//--- * call internal GridDiff() function to get Hermite form of spline
//--- * convert using internal Conv() function
//--- * convert Y2 back to original order
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d1,false,d2,false);
//--- check
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
return;
//--- copy
for(int i=0; i<=n2-1; i++)
dt[p2[i]]=y2[i];
for(int i_=0; i_<=n2-1; i_++)
y2[i_]=dt[i_];
}
//+------------------------------------------------------------------+
//| This function solves following problem: given table y[] of |
//| function values at old nodes x[] and new nodes x2[], it |
//| calculates and returns table of function values y2[] and |
//| derivatives d2[] (calculated at x2[]). |
//| This function yields same result as Spline1DBuildCubic() call |
//| followed by sequence of Spline1DDiff() calls, but it can be |
//| several times faster when called for ordered X[] and X2[]. |
//| INPUT PARAMETERS: |
//| X - old spline nodes |
//| Y - function values |
//| X2 - new spline nodes |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points from X/Y are |
//| used |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| N2 - new points count: |
//| * N2>=2 |
//| * if given, only first N2 points from X2 are |
//| used |
//| * if not given, automatically detected from |
//| X2 size |
//| OUTPUT PARAMETERS: |
//| F2 - function values at X2[] |
//| D2 - first derivatives at X2[] |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. Function values are correctly reordered on |
//| return, so F2[I] is always equal to S(X2[I]) independently of |
//| points order. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DConvDiffCubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,const int boundrtype,
const double boundr,double &cx2[],
const int n2,double &y2[],double &d2[])
{
//--- create variables
int i=0;
int ylen=0;
double t=0;
double t2=0;
int i_=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double d[];
double dt[];
double rt1[];
int p[];
int p2[];
double x[];
double y[];
double x2[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(x2,cx2);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(n2>=2,__FUNCTION__+"Spline1DConvDiffCubic: N2<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
return;
//--- check and sort X/Y
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- set up DT (we will need it below)
ArrayResize(dt,MathMax(n,n2));
//--- sort X2:
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
//--- * if we have periodic problem,wrap points
//--- * sort them,store permutation at P2
if(boundrtype==-1 && boundltype==-1)
{
for(i=0; i<=n2-1; i++)
{
t=x2[i];
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
x2[i]=t;
}
}
//--- function call
HeapSortPPoints(x2,dt,p2,n2);
//--- Now we've checked and preordered everything,so we:
//--- * call internal GridDiff() function to get Hermite form of spline
//--- * convert using internal Conv() function
//--- * convert Y2 back to original order
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d2,true,rt1,false);
//--- check
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
return;
//--- copy
for(i=0; i<=n2-1; i++)
dt[p2[i]]=y2[i];
for(i_=0; i_<=n2-1; i_++)
y2[i_]=dt[i_];
for(i=0; i<=n2-1; i++)
dt[p2[i]]=d2[i];
for(i_=0; i_<=n2-1; i_++)
d2[i_]=dt[i_];
}
//+------------------------------------------------------------------+
//| This function solves following problem: given table y[] of |
//| function values at old nodes x[] and new nodes x2[], it |
//| calculates and returns table of function values y2[], first and |
//| second derivatives d2[] and dd2[] (calculated at x2[]). |
//| This function yields same result as Spline1DBuildCubic() call |
//| followed by sequence of Spline1DDiff() calls, but it can be |
//| several times faster when called for ordered X[] and X2[]. |
//| INPUT PARAMETERS: |
//| X - old spline nodes |
//| Y - function values |
//| X2 - new spline nodes |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points from X/Y are |
//| used |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundLType - boundary condition type for the left boundary|
//| BoundL - left boundary condition (first or second |
//| derivative, depending on the BoundLType) |
//| BoundRType - boundary condition type for the right |
//| boundary |
//| BoundR - right boundary condition (first or second |
//| derivative, depending on the BoundRType) |
//| N2 - new points count: |
//| * N2>=2 |
//| * if given, only first N2 points from X2 are |
//| used |
//| * if not given, automatically detected from |
//| X2 size |
//| OUTPUT PARAMETERS: |
//| F2 - function values at X2[] |
//| D2 - first derivatives at X2[] |
//| DD2 - second derivatives at X2[] |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. Function values are correctly reordered on |
//| return, so F2[I] is always equal to S(X2[I]) independently of |
//| points order. |
//| SETTING BOUNDARY VALUES: |
//| The BoundLType/BoundRType parameters can have the following |
//| values: |
//| * -1, which corresonds to the periodic (cyclic) boundary |
//| conditions. In this case: |
//| * both BoundLType and BoundRType must be equal to -1. |
//| * BoundL/BoundR are ignored |
//| * Y[last] is ignored (it is assumed to be equal to |
//| Y[first]). |
//| * 0, which corresponds to the parabolically terminated |
//| spline (BoundL and/or BoundR are ignored). |
//| * 1, which corresponds to the first derivative boundary |
//| condition |
//| * 2, which corresponds to the second derivative boundary |
//| condition |
//| * by default, BoundType=0 is used |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DConvDiff2Cubic(double &cx[],double &cy[],
const int n,const int boundltype,
const double boundl,
const int boundrtype,
const double boundr,double &cx2[],
const int n2,double &y2[],
double &d2[],double &dd2[])
{
//--- create variables
int i=0;
int ylen=0;
double t=0;
double t2=0;
int i_=0;
//--- create arrays
double a1[];
double a2[];
double a3[];
double b[];
double d[];
double dt[];
int p[];
int p2[];
double x[];
double y[];
double x2[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(x2,cx2);
//--- check correctness of boundary conditions
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
return;
//--- check
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
return;
//--- check
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
return;
//--- check
if(boundltype==1 || boundltype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
return;
}
//--- check
if(boundrtype==1 || boundrtype==2)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
return;
}
//--- check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(n2>=2,__FUNCTION__+": N2<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
return;
//--- check and sort X/Y
ylen=n;
//--- check
if(boundltype==-1)
ylen=n-1;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
return;
//--- function call
HeapSortPPoints(x,y,p,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- set up DT (we will need it below)
ArrayResize(dt,MathMax(n,n2));
//--- sort X2:
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
//--- * if we have periodic problem,wrap points
//--- * sort them,store permutation at P2
if(boundrtype==-1 && boundltype==-1)
{
for(i=0; i<=n2-1; i++)
{
t=x2[i];
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
x2[i]=t;
}
}
//--- function call
HeapSortPPoints(x2,dt,p2,n2);
//--- Now we've checked and preordered everything,so we:
//--- * call internal GridDiff() function to get Hermite form of spline
//--- * convert using internal Conv() function
//--- * convert Y2 back to original order
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d2,true,dd2,true);
//--- check
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
return;
//--- copy
for(i=0; i<=n2-1; i++)
dt[p2[i]]=y2[i];
for(i_=0; i_<=n2-1; i_++)
y2[i_]=dt[i_];
for(i=0; i<=n2-1; i++)
dt[p2[i]]=d2[i];
for(i_=0; i_<=n2-1; i_++)
d2[i_]=dt[i_];
for(i=0; i<=n2-1; i++)
dt[p2[i]]=dd2[i];
for(i_=0; i_<=n2-1; i_++)
dd2[i_]=dt[i_];
}
//+------------------------------------------------------------------+
//| This subroutine builds Catmull-Rom spline interpolant. |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| OPTIONAL PARAMETERS: |
//| N - points count: |
//| * N>=2 |
//| * if given, only first N points are used to |
//| build spline |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| BoundType - boundary condition type: |
//| * -1 for periodic boundary condition |
//| * 0 for parabolically terminated spline |
//| (default) |
//| Tension - tension parameter: |
//| * tension=0 corresponds to classic |
//| Catmull-Rom spline (default) |
//| * 0<tension<1 corresponds to more general |
//| form - cardinal spline |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
//| Problems with periodic boundary conditions have |
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
//| require you to specify equal values for the first and last |
//| points - it automatically forces them to be equal by copying |
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
//| Y[last_point]. However it is recommended to pass consistent |
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildCatmullRom(double &cx[],double &cy[],
const int n,const int boundtype,
const double tension,
CSpline1DInterpolant &c)
{
//--- create arrays
double d[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(boundtype==-1 || boundtype==0,__FUNCTION__+": incorrect BoundType!"))
return;
//--- check
if(!CAp::Assert((double)(tension)>=0.0,__FUNCTION__+": Tension<0!"))
return;
//--- check
if(!CAp::Assert((double)(tension)<=1.0,__FUNCTION__+": Tension>1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPoints(x,y,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Special cases:
//--- * N=2,parabolic terminated boundary condition on both ends
//--- * N=2,periodic boundary condition
if(n==2 && boundtype==0)
{
//--- Just linear spline
Spline1DBuildLinear(x,y,n,c);
return;
}
if(n==2 && boundtype==-1)
{
//--- Same as cubic spline with periodic conditions
Spline1DBuildCubic(x,y,n,-1,0.0,-1,0.0,c);
return;
}
//--- Periodic or non-periodic boundary conditions
if(boundtype==-1)
{
//--- Periodic boundary conditions
y[n-1]=y[0];
//--- allocation
ArrayResize(d,n);
d[0]=(y[1]-y[n-2])/(2*(x[1]-x[0]+x[n-1]-x[n-2]));
for(int i=1; i<=n-2; i++)
d[i]=(1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
d[n-1]=d[0];
//--- Now problem is reduced to the cubic Hermite spline
Spline1DBuildHermite(x,y,d,n,c);
c.m_periodic=true;
}
else
{
//--- Non-periodic boundary conditions
ArrayResize(d,n);
for(int i=1; i<=n-2; i++)
d[i]=(1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
d[0]=2*(y[1]-y[0])/(x[1]-x[0])-d[1];
d[n-1]=2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])-d[n-2];
//--- Now problem is reduced to the cubic Hermite spline
Spline1DBuildHermite(x,y,d,n,c);
}
}
//+------------------------------------------------------------------+
//| This subroutine builds Hermite spline interpolant. |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1] |
//| Y - function values, array[0..N-1] |
//| D - derivatives, array[0..N-1] |
//| N - points count (optional): |
//| * N>=2 |
//| * if given, only first N points are used to |
//| build spline |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant. |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildHermite(double &cx[],double &cy[],
double &cd[],const int n,
CSpline1DInterpolant &c)
{
//--- create variables
double delta=0;
double delta2=0;
double delta3=0;
//--- create arrays
double x[];
double y[];
double d[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(d,cd);
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(d)>=n,__FUNCTION__+": Length(D)<N!"))
return;
//--- check and sort points
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(d,n),__FUNCTION__+": D contains infinite or NAN values!"))
return;
HeapSortDPoints(x,y,d,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Build
ArrayResize(c.m_x,n);
ArrayResize(c.m_c,4*(n-1));
//--- change values
c.m_periodic=false;
c.m_k=3;
c.m_n=n;
//--- copy
for(int i=0; i<n; i++)
c.m_x[i]=x[i];
//--- calculation
for(int i=0; i<=n-2; i++)
{
delta=x[i+1]-x[i];
delta2=CMath::Sqr(delta);
delta3=delta*delta2;
c.m_c[4*i+0]=y[i];
c.m_c[4*i+1]=d[i];
c.m_c[4*i+2]=(3*(y[i+1]-y[i])-2*d[i]*delta-d[i+1]*delta)/delta2;
c.m_c[4*i+3]=(2*(y[i]-y[i+1])+d[i]*delta+d[i+1]*delta)/delta3;
}
}
//+------------------------------------------------------------------+
//| This subroutine builds Akima spline interpolant |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1] |
//| Y - function values, array[0..N-1] |
//| N - points count (optional): |
//| * N>=5 |
//| * if given, only first N points are used to |
//| build spline |
//| * if not given, automatically detected from |
//| X/Y sizes (len(X) must be equal to len(Y)) |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildAkima(double &cx[],double &cy[],
const int n,CSpline1DInterpolant &c)
{
//--- create arrays
double d[];
double w[];
double diff[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- check
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check and sort points
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- function call
HeapSortPoints(x,y,n);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
return;
//--- Prepare W (weights),Diff (divided differences)
ArrayResize(w,n-1);
ArrayResize(diff,n-1);
for(int i=0; i<=n-2; i++)
diff[i]=(y[i+1]-y[i])/(x[i+1]-x[i]);
for(int i=1; i<=n-2; i++)
w[i]=MathAbs(diff[i]-diff[i-1]);
//--- Prepare Hermite interpolation scheme
ArrayResize(d,n);
for(int i=2; i<=n-3; i++)
{
//--- check
if(MathAbs(w[i-1])+MathAbs(w[i+1])!=0.0)
d[i]=(w[i+1]*diff[i-1]+w[i-1]*diff[i])/(w[i+1]+w[i-1]);
else
d[i]=((x[i+1]-x[i])*diff[i-1]+(x[i]-x[i-1])*diff[i])/(x[i+1]-x[i-1]);
}
//--- change values
d[0]=DiffThreePoint(x[0],x[0],y[0],x[1],y[1],x[2],y[2]);
d[1]=DiffThreePoint(x[1],x[0],y[0],x[1],y[1],x[2],y[2]);
d[n-2]=DiffThreePoint(x[n-2],x[n-3],y[n-3],x[n-2],y[n-2],x[n-1],y[n-1]);
d[n-1]=DiffThreePoint(x[n-1],x[n-3],y[n-3],x[n-2],y[n-2],x[n-1],y[n-1]);
//--- Build Akima spline using Hermite interpolation scheme
Spline1DBuildHermite(x,y,d,n,c);
}
//+------------------------------------------------------------------+
//| This subroutine calculates the value of the spline at the given |
//| point X. |
//| INPUT PARAMETERS: |
//| C - spline interpolant |
//| X - point |
//| Result: |
//| S(x) |
//+------------------------------------------------------------------+
double CSpline1D::Spline1DCalc(CSpline1DInterpolant &c,double x)
{
//--- create variables
int l=0;
int r=0;
int m=0;
double t=0;
//--- check
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
return(EMPTY_VALUE);
//--- special case: NaN
if(CInfOrNaN::IsNaN(x))
return(CInfOrNaN::NaN());
//--- correct if periodic
if(c.m_periodic)
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
//--- Binary search in the [ x[0],...,x[n-2] ] (x[n-1] is not included)
l=0;
r=c.m_n-2+1;
while(l!=r-1)
{
m=(l+r)/2;
//--- check
if(c.m_x[m]>=x)
r=m;
else
l=m;
}
//--- Interpolation
x=x-c.m_x[l];
m=4*l;
//--- return result
return(c.m_c[m]+x*(c.m_c[m+1]+x*(c.m_c[m+2]+x*c.m_c[m+3])));
}
//+------------------------------------------------------------------+
//| This subroutine differentiates the spline. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X - point |
//| Result: |
//| S - S(x) |
//| DS - S'(x) |
//| D2S - S''(x) |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DDiff(CSpline1DInterpolant &c,double x,
double &s,double &ds,double &d2s)
{
//--- create variables
int l=0;
int r=0;
int m=0;
double t=0;
//--- initialization
s=0;
ds=0;
d2s=0;
//--- check
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
return;
//--- check
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
return;
//--- special case: NaN
if(CInfOrNaN::IsNaN(x))
{
//--- change values
s=CInfOrNaN::NaN();
ds=CInfOrNaN::NaN();
d2s=CInfOrNaN::NaN();
//--- exit the function
return;
}
//--- correct if periodic
if(c.m_periodic)
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
//--- Binary search
l=0;
r=c.m_n-2+1;
while(l!=r-1)
{
m=(l+r)/2;
//--- check
if(c.m_x[m]>=x)
r=m;
else
l=m;
}
//--- Differentiation
x=x-c.m_x[l];
m=4*l;
s=c.m_c[m]+x*(c.m_c[m+1]+x*(c.m_c[m+2]+x*c.m_c[m+3]));
ds=c.m_c[m+1]+2*x*c.m_c[m+2]+3*CMath::Sqr(x)*c.m_c[m+3];
d2s=2*c.m_c[m+2]+6*x*c.m_c[m+3];
}
//+------------------------------------------------------------------+
//| This subroutine makes the copy of the spline. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| Result: |
//| CC - spline copy |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DCopy(CSpline1DInterpolant &c,CSpline1DInterpolant &cc)
{
//--- change values
cc.m_periodic=c.m_periodic;
cc.m_n=c.m_n;
cc.m_k=c.m_k;
cc.m_continuity=c.m_continuity;
//--- allocation
ArrayResize(cc.m_x,cc.m_n);
//--- copy
for(int i_=0; i_<=cc.m_n-1; i_++)
cc.m_x[i_]=c.m_x[i_];
//--- allocation
ArrayResize(cc.m_c,(cc.m_k+1)*(cc.m_n-1));
//--- copy
for(int i_=0; i_<=(cc.m_k+1)*(cc.m_n-1)-1; i_++)
cc.m_c[i_]=c.m_c[i_];
}
//+------------------------------------------------------------------+
//| This subroutine unpacks the spline into the coefficients table. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X - point |
//| Result: |
//| Tbl - coefficients table, unpacked format, array[0..N-2, |
//| 0..5]. |
//| For I = 0...N-2: |
//| Tbl[I,0] = X[i] |
//| Tbl[I,1] = X[i+1] |
//| Tbl[I,2] = C0 |
//| Tbl[I,3] = C1 |
//| Tbl[I,4] = C2 |
//| Tbl[I,5] = C3 |
//| On [x[i], x[i+1]] spline is equals to: |
//| S(x) = C0 + C1*t + C2*t^2 + C3*t^3 |
//| t = x-x[i] |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DUnpack(CSpline1DInterpolant &c,int &n,
CMatrixDouble &tbl)
{
//--- allocation
tbl.Resize(c.m_n-2+1,2+c.m_k+1);
//--- initialization
n=c.m_n;
//--- Fill
for(int i=0; i<=n-2; i++)
{
tbl.Set(i,0,c.m_x[i]);
tbl.Set(i,1,c.m_x[i+1]);
for(int j=0; j<=c.m_k; j++)
tbl.Set(i,2+j,c.m_c[(c.m_k+1)*i+j]);
}
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline |
//| argument. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| A, B- transformation coefficients: x = A*t + B |
//| Result: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DLinTransX(CSpline1DInterpolant &c,const double a,
const double b)
{
//--- create variables
int n=c.m_n;
double v=0;
double dv=0;
double d2v=0;
//--- create arrays
double x[];
double y[];
double d[];
//--- Special case: A=0
if(a==0.0)
{
v=Spline1DCalc(c,b);
for(int i=0; i<=n-2; i++)
{
c.m_c[(c.m_k+1)*i]=v;
for(int j=1; j<=c.m_k; j++)
c.m_c[(c.m_k+1)*i+j]=0;
}
//--- exit the function
return;
}
//--- General case: A<>0.
//--- Unpack,X,Y,dY/dX.
//--- Scale and pack again.
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
return;
//--- allocation
ArrayResize(x,n);
ArrayResize(y,n);
ArrayResize(d,n);
//--- calculation
for(int i=0; i<n; i++)
{
x[i]=c.m_x[i];
Spline1DDiff(c,x[i],v,dv,d2v);
x[i]=(x[i]-b)/a;
y[i]=v;
d[i]=a*dv;
}
//--- function call
Spline1DBuildHermite(x,y,d,n,c);
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| A,B- transformation coefficients: S2(x)=A*S(x) + B |
//| Result: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DLinTransY(CSpline1DInterpolant &c,const double a,
const double b)
{
int n=c.m_n;
//--- calculation
for(int i=0; i<=n-2; i++)
{
c.m_c[(c.m_k+1)*i]=a*c.m_c[(c.m_k+1)*i]+b;
for(int j=1; j<=c.m_k; j++)
c.m_c[(c.m_k+1)*i+j]=a*c.m_c[(c.m_k+1)*i+j];
}
}
//+------------------------------------------------------------------+
//| This subroutine integrates the spline. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X - right bound of the integration interval [a, x], |
//| here 'a' denotes min(x[]) |
//| Result: |
//| integral(S(t)dt,a,x) |
//+------------------------------------------------------------------+
double CSpline1D::Spline1DIntegrate(CSpline1DInterpolant &c,double x)
{
//--- create variables
double result=0;
int n=c.m_n;
int l=0;
int r=0;
int m=0;
double w=0;
double v=0;
double t=0;
double intab=0;
double additionalterm=0;
//--- Periodic splines require special treatment. We make
//--- following transformation:
//--- integral(S(t)dt,A,X)=integral(S(t)dt,A,Z)+AdditionalTerm
//--- here X may lie outside of [A,B],Z lies strictly in [A,B],
//--- AdditionalTerm is equals to integral(S(t)dt,A,B) times some
//--- integer number (may be zero).
if(c.m_periodic && (x<c.m_x[0] || x>c.m_x[c.m_n-1]))
{
//--- compute integral(S(x)dx,A,B)
intab=0;
for(int i=0; i<=c.m_n-2; i++)
{
w=c.m_x[i+1]-c.m_x[i];
m=(c.m_k+1)*i;
intab=intab+c.m_c[m]*w;
v=w;
for(int j=1; j<=c.m_k; j++)
{
v=v*w;
intab=intab+c.m_c[m+j]*v/(j+1);
}
}
//--- map X into [A,B]
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
additionalterm=t*intab;
}
else
additionalterm=0;
//--- Binary search in the [ x[0],...,x[n-2] ] (x[n-1] is not included)
l=0;
r=n-2+1;
while(l!=r-1)
{
m=(l+r)/2;
//--- check
if(c.m_x[m]>=x)
r=m;
else
l=m;
}
//--- Integration
result=0;
for(int i=0; i<=l-1; i++)
{
w=c.m_x[i+1]-c.m_x[i];
m=(c.m_k+1)*i;
result=result+c.m_c[m]*w;
v=w;
//--- calculation
for(int j=1; j<=c.m_k; j++)
{
v=v*w;
result=result+c.m_c[m+j]*v/(j+1);
}
}
//--- change values
w=x-c.m_x[l];
m=(c.m_k+1)*l;
v=w;
result=result+c.m_c[m]*w;
//--- calculation
for(int j=1; j<=c.m_k; j++)
{
v=v*w;
result=result+c.m_c[m+j]*v/(j+1);
}
//--- return result
return(result+additionalterm);
}
//+------------------------------------------------------------------+
//| Fitting by smoothing (penalized) cubic spline. |
//| This function approximates N scattered points (some of X[] may |
//| be equal to each other) by cubic spline with M nodes at |
//| equidistant grid spanning interval [min(x,xc),max(x,xc)]. |
//| The problem is regularized by adding nonlinearity penalty to |
//| usual least squares penalty function: |
//| MERIT_FUNC = F_LS + F_NL |
//| where F_LS is a least squares error term, and F_NL is a |
//| nonlinearity penalty which is roughly proportional to |
//| LambdaNS*integral{ S''(x)^2*dx }. Algorithm applies automatic |
//| renormalization of F_NL which makes penalty term roughly |
//| invariant to scaling of X[] and changes in M. |
//| This function is a new edition of penalized regression spline|
//| fitting, a fast and compact one which needs much less resources |
//| that its previous version: just O(maxMN) memory and |
//| O(maxMN*log(maxMN)) time. |
//| NOTE: it is OK to run this function with both M<<N and M>>N; say,|
//| it is possible to process 100 points with 1000-node spline.|
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| N - number of points (optional): |
//| * N>0 |
//| * if given, only first N elements of X/Y are |
//| processed |
//| * if not given, automatically determined from |
//| lengths |
//| M - number of basis functions ( = number_of_nodes), |
//| M>=4. |
//| LambdaNS - LambdaNS>=0, regularization constant passed by |
//| user. It penalizes nonlinearity in the regression |
//| spline. Possible values to start from are 0.00001, |
//| 0.1, 1 |
//| OUTPUT PARAMETERS: |
//| S - spline interpolant. |
//| Rep - Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DFit(double &X[],double &Y[],int n,int m,
double lambdans,CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create variables
int bfrad=0;
double xa=0;
double xb=0;
int i=0;
int j=0;
int k=0;
int k0=0;
int k1=0;
double v=0;
double dv=0;
double d2v=0;
int gridexpansion=0;
double xywork[];
CMatrixDouble vterm;
double sx[];
double sy[];
double sdy[];
double tmpx[];
double tmpy[];
CSpline1DInterpolant basis1;
CSparseMatrix av;
CSparseMatrix ah;
CSparseMatrix ata;
CRowDouble targets;
double meany=0;
int lsqrcnt=0;
int nrel=0;
double rss=0;
double tss=0;
int arows=0;
CRowDouble tmp0;
CRowDouble tmp1;
CLinLSQRState solver;
CLinLSQRReport srep;
double creg=0;
double mxata=0;
int bw=0;
CRowInt nzidx;
CRowDouble nzval;
int nzcnt=0;
double scaletargetsby=0;
double scalepenaltyby=0;
double x[];
double y[];
ArrayCopy(x,X);
ArrayCopy(y,Y);
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is infinite!"))
return;
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0!"))
return;
bfrad=2;
lsqrcnt=10;
//--- Sort points.
//--- Determine actual area size, make sure that XA<XB
CTSort::TagSortFastR(x,y,tmpx,tmpy,n);
xa=x[0];
xb=x[n-1];
if(xa==xb)
{
v=xa;
if(v>=0.0)
{
xa=v/2-1;
xb=v*2+1;
}
else
{
xa=v*2-1;
xb=v/2+1;
}
}
//--- check
if(!CAp::Assert(xa<xb,__FUNCTION__+": integrity error"))
return;
//--- Perform a grid correction according to current grid expansion size.
m=MathMax(m,4);
gridexpansion=1;
v=(xb-xa)/m;
xa=xa-v*gridexpansion;
xb=xb+v*gridexpansion;
m=m+2*gridexpansion;
//--- Convert X/Y to work representation, remove linear trend (in
//--- order to improve condition number).
//--- Compute total-sum-of-squares (needed later for R2 coefficient).
ArrayResize(xywork,2*n);
for(i=0; i<n; i++)
{
xywork[2*i+0]=(x[i]-xa)/(xb-xa);
xywork[2*i+1]=y[i];
}
CIntFitServ::BuildPriorTerm1(xywork,n,1,1,1,0.0,vterm);
meany=0;
for(i=0; i<n; i++)
meany+=y[i];
meany=meany/n;
tss=0;
for(i=0; i<n; i++)
tss+=CMath::Sqr(y[i]-meany);
//--- Build 1D compact basis function
//--- Generate design matrix AV ("vertical") and its transpose AH ("horizontal").
ArrayResize(tmpx,7);
ArrayResize(tmpy,7);
tmpx[0]=-(3.0/(double)(m-1));
tmpx[1]=-(2.0/(double)(m-1));
tmpx[2]=-(1.0/(double)(m-1));
tmpx[3]=0.0;
tmpx[4]=1.0/(double)(m-1);
tmpx[5]=2.0/(double)(m-1);
tmpx[6]=3.0/(double)(m-1);
tmpy[0]=0.0;
tmpy[1]=0.0;
tmpy[2]=1.0/12.0;
tmpy[3]=2.0/6.0;
tmpy[4]=1.0/12.0;
tmpy[5]=0.0;
tmpy[6]=0.0;
Spline1DBuildCubic(tmpx,tmpy,CAp::Len(tmpx),2,0.0,2,0.0,basis1);
arows=n+2*m;
CSparse::SparseCreate(arows,m,0,av);
targets=vector<double>::Zeros(arows);
scaletargetsby=1/MathSqrt(n);
scalepenaltyby=1/MathSqrt(m);
for(i=0; i<n; i++)
{
//--- Generate design matrix row #I which corresponds to I-th dataset point
k=(int)MathFloor(CApServ::BoundVal(xywork[2*i+0]*(m-1),0.0,m-1.0));
k0=MathMax(k-(bfrad-1),0);
k1=MathMin(k+bfrad,m-1);
for(j=k0; j<=k1; j++)
CSparse::SparseSet(av,i,j,Spline1DCalc(basis1,xywork[2*i+0]-(double)j/(double)(m-1))*scaletargetsby);
targets.Set(i,xywork[2*i+1]*scaletargetsby);
}
for(i=0; i<m; i++)
{
//--- Generate design matrix row #(I+N) which corresponds to nonlinearity penalty at I-th node
k0=MathMax(i-(bfrad-1),0);
k1=MathMin(i+(bfrad-1),m-1);
for(j=k0; j<=k1; j++)
{
Spline1DDiff(basis1,(double)i/(double)(m-1)-(double)j/(double)(m-1),v,dv,d2v);
CSparse::SparseSet(av,n+i,j,lambdans*d2v*scalepenaltyby);
}
}
for(i=0; i<m; i++)
{
//--- Generate design matrix row #(I+N+M) which corresponds to regularization for I-th coefficient
CSparse::SparseSet(av,n+m+i,i,m_lambdareg);
}
CSparse::SparseConvertToCRS(av);
CSparse::SparseCopyTransposeCRS(av,ah);
//--- Build 7-diagonal (bandwidth=3) normal equations matrix and perform Cholesky
//--- decomposition (to be used later as preconditioner for LSQR iterations).
bw=3;
CSparse::SparseCreateSKSBand(m,m,bw,ata);
mxata=0;
for(i=0; i<m; i++)
{
for(j=i; j<=MathMin(i+bw,m-1); j++)
{
//--- Get pattern of nonzeros in one of the rows (let it be I-th one)
//--- and compute dot product only for nonzero entries.
CSparse::SparseGetCompressedRow(ah,i,nzidx,nzval,nzcnt);
v=0;
for(k=0; k<nzcnt; k++)
v+=CSparse::SparseGet(ah,i,nzidx[k])*CSparse::SparseGet(ah,j,nzidx[k]);
//--- Update ATA and max(ATA)
CSparse::SparseSet(ata,i,j,v);
if(i==j)
mxata=MathMax(mxata,MathAbs(v));
}
}
mxata=CApServ::Coalesce(mxata,1.0);
creg=m_cholreg;
while(true)
{
//--- Regularization
for(i=0; i<=m-1; i++)
CSparse::SparseSet(ata,i,i,CSparse::SparseGet(ata,i,i)+mxata*creg);
//--- Try Cholesky factorization.
if(!CTrFac::SparseCholeskySkyLine(ata,m,true))
{
//--- Factorization failed, increase regularizer and repeat
creg=CApServ::Coalesce(10*creg,1.0E-12);
continue;
}
break;
}
//--- Solve with preconditioned LSQR:
//--- use Cholesky factor U of squared design matrix A'*A to
//--- transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
//--- Preconditioned problem is solved with LSQR solver, which
//--- gives superior results to normal equations approach. Due
//--- to Cholesky preconditioner being utilized we can solve
//--- problem in just a few iterations.
CApServ::RVectorSetLengthAtLeast(tmp0,arows);
CApServ::RVectorSetLengthAtLeast(tmp1,m);
CLinLSQR::LinLSQRCreateBuf(arows,m,solver);
CLinLSQR::LinLSQRSetB(solver,targets);
CLinLSQR::LinLSQRSetCond(solver,1.0E-14,1.0E-14,lsqrcnt);
while(CLinLSQR::LinLSQRIteration(solver))
{
if(solver.m_needmv)
{
tmp1=solver.m_x;
//--- Use Cholesky factorization of the system matrix
//--- as preconditioner: solve TRSV(U,Solver.X)
CSparse::SparseTRSV(ata,true,false,0,tmp1);
//--- After preconditioning is done, multiply by A
CSparse::SparseMV(av,tmp1,solver.m_mv);
}
if(solver.m_needmtv)
{
//--- Multiply by design matrix A
CSparse::SparseMTV(av,solver.m_x,solver.m_mtv);
//--- Multiply by preconditioner: solve TRSV(U',A*Solver.X)
CSparse::SparseTRSV(ata,true,false,1,solver.m_mtv);
}
}
CLinLSQR::LinLSQRResults(solver,tmp1,srep);
CSparse::SparseTRSV(ata,true,false,0,tmp1);
//--- Generate output spline as a table of spline valued and first
//--- derivatives at nodes (used to build Hermite spline)
ArrayResize(sx,m);
ArrayResize(sy,m);
ArrayResize(sdy,m);
for(i=0; i<m; i++)
{
sx[i]=(double)i/(double)(m-1);
sy[i]=0;
sdy[i]=0;
}
for(i=0; i<m; i++)
{
k0=MathMax(i-(bfrad-1),0);
k1=MathMin(i+bfrad,m-1);
for(j=k0; j<=k1; j++)
{
Spline1DDiff(basis1,(double)j/(double)(m-1)-(double)i/(double)(m-1),v,dv,d2v);
sy[j]=sy[j]+tmp1[i]*v;
sdy[j]=sdy[j]+tmp1[i]*dv;
}
}
//--- Calculate model values
CSparse::SparseMV(av,tmp1,tmp0);
tmp0/=scaletargetsby;
rss=0.0;
nrel=0;
rep.m_rmserror=0;
rep.m_maxerror=0;
rep.m_avgerror=0;
rep.m_avgrelerror=0;
for(i=0; i<n; i++)
{
v=xywork[2*i+1]-tmp0[i];
rss+=v*v;
rep.m_rmserror+=CMath::Sqr(v);
rep.m_avgerror+=MathAbs(v);
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
if(y[i]!=0.0)
{
rep.m_avgrelerror+=MathAbs(v/y[i]);
nrel+=1;
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
rep.m_avgerror/=n;
rep.m_avgrelerror/=CApServ::Coalesce(nrel,1.0);
//--- Append prior term.
//--- Transform spline to original coordinates.
//--- Output.
for(i=0; i<m; i++)
{
sy[i]+=vterm.Get(0,0)*sx[i]+vterm.Get(0,1);
sdy[i]+=vterm.Get(0,0);
}
for(i=0; i<m; i++)
{
sx[i]=sx[i]*(xb-xa)+xa;
sdy[i]/=(xb-xa);
}
Spline1DBuildHermite(sx,sy,sdy,m,s);
}
//+------------------------------------------------------------------+
//| This function builds monotone cubic Hermite interpolant. This |
//| interpolant is monotonic in [x(0),x(n-1)] and is constant outside|
//| of this interval. |
//| In case y[] form non-monotonic sequence, interpolant is |
//| piecewise monotonic. Say, for x=(0,1,2,3,4) and y=(0,1,2,1,0) |
//| interpolant will monotonically grow at [0..2] and monotonically |
//| decrease at [2..4]. |
//| INPUT PARAMETERS: |
//| X - spline nodes, array[0..N-1]. Subroutine |
//| automatically sorts points, so caller may pass |
//| unsorted array. |
//| Y - function values, array[0..N-1] |
//| N - the number of points(N>=2). |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant. |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DBuildMonotone(double &X[],double &Y[],int n,
CSpline1DInterpolant &c)
{
//--- create variables
double d[];
double ex[];
double ey[];
int p[];
double delta=0;
double alpha=0;
double beta=0;
int tmpn=0;
int sn=0;
double ca=0;
double cb=0;
double epsilon=0;
int i=0;
int j=0;
double x[];
double y[];
ArrayCopy(x,X);
ArrayCopy(y,Y);
//--- Check lengths of arguments
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N"))
return;
//--- Check and sort points
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values"))
return;
HeapSortPPoints(x,y,p,n);
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close"))
return;
epsilon=CMath::m_machineepsilon;
n+=2;
ArrayResize(d,n);
ArrayResize(ex,n);
ArrayResize(ey,n);
ex[0]=x[0]-MathAbs(x[1]-x[0]);
ex[n-1]=x[n-3]+MathAbs(x[n-3]-x[n-4]);
ey[0]=y[0];
ey[n-1]=y[n-3];
for(i=1; i<=n-2; i++)
{
ex[i]=x[i-1];
ey[i]=y[i-1];
}
//--- Init sign of the function for first segment
i=0;
ca=0;
do
{
ca=ey[i+1]-ey[i];
i++;
}
while(!(ca!=0.0 || i>n-2));
if(ca!=0.0)
ca/=MathAbs(ca);
i=0;
while(i<n-1)
{
//--- Partition of the segment [X0;Xn]
tmpn=1;
for(j=i; j<n-1; j++)
{
cb=ey[j+1]-ey[j];
if((ca*cb)>=0.0)
tmpn++;
else
{
ca=cb/MathAbs(cb);
break;
}
}
sn=i+tmpn;
//--- check
if(!CAp::Assert(tmpn>=2,__FUNCTION__+": internal error"))
return;
//--- Calculate derivatives for current segment
d[i]=0;
d[sn-1]=0;
for(j=i+1; j<sn-1; j++)
d[j]=((ey[j]-ey[j-1])/(ex[j]-ex[j-1])+(ey[j+1]-ey[j])/(ex[j+1]-ex[j]))/2;
for(j=i; j<sn-1; j++)
{
delta=(ey[j+1]-ey[j])/(ex[j+1]-ex[j]);
if(MathAbs(delta)<=epsilon)
{
d[j]=0;
d[j+1]=0;
}
else
{
alpha=d[j]/delta;
beta=d[j+1]/delta;
if(alpha!=0.0)
cb=alpha*MathSqrt(1+CMath::Sqr(beta/alpha));
else
{
if(beta!=0.0)
cb=beta;
else
continue;
}
if(cb>3.0)
{
d[j]=3*alpha*delta/cb;
d[j+1]=3*beta*delta/cb;
}
}
}
//--- Transition to next segment
i=sn-1;
}
Spline1DBuildHermite(ex,ey,d,n,c);
c.m_continuity=2;
}
//+------------------------------------------------------------------+
//| Internal version of Spline1DConvDiff |
//| Converts from Hermite spline given by grid XOld to new grid X2 |
//| INPUT PARAMETERS: |
//| XOld - old grid |
//| YOld - values at old grid |
//| DOld - first derivative at old grid |
//| N - grid size |
//| X2 - new grid |
//| N2 - new grid size |
//| Y - possibly preallocated output array |
//| (reallocate if too small) |
//| NeedY - do we need Y? |
//| D1 - possibly preallocated output array |
//| (reallocate if too small) |
//| NeedD1 - do we need D1? |
//| D2 - possibly preallocated output array |
//| (reallocate if too small) |
//| NeedD2 - do we need D1? |
//| OUTPUT ARRAYS: |
//| Y - values, if needed |
//| D1 - first derivative, if needed |
//| D2 - second derivative, if needed |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DConvDiffInternal(double &xold[],double &yold[],
double &dold[],const int n,
double &x2[],const int n2,
double &y[],const bool needy,
double &d1[],const bool needd1,
double &d2[],const bool needd2)
{
//--- create variables
int intervalindex=0;
int pointindex=0;
bool havetoadvance;
double c0=0;
double c1=0;
double c2=0;
double c3=0;
double a=0;
double b=0;
double w=0;
double w2=0;
double w3=0;
double fa=0;
double fb=0;
double da=0;
double db=0;
double t=0;
//--- Prepare space
if(needy && CAp::Len(y)<n2)
ArrayResize(y,n2);
//--- check
if(needd1 && CAp::Len(d1)<n2)
ArrayResize(d1,n2);
//--- check
if(needd2 && CAp::Len(d2)<n2)
ArrayResize(d2,n2);
//--- These assignments aren't actually needed
//--- (variables are initialized in the loop below),
//--- but without them compiler will complain about uninitialized locals
c0=0;
c1=0;
c2=0;
c3=0;
a=0;
b=0;
//--- Cycle
intervalindex=-1;
pointindex=0;
//--- calculation
while(true)
{
//--- are we ready to exit?
if(pointindex>=n2)
break;
t=x2[pointindex];
//--- do we need to advance interval?
havetoadvance=false;
//--- check
if(intervalindex==-1)
havetoadvance=true;
else
{
//--- check
if(intervalindex<n-2)
havetoadvance=t>=b;
}
//--- check
if(havetoadvance)
{
//--- change values
intervalindex=intervalindex+1;
a=xold[intervalindex];
b=xold[intervalindex+1];
w=b-a;
w2=w*w;
w3=w*w2;
fa=yold[intervalindex];
fb=yold[intervalindex+1];
da=dold[intervalindex];
db=dold[intervalindex+1];
c0=fa;
c1=da;
c2=(3*(fb-fa)-2*da*w-db*w)/w2;
c3=(2*(fa-fb)+da*w+db*w)/w3;
continue;
}
//--- Calculate spline and its derivatives using power basis
t=t-a;
if(needy)
y[pointindex]=c0+t*(c1+t*(c2+t*c3));
//--- check
if(needd1)
d1[pointindex]=c1+2*t*c2+3*t*t*c3;
//--- check
if(needd2)
d2[pointindex]=2*c2+6*t*c3;
//--- change value
pointindex=pointindex+1;
}
}
//+------------------------------------------------------------------+
//| Internal subroutine. Heap sort. |
//+------------------------------------------------------------------+
void CSpline1D::HeapSortDPoints(double &x[],double &y[],double &d[],
const int n)
{
//--- create arrays
double rbuf[];
int ibuf[];
double rbuf2[];
int ibuf2[];
//--- allocation
ArrayResize(ibuf,n);
ArrayResize(rbuf,n);
for(int i=0; i<n; i++)
ibuf[i]=i;
//--- function call
CTSort::TagSortFastI(x,ibuf,rbuf2,ibuf2,n);
//--- copy
for(int i=0; i<n; i++)
rbuf[i]=y[ibuf[i]];
ArrayCopy(y,rbuf);
for(int i=0; i<n; i++)
rbuf[i]=d[ibuf[i]];
ArrayCopy(d,rbuf);
}
//+------------------------------------------------------------------+
//| Internal subroutine. Heap sort. |
//+------------------------------------------------------------------+
void CSpline1D::HeapSortDPoints(CRowDouble &x,CRowDouble &y,CRowDouble &d,
const int n)
{
//--- create arrays
CRowDouble rbuf;
CRowInt ibuf;
CRowDouble rbuf2;
CRowInt ibuf2;
//--- allocation
ibuf.Resize(n);
rbuf.Resize(n);
for(int i=0; i<n; i++)
ibuf.Set(i,i);
//--- function call
CTSort::TagSortFastI(x,ibuf,rbuf2,ibuf2,n);
//--- copy
for(int i=0; i<n; i++)
rbuf.Set(i,y[ibuf[i]]);
for(int i_=0; i_<n; i_++)
y.Set(i_,rbuf[i_]);
for(int i=0; i<n; i++)
rbuf.Set(i,d[ibuf[i]]);
for(int i_=0; i_<n; i_++)
d.Set(i_,rbuf[i_]);
}
//+------------------------------------------------------------------+
//| Internal version of Spline1DGridDiffCubic. |
//| Accepts pre-ordered X/Y, temporary arrays (which may be |
//| preallocated, if you want to save time, or not) and output array |
//| (which may be preallocated too). |
//| Y is passed as var-parameter because we may need to force last |
//| element to be equal to the first one (if periodic boundary |
//| conditions are specified). |
//+------------------------------------------------------------------+
void CSpline1D::Spline1DGridDiffCubicInternal(double &x[],double &y[],
const int n,const int boundltype,
const double boundl,
const int boundrtype,
const double boundr,
double &d[],double &a1[],
double &a2[],double &a3[],
double &b[],double &dt[])
{
//--- create variables
int i=0;
int i_=0;
//--- allocate arrays
if(CAp::Len(d)<n)
ArrayResize(d,n);
//--- check
if(CAp::Len(a1)<n)
ArrayResize(a1,n);
//--- check
if(CAp::Len(a2)<n)
ArrayResize(a2,n);
//--- check
if(CAp::Len(a3)<n)
ArrayResize(a3,n);
//--- check
if(CAp::Len(b)<n)
ArrayResize(b,n);
//--- check
if(CAp::Len(dt)<n)
ArrayResize(dt,n);
//--- Special cases:
//--- * N=2,parabolic terminated boundary condition on both ends
//--- * N=2,periodic boundary condition
if((n==2 && boundltype==0) && boundrtype==0)
{
//--- change values
d[0]=(y[1]-y[0])/(x[1]-x[0]);
d[1]=d[0];
//--- exit the function
return;
}
//--- check
if((n==2 && boundltype==-1) && boundrtype==-1)
{
//--- change values
d[0]=0;
d[1]=0;
//--- exit the function
return;
}
//--- Periodic and non-periodic boundary conditions are
//--- two separate classes
if(boundrtype==-1 && boundltype==-1)
{
//--- Periodic boundary conditions
y[n-1]=y[0];
//--- Boundary conditions at N-1 points
//--- (one point less because last point is the same as first point).
a1[0]=x[1]-x[0];
a2[0]=2*(x[1]-x[0]+x[n-1]-x[n-2]);
a3[0]=x[n-1]-x[n-2];
b[0]=3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])*(x[1]-x[0])+3*(y[1]-y[0])/(x[1]-x[0])*(x[n-1]-x[n-2]);
//--- calculation
for(i=1; i<=n-2; i++)
{
//--- Altough last point is [N-2],we use X[N-1] and Y[N-1]
//--- (because of periodicity)
a1[i]=x[i+1]-x[i];
a2[i]=2*(x[i+1]-x[i-1]);
a3[i]=x[i]-x[i-1];
b[i]=3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
}
//--- Solve,add last point (with index N-1)
SolveCyclicTridiagonal(a1,a2,a3,b,n-1,dt);
for(i_=0; i_<=n-2; i_++)
d[i_]=dt[i_];
d[n-1]=d[0];
}
else
{
//--- Non-periodic boundary condition.
//--- Left boundary conditions.
if(boundltype==0)
{
//--- change values
a1[0]=0;
a2[0]=1;
a3[0]=1;
b[0]=2*(y[1]-y[0])/(x[1]-x[0]);
}
//--- check
if(boundltype==1)
{
//--- change values
a1[0]=0;
a2[0]=1;
a3[0]=0;
b[0]=boundl;
}
//--- check
if(boundltype==2)
{
//--- change values
a1[0]=0;
a2[0]=2;
a3[0]=1;
b[0]=3*(y[1]-y[0])/(x[1]-x[0])-0.5*boundl*(x[1]-x[0]);
}
//--- Central conditions
for(i=1; i<=n-2; i++)
{
a1[i]=x[i+1]-x[i];
a2[i]=2*(x[i+1]-x[i-1]);
a3[i]=x[i]-x[i-1];
b[i]=3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
}
//--- Right boundary conditions
if(boundrtype==0)
{
//--- change values
a1[n-1]=1;
a2[n-1]=1;
a3[n-1]=0;
b[n-1]=2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2]);
}
//--- check
if(boundrtype==1)
{
//--- change values
a1[n-1]=0;
a2[n-1]=1;
a3[n-1]=0;
b[n-1]=boundr;
}
//--- check
if(boundrtype==2)
{
//--- change values
a1[n-1]=1;
a2[n-1]=2;
a3[n-1]=0;
b[n-1]=3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])+0.5*boundr*(x[n-1]-x[n-2]);
}
//--- Solve
SolveTridiagonal(a1,a2,a3,b,n,d);
}
}
//+------------------------------------------------------------------+
//| Internal subroutine. Heap sort. |
//+------------------------------------------------------------------+
void CSpline1D::HeapSortPoints(double &x[],double &y[],const int n)
{
//--- create arrays
double bufx[];
double bufy[];
//--- function call
CTSort::TagSortFastR(x,y,bufx,bufy,n);
}
//+------------------------------------------------------------------+
//| Internal subroutine. Heap sort. |
//| Accepts: |
//| X, Y - points |
//| P - empty or preallocated array |
//| Returns: |
//| X, Y - sorted by X |
//| P - array of permutations; I-th position of output |
//| arrays X/Y contains(X[P[I]],Y[P[I]]) |
//+------------------------------------------------------------------+
void CSpline1D::HeapSortPPoints(double &x[],double &y[],int &p[],
const int n)
{
//--- create arrays
double rbuf[];
int ibuf[];
//--- check
if(CAp::Len(p)<n)
ArrayResizeAL(p,n);
//--- allocation
ArrayResize(rbuf,n);
//--- initialization
for(int i=0; i<n; i++)
p[i]=i;
//--- function call
CTSort::TagSortFastI(x,p,rbuf,ibuf,n);
//--- copy
for(int i=0; i<n; i++)
rbuf[i]=y[p[i]];
for(int i_=0; i_<n; i_++)
y[i_]=rbuf[i_];
}
//+------------------------------------------------------------------+
//| Internal subroutine. Tridiagonal solver. Solves |
//| ( B[0] C[0] ) |
//| ( A[1] B[1] C[1] ) |
//| ( A[2] B[2] C[2] ) |
//| ( .......... ) * X=D |
//| ( .......... ) |
//| ( A[N-2] B[N-2] C[N-2] ) |
//| ( A[N-1] B[N-1] ) |
//+------------------------------------------------------------------+
void CSpline1D::SolveTridiagonal(double &a[],double &cb[],double &c[],
double &cd[],const int n,double &x[])
{
double t=0;
//--- create arrays
double d[];
double b[];
//--- copy arrays
ArrayCopy(d,cd);
ArrayCopy(b,cb);
//--- check
if(CAp::Len(x)<n)
ArrayResize(x,n);
//--- calculation
for(int k=1; k<n; k++)
{
t=a[k]/b[k-1];
b[k]=b[k]-t*c[k-1];
d[k]=d[k]-t*d[k-1];
}
x[n-1]=d[n-1]/b[n-1];
for(int k=n-2; k>=0; k--)
x[k]=(d[k]-c[k]*x[k+1])/b[k];
}
//+------------------------------------------------------------------+
//| Internal subroutine. Cyclic tridiagonal solver. Solves |
//| ( B[0] C[0] A[0] ) |
//| ( A[1] B[1] C[1] ) |
//| ( A[2] B[2] C[2] ) |
//| ( .......... ) * X=D |
//| ( .......... ) |
//| ( A[N-2] B[N-2] C[N-2] ) |
//| ( C[N-1] A[N-1] B[N-1] ) |
//+------------------------------------------------------------------+
void CSpline1D::SolveCyclicTridiagonal(double &a[],double &cb[],
double &c[],double &d[],
const int n,double &x[])
{
//--- create variables
double alpha=0;
double beta=0;
double gamma=0;
//--- create arrays
double y[];
double z[];
double u[];
double b[];
//--- copy array
ArrayCopy(b,cb);
//--- check
if(CAp::Len(x)<n)
ArrayResize(x,n);
//--- change values
beta=a[0];
alpha=c[n-1];
gamma=-b[0];
b[0]=2*b[0];
b[n-1]=b[n-1]-alpha*beta/gamma;
//--- allocation
ArrayResize(u,n);
//--- initialization
for(int k=0; k<n; k++)
u[k]=0;
u[0]=gamma;
u[n-1]=alpha;
//--- function call
SolveTridiagonal(a,b,c,d,n,y);
//--- function call
SolveTridiagonal(a,b,c,u,n,z);
//--- calculation
for(int k=0; k<n; k++)
x[k]=y[k]-(y[0]+beta/gamma*y[n-1])/(1+z[0]+beta/gamma*z[n-1])*z[k];
}
//+------------------------------------------------------------------+
//| Internal subroutine. Three-point differentiation |
//+------------------------------------------------------------------+
double CSpline1D::DiffThreePoint(double t,const double x0,const double f0,
double x1,const double f1,double x2,
const double f2)
{
//--- create variables
double a=0;
double b=0;
//--- change values
t=t-x0;
x1=x1-x0;
x2=x2-x0;
a=(f2-f0-x2/x1*(f1-f0))/(CMath::Sqr(x2)-x1*x2);
b=(f1-f0-a*CMath::Sqr(x1))/x1;
//--- return result
return(2*a*t+b);
}
//+------------------------------------------------------------------+
//| Polynomial fitting report: |
//| TaskRCond reciprocal of task's condition number |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//+------------------------------------------------------------------+
class CPolynomialFitReport
{
public:
//--- variables
double m_taskrcond;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
double m_maxerror;
//--- constructor, destructor
CPolynomialFitReport(void) { ZeroMemory(this); }
~CPolynomialFitReport(void) {}
//--- copy
void Copy(CPolynomialFitReport &obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CPolynomialFitReport::Copy(CPolynomialFitReport &obj)
{
//--- copy variables
m_taskrcond=obj.m_taskrcond;
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_maxerror=obj.m_maxerror;
}
//+------------------------------------------------------------------+
//| Polynomial fitting report: |
//| TaskRCond reciprocal of task's condition number |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//+------------------------------------------------------------------+
class CPolynomialFitReportShell
{
private:
CPolynomialFitReport m_innerobj;
public:
//--- constructors, destructor
CPolynomialFitReportShell(void) {}
CPolynomialFitReportShell(CPolynomialFitReport &obj) { m_innerobj.Copy(obj); }
~CPolynomialFitReportShell(void) {}
//--- methods
double GetTaskRCond(void);
void SetTaskRCond(const double d);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetMaxError(void);
void SetMaxError(const double d);
CPolynomialFitReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Returns the value of the variable taskrcond |
//+------------------------------------------------------------------+
double CPolynomialFitReportShell::GetTaskRCond(void)
{
return(m_innerobj.m_taskrcond);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable taskrcond |
//+------------------------------------------------------------------+
void CPolynomialFitReportShell::SetTaskRCond(const double d)
{
m_innerobj.m_taskrcond=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CPolynomialFitReportShell::GetRMSError(void)
{
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CPolynomialFitReportShell::SetRMSError(const double d)
{
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CPolynomialFitReportShell::GetAvgError(void)
{
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CPolynomialFitReportShell::SetAvgError(const double d)
{
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CPolynomialFitReportShell::GetAvgRelError(void)
{
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CPolynomialFitReportShell::SetAvgRelError(const double d)
{
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable maxerror |
//+------------------------------------------------------------------+
double CPolynomialFitReportShell::GetMaxError(void)
{
return(m_innerobj.m_maxerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable maxerror |
//+------------------------------------------------------------------+
void CPolynomialFitReportShell::SetMaxError(const double d)
{
m_innerobj.m_maxerror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CPolynomialFitReport *CPolynomialFitReportShell::GetInnerObj(void)
{
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Barycentric fitting report: |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| TaskRCond reciprocal of task's condition number |
//+------------------------------------------------------------------+
class CBarycentricFitReport
{
public:
//--- variables
double m_taskrcond;
int m_dbest;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
double m_maxerror;
//--- constructor, destructor
CBarycentricFitReport(void) { ZeroMemory(this); }
~CBarycentricFitReport(void) {}
//--- copy
void Copy(CBarycentricFitReport &obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CBarycentricFitReport::Copy(CBarycentricFitReport &obj)
{
//--- copy variables
m_taskrcond=obj.m_taskrcond;
m_dbest=obj.m_dbest;
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_maxerror=obj.m_maxerror;
}
//+------------------------------------------------------------------+
//| Barycentric fitting report: |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| TaskRCond reciprocal of task's condition number |
//+------------------------------------------------------------------+
class CBarycentricFitReportShell
{
private:
CBarycentricFitReport m_innerobj;
public:
//--- constructors, destructor
CBarycentricFitReportShell(void) {}
CBarycentricFitReportShell(CBarycentricFitReport &obj) { m_innerobj.Copy(obj); }
~CBarycentricFitReportShell(void) {}
//--- methods
double GetTaskRCond(void);
void SetTaskRCond(const double d);
int GetDBest(void);
void SetDBest(const int i);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetMaxError(void);
void SetMaxError(const double d);
CBarycentricFitReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Returns the value of the variable taskrcond |
//+------------------------------------------------------------------+
double CBarycentricFitReportShell::GetTaskRCond(void)
{
return(m_innerobj.m_taskrcond);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable taskrcond |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetTaskRCond(const double d)
{
m_innerobj.m_taskrcond=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable dbest |
//+------------------------------------------------------------------+
int CBarycentricFitReportShell::GetDBest(void)
{
return(m_innerobj.m_dbest);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable dbest |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetDBest(const int i)
{
m_innerobj.m_dbest=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CBarycentricFitReportShell::GetRMSError(void)
{
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetRMSError(const double d)
{
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CBarycentricFitReportShell::GetAvgError(void)
{
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetAvgError(const double d)
{
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CBarycentricFitReportShell::GetAvgRelError(void)
{
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetAvgRelError(const double d)
{
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable maxerror |
//+------------------------------------------------------------------+
double CBarycentricFitReportShell::GetMaxError(void)
{
return(m_innerobj.m_maxerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable maxerror |
//+------------------------------------------------------------------+
void CBarycentricFitReportShell::SetMaxError(const double d)
{
m_innerobj.m_maxerror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CBarycentricFitReport *CBarycentricFitReportShell::GetInnerObj(void)
{
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Least squares fitting report: |
//| TaskRCond reciprocal of task's condition number |
//| IterationsCount number of internal iterations |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| WRMSError weighted RMS error |
//+------------------------------------------------------------------+
class CLSFitReport
{
public:
//--- variables
int m_iterationscount;
int m_varidx;
double m_avgerror;
double m_avgrelerror;
double m_maxerror;
double m_r2;
double m_rmserror;
double m_taskrcond;
double m_wrmserror;
CRowDouble m_errcurve;
CRowDouble m_errpar;
CRowDouble m_noise;
CMatrixDouble m_covpar;
//--- constructor, destructor
CLSFitReport(void);
~CLSFitReport(void) {}
//--- copy
void Copy(CLSFitReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CLSFitReport::CLSFitReport(void)
{
m_iterationscount=0;
m_varidx=0;
m_avgerror=0;
m_avgrelerror=0;
m_maxerror=0;
m_r2=0;
m_rmserror=0;
m_taskrcond=0;
m_wrmserror=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CLSFitReport::Copy(CLSFitReport &obj)
{
//--- copy variables
m_iterationscount=obj.m_iterationscount;
m_varidx=obj.m_varidx;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_maxerror=obj.m_maxerror;
m_r2=obj.m_r2;
m_rmserror=obj.m_rmserror;
m_taskrcond=obj.m_taskrcond;
m_wrmserror=obj.m_wrmserror;
m_errcurve=obj.m_errcurve;
m_errpar=obj.m_errpar;
m_noise=obj.m_noise;
m_covpar=obj.m_covpar;
}
//+------------------------------------------------------------------+
//| Least squares fitting report: |
//| TaskRCond reciprocal of task's condition number |
//| IterationsCount number of internal iterations |
//| RMSError RMS error |
//| AvgError average error |
//| AvgRelError average relative error (for non-zero Y[I]) |
//| MaxError maximum error |
//| WRMSError weighted RMS error |
//+------------------------------------------------------------------+
class CLSFitReportShell
{
private:
CLSFitReport m_innerobj;
public:
//--- constructors, destructor
CLSFitReportShell(void) {}
CLSFitReportShell(CLSFitReport &obj) { m_innerobj.Copy(obj); }
~CLSFitReportShell(void) {}
//--- methods
double GetTaskRCond(void);
void SetTaskRCond(const double d);
int GetIterationsCount(void);
void SetIterationsCount(const int i);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetMaxError(void);
void SetMaxError(const double d);
double GetWRMSError(void);
void SetWRMSError(const double d);
CLSFitReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Returns the value of the variable taskrcond |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetTaskRCond(void)
{
return(m_innerobj.m_taskrcond);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable taskrcond |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetTaskRCond(const double d)
{
m_innerobj.m_taskrcond=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable iterationscount |
//+------------------------------------------------------------------+
int CLSFitReportShell::GetIterationsCount(void)
{
return(m_innerobj.m_iterationscount);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable iterationscount |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetIterationsCount(const int i)
{
m_innerobj.m_iterationscount=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetRMSError(void)
{
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetRMSError(const double d)
{
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetAvgError(void)
{
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetAvgError(const double d)
{
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetAvgRelError(void)
{
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetAvgRelError(const double d)
{
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable maxerror |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetMaxError(void)
{
return(m_innerobj.m_maxerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable maxerror |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetMaxError(const double d)
{
m_innerobj.m_maxerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable wrmserror |
//+------------------------------------------------------------------+
double CLSFitReportShell::GetWRMSError(void)
{
return(m_innerobj.m_wrmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable wrmserror |
//+------------------------------------------------------------------+
void CLSFitReportShell::SetWRMSError(const double d)
{
m_innerobj.m_wrmserror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CLSFitReport *CLSFitReportShell::GetInnerObj(void)
{
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Nonlinear fitter. |
//| You should use ALGLIB functions to work with fitter. |
//| Never try to access its fields directly! |
//+------------------------------------------------------------------+
class CLSFitState
{
public:
//--- variables
int m_k;
int m_m;
int m_maxits;
int m_nec;
int m_nic;
int m_npoints;
int m_nweights;
int m_optalgo;
int m_pointindex;
int m_prevalgo;
int m_prevnpt;
int m_repiterationscount;
int m_repterminationtype;
int m_repvaridx;
int m_wits;
int m_wkind;
double m_diffstep;
double m_epsx;
double m_f;
double m_repavgerror;
double m_repavgrelerror;
double m_repmaxerror;
double m_reprmserror;
double m_repwrmserror;
double m_stpmax;
double m_teststep;
double m_tmpnoise;
bool m_needf;
bool m_needfg;
bool m_needfgh;
bool m_xrep;
bool m_xupdated;
RCommState m_rstate;
CRowInt m_tmpct;
CRowDouble m_bndl;
CRowDouble m_bndu;
CRowDouble m_c0;
CRowDouble m_c1;
CRowDouble m_c;
CRowDouble m_g;
CRowDouble m_s;
CRowDouble m_taskw;
CRowDouble m_tasky;
CRowDouble m_tmp;
CRowDouble m_tmpf;
CRowDouble m_wcur;
CRowDouble m_x;
CMinLMState m_optstate;
CMinLMReport m_optrep;
CMatrixDouble m_cleic;
CMatrixDouble m_h;
CMatrixDouble m_taskx;
CMatrixDouble m_tmpjac;
CMatrixDouble m_tmpjacw;
CMatInvReport m_invrep;
CLSFitReport m_rep;
//--- constructor, destructor
CLSFitState(void);
~CLSFitState(void) {}
//--- copy
void Copy(const CLSFitState &obj);
//--- overloading
void operator=(const CLSFitState &obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CLSFitState::CLSFitState(void)
{
m_k=0;
m_m=0;
m_maxits=0;
m_nec=0;
m_nic=0;
m_npoints=0;
m_nweights=0;
m_optalgo=0;
m_pointindex=0;
m_prevalgo=0;
m_prevnpt=0;
m_repiterationscount=0;
m_repterminationtype=0;
m_repvaridx=0;
m_wits=0;
m_wkind=0;
m_diffstep=0;
m_epsx=0;
m_f=0;
m_repavgerror=0;
m_repavgrelerror=0;
m_repmaxerror=0;
m_reprmserror=0;
m_repwrmserror=0;
m_stpmax=0;
m_teststep=0;
m_tmpnoise=0;
m_needf=false;
m_needfg=false;
m_needfgh=false;
m_xrep=false;
m_xupdated=false;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CLSFitState::Copy(const CLSFitState &obj)
{
//--- copy variables
m_k=obj.m_k;
m_m=obj.m_m;
m_maxits=obj.m_maxits;
m_nec=obj.m_nec;
m_nic=obj.m_nic;
m_npoints=obj.m_npoints;
m_nweights=obj.m_nweights;
m_optalgo=obj.m_optalgo;
m_pointindex=obj.m_pointindex;
m_prevalgo=obj.m_prevalgo;
m_prevnpt=obj.m_prevnpt;
m_repiterationscount=obj.m_repiterationscount;
m_repterminationtype=obj.m_repterminationtype;
m_repvaridx=obj.m_repvaridx;
m_wits=obj.m_wits;
m_wkind=obj.m_wkind;
m_diffstep=obj.m_diffstep;
m_epsx=obj.m_epsx;
m_f=obj.m_f;
m_repavgerror=obj.m_repavgerror;
m_repavgrelerror=obj.m_repavgrelerror;
m_repmaxerror=obj.m_repmaxerror;
m_reprmserror=obj.m_reprmserror;
m_repwrmserror=obj.m_repwrmserror;
m_stpmax=obj.m_stpmax;
m_teststep=obj.m_teststep;
m_tmpnoise=obj.m_tmpnoise;
m_needf=obj.m_needf;
m_needfg=obj.m_needfg;
m_needfgh=obj.m_needfgh;
m_xrep=obj.m_xrep;
m_xupdated=obj.m_xupdated;
m_rstate=obj.m_rstate;
m_tmpct=obj.m_tmpct;
m_bndl=obj.m_bndl;
m_bndu=obj.m_bndu;
m_c0=obj.m_c0;
m_c1=obj.m_c1;
m_c=obj.m_c;
m_g=obj.m_g;
m_s=obj.m_s;
m_taskw=obj.m_taskw;
m_tasky=obj.m_tasky;
m_tmp=obj.m_tmp;
m_tmpf=obj.m_tmpf;
m_wcur=obj.m_wcur;
m_x=obj.m_x;
m_optstate=obj.m_optstate;
m_optrep=obj.m_optrep;
m_cleic=obj.m_cleic;
m_h=obj.m_h;
m_taskx=obj.m_taskx;
m_tmpjac=obj.m_tmpjac;
m_tmpjacw=obj.m_tmpjacw;
m_invrep=obj.m_invrep;
m_rep=obj.m_rep;
}
//+------------------------------------------------------------------+
//| Nonlinear fitter. |
//| You should use ALGLIB functions to work with fitter. |
//| Never try to access its fields directly! |
//+------------------------------------------------------------------+
class CLSFitStateShell
{
private:
CLSFitState m_innerobj;
public:
//--- constructors, destructor
CLSFitStateShell(void) {}
CLSFitStateShell(CLSFitState &obj) { m_innerobj.Copy(obj); }
~CLSFitStateShell(void) {}
//--- methods
bool GetNeedF(void);
void SetNeedF(const bool b);
bool GetNeedFG(void);
void SetNeedFG(const bool b);
bool GetNeedFGH(void);
void SetNeedFGH(const bool b);
bool GetXUpdated(void);
void SetXUpdated(const bool b);
double GetF(void);
void SetF(const double d);
CLSFitState *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Returns the value of the variable needf |
//+------------------------------------------------------------------+
bool CLSFitStateShell::GetNeedF(void)
{
return(m_innerobj.m_needf);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable needf |
//+------------------------------------------------------------------+
void CLSFitStateShell::SetNeedF(const bool b)
{
m_innerobj.m_needf=b;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable needfg |
//+------------------------------------------------------------------+
bool CLSFitStateShell::GetNeedFG(void)
{
return(m_innerobj.m_needfg);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable needfg |
//+------------------------------------------------------------------+
void CLSFitStateShell::SetNeedFG(const bool b)
{
m_innerobj.m_needfg=b;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable needfgh |
//+------------------------------------------------------------------+
bool CLSFitStateShell::GetNeedFGH(void)
{
return(m_innerobj.m_needfgh);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable needfgh |
//+------------------------------------------------------------------+
void CLSFitStateShell::SetNeedFGH(const bool b)
{
m_innerobj.m_needfgh=b;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable xupdated |
//+------------------------------------------------------------------+
bool CLSFitStateShell::GetXUpdated(void)
{
return(m_innerobj.m_xupdated);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable xupdated |
//+------------------------------------------------------------------+
void CLSFitStateShell::SetXUpdated(const bool b)
{
m_innerobj.m_xupdated=b;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable f |
//+------------------------------------------------------------------+
double CLSFitStateShell::GetF(void)
{
return(m_innerobj.m_f);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable f |
//+------------------------------------------------------------------+
void CLSFitStateShell::SetF(const double d)
{
m_innerobj.m_f=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CLSFitState *CLSFitStateShell::GetInnerObj(void)
{
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Least squares fitting |
//+------------------------------------------------------------------+
class CLSFit
{
public:
//--- class constant
static const int m_rfsmax;
//--- public methods
static void PolynomialFit(double &x[],double &y[],const int n,const int m,int &info,CBarycentricInterpolant &p,CPolynomialFitReport &rep);
static void PolynomialFitWC(double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,CBarycentricInterpolant &p,CPolynomialFitReport &rep);
static void BarycentricFitFloaterHormannWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
static void BarycentricFitFloaterHormann(double &x[],double &y[],const int n,const int m,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
static void Spline1DFitCubicWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void Spline1DFitHermiteWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void Spline1DFitCubic(double &x[],double &y[],const int n,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void Spline1DFitHermite(double &x[],double &y[],const int n,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void LSFitLinearW(double &y[],double &w[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
static void LSFitLinearWC(double &cy[],double &w[],CMatrixDouble &fmatrix,CMatrixDouble &ccmatrix,const int n,const int m,const int k,int &info,double &c[],CLSFitReport &rep);
static void LSFitLinear(double &y[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
static void LSFitLinear(CRowDouble &y,CMatrixDouble &fmatrix,const int n,const int m,int &info,CRowDouble &c,CLSFitReport &rep);
static void LSFitLinearC(double &cy[],CMatrixDouble &fmatrix,CMatrixDouble &cmatrix,const int n,const int m,const int k,int &info,double &c[],CLSFitReport &rep);
static void LSFitCreateWF(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,const double diffstep,CLSFitState &State);
static void LSFitCreateF(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,const double diffstep,CLSFitState &State);
static void LSFitCreateWFG(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,bool cheapfg,CLSFitState &State);
static void LSFitCreateFG(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,const bool cheapfg,CLSFitState &State);
static void LSFitCreateWFGH(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,CLSFitState &State);
static void LSFitCreateFGH(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,CLSFitState &State);
static void LSFitSetCond(CLSFitState &State,const double epsx,const int maxits);
static void LSFitSetStpMax(CLSFitState &State,const double stpmax);
static void LSFitSetXRep(CLSFitState &State,const bool needxrep);
static void LSFitSetScale(CLSFitState &State,double &s[]);
static void LSFitSetBC(CLSFitState &State,double &bndl[],double &bndu[]);
static void LSFitResults(CLSFitState &State,int &info,double &c[],CLSFitReport &rep);
static void LSFitScaleXY(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,double &xa,double &xb,double &sa,double &sb,double &xoriginal[],double &yoriginal[]);
static bool LSFitIteration(CLSFitState &State);
static double LogisticCalc4(double x,double a,double b,double c,double d);
static double LogisticCalc5(double x,double a,double b,double c,double d,double g);
static void LogisticFit4(CRowDouble &X,CRowDouble &Y,int n,double &a,double &b,double &c,double &d,CLSFitReport &rep);
static void LogisticFit4ec(CRowDouble &X,CRowDouble &Y,int n,double cnstrleft,double cnstrright,double &a,double &b,double &c,double &d,CLSFitReport &rep);
static void LogisticFit5(CRowDouble &x,CRowDouble &Y,int n,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
static void LogisticFit5ec(CRowDouble &X,CRowDouble &Y,int n,double cnstrleft,double cnstrright,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
static void LogisticFit45x(CRowDouble &x,CRowDouble &y,int n,double cnstrleft,double cnstrright,bool is4pl,double lambdav,double epsx,int rscnt,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
private:
static void Spline1DFitInternal(const int st,double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
static void LSFitLinearInternal(double &y[],double &w[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
static void LSFitClearRequestFields(CLSFitState &State);
static void BarycentricCalcBasis(CBarycentricInterpolant &b,const double t,double &y[]);
static void InternalChebyshevFit(double &x[],double &y[],double &w[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,double &c[],CLSFitReport &rep);
static void BarycentricFitWCFixedD(double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,const int d,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
static void EstimateErrors(CMatrixDouble &f1,CRowDouble &f0,CRowDouble &y,CRowDouble &w,CRowDouble &x,CRowDouble &s,int n,int k,CLSFitReport &rep,CMatrixDouble &z,int zkind);
static void ClearReport(CLSFitReport &rep);
static void LogisticFit45Errors(CRowDouble &x,CRowDouble &y,int n,double a,double b,double c,double d,double g,CLSFitReport &rep);
static void LogisticFitInternal(CRowDouble &x,CRowDouble &y,int n,bool is4pl,double lambdav,CMinLMState &state,CMinLMReport &replm,CRowDouble &p1,double &flast);
};
//+------------------------------------------------------------------+
//| Initialize constant |
//+------------------------------------------------------------------+
const int CLSFit::m_rfsmax=10;
//+------------------------------------------------------------------+
//| Fitting by polynomials in barycentric form. This function |
//| provides simple unterface for unconstrained unweighted fitting. |
//| See PolynomialFitWC() if you need constrained fitting. |
//| Task is linear, so linear least squares solver is used. |
//| Complexity of this computational scheme is O(N*M^2), mostly |
//| dominated by least squares solver |
//| SEE ALSO: |
//| PolynomialFitWC() |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| N - number of points, N>0 |
//| * if given, only leading N elements of X/Y are used |
//| * if not given, automatically determined from sizes |
//| of X/Y |
//| M - number of basis functions (= polynomial_degree + 1), |
//| M>=1 |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearW() subroutine: |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| P - interpolant in barycentric form. |
//| Rep - report, same format as in LSFitLinearW() subroutine. |
//| Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| NOTES: |
//| you can convert P from barycentric form to the power or |
//| Chebyshev basis with PolynomialBar2Pow() or |
//| PolynomialBar2Cheb() functions from POLINT subpackage. |
//+------------------------------------------------------------------+
void CLSFit::PolynomialFit(double &x[],double &y[],const int n,
const int m,int &info,
CBarycentricInterpolant &p,
CPolynomialFitReport &rep)
{
//--- create arrays
double w[];
double xc[];
double yc[];
int dc[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1);
//--- function call
PolynomialFitWC(x,y,w,n,xc,yc,dc,0,m,info,p,rep);
}
//+------------------------------------------------------------------+
//| Weighted fitting by polynomials in barycentric form, with |
//| constraints on function values or first derivatives. |
//| Small regularizing term is used when solving constrained tasks |
//| (to improve stability). |
//| Task is linear, so linear least squares solver is used. |
//| Complexity of this computational scheme is O(N*M^2), mostly |
//| dominated by least squares solver |
//| SEE ALSO: |
//| PolynomialFit() |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| Each summand in square sum of approximation |
//| deviations from given values is multiplied by the |
//| square of corresponding weight. Fill it by 1's if you|
//| don't want to solve weighted task. |
//| N - number of points, N>0. |
//| * if given, only leading N elements of X/Y/W are used|
//| * if not given, automatically determined from sizes |
//| of X/Y/W |
//| XC - points where polynomial values/derivatives are |
//| constrained, array[0..K-1]. |
//| YC - values of constraints, array[0..K-1] |
//| DC - array[0..K-1], types of constraints: |
//| * DC[i]=0 means that P(XC[i])=YC[i] |
//| * DC[i]=1 means that P'(XC[i])=YC[i] |
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
//| K - number of constraints, 0<=K<M. |
//| K=0 means no constraints (XC/YC/DC are not used in |
//| such cases) |
//| M - number of basis functions (= polynomial_degree + 1), |
//| M>=1 |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearW() subroutine: |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| P - interpolant in barycentric form. |
//| Rep - report, same format as in LSFitLinearW() subroutine. |
//| Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//| NOTES: |
//| you can convert P from barycentric form to the power or |
//| Chebyshev basis with PolynomialBar2Pow() or |
//| PolynomialBar2Cheb() functions from POLINT subpackage. |
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
//| Setting constraints can lead to undesired results, like |
//| ill-conditioned behavior, or inconsistency being detected. |
//| From the other side, it allows us to improve quality of the fit. |
//| Here we summarize our experience with constrained regression |
//| splines: |
//| * even simple constraints can be inconsistent, see Wikipedia |
//| article on this subject: |
//| http://en.wikipedia.org/wiki/Birkhoff_interpolation |
//| * the greater is M (given fixed constraints), the more chances |
//| that constraints will be consistent |
//| * in the general case, consistency of constraints is NOT |
//| GUARANTEED. |
//| * in the one special cases, however, we can guarantee |
//| consistency. This case is: M>1 and constraints on the |
//| function values (NOT DERIVATIVES) |
//| Our final recommendation is to use constraints WHEN AND ONLY when|
//| you can't solve your task without them. Anything beyond special |
//| cases given above is not guaranteed and may result in |
//| inconsistency. |
//+------------------------------------------------------------------+
void CLSFit::PolynomialFitWC(double &cx[],double &cy[],double &cw[],
const int n,double &cxc[],double &cyc[],
int &dc[],const int k,const int m,
int &info,CBarycentricInterpolant &p,
CPolynomialFitReport &rep)
{
//--- create variables
double xa=0;
double xb=0;
double sa=0;
double sb=0;
double u=0;
double v=0;
double s=0;
int relcnt=0;
//--- create arrays
double xoriginal[];
double yoriginal[];
double y2[];
double w2[];
double tmp[];
double tmp2[];
double bx[];
double by[];
double bw[];
double x[];
double y[];
double w[];
double xc[];
double yc[];
//--- object of class
CLSFitReport lrep;
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(w,cw);
ArrayCopy(xc,cxc);
ArrayCopy(yc,cyc);
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": XC contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": YC contains infinite or NaN values!"))
return;
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": one of DC[] is not 0 or 1!"))
return;
}
//--- Scale X,Y,XC,YC.
//--- Solve scaled problem using internal Chebyshev fitting function.
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
InternalChebyshevFit(x,y,w,n,xc,yc,dc,k,m,info,tmp,lrep);
//--- check
if(info<0)
return;
//--- Generate barycentric model and scale it
//--- * BX,BY store barycentric model nodes
//--- * FMatrix is reused (remember - it is at least MxM,what we need)
//--- Model intialization is done in O(M^2). In principle,it can be
//--- done in O(M*log(M)),but before it we solved task with O(N*M^2)
//--- complexity,so it is only a small amount of total time spent.
ArrayResize(bx,m);
ArrayResize(by,m);
ArrayResize(bw,m);
ArrayResize(tmp2,m);
s=1;
//--- calculation
for(int i=0; i<=m-1; i++)
{
//--- check
if(m!=1)
u=MathCos(M_PI*i/(m-1));
else
u=0;
v=0;
for(int j=0; j<=m-1; j++)
{
//--- check
if(j==0)
tmp2[j]=1;
else
{
//--- check
if(j==1)
tmp2[j]=u;
else
tmp2[j]=2*u*tmp2[j-1]-tmp2[j-2];
}
v=v+tmp[j]*tmp2[j];
}
//--- change values
bx[i]=u;
by[i]=v;
bw[i]=s;
//--- check
if(i==0 || i==m-1)
bw[i]=0.5*bw[i];
s=-s;
}
//--- function call
CRatInt::BarycentricBuildXYW(bx,by,bw,m,p);
//--- function call
CRatInt::BarycentricLinTransX(p,2/(xb-xa),-((xa+xb)/(xb-xa)));
//--- function call
CRatInt::BarycentricLinTransY(p,sb-sa,sa);
//--- Scale absolute errors obtained from LSFitLinearW.
//--- Relative error should be calculated separately
//--- (because of shifting/scaling of the task)
rep.m_taskrcond=lrep.m_taskrcond;
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
rep.m_avgrelerror=0;
relcnt=0;
//--- calculation
for(int i=0; i<n; i++)
{
//--- check
if(yoriginal[i]!=0.0)
{
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CRatInt::BarycentricCalc(p,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
relcnt=relcnt+1;
}
}
//--- check
if(relcnt!=0)
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
}
//+------------------------------------------------------------------+
//| Weghted rational least squares fitting using Floater-Hormann |
//| rational functions with optimal D chosen from [0,9], with |
//| constraints and individual weights. |
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
//| basis functions. Different values of D are tried, optimal D |
//| (least WEIGHTED root mean square error) is chosen. Task is |
//| linear, so linear least squares solver is used. Complexity of |
//| this computational scheme is O(N*M^2) (mostly dominated by the |
//| least squares solver). |
//| SEE ALSO |
//|*BarycentricFitFloaterHormann(), "lightweight" fitting without |
//| invididual weights and constraints. |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| Each summand in square sum of approximation |
//| deviations from given values is multiplied by the |
//| square of corresponding weight. Fill it by 1's if |
//| you don't want to solve weighted task. |
//| N - number of points, N>0. |
//| XC - points where function values/derivatives are |
//| constrained, array[0..K-1]. |
//| YC - values of constraints, array[0..K-1] |
//| DC - array[0..K-1], types of constraints: |
//| * DC[i]=0 means that S(XC[i])=YC[i] |
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
//| K - number of constraints, 0<=K<M. |
//| K=0 means no constraints (XC/YC/DC are not used in |
//| such cases) |
//| M - number of basis functions ( = number_of_nodes), |
//| M>=2. |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearWC() subroutine. |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| -1 means another errors in parameters |
//| passed (N<=0, for example) |
//| B - barycentric interpolant. |
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
//| Following fields are set: |
//| * DBest best value of the D parameter |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroutine doesn't calculate task's condition number |
//| for K<>0. |
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
//| Setting constraints can lead to undesired results, like |
//| ill-conditioned behavior, or inconsistency being detected. From |
//| the other side, it allows us to improve quality of the fit. Here |
//| we summarize our experience with constrained barycentric |
//| interpolants: |
//| * excessive constraints can be inconsistent. Floater-Hormann |
//| basis functions aren't as flexible as splines (although they |
//| are very smooth). |
//| * the more evenly constraints are spread across [min(x),max(x)], |
//| the more chances that they will be consistent |
//| * the greater is M (given fixed constraints), the more chances |
//| that constraints will be consistent |
//| * in the general case, consistency of constraints IS NOT |
//| GUARANTEED. |
//| * in the several special cases, however, we CAN guarantee |
//| consistency. |
//| * one of this cases is constraints on the function VALUES at the |
//| interval boundaries. Note that consustency of the constraints |
//| on the function DERIVATIVES is NOT guaranteed (you can use in |
//| such cases cubic splines which are more flexible). |
//| * another special case is ONE constraint on the function value |
//| (OR, but not AND, derivative) anywhere in the interval |
//| Our final recommendation is to use constraints WHEN AND ONLY |
//| WHEN you can't solve your task without them. Anything beyond |
//| special cases given above is not guaranteed and may result in |
//| inconsistency. |
//+------------------------------------------------------------------+
void CLSFit::BarycentricFitFloaterHormannWC(double &x[],double &y[],
double &w[],const int n,
double &xc[],double &yc[],
int &dc[],const int k,
const int m,int &info,
CBarycentricInterpolant &b,
CBarycentricFitReport &rep)
{
//--- create variables
double wrmscur=0;
double wrmsbest=0;
int locinfo=0;
//--- objects of classes
CBarycentricInterpolant locb;
CBarycentricFitReport locrep;
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": XC contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": YC contains infinite or NaN values!"))
return;
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": one of DC[] is not 0 or 1!"))
return;
}
//--- Find optimal D
//--- Info is -3 by default (degenerate constraints).
//--- If LocInfo will always be equal to -3,Info will remain equal to -3.
//--- If at least once LocInfo will be -4,Info will be -4.
wrmsbest=CMath::m_maxrealnumber;
rep.m_dbest=-1;
info=-3;
//--- calculation
for(int d=0; d<=MathMin(9,n-1); d++)
{
//--- function call
BarycentricFitWCFixedD(x,y,w,n,xc,yc,dc,k,m,d,locinfo,locb,locrep);
//--- check
if(!CAp::Assert((locinfo==-4 || locinfo==-3) || locinfo>0,__FUNCTION__+": unexpected result from BarycentricFitWCFixedD!"))
return;
//--- check
if(locinfo>0)
{
//--- Calculate weghted RMS
wrmscur=0;
for(int i=0; i<n; i++)
wrmscur=wrmscur+CMath::Sqr(w[i]*(y[i]-CRatInt::BarycentricCalc(locb,x[i])));
wrmscur=MathSqrt(wrmscur/n);
//--- check
if(wrmscur<wrmsbest || rep.m_dbest<0)
{
//--- function call
CRatInt::BarycentricCopy(locb,b);
//--- change values
rep.m_dbest=d;
info=1;
rep.m_rmserror=locrep.m_rmserror;
rep.m_avgerror=locrep.m_avgerror;
rep.m_avgrelerror=locrep.m_avgrelerror;
rep.m_maxerror=locrep.m_maxerror;
rep.m_taskrcond=locrep.m_taskrcond;
wrmsbest=wrmscur;
}
}
else
{
//--- check
if(locinfo!=-3 && info<0)
info=locinfo;
}
}
}
//+------------------------------------------------------------------+
//| Rational least squares fitting using Floater-Hormann rational |
//| functions with optimal D chosen from [0,9]. |
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
//| basis functions. Different values of D are tried, optimal D |
//| (least root mean square error) is chosen. Task is linear, so |
//| linear least squares solver is used. Complexity of this |
//| computational scheme is O(N*M^2) (mostly dominated by the least |
//| squares solver). |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| N - number of points, N>0. |
//| M - number of basis functions ( = number_of_nodes), M>=2.|
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearWC() subroutine. |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| B - barycentric interpolant. |
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
//| Following fields are set: |
//| * DBest best value of the D parameter |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//+------------------------------------------------------------------+
void CLSFit::BarycentricFitFloaterHormann(double &x[],double &y[],
const int n,const int m,
int &info,CBarycentricInterpolant &b,
CBarycentricFitReport &rep)
{
//--- create arrays
double w[];
double xc[];
double yc[];
int dc[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
return;
//--- check
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
for(int i=0; i<n; i++)
w[i]=1;
//--- function call
BarycentricFitFloaterHormannWC(x,y,w,n,xc,yc,dc,0,m,info,b,rep);
}
//+------------------------------------------------------------------+
//| Weighted fitting by cubic spline, with constraints on function |
//| values or derivatives. |
//| Equidistant grid with M-2 nodes on [min(x,xc),max(x,xc)] is used |
//| to build basis functions. Basis functions are cubic splines with |
//| continuous second derivatives and non-fixed first derivatives at |
//| interval ends. Small regularizing term is used when solving |
//| constrained tasks (to improve stability). |
//| Task is linear, so linear least squares solver is used. |
//| Complexity of this computational scheme is O(N*M^2), mostly |
//| dominated by least squares solver |
//| SEE ALSO |
//| Spline1DFitHermiteWC() - fitting by Hermite splines (more |
//| flexible, less smooth) |
//| Spline1DFitCubic() - "lightweight" fitting by cubic |
//| splines, without invididual |
//| weights and constraints |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| Each summand in square sum of approximation |
//| deviations from given values is multiplied by the |
//| square of corresponding weight. Fill it by 1's if you|
//| don't want to solve weighted task. |
//| N - number of points (optional): |
//| * N>0 |
//| * if given, only first N elements of X/Y/W are |
//| processed |
//| * if not given, automatically determined from X/Y/W |
//| sizes |
//| XC - points where spline values/derivatives are |
//| constrained, array[0..K-1]. |
//| YC - values of constraints, array[0..K-1] |
//| DC - array[0..K-1], types of constraints: |
//| * DC[i]=0 means that S(XC[i])=YC[i] |
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
//| K - number of constraints (optional): |
//| * 0<=K<M. |
//| * K=0 means no constraints (XC/YC/DC are not used) |
//| * if given, only first K elements of XC/YC/DC are |
//| used |
//| * if not given, automatically determined from |
//| XC/YC/DC |
//| M - number of basis functions ( = number_of_nodes+2), |
//| M>=4. |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearWC() subroutine. |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| S - spline interpolant. |
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
//| Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//| ORDER OF POINTS |
//| Subroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
//| Setting constraints can lead to undesired results, like |
//| ill-conditioned behavior, or inconsistency being detected. From |
//| the other side, it allows us to improve quality of the fit. |
//| Here we summarize our experience with constrained regression |
//| splines: |
//| * excessive constraints can be inconsistent. Splines are |
//| piecewise cubic functions, and it is easy to create an |
//| example, where large number of constraints concentrated in |
//| small area will result in inconsistency. Just because spline |
//| is not flexible enough to satisfy all of them. And same |
//| constraints spread across the [min(x),max(x)] will be |
//| perfectly consistent. |
//| * the more evenly constraints are spread across [min(x),max(x)], |
//| the more chances that they will be consistent |
//| * the greater is M (given fixed constraints), the more chances |
//| that constraints will be consistent |
//| * in the general case, consistency of constraints IS NOT |
//| GUARANTEED. |
//| * in the several special cases, however, we CAN guarantee |
//| consistency. |
//| * one of this cases is constraints on the function values |
//| AND/OR its derivatives at the interval boundaries. |
//| * another special case is ONE constraint on the function value |
//| (OR, but not AND, derivative) anywhere in the interval |
//| Our final recommendation is to use constraints WHEN AND ONLY WHEN|
//| you can't solve your task without them. Anything beyond special |
//| cases given above is not guaranteed and may result in |
//| inconsistency. |
//+------------------------------------------------------------------+
void CLSFit::Spline1DFitCubicWC(double &x[],double &y[],double &w[],
const int n,double &xc[],double &yc[],
int &dc[],const int k,const int m,
int &info,CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": DC[i] is neither 0 or 1!"))
return;
}
//--- function call
Spline1DFitInternal(0,x,y,w,n,xc,yc,dc,k,m,info,s,rep);
}
//+------------------------------------------------------------------+
//| Weighted fitting by Hermite spline, with constraints on function |
//| values or first derivatives. |
//| Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is used to|
//| build basis functions. Basis functions are Hermite splines. Small|
//| regularizing term is used when solving constrained tasks (to |
//| improve stability). |
//| Task is linear, so linear least squares solver is used. |
//| Complexity of this computational scheme is O(N*M^2), mostly |
//| dominated by least squares solver |
//| SEE ALSO |
//| Spline1DFitCubicWC() - fitting by Cubic splines (less |
//| flexible, more smooth) |
//| Spline1DFitHermite() - "lightweight" Hermite fitting, |
//| without invididual weights and |
//| constraints |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| Each summand in square sum of approximation |
//| deviations from given values is multiplied by the |
//| square of corresponding weight. Fill it by 1's if |
//| you don't want to solve weighted task. |
//| N - number of points (optional): |
//| * N>0 |
//| * if given, only first N elements of X/Y/W are |
//| processed |
//| * if not given, automatically determined from X/Y/W |
//| sizes |
//| XC - points where spline values/derivatives are |
//| constrained, array[0..K-1]. |
//| YC - values of constraints, array[0..K-1] |
//| DC - array[0..K-1], types of constraints: |
//| * DC[i]=0 means that S(XC[i])=YC[i] |
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
//| K - number of constraints (optional): |
//| * 0<=K<M. |
//| * K=0 means no constraints (XC/YC/DC are not used) |
//| * if given, only first K elements of XC/YC/DC are |
//| used |
//| * if not given, automatically determined from |
//| XC/YC/DC |
//| M - number of basis functions (= 2 * number of nodes), |
//| M>=4, |
//| M IS EVEN! |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearW() subroutine: |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| -2 means odd M was passed (which is not |
//| supported) |
//| -1 means another errors in parameters |
//| passed (N<=0, for example) |
//| S - spline interpolant. |
//| Rep - report, same format as in LSFitLinearW() subroutine. |
//| Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//| IMPORTANT: |
//| this subroitine supports only even M's |
//| ORDER OF POINTS |
//| ubroutine automatically sorts points, so caller may pass |
//| unsorted array. |
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
//| Setting constraints can lead to undesired results, like |
//| ill-conditioned behavior, or inconsistency being detected. From |
//| the other side, it allows us to improve quality of the fit. Here |
//| we summarize our experience with constrained regression splines:|
//| * excessive constraints can be inconsistent. Splines are |
//| piecewise cubic functions, and it is easy to create an example,|
//| where large number of constraints concentrated in small area |
//| will result in inconsistency. Just because spline is not |
//| flexible enough to satisfy all of them. And same constraints |
//| spread across the [min(x),max(x)] will be perfectly consistent.|
//| * the more evenly constraints are spread across [min(x),max(x)], |
//| the more chances that they will be consistent |
//| * the greater is M (given fixed constraints), the more chances |
//| that constraints will be consistent |
//| * in the general case, consistency of constraints is NOT |
//| GUARANTEED. |
//| * in the several special cases, however, we can guarantee |
//| consistency. |
//| * one of this cases is M>=4 and constraints on the function |
//| value (AND/OR its derivative) at the interval boundaries. |
//| * another special case is M>=4 and ONE constraint on the |
//| function value (OR, BUT NOT AND, derivative) anywhere in |
//| [min(x),max(x)] |
//| Our final recommendation is to use constraints WHEN AND ONLY when|
//| you can't solve your task without them. Anything beyond special |
//| cases given above is not guaranteed and may result in |
//| inconsistency. |
//+------------------------------------------------------------------+
void CLSFit::Spline1DFitHermiteWC(double &x[],double &y[],double &w[],
const int n,double &xc[],double &yc[],
int &dc[],const int k,const int m,
int &info,CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(m%2==0,__FUNCTION__+": M is odd!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": DC[i] is neither 0 or 1!"))
return;
}
//--- function call
Spline1DFitInternal(1,x,y,w,n,xc,yc,dc,k,m,info,s,rep);
}
//+------------------------------------------------------------------+
//| Least squares fitting by cubic spline. |
//| This subroutine is "lightweight" alternative for more complex |
//| and feature - rich Spline1DFitCubicWC(). See Spline1DFitCubicWC()|
//| for more information about subroutine parameters (we don't |
//| duplicate it here because of length) |
//+------------------------------------------------------------------+
void CLSFit::Spline1DFitCubic(double &x[],double &y[],const int n,
const int m,int &info,
CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create arrays
double w[];
double xc[];
double yc[];
int dc[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1.0);
//--- function call
Spline1DFitCubicWC(x,y,w,n,xc,yc,dc,0,m,info,s,rep);
}
//+------------------------------------------------------------------+
//| Least squares fitting by Hermite spline. |
//| This subroutine is "lightweight" alternative for more complex |
//| and feature - rich Spline1DFitHermiteWC(). See |
//| Spline1DFitHermiteWC() description for more information about |
//| subroutine parameters (we don't duplicate it here because of |
//| length). |
//+------------------------------------------------------------------+
void CLSFit::Spline1DFitHermite(double &x[],double &y[],const int n,
const int m,int &info,
CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create arrays
double w[];
double xc[];
double yc[];
int dc[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(m%2==0,__FUNCTION__+": M is odd!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1.0);
//--- function call
Spline1DFitHermiteWC(x,y,w,n,xc,yc,dc,0,m,info,s,rep);
}
//+------------------------------------------------------------------+
//| Weighted linear least squares fitting. |
//| QR decomposition is used to reduce task to MxM, then triangular |
//| solver or SVD-based solver is used depending on condition number |
//| of the system. It allows to maximize speed and retain decent |
//| accuracy. |
//| INPUT PARAMETERS: |
//| Y - array[0..N-1] Function values in N points. |
//| W - array[0..N-1] Weights corresponding to function |
//| values. Each summand in square sum of |
//| approximation deviations from given values is |
//| multiplied by the square of corresponding weight.|
//| FMatrix - a table of basis functions values, |
//| array[0..N-1, 0..M-1]. FMatrix[I, J] - value of |
//| J-th basis function in I-th point. |
//| N - number of points used. N>=1. |
//| M - number of basis functions, M>=1. |
//| OUTPUT PARAMETERS: |
//| Info - error code: |
//| * -4 internal SVD decomposition subroutine |
//| failed (very rare and for degenerate |
//| systems only) |
//| * -1 incorrect N/M were specified |
//| * 1 task is solved |
//| C - decomposition coefficients, array[0..M-1] |
//| Rep - fitting report. Following fields are set: |
//| * Rep.TaskRCond reciprocal of condition |
//| number |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the|
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE |
//| CALCULATED |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinearW(double &y[],double &w[],CMatrixDouble &fmatrix,
const int n,const int m,int &info,
double &c[],CLSFitReport &rep)
{
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
return;
//--- function call
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
}
//+------------------------------------------------------------------+
//| Weighted constained linear least squares fitting. |
//| This is variation of LSFitLinearW(), which searchs for |
//| min|A*x=b| given that K additional constaints C*x=bc are |
//| satisfied. It reduces original task to modified one: min|B*y-d| |
//| WITHOUT constraints, then LSFitLinearW() is called. |
//| INPUT PARAMETERS: |
//| Y - array[0..N-1] Function values in N points. |
//| W - array[0..N-1] Weights corresponding to function |
//| values. Each summand in square sum of |
//| approximation deviations from given values is |
//| multiplied by the square of corresponding |
//| weight. |
//| FMatrix - a table of basis functions values, |
//| array[0..N-1, 0..M-1]. FMatrix[I,J] - value of |
//| J-th basis function in I-th point. |
//| CMatrix - a table of constaints, array[0..K-1,0..M]. |
//| I-th row of CMatrix corresponds to I-th linear |
//| constraint: CMatrix[I,0]*C[0] + ... + |
//| + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M] |
//| N - number of points used. N>=1. |
//| M - number of basis functions, M>=1. |
//| K - number of constraints, 0 <= K < M |
//| K=0 corresponds to absence of constraints. |
//| OUTPUT PARAMETERS: |
//| Info - error code: |
//| * -4 internal SVD decomposition subroutine |
//| failed (very rare and for degenerate |
//| systems only) |
//| * -3 either too many constraints (M or more), |
//| degenerate constraints (some constraints |
//| are repetead twice) or inconsistent |
//| constraints were specified. |
//| * 1 task is solved |
//| C - decomposition coefficients, array[0..M-1] |
//| Rep - fitting report. Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the|
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE |
//| CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinearWC(double &cy[],double &w[],CMatrixDouble &fmatrix,
CMatrixDouble &ccmatrix,const int n,
const int m,const int k,int &info,
double &c[],CLSFitReport &rep)
{
double v=0;
//--- create arrays
double tau[];
double tmp[];
double c0[];
double y[];
//--- create matrix
CMatrixDouble q;
CMatrixDouble f2;
CMatrixDouble cmatrix;
//--- copy array
ArrayCopy(y,cy);
//--- copy matrix
cmatrix=ccmatrix;
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(cmatrix)>=k,__FUNCTION__+": rows(CMatrix)<K!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(cmatrix)>=m+1 || k==0,__FUNCTION__+": cols(CMatrix)<M+1!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(cmatrix,k,m+1),__FUNCTION__+": CMatrix contains infinite or NaN values!"))
return;
//--- check
if(k>=m)
{
info=-3;
return;
}
//--- Solve
if(k==0)
{
//--- no constraints
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
}
else
{
//--- First,find general form solution of constraints system:
//--- * factorize C=L*Q
//--- * unpack Q
//--- * fill upper part of C with zeros (for RCond)
//--- We got C=C0+Q2'*y where Q2 is lower M-K rows of Q.
COrtFac::RMatrixLQ(cmatrix,k,m,tau);
COrtFac::RMatrixLQUnpackQ(cmatrix,k,m,tau,m,q);
for(int i=0; i<k; i++)
{
for(int j=i+1; j<=m-1; j++)
cmatrix.Set(i,j,0.0);
}
//--- check
if(CRCond::RMatrixLURCondInf(cmatrix,k)<1000*CMath::m_machineepsilon)
{
info=-3;
return;
}
//--- allocation
ArrayResize(tmp,k);
//--- calculation
for(int i=0; i<k; i++)
{
//--- check
if(i>0)
{
v=0.0;
for(int i_=0; i_<=i-1; i_++)
v+=cmatrix.Get(i,i_)*tmp[i_];
}
else
v=0;
//--- change values
tmp[i]=(cmatrix[i][m]-v)/cmatrix[i][i];
}
//--- allocation
ArrayResize(c0,m);
//--- calculation
for(int i=0; i<=m-1; i++)
c0[i]=0;
for(int i=0; i<k; i++)
{
v=tmp[i];
for(int i_=0; i_<=m-1; i_++)
c0[i_]=c0[i_]+v*q.Get(i,i_);
}
//--- Second,prepare modified matrix F2=F*Q2' and solve modified task
ArrayResize(tmp,MathMax(n,m)+1);
f2.Resize(n,m-k);
//--- function call
CBlas::MatrixVectorMultiply(fmatrix,0,n-1,0,m-1,false,c0,0,m-1,-1.0,y,0,n-1,1.0);
//--- function call
CBlas::MatrixMatrixMultiply(fmatrix,0,n-1,0,m-1,false,q,k,m-1,0,m-1,true,1.0,f2,0,n-1,0,m-k-1,0.0,tmp);
//--- function call
LSFitLinearInternal(y,w,f2,n,m-k,info,tmp,rep);
rep.m_taskrcond=-1;
//--- check
if(info<=0)
return;
//--- then,convert back to original answer: C=C0 + Q2'*Y0
ArrayResize(c,m);
for(int i_=0; i_<=m-1; i_++)
c[i_]=c0[i_];
//--- function call
CBlas::MatrixVectorMultiply(q,k,m-1,0,m-1,true,tmp,0,m-k-1,1.0,c,0,m-1,1.0);
}
}
//+------------------------------------------------------------------+
//| Linear least squares fitting. |
//| QR decomposition is used to reduce task to MxM, then triangular |
//| solver or SVD-based solver is used depending on condition number |
//| of the system. It allows to maximize speed and retain decent |
//| accuracy. |
//| INPUT PARAMETERS: |
//| Y - array[0..N-1] Function values in N points. |
//| FMatrix - a table of basis functions values, |
//| array[0..N-1, 0..M-1]. |
//| FMatrix[I, J] - value of J-th basis function in |
//| I-th point. |
//| N - number of points used. N>=1. |
//| M - number of basis functions, M>=1. |
//| OUTPUT PARAMETERS: |
//| Info - error code: |
//| * -4 internal SVD decomposition subroutine |
//| failed (very rare and for degenerate |
//| systems only) |
//| * 1 task is solved |
//| C - decomposition coefficients, array[0..M-1] |
//| Rep - fitting report. Following fields are set: |
//| * Rep.TaskRCond reciprocal of condition |
//| number |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the|
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE |
//| CALCULATED |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinear(double &y[],CMatrixDouble &fmatrix,
const int n,const int m,int &info,
double &c[],CLSFitReport &rep)
{
//--- create array
double w[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1.0);
//--- function call
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinear(CRowDouble &y,CMatrixDouble &fmatrix,
const int n,const int m,int &info,
CRowDouble &c,CLSFitReport &rep)
{
//--- create array
double w[];
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1.0);
double Y[];
double C[];
y.ToArray(Y);
c.ToArray(C);
//--- function call
LSFitLinearInternal(Y,w,fmatrix,n,m,info,C,rep);
c=C;
}
//+------------------------------------------------------------------+
//| Constained linear least squares fitting. |
//| This is variation of LSFitLinear(), which searchs for min|A*x=b| |
//| given that K additional constaints C*x=bc are satisfied. It |
//| reduces original task to modified one: min|B*y-d| WITHOUT |
//| constraints, then LSFitLinear() is called. |
//| INPUT PARAMETERS: |
//| Y - array[0..N-1] Function values in N points. |
//| FMatrix - a table of basis functions values, |
//| array[0..N-1, 0..M-1]. FMatrix[I,J] - value of |
//| J-th basis function in I-th point. |
//| CMatrix - a table of constaints, array[0..K-1,0..M]. |
//| I-th row of CMatrix corresponds to I-th linear |
//| constraint: CMatrix[I,0]*C[0] + ... + |
//| + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M] |
//| N - number of points used. N>=1. |
//| M - number of basis functions, M>=1. |
//| K - number of constraints, 0 <= K < M |
//| K=0 corresponds to absence of constraints. |
//| OUTPUT PARAMETERS: |
//| Info - error code: |
//| * -4 internal SVD decomposition subroutine |
//| failed (very rare and for degenerate |
//| systems only) |
//| * -3 either too many constraints (M or more), |
//| degenerate constraints (some constraints |
//| are repetead twice) or inconsistent |
//| constraints were specified. |
//| * 1 task is solved |
//| C - decomposition coefficients, array[0..M-1] |
//| Rep - fitting report. Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the|
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE |
//| CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinearC(double &cy[],CMatrixDouble &fmatrix,
CMatrixDouble &cmatrix,const int n,
const int m,const int k,int &info,
double &c[],CLSFitReport &rep)
{
//--- create arrays
double w[];
double y[];
//--- copy array
ArrayCopy(y,cy);
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(cmatrix)>=k,__FUNCTION__+": rows(CMatrix)<K!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(cmatrix)>=m+1 || k==0,__FUNCTION__+": cols(CMatrix)<M+1!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(cmatrix,k,m+1),__FUNCTION__+": CMatrix contains infinite or NaN values!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
ArrayInitialize(w,1.0);
//--- function call
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,c,rep);
}
//+------------------------------------------------------------------+
//| Weighted nonlinear least squares fitting using function values |
//| only. |
//| Combination of numerical differentiation and secant updates is |
//| used to obtain function Jacobian. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
//| + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * w is an N-dimensional vector of weight coefficients, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses only f(c,x[i]). |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| W - weights, array[0..N-1] |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| DiffStep- numerical differentiation step; |
//| should not be very small or large; |
//| large = loss of accuracy |
//| small = growth of round-off errors |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateWF(CMatrixDouble &x,double &y[],double &w[],
double &c[],const int n,const int m,
const int k,const double diffstep,
CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is not finite!"))
return;
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
return;
State.m_teststep=0;
State.m_diffstep=diffstep;
State.m_npoints=n;
State.m_nweights=n;
State.m_wkind=1;
State.m_m=m;
State.m_k=k;
LSFitSetCond(State,0.0,0);
LSFitSetStpMax(State,0.0);
LSFitSetXRep(State,false);
State.m_taskx.Resize(n,m);
State.m_tasky.Resize(n);
State.m_taskw.Resize(n);
State.m_c.Resize(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_c0=c;
State.m_c1=c;
State.m_x.Resize(m);
State.m_taskw=w;
State.m_taskx=x;
State.m_tasky=y;
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
State.m_optalgo=0;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
CMinLM::MinLMCreateV(k,n,State.m_c0,diffstep,State.m_optstate);
LSFitClearRequestFields(State);
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Nonlinear least squares fitting using function values only. |
//| Combination of numerical differentiation and secant updates is |
//| used to obtain function Jacobian. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = (f(c,x[0])-y[0])^2 + ... + (f(c,x[n-1])-y[n-1])^2, |
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * w is an N-dimensional vector of weight coefficients, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses only f(c,x[i]). |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| DiffStep- numerical differentiation step; |
//| should not be very small or large; |
//| large = loss of accuracy |
//| small = growth of round-off errors |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateF(CMatrixDouble &x,double &y[],double &c[],
const int n,const int m,const int k,
const double diffstep,CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is not finite!"))
return;
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
return;
State.m_teststep=0;
State.m_diffstep=diffstep;
State.m_npoints=n;
State.m_wkind=0;
State.m_m=m;
State.m_k=k;
LSFitSetCond(State,0.0,0);
LSFitSetStpMax(State,0.0);
LSFitSetXRep(State,false);
State.m_c0=c;
State.m_c1=c;
State.m_x.Resize(m);
State.m_taskx=x;
State.m_tasky=y;
State.m_taskx.Resize(n,m);
State.m_tasky.Resize(n);
State.m_c.Resize(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
State.m_optalgo=0;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
CMinLM::MinLMCreateV(k,n,State.m_c0,diffstep,State.m_optstate);
LSFitClearRequestFields(State);
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Weighted nonlinear least squares fitting using gradient only. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
//| + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * w is an N-dimensional vector of weight coefficients, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses only f(c,x[i]) and its gradient. |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| W - weights, array[0..N-1] |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| CheapFG - boolean flag, which is: |
//| * True if both function and gradient calculation |
//| complexity are less than O(M^2). An |
//| improved algorithm can be used which |
//| corresponds to FGJ scheme from MINLM unit.|
//| * False otherwise. |
//| Standard Jacibian-bases |
//| Levenberg-Marquardt algo will be used (FJ |
//| scheme). |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//| See also: |
//| LSFitResults |
//| LSFitCreateFG (fitting without weights) |
//| LSFitCreateWFGH (fitting using Hessian) |
//| LSFitCreateFGH (fitting using Hessian, without weights) |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateWFG(CMatrixDouble &x,double &y[],double &w[],
double &c[],const int n,const int m,
const int k,bool cheapfg,CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
return;
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
State.m_teststep=0;
State.m_diffstep=0;
State.m_npoints=n;
State.m_nweights=n;
State.m_wkind=1;
State.m_m=m;
State.m_k=k;
LSFitSetCond(State,0.0,0);
LSFitSetStpMax(State,0.0);
LSFitSetXRep(State,false);
State.m_c0=c;
State.m_c1=c;
State.m_taskw=w;
State.m_taskx=x;
State.m_tasky=y;
State.m_taskx.Resize(n,m);
State.m_tasky.Resize(n);
State.m_taskw.Resize(n);
State.m_c.Resize(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_x.Resize(m);
State.m_g.Resize(k);
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
State.m_optalgo=1;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
if(cheapfg)
CMinLM::MinLMCreateVGJ(k,n,State.m_c0,State.m_optstate);
else
CMinLM::MinLMCreateVJ(k,n,State.m_c0,State.m_optstate);
//--- function call
LSFitClearRequestFields(State);
//--- allocation
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Nonlinear least squares fitting using gradient only, without |
//| individual weights. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,|
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses only f(c,x[i]) and its gradient. |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| CheapFG - boolean flag, which is: |
//| * True if both function and gradient calculation|
//| complexity are less than O(M^2). An |
//| improved algorithm can be used which |
//| corresponds to FGJ scheme from MINLM |
//| unit. |
//| * False otherwise. |
//| Standard Jacibian-bases |
//| Levenberg-Marquardt algo will be used |
//| (FJ scheme). |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateFG(CMatrixDouble &x,double &y[],double &c[],
const int n,const int m,const int k,
const bool cheapfg,CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- initialization
State.m_teststep=0;
State.m_diffstep=0;
State.m_npoints=n;
State.m_wkind=0;
State.m_m=m;
State.m_k=k;
LSFitSetCond(State,0.0,0);
LSFitSetStpMax(State,0.0);
LSFitSetXRep(State,false);
//--- copy
State.m_c0=c;
State.m_c1=c;
State.m_taskx=x;
State.m_tasky=y;
//--- allocation
State.m_taskx.Resize(n,m);
State.m_tasky.Resize(n);
State.m_c.Resize(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_x.Resize(m);
State.m_g.Resize(k);
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
State.m_optalgo=1;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
//--- check
if(cheapfg)
CMinLM::MinLMCreateVGJ(k,n,State.m_c0,State.m_optstate);
else
CMinLM::MinLMCreateVJ(k,n,State.m_c0,State.m_optstate);
//--- function call
LSFitClearRequestFields(State);
//--- allocation
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Weighted nonlinear least squares fitting using gradient/Hessian. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
//| (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * w is an N-dimensional vector of weight coefficients, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses f(c,x[i]), its gradient and its Hessian. |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| W - weights, array[0..N-1] |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateWFGH(CMatrixDouble &x,double &y[],double &w[],
double &c[],const int n,const int m,
const int k,CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- initialization
State.m_teststep=0;
State.m_diffstep=0;
State.m_npoints=n;
State.m_nweights=n;
State.m_wkind=1;
State.m_m=m;
State.m_k=k;
//--- function call
LSFitSetCond(State,0.0,0);
//--- function call
LSFitSetStpMax(State,0.0);
//--- function call
LSFitSetXRep(State,false);
//--- copy
State.m_c0=c;
State.m_c1=c;
State.m_taskx=x;
State.m_tasky=y;
State.m_taskw=w;
//--- allocation
State.m_taskx.Resize(n,m);
State.m_h=matrix<double>::Zeros(k,k);
State.m_tasky.Resize(n);
State.m_taskw.Resize(n);
State.m_c=vector<double>::Zeros(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_x=vector<double>::Zeros(m);
State.m_g=vector<double>::Zeros(k);
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
//--- change values
State.m_optalgo=2;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
//--- function call
CMinLM::MinLMCreateFGH(k,State.m_c0,State.m_optstate);
//--- function call
LSFitClearRequestFields(State);
//--- allocation
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Nonlinear least squares fitting using gradient/Hessian, without |
//| individial weights. |
//| Nonlinear task min(F(c)) is solved, where |
//| F(c) = ((f(c,x[0])-y[0]))^2 + ... + |
//| ((f(c,x[n-1])-y[n-1]))^2, |
//| * N is a number of points, |
//| * M is a dimension of a space points belong to, |
//| * K is a dimension of a space of parameters being fitted, |
//| * x is a set of N points, each of them is an M-dimensional |
//| vector, |
//| * c is a K-dimensional vector of parameters being fitted |
//| This subroutine uses f(c,x[i]), its gradient and its Hessian. |
//| INPUT PARAMETERS: |
//| X - array[0..N-1,0..M-1], points (one row = one |
//| point) |
//| Y - array[0..N-1], function values. |
//| C - array[0..K-1], initial approximation to the |
//| solution, |
//| N - number of points, N>1 |
//| M - dimension of space |
//| K - number of parameters being fitted |
//| OUTPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//+------------------------------------------------------------------+
void CLSFit::LSFitCreateFGH(CMatrixDouble &x,double &y[],double &c[],
const int n,const int m,const int k,
CLSFitState &State)
{
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
return;
//--- check
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
return;
//--- check
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
return;
//--- initialization
State.m_teststep=0;
State.m_diffstep=0;
State.m_npoints=n;
State.m_wkind=0;
State.m_m=m;
State.m_k=k;
//--- function call
LSFitSetCond(State,0.0,0);
//--- function call
LSFitSetStpMax(State,0.0);
//--- function call
LSFitSetXRep(State,false);
//--- copy
State.m_c0=c;
State.m_c1=c;
State.m_taskx=x;
State.m_tasky=y;
//--- allocation
State.m_taskx.Resize(n,m);
State.m_tasky.Resize(n);
State.m_c.Resize(k);
State.m_c0.Resize(k);
State.m_c1.Resize(k);
State.m_h.Resize(k,k);
State.m_x.Resize(m);
State.m_g.Resize(k);
State.m_s=vector<double>::Ones(k);
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
State.m_bndu=vector<double>::Full(k,AL_POSINF);
//--- change values
State.m_optalgo=2;
State.m_prevnpt=-1;
State.m_prevalgo=-1;
State.m_nec=0;
State.m_nic=0;
//--- function call
CMinLM::MinLMCreateFGH(k,State.m_c0,State.m_optstate);
//--- function call
LSFitClearRequestFields(State);
//--- allocation
State.m_rstate.ia.Resize(7);
State.m_rstate.ra.Resize(9);
State.m_rstate.stage=-1;
}
//+------------------------------------------------------------------+
//| Stopping conditions for nonlinear least squares fitting. |
//| INPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//| EpsF - stopping criterion. Algorithm stops if |
//| |F(k+1)-F(k)| <= EpsF*max{|F(k)|, |F(k+1)|, 1} |
//| EpsX - >=0 |
//| The subroutine finishes its work if on k+1-th |
//| iteration the condition |v|<=EpsX is fulfilled, |
//| where: |
//| * |.| means Euclidian norm |
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
//| * dx - ste pvector, dx=X(k+1)-X(k) |
//| * s - scaling coefficients set by LSFitSetScale()|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
//| number of iterations is unlimited. Only |
//| Levenberg-Marquardt iterations are counted |
//| (L-BFGS/CG iterations are NOT counted because |
//| their cost is very low compared to that of LM). |
//| NOTE |
//| Passing EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to|
//| automatic stopping criterion selection (according to the scheme |
//| used by MINLM unit). |
//+------------------------------------------------------------------+
void CLSFit::LSFitSetCond(CLSFitState &State,const double epsx,const int maxits)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite!"))
return;
//--- check
if(!CAp::Assert((double)(epsx)>=0.0,__FUNCTION__+": negative EpsX!"))
return;
//--- check
if(!CAp::Assert(maxits>=0,__FUNCTION__+": negative MaxIts!"))
return;
//--- change values
State.m_epsx=epsx;
State.m_maxits=maxits;
}
//+------------------------------------------------------------------+
//| This function sets maximum step length |
//| INPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//| StpMax - maximum step length, >=0. Set StpMax to 0.0, if |
//| you don't want to limit step length. |
//| Use this subroutine when you optimize target function which |
//| contains exp() or other fast growing functions, and optimization |
//| algorithm makes too large steps which leads to overflow. This |
//| function allows us to reject steps that are too large (and |
//| therefore expose us to the possible overflow) without actually |
//| calculating function value at the x+stp*d. |
//| NOTE: non-zero StpMax leads to moderate performance degradation |
//| because intermediate step of preconditioned L-BFGS optimization |
//| is incompatible with limits on step size. |
//+------------------------------------------------------------------+
void CLSFit::LSFitSetStpMax(CLSFitState &State,const double stpmax)
{
//--- check
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
return;
//--- change value
State.m_stpmax=stpmax;
}
//+------------------------------------------------------------------+
//| This function turns on/off reporting. |
//| INPUT PARAMETERS: |
//| State - structure which stores algorithm State |
//| NeedXRep- whether iteration reports are needed or not |
//| When reports are needed, State.C (current parameters) and State. |
//| F (current value of fitting function) are reported. |
//+------------------------------------------------------------------+
void CLSFit::LSFitSetXRep(CLSFitState &State,const bool needxrep)
{
State.m_xrep=needxrep;
}
//+------------------------------------------------------------------+
//| This function sets scaling coefficients for underlying optimizer.|
//| ALGLIB optimizers use scaling matrices to test stopping |
//| conditions (step size and gradient are scaled before comparison |
//| with tolerances). Scale of the I-th variable is a translation |
//| invariant measure of: |
//| a) "how large" the variable is |
//| b) how large the step should be to make significant changes in |
//| the function |
//| Generally, scale is NOT considered to be a form of |
//| preconditioner. But LM optimizer is unique in that it uses |
//| scaling matrix both in the stopping condition tests and as |
//| Marquardt damping factor. |
//| Proper scaling is very important for the algorithm performance. |
//| It is less important for the quality of results, but still has |
//| some influence (it is easier to converge when variables are |
//| properly scaled, so premature stopping is possible when very |
//| badly scalled variables are combined with relaxed stopping |
//| conditions). |
//| INPUT PARAMETERS: |
//| State - structure stores algorithm State |
//| S - array[N], non-zero scaling coefficients |
//| S[i] may be negative, sign doesn't matter. |
//+------------------------------------------------------------------+
void CLSFit::LSFitSetScale(CLSFitState &State,double &s[])
{
//--- check
if(!CAp::Assert(CAp::Len(s)>=State.m_k,__FUNCTION__+": Length(S)<K"))
return;
for(int i=0; i<State.m_k; i++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
return;
//--- check
if(!CAp::Assert((double)(s[i])!=0.0,__FUNCTION__+": S contains infinite or NAN elements"))
return;
//--- change values
State.m_s.Set(i,MathAbs(s[i]));
}
}
//+------------------------------------------------------------------+
//| This function sets boundary constraints for underlying optimizer |
//| Boundary constraints are inactive by default (after initial |
//| creation). They are preserved until explicitly turned off with |
//| another SetBC() call. |
//| INPUT PARAMETERS: |
//| State - structure stores algorithm State |
//| BndL - lower bounds, array[K]. |
//| If some (all) variables are unbounded, you may |
//| specify very small number or -INF (latter is |
//| recommended because it will allow solver to use |
//| better algorithm). |
//| BndU - upper bounds, array[K]. |
//| If some (all) variables are unbounded, you may |
//| specify very large number or +INF (latter is |
//| recommended because it will allow solver to use |
//| better algorithm). |
//| NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case |
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
//| NOTE 2: unlike other constrained optimization algorithms, this |
//| solver has following useful properties: |
//| * bound constraints are always satisfied exactly |
//| * function is evaluated only INSIDE area specified by bound |
//| constraints |
//+------------------------------------------------------------------+
void CLSFit::LSFitSetBC(CLSFitState &State,double &bndl[],
double &bndu[])
{
int k=State.m_k;
//--- check
if(!CAp::Assert(CAp::Len(bndl)>=k,__FUNCTION__+": Length(BndL)<K"))
return;
//--- check
if(!CAp::Assert(CAp::Len(bndu)>=k,__FUNCTION__+": Length(BndU)<K"))
return;
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || CInfOrNaN::IsNegativeInfinity(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || CInfOrNaN::IsPositiveInfinity(bndu[i]),__FUNCTION__+": BndU contains NAN or -INF"))
return;
//--- check
if(CMath::IsFinite(bndl[i]) && CMath::IsFinite(bndu[i]))
{
//--- check
if(!CAp::Assert(bndl[i]<=bndu[i],__FUNCTION__+": BndL[i]>BndU[i]"))
return;
}
//--- change values
State.m_bndl.Set(i,bndl[i]);
State.m_bndu.Set(i,bndu[i]);
}
}
//+------------------------------------------------------------------+
//| Nonlinear least squares fitting results. |
//| Called after return from LSFitFit(). |
//| INPUT PARAMETERS: |
//| State - algorithm State |
//| OUTPUT PARAMETERS: |
//| Info - completetion code: |
//| * 1 relative function improvement is no |
//| more than EpsF. |
//| * 2 relative step is no more than EpsX. |
//| * 4 gradient norm is no more than EpsG |
//| * 5 MaxIts steps was taken |
//| * 7 stopping conditions are too |
//| stringent, further improvement is |
//| impossible |
//| C - array[0..K-1], solution |
//| Rep - optimization report. Following fields are set: |
//| * Rep.TerminationType completetion code: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the|
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE |
//| CALCULATED |
//| * WRMSError weighted rms error on the |
//| (X,Y). |
//+------------------------------------------------------------------+
void CLSFit::LSFitResults(CLSFitState &State,int &info,double &c[],
CLSFitReport &rep)
{
//--- initialization
info=State.m_repterminationtype;
//--- check
if(info>0)
{
//--- allocation
ArrayResize(c,State.m_k);
for(int i_=0; i_<=State.m_k-1; i_++)
c[i_]=State.m_c[i_];
//--- change values
rep.m_rmserror=State.m_reprmserror;
rep.m_wrmserror=State.m_repwrmserror;
rep.m_avgerror=State.m_repavgerror;
rep.m_avgrelerror=State.m_repavgrelerror;
rep.m_maxerror=State.m_repmaxerror;
rep.m_iterationscount=State.m_repiterationscount;
}
}
//+------------------------------------------------------------------+
//| Internal subroutine: automatic scaling for LLS tasks. |
//| NEVER CALL IT DIRECTLY! |
//| Maps abscissas to [-1,1], standartizes ordinates and |
//| correspondingly scales constraints. It also scales weights so |
//| that max(W[i])=1 |
//| Transformations performed: |
//| * X, XC [XA,XB] => [-1,+1] |
//| transformation makes min(X)=-1, max(X)=+1 |
//| * Y [SA,SB] => [0,1] |
//| transformation makes mean(Y)=0, stddev(Y)=1 |
//| * YC transformed accordingly to SA, SB, DC[I] |
//+------------------------------------------------------------------+
void CLSFit::LSFitScaleXY(double &x[],double &y[],double &w[],
const int n,double &xc[],double &yc[],
int &dc[],const int k,double &xa,
double &xb,double &sa,double &sb,
double &xoriginal[],double &yoriginal[])
{
//--- create variables
double xmin=0;
double xmax=0;
double mx=0;
//--- initialization
xa=0;
xb=0;
sa=0;
sb=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": incorrect N"))
return;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": incorrect K"))
return;
//--- Calculate xmin/xmax.
//--- Force xmin<>xmax.
xmin=x[0];
xmax=x[0];
for(int i=1; i<n; i++)
{
xmin=MathMin(xmin,x[i]);
xmax=MathMax(xmax,x[i]);
}
for(int i=0; i<k; i++)
{
xmin=MathMin(xmin,xc[i]);
xmax=MathMax(xmax,xc[i]);
}
//--- check
if(xmin==xmax)
{
//--- check
if(xmin==0.0)
{
xmin=-1;
xmax=1;
}
else
{
//--- check
if(xmin>0.0)
xmin=0.5*xmin;
else
xmax=0.5*xmax;
}
}
//--- Transform abscissas: map [XA,XB] to [0,1]
//--- Store old X[] in XOriginal[] (it will be used
//--- to calculate relative error).
ArrayResize(xoriginal,n);
for(int i_=0; i_<n; i_++)
xoriginal[i_]=x[i_];
//--- change values
xa=xmin;
xb=xmax;
for(int i=0; i<n; i++)
x[i]=2*(x[i]-0.5*(xa+xb))/(xb-xa);
//--- calculation
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]>=0,__FUNCTION__+": internal error!"))
return;
xc[i]=2*(xc[i]-0.5*(xa+xb))/(xb-xa);
yc[i]=yc[i]*MathPow(0.5*(xb-xa),dc[i]);
}
//--- Transform function values: map [SA,SB] to [0,1]
//--- SA=mean(Y),
//--- SB=SA+stddev(Y).
//--- Store old Y[] in YOriginal[] (it will be used
//--- to calculate relative error).
ArrayResize(yoriginal,n);
for(int i_=0; i_<n; i_++)
yoriginal[i_]=y[i_];
sa=0;
for(int i=0; i<n; i++)
sa=sa+y[i];
sa=sa/n;
//--- change value
sb=0;
for(int i=0; i<n; i++)
sb=sb+CMath::Sqr(y[i]-sa);
sb=MathSqrt(sb/n)+sa;
//--- check
if(sb==sa)
sb=2*sa;
//--- check
if(sb==sa)
sb=sa+1;
for(int i=0; i<n; i++)
y[i]=(y[i]-sa)/(sb-sa);
for(int i=0; i<k; i++)
{
//--- check
if(dc[i]==0)
yc[i]=(yc[i]-sa)/(sb-sa);
else
yc[i]=yc[i]/(sb-sa);
}
//--- Scale weights
mx=0;
for(int i=0; i<n; i++)
mx=MathMax(mx,MathAbs(w[i]));
//--- check
if(mx!=0.0)
{
for(int i=0; i<n; i++)
w[i]=w[i]/mx;
}
}
//+------------------------------------------------------------------+
//| Internal spline fitting subroutine |
//+------------------------------------------------------------------+
void CLSFit::Spline1DFitInternal(const int st,double &cx[],double &cy[],
double &cw[],const int n,double &cxc[],
double &cyc[],int &dc[],const int k,
const int m,int &info,
CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create variables
double v0=0;
double v1=0;
double v2=0;
double mx=0;
int i=0;
int j=0;
int relcnt=0;
double xa=0;
double xb=0;
double sa=0;
double sb=0;
double bl=0;
double br=0;
double decay=0;
int i_=0;
//--- create arrays
double y2[];
double w2[];
double sx[];
double sy[];
double sd[];
double tmp[];
double xoriginal[];
double yoriginal[];
double x[];
double y[];
double w[];
double xc[];
double yc[];
//--- create matrix
CMatrixDouble fmatrix;
CMatrixDouble cmatrix;
//--- objects of classes
CLSFitReport lrep;
CSpline1DInterpolant s2;
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(w,cw);
ArrayCopy(xc,cxc);
ArrayCopy(yc,cyc);
//--- initialization
info=0;
//--- check
if(!CAp::Assert(st==0 || st==1,__FUNCTION__+": internal error!"))
return;
//--- check
if(st==0 && m<4)
{
info=-1;
return;
}
//--- check
if(st==1 && m<4)
{
info=-1;
return;
}
//--- check
if((n<1 || k<0) || k>=m)
{
info=-1;
return;
}
for(i=0; i<k; i++)
{
info=0;
//--- check
if(dc[i]<0)
info=-1;
//--- check
if(dc[i]>1)
info=-1;
//--- check
if(info<0)
return;
}
//--- check
if(st==1 && m%2!=0)
{
//--- Hermite fitter must have even number of basis functions
info=-2;
return;
}
//--- weight decay for correct handling of task which becomes
//--- degenerate after constraints are applied
decay=10000*CMath::m_machineepsilon;
//--- Scale X,Y,XC,YC
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
//--- allocate space,initialize:
//--- * SX - grid for basis functions
//--- * SY - values of basis functions at grid points
//--- * FMatrix- values of basis functions at X[]
//--- * CMatrix- values (derivatives) of basis functions at XC[]
ArrayResize(y2,n+m);
ArrayResize(w2,n+m);
fmatrix.Resize(n+m,m);
//--- check
if(k>0)
cmatrix.Resize(k,m+1);
//--- check
if(st==0)
{
//--- allocate space for cubic spline
ArrayResize(sx,m-2);
ArrayResize(sy,m-2);
for(j=0; j<=m-2-1; j++)
sx[j]=(double)(2*j)/(double)(m-2-1)-1;
}
//--- check
if(st==1)
{
//--- allocate space for Hermite spline
ArrayResize(sx,m/2);
ArrayResize(sy,m/2);
ArrayResize(sd,m/2);
for(j=0; j<=m/2-1; j++)
sx[j]=(double)(2*j)/(double)(m/2-1)-1;
}
//--- Prepare design and constraints matrices:
//--- * fill constraints matrix
//--- * fill first N rows of design matrix with values
//--- * fill next M rows of design matrix with regularizing term
//--- * append M zeros to Y
//--- * append M elements,mean(abs(W)) each,to W
for(j=0; j<=m-1; j++)
{
//--- prepare Jth basis function
if(st==0)
{
//--- cubic spline basis
for(i=0; i<=m-2-1; i++)
sy[i]=0;
bl=0;
br=0;
//--- check
if(j<m-2)
sy[j]=1;
//--- check
if(j==m-2)
bl=1;
//--- check
if(j==m-1)
br=1;
//--- function call
CSpline1D::Spline1DBuildCubic(sx,sy,m-2,1,bl,1,br,s2);
}
//--- check
if(st==1)
{
//--- Hermite basis
for(i=0; i<=m/2-1; i++)
{
sy[i]=0;
sd[i]=0;
}
//--- check
if(j%2==0)
sy[j/2]=1;
else
sd[j/2]=1;
//--- function call
CSpline1D::Spline1DBuildHermite(sx,sy,sd,m/2,s2);
}
//--- values at X[],XC[]
for(i=0; i<n; i++)
fmatrix.Set(i,j,CSpline1D::Spline1DCalc(s2,x[i]));
for(i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]>=0 && dc[i]<=2,__FUNCTION__+": internal error!"))
return;
//--- function call
CSpline1D::Spline1DDiff(s2,xc[i],v0,v1,v2);
//--- check
if(dc[i]==0)
cmatrix.Set(i,j,v0);
//--- check
if(dc[i]==1)
cmatrix.Set(i,j,v1);
//--- check
if(dc[i]==2)
cmatrix.Set(i,j,v2);
}
}
//--- calculation
for(i=0; i<k; i++)
cmatrix.Set(i,m,yc[i]);
for(i=0; i<=m-1; i++)
{
for(j=0; j<=m-1; j++)
{
//--- check
if(i==j)
fmatrix.Set(n+i,j,decay);
else
fmatrix.Set(n+i,j,0);
}
}
//--- allocation
ArrayResize(y2,n+m);
ArrayResize(w2,n+m);
//--- copy
for(i_=0; i_<n; i_++)
y2[i_]=y[i_];
for(i_=0; i_<n; i_++)
w2[i_]=w[i_];
//--- change value
mx=0;
for(i=0; i<n; i++)
mx=mx+MathAbs(w[i]);
mx=mx/n;
for(i=0; i<=m-1; i++)
{
y2[n+i]=0;
w2[n+i]=mx;
}
//--- Solve constrained task
if(k>0)
{
//--- solve using regularization
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,tmp,lrep);
}
else
{
//--- no constraints,no regularization needed
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,tmp,lrep);
}
//--- check
if(info<0)
return;
//--- Generate spline and scale it
if(st==0)
{
//--- cubic spline basis
for(i_=0; i_<=m-2-1; i_++)
sy[i_]=tmp[i_];
//--- function call
CSpline1D::Spline1DBuildCubic(sx,sy,m-2,1,tmp[m-2],1,tmp[m-1],s);
}
//--- check
if(st==1)
{
//--- Hermite basis
for(i=0; i<=m/2-1; i++)
{
sy[i]=tmp[2*i];
sd[i]=tmp[2*i+1];
}
//--- function call
CSpline1D::Spline1DBuildHermite(sx,sy,sd,m/2,s);
}
//--- function call
CSpline1D::Spline1DLinTransX(s,2/(xb-xa),-((xa+xb)/(xb-xa)));
//--- function call
CSpline1D::Spline1DLinTransY(s,sb-sa,sa);
//--- Scale absolute errors obtained from LSFitLinearW.
//--- Relative error should be calculated separately
//--- (because of shifting/scaling of the task)
rep.m_taskrcond=lrep.m_taskrcond;
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
rep.m_avgrelerror=0;
relcnt=0;
for(i=0; i<n; i++)
{
//--- check
if(yoriginal[i]!=0.0)
{
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CSpline1D::Spline1DCalc(s,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
relcnt=relcnt+1;
}
}
//--- check
if(relcnt!=0)
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
}
//+------------------------------------------------------------------+
//| Internal fitting subroutine |
//+------------------------------------------------------------------+
void CLSFit::LSFitLinearInternal(double &y[],double &w[],
CMatrixDouble &fmatrix,const int n,
const int m,int &info,double &c[],
CLSFitReport &rep)
{
//--- create variables
double threshold=0;
int i=0;
int j=0;
double v=0;
int relcnt=0;
int i_=0;
//--- create arrays
double b[];
double wmod[];
double tau[];
double sv[];
double tmp[];
double utb[];
double sutb[];
//--- create matrix
CMatrixDouble ft;
CMatrixDouble q;
CMatrixDouble l;
CMatrixDouble r;
CMatrixDouble u;
CMatrixDouble vt;
//--- initialization
info=0;
ClearReport(rep);
//--- check
if(n<1 || m<1)
{
info=-1;
return;
}
//--- initialization
info=1;
threshold=MathSqrt(CMath::m_machineepsilon);
//--- Degenerate case,needs special handling
if(n<m)
{
//--- Create design matrix.
ft.Resize(n,m);
ArrayResize(b,n);
ArrayResize(wmod,n);
for(j=0; j<n; j++)
{
v=w[j];
ft.Row(j,fmatrix[j]* v);
//--- change values
b[j]=w[j]*y[j];
wmod[j]=1;
}
//--- LQ decomposition and reduction to M=N
ArrayResize(c,m);
ArrayInitialize(c,0);
rep.m_taskrcond=0;
//--- function call
COrtFac::RMatrixLQ(ft,n,m,tau);
//--- function call
COrtFac::RMatrixLQUnpackQ(ft,n,m,tau,n,q);
//--- function call
COrtFac::RMatrixLQUnpackL(ft,n,m,l);
//--- function call
LSFitLinearInternal(b,wmod,l,n,n,info,tmp,rep);
//--- check
if(info<=0)
return;
//--- calculation
for(i=0; i<n; i++)
{
v=tmp[i];
for(i_=0; i_<m; i_++)
c[i_]+=v*q.Get(i,i_);
}
//--- exit the function
return;
}
//--- N>=M. Generate design matrix and reduce to N=M using
//--- QR decomposition.
ft.Resize(n,m);
ArrayResize(b,n);
for(j=0; j<n; j++)
{
ft.Row(j,fmatrix[j]*w[j]);
b[j]=w[j]*y[j];
}
//--- function call
COrtFac::RMatrixQR(ft,n,m,tau);
//--- function call
COrtFac::RMatrixQRUnpackQ(ft,n,m,tau,m,q);
//--- function call
COrtFac::RMatrixQRUnpackR(ft,n,m,r);
//--- allocation
ArrayResize(tmp,m);
ArrayInitialize(tmp,0);
//--- calculation
for(i=0; i<n; i++)
{
v=b[i];
for(i_=0; i_<m; i_++)
tmp[i_]+=v*q.Get(i,i_);
}
//--- allocation
//--- copy
ArrayCopy(b,tmp);
//--- R contains reduced MxM design upper triangular matrix,
//--- B contains reduced Mx1 right part.
//--- Determine system condition number and decide
//--- should we use triangular solver (faster) or
//--- SVD-based solver (more stable).
//--- We can use LU-based RCond estimator for this task.
rep.m_taskrcond=CRCond::RMatrixLURCondInf(r,m);
//--- check
if(rep.m_taskrcond>threshold)
{
//--- use QR-based solver
ArrayResize(c,m);
c[m-1]=b[m-1]/r.Get(m-1,m-1);
//--- calculation
for(i=m-2; i>=0; i--)
{
v=0.0;
for(i_=i+1; i_<=m-1; i_++)
v+=r.Get(i,i_)*c[i_];
c[i]=(b[i]-v)/r.Get(i,i);
}
}
else
{
//--- use SVD-based solver
if(!CSingValueDecompose::RMatrixSVD(r,m,m,1,1,2,sv,u,vt))
{
info=-4;
return;
}
//--- allocation
ArrayResize(utb,m);
ArrayResize(sutb,m);
ArrayInitialize(utb,0);
//--- calculation
for(i=0; i<m; i++)
{
v=b[i];
for(i_=0; i_<m; i_++)
utb[i_]+=v*u.Get(i,i_);
}
//--- check
if(sv[0]>0.0)
{
rep.m_taskrcond=sv[m-1]/sv[0];
for(i=0; i<m; i++)
{
//--- check
if(sv[i]>threshold*sv[0])
sutb[i]=utb[i]/sv[i];
else
sutb[i]=0;
}
}
else
{
//--- change values
rep.m_taskrcond=0;
ArrayInitialize(sutb,0);
}
//--- allocation
ArrayResize(c,m);
ArrayInitialize(c,0);
//--- calculation
for(i=0; i<=m-1; i++)
{
v=sutb[i];
for(i_=0; i_<m; i_++)
c[i_]+=v*vt.Get(i,i_);
}
}
//--- calculate errors
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_avgrelerror=0;
rep.m_maxerror=0;
relcnt=0;
//--- calculation
for(i=0; i<n; i++)
{
v=0.0;
for(i_=0; i_<m; i_++)
v+=fmatrix.Get(i,i_)*c[i_];
//--- change values
rep.m_rmserror+=CMath::Sqr(v-y[i]);
rep.m_avgerror+=MathAbs(v-y[i]);
//--- check
if(y[i]!=0.0)
{
rep.m_avgrelerror+=MathAbs(v-y[i])/MathAbs(y[i]);
relcnt++;
}
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v-y[i]));
}
//--- change values
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
rep.m_avgerror/=n;
//--- check
if(relcnt!=0)
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
CRowDouble nzeros,s,Y,W,C;
nzeros=vector<double>::Zeros(n);
s=MathPow(fmatrix.ToMatrix()+0,2).Sum(0);
for(i=0; i<m; i++)
{
if(s[i]!=0)
s.Set(i,MathSqrt(1/s[i]));
else
s.Set(i,1);
}
Y=y;
W=w;
C=c;
EstimateErrors(fmatrix,nzeros,Y,W,C,s,n,m,rep,r,1);
}
//+------------------------------------------------------------------+
//| Internal subroutine |
//+------------------------------------------------------------------+
void CLSFit::LSFitClearRequestFields(CLSFitState &State)
{
//--- change values
State.m_needf=false;
State.m_needfg=false;
State.m_needfgh=false;
State.m_xupdated=false;
}
//+------------------------------------------------------------------+
//| Internal subroutine, calculates barycentric basis functions. |
//| Used for efficient simultaneous calculation of N basis functions.|
//+------------------------------------------------------------------+
void CLSFit::BarycentricCalcBasis(CBarycentricInterpolant &b,
const double t,double &y[])
{
//--- create variables
double s2=0;
double s=0;
double v=0;
//--- special case: N=1
if(b.m_n==1)
{
y[0]=1;
return;
}
//--- Here we assume that task is normalized,i.m_e.:
//--- 1. abs(Y[i])<=1
//--- 2. abs(W[i])<=1
//--- 3. X[] is ordered
//--- First,we decide: should we use "safe" formula (guarded
//--- against overflow) or fast one?
s=MathAbs(t-b.m_x[0]);
for(int i=0; i<=b.m_n-1; i++)
{
v=b.m_x[i];
//--- check
if(v==t)
{
for(int j=0; j<=b.m_n-1; j++)
y[j]=0;
y[i]=1;
//--- exit the function
return;
}
//--- change value
v=MathAbs(t-v);
//--- check
if(v<s)
s=v;
}
s2=0;
//--- calculation
for(int i=0; i<=b.m_n-1; i++)
{
v=s/(t-b.m_x[i]);
v=v*b.m_w[i];
y[i]=v;
s2=s2+v;
}
//--- change values
v=1/s2;
for(int i_=0; i_<=b.m_n-1; i_++)
y[i_]=v*y[i_];
}
//+------------------------------------------------------------------+
//| This is internal function for Chebyshev fitting. |
//| It assumes that input data are normalized: |
//| * X/XC belong to [-1,+1], |
//| * mean(Y)=0, stddev(Y)=1. |
//| It does not checks inputs for errors. |
//| This function is used to fit general (shifted) Chebyshev models, |
//| power basis models or barycentric models. |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| N - number of points, N>0. |
//| XC - points where polynomial values/derivatives are |
//| constrained, array[0..K-1]. |
//| YC - values of constraints, array[0..K-1] |
//| DC - array[0..K-1], types of constraints: |
//| * DC[i]=0 means that P(XC[i])=YC[i] |
//| * DC[i]=1 means that P'(XC[i])=YC[i] |
//| K - number of constraints, 0<=K<M. |
//| K=0 means no constraints (XC/YC/DC are not used in |
//| such cases) |
//| M - number of basis functions (= polynomial_degree + 1), |
//| M>=1 |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearW() subroutine: |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| C - interpolant in Chebyshev form; [-1,+1] is used as |
//| base interval |
//| Rep - report, same format as in LSFitLinearW() subroutine. |
//| Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number for|
//| K<>0. |
//+------------------------------------------------------------------+
void CLSFit::InternalChebyshevFit(double &x[],double &y[],double &w[],
const int n,double &cxc[],double &cyc[],
int &dc[],const int k,const int m,
int &info,double &c[],CLSFitReport &rep)
{
//--- create variables
double mx=0;
double decay=0;
//--- create arrays
double y2[];
double w2[];
double tmp[];
double tmp2[];
double tmpdiff[];
double bx[];
double by[];
double bw[];
double xc[];
double yc[];
//--- create matrix
CMatrixDouble fmatrix;
CMatrixDouble cmatrix;
//--- copy arrays
ArrayCopy(xc,cxc);
ArrayCopy(yc,cyc);
//--- initialization
info=0;
//--- weight decay for correct handling of task which becomes
//--- degenerate after constraints are applied
decay=10000*CMath::m_machineepsilon;
//--- allocate space,initialize/fill:
//--- * FMatrix- values of basis functions at X[]
//--- * CMatrix- values (derivatives) of basis functions at XC[]
//--- * fill constraints matrix
//--- * fill first N rows of design matrix with values
//--- * fill next M rows of design matrix with regularizing term
//--- * append M zeros to Y
//--- * append M elements,mean(abs(W)) each,to W
ArrayResize(y2,n+m);
ArrayResize(w2,n+m);
ArrayResize(tmp,m);
ArrayResize(tmpdiff,m);
fmatrix=matrix<double>::Zeros(n+m,m);
//--- check
if(k>0)
cmatrix.Resize(k,m+1);
//--- Fill design matrix,Y2,W2:
//--- * first N rows with basis functions for original points
//--- * next M rows with decay terms
for(int i=0; i<n; i++)
{
//--- prepare Ith row
//--- use Tmp for calculations to avoid multidimensional arrays overhead
for(int j=0; j<=m-1; j++)
{
//--- check
if(j==0)
tmp[j]=1;
else
{
//--- check
if(j==1)
tmp[j]=x[i];
else
tmp[j]=2*x[i]*tmp[j-1]-tmp[j-2];
}
}
//--- copy
for(int i_=0; i_<=m-1; i_++)
fmatrix.Set(i,i_,tmp[i_]);
}
//--- calculation
for(int i=0; i<=m-1; i++)
{
for(int j=0; j<=m-1; j++)
{
//--- check
if(i==j)
fmatrix.Set(n+i,j,decay);
else
fmatrix.Set(n+i,j,0);
}
}
//--- copy
for(int i_=0; i_<n; i_++)
y2[i_]=y[i_];
for(int i_=0; i_<n; i_++)
w2[i_]=w[i_];
//--- change value
mx=0;
for(int i=0; i<n; i++)
mx=mx+MathAbs(w[i]);
mx=mx/n;
//--- change values
for(int i=0; i<=m-1; i++)
{
y2[n+i]=0;
w2[n+i]=mx;
}
//--- fill constraints matrix
for(int i=0; i<k; i++)
{
//--- prepare Ith row
//--- use Tmp for basis function values,
//--- TmpDiff for basos function derivatives
for(int j=0; j<=m-1; j++)
{
//--- check
if(j==0)
{
tmp[j]=1;
tmpdiff[j]=0;
}
else
{
//--- check
if(j==1)
{
tmp[j]=xc[i];
tmpdiff[j]=1;
}
else
{
tmp[j]=2*xc[i]*tmp[j-1]-tmp[j-2];
tmpdiff[j]=2*(tmp[j-1]+xc[i]*tmpdiff[j-1])-tmpdiff[j-2];
}
}
}
//--- check
if(dc[i]==0)
{
for(int i_=0; i_<=m-1; i_++)
cmatrix.Set(i,i_,tmp[i_]);
}
//--- check
if(dc[i]==1)
{
for(int i_=0; i_<=m-1; i_++)
cmatrix.Set(i,i_,tmpdiff[i_]);
}
cmatrix.Set(i,m,yc[i]);
}
//--- Solve constrained task
if(k>0)
{
//--- solve using regularization
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,c,rep);
}
else
{
//--- no constraints,no regularization needed
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,0,info,c,rep);
}
}
//+------------------------------------------------------------------+
//| Internal Floater-Hormann fitting subroutine for fixed D |
//+------------------------------------------------------------------+
void CLSFit::BarycentricFitWCFixedD(double &cx[],double &cy[],
double &cw[],const int n,
double &cxc[],double &cyc[],
int &dc[],const int k,const int m,
const int d,int &info,
CBarycentricInterpolant &b,
CBarycentricFitReport &rep)
{
//--- create variables
double v0=0;
double v1=0;
double mx=0;
int i=0;
int j=0;
int relcnt=0;
double xa=0;
double xb=0;
double sa=0;
double sb=0;
double decay=0;
int i_=0;
//--- create arrays
double y2[];
double w2[];
double sx[];
double sy[];
double sbf[];
double xoriginal[];
double yoriginal[];
double tmp[];
double x[];
double y[];
double w[];
double xc[];
double yc[];
//--- create matrix
CMatrixDouble fmatrix;
CMatrixDouble cmatrix;
//--- objects of classes
CLSFitReport lrep;
CBarycentricInterpolant b2;
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(w,cw);
ArrayCopy(xc,cxc);
ArrayCopy(yc,cyc);
//--- initialization
info=0;
//--- check
if(((n<1 || m<2) || k<0) || k>=m)
{
info=-1;
return;
}
for(i=0; i<k; i++)
{
info=0;
//--- check
if(dc[i]<0)
info=-1;
//--- check
if(dc[i]>1)
info=-1;
//--- check
if(info<0)
return;
}
//--- weight decay for correct handling of task which becomes
//--- degenerate after constraints are applied
decay=10000*CMath::m_machineepsilon;
//--- Scale X,Y,XC,YC
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
//--- allocate space,initialize:
//--- * FMatrix- values of basis functions at X[]
//--- * CMatrix- values (derivatives) of basis functions at XC[]
ArrayResize(y2,n+m);
ArrayResize(w2,n+m);
fmatrix.Resize(n+m,m);
//--- check
if(k>0)
cmatrix.Resize(k,m+1);
//--- allocation
ArrayResize(y2,n+m);
ArrayResize(w2,n+m);
//--- Prepare design and constraints matrices:
//--- * fill constraints matrix
//--- * fill first N rows of design matrix with values
//--- * fill next M rows of design matrix with regularizing term
//--- * append M zeros to Y
//--- * append M elements,mean(abs(W)) each,to W
ArrayResize(sx,m);
ArrayResize(sy,m);
ArrayResize(sbf,m);
for(j=0; j<=m-1; j++)
sx[j]=(double)(2*j)/(double)(m-1)-1;
for(i=0; i<=m-1; i++)
sy[i]=1;
//--- function call
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b2);
//--- change value
mx=0;
//--- calculation
for(i=0; i<n; i++)
{
//--- function call
BarycentricCalcBasis(b2,x[i],sbf);
for(i_=0; i_<=m-1; i_++)
fmatrix.Set(i,i_,sbf[i_]);
//--- change values
y2[i]=y[i];
w2[i]=w[i];
mx=mx+MathAbs(w[i])/n;
}
//--- calculation
for(i=0; i<=m-1; i++)
{
for(j=0; j<=m-1; j++)
{
//--- check
if(i==j)
fmatrix.Set(n+i,j,decay);
else
fmatrix.Set(n+i,j,0);
}
//--- change values
y2[n+i]=0;
w2[n+i]=mx;
}
//--- check
if(k>0)
{
for(j=0; j<=m-1; j++)
{
for(i=0; i<=m-1; i++)
sy[i]=0;
sy[j]=1;
//--- function call
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b2);
//--- calculation
for(i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]>=0 && dc[i]<=1,__FUNCTION__+": internal error!"))
return;
//--- function call
CRatInt::BarycentricDiff1(b2,xc[i],v0,v1);
//--- check
if(dc[i]==0)
cmatrix.Set(i,j,v0);
//--- check
if(dc[i]==1)
cmatrix.Set(i,j,v1);
}
}
for(i=0; i<k; i++)
cmatrix.Set(i,m,yc[i]);
}
//--- Solve constrained task
if(k>0)
{
//--- solve using regularization
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,tmp,lrep);
}
else
{
//--- no constraints,no regularization needed
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,tmp,lrep);
}
//--- check
if(info<0)
return;
//--- Generate interpolant and scale it
for(i_=0; i_<=m-1; i_++)
sy[i_]=tmp[i_];
//--- function call
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b);
//--- function call
CRatInt::BarycentricLinTransX(b,2/(xb-xa),-((xa+xb)/(xb-xa)));
//--- function call
CRatInt::BarycentricLinTransY(b,sb-sa,sa);
//--- Scale absolute errors obtained from LSFitLinearW.
//--- Relative error should be calculated separately
//--- (because of shifting/scaling of the task)
rep.m_taskrcond=lrep.m_taskrcond;
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
rep.m_avgrelerror=0;
relcnt=0;
for(i=0; i<n; i++)
{
//--- check
if(yoriginal[i]!=0.0)
{
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CRatInt::BarycentricCalc(b,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
relcnt=relcnt+1;
}
}
//--- check
if(relcnt!=0)
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
}
//+------------------------------------------------------------------+
//| This function calculates value of four-parameter logistic (4PL) |
//| model at specified point X. 4PL model has following form: |
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
//| INPUT PARAMETERS: |
//| X - current point, X>=0: |
//| * zero X is correctly handled even for B<=0 |
//| * negative X results in exception. |
//| A, B, C, D - parameters of 4PL model: |
//| * A is unconstrained |
//| * B is unconstrained; zero or negative values |
//| are handled correctly. |
//| * C>0, non-positive value results in exception |
//| * D is unconstrained |
//| RESULT: |
//| model value at X |
//| NOTE: if B=0, denominator is assumed to be equal to 2.0 even for|
//| zero X (strictly speaking, 0^0 is undefined). |
//| NOTE: this function also throws exception if all input parameters|
//| are correct, but overflow was detected during calculations.|
//| NOTE: this function performs a lot of checks; if you need really |
//| high performance, consider evaluating model yourself, |
//| without checking for degenerate cases. |
//+------------------------------------------------------------------+
double CLSFit::LogisticCalc4(double x,double a,double b,double c,
double d)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x),"LogisticCalc4: X is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(a),"LogisticCalc4: A is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(b),"LogisticCalc4: B is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(c),"LogisticCalc4: C is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(d),"LogisticCalc4: D is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(x>=0.0,"LogisticCalc4: X is negative"))
return(EMPTY_VALUE);
if(!CAp::Assert(c>0.0,"LogisticCalc4: C is non-positive"))
return(EMPTY_VALUE);
//--- Check for degenerate cases
if(b==0.0)
{
result=0.5*(a+d);
return(result);
}
if(x==0.0)
{
if(b>0.0)
result=a;
else
result=d;
return(result);
}
//--- General case
result=d+(a-d)/(1.0+MathPow(x/c,b));
if(!CAp::Assert(MathIsValidNumber(result),"LogisticCalc4: overflow during calculations"))
return(EMPTY_VALUE);
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates value of five-parameter logistic (5PL) |
//| model at specified point X. 5PL model has following form: |
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
//| INPUT PARAMETERS: |
//| X - current point, X>=0: |
//| * zero X is correctly handled even for B<=0 |
//| * negative X results in exception. |
//| A, B, C, D, G- parameters of 5PL model: |
//| * A is unconstrained |
//| * B is unconstrained; zero or negative values |
//| are handled correctly. |
//| * C>0, non-positive value results in exception |
//| * D is unconstrained |
//| * G>0, non-positive value results in exception |
//| RESULT: |
//| model value at X |
//| NOTE: if B=0, denominator is assumed to be equal to Power(2.0,G) |
//| even for zero X (strictly speaking, 0^0 is undefined). |
//| NOTE: this function also throws exception if all input parameters|
//| are correct, but overflow was detected during calculations.|
//| NOTE: this function performs a lot of checks; if you need really |
//| high performance, consider evaluating model yourself, |
//| without checking for degenerate cases. |
//+------------------------------------------------------------------+
double CLSFit::LogisticCalc5(double x,double a,double b,double c,
double d,double g)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x),"LogisticCalc5: X is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(a),"LogisticCalc5: A is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(b),"LogisticCalc5: B is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(c),"LogisticCalc5: C is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(d),"LogisticCalc5: D is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(MathIsValidNumber(g),"LogisticCalc5: G is not finite"))
return(EMPTY_VALUE);
if(!CAp::Assert(x>=0.0,"LogisticCalc5: X is negative"))
return(EMPTY_VALUE);
if(!CAp::Assert(c>0.0,"LogisticCalc5: C is non-positive"))
return(EMPTY_VALUE);
if(!CAp::Assert(g>0.0,"LogisticCalc5: G is non-positive"))
return(EMPTY_VALUE);
//--- Check for degenerate cases
if(b==0.0)
{
result=d+(a-d)/MathPow(2.0,g);
return(result);
}
if(x==0.0)
{
if(b>0.0)
result=a;
else
result=d;
return(result);
}
//--- General case
result=d+(a-d)/MathPow(1.0+MathPow(x/c,b),g);
//--- check
if(!CAp::Assert(MathIsValidNumber(result),"LogisticCalc5: overflow during calculations"))
return(EMPTY_VALUE);
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function fits four-parameter logistic (4PL) model to data |
//| provided by user. 4PL model has following form: |
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
//| Here: |
//| * A, D - unconstrained (see LogisticFit4EC() for |
//| constrained 4PL) |
//| * B>=0 |
//| * C>0 |
//| IMPORTANT: output of this function is constrained in such way |
//| that B>0. Because 4PL model is symmetric with respect|
//| to B, there is no need to explore B<0. Constraining |
//| B makes algorithm easier to stabilize and debug. |
//| Users who for some reason prefer to work with |
//| negative B's should transform output themselves (swap |
//| A and D, replace B by -B). |
//| 4PL fitting is implemented as follows: |
//| * we perform small number of restarts from random locations |
//| which helps to solve problem of bad local extrema. Locations |
//| are only partially random - we use input data to determine |
//| good initial guess, but we include controlled amount of |
//| randomness. |
//| * we perform Levenberg-Marquardt fitting with very tight |
//| constraints on parameters B and C - it allows us to find good|
//| initial guess for the second stage without risk of running|
//| into "flat spot". |
//| * second Levenberg-Marquardt round is performed without |
//| excessive constraints. Results from the previous round are |
//| used as initial guess. |
//| * after fitting is done, we compare results with best values |
//| found so far, rewrite "best solution" if needed, and move to |
//| next random location. |
//| Overall algorithm is very stable and is not prone to bad local |
//| extrema. Furthermore, it automatically scales when input data |
//| have very large or very small range. |
//| INPUT PARAMETERS: |
//| X - array[N], stores X-values. |
//| MUST include only non-negative numbers (but may |
//| include zero values). Can be unsorted. |
//| Y - array[N], values to fit. |
//| N - number of points. If N is less than length of |
//| X/Y, only leading N elements are used. |
//| OUTPUT PARAMETERS: |
//| A, B, C, D- parameters of 4PL model |
//| Rep - fitting report. This structure has many fields, |
//| but ONLY ONES LISTED BELOW ARE SET: |
//| * Rep.IterationsCount - number of iterations |
//| performed |
//| * Rep.RMSError - root-mean-square error |
//| * Rep.AvgError - average absolute error |
//| * Rep.AvgRelError - average relative error |
//| (calculated for non-zero |
//| Y-values) |
//| * Rep.MaxError - maximum absolute error |
//| * Rep.R2 - coefficient of determination,|
//| R-squared. This coefficient|
//| is calculated as |
//| R2=1-RSS/TSS (in case of |
//| nonlinear regression there|
//| are multiple ways to |
//| define R2, each of them |
//| giving different results). |
//| NOTE: for stability reasons the B parameter is restricted by |
//| [1/1000,1000] range. It prevents algorithm from making |
//| trial steps deep into the area of bad parameters. |
//| NOTE: after you obtained coefficients, you can evaluate model |
//| with LogisticCalc4() function. |
//| NOTE: if you need better control over fitting process than |
//| provided by this function, you may use LogisticFit45X(). |
//| NOTE: step is automatically scaled according to scale of |
//| parameters being fitted before we compare its length with |
//| EpsX. Thus, this function can be used to fit data with |
//| very small or very large values without changing EpsX. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit4(CRowDouble &x,CRowDouble &y,int n,
double &a,double &b,double &c,
double &d,CLSFitReport &rep)
{
double g=0;
//--- function call
LogisticFit45x(x,y,n,AL_NaN,AL_NaN,true,0.0,0.0,0,a,b,c,d,g,rep);
}
//+------------------------------------------------------------------+
//| This function fits four-parameter logistic (4PL) model to data |
//| provided by user. 4PL model has following form: |
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
//| Here: |
//| * A, D - unconstrained (see LogisticFit4EC() for |
//| constrained 4PL) |
//| * B>=0 |
//| * C>0 |
//| IMPORTANT: output of this function is constrained in such way |
//| that B>0. Because 4PL model is symmetric with respect|
//| to B, there is no need to explore B<0. Constraining |
//| B makes algorithm easier to stabilize and debug. |
//| Users who for some reason prefer to work with |
//| negative B's should transform output themselves (swap |
//| A and D, replace B by -B). |
//| 4PL fitting is implemented as follows: |
//| * we perform small number of restarts from random locations |
//| which helps to solve problem of bad local extrema. Locations |
//| are only partially random - we use input data to determine |
//| good initial guess, but we include controlled amount of |
//| randomness. |
//| * we perform Levenberg-Marquardt fitting with very tight |
//| constraints on parameters B and C - it allows us to find good|
//| initial guess for the second stage without risk of running|
//| into "flat spot". |
//| * second Levenberg-Marquardt round is performed without |
//| excessive constraints. Results from the previous round are |
//| used as initial guess. |
//| * after fitting is done, we compare results with best values |
//| found so far, rewrite "best solution" if needed, and move to |
//| next random location. |
//| Overall algorithm is very stable and is not prone to bad local |
//| extrema. Furthermore, it automatically scales when input data |
//| have very large or very small range. |
//| INPUT PARAMETERS: |
//| X - array[N], stores X-values. |
//| MUST include only non-negative numbers (but may |
//| include zero values). Can be unsorted. |
//| Y - array[N], values to fit. |
//| N - number of points. If N is less than length of |
//| X/Y, only leading N elements are used. |
//| CnstrLeft- optional equality constraint for model value at the|
//| left boundary (at X=0). Specify NAN (Not-a-Number) |
//| if you do not need constraint on the model value |
//| at X=0. See below, section "EQUALITY CONSTRAINTS" |
//| for more information about constraints. |
//| CnstrRight- optional equality constraint for model value at |
//| X=infinity. Specify NAN (Not-a-Number) if you do |
//| not need constraint on the model value. See below,|
//| section "EQUALITY CONSTRAINTS" for more |
//| information about constraints. |
//| OUTPUT PARAMETERS:
//| OUTPUT PARAMETERS: |
//| A, B, C, D- parameters of 4PL model |
//| Rep - fitting report. This structure has many fields, |
//| but ONLY ONES LISTED BELOW ARE SET: |
//| * Rep.IterationsCount - number of iterations |
//| performed |
//| * Rep.RMSError - root-mean-square error |
//| * Rep.AvgError - average absolute error |
//| * Rep.AvgRelError - average relative error |
//| (calculated for non-zero |
//| Y-values) |
//| * Rep.MaxError - maximum absolute error |
//| * Rep.R2 - coefficient of determination,|
//| R-squared. This coefficient|
//| is calculated as |
//| R2=1-RSS/TSS (in case of |
//| nonlinear regression there|
//| are multiple ways to |
//| define R2, each of them |
//| giving different results). |
//| NOTE: for stability reasons the B parameter is restricted by |
//| [1/1000,1000] range. It prevents algorithm from making |
//| trial steps deep into the area of bad parameters. |
//| NOTE: after you obtained coefficients, you can evaluate model |
//| with LogisticCalc4() function. |
//| NOTE: if you need better control over fitting process than |
//| provided by this function, you may use LogisticFit45X(). |
//| NOTE: step is automatically scaled according to scale of |
//| parameters being fitted before we compare its length with |
//| EpsX. Thus, this function can be used to fit data with |
//| very small or very large values without changing EpsX. |
//| EQUALITY CONSTRAINTS ON PARAMETERS |
//| 4PL/5PL solver supports equality constraints on model values at |
//| the left boundary (X=0) and right boundary (X=infinity). These|
//| constraints are completely optional and you can specify both of |
//| them, only one - or no constraints at all. |
//| Parameter CnstrLeft contains left constraint (or NAN for |
//| unconstrained fitting), and CnstrRight contains right one. For |
//| 4PL, left constraint ALWAYS corresponds to parameter A, and |
//| right one is ALWAYS constraint on D. That's because 4PL model |
//| is normalized in such way that B>=0. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit4ec(CRowDouble &x,CRowDouble &y,int n,
double cnstrleft,double cnstrright,
double &a,double &b,double &c,
double &d,CLSFitReport &rep)
{
double g=0;
//--- function call
LogisticFit45x(x,y,n,cnstrleft,cnstrright,true,0.0,0.0,0,a,b,c,d,g,rep);
}
//+------------------------------------------------------------------+
//| This function fits five-parameter logistic (5PL) model to data |
//| provided by user. 5PL model has following form: |
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
//| Here: |
//| * A, D - unconstrained |
//| * B - unconstrained |
//| * C>0 |
//| * G>0 |
//| IMPORTANT: unlike in 4PL fitting, output of this function |
//| is NOT constrained in such way that B is guaranteed |
//| to be positive. Furthermore, unlike 4PL, 5PL model |
//| is NOT symmetric with respect to B, so you can NOT |
//| transform model to equivalent one, with B having |
//| desired sign (>0 or <0). |
//| 5PL fitting is implemented as follows: |
//| * we perform small number of restarts from random locations |
//| which helps to solve problem of bad local extrema. Locations |
//| are only partially random - we use input data to determine |
//| good initial guess, but we include controlled amount of |
//| randomness. |
//| * we perform Levenberg - Marquardt fitting with very tight |
//| constraints on parameters B and C - it allows us to find good|
//| initial guess for the second stage without risk of running|
//| into "flat spot". Parameter G is fixed at G = 1. |
//| * second Levenberg - Marquardt round is performed without |
//| excessive constraints on B and C, but with G still equal to 1|
//| Results from the previous round are used as initial guess. |
//| * third Levenberg - Marquardt round relaxes constraints on G |
//| and tries two different models - one with B > 0 and one |
//| with B < 0. |
//| * after fitting is done, we compare results with best values |
//| found so far, rewrite "best solution" if needed, and move to |
//| next random location. |
//| Overall algorithm is very stable and is not prone to bad local |
//| extrema. Furthermore, it automatically scales when input data |
//| have very large or very small range. |
//| INPUT PARAMETERS: |
//| X - array[N], stores X - values. |
//| MUST include only non - negative numbers(but may|
//| include zero values). Can be unsorted. |
//| Y - array[N], values to fit. |
//| N - number of points. If N is less than length of |
//| X / Y, only leading N elements are used. |
//| OUTPUT PARAMETERS: |
//| A, B, C, D, G - parameters of 5PL model |
//| Rep - fitting report. This structure has many fields, |
//| but ONLY ONES LISTED BELOW ARE SET: |
//| * Rep.IterationsCount - number of iterations performed|
//| * Rep.RMSError - root - mean - square error |
//| * Rep.AvgError - average absolute error |
//| * Rep.AvgRelError - average relative error |
//| (calculated for non - zero |
//| Y - values) |
//| * Rep.MaxError - maximum absolute error |
//| * Rep.R2 - coefficient of determination, |
//| R - squared. This coefficient|
//| is calculated as |
//| R2 = 1 - RSS / TSS (in case |
//| of nonlinear regression there|
//| are multiple ways to define|
//| R2, each of them giving |
//| different results). |
//| NOTE: for better stability B parameter is restricted by |
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
//| range. It prevents algorithm from making trial steps deep |
//| into the area of bad parameters. |
//| NOTE: after you obtained coefficients, you can evaluate |
//| model with LogisticCalc5() function. |
//| NOTE: if you need better control over fitting process than |
//| provided by this function, you may use LogisticFit45X(). |
//| NOTE: step is automatically scaled according to scale of |
//| parameters being fitted before we compare its length with |
//| EpsX. Thus, this function can be used to fit data with |
//| very small or very large values without changing EpsX. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit5(CRowDouble &x,CRowDouble &y,int n,
double &a,double &b,double &c,
double &d,double &g,CLSFitReport &rep)
{
LogisticFit45x(x,y,n,AL_NaN,AL_NaN,false,0.0,0.0,0,a,b,c,d,g,rep);
}
//+------------------------------------------------------------------+
//| This function fits five-parameter logistic (5PL) model to data |
//| provided by user. 5PL model has following form: |
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
//| Here: |
//| * A, D - unconstrained |
//| * B - unconstrained |
//| * C>0 |
//| * G>0 |
//| IMPORTANT: unlike in 4PL fitting, output of this function |
//| is NOT constrained in such way that B is guaranteed |
//| to be positive. Furthermore, unlike 4PL, 5PL model |
//| is NOT symmetric with respect to B, so you can NOT |
//| transform model to equivalent one, with B having |
//| desired sign (>0 or <0). |
//| 5PL fitting is implemented as follows: |
//| * we perform small number of restarts from random locations |
//| which helps to solve problem of bad local extrema. Locations |
//| are only partially random - we use input data to determine |
//| good initial guess, but we include controlled amount of |
//| randomness. |
//| * we perform Levenberg - Marquardt fitting with very tight |
//| constraints on parameters B and C - it allows us to find good|
//| initial guess for the second stage without risk of running|
//| into "flat spot". Parameter G is fixed at G = 1. |
//| * second Levenberg - Marquardt round is performed without |
//| excessive constraints on B and C, but with G still equal to 1|
//| Results from the previous round are used as initial guess. |
//| * third Levenberg - Marquardt round relaxes constraints on G |
//| and tries two different models - one with B > 0 and one |
//| with B < 0. |
//| * after fitting is done, we compare results with best values |
//| found so far, rewrite "best solution" if needed, and move to |
//| next random location. |
//| Overall algorithm is very stable and is not prone to bad local |
//| extrema. Furthermore, it automatically scales when input data |
//| have very large or very small range. |
//| INPUT PARAMETERS: |
//| X - array[N], stores X - values. |
//| MUST include only non - negative numbers(but may|
//| include zero values). Can be unsorted. |
//| Y - array[N], values to fit. |
//| N - number of points. If N is less than length of |
//| X / Y, only leading N elements are used. |
//| CnstrLeft - optional equality constraint for model value at |
//| the left boundary(at X = 0). |
//| Specify NAN(Not - a - Number) if you do not |
//| need constraint on the model value at X = 0. |
//| See below, section "EQUALITY CONSTRAINTS" |
//| for more information about constraints. |
//| CnstrRight - optional equality constraint for model value at |
//| X = infinity. |
//| Specify NAN(Not - a - Number) if you do not |
//| need constraint on the model value at X = 0. |
//| See below, section "EQUALITY CONSTRAINTS" |
//| for more information about constraints. |
//| OUTPUT PARAMETERS: |
//| A, B, C, D, G - parameters of 5PL model |
//| Rep - fitting report. This structure has many fields, |
//| but ONLY ONES LISTED BELOW ARE SET: |
//| * Rep.IterationsCount - number of iterations performed|
//| * Rep.RMSError - root - mean - square error |
//| * Rep.AvgError - average absolute error |
//| * Rep.AvgRelError - average relative error |
//| (calculated for non - zero |
//| Y - values) |
//| * Rep.MaxError - maximum absolute error |
//| * Rep.R2 - coefficient of determination, |
//| R - squared. This coefficient|
//| is calculated as |
//| R2 = 1 - RSS / TSS (in case |
//| of nonlinear regression there|
//| are multiple ways to define|
//| R2, each of them giving |
//| different results). |
//| NOTE: for better stability B parameter is restricted by |
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
//| range. It prevents algorithm from making trial steps deep |
//| into the area of bad parameters. |
//| NOTE: after you obtained coefficients, you can evaluate |
//| model with LogisticCalc5() function. |
//| NOTE: if you need better control over fitting process than |
//| provided by this function, you may use LogisticFit45X(). |
//| NOTE: step is automatically scaled according to scale of |
//| parameters being fitted before we compare its length with |
//| EpsX. Thus, this function can be used to fit data with |
//| very small or very large values without changing EpsX. |
//| EQUALITY CONSTRAINTS ON PARAMETERS |
//| 5PL solver supports equality constraints on model values at the|
//| left boundary(X = 0) and right boundary(X = infinity). These |
//| constraints are completely optional and you can specify both of |
//| them, only one - or no constraints at all. |
//| Parameter CnstrLeft contains left constraint (or NAN for |
//| unconstrained fitting), and CnstrRight contains right one. |
//| Unlike 4PL one, 5PL model is NOT symmetric with respect to change|
//| in sign of B. Thus, negative B's are possible, and left |
//| constraint may constrain parameter A(for positive B's) - or |
//| parameter D (for negative B's). Similarly changes meaning of |
//| right constraint. |
//| You do not have to decide what parameter to constrain - algorithm|
//| will automatically determine correct parameters as fitting |
//| progresses. However, question highlighted above is important when|
//| you interpret fitting results. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit5ec(CRowDouble &x,CRowDouble &y,int n,
double cnstrleft,double cnstrright,
double &a,double &b,double &c,double &d,
double &g,CLSFitReport &rep)
{
LogisticFit45x(x,y,n,cnstrleft,cnstrright,false,0.0,0.0,0,a,b,c,d,g,rep);
}
//+------------------------------------------------------------------+
//| This is "expert" 4PL / 5PL fitting function, which can be used if|
//| you need better control over fitting process than provided by |
//| LogisticFit4() or LogisticFit5(). |
//| This function fits model of the form |
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) (4PL model) |
//| or |
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)(5PL model) |
//| Here: |
//| * A, D - unconstrained |
//| * B >= 0 for 4PL, unconstrained for 5PL |
//| * C > 0 |
//| * G > 0(if present) |
//| INPUT PARAMETERS: |
//| X - array[N], stores X - values. |
//| MUST include only non-negative numbers(but may |
//| include zero values). Can be unsorted. |
//| Y - array[N], values to fit. |
//| N - number of points. If N is less than length of |
//| X / Y, only leading N elements are used. |
//| CnstrLeft - optional equality constraint for model value at |
//| the left boundary(at X = 0). |
//| Specify NAN (Not-a-Number) if you do not need |
//| constraint on the model value at X = 0. |
//| See below, section "EQUALITY CONSTRAINTS" |
//| for more information about constraints. |
//| CnstrRight - optional equality constraint for model value at |
//| X = infinity. |
//| Specify NAN (Not-a-Number) if you do not need |
//| constraint on the model value at X = 0. |
//| See below, section "EQUALITY CONSTRAINTS" |
//| for more information about constraints. |
//| Is4PL - whether 4PL or 5PL models are fitted |
//| LambdaV - regularization coefficient, LambdaV >= 0. Set it|
//| to zero unless you know what you are doing. |
//| EpsX - stopping condition(step size), EpsX >= 0. Zero |
//| value means that small step is automatically |
//| chosen. See notes below for more information. |
//| RsCnt - number of repeated restarts from random points.|
//| 4PL/5PL models are prone to problem of bad local|
//| extrema. Utilizing multiple random restarts |
//| allows us to improve algorithm convergence. |
//| RsCnt >= 0. Zero value means that function |
//| automatically choose small amount of restarts |
//| (recommended). |
//| OUTPUT PARAMETERS: |
//| A, B, C, D - parameters of 4PL model |
//| G - parameter of 5PL model; for Is4PL = True, G = 1 |
//| is returned. |
//| Rep - fitting report. This structure has many fields, |
//| but ONLY ONES LISTED BELOW ARE SET: |
//| * Rep.IterationsCount - number of iterations performed|
//| * Rep.RMSError - root - mean - square error |
//| * Rep.AvgError - average absolute error |
//| * Rep.AvgRelError - average relative error |
//| (calculated for non - zero |
//| Y - values) |
//| * Rep.MaxError - maximum absolute error |
//| * Rep.R2 - coefficient of determination, |
//| R - squared. This coefficient|
//| is calculated as |
//| R2 = 1 - RSS / TSS (in case |
//| of nonlinear regression there|
//| are multiple ways to define|
//| R2, each of them giving |
//| different results). |
//| NOTE: for better stability B parameter is restricted by |
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
//| range. It prevents algorithm from making trial steps deep |
//| into the area of bad parameters. |
//| NOTE: after you obtained coefficients, you can evaluate |
//| model with LogisticCalc5() function. |
//| NOTE: if you need better control over fitting process than |
//| provided by this function, you may use LogisticFit45X(). |
//| NOTE: step is automatically scaled according to scale of |
//| parameters being fitted before we compare its length with |
//| EpsX. Thus, this function can be used to fit data with |
//| very small or very large values without changing EpsX. |
//| EQUALITY CONSTRAINTS ON PARAMETERS |
//| 4PL/5PL solver supports equality constraints on model values at |
//| the left boundary(X = 0) and right boundary(X = infinity). These |
//| constraints are completely optional and you can specify both of |
//| them, only one - or no constraints at all. |
//| Parameter CnstrLeft contains left constraint (or NAN for |
//| unconstrained fitting), and CnstrRight contains right one. For |
//| 4PL, left constraint ALWAYS corresponds to parameter A, and right|
//| one is ALWAYS constraint on D. That's because 4PL model is |
//| normalized in such way that B>=0. |
//| For 5PL model things are different. Unlike 4PL one, 5PL model is |
//| NOT symmetric with respect to change in sign of B. Thus, |
//| negative B's are possible, and left constraint may constrain |
//| parameter A(for positive B's) - or parameter D(for negative B's).|
//| Similarly changes meaning of right constraint. |
//| You do not have to decide what parameter to constrain - algorithm|
//| will automatically determine correct parameters as fitting |
//| progresses. However, question highlighted above is important when|
//| you interpret fitting results. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit45x(CRowDouble &X,CRowDouble &Y,int n,
double cnstrleft,double cnstrright,
bool is4pl,double lambdav,double epsx,
int rscnt,double &a,double &b,double &c,
double &d,double &g,CLSFitReport &rep)
{
//--- create variables
int i=0;
int outerit=0;
int nz=0;
double v=0;
CRowDouble p0;
CRowDouble p1;
CRowDouble p2;
CRowDouble bndl;
CRowDouble bndu;
CRowDouble s;
CRowDouble bndl1;
CRowDouble bndu1;
CRowDouble bndl2;
CRowDouble bndu2;
CMatrixDouble z;
CHighQualityRandState rs;
CMinLMState state;
CMinLMReport replm;
int maxits=0;
double fbest=0;
double flast=0;
double scalex=0;
double scaley=0;
CRowDouble bufx;
CRowDouble bufy;
double fposb=0;
double fnegb=0;
CRowDouble x=X;
CRowDouble y=Y;
a=0;
b=0;
c=0;
d=0;
g=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(epsx),"LogisticFitX: EpsX is infinite/NAN"))
return;
if(!CAp::Assert(MathIsValidNumber(lambdav),"LogisticFitX: LambdaV is infinite/NAN"))
return;
if(!CAp::Assert(MathIsValidNumber(cnstrleft) || CInfOrNaN::IsNaN(cnstrleft),"LogisticFitX: CnstrLeft is NOT finite or NAN"))
return;
if(!CAp::Assert(MathIsValidNumber(cnstrright) || CInfOrNaN::IsNaN(cnstrright),"LogisticFitX: CnstrRight is NOT finite or NAN"))
return;
if(!CAp::Assert(lambdav>=0.0,"LogisticFitX: negative LambdaV"))
return;
if(!CAp::Assert(n>0,"LogisticFitX: N<=0"))
return;
if(!CAp::Assert(rscnt>=0,"LogisticFitX: RsCnt<0"))
return;
if(!CAp::Assert(epsx>=0.0,"LogisticFitX: EpsX<0"))
return;
if(!CAp::Assert(CAp::Len(x)>=n,"LogisticFitX: Length(X)<N"))
return;
if(!CAp::Assert(CAp::Len(y)>=n,"LogisticFitX: Length(Y)<N"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),"LogisticFitX: X contains infinite/NAN values"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),"LogisticFitX: X contains infinite/NAN values"))
return;
CHighQualityRand::HQRndSeed(2211,1033044,rs);
ClearReport(rep);
if(epsx==0.0)
epsx=1.0E-10;
if(rscnt==0)
rscnt=4;
maxits=1000;
//--- Sort points by X.
//--- Determine number of zero and non-zero values.
CTSort::TagSortFastR(x,y,bufx,bufy,n);
//--- check
if(!CAp::Assert(x[0]>=0.0,"LogisticFitX: some X[] are negative"))
return;
nz=n;
for(i=0; i<n; i++)
if(x[i]>0.0)
{
nz=i;
break;
}
//--- For NZ=N (all X[] are zero) special code is used.
//--- For NZ<N we use general-purpose code.
rep.m_iterationscount=0;
if(nz==n)
{
//--- NZ=N, degenerate problem.
//--- No need to run optimizer.
v=0.0;
for(i=0; i<n; i++)
v+=y[i];
v/=n;
if(MathIsValidNumber(cnstrleft))
a=cnstrleft;
else
a=v;
b=1;
c=1;
if(MathIsValidNumber(cnstrright))
d=cnstrright;
else
d=a;
g=1;
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
return;
}
//--- Non-degenerate problem.
//--- Determine scale of data.
scalex=x[nz+(n-nz)/2];
//--- check
if(!CAp::Assert(scalex>0.0,"LogisticFitX: internal error"))
return;
v=0.0;
for(i=0; i<n; i++)
v+=y[i];
v/=n;
scaley=0.0;
for(i=0; i<n; i++)
scaley+=CMath::Sqr(y[i]-v);
scaley=MathSqrt(scaley/n);
if(scaley==0.0)
scaley=1.0;
s=vector<double>::Zeros(5);
s.Set(0,scaley);
s.Set(1,0.1);
s.Set(2,scalex);
s.Set(3,scaley);
s.Set(4,0.1);
p0=vector<double>::Zeros(5);
bndl=vector<double>::Zeros(5);
bndu=vector<double>::Zeros(5);
bndl1=vector<double>::Zeros(5);
bndu1=vector<double>::Zeros(5);
bndl2=vector<double>::Zeros(5);
bndu2=vector<double>::Zeros(5);
CMinLM::MinLMCreateVJ(5,n+5,p0,state);
CMinLM::MinLMSetScale(state,s);
CMinLM::MinLMSetCond(state,epsx,maxits);
CMinLM::MinLMSetXRep(state,true);
p1=vector<double>::Zeros(5);
p2=vector<double>::Zeros(5);
//--- Is it 4PL problem?
if(is4pl)
{
//--- Run outer iterations
a=0;
b=1;
c=1;
d=1;
g=1;
fbest=CMath::m_maxrealnumber;
for(outerit=0; outerit<rscnt; outerit++)
{
//--- Prepare initial point; use B>0
if(MathIsValidNumber(cnstrleft))
p1.Set(0,cnstrleft);
else
p1.Set(0,y[0]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p1.Set(1,0.5+CHighQualityRand::HQRndUniformR(rs));
p1.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
if(MathIsValidNumber(cnstrright))
p1.Set(3,cnstrright);
else
p1.Set(3,y[n-1]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p1.Set(4,1.0);
//--- Run optimization with tight constraints and increased regularization
if(MathIsValidNumber(cnstrleft))
{
bndl.Set(0,cnstrleft);
bndu.Set(0,cnstrleft);
}
else
{
bndl.Set(0,AL_NEGINF);
bndu.Set(0,AL_POSINF);
}
bndl.Set(1,0.5);
bndu.Set(1,2.0);
bndl.Set(2,0.5*scalex);
bndu.Set(2,2.0*scalex);
if(MathIsValidNumber(cnstrright))
{
bndl.Set(3,cnstrright);
bndu.Set(3,cnstrright);
}
else
{
bndl.Set(3,AL_NEGINF);
bndu.Set(3,AL_POSINF);
}
bndl.Set(4,1.0);
bndu.Set(4,1.0);
CMinLM::MinLMSetBC(state,bndl,bndu);
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Relax constraints, run optimization one more time
bndl.Set(1,0.1);
bndu.Set(1,10.0);
bndl.Set(2,CMath::m_machineepsilon*scalex);
bndu.Set(2,scalex/CMath::m_machineepsilon);
CMinLM::MinLMSetBC(state,bndl,bndu);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Relax constraints more, run optimization one more time
bndl.Set(1,0.01);
bndu.Set(1,100.0);
CMinLM::MinLMSetBC(state,bndl,bndu);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Relax constraints ever more, run optimization one more time
bndl.Set(1,0.001);
bndu.Set(1,1000.0);
CMinLM::MinLMSetBC(state,bndl,bndu);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Compare results with best value found so far.
if((double)(flast)<(double)(fbest))
{
a=p1[0];
b=p1[1];
c=p1[2];
d=p1[3];
g=p1[4];
fbest=flast;
}
}
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
return;
}
//--- Well.... we have 5PL fit, and we have to test two separate branches:
//--- B>0 and B<0, because of asymmetry in the curve. First, we run optimization
//--- with tight constraints two times, in order to determine better sign for B.
//--- Run outer iterations
a=0;
b=1;
c=1;
d=1;
g=1;
fbest=CMath::m_maxrealnumber;
for(outerit=0; outerit<rscnt; outerit++)
{
//--- First, we try positive B.
p1.Set(0,y[0]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p1.Set(1,0.5+CHighQualityRand::HQRndUniformR(rs));
p1.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
p1.Set(3,y[n-1]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p1.Set(4,1.0);
bndl1.Set(0,AL_NEGINF);
bndu1.Set(0,AL_POSINF);
bndl1.Set(1,0.5);
bndu1.Set(1,2.0);
bndl1.Set(2,0.5*scalex);
bndu1.Set(2,2.0*scalex);
bndl1.Set(3,AL_NEGINF);
bndu1.Set(3,AL_POSINF);
bndl1.Set(4,0.5);
bndu1.Set(4,2.0);
if(MathIsValidNumber(cnstrleft))
{
p1.Set(0,cnstrleft);
bndl1.Set(0,cnstrleft);
bndu1.Set(0,cnstrleft);
}
if(MathIsValidNumber(cnstrright))
{
p1.Set(3,cnstrright);
bndl1.Set(3,cnstrright);
bndu1.Set(3,cnstrright);
}
CMinLM::MinLMSetBC(state,bndl1,bndu1);
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p1,fposb);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Second attempt - with negative B (constraints are still tight).
p2.Set(0,y[n-1]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p2.Set(1,-(0.5+CHighQualityRand::HQRndUniformR(rs)));
p2.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
p2.Set(3,y[0]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
p2.Set(4,1.0);
bndl2.Set(0,AL_NEGINF);
bndu2.Set(0,AL_POSINF);
bndl2.Set(1,-2.0);
bndu2.Set(1,-0.5);
bndl2.Set(2,0.5*scalex);
bndu2.Set(2,2.0*scalex);
bndl2.Set(3,AL_NEGINF);
bndu2.Set(3,AL_POSINF);
bndl2.Set(4,0.5);
bndu2.Set(4,2.0);
if(MathIsValidNumber(cnstrleft))
{
p2.Set(3,cnstrleft);
bndl2.Set(3,cnstrleft);
bndu2.Set(3,cnstrleft);
}
if(MathIsValidNumber(cnstrright))
{
p2.Set(0,cnstrright);
bndl2.Set(0,cnstrright);
bndu2.Set(0,cnstrright);
}
CMinLM::MinLMSetBC(state,bndl2,bndu2);
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p2,fnegb);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Select best version of B sign
if((double)(fposb)<(double)(fnegb))
{
//--- Prepare relaxed constraints assuming that B is positive
bndl1.Set(1,0.1);
bndu1.Set(1,10.0);
bndl1.Set(2,CMath::m_machineepsilon*scalex);
bndu1.Set(2,scalex/CMath::m_machineepsilon);
bndl1.Set(4,0.1);
bndu1.Set(4,10.0);
CMinLM::MinLMSetBC(state,bndl1,bndu1);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Prepare stronger relaxation of constraints
bndl1.Set(1,0.01);
bndu1.Set(1,100.0);
CMinLM::MinLMSetBC(state,bndl1,bndu1);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Prepare stronger relaxation of constraints
bndl1.Set(1,0.001);
bndu1.Set(1,1000.0);
CMinLM::MinLMSetBC(state,bndl1,bndu1);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Compare results with best value found so far.
if((double)(flast)<(double)(fbest))
{
a=p1[0];
b=p1[1];
c=p1[2];
d=p1[3];
g=p1[4];
fbest=flast;
}
}
else
{
//--- Prepare relaxed constraints assuming that B is negative
bndl2.Set(1,-10.0);
bndu2.Set(1,-0.1);
bndl2.Set(2,CMath::m_machineepsilon*scalex);
bndu2.Set(2,scalex/CMath::m_machineepsilon);
bndl2.Set(4,0.1);
bndu2.Set(4,10.0);
CMinLM::MinLMSetBC(state,bndl2,bndu2);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Prepare stronger relaxation
bndl2.Set(1,-100.0);
bndu2.Set(1,-0.01);
CMinLM::MinLMSetBC(state,bndl2,bndu2);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Prepare stronger relaxation
bndl2.Set(1,-1000.0);
bndu2.Set(1,-0.001);
CMinLM::MinLMSetBC(state,bndl2,bndu2);
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
rep.m_iterationscount+=replm.m_iterationscount;
//--- Compare results with best value found so far.
if((double)(flast)<(double)(fbest))
{
a=p2[0];
b=p2[1];
c=p2[2];
d=p2[3];
g=p2[4];
fbest=flast;
}
}
}
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
}
//+------------------------------------------------------------------+
//| NOTES: |
//| 1. this algorithm is somewhat unusual because it works with |
//| parameterized function f(C,X), where X is a function argument |
//| (we have many points which are characterized by different |
//| argument values), and C is a parameter to fit. |
//| For example, if we want to do linear fit by f(c0,c1,x) = |
//| = c0*x+c1, then x will be argument, and {c0,c1} will be |
//| parameters. |
//| It is important to understand that this algorithm finds |
//| minimum in the space of function PARAMETERS (not arguments), |
//| so it needs derivatives of f() with respect to C, not X. |
//| In the example above it will need f=c0*x+c1 and |
//| {df/dc0,df/dc1} = {x,1} instead of {df/dx} = {c0}. |
//| 2. Callback functions accept C as the first parameter, and X as |
//| the second |
//| 3. If State was created with LSFitCreateFG(), algorithm needs |
//| just function and its gradient, but if State was created with |
//| LSFitCreateFGH(), algorithm will need function, gradient and |
//| Hessian. |
//| According to the said above, there ase several versions of |
//| this function, which accept different sets of callbacks. |
//| This flexibility opens way to subtle errors - you may create |
//| State with LSFitCreateFGH() (optimization using Hessian), but |
//| call function which does not accept Hessian. So when algorithm|
//| will request Hessian, there will be no callback to call. In |
//| this case exception will be thrown. |
//| Be careful to avoid such errors because there is no way to |
//| find them at compile time - you can see them at runtime only. |
//+------------------------------------------------------------------+
bool CLSFit::LSFitIteration(CLSFitState &State)
{
//--- create variables
double lx=0;
double lf=0;
double ld=0;
double rx=0;
double rf=0;
double rd=0;
int n=0;
int m=0;
int k=0;
double v=0;
double vv=0;
double relcnt=0;
int i=0;
int j=0;
int j1=0;
int info=0;
int i_=0;
int label=-1;
//--- Reverse communication preparations
//--- This code initializes locals by:
//--- * random values determined during code
//--- generation - on first subroutine call
//--- * values from previous call - on subsequent calls
if(State.m_rstate.stage>=0)
{
n=State.m_rstate.ia[0];
m=State.m_rstate.ia[1];
k=State.m_rstate.ia[2];
i=State.m_rstate.ia[3];
j=State.m_rstate.ia[4];
j1=State.m_rstate.ia[5];
info=State.m_rstate.ia[6];
lx=State.m_rstate.ra[0];
lf=State.m_rstate.ra[1];
ld=State.m_rstate.ra[2];
rx=State.m_rstate.ra[3];
rf=State.m_rstate.ra[4];
rd=State.m_rstate.ra[5];
v=State.m_rstate.ra[6];
vv=State.m_rstate.ra[7];
relcnt=State.m_rstate.ra[8];
}
else
{
n=359;
m=-58;
k=-919;
i=-909;
j=81;
j1=255;
info=74;
lx=-788;
lf=809;
ld=205;
rx=-838;
rf=939;
rd=-526;
v=763;
vv=-541;
relcnt=-698;
}
//--- select stage
switch(State.m_rstate.stage)
{
case 0:
label=0;
break;
case 1:
label=1;
break;
case 2:
label=2;
break;
case 3:
label=3;
break;
case 4:
label=4;
break;
case 5:
label=5;
break;
case 6:
label=6;
break;
case 7:
label=7;
break;
case 8:
label=8;
break;
case 9:
label=9;
break;
case 10:
label=10;
break;
case 11:
label=11;
break;
case 12:
label=12;
break;
case 13:
label=13;
break;
default:
//--- Routine body
//--- Init
if(State.m_wkind==1)
{
//--- check
if(!CAp::Assert(State.m_npoints==State.m_nweights,__FUNCTION__+": number of points is not equal to the number of weights"))
return(false);
}
State.m_repvaridx=-1;
n=State.m_npoints;
m=State.m_m;
k=State.m_k;
State.m_tmpct.Resize(State.m_nec+State.m_nic);
State.m_tmpct.Fill(0,0,State.m_nec);
State.m_tmpct.Fill(-1,State.m_nec,State.m_nic);
CMinLM::MinLMSetCond(State.m_optstate,State.m_epsx,State.m_maxits);
CMinLM::MinLMSetStpMax(State.m_optstate,State.m_stpmax);
CMinLM::MinLMSetXRep(State.m_optstate,State.m_xrep);
CMinLM::MinLMSetScale(State.m_optstate,State.m_s);
CMinLM::MinLMSetBC(State.m_optstate,State.m_bndl,State.m_bndu);
CMinLM::MinLMSetLC(State.m_optstate,State.m_cleic,State.m_tmpct,State.m_nec+State.m_nic);
//--- Check that user-supplied gradient is correct
LSFitClearRequestFields(State);
if(!(State.m_teststep>0.0 && State.m_optalgo==1))
{
label=14;
break;
}
State.m_c=State.m_c0;
for(i=0; i<k; i++)
{
if(MathIsValidNumber(State.m_bndl[i]))
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
if(MathIsValidNumber(State.m_bndu[i]))
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
}
State.m_needfg=true;
i=0;
label=16;
break;
}
//--- main loop
while(label>=0)
switch(label)
{
case 16:
if(i>k-1)
{
label=18;
break;
}
//--- check
if(!CAp::Assert(State.m_bndl[i]<=State.m_c[i] && State.m_c[i]<=State.m_bndu[i],__FUNCTION__+": internal error(State.C is out of bounds)"))
return(false);
v=State.m_c[i];
j=0;
case 19:
if(j>n-1)
{
label=21;
break;
}
State.m_x=State.m_taskx[j]+0;
State.m_c.Set(i,v-State.m_teststep*State.m_s[i]);
if(MathIsValidNumber(State.m_bndl[i]))
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
lx=State.m_c[i];
State.m_rstate.stage=0;
label=-1;
break;
case 0:
lf=State.m_f;
ld=State.m_g[i];
State.m_c.Set(i,v+State.m_teststep*State.m_s[i]);
if(MathIsValidNumber(State.m_bndu[i]))
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
rx=State.m_c[i];
State.m_rstate.stage=1;
label=-1;
break;
case 1:
rf=State.m_f;
rd=State.m_g[i];
State.m_c.Set(i,(lx+rx)/2.0);
if(MathIsValidNumber(State.m_bndl[i]))
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
if(MathIsValidNumber(State.m_bndu[i]))
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
State.m_rstate.stage=2;
label=-1;
break;
case 2:
State.m_c.Set(i,v);
if(!COptServ::DerivativeCheck(lf,ld,rf,rd,State.m_f,State.m_g[i],rx-lx))
{
State.m_repvaridx=i;
State.m_repterminationtype=-7;
return(false);
}
j++;
label=19;
break;
case 21:
i++;
label=16;
break;
case 18:
State.m_needfg=false;
case 14:
//--- Fill WCur by weights:
//--- * for WKind=0 unit weights are chosen
//--- * for WKind=1 we use user-supplied weights stored in State.TaskW
if(State.m_wkind==1)
State.m_wcur=State.m_taskw;
else
State.m_wcur=vector<double>::Ones(n);
//--- Optimize
case 22:
if(!CMinLM::MinLMIteration(State.m_optstate))
{
label=23;
break;
}
if(!State.m_optstate.m_needfi)
{
label=24;
break;
}
//--- calculate f[] = wi*(f(xi,c)-yi)
i=0;
case 26:
if(i>n-1)
{
label=22;
break;
}
State.m_c=State.m_optstate.m_x;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needf=true;
State.m_rstate.stage=3;
label=-1;
break;
case 3:
State.m_needf=false;
vv=State.m_wcur[i];
State.m_optstate.m_fi.Set(i,vv*(State.m_f-State.m_tasky[i]));
i++;
label=26;
break;
case 28:
label=22;
break;
case 24:
if(!State.m_optstate.m_needf)
{
label=29;
break;
}
//--- calculate F = sum (wi*(f(xi,c)-yi))^2
State.m_optstate.m_f=0;
i=0;
case 31:
if(i>n-1)
{
label=22;
break;
}
State.m_c=State.m_optstate.m_x;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needf=true;
State.m_rstate.stage=4;
label=-1;
break;
case 4:
State.m_needf=false;
vv=State.m_wcur[i];
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
i++;
label=31;
break;
case 33:
label=22;
break;
case 29:
if(!State.m_optstate.m_needfg)
{
label=34;
break;
}
//--- calculate F/gradF
State.m_optstate.m_f=0;
State.m_optstate.m_g.Fill(0);
i=0;
case 36:
if(i>n-1)
{
label=22;
break;
}
State.m_c=State.m_optstate.m_x;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needfg=true;
State.m_rstate.stage=5;
label=-1;
break;
case 5:
State.m_needfg=false;
vv=State.m_wcur[i];
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
v=CMath::Sqr(vv)*2*(State.m_f-State.m_tasky[i]);
State.m_optstate.m_g+=State.m_g.ToVector()*v;
i++;
label=36;
break;
case 38:
label=22;
break;
case 34:
if(!State.m_optstate.m_needfij)
{
label=39;
break;
}
//--- calculate Fi/jac(Fi)
i=0;
case 41:
if(i>n-1)
{
label=22;
break;
}
State.m_c=State.m_optstate.m_x;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needfg=true;
State.m_rstate.stage=6;
label=-1;
break;
case 6:
State.m_needfg=false;
vv=State.m_wcur[i];
State.m_optstate.m_fi.Set(i,vv*(State.m_f-State.m_tasky[i]));
State.m_optstate.m_j.Row(i,State.m_g.ToVector()*vv);
i++;
label=41;
break;
case 43:
label=22;
break;
case 39:
if(!State.m_optstate.m_needfgh)
{
label=44;
break;
}
//--- calculate F/grad(F)/hess(F)
State.m_optstate.m_f=0;
State.m_optstate.m_g.Fill(0);
State.m_optstate.m_h.Fill(0,k,k);
i=0;
case 46:
if(i>n-1)
{
label=22;
break;
}
State.m_c=State.m_optstate.m_x;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needfgh=true;
State.m_rstate.stage=7;
label=-1;
break;
case 7:
State.m_needfgh=false;
vv=State.m_wcur[i];
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
v=CMath::Sqr(vv)*2*(State.m_f-State.m_tasky[i]);
State.m_optstate.m_g+=State.m_g.ToVector()*v;
for(j=0; j<k; j++)
{
v=2*CMath::Sqr(vv)*State.m_g[j];
State.m_optstate.m_h.Row(j,State.m_optstate.m_h[j]+State.m_g*v);
v=2*CMath::Sqr(vv)*(State.m_f-State.m_tasky[i]);
State.m_optstate.m_h.Row(j,State.m_optstate.m_h[j]+State.m_h[j]*v);
}
i++;
label=46;
break;
case 48:
label=22;
break;
case 44:
if(!State.m_optstate.m_xupdated)
{
label=49;
break;
}
//--- Report new iteration
State.m_c=State.m_optstate.m_x;
State.m_f=State.m_optstate.m_f;
LSFitClearRequestFields(State);
State.m_xupdated=true;
State.m_rstate.stage=8;
label=-1;
break;
case 8:
State.m_xupdated=false;
label=22;
break;
case 49:
label=22;
break;
case 23:
//--- Extract results
//--- NOTE: reverse communication protocol used by this unit does NOT
//--- allow us to reallocate State.C[] array. Thus, we extract
//--- results to the temporary variable in order to avoid possible
//--- reallocation.
CMinLM::MinLMResults(State.m_optstate,State.m_c1,State.m_optrep);
State.m_repterminationtype=State.m_optrep.m_terminationtype;
State.m_repiterationscount=State.m_optrep.m_iterationscount;
//--- calculate errors
if(State.m_repterminationtype<=0)
{
label=51;
break;
}
//--- Calculate RMS/Avg/Max/... errors
State.m_reprmserror=0;
State.m_repwrmserror=0;
State.m_repavgerror=0;
State.m_repavgrelerror=0;
State.m_repmaxerror=0;
relcnt=0;
i=0;
case 53:
if(i>n-1)
{
label=55;
break;
}
State.m_c=State.m_c1;
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
LSFitClearRequestFields(State);
State.m_needf=true;
State.m_rstate.stage=9;
label=-1;
break;
case 9:
State.m_needf=false;
v=State.m_f;
vv=State.m_wcur[i];
State.m_reprmserror+=CMath::Sqr(v-State.m_tasky[i]);
State.m_repwrmserror+=CMath::Sqr(vv*(v-State.m_tasky[i]));
State.m_repavgerror+=MathAbs(v-State.m_tasky[i]);
if(State.m_tasky[i]!=0.0)
{
State.m_repavgrelerror+=MathAbs(v-State.m_tasky[i])/MathAbs(State.m_tasky[i]);
relcnt++;
}
State.m_repmaxerror=MathMax(State.m_repmaxerror,MathAbs(v-State.m_tasky[i]));
i++;
label=53;
break;
case 55:
State.m_reprmserror=MathSqrt(State.m_reprmserror/n);
State.m_repwrmserror=MathSqrt(State.m_repwrmserror/n);
State.m_repavgerror=State.m_repavgerror/n;
if(relcnt!=0.0)
State.m_repavgrelerror=State.m_repavgrelerror/relcnt;
//--- Calculate covariance matrix
CApServ::RMatrixSetLengthAtLeast(State.m_tmpjac,n,k);
CApServ::RVectorSetLengthAtLeast(State.m_tmpf,n);
CApServ::RVectorSetLengthAtLeast(State.m_tmp,k);
if(State.m_diffstep<=0.0)
{
label=56;
break;
}
//--- Compute Jacobian by means of numerical differentiation
LSFitClearRequestFields(State);
State.m_needf=true;
i=0;
case 58:
if(i>n-1)
{
label=60;
break;
}
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
State.m_rstate.stage=10;
label=-1;
break;
case 10:
State.m_tmpf.Set(i,State.m_f);
j=0;
case 61:
if(j>k-1)
{
label=63;
break;
}
v=State.m_c[j];
lx=v-State.m_diffstep*State.m_s[j];
State.m_c.Set(j,lx);
if(MathIsValidNumber(State.m_bndl[j]))
State.m_c.Set(j,MathMax(State.m_c[j],State.m_bndl[j]));
State.m_rstate.stage=11;
label=-1;
break;
case 11:
lf=State.m_f;
rx=v+State.m_diffstep*State.m_s[j];
State.m_c.Set(j,rx);
if(MathIsValidNumber(State.m_bndu[j]))
State.m_c.Set(j,MathMin(State.m_c[j],State.m_bndu[j]));
State.m_rstate.stage=12;
label=-1;
break;
case 12:
rf=State.m_f;
State.m_c.Set(j,v);
if(rx!=lx)
State.m_tmpjac.Set(i,j,(rf-lf)/(rx-lx));
else
State.m_tmpjac.Set(i,j,0);
j++;
label=61;
break;
case 63:
i++;
label=58;
break;
case 60:
State.m_needf=false;
label=57;
break;
case 56:
//--- Jacobian is calculated with user-provided analytic gradient
LSFitClearRequestFields(State);
State.m_needfg=true;
i=0;
case 64:
if(i>n-1)
{
label=66;
break;
}
State.m_x=State.m_taskx[i]+0;
State.m_pointindex=i;
State.m_rstate.stage=13;
label=-1;
break;
case 13:
State.m_tmpf.Set(i,State.m_f);
State.m_tmpjac.Row(i,State.m_g);
i++;
label=64;
break;
case 66:
State.m_needfg=false;
case 57:
State.m_tmp.Fill(0.0);
EstimateErrors(State.m_tmpjac,State.m_tmpf,State.m_tasky,State.m_wcur,State.m_tmp,State.m_s,n,k,State.m_rep,State.m_tmpjacw,0);
case 51:
return(false);
}
//--- Saving State
State.m_rstate.ia.Set(0,n);
State.m_rstate.ia.Set(1,m);
State.m_rstate.ia.Set(2,k);
State.m_rstate.ia.Set(3,i);
State.m_rstate.ia.Set(4,j);
State.m_rstate.ia.Set(5,j1);
State.m_rstate.ia.Set(6,info);
State.m_rstate.ra.Set(0,lx);
State.m_rstate.ra.Set(1,lf);
State.m_rstate.ra.Set(2,ld);
State.m_rstate.ra.Set(3,rx);
State.m_rstate.ra.Set(4,rf);
State.m_rstate.ra.Set(5,rd);
State.m_rstate.ra.Set(6,v);
State.m_rstate.ra.Set(7,vv);
State.m_rstate.ra.Set(8,relcnt);
//--- return result
return(true);
}
//+------------------------------------------------------------------+
//| This internal function estimates covariance matrix and other |
//| error-related information for linear/nonlinear least squares |
//| model. |
//| It has a bit awkward interface, but it can be used for both |
//| linear and nonlinear problems. |
//| INPUT PARAMETERS: |
//| F1 - array[0..N-1,0..K-1]: |
//| * for linear problems - matrix of function values |
//| * for nonlinear problems - Jacobian matrix |
//| F0 - array[0..N-1]: |
//| * for linear problems - must be filled with zeros |
//| * for nonlinear problems - must store values of |
//| function being fitted |
//| Y - array[0..N-1]: |
//| * for linear and nonlinear problems - must store |
//| target values |
//| W - weights, array[0..N-1]: |
//| * for linear and nonlinear problems - weights |
//| X - array[0..K-1]: |
//| * for linear and nonlinear problems - current |
//| solution |
//| S - array[0..K-1]: |
//| * its components should be strictly positive |
//| * squared inverse of this diagonal matrix is used |
//| as damping factor for covariance matrix (linear |
//| and nonlinear problems) |
//| * for nonlinear problems, when scale of the |
//| variables is usually explicitly given by user, |
//| you may use scale vector for this parameter |
//| * for linear problems you may set this parameter to|
//| S=sqrt(1/diag(F'*F)) |
//| * this parameter is automatically rescaled by this |
//| function, only relative magnitudes of its |
//| components (with respect to each other) matter. |
//| N - number of points, N>0. |
//| K - number of dimensions |
//| Rep - structure which is used to store results |
//| Z - additional matrix which, depending on ZKind, may |
//| contain some information used to accelerate |
//| calculations - or just can be temporary buffer: |
//| * for ZKind=0 Z contains no information, just |
//| temporary buffer which can be |
//| resized and used as needed |
//| * for ZKind=1 Z contains triangular matrix from|
//| QR decomposition of W*F1. This |
//| matrix can be used to speedup |
//| calculation of covariance matrix.|
//| It should not be changed by |
//| algorithm. |
//| ZKind - contents of Z |
//| OUTPUT PARAMETERS: |
//| * Rep.CovPar covariance matrix for parameters, array[K,K].|
//| * Rep.ErrPar errors in parameters, array[K], |
//| errpar = sqrt(diag(CovPar)) |
//| * Rep.ErrCurve vector of fit errors - standard deviations of|
//| empirical best-fit curve from "ideal" |
//| best-fit curve built with infinite number |
//| of samples, array[N]. |
//| errcurve = sqrt(diag(J*CovPar*J')), |
//| where J is Jacobian matrix. |
//| * Rep.Noise vector of per-point estimates of noise, |
//| array[N] |
//| * Rep.R2 coefficient of determination (non-weighted) |
//| Other fields of Rep are not changed. |
//| IMPORTANT: errors in parameters are calculated without taking |
//| into account boundary/linear constraints! Presence of |
//| constraints changes distribution of errors, but there |
//| is no easy way to account for constraints when you |
//| calculate covariance matrix. |
//| NOTE: noise in the data is estimated as follows: |
//| * for fitting without user-supplied weights all points |
//| are assumed to have same level of noise, which is |
//| estimated from the data |
//| * for fitting with user-supplied weights we assume that |
//| noise level in I-th point is inversely proportional to |
//| Ith weight. Coefficient of proportionality is estimated|
//| from the data. |
//| NOTE: we apply small amount of regularization when we invert |
//| squared Jacobian and calculate covariance matrix. It |
//| guarantees that algorithm won't divide by zero during |
//| inversion, but skews error estimates a bit (fractional |
//| error is about 10^-9). |
//| However, we believe that this difference is insignificant for all|
//| practical purposes except for the situation when you want to |
//| compare ALGLIB results with "reference" implementation up to the|
//| last significant digit. |
//+------------------------------------------------------------------+
void CLSFit::EstimateErrors(CMatrixDouble &f1,CRowDouble &f0,CRowDouble &y,
CRowDouble &w,CRowDouble &x,CRowDouble &S,
int n,int k,CLSFitReport &rep,
CMatrixDouble &z,int zkind)
{
//--- create variables
int i=0;
int j=0;
int j1=0;
double v=0;
double noisec=0;
int info=0;
CMatInvReport invrep;
int nzcnt=0;
double avg=0;
double rss=0;
double tss=0;
double sz=0;
double ss=0;
int i_=0;
CRowDouble s=S;
//--- Compute NZCnt - count of non-zero weights
//--- Compute R2
nzcnt=0;
avg=0.0;
for(i=0; i<n; i++)
if(w[i]!=0.0)
{
avg+=y[i];
nzcnt++;
}
//--- Compute R2
if(nzcnt>0)
{
avg/=nzcnt;
rss=0.0;
tss=0.0;
for(i=0; i<n; i++)
{
if(w[i]!=0.0)
{
v=CAblasF::RDotVR(k,x,f1,i);
v+=f0[i];
rss+=CMath::Sqr(v-y[i]);
tss+=CMath::Sqr(y[i]-avg);
}
}
if(tss!=0.0)
rep.m_r2=MathMax(1.0-rss/tss,0.0);
else
rep.m_r2=1.0;
}
else
rep.m_r2=0;
//--- Compute estimate of proportionality between noise in the data and weights:
//--- NoiseC = mean(per-point-noise*per-point-weight)
//--- Noise level (standard deviation) at each point is equal to NoiseC/W[I].
//
if(nzcnt>k)
{
noisec=0.0;
for(i=0; i<n; i++)
{
if(w[i]!=0.0)
{
v=CAblasF::RDotVR(k,x,f1,i)+f0[i];
noisec+=CMath::Sqr((v-y[i])*w[i]);
}
}
noisec=MathSqrt(noisec/(nzcnt-k));
}
else
noisec=0.0;
//--- Two branches on noise level:
//--- * NoiseC>0 normal situation
//--- * NoiseC=0 degenerate case CovPar is filled by zeros
CApServ::RMatrixSetLengthAtLeast(rep.m_covpar,k,k);
if(noisec>0.0)
{
//--- Normal situation: non-zero noise level
if(!CAp::Assert(zkind==0 || zkind==1,__FUNCTION__+": internal error in EstimateErrors() function"))
return;
if(zkind==0)
{
//--- Z contains no additional information which can be used to speed up
//--- calculations. We have to calculate covariance matrix on our own:
//--- * Compute scaled Jacobian N*J, where N[i,i]=WCur[I]/NoiseC, store in Z
//--- * Compute Z'*Z, store in CovPar
//--- * Apply moderate regularization to CovPar and compute matrix inverse.
//--- In case inverse failed, increase regularization parameter and try
//--- again.
CApServ::RMatrixSetLengthAtLeast(z,n,k);
for(i=0; i<n; i++)
{
v=w[i]/noisec;
z.Row(i,f1[i]*v);
}
//--- Convert S to automatically scaled damped matrix:
//--- * calculate SZ - sum of diagonal elements of Z'*Z
//--- * calculate SS - sum of diagonal elements of S^(-2)
//--- * overwrite S by (SZ/SS)*S^(-2)
//--- * now S has approximately same magnitude as giagonal of Z'*Z
sz=0;
for(i=0; i<n; i++)
sz+=CAblasF::RDotRR(k,z,i,z,i);
if(sz==0.0)
sz=1;
ss=0;
for(j=0; j<k; j++)
ss+=1/CMath::Sqr(s[j]);
for(j=0; j<k; j++)
s.Set(j,sz/ss/CMath::Sqr(s[j]));
//--- Calculate damped inverse inv(Z'*Z+S).
//--- We increase damping factor V until Z'*Z become well-conditioned.
v=1.0E3*CMath::m_machineepsilon;
do
{
CAblas::RMatrixSyrk(k,n,1.0,z,0,0,2,0.0,rep.m_covpar,0,0,true);
for(i=0; i<k; i++)
rep.m_covpar.Add(i,i,v*s[i]);
CMatInv::SPDMatrixInverse(rep.m_covpar,k,true,info,invrep);
v=10*v;
}
while(info<=0);
rep.m_covpar+=(rep.m_covpar.TriU(1)+0).Transpose();
}
if(zkind==1)
{
//--- We can reuse additional information:
//--- * Z contains R matrix from QR decomposition of W*F1
//--- * After multiplication by 1/NoiseC we get Z_mod = N*F1, where diag(N)=w[i]/NoiseC
//--- * Such triangular Z_mod is a Cholesky factor from decomposition of J'*N'*N*J.
//--- Thus, we can calculate covariance matrix as inverse of the matrix given by
//--- its Cholesky decomposition. It allow us to avoid time-consuming calculation
//--- of J'*N'*N*J in CovPar - complexity is reduced from O(N*K^2) to O(K^3), which
//--- is quite good because K is usually orders of magnitude smaller than N.
//--- First, convert S to automatically scaled damped matrix:
//--- * calculate SZ - sum of magnitudes of diagonal elements of Z/NoiseC
//--- * calculate SS - sum of diagonal elements of S^(-1)
//--- * overwrite S by (SZ/SS)*S^(-1)
//--- * now S has approximately same magnitude as giagonal of Z'*Z
sz=MathAbs(z.Diag()/noisec).Sum();
if(sz==0.0)
sz=1;
ss=0;
for(j=0; j<k; j++)
ss+=1/s[j];
for(j=0; j<k; j++)
s.Set(j,sz/ss/s[j]);
//--- Calculate damped inverse of inv((Z+v*S)'*(Z+v*S))
//--- We increase damping factor V until matrix become well-conditioned.
v=1.0E3*CMath::m_machineepsilon;
do
{
for(i=0; i<k; i++)
{
rep.m_covpar.Row(i,z[i]/noisec);
rep.m_covpar.Add(i,i,v*s[i]);
}
CMatInv::SPDMatrixCholeskyInverse(rep.m_covpar,k,true,info,invrep);
v=10*v;
}
while(info<=0);
rep.m_covpar+=(rep.m_covpar.TriU(1)+0).Transpose();
}
}
else
{
//--- Degenerate situation: zero noise level, covariance matrix is zero.
rep.m_covpar.Fill(0);
}
//--- Estimate erorrs in parameters, curve and per-point noise
CApServ::RVectorSetLengthAtLeast(rep.m_errpar,k);
CApServ::RVectorSetLengthAtLeast(rep.m_errcurve,n);
CApServ::RVectorSetLengthAtLeast(rep.m_noise,n);
rep.m_errpar=MathSqrt(rep.m_covpar.Diag()+0);
for(i=0; i<n; i++)
{
//--- ErrCurve[I] is sqrt(P[i,i]) where P=J*CovPar*J'
v=0.0;
for(j=0; j<k; j++)
for(j1=0; j1<k; j1++)
v+=f1.Get(i,j)*rep.m_covpar.Get(j,j1)*f1.Get(i,j1);
rep.m_errcurve.Set(i,MathSqrt(v));
//--- Noise[i] is filled using weights and current estimate of noise level
if(w[i]!=0.0)
rep.m_noise.Set(i,noisec/w[i]);
else
rep.m_noise.Set(i,0);
}
}
//+------------------------------------------------------------------+
//| Internal 4PL/5PL fitting function. |
//| Accepts X, Y and already initialized and prepared MinLMState |
//| structure. On input P1 contains initial guess, on output it |
//| contains solution. FLast stores function value at P1. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFitInternal(CRowDouble &x,CRowDouble &y,int n,
bool is4pl,double lambdav,
CMinLMState &state,
CMinLMReport &replm,
CRowDouble &p1,double &flast)
{
//--- create variables
int i=0;
int j=0;
double ta=0;
double tb=0;
double tc=0;
double td=0;
double tg=0;
double vp0=0;
double vp1=0;
//--- initialization
flast=0;
//--- function call
CMinLM::MinLMRestartFrom(state,p1);
while(CMinLM::MinLMIteration(state))
{
ta=state.m_x[0];
tb=state.m_x[1];
tc=state.m_x[2];
td=state.m_x[3];
tg=state.m_x[4];
if(state.m_xupdated)
{
//--- Save best function value obtained so far.
flast=state.m_f;
continue;
}
if(state.m_needfi || state.m_needfij)
{
//--- Function vector and Jacobian
for(i=0; i<n; i++)
{
if(!CAp::Assert(x[i]>=0.0,"LogisticFitInternal: integrity error"))
return;
//--- Handle zero X
if(x[i]==0.0)
{
if(tb>=0.0)
{
//--- Positive or zero TB, limit X^TB subject to X->+0 is equal to zero.
state.m_fi.Set(i,ta-y[i]);
if(state.m_needfij)
{
state.m_j.Row(i,vector<double>::Zeros(5));
state.m_j.Set(i,0,1);
}
}
else
{
//--- Negative TB, limit X^TB subject to X->+0 is equal to +INF.
state.m_fi.Set(i,td-y[i]);
if(state.m_needfij)
state.m_j.Row(i,vector<double>::Zeros(5));
}
continue;
}
//--- Positive X.
//--- Prepare VP0/VP1, it may become infinite or nearly overflow in some rare cases,
//--- handle these cases
vp0=MathPow(x[i]/tc,tb);
if(is4pl)
vp1=1+vp0;
else
vp1=MathPow(1+vp0,tg);
if((!MathIsValidNumber(vp1) || vp0>1.0E50) || vp1>1.0E50)
{
//--- VP0/VP1 are not finite, assume that it is +INF or -INF
state.m_fi.Set(i,td-y[i]);
if(state.m_needfij)
{
state.m_j.Row(i,vector<double>::Zeros(5));
state.m_j.Set(i,3,1.0);
}
continue;
}
//--- VP0/VP1 are finite, normal processing
if(is4pl)
{
state.m_fi.Set(i,td+(ta-td)/vp1-y[i]);
if(state.m_needfij)
{
state.m_j.Set(i,0,1/vp1);
state.m_j.Set(i,1,-((ta-td)*vp0*MathLog(x[i]/tc)/CMath::Sqr(vp1)));
state.m_j.Set(i,2,(ta-td)*(tb/tc)*vp0/CMath::Sqr(vp1));
state.m_j.Set(i,3,1-1/vp1);
state.m_j.Set(i,4,0);
}
}
else
{
state.m_fi.Set(i,td+(ta-td)/vp1-y[i]);
if(state.m_needfij)
{
state.m_j.Set(i,0,1/vp1);
state.m_j.Set(i,1,(ta-td)*-tg*MathPow(1+vp0,-tg-1)*vp0*MathLog(x[i]/tc));
state.m_j.Set(i,2,(ta-td)*-tg*MathPow(1+vp0,-tg-1)*vp0*-(tb/tc));
state.m_j.Set(i,3,1-1/vp1);
state.m_j.Set(i,4,-((ta-td)/vp1*MathLog(1+vp0)));
}
}
}
//--- Add regularizer
for(i=0; i<=4; i++)
{
state.m_fi.Set(n+i,lambdav*state.m_x[i]);
if(state.m_needfij)
{
state.m_j.Row(n+i,vector<double>::Zeros(5));
state.m_j.Set(n+i,i,lambdav);
}
}
//--- Done
continue;
}
CAp::Assert(false,"LogisticFitX: internal error");
return;
}
CMinLM::MinLMResultsBuf(state,p1,replm);
CAp::Assert(replm.m_terminationtype>0,"LogisticFitX: internal error");
}
//+------------------------------------------------------------------+
//| Calculate errors for 4PL/5PL fit. |
//| Leaves other fields of Rep unchanged, so caller should properly |
//| initialize it with ClearRep() call. |
//+------------------------------------------------------------------+
void CLSFit::LogisticFit45Errors(CRowDouble &x,CRowDouble &y,int n,
double a,double b,double c,
double d,double g,CLSFitReport &rep)
{
//--- create variables
int k=0;
double v=0;
double rss=0;
double tss=0;
double meany=0;
//--- Calculate errors
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_avgrelerror=0;
rep.m_maxerror=0;
k=0;
rss=0.0;
tss=0.0;
meany=0.0;
for(int i=0; i<n; i++)
meany+=y[i];
meany/=n;
for(int i=0; i<n; i++)
{
//--- Calculate residual from regression
if(x[i]>0)
v=d+(a-d)/MathPow(1.0+MathPow(x[i]/c,b),g)-y[i];
else
if(b>=0.0)
v=a-y[i];
else
v=d-y[i];
//--- Update RSS (residual sum of squares) and TSS (total sum of squares)
//--- which are used to calculate coefficient of determination.
//--- NOTE: we use formula R2 = 1-RSS/TSS because it has nice property of
//--- being equal to 0.0 if and only if model perfectly fits data.
//--- When we fit nonlinear models, there are exist multiple ways of
//--- determining R2, each of them giving different results. Formula
//--- above is the most intuitive one.
rss+=v*v;
tss+=CMath::Sqr(y[i]-meany);
//--- Update errors
rep.m_rmserror+=CMath::Sqr(v);
rep.m_avgerror+=MathAbs(v);
if(y[i]!=0.0)
{
rep.m_avgrelerror+=MathAbs(v/y[i]);
k++;
}
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
rep.m_avgerror/=n;
if(k>0)
rep.m_avgrelerror/=k;
rep.m_r2=1.0-rss/tss;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CLSFit::ClearReport(CLSFitReport &rep)
{
rep.m_taskrcond=0;
rep.m_iterationscount=0;
rep.m_varidx=-1;
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_avgrelerror=0;
rep.m_maxerror=0;
rep.m_wrmserror=0;
rep.m_r2=0;
rep.m_covpar.Resize(0,0);
rep.m_errpar.Resize(0);
rep.m_errcurve.Resize(0);
rep.m_noise.Resize(0);
}
//+------------------------------------------------------------------+
//| Parametric spline inteprolant: 2-dimensional curve. |
//| You should not try to access its members directly - use |
//| PSpline2XXXXXXXX() functions instead. |
//+------------------------------------------------------------------+
class CPSpline2Interpolant
{
public:
//--- variables
int m_n;
bool m_periodic;
CSpline1DInterpolant m_x;
CSpline1DInterpolant m_y;
//--- array
double m_p[];
//--- constructor, destructor
CPSpline2Interpolant(void) { m_n=0; m_periodic=false; }
~CPSpline2Interpolant(void) {}
//--- copy
void Copy(CPSpline2Interpolant&obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CPSpline2Interpolant::Copy(CPSpline2Interpolant &obj)
{
//--- copy variables
m_n=obj.m_n;
m_periodic=obj.m_periodic;
m_x.Copy(obj.m_x);
m_y.Copy(obj.m_y);
//--- copy array
ArrayCopy(m_p,obj.m_p);
}
//+------------------------------------------------------------------+
//| Parametric spline inteprolant: 2-dimensional curve. |
//| You should not try to access its members directly - use |
//| PSpline2XXXXXXXX() functions instead. |
//+------------------------------------------------------------------+
class CPSpline2InterpolantShell
{
private:
CPSpline2Interpolant m_innerobj;
public:
//--- constructors, destructor
CPSpline2InterpolantShell(void) {}
CPSpline2InterpolantShell(CPSpline2Interpolant&obj) { m_innerobj.Copy(obj); }
~CPSpline2InterpolantShell(void) {}
//--- method
CPSpline2Interpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Parametric spline inteprolant: 3-dimensional curve. |
//| You should not try to access its members directly - use |
//| PSpline3XXXXXXXX() functions instead. |
//+------------------------------------------------------------------+
class CPSpline3Interpolant
{
public:
//--- variables
int m_n;
bool m_periodic;
CSpline1DInterpolant m_x;
CSpline1DInterpolant m_y;
CSpline1DInterpolant m_z;
//--- array
double m_p[];
//--- constructor, destructor
CPSpline3Interpolant(void) { m_n=0; m_periodic=false; }
~CPSpline3Interpolant(void) {}
//--- copy
void Copy(CPSpline3Interpolant&obj);
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CPSpline3Interpolant::Copy(CPSpline3Interpolant &obj)
{
//--- copy variables
m_n=obj.m_n;
m_periodic=obj.m_periodic;
m_x.Copy(obj.m_x);
m_y.Copy(obj.m_y);
m_z.Copy(obj.m_z);
//--- copy array
ArrayCopy(m_p,obj.m_p);
}
//+------------------------------------------------------------------+
//| Parametric spline inteprolant: 3-dimensional curve. |
//| You should not try to access its members directly - use |
//| PSpline3XXXXXXXX() functions instead. |
//+------------------------------------------------------------------+
class CPSpline3InterpolantShell
{
private:
CPSpline3Interpolant m_innerobj;
public:
//--- constructors, destructor
CPSpline3InterpolantShell(void) {}
CPSpline3InterpolantShell(CPSpline3Interpolant&obj) { m_innerobj.Copy(obj); }
~CPSpline3InterpolantShell(void) {}
//--- method
CPSpline3Interpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Parametric spline |
//+------------------------------------------------------------------+
class CPSpline
{
public:
static void PSpline2Build(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline2Interpolant&p);
static void PSpline3Build(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline3Interpolant&p);
static void PSpline2BuildPeriodic(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline2Interpolant&p);
static void PSpline3BuildPeriodic(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline3Interpolant&p);
static void PSpline2ParameterValues(CPSpline2Interpolant&p,int &n,double &t[]);
static void PSpline3ParameterValues(CPSpline3Interpolant&p,int &n,double &t[]);
static void PSpline2Calc(CPSpline2Interpolant&p,double t,double&x,double&y);
static void PSpline3Calc(CPSpline3Interpolant&p,double t,double&x,double&y,double&z);
static void PSpline2Tangent(CPSpline2Interpolant&p,double t,double&x,double&y);
static void PSpline3Tangent(CPSpline3Interpolant&p,double t,double&x,double&y,double&z);
static void PSpline2Diff(CPSpline2Interpolant&p,double t,double&x,double&dx,double&y,double&dy);
static void PSpline3Diff(CPSpline3Interpolant&p,double t,double&x,double&dx,double&y,double&dy,double&z,double&dz);
static void PSpline2Diff2(CPSpline2Interpolant&p,double t,double&x,double&dx,double&d2x,double&y,double&dy,double&d2y);
static void PSpline3Diff2(CPSpline3Interpolant&p,double t,double&x,double&dx,double&d2x,double&y,double&dy,double&d2y,double&z,double&dz,double&d2z);
static double PSpline2ArcLength(CPSpline2Interpolant&p,const double a,const double b);
static double PSpline3ArcLength(CPSpline3Interpolant&p,const double a,const double b);
static void ParametricRDPFixed(CMatrixDouble&x,int n,int d,int stopm,double stopeps,CMatrixDouble&x2,int &idx2[],int &nsections);
private:
static void PSpline2Par(CMatrixDouble&xy,const int n,const int pt,double &p[]);
static void PSpline3Par(CMatrixDouble&xy,const int n,const int pt,double &p[]);
static void RDPAnalyzeSectionPar(CMatrixDouble&xy,int i0,int i1,int d,int &worstidx,double&worsterror);
};
//+------------------------------------------------------------------+
//| This function builds non-periodic 2-dimensional parametric |
//| spline which starts at (X[0],Y[0]) and ends at (X[N-1],Y[N-1]). |
//| INPUT PARAMETERS: |
//| XY - points, array[0..N-1,0..1]. |
//| XY[I,0:1] corresponds to the Ith point. |
//| Order of points is important! |
//| N - points count, N>=5 for Akima splines, N>=2 for other |
//| types of splines. |
//| ST - spline type: |
//| * 0 Akima spline |
//| * 1 parabolically terminated Catmull-Rom spline |
//| (Tension=0) |
//| * 2 parabolically terminated cubic spline |
//| PT - parameterization type: |
//| * 0 uniform |
//| * 1 chord length |
//| * 2 centripetal |
//| OUTPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| NOTES: |
//| * this function assumes that there all consequent points are |
//| distinct. I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2), |
//| (x2,y2)<>(x3,y3) and so on. However, non-consequent points may |
//| coincide, i.e. we can have (x0,y0) = (x2,y2). |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Build(CMatrixDouble &cxy,const int n,
const int st,const int pt,
CPSpline2Interpolant &p)
{
int i_=0;
//--- create array
double tmp[];
//--- copy matrix
CMatrixDouble xy;
xy=cxy;
//--- check
if(!CAp::Assert(st>=0 && st<=2,__FUNCTION__+": incorrect spline type!"))
return;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
return;
//--- check
if(st==0)
{
//--- check
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5 (minimum value for Akima splines)!"))
return;
}
else
{
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
return;
}
//--- Prepare
p.m_n=n;
p.m_periodic=false;
//--- allocation
ArrayResize(tmp,n);
//--- Build parameterization,check that all parameters are distinct
PSpline2Par(xy,n,pt,p.m_p);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n),__FUNCTION__+": consequent points are too close!"))
return;
//--- Build splines
if(st==0)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_y);
}
//--- check
if(st==1)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_y);
}
//--- check
if(st==2)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_y);
}
}
//+------------------------------------------------------------------+
//| This function builds non-periodic 3-dimensional parametric spline|
//| which starts at (X[0],Y[0],Z[0]) and ends at |
//| (X[N-1],Y[N-1],Z[N-1]). |
//| Same as PSpline2Build() function, but for 3D, so we won't |
//| duplicate its description here. |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Build(CMatrixDouble &cxy,const int n,
const int st,const int pt,
CPSpline3Interpolant &p)
{
int i_=0;
//--- create array
double tmp[];
//--- copy matrix
CMatrixDouble xy;
xy=cxy;
//--- check
if(!CAp::Assert(st>=0 && st<=2,__FUNCTION__+": incorrect spline type!"))
return;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
return;
//--- check
if(st==0)
{
//--- check
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5 (minimum value for Akima splines)!"))
return;
}
else
{
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+"PSpline3Build: N<2!"))
return;
}
//--- Prepare
p.m_n=n;
p.m_periodic=false;
//--- allocation
ArrayResize(tmp,n);
//--- Build parameterization,check that all parameters are distinct
PSpline3Par(xy,n,pt,p.m_p);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n),__FUNCTION__+": consequent points are too close!"))
return;
//--- Build splines
if(st==0)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_y);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][2];
//--- function call
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_z);
}
//--- check
if(st==1)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_y);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][2];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_z);
}
//--- check
if(st==2)
{
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][0];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_x);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][1];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_y);
//--- copy
for(i_=0; i_<n; i_++)
tmp[i_]=xy[i_][2];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_z);
}
}
//+------------------------------------------------------------------+
//| This function builds periodic 2-dimensional parametric spline |
//| which starts at (X[0],Y[0]), goes through all points to |
//| (X[N-1],Y[N-1]) and then back to (X[0],Y[0]). |
//| INPUT PARAMETERS: |
//| XY - points, array[0..N-1,0..1]. |
//| XY[I,0:1] corresponds to the Ith point. |
//| XY[N-1,0:1] must be different from XY[0,0:1]. |
//| Order of points is important! |
//| N - points count, N>=3 for other types of splines. |
//| ST - spline type: |
//| * 1 Catmull-Rom spline (Tension=0) with cyclic |
//| boundary conditions |
//| * 2 cubic spline with cyclic boundary conditions |
//| PT - parameterization type: |
//| * 0 uniform |
//| * 1 chord length |
//| * 2 centripetal |
//| OUTPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| NOTES: |
//| * this function assumes that there all consequent points are |
//| distinct. I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2), |
//| (x2,y2)<>(x3,y3) and so on. However, non-consequent points may |
//| coincide, i.e. we can have (x0,y0) = (x2,y2). |
//| * last point of sequence is NOT equal to the first point. You |
//| shouldn't make curve "explicitly periodic" by making them |
//| equal. |
//+------------------------------------------------------------------+
void CPSpline::PSpline2BuildPeriodic(CMatrixDouble &cxy,const int n,
const int st,const int pt,
CPSpline2Interpolant &p)
{
int i_=0;
//--- create array
double tmp[];
//--- create matrix
CMatrixDouble xyp;
CMatrixDouble xy;
//--- copy matrix
xy=cxy;
//--- check
if(!CAp::Assert(st>=1 && st<=2,__FUNCTION__+": incorrect spline type!"))
return;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
return;
//--- check
if(!CAp::Assert(n>=3,__FUNCTION__+": N<3!"))
return;
//--- Prepare
p.m_n=n;
p.m_periodic=true;
//--- allocation
ArrayResize(tmp,n+1);
xyp.Resize(n+1,2);
//--- change values
for(i_=0; i_<n; i_++)
xyp.Set(i_,0,xy[i_][0]);
for(i_=0; i_<n; i_++)
xyp.Set(i_,1,xy[i_][1]);
for(i_=0; i_<=1; i_++)
xyp.Set(n,i_,xy[0][i_]);
//--- Build parameterization,check that all parameters are distinct
PSpline2Par(xyp,n+1,pt,p.m_p);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n+1),__FUNCTION__+": consequent (or first and last) points are too close!"))
return;
//--- Build splines
if(st==1)
{
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][0];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_x);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][1];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_y);
}
//--- check
if(st==2)
{
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][0];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_x);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][1];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_y);
}
}
//+------------------------------------------------------------------+
//| This function builds periodic 3-dimensional parametric spline |
//| which starts at (X[0],Y[0],Z[0]), goes through all points to |
//| (X[N-1],Y[N-1],Z[N-1]) and then back to (X[0],Y[0],Z[0]). |
//| Same as PSpline2Build() function, but for 3D, so we won't |
//| duplicate its description here. |
//+------------------------------------------------------------------+
void CPSpline::PSpline3BuildPeriodic(CMatrixDouble &cxy,const int n,
const int st,const int pt,
CPSpline3Interpolant &p)
{
int i_=0;
//--- create array
double tmp[];
//--- create matrix
CMatrixDouble xyp;
CMatrixDouble xy;
//--- copy matrix
xy=cxy;
//--- check
if(!CAp::Assert(st>=1 && st<=2,__FUNCTION__+": incorrect spline type!"))
return;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
return;
//--- check
if(!CAp::Assert(n>=3,__FUNCTION__+": N<3!"))
return;
//--- Prepare
p.m_n=n;
p.m_periodic=true;
//--- allocation
ArrayResize(tmp,n+1);
xyp.Resize(n+1,3);
//--- change values
for(i_=0; i_<n; i_++)
xyp.Set(i_,0,xy[i_][0]);
for(i_=0; i_<n; i_++)
xyp.Set(i_,1,xy[i_][1]);
for(i_=0; i_<n; i_++)
xyp.Set(i_,2,xy[i_][2]);
for(i_=0; i_<=2; i_++)
xyp.Set(n,i_,xy[0][i_]);
//--- Build parameterization,check that all parameters are distinct
PSpline3Par(xyp,n+1,pt,p.m_p);
//--- check
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n+1),__FUNCTION__+": consequent (or first and last) points are too close!"))
return;
//--- Build splines
if(st==1)
{
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][0];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_x);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][1];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_y);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][2];
//--- function call
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_z);
}
//--- check
if(st==2)
{
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][0];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_x);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][1];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_y);
//--- copy
for(i_=0; i_<=n; i_++)
tmp[i_]=xyp[i_][2];
//--- function call
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_z);
}
}
//+------------------------------------------------------------------+
//| This function returns vector of parameter values correspoding to |
//| points. |
//| I.e. for P created from (X[0],Y[0])...(X[N-1],Y[N-1]) and |
//| U=TValues(P) we have |
//| (X[0],Y[0]) = PSpline2Calc(P,U[0]), |
//| (X[1],Y[1]) = PSpline2Calc(P,U[1]), |
//| (X[2],Y[2]) = PSpline2Calc(P,U[2]), |
//| ... |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| OUTPUT PARAMETERS: |
//| N - array size |
//| T - array[0..N-1] |
//| NOTES: |
//| * for non-periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]=1|
//| * for periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]<1|
//+------------------------------------------------------------------+
void CPSpline::PSpline2ParameterValues(CPSpline2Interpolant &p,
int &n,double &t[])
{
//--- initialization
n=0;
//--- check
if(!CAp::Assert(p.m_n>=2,__FUNCTION__+": internal error!"))
return;
//--- initialization
n=p.m_n;
//--- allocation
ArrayResize(t,n);
//--- copy
for(int i_=0; i_<n; i_++)
t[i_]=p.m_p[i_];
t[0]=0;
//--- check
if(!p.m_periodic)
t[n-1]=1;
}
//+------------------------------------------------------------------+
//| This function returns vector of parameter values correspoding to |
//| points. |
//| Same as PSpline2ParameterValues(), but for 3D. |
//+------------------------------------------------------------------+
void CPSpline::PSpline3ParameterValues(CPSpline3Interpolant &p,
int &n,double &t[])
{
int i_=0;
//--- initialization
n=0;
//--- check
if(!CAp::Assert(p.m_n>=2,__FUNCTION__+": internal error!"))
return;
//--- initialization
n=p.m_n;
//--- allocation
ArrayResize(t,n);
//--- copy
for(i_=0; i_<n; i_++)
t[i_]=p.m_p[i_];
t[0]=0;
//--- check
if(!p.m_periodic)
t[n-1]=1;
}
//+------------------------------------------------------------------+
//| This function calculates the value of the parametric spline for a|
//| given value of parameter T |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-position |
//| Y - Y-position |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Calc(CPSpline2Interpolant &p,double t,
double &x,double &y)
{
//--- initialization
x=0;
y=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
x=CSpline1D::Spline1DCalc(p.m_x,t);
//--- function call
y=CSpline1D::Spline1DCalc(p.m_y,t);
}
//+------------------------------------------------------------------+
//| This function calculates the value of the parametric spline for a|
//| given value of parameter T. |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond |
//| to parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-position |
//| Y - Y-position |
//| Z - Z-position |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Calc(CPSpline3Interpolant &p,double t,
double &x,double &y,double &z)
{
//--- initialization
x=0;
y=0;
z=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
x=CSpline1D::Spline1DCalc(p.m_x,t);
//--- function call
y=CSpline1D::Spline1DCalc(p.m_y,t);
//--- function call
z=CSpline1D::Spline1DCalc(p.m_z,t);
}
//+------------------------------------------------------------------+
//| This function calculates tangent vector for a given value of |
//| parameter T |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-component of tangent vector (normalized) |
//| Y - Y-component of tangent vector (normalized) |
//| NOTE: |
//| X^2+Y^2 is either 1 (for non-zero tangent vector) or 0. |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Tangent(CPSpline2Interpolant &p,double t,
double &x,double &y)
{
//--- create variables
double v=0;
double v0=0;
double v1=0;
//--- initialization
x=0;
y=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
PSpline2Diff(p,t,v0,x,v1,y);
//--- check
if(x!=0.0 || y!=0.0)
{
//--- this code is a bit more complex than X^2+Y^2 to avoid
//--- overflow for large values of X and Y.
v=CApServ::SafePythag2(x,y);
x=x/v;
y=y/v;
}
}
//+------------------------------------------------------------------+
//| This function calculates tangent vector for a given value of |
//| parameter T |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-component of tangent vector (normalized) |
//| Y - Y-component of tangent vector (normalized) |
//| Z - Z-component of tangent vector (normalized) |
//| NOTE: |
//| X^2+Y^2+Z^2 is either 1 (for non-zero tangent vector) or 0. |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Tangent(CPSpline3Interpolant &p,double t,
double &x,double &y,double &z)
{
//--- create variables
double v=0;
double v0=0;
double v1=0;
double v2=0;
//--- initialization
x=0;
y=0;
z=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
PSpline3Diff(p,t,v0,x,v1,y,v2,z);
//--- check
if((x!=0.0 || y!=0.0) || z!=0.0)
{
//--- function call
v=CApServ::SafePythag3(x,y,z);
//--- change values
x=x/v;
y=y/v;
z=z/v;
}
}
//+------------------------------------------------------------------+
//| This function calculates derivative, i.e. it returns |
//| (dX/dT,dY/dT). |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-value |
//| DX - X-derivative |
//| Y - Y-value |
//| DY - Y-derivative |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Diff(CPSpline2Interpolant &p,double t,
double &x,double &dx,double &y,
double &dy)
{
double d2s=0;
//--- change values
x=0;
dx=0;
y=0;
dy=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2s);
//--- function call
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2s);
}
//+------------------------------------------------------------------+
//| This function calculates derivative, i.e. it returns |
//| (dX/dT,dY/dT,dZ/dT). |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-value |
//| DX - X-derivative |
//| Y - Y-value |
//| DY - Y-derivative |
//| Z - Z-value |
//| DZ - Z-derivative |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Diff(CPSpline3Interpolant &p,double t,
double &x,double &dx,double &y,
double &dy,double &z,double &dz)
{
double d2s=0;
//--- initialization
x=0;
dx=0;
y=0;
dy=0;
z=0;
dz=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2s);
//--- function call
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2s);
//--- function call
CSpline1D::Spline1DDiff(p.m_z,t,z,dz,d2s);
}
//+------------------------------------------------------------------+
//| This function calculates first and second derivative with respect|
//| to T. |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-value |
//| DX - derivative |
//| D2X - second derivative |
//| Y - Y-value |
//| DY - derivative |
//| D2Y - second derivative |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Diff2(CPSpline2Interpolant &p,double t,
double &x,double &dx,double &d2x,
double &y,double &dy,double &d2y)
{
//--- initialization
x=0;
dx=0;
d2x=0;
y=0;
dy=0;
d2y=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2x);
//--- function call
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2y);
}
//+------------------------------------------------------------------+
//| This function calculates first and second derivative with respect|
//| to T. |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| T - point: |
//| * T in [0,1] corresponds to interval spanned by |
//| points |
//| * for non-periodic splines T<0 (or T>1) correspond to|
//| parts of the curve before the first (after the |
//| last) point |
//| * for periodic splines T<0 (or T>1) are projected |
//| into [0,1] by making T=T-floor(T). |
//| OUTPUT PARAMETERS: |
//| X - X-value |
//| DX - derivative |
//| D2X - second derivative |
//| Y - Y-value |
//| DY - derivative |
//| D2Y - second derivative |
//| Z - Z-value |
//| DZ - derivative |
//| D2Z - second derivative |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Diff2(CPSpline3Interpolant &p,double t,
double &x,double &dx,double &d2x,
double &y,double &dy,double &d2y,
double &z,double &dz,double &d2z)
{
//--- initialization
x=0;
dx=0;
d2x=0;
y=0;
dy=0;
d2y=0;
z=0;
dz=0;
d2z=0;
//--- check
if(p.m_periodic)
t=t-(int)MathFloor(t);
//--- function call
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2x);
//--- function call
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2y);
//--- function call
CSpline1D::Spline1DDiff(p.m_z,t,z,dz,d2z);
}
//+------------------------------------------------------------------+
//| This function calculates arc length, i.e. length of curve between|
//| t=a and t=b. |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| A,B - parameter values corresponding to arc ends: |
//| * B>A will result in positive length returned |
//| * B<A will result in negative length returned |
//| RESULT: |
//| length of arc starting at T=A and ending at T=B. |
//+------------------------------------------------------------------+
double CPSpline::PSpline2ArcLength(CPSpline2Interpolant &p,const double a,
const double b)
{
//--- create variables
double result=0;
double sx=0;
double dsx=0;
double d2sx=0;
double sy=0;
double dsy=0;
double d2sy=0;
//--- objects of classes
CAutoGKState State;
CAutoGKReport rep;
//--- function call
CAutoGK::AutoGKSmooth(a,b,State);
//--- cycle
while(CAutoGK::AutoGKIteration(State))
{
CSpline1D::Spline1DDiff(p.m_x,State.m_x,sx,dsx,d2sx);
CSpline1D::Spline1DDiff(p.m_y,State.m_x,sy,dsy,d2sy);
State.m_f=CApServ::SafePythag2(dsx,dsy);
}
//--- function call
CAutoGK::AutoGKResults(State,result,rep);
//--- check
if(!CAp::Assert(rep.m_terminationtype>0,__FUNCTION__+": internal error!"))
return(EMPTY_VALUE);
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates arc length, i.e. length of curve between|
//| t=a and t=b. |
//| INPUT PARAMETERS: |
//| P - parametric spline interpolant |
//| A,B - parameter values corresponding to arc ends: |
//| * B>A will result in positive length returned |
//| * B<A will result in negative length returned |
//| RESULT: |
//| length of arc starting at T=A and ending at T=B. |
//+------------------------------------------------------------------+
double CPSpline::PSpline3ArcLength(CPSpline3Interpolant &p,const double a,
const double b)
{
//--- create variables
double result=0;
double sx=0;
double dsx=0;
double d2sx=0;
double sy=0;
double dsy=0;
double d2sy=0;
double sz=0;
double dsz=0;
double d2sz=0;
//--- objects of classes
CAutoGKState State;
CAutoGKReport rep;
//--- function call
CAutoGK::AutoGKSmooth(a,b,State);
//--- cycle
while(CAutoGK::AutoGKIteration(State))
{
CSpline1D::Spline1DDiff(p.m_x,State.m_x,sx,dsx,d2sx);
CSpline1D::Spline1DDiff(p.m_y,State.m_x,sy,dsy,d2sy);
CSpline1D::Spline1DDiff(p.m_z,State.m_x,sz,dsz,d2sz);
State.m_f=CApServ::SafePythag3(dsx,dsy,dsz);
}
//--- function call
CAutoGK::AutoGKResults(State,result,rep);
//--- check
if(!CAp::Assert(rep.m_terminationtype>0,__FUNCTION__+": internal error!"))
return(EMPTY_VALUE);
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This subroutine fits piecewise linear curve to points with |
//| Ramer-Douglas-Peucker algorithm. This function performs |
//| PARAMETRIC fit, i.e. it can be used to fit curves like circles. |
//| On input it accepts dataset which describes parametric |
//| multidimensional curve X(t), with X being vector, and t taking |
//| values in [0,N), where N is a number of points in dataset. As |
//| result, it returns reduced dataset X2, which can be used to |
//| build parametric curve X2(t), which approximates X(t) with |
//| desired precision (or has specified number of sections). |
//| INPUT PARAMETERS: |
//| X - array of multidimensional points: |
//| * at least N elements, leading N elements are used |
//| if more than N elements were specified |
//| * order of points is IMPORTANT because it is |
//| parametric fit |
//| * each row of array is one point which has D |
//| coordinates |
//| N - number of elements in X |
//| D - number of dimensions (elements per row of X) |
//| StopM - stopping condition - desired number of sections: |
//| * at most M sections are generated by this function|
//| * less than M sections can be generated if we have |
//| N<M (or some X are non-distinct). |
//| * zero StopM means that algorithm does not stop |
//| after achieving some pre-specified section count |
//| StopEps - stopping condition - desired precision: |
//| * algorithm stops after error in each section is at|
//| most Eps |
//| * zero Eps means that algorithm does not stop after|
//| achieving some pre-specified precision |
//| OUTPUT PARAMETERS: |
//| X2 - array of corner points for piecewise approximation,|
//| has length NSections+1 or zero (for NSections=0). |
//| Idx2 - array of indexes (parameter values): |
//| * has length NSections+1 or zero (for NSections=0).|
//| * each element of Idx2 corresponds to same-numbered|
//| element of X2 |
//| * each element of Idx2 is index of corresponding |
//| element of X2 at original array X, i.e. I-th row |
//| of X2 is Idx2[I]-th row of X. |
//| * elements of Idx2 can be treated as parameter |
//| values which should be used when building new |
//| parametric curve |
//| * Idx2[0]=0, Idx2[NSections]=N-1 |
//| NSections - number of sections found by algorithm,NSections<=M,|
//| NSections can be zero for degenerate datasets (N<=1|
//| or all X[] are non-distinct). |
//| NOTE: algorithm stops after: |
//| a) dividing curve into StopM sections |
//| b) achieving required precision StopEps |
//| c) dividing curve into N-1 sections |
//| If both StopM and StopEps are non-zero, algorithm is stopped by |
//| the FIRST criterion which is satisfied. In case both StopM and |
//| StopEps are zero, algorithm stops because of (c). |
//+------------------------------------------------------------------+
void CPSpline::ParametricRDPFixed(CMatrixDouble &x,
int n,
int d,
int stopm,
double stopeps,
CMatrixDouble &x2,
int &idx2[],
int &nsections)
{
//--- create variables
int i=0;
int j=0;
int k=0;
bool allsame=false;
int k0=0;
int k1=0;
int k2=0;
double e0=0;
double e1=0;
int idx0=0;
int idx1=0;
int worstidx=0;
double worsterror=0;
CMatrixDouble sections;
double heaperrors[];
int heaptags[];
double buf0[];
double buf1[];
x2.Resize(0,0);
ArrayFree(idx2);
nsections=0;
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return;
if(!CAp::Assert(d>=1,__FUNCTION__+": D<=0"))
return;
if(!CAp::Assert(stopm>=0,__FUNCTION__+": StopM<1"))
return;
if(!CAp::Assert(MathIsValidNumber(stopeps) && stopeps>=0.0,__FUNCTION__+": StopEps<0 or is infinite"))
return;
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": Rows(X)<N"))
return;
if(!CAp::Assert(CAp::Cols(x)>=d,__FUNCTION__+": Cols(X)<D"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,d),__FUNCTION__+": X contains infinite/NAN values"))
return;
//--- Handle degenerate cases
if(n<=1)
{
nsections=0;
return;
}
allsame=true;
for(i=1; i<n; i++)
{
for(j=0; j<d; j++)
allsame=allsame && x.Get(i,j)==x.Get(0,j);
}
if(allsame)
{
nsections=0;
return;
}
//--- Prepare first section
RDPAnalyzeSectionPar(x,0,n-1,d,worstidx,worsterror);
sections.Resize(n,4);
ArrayResize(heaperrors,n);
ArrayResize(heaptags,n);
nsections=1;
sections.Set(0,0,0);
sections.Set(0,1,n-1);
sections.Set(0,2,worstidx);
sections.Set(0,3,worsterror);
heaperrors[0]=worsterror;
heaptags[0]=0;
//--- check
if(!CAp::Assert(sections.Get(0,1)==(n-1),__FUNCTION__+": integrity check failed"))
return;
//--- Main loop.
//--- Repeatedly find section with worst error and divide it.
//--- Terminate after M-th section, or because of other reasons (see loop internals).
while(true)
{
//--- Break loop if one of the stopping conditions was met.
//--- Store index of worst section to K.
if(heaperrors[0]==0.0)
break;
if(stopeps>0.0 && heaperrors[0]<=stopeps)
break;
if(stopm>0 && nsections>=stopm)
break;
k=heaptags[0];
//--- K-th section is divided in two:
//--- * first one spans interval from X[Sections[K,0]] to X[Sections[K,2]]
//--- * second one spans interval from X[Sections[K,2]] to X[Sections[K,1]]
//--- First section is stored at K-th position, second one is appended to the table.
//--- Then we update heap which stores pairs of (error,section_index)
k0=(int)MathRound(sections.Get(k,0));
k1=(int)MathRound(sections.Get(k,1));
k2=(int)MathRound(sections.Get(k,2));
RDPAnalyzeSectionPar(x,k0,k2,d,idx0,e0);
RDPAnalyzeSectionPar(x,k2,k1,d,idx1,e1);
sections.Set(k,0,k0);
sections.Set(k,1,k2);
sections.Set(k,2,idx0);
sections.Set(k,3,e0);
CTSort::TagHeapReplaceTopI(heaperrors,heaptags,nsections,e0,k);
sections.Set(nsections,0,k2);
sections.Set(nsections,1,k1);
sections.Set(nsections,2,idx1);
sections.Set(nsections,3,e1);
CTSort::TagHeapPushI(heaperrors,heaptags,nsections,e1,nsections);
}
//--- Convert from sections to indexes
ArrayResize(buf0,nsections+1);
for(i=0; i<nsections; i++)
buf0[i]=(int)MathRound(sections.Get(i,0));
buf0[nsections]=n-1;
CTSort::TagSortFast(buf0,buf1,nsections+1);
ArrayResize(idx2,nsections+1);
for(i=0; i<=nsections; i++)
idx2[i]=(int)MathRound(buf0[i]);
//--- check
if(!CAp::Assert(idx2[0]==0,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(idx2[nsections]==n-1,__FUNCTION__+": integrity check failed"))
return;
//--- Output sections:
//--- * first NSection elements of X2/Y2 are filled by x/y at left boundaries of sections
//--- * last element of X2/Y2 is filled by right boundary of rightmost section
//--- * X2/Y2 is sorted by ascending of X2
x2.Resize(nsections+1,d);
for(i=0; i<=nsections; i++)
for(j=0; j<d; j++)
x2.Set(i,j,x.Get(idx2[i],j));
}
//+------------------------------------------------------------------+
//| Builds non-periodic parameterization for 2-dimensional spline |
//+------------------------------------------------------------------+
void CPSpline::PSpline2Par(CMatrixDouble &xy,const int n,const int pt,
double &p[])
{
double v=0;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": internal error!"))
return;
//--- Build parameterization:
//--- * fill by non-normalized values
//--- * normalize them so we have P[0]=0,P[N-1]=1.
ArrayResize(p,n);
//--- check
if(pt==0)
{
for(int i=0; i<n; i++)
p[i]=i;
}
//--- check
if(pt==1)
{
p[0]=0;
//--- calculation
for(int i=1; i<n; i++)
p[i]=p[i-1]+CApServ::SafePythag2(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
}
//--- check
if(pt==2)
{
p[0]=0;
//--- calculation
for(int i=1; i<n; i++)
p[i]=p[i-1]+MathSqrt(CApServ::SafePythag2(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]));
}
//--- change value
v=1/p[n-1];
//--- calculation
for(int i_=0; i_<n; i_++)
p[i_]=v*p[i_];
}
//+------------------------------------------------------------------+
//| Builds non-periodic parameterization for 3-dimensional spline |
//+------------------------------------------------------------------+
void CPSpline::PSpline3Par(CMatrixDouble &xy,const int n,const int pt,
double &p[])
{
double v=0;
//--- check
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": internal error!"))
return;
//--- Build parameterization:
//--- * fill by non-normalized values
//--- * normalize them so we have P[0]=0,P[N-1]=1.
ArrayResize(p,n);
//--- check
if(pt==0)
{
for(int i=0; i<n; i++)
p[i]=i;
}
//--- check
if(pt==1)
{
p[0]=0;
//--- calculation
for(int i=1; i<n; i++)
p[i]=p[i-1]+CApServ::SafePythag3(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1],xy[i][2]-xy[i-1][2]);
}
//--- check
if(pt==2)
{
p[0]=0;
//--- calculation
for(int i=1; i<n; i++)
p[i]=p[i-1]+MathSqrt(CApServ::SafePythag3(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1],xy[i][2]-xy[i-1][2]));
}
//--- change value
v=1/p[n-1];
//--- calculation
for(int i_=0; i_<n; i_++)
p[i_]=v*p[i_];
}
//+------------------------------------------------------------------+
//| This function analyzes section of curve for processing by RDP |
//| algorithm: given set of points X,Y with indexes [I0,I1] it |
//| returns point with worst deviation from linear model (PARAMETRIC |
//| version which sees curve as X(t) with vector X). |
//| Input parameters: |
//| XY - array |
//| I0,I1 - interval (boundaries included) to process |
//| D - number of dimensions |
//| OUTPUT PARAMETERS: |
//| WorstIdx - index of worst point |
//| WorstError - error at worst point |
//| NOTE: this function guarantees that it returns exactly zero for |
//| a section with less than 3 points. |
//+------------------------------------------------------------------+
void CPSpline::RDPAnalyzeSectionPar(CMatrixDouble &xy,
int i0,
int i1,
int d,
int &worstidx,
double &worsterror)
{
//--- create variables
double v=0;
double d2=0;
double ts=0;
double vv=0;
worstidx=0;
worsterror=0;
//--- Quick exit for 0, 1, 2 points
if(i1-i0+1<3)
{
worstidx=i0;
worsterror=0.0;
return;
}
//--- Estimate D2 - squared distance between XY[I1] and XY[I0].
//--- In case D2=0 handle it as special case.
d2=0.0;
for(int j=0; j<d; j++)
d2+=CMath::Sqr(xy.Get(i1,j)-xy.Get(i0,j));
if(d2==0.0)
{
//--- First and last points are equal, interval evaluation is
//--- trivial - we just calculate distance from all points to
//--- the first/last one.
worstidx=i0;
worsterror=0.0;
for(int i=i0+1; i<i1; i++)
{
vv=0.0;
for(int j=0; j<d ; j++)
{
v=xy.Get(i,j)-xy.Get(i0,j);
vv+=v*v;
}
vv=MathSqrt(vv);
if(vv>worsterror)
{
worsterror=vv;
worstidx=i;
}
}
return;
}
//--- General case
//--- Current section of curve is modeled as x(t) = d*t+c, where
//--- d = XY[I1]-XY[I0]
//--- c = XY[I0]
//--- t is in [0,1]
worstidx=i0;
worsterror=0.0;
for(int i=i0+1; i<i1; i++)
{
//--- Determine t_s - parameter value for projected point.
ts=(double)(i-i0)/(double)(i1-i0);
//--- Estimate error norm
vv=0.0;
for(int j=0; j<d; j++)
{
v=(xy.Get(i1,j)-xy.Get(i0,j))*ts-(xy.Get(i,j)-xy.Get(i0,j));
vv=vv+CMath::Sqr(v);
}
vv=MathSqrt(vv);
if(vv>worsterror)
{
worsterror=vv;
worstidx=i;
}
}
}
//+------------------------------------------------------------------+
//| 2-dimensional spline inteprolant |
//+------------------------------------------------------------------+
class CSpline2DInterpolant
{
public:
//--- variable
int m_d;
int m_m;
int m_n;
int m_stype;
//--- array
CRowDouble m_f;
CRowDouble m_x;
CRowDouble m_y;
//--- constructor, destructor
CSpline2DInterpolant(void);
~CSpline2DInterpolant(void) {}
//--- copy
void Copy(const CSpline2DInterpolant&obj);
//--- overloading
void operator=(const CSpline2DInterpolant&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CSpline2DInterpolant::CSpline2DInterpolant(void)
{
m_d=0;
m_m=0;
m_n=0;
m_stype=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline2DInterpolant::Copy(const CSpline2DInterpolant &obj)
{
//--- copy variable
m_d=obj.m_d;
m_m=obj.m_m;
m_n=obj.m_n;
m_stype=obj.m_stype;
//--- copy array
m_f=obj.m_f;
m_x=obj.m_x;
m_y=obj.m_y;
}
//+------------------------------------------------------------------+
//| 2-dimensional spline inteprolant |
//+------------------------------------------------------------------+
class CSpline2DInterpolantShell
{
private:
CSpline2DInterpolant m_innerobj;
public:
//--- constructors, destructor
CSpline2DInterpolantShell(void) {}
CSpline2DInterpolantShell(CSpline2DInterpolant&obj) { m_innerobj.Copy(obj); }
~CSpline2DInterpolantShell(void) {}
//--- method
CSpline2DInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
};
//+------------------------------------------------------------------+
//| Nonlinear least squares solver used to fit 2D splines to data |
//+------------------------------------------------------------------+
class CSpline2DBuilder
{
public:
int m_areatype;
int m_d;
int m_gridtype;
int m_interfacesize;
int m_kx;
int m_ky;
int m_lsqrcnt;
int m_maxcoresize;
int m_nlayers;
int m_npoints;
int m_priorterm;
int m_solvertype;
double m_lambdabase;
double m_priortermval;
double m_smoothing;
double m_sx;
double m_sy;
double m_xa;
double m_xb;
double m_ya;
double m_yb;
bool m_adddegreeoffreedom;
CRowDouble m_xy;
//--- constructor / destructor
CSpline2DBuilder(void);
~CSpline2DBuilder(void) {}
//---
void Copy(const CSpline2DBuilder&obj);
//--- overloading
void operator=(const CSpline2DBuilder&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CSpline2DBuilder::CSpline2DBuilder(void)
{
m_areatype=0;
m_d=0;
m_gridtype=0;
m_interfacesize=0;
m_kx=0;
m_ky=0;
m_lsqrcnt=0;
m_maxcoresize=0;
m_nlayers=0;
m_npoints=0;
m_priorterm=0;
m_solvertype=0;
m_lambdabase=0;
m_priortermval=0;
m_smoothing=0;
m_sx=0;
m_sy=0;
m_xa=0;
m_xb=0;
m_ya=0;
m_yb=0;
m_adddegreeoffreedom=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline2DBuilder::Copy(const CSpline2DBuilder &obj)
{
m_areatype=obj.m_areatype;
m_d=obj.m_d;
m_gridtype=obj.m_gridtype;
m_interfacesize=obj.m_interfacesize;
m_kx=obj.m_kx;
m_ky=obj.m_ky;
m_lsqrcnt=obj.m_lsqrcnt;
m_maxcoresize=obj.m_maxcoresize;
m_nlayers=obj.m_nlayers;
m_npoints=obj.m_npoints;
m_priorterm=obj.m_priorterm;
m_solvertype=obj.m_solvertype;
m_lambdabase=obj.m_lambdabase;
m_priortermval=obj.m_priortermval;
m_smoothing=obj.m_smoothing;
m_sx=obj.m_sx;
m_sy=obj.m_sy;
m_xa=obj.m_xa;
m_xb=obj.m_xb;
m_ya=obj.m_ya;
m_yb=obj.m_yb;
m_adddegreeoffreedom=obj.m_adddegreeoffreedom;
m_xy=obj.m_xy;
}
//+------------------------------------------------------------------+
//| Spline 2D fitting report: |
//| rmserror RMS error |
//| avgerror average error |
//| maxerror maximum error |
//| r2 coefficient of determination, R-squared, 1-RSS/TSS/
//+------------------------------------------------------------------+
struct CSpline2DFitReport
{
double m_avgerror;
double m_maxerror;
double m_r2;
double m_rmserror;
//--- constructor / destructor
CSpline2DFitReport(void) { ZeroMemory(this); }
~CSpline2DFitReport(void) {}
//--- copy
void Copy(const CSpline2DFitReport&obj);
//--- overloading
void operator=(const CSpline2DFitReport&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline2DFitReport::Copy(const CSpline2DFitReport &obj)
{
m_avgerror=obj.m_avgerror;
m_maxerror=obj.m_maxerror;
m_r2=obj.m_r2;
m_rmserror=obj.m_rmserror;
}
//+------------------------------------------------------------------+
//| Design matrix stored in batch/block sparse format. |
//| The idea is that design matrix for bicubic spline fitting has |
//| very regular structure: |
//| 1. I-th row has non-zero entries in elements with indexes |
//| starting from some IDX, and including: IDX, IDX+1, IDX+2, |
//| IDX+3, IDX+KX+0, IDX+KX+1, and so on, up to 16 elements in |
//| total. |
//| Rows corresponding to dataset points have 16 non-zero |
//| elements, rows corresponding to nonlinearity penalty have 9 |
//| non-zero elements, and rows of regularizer have 1 element. |
//| For the sake of simplicity, we can use 16 elements for dataset|
//| rows and penalty rows, and process regularizer explicitly. |
//| 2. points located in the same cell of the grid have same pattern |
//| of non-zeros, so we can use dense Level 2 and Level 3 linear |
//| algebra to work with such matrices. |
//+------------------------------------------------------------------+
struct CSpline2DXDesignMatrix
{
int m_blockwidth;
int m_d;
int m_kx;
int m_ky;
int m_maxbatch;
int m_ndensebatches;
int m_ndenserows;
int m_npoints;
int m_nrows;
double m_lambdareg;
CRowInt m_batchbases;
CRowInt m_batches;
CRowDouble m_tmp0;
CRowDouble m_tmp1;
CMatrixDouble m_tmp2;
CMatrixDouble m_vals;
//--- constructor / destructor
CSpline2DXDesignMatrix(void);
~CSpline2DXDesignMatrix(void) {}
//--- copy
void Copy(const CSpline2DXDesignMatrix&obj);
//--- overloading
void operator=(const CSpline2DXDesignMatrix&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CSpline2DXDesignMatrix::CSpline2DXDesignMatrix(void)
{
m_blockwidth=0;
m_d=0;
m_kx=0;
m_ky=0;
m_maxbatch=0;
m_ndensebatches=0;
m_ndenserows=0;
m_npoints=0;
m_nrows=0;
m_lambdareg=0;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CSpline2DXDesignMatrix::Copy(const CSpline2DXDesignMatrix &obj)
{
m_blockwidth=obj.m_blockwidth;
m_d=obj.m_d;
m_kx=obj.m_kx;
m_ky=obj.m_ky;
m_maxbatch=obj.m_maxbatch;
m_ndensebatches=obj.m_ndensebatches;
m_ndenserows=obj.m_ndenserows;
m_npoints=obj.m_npoints;
m_nrows=obj.m_nrows;
m_lambdareg=obj.m_lambdareg;
m_batchbases=obj.m_batchbases;
m_batches=obj.m_batches;
m_tmp0=obj.m_tmp0;
m_tmp1=obj.m_tmp1;
m_tmp2=obj.m_tmp2;
m_vals=obj.m_vals;
}
//+------------------------------------------------------------------+
//| Temporaries for BlockLLS solver |
//+------------------------------------------------------------------+
struct CSpline2DBlockLLSBuf
{
CRowDouble m_cholbuf1;
CRowDouble m_tmp0;
CRowDouble m_tmp1;
CMatrixDouble m_blockata;
CMatrixDouble m_cholbuf2;
CMatrixDouble m_trsmbuf2;
CLinLSQRState m_solver;
CLinLSQRReport m_solverrep;
//--- constructor / destructor
CSpline2DBlockLLSBuf(void) {}
~CSpline2DBlockLLSBuf(void) {}
//--- copy
void Copy(const CSpline2DBlockLLSBuf&obj);
//--- overloading
void operator=(const CSpline2DBlockLLSBuf&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline2DBlockLLSBuf::Copy(const CSpline2DBlockLLSBuf &obj)
{
m_cholbuf1=obj.m_cholbuf1;
m_tmp0=obj.m_tmp0;
m_tmp1=obj.m_tmp1;
m_blockata=obj.m_blockata;
m_cholbuf2=obj.m_cholbuf2;
m_trsmbuf2=obj.m_trsmbuf2;
m_solver=obj.m_solver;
m_solverrep=obj.m_solverrep;
}
//+------------------------------------------------------------------+
//| Temporaries for FastDDM solver |
//+------------------------------------------------------------------+
struct CSpline2DFastDDMBuf
{
CSpline2DXDesignMatrix m_xdesignmatrix;
CSpline2DInterpolant m_localmodel;
CSpline2DFitReport m_dummyrep;
CSpline2DBlockLLSBuf m_blockllsbuf;
CRowDouble m_tmp0;
CRowDouble m_tmpz;
//--- constructor / destructor
CSpline2DFastDDMBuf(void) {}
~CSpline2DFastDDMBuf(void) {}
//--- copy
void Copy(const CSpline2DFastDDMBuf&obj);
//--- overloading
void operator=(const CSpline2DFastDDMBuf&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline2DFastDDMBuf::Copy(const CSpline2DFastDDMBuf &obj)
{
m_xdesignmatrix=obj.m_xdesignmatrix;
m_localmodel=obj.m_localmodel;
m_dummyrep=obj.m_dummyrep;
m_blockllsbuf=obj.m_blockllsbuf;
m_tmp0=obj.m_tmp0;
m_tmpz=obj.m_tmpz;
}
//+------------------------------------------------------------------+
//| 2-dimensional spline interpolation |
//+------------------------------------------------------------------+
class CSpline2D
{
public:
//--- constants
static const double m_cholreg;
static const double m_lambdaregblocklls;
static const double m_lambdaregfastddm;
static const double m_lambdadecay;
//--- public methods
static void Spline2DBuildBilinear(double &cx[],double &cy[],CMatrixDouble&cf,const int m,const int n,CSpline2DInterpolant&c);
static void Spline2DBuildBicubic(double &cx[],double &cy[],CMatrixDouble&cf,const int m,const int n,CSpline2DInterpolant&c);
static double Spline2DCalc(CSpline2DInterpolant&c,const double x,const double y);
static void Spline2DDiff(CSpline2DInterpolant&c,const double x,const double y,double&f,double&fx,double&fy,double&fxy);
static void Spline2DCalcVBuf(CSpline2DInterpolant&c,double x,double y,CRowDouble&f);
static double Spline2DCalcVi(CSpline2DInterpolant&c,double x,double y,int i);
static void Spline2DCalcV(CSpline2DInterpolant&c,double x,double y,CRowDouble&f);
static void Spline2DDiffVi(CSpline2DInterpolant&c,double x,double y,int i,double&f,double&fx,double&fy,double&fxy);
static void Spline2DUnpack(CSpline2DInterpolant&c,int &m,int &n,CMatrixDouble&tbl);
static void Spline2DLinTransXY(CSpline2DInterpolant&c,double ax,double bx,double ay,double by);
static void Spline2DLinTransF(CSpline2DInterpolant&c,const double a,const double b);
static void Spline2DCopy(CSpline2DInterpolant&c,CSpline2DInterpolant&cc);
static void Spline2DResampleBicubic(CMatrixDouble&a,const int oldheight,const int oldwidth,CMatrixDouble&b,const int newheight,const int newwidth);
static void Spline2DResampleBilinear(CMatrixDouble&a,const int oldheight,const int oldwidth,CMatrixDouble&b,const int newheight,const int newwidth);
static void Spline2DBuildBilinearV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&f,int d,CSpline2DInterpolant&c);
static void Spline2DBuildBicubicV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&f,int d,CSpline2DInterpolant&c);
static void Spline2DUnpackV(CSpline2DInterpolant&c,int &m,int &n,int &d,CMatrixDouble&tbl);
static void Spline2DBuilderCreate(int d,CSpline2DBuilder&State);
static void Spline2DBuilderSetUserTerm(CSpline2DBuilder&State,double v);
static void Spline2DBuilderSetLinTerm(CSpline2DBuilder&State);
static void Spline2DBuilderSetConstTerm(CSpline2DBuilder&State);
static void Spline2DBuilderSetZeroTerm(CSpline2DBuilder&State);
static void Spline2DBuilderSetPoints(CSpline2DBuilder&State,CMatrixDouble&xy,int n);
static void Spline2DBuilderSetAreaAuto(CSpline2DBuilder&State);
static void Spline2DBuilderSetArea(CSpline2DBuilder&State,double xa,double xb,double ya,double yb);
static void Spline2DBuilderSetGrid(CSpline2DBuilder&State,int kx,int ky);
static void Spline2DBuilderSetAlgoFastDDM(CSpline2DBuilder&State,int nlayers,double lambdav);
static void Spline2DBuilderSetAlgoBlockLLS(CSpline2DBuilder&State,double lambdans);
static void Spline2DBuilderSetAlgoNaiveLLS(CSpline2DBuilder&State,double lambdans);
static void Spline2DFit(CSpline2DBuilder&State,CSpline2DInterpolant&s,CSpline2DFitReport&rep);
static void Spline2DAlloc(CSerializer&s,CSpline2DInterpolant&spline);
static void Spline2DSerialize(CSerializer&s,CSpline2DInterpolant&spline);
static void Spline2DUnserialize(CSerializer&s,CSpline2DInterpolant&spline);
private:
static void BicubicCalcDerivatives(CMatrixDouble&a,CRowDouble&x,CRowDouble&y,const int m,const int n,CMatrixDouble&dx,CMatrixDouble&dy,CMatrixDouble&dxy);
static void GenerateDesignMatrix(CRowDouble&xy,int npoints,int d,int kx,int ky,double smoothing,double lambdareg,CSpline1DInterpolant&basis1,CSparseMatrix&av,CSparseMatrix&ah,int &arows);
static void UpdateSplineTable(CRowDouble&z,int kx,int ky,int d,CSpline1DInterpolant&basis1,int bfrad,CRowDouble&ftbl,int m,int n,int scalexy);
static void FastDDMFit(CRowDouble&xy,int npoints,int d,int kx,int ky,int basecasex,int basecasey,int maxcoresize,int interfacesize,int nlayers,double smoothing,int lsqrcnt,CSpline1DInterpolant&basis1,CSpline2DInterpolant&spline,CSpline2DFitReport&rep,double tss);
static void FastDDMFitLayer(CRowDouble&xy,int d,int scalexy,CRowInt&xyindex,int basecasex,int tilex0,int tilex1,int tilescountx,int basecasey,int tiley0,int tiley1,int tilescounty,int maxcoresize,int interfacesize,int lsqrcnt,double lambdareg,CSpline1DInterpolant&basis1,CSpline2DFastDDMBuf&pool,CSpline2DInterpolant&spline);
static void BlockLLSFit(CSpline2DXDesignMatrix&xdesign,int lsqrcnt,CRowDouble&z,CSpline2DFitReport&rep,double tss,CSpline2DBlockLLSBuf&buf);
static void NaiveLLSFit(CSparseMatrix&av,CSparseMatrix&ah,int arows,CRowDouble&xy,int kx,int ky,int npoints,int d,int lsqrcnt,CRowDouble&z,CSpline2DFitReport&rep,double tss);
static int GetCellOffset(int kx,int ky,int blockbandwidth,int i,int j);
static void CopyCellTo(int kx,int ky,int blockbandwidth,CMatrixDouble&blockata,int i,int j,CMatrixDouble&dst,int dst0,int dst1);
static void FlushToZeroCell(int kx,int ky,int blockbandwidth,CMatrixDouble&blockata,int i,int j,double eps);
static void BlockLLSGenerateATA(CSparseMatrix&ah,int ky0,int ky1,int kx,int ky,CMatrixDouble&blockata,double mxata);
static bool BlockLLSCholesky(CMatrixDouble&blockata,int kx,int ky,CMatrixDouble&trsmbuf2,CMatrixDouble&cholbuf2,CRowDouble&cholbuf1);
static void BlockLLSTrsV(CMatrixDouble&blockata,int kx,int ky,bool transu,CRowDouble&b);
static void ComputeResidualsFromScratch(CRowDouble&xy,CRowDouble&yraw,int npoints,int d,int scalexy,CSpline2DInterpolant&spline);
static void ComputeResidualsFromScratchRec(CRowDouble&xy,CRowDouble&yraw,int pt0,int pt1,int chunksize,int d,int scalexy,CSpline2DInterpolant&spline,CRowDouble&pool);
static void ReorderDatasetAndBuildIndex(CRowDouble&xy,int npoints,int d,CRowDouble&shadow,int ns,int kx,int ky,CRowInt&xyindex,CRowInt&bufi);
static void RescaleDatasetAndRefineIndex(CRowDouble&xy,int npoints,int d,CRowDouble&shadow,int ns,int kx,int ky,CRowInt&xyindex,CRowInt&bufi);
static void ExpandIndexRows(CRowDouble&xy,int d,CRowDouble&shadow,int ns,CRowInt&cidx,int pt0,int pt1,CRowInt&xyindexprev,int row0,int row1,CRowInt&xyindexnew,int kxnew,int kynew,bool rootcall);
static void ReorderDatasetAndBuildIndexRec(CRowDouble&xy,int d,CRowDouble&shadow,int ns,CRowInt&cidx,int pt0,int pt1,CRowInt&xyindex,int idx0,int idx1,bool rootcall);
static void XDesignGenerate(CRowDouble&xy,CRowInt&xyindex,int kx0,int kx1,int kxtotal,int ky0,int ky1,int kytotal,int d,double lambdareg,double lambdans,CSpline1DInterpolant&basis1,CSpline2DXDesignMatrix&a);
static void XDesignMV(CSpline2DXDesignMatrix&a,CRowDouble&x,CRowDouble&y);
static void XDesignMTV(CSpline2DXDesignMatrix&a,CRowDouble&x,CRowDouble&y);
static void XDesignBlockATA(CSpline2DXDesignMatrix&a,CMatrixDouble&blockata,double&mxata);
};
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
const double CSpline2D::m_cholreg=1.0E-12;
const double CSpline2D::m_lambdaregblocklls=1.0E-6;
const double CSpline2D::m_lambdaregfastddm=1.0E-4;
const double CSpline2D::m_lambdadecay=0.5;
//+------------------------------------------------------------------+
//| This subroutine builds bilinear spline coefficients table. |
//| Input parameters: |
//| X - spline abscissas, array[0..N-1] |
//| Y - spline ordinates, array[0..M-1] |
//| F - function values, array[0..M-1,0..N-1] |
//| M,N - grid size, M>=2, N>=2 |
//| Output parameters: |
//| C - spline interpolant |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuildBilinear(double &x[],double &y[],
CMatrixDouble &f,const int m,
const int n,CSpline2DInterpolant &c)
{
//--- create variables
double t=0;
int k=0;
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
return;
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
if(!CAp::Assert(CAp::Rows(f)>=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)<M or cols(F)<N)"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(f,m,n),__FUNCTION__+": F contains NaN or Infinite value"))
return;
//---Fill interpolant
c.m_n=n;
c.m_m=m;
c.m_d=1;
c.m_stype=-1;
c.m_x=x;
c.m_y=y;
c.m_x.Resize(c.m_n);
c.m_y.Resize(c.m_m);
c.m_f.Resize(c.m_n*c.m_m);
for(int i=0; i<c.m_m; i++)
for(int j=0; j<c.m_n; j++)
c.m_f.Set(i*c.m_n+j,f.Get(i,j));
//---Sort points
for(int j=0; j<c.m_n; j++)
{
k=j;
for(int i=j+1; i<c.m_n; i++)
{
if(c.m_x[i]<c.m_x[k])
k=i;
}
if(k!=j)
{
for(int i=0; i<c.m_m; i++)
c.m_f.Swap(i*c.m_n+j,i*c.m_n+k);
c.m_x.Swap(j,k);
}
}
for(int i=0; i<c.m_m; i++)
{
k=i;
for(int j=i+1; j<c.m_m; j++)
{
if(c.m_y[j]<c.m_y[k])
k=j;
}
if(k!=i)
{
for(int j=0; j<=c.m_n-1; j++)
c.m_f.Swap(i*c.m_n+j,k*c.m_n+j);
c.m_y.Swap(i,k);
}
}
}
//+------------------------------------------------------------------+
//| This subroutine builds bicubic spline coefficients table. |
//| Input parameters: |
//| X - spline abscissas, array[0..N-1] |
//| Y - spline ordinates, array[0..M-1] |
//| F - function values, array[0..M-1,0..N-1] |
//| M,N - grid size, M>=2, N>=2 |
//| Output parameters: |
//| C - spline interpolant |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuildBicubic(double &cx[],double &cy[],
CMatrixDouble &cf,const int m,
const int n,CSpline2DInterpolant &c)
{
//--- create variables
int sfx=0;
int sfy=0;
int sfxy=0;
CMatrixDouble dx;
CMatrixDouble dy;
CMatrixDouble dxy;
double t=0;
int k=0;
//--- copy
CMatrixDouble f=cf;
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
return;
if(!CAp::Assert(CAp::Len(cx)>=n && CAp::Len(cy)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(cx,n) && CApServ::IsFiniteVector(cy,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
if(!CAp::Assert(CAp::Rows(f)>=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)<M or cols(F)<N)"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(f,m,n),__FUNCTION__+": F contains NaN or Infinite value"))
return;
//---Fill interpolant:
//--- F[0]...F[N*M-1]:
//--- f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
//--- F[N*M]...F[2*N*M-1]:
//--- df(i,j)/dx table.
//--- F[2*N*M]...F[3*N*M-1]:
//--- df(i,j)/dy table.
//--- F[3*N*M]...F[4*N*M-1]:
//--- d2f(i,j)/dxdy table.
c.m_d=1;
c.m_n=n;
c.m_m=m;
c.m_stype=-3;
sfx=c.m_n*c.m_m;
sfy=2*c.m_n*c.m_m;
sfxy=3*c.m_n*c.m_m;
c.m_x=cx;
c.m_y=cy;
c.m_x.Resize(c.m_n);
c.m_y.Resize(c.m_m);
c.m_f.Resize(4*c.m_n*c.m_m);
//---Sort points
for(int j=0; j<c.m_n; j++)
{
k=j;
for(int i=j+1; i<c.m_n; i++)
if(c.m_x[i]<c.m_x[k])
k=i;
if(k!=j)
{
f.SwapCols(j,k);
c.m_x.Swap(j,k);
}
}
for(int i=0; i<c.m_m; i++)
{
k=i;
for(int j=i+1; j<c.m_m; j++)
if(c.m_y[j]<c.m_y[k])
k=j;
if(k!=i)
{
f.SwapRows(i,k);
c.m_y.Swap(i,k);
}
}
BicubicCalcDerivatives(f,c.m_x,c.m_y,c.m_m,c.m_n,dx,dy,dxy);
for(int i=0; i<c.m_m; i++)
for(int j=0; j<c.m_n; j++)
{
k=i*c.m_n+j;
c.m_f.Set(k,f.Get(i,j));
c.m_f.Set(sfx+k,dx.Get(i,j));
c.m_f.Set(sfy+k,dy.Get(i,j));
c.m_f.Set(sfxy+k,dxy.Get(i,j));
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates the value of the bilinear or bicubic |
//| spline at the given point X. |
//| Input parameters: |
//| C - coefficients table. |
//| Built by BuildBilinearSpline or BuildBicubicSpline. |
//| X, Y- point |
//| Result: |
//| S(x,y) |
//+------------------------------------------------------------------+
double CSpline2D::Spline2DCalc(CSpline2DInterpolant &c,const double x,
const double y)
{
//--- create variables
double result=0;
int ix=0;
int iy=0;
int l=0;
int r=0;
int h=0;
double t=0;
double dt=0;
double u=0;
double du=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double t2=0;
double t3=0;
double u2=0;
double u3=0;
double ht00=0;
double ht01=0;
double ht10=0;
double ht11=0;
double hu00=0;
double hu01=0;
double hu10=0;
double hu11=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return(0);
if(c.m_d!=1)
{
result=0;
return(result);
}
//--- Determine evaluation interval
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
t=(x-c.m_x[l])*dt;
ix=l;
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
du=1.0/(c.m_y[l+1]-c.m_y[l]);
u=(y-c.m_y[l])*du;
iy=l;
//--- Bilinear interpolation
if(c.m_stype==-1)
{
y1=c.m_f[c.m_n*iy+ix];
y2=c.m_f[c.m_n*iy+(ix+1)];
y3=c.m_f[c.m_n*(iy+1)+(ix+1)];
y4=c.m_f[c.m_n*(iy+1)+ix];
result=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
//--- return result
return(result);
}
//--- Bicubic interpolation:
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
//--- Same for HUij.
//--- * after initial calculation, apply scaling by DT/DU to the basis
//--- * calculate using stored table of second derivatives
//--- check
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
return(0);
sfx=c.m_n*c.m_m;
sfy=2*c.m_n*c.m_m;
sfxy=3*c.m_n*c.m_m;
s1=c.m_n*iy+ix;
s2=c.m_n*iy+(ix+1);
s3=c.m_n*(iy+1)+ix;
s4=c.m_n*(iy+1)+(ix+1);
t2=t*t;
t3=t*t2;
u2=u*u;
u3=u*u2;
ht00=2*t3-3*t2+1;
ht10=t3-2*t2+t;
ht01=-(2*t3)+3*t2;
ht11=t3-t2;
hu00=2*u3-3*u2+1;
hu10=u3-2*u2+u;
hu01=-(2*u3)+3*u2;
hu11=u3-u2;
ht10=ht10/dt;
ht11=ht11/dt;
hu10=hu10/du;
hu11=hu11/du;
result=c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01;
result+=c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01;
result+=c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11;
result+=c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This subroutine calculates the value of the bilinear or bicubic |
//| spline at the given point X and its derivatives. |
//| Input parameters: |
//| C - spline interpolant. |
//| X, Y- point |
//| Output parameters: |
//| F - S(x,y) |
//| FX - dS(x,y)/dX |
//| FY - dS(x,y)/dY |
//| FXY - d2S(x,y)/dXdY |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DDiff(CSpline2DInterpolant &c,const double x,
const double y,double &f,double &fx,
double &fy,double &fxy)
{
//--- create variables
double t=0;
double dt=0;
double u=0;
double du=0;
int ix=0;
int iy=0;
int l=0;
int r=0;
int h=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
double v0=0;
double v1=0;
double v2=0;
double v3=0;
double t2=0;
double t3=0;
double u2=0;
double u3=0;
double ht00=0;
double ht01=0;
double ht10=0;
double ht11=0;
double hu00=0;
double hu01=0;
double hu10=0;
double hu11=0;
double dht00=0;
double dht01=0;
double dht10=0;
double dht11=0;
double dhu00=0;
double dhu01=0;
double dhu10=0;
double dhu11=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
//--- Prepare F, dF/dX, dF/dY, d2F/dXdY
f=0;
fx=0;
fy=0;
fxy=0;
if(c.m_d!=1)
return;
//--- Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
t=(x-c.m_x[l])/(c.m_x[l+1]-c.m_x[l]);
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
ix=l;
//--- Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
u=(y-c.m_y[l])/(c.m_y[l+1]-c.m_y[l]);
du=1.0/(c.m_y[l+1]-c.m_y[l]);
iy=l;
//--- Bilinear interpolation
if(c.m_stype==-1)
{
y1=c.m_f[c.m_n*iy+ix];
y2=c.m_f[c.m_n*iy+(ix+1)];
y3=c.m_f[c.m_n*(iy+1)+(ix+1)];
y4=c.m_f[c.m_n*(iy+1)+ix];
f=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
fx=(-((1-u)*y1)+(1-u)*y2+u*y3-u*y4)*dt;
fy=(-((1-t)*y1)-t*y2+t*y3+(1-t)*y4)*du;
fxy=(y1-y2+y3-y4)*du*dt;
//--- exit the function
return;
}
//--- Bicubic interpolation
if(c.m_stype==-3)
{
sfx=c.m_n*c.m_m;
sfy=2*c.m_n*c.m_m;
sfxy=3*c.m_n*c.m_m;
s1=c.m_n*iy+ix;
s2=c.m_n*iy+(ix+1);
s3=c.m_n*(iy+1)+ix;
s4=c.m_n*(iy+1)+(ix+1);
t2=t*t;
t3=t*t2;
u2=u*u;
u3=u*u2;
ht00=2*t3-3*t2+1;
ht10=t3-2*t2+t;
ht01=-(2*t3)+3*t2;
ht11=t3-t2;
hu00=2*u3-3*u2+1;
hu10=u3-2*u2+u;
hu01=-(2*u3)+3*u2;
hu11=u3-u2;
ht10=ht10/dt;
ht11=ht11/dt;
hu10=hu10/du;
hu11=hu11/du;
dht00=6*t2-6*t;
dht10=3*t2-4*t+1;
dht01=-(6*t2)+6*t;
dht11=3*t2-2*t;
dhu00=6*u2-6*u;
dhu10=3*u2-4*u+1;
dhu01=-(6*u2)+6*u;
dhu11=3*u2-2*u;
dht00=dht00*dt;
dht01=dht01*dt;
dhu00=dhu00*du;
dhu01=dhu01*du;
v0=c.m_f[s1];
v1=c.m_f[s2];
v2=c.m_f[s3];
v3=c.m_f[s4];
f=v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
fx=v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
fy=v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
fxy=v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
v0=c.m_f[sfx+s1];
v1=c.m_f[sfx+s2];
v2=c.m_f[sfx+s3];
v3=c.m_f[sfx+s4];
f+=v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
fx+=v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
fy+=v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
fxy+=v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
v0=c.m_f[sfy+s1];
v1=c.m_f[sfy+s2];
v2=c.m_f[sfy+s3];
v3=c.m_f[sfy+s4];
f+=v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
fx+=v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
fy+=v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
fxy+=v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
v0=c.m_f[sfxy+s1];
v1=c.m_f[sfxy+s2];
v2=c.m_f[sfxy+s3];
v3=c.m_f[sfxy+s4];
f+=v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
fx+=v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
fy+=v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
fxy+=v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
//--- exit the function
return;
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates bilinear or bicubic vector-valued |
//| spline at the given point (X,Y). |
//| If you need just some specific component of vector-valued spline,|
//| you can use Spline2DCalcVi() function. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y - point |
//| F - output buffer, possibly preallocated array. In case|
//| array size is large enough to store result, it is |
//| not reallocated. Array which is too short will be |
//| reallocated |
//| OUTPUT PARAMETERS: |
//| F - array[D] (or larger) which stores function values |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DCalcVBuf(CSpline2DInterpolant &c,
double x,
double y,
CRowDouble &f)
{
//--- create variables
int ix=0;
int iy=0;
int l=0;
int r=0;
int h=0;
double t=0;
double dt=0;
double u=0;
double du=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double t2=0;
double t3=0;
double u2=0;
double u3=0;
double ht00=0;
double ht01=0;
double ht10=0;
double ht11=0;
double hu00=0;
double hu01=0;
double hu10=0;
double hu11=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
//--- Allocate place for output
CApServ::RVectorSetLengthAtLeast(f,c.m_d);
//--- Determine evaluation interval
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
t=(x-c.m_x[l])*dt;
ix=l;
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
du=1.0/(c.m_y[l+1]-c.m_y[l]);
u=(y-c.m_y[l])*du;
iy=l;
//--- Bilinear interpolation
if(c.m_stype==-1)
{
for(int i=0; i<c.m_d; i++)
{
y1=c.m_f[c.m_d*(c.m_n*iy+ix)+i];
y2=c.m_f[c.m_d*(c.m_n*iy+(ix+1))+i];
y3=c.m_f[c.m_d*(c.m_n*(iy+1)+(ix+1))+i];
y4=c.m_f[c.m_d*(c.m_n*(iy+1)+ix)+i];
f.Set(i,(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4);
}
return;
}
//--- Bicubic interpolation:
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
//--- Same for HUij.
//--- * after initial calculation, apply scaling by DT/DU to the basis
//--- * calculate using stored table of second derivatives
//--- check
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
return;
sfx=c.m_n*c.m_m*c.m_d;
sfy=2*c.m_n*c.m_m*c.m_d;
sfxy=3*c.m_n*c.m_m*c.m_d;
s1=(c.m_n*iy+ix)*c.m_d;
s2=(c.m_n*iy+(ix+1))*c.m_d;
s3=(c.m_n*(iy+1)+ix)*c.m_d;
s4=(c.m_n*(iy+1)+(ix+1))*c.m_d;
t2=t*t;
t3=t*t2;
u2=u*u;
u3=u*u2;
ht00=2*t3-3*t2+1;
ht10=t3-2*t2+t;
ht01=-(2*t3)+3*t2;
ht11=t3-t2;
hu00=2*u3-3*u2+1;
hu10=u3-2*u2+u;
hu01=-(2*u3)+3*u2;
hu11=u3-u2;
ht10=ht10/dt;
ht11=ht11/dt;
hu10=hu10/du;
hu11=hu11/du;
for(int i=0; i<c.m_d; i++)
{
//--- Calculate I-th component
f.Set(i,c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01);
f.Add(i,c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01);
f.Add(i,c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11);
f.Add(i,c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11);
//--- Advance source indexes
s1=s1+1;
s2=s2+1;
s3=s3+1;
s4=s4+1;
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates specific component of vector-valued |
//| bilinear or bicubic spline at the given point (X,Y). |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y - point |
//| I - component index, in [0,D). An exception is |
//| generated for out of range values. |
//| RESULT: |
//| value of I-th component |
//+------------------------------------------------------------------+
double CSpline2D::Spline2DCalcVi(CSpline2DInterpolant &c,
double x,
double y,
int i)
{
//--- create variables
double result=0;
int ix=0;
int iy=0;
int l=0;
int r=0;
int h=0;
double t=0;
double dt=0;
double u=0;
double du=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double t2=0;
double t3=0;
double u2=0;
double u3=0;
double ht00=0;
double ht01=0;
double ht10=0;
double ht11=0;
double hu00=0;
double hu01=0;
double hu10=0;
double hu11=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return(0);
if(!CAp::Assert(i>=0 && i<c.m_d,__FUNCTION__+": incorrect I (I<0 or I>=D)"))
return(0);
//--- Determine evaluation interval
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
t=(x-c.m_x[l])*dt;
ix=l;
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
du=1.0/(c.m_y[l+1]-c.m_y[l]);
u=(y-c.m_y[l])*du;
iy=l;
//--- Bilinear interpolation
if(c.m_stype==-1)
{
y1=c.m_f[c.m_d*(c.m_n*iy+ix)+i];
y2=c.m_f[c.m_d*(c.m_n*iy+(ix+1))+i];
y3=c.m_f[c.m_d*(c.m_n*(iy+1)+(ix+1))+i];
y4=c.m_f[c.m_d*(c.m_n*(iy+1)+ix)+i];
result=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
//--- return result
return(result);
}
//--- Bicubic interpolation:
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
//--- Same for HUij.
//--- * after initial calculation, apply scaling by DT/DU to the basis
//--- * calculate using stored table of second derivatives
//--- check
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
return(0);
sfx=c.m_n*c.m_m*c.m_d;
sfy=2*c.m_n*c.m_m*c.m_d;
sfxy=3*c.m_n*c.m_m*c.m_d;
s1=(c.m_n*iy+ix)*c.m_d;
s2=(c.m_n*iy+(ix+1))*c.m_d;
s3=(c.m_n*(iy+1)+ix)*c.m_d;
s4=(c.m_n*(iy+1)+(ix+1))*c.m_d;
t2=t*t;
t3=t*t2;
u2=u*u;
u3=u*u2;
ht00=2*t3-3*t2+1;
ht10=t3-2*t2+t;
ht01=-(2*t3)+3*t2;
ht11=t3-t2;
hu00=2*u3-3*u2+1;
hu10=u3-2*u2+u;
hu01=-(2*u3)+3*u2;
hu11=u3-u2;
ht10=ht10/dt;
ht11=ht11/dt;
hu10=hu10/du;
hu11=hu11/du;
//--- Advance source indexes to I-th position
s1=s1+i;
s2=s2+i;
s3=s3+i;
s4=s4+i;
//--- Calculate I-th component
result=c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01;
result+=c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01;
result+=c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11;
result+=c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This subroutine calculates bilinear or bicubic vector-valued |
//| spline at the given point (X,Y). |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y - point |
//| OUTPUT PARAMETERS: |
//| F - array[D] which stores function values. F is |
//| out-parameter and it is reallocated after call to |
//| this function. In case you want to reuse previously|
//| allocated F, you may use Spline2DCalcVBuf(), which |
//| reallocates F only when it is too small. |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DCalcV(CSpline2DInterpolant &c,
double x,
double y,
CRowDouble &f)
{
f.Resize(0);
Spline2DCalcVBuf(c,x,y,f);
}
//+------------------------------------------------------------------+
//| This subroutine calculates value of specific component of |
//| bilinear or bicubic vector-valued spline and its derivatives. |
//| Input parameters: |
//| C - spline interpolant. |
//| X, Y - point |
//| I - component index, in [0,D) |
//| Output parameters: |
//| F - S(x,y) |
//| FX - dS(x,y)/dX |
//| FY - dS(x,y)/dY |
//| FXY - d2S(x,y)/dXdY |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DDiffVi(CSpline2DInterpolant &c,
double x,
double y,
int i,
double &f,
double &fx,
double &fy,
double &fxy)
{
//--- create variables
int d=0;
double t=0;
double dt=0;
double u=0;
double du=0;
int ix=0;
int iy=0;
int l=0;
int r=0;
int h=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
double v0=0;
double v1=0;
double v2=0;
double v3=0;
double t2=0;
double t3=0;
double u2=0;
double u3=0;
double ht00=0;
double ht01=0;
double ht10=0;
double ht11=0;
double hu00=0;
double hu01=0;
double hu10=0;
double hu11=0;
double dht00=0;
double dht01=0;
double dht10=0;
double dht11=0;
double dhu00=0;
double dhu01=0;
double dhu10=0;
double dhu11=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
if(!CAp::Assert(i>=0 && i<c.m_d,__FUNCTION__+": I<0 or I>=D"))
return;
//--- Prepare F, dF/dX, dF/dY, d2F/dXdY
f=0;
fx=0;
fy=0;
fxy=0;
d=c.m_d;
//--- Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
t=(x-c.m_x[l])/(c.m_x[l+1]-c.m_x[l]);
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
ix=l;
//--- Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
u=(y-c.m_y[l])/(c.m_y[l+1]-c.m_y[l]);
du=1.0/(c.m_y[l+1]-c.m_y[l]);
iy=l;
//--- Bilinear interpolation
if(c.m_stype==-1)
{
y1=c.m_f[d*(c.m_n*iy+ix)+i];
y2=c.m_f[d*(c.m_n*iy+(ix+1))+i];
y3=c.m_f[d*(c.m_n*(iy+1)+(ix+1))+i];
y4=c.m_f[d*(c.m_n*(iy+1)+ix)+i];
f=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
fx=(-((1-u)*y1)+(1-u)*y2+u*y3-u*y4)*dt;
fy=(-((1-t)*y1)-t*y2+t*y3+(1-t)*y4)*du;
fxy=(y1-y2+y3-y4)*du*dt;
//--- exit
return;
}
//--- Bicubic interpolation
if(c.m_stype==-3)
{
sfx=c.m_n*c.m_m*d;
sfy=2*c.m_n*c.m_m*d;
sfxy=3*c.m_n*c.m_m*d;
s1=d*(c.m_n*iy+ix)+i;
s2=d*(c.m_n*iy+(ix+1))+i;
s3=d*(c.m_n*(iy+1)+ix)+i;
s4=d*(c.m_n*(iy+1)+(ix+1))+i;
t2=t*t;
t3=t*t2;
u2=u*u;
u3=u*u2;
ht00=2*t3-3*t2+1;
ht10=t3-2*t2+t;
ht01=-(2*t3)+3*t2;
ht11=t3-t2;
hu00=2*u3-3*u2+1;
hu10=u3-2*u2+u;
hu01=-(2*u3)+3*u2;
hu11=u3-u2;
ht10=ht10/dt;
ht11=ht11/dt;
hu10=hu10/du;
hu11=hu11/du;
dht00=6*t2-6*t;
dht10=3*t2-4*t+1;
dht01=-(6*t2)+6*t;
dht11=3*t2-2*t;
dhu00=6*u2-6*u;
dhu10=3*u2-4*u+1;
dhu01=-(6*u2)+6*u;
dhu11=3*u2-2*u;
dht00=dht00*dt;
dht01=dht01*dt;
dhu00=dhu00*du;
dhu01=dhu01*du;
v0=c.m_f[s1];
v1=c.m_f[s2];
v2=c.m_f[s3];
v3=c.m_f[s4];
f=v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
fx=v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
fy=v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
fxy=v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
v0=c.m_f[sfx+s1];
v1=c.m_f[sfx+s2];
v2=c.m_f[sfx+s3];
v3=c.m_f[sfx+s4];
f+=v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
fx+=v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
fy+=v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
fxy+=v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
v0=c.m_f[sfy+s1];
v1=c.m_f[sfy+s2];
v2=c.m_f[sfy+s3];
v3=c.m_f[sfy+s4];
f+=v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
fx+=v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
fy+=v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
fxy+=v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
v0=c.m_f[sfxy+s1];
v1=c.m_f[sfxy+s2];
v2=c.m_f[sfxy+s3];
v3=c.m_f[sfxy+s4];
f+=v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
fx+=v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
fy+=v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
fxy+=v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
//--- exit
return;
}
}
//+------------------------------------------------------------------+
//| This subroutine unpacks two-dimensional spline into the |
//| coefficients table |
//| Input parameters: |
//| C - spline interpolant. |
//| Result: |
//| M, N- grid size (x-axis and y-axis) |
//| Tbl - coefficients table, unpacked format, |
//| [0..(N-1)*(M-1)-1, 0..19]. |
//| For I = 0...M-2, J=0..N-2: |
//| K = I*(N-1)+J |
//| Tbl[K,0] = X[j] |
//| Tbl[K,1] = X[j+1] |
//| Tbl[K,2] = Y[i] |
//| Tbl[K,3] = Y[i+1] |
//| Tbl[K,4] = C00 |
//| Tbl[K,5] = C01 |
//| Tbl[K,6] = C02 |
//| Tbl[K,7] = C03 |
//| Tbl[K,8] = C10 |
//| Tbl[K,9] = C11 |
//| ... |
//| Tbl[K,19] = C33 |
//| On each grid square spline is equals to: |
//| S(x) = SUM(c[i,j]*(x^i)*(y^j), i=0..3, j=0..3) |
//| t = x-x[j] |
//| u = y-y[i] |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DUnpack(CSpline2DInterpolant &c,int &m,
int &n,CMatrixDouble &tbl)
{
//--- create variables
int k=0;
int p=0;
int ci=0;
int cj=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
double dt=0;
double du=0;
int i=0;
int j=0;
m=0;
n=0;
tbl.Resize(0,0);
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(c.m_d!=1)
return;
n=c.m_n;
m=c.m_m;
tbl.Resize((n-1)*(m-1),20);
sfx=n*m;
sfy=2*n*m;
sfxy=3*n*m;
//---Fill
for(i=0; i<m-1; i++)
{
for(j=0; j<n-1; j++)
{
p=i*(n-1)+j;
tbl.Set(p,0,c.m_x[j]);
tbl.Set(p,1,c.m_x[j+1]);
tbl.Set(p,2,c.m_y[i]);
tbl.Set(p,3,c.m_y[i+1]);
dt=1/(tbl.Get(p,1)-tbl.Get(p,0));
du=1/(tbl.Get(p,3)-tbl.Get(p,2));
//---Bilinear interpolation
if(c.m_stype==-1)
{
for(k=4; k<=19; k++)
tbl.Set(p,k,0);
y1=c.m_f[n*i+j];
y2=c.m_f[n*i+(j+1)];
y3=c.m_f[n*(i+1)+(j+1)];
y4=c.m_f[n*(i+1)+j];
tbl.Set(p,4,y1);
tbl.Set(p,4+1*4+0,y2-y1);
tbl.Set(p,4+0*4+1,y4-y1);
tbl.Set(p,4+1*4+1,y3-y2-y4+y1);
}
//---Bicubic interpolation
if(c.m_stype==-3)
{
s1=n*i+j;
s2=n*i+(j+1);
s3=n*(i+1)+(j+1);
s4=n*(i+1)+j;
tbl.Set(p,4+0*4+0,c.m_f[s1]);
tbl.Set(p,4+0*4+1,c.m_f[sfy+s1]/du);
tbl.Set(p,4+0*4+2,-(3*c.m_f[s1])+3*c.m_f[s4]-2*c.m_f[sfy+s1]/du-c.m_f[sfy+s4]/du);
tbl.Set(p,4+0*4+3,2*c.m_f[s1]-2*c.m_f[s4]+c.m_f[sfy+s1]/du+c.m_f[sfy+s4]/du);
tbl.Set(p,4+1*4+0,c.m_f[sfx+s1]/dt);
tbl.Set(p,4+1*4+1,c.m_f[sfxy+s1]/(dt*du));
tbl.Set(p,4+1*4+2,-(3*c.m_f[sfx+s1]/dt)+3*c.m_f[sfx+s4]/dt-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+1*4+3,2*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s4]/dt+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+2*4+0,-(3*c.m_f[s1])+3*c.m_f[s2]-2*c.m_f[sfx+s1]/dt-c.m_f[sfx+s2]/dt);
tbl.Set(p,4+2*4+1,-(3*c.m_f[sfy+s1]/du)+3*c.m_f[sfy+s2]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du));
tbl.Set(p,4+2*4+2,9*c.m_f[s1]-9*c.m_f[s2]+9*c.m_f[s3]-9*c.m_f[s4]+6*c.m_f[sfx+s1]/dt+3*c.m_f[sfx+s2]/dt-3*c.m_f[sfx+s3]/dt-6*c.m_f[sfx+s4]/dt+6*c.m_f[sfy+s1]/du-6*c.m_f[sfy+s2]/du-3*c.m_f[sfy+s3]/du+3*c.m_f[sfy+s4]/du+4*c.m_f[sfxy+s1]/(dt*du)+2*c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+2*c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+2*4+3,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-4*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s2]/dt+2*c.m_f[sfx+s3]/dt+4*c.m_f[sfx+s4]/dt-3*c.m_f[sfy+s1]/du+3*c.m_f[sfy+s2]/du+3*c.m_f[sfy+s3]/du-3*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-2*c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+3*4+0,2*c.m_f[s1]-2*c.m_f[s2]+c.m_f[sfx+s1]/dt+c.m_f[sfx+s2]/dt);
tbl.Set(p,4+3*4+1,2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du));
tbl.Set(p,4+3*4+2,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-3*c.m_f[sfx+s1]/dt-3*c.m_f[sfx+s2]/dt+3*c.m_f[sfx+s3]/dt+3*c.m_f[sfx+s4]/dt-4*c.m_f[sfy+s1]/du+4*c.m_f[sfy+s2]/du+2*c.m_f[sfy+s3]/du-2*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-2*c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+3*4+3,4*c.m_f[s1]-4*c.m_f[s2]+4*c.m_f[s3]-4*c.m_f[s4]+2*c.m_f[sfx+s1]/dt+2*c.m_f[sfx+s2]/dt-2*c.m_f[sfx+s3]/dt-2*c.m_f[sfx+s4]/dt+2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du-2*c.m_f[sfy+s3]/du+2*c.m_f[sfy+s4]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
}
//---Rescale Cij
for(ci=0; ci<=3; ci++)
for(cj=0; cj<=3; cj++)
tbl.Mul(p,4+ci*4+cj,MathPow(dt,ci)*MathPow(du,cj));
}
}
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline |
//| argument. |
//| Input parameters: |
//| C - spline interpolant |
//| AX, BX - transformation coefficients: x = A*t + B |
//| AY, BY - transformation coefficients: y = A*u + B |
//| Result: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DLinTransXY(CSpline2DInterpolant &c,double ax,
double bx,double ay,double by)
{
//--- create variables
CRowDouble x;
CRowDouble y;
CRowDouble f;
CRowDouble v;
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(ax),__FUNCTION__+": AX is infinite or NaN"))
return;
if(!CAp::Assert(MathIsValidNumber(bx),__FUNCTION__+": BX is infinite or NaN"))
return;
if(!CAp::Assert(MathIsValidNumber(ay),__FUNCTION__+": AY is infinite or NaN"))
return;
if(!CAp::Assert(MathIsValidNumber(by),__FUNCTION__+": BY is infinite or NaN"))
return;
x=c.m_x;
y=c.m_y;
f=c.m_f;
x.Resize(c.m_n);
y.Resize(c.m_m);
f.Resize(c.m_m*c.m_n*c.m_d);
//--- Handle different combinations of AX/AY
if(ax==0.0 && ay!=0.0)
{
for(int i=0; i<c.m_m; i++)
{
Spline2DCalcVBuf(c,bx,y[i],v);
y.Set(i,(y[i]-by)/ay);
for(int j=0; j<c.m_n; j++)
for(int k=0; k<c.m_d; k++)
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
}
}
if(ax!=0.0 && ay==0.0)
{
for(int j=0; j<c.m_n; j++)
{
Spline2DCalcVBuf(c,x[j],by,v);
x.Set(j,(x[j]-bx)/ax);
for(int i=0; i<c.m_m; i++)
for(int k=0; k<c.m_d; k++)
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
}
}
if(ax!=0.0 && ay!=0.0)
{
for(int j=0; j<c.m_n; j++)
x.Set(j,(x[j]-bx)/ax);
for(int i=0; i<c.m_m; i++)
y.Set(i,(y[i]-by)/ay);
}
if(ax==0.0 && ay==0.0)
{
Spline2DCalcVBuf(c,bx,by,v);
for(int i=0; i<c.m_m; i++)
for(int j=0; j<c.m_n; j++)
for(int k=0; k<c.m_d; k++)
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
}
//--- Rebuild spline
if(c.m_stype==-3)
Spline2DBuildBicubicV(x,c.m_n,y,c.m_m,f,c.m_d,c);
if(c.m_stype==-1)
Spline2DBuildBilinearV(x,c.m_n,y,c.m_m,f,c.m_d,c);
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline. |
//| Input parameters: |
//| C - spline interpolant. |
//| A, B- transformation coefficients: S2(x,y) = A*S(x,y) + B |
//| Output parameters: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DLinTransF(CSpline2DInterpolant &c,const double a,
const double b)
{
//--- create variables
CRowDouble x;
CRowDouble y;
CRowDouble f;
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
x=c.m_x;
y=c.m_y;
f=c.m_f*a+b;
x.Resize(c.m_n);
y.Resize(c.m_m);
f.Resize(c.m_m*c.m_n*c.m_d);
if(c.m_stype==-3)
Spline2DBuildBicubicV(x,c.m_n,y,c.m_m,f,c.m_d,c);
if(c.m_stype==-1)
Spline2DBuildBilinearV(x,c.m_n,y,c.m_m,f,c.m_d,c);
}
//+------------------------------------------------------------------+
//| This subroutine makes the copy of the spline model. |
//| Input parameters: |
//| C - spline interpolant |
//| Output parameters: |
//| CC - spline copy |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DCopy(CSpline2DInterpolant &c,CSpline2DInterpolant &cc)
{
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
cc.m_n=c.m_n;
cc.m_m=c.m_m;
cc.m_d=c.m_d;
cc.m_stype=c.m_stype;
cc.m_x=c.m_x;
cc.m_y=c.m_y;
cc.m_f=c.m_f;
}
//+------------------------------------------------------------------+
//| Bicubic spline resampling |
//| Input parameters: |
//| A - function values at the old grid, |
//| array[0..OldHeight-1, 0..OldWidth-1] |
//| OldHeight - old grid height, OldHeight>1 |
//| OldWidth - old grid width, OldWidth>1 |
//| NewHeight - new grid height, NewHeight>1 |
//| NewWidth - new grid width, NewWidth>1 |
//| Output parameters: |
//| B - function values at the new grid, |
//| array[0..NewHeight-1, 0..NewWidth-1] |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DResampleBicubic(CMatrixDouble &a,const int oldheight,
const int oldwidth,CMatrixDouble &b,
const int newheight,const int newwidth)
{
//--- create variables
int mw=0;
int mh=0;
//--- create arrays
double x[];
double y[];
//--- create matrix
CMatrixDouble buf;
//--- object of class
CSpline1DInterpolant c;
//--- check
if(!CAp::Assert(oldwidth>1 && oldheight>1,__FUNCTION__+": width/height less than 1"))
return;
//--- check
if(!CAp::Assert(newwidth>1 && newheight>1,__FUNCTION__+": width/height less than 1"))
return;
//--- Prepare
mw=MathMax(oldwidth,newwidth);
mh=MathMax(oldheight,newheight);
//--- allocation
b.Resize(newheight,newwidth);
buf.Resize(oldheight,newwidth);
ArrayResize(x,MathMax(mw,mh));
ArrayResize(y,MathMax(mw,mh));
//--- Horizontal interpolation
for(int i=0; i<oldheight; i++)
{
//--- Fill X,Y
for(int j=0; j<oldwidth; j++)
{
x[j]=(double)j/(double)(oldwidth-1);
y[j]=a.Get(i,j);
}
//--- Interpolate and place result into temporary matrix
CSpline1D::Spline1DBuildCubic(x,y,oldwidth,0,0.0,0,0.0,c);
for(int j=0; j<newwidth; j++)
buf.Set(i,j,CSpline1D::Spline1DCalc(c,(double)j/(double)(newwidth-1)));
}
//--- Vertical interpolation
for(int j=0; j<newwidth; j++)
{
//--- Fill X,Y
for(int i=0; i<oldheight; i++)
{
x[i]=(double)i/(double)(oldheight-1);
y[i]=buf.Get(i,j);
}
//--- Interpolate and place result into B
CSpline1D::Spline1DBuildCubic(x,y,oldheight,0,0.0,0,0.0,c);
for(int i=0; i<newheight; i++)
b.Set(i,j,CSpline1D::Spline1DCalc(c,(double)i/(double)(newheight-1)));
}
}
//+------------------------------------------------------------------+
//| Bilinear spline resampling |
//| Input parameters: |
//| A - function values at the old grid, |
//| array[0..OldHeight-1, 0..OldWidth-1] |
//| OldHeight - old grid height, OldHeight>1 |
//| OldWidth - old grid width, OldWidth>1 |
//| NewHeight - new grid height, NewHeight>1 |
//| NewWidth - new grid width, NewWidth>1 |
//| Output parameters: |
//| B - function values at the new grid, |
//| array[0..NewHeight-1, 0..NewWidth-1] |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DResampleBilinear(CMatrixDouble &a,const int oldheight,
const int oldwidth,CMatrixDouble &b,
const int newheight,const int newwidth)
{
//--- create variables
int l=0;
int c=0;
double t=0;
double u=0;
//--- check
if(!CAp::Assert(oldwidth>1 && oldheight>1,__FUNCTION__+": width/height less than 1"))
return;
if(!CAp::Assert(newwidth>1 && newheight>1,__FUNCTION__+": width/height less than 1"))
return;
//--- allocation
b.Resize(newheight,newwidth);
for(int i=0; i<newheight; i++)
{
for(int j=0; j<newwidth; j++)
{
//--- calculation
l=i*(oldheight-1)/(newheight-1);
//--- check
if(l==oldheight-1)
l=oldheight-2;
//--- calculation
u=(double)i/(double)(newheight-1)*(oldheight-1)-l;
c=j*(oldwidth-1)/(newwidth-1);
//--- check
if(c==oldwidth-1)
c=oldwidth-2;
//--- calculation
t=(double)(j*(oldwidth-1))/(double)(newwidth-1)-c;
b.Set(i,j,(1-t)*(1-u)*a.Get(l,c)+t*(1-u)*a.Get(l,c+1)+t*u*a.Get(l+1,c+1)+(1-t)*u*a.Get(l+1,c));
}
}
}
//+------------------------------------------------------------------+
//| This subroutine builds bilinear vector-valued spline. |
//| Input parameters: |
//| X - spline abscissas, array[0..N-1] |
//| Y - spline ordinates, array[0..M-1] |
//| F - function values, array[0..M*N*D-1]: |
//| * first D elements store D values at (X[0],Y[0]) |
//| * next D elements store D values at (X[1],Y[0]) |
//| * general form - D function values at (X[i],Y[j]) |
//| are stored at F[D*(J*N+I)...D*(J*N+I)+D-1]. |
//| M,N - grid size, M>=2, N>=2 |
//| D - vector dimension, D>=1 |
//| Output parameters: |
//| C - spline interpolant |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuildBilinearV(CRowDouble &x,int n,
CRowDouble &y,
int m,
CRowDouble &f,
int d,
CSpline2DInterpolant &c)
{
//--- create variables
double t=0;
int k=0;
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N is less then 2"))
return;
if(!CAp::Assert(m>=2,__FUNCTION__+": M is less then 2"))
return;
if(!CAp::Assert(d>=1,__FUNCTION__+": invalid argument D (D<1)"))
return;
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
k=n*m*d;
//--- check
if(!CAp::Assert(CAp::Len(f)>=k,__FUNCTION__+": length of F is too short (Length(F)<N*M*D)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(f,k),__FUNCTION__+": F contains NaN or Infinite value"))
return;
//--- Fill interpolant
c.m_n=n;
c.m_m=m;
c.m_d=d;
c.m_stype=-1;
c.m_x=x;
c.m_y=y;
c.m_f=f;
c.m_x.Resize(c.m_n);
c.m_y.Resize(c.m_m);
c.m_f.Resize(k);
//--- Sort points
for(int j=0; j<c.m_n; j++)
{
k=j;
for(int i=j+1; i<c.m_n; i++)
if(c.m_x[i]<c.m_x[k])
k=i;
if(k!=j)
{
for(int i=0; i<c.m_m; i++)
for(int i0=0; i0<c.m_d; i0++)
c.m_f.Swap(c.m_d*(i*c.m_n+j)+i0,c.m_d*(i*c.m_n+k)+i0);
c.m_x.Swap(j,k);
}
}
for(int i=0; i<c.m_m; i++)
{
k=i;
for(int j=i+1; j<c.m_m; j++)
if(c.m_y[j]<c.m_y[k])
k=j;
if(k!=i)
{
for(int j=0; j<c.m_n; j++)
for(int i0=0; i0<=c.m_d-1; i0++)
c.m_f.Swap(c.m_d*(i*c.m_n+j)+i0,c.m_d*(k*c.m_n+j)+i0);
c.m_y.Swap(i,k);
}
}
}
//+------------------------------------------------------------------+
//| This subroutine builds bicubic vector-valued spline. |
//| Input parameters: |
//| X - spline abscissas, array[0..N-1] |
//| Y - spline ordinates, array[0..M-1] |
//| F - function values, array[0..M*N*D-1]: |
//| * first D elements store D values at (X[0],Y[0]) |
//| * next D elements store D values at (X[1],Y[0]) |
//| * general form - D function values at (X[i],Y[j]) |
//| are stored at F[D*(J*N+I)...D*(J*N+I)+D-1]. |
//| M,N - grid size, M>=2, N>=2 |
//| D - vector dimension, D>=1 |
//| Output parameters: |
//| C - spline interpolant |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuildBicubicV(CRowDouble &x,
int n,
CRowDouble &y,
int m,
CRowDouble &F,
int d,
CSpline2DInterpolant &c)
{
//--- create variables
CMatrixDouble tf;
CMatrixDouble dx;
CMatrixDouble dy;
CMatrixDouble dxy;
double t=0;
int i=0;
int j=0;
int k=0;
int di=0;
//--- copy
CRowDouble f=F;
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N is less than 2"))
return;
if(!CAp::Assert(m>=2,__FUNCTION__+": M is less than 2"))
return;
if(!CAp::Assert(d>=1,__FUNCTION__+": invalid argument D (D<1)"))
return;
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
k=n*m*d;
//--- check
if(!CAp::Assert(CAp::Len(f)>=k,__FUNCTION__+": length of F is too short (Length(F)<N*M*D)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(f,k),__FUNCTION__+": F contains NaN or Infinite value"))
return;
//--- Fill interpolant:
//--- F[0]...F[N*M*D-1]:
//--- f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
//--- F[N*M*D]...F[2*N*M*D-1]:
//--- df(i,j)/dx table.
//--- F[2*N*M*D]...F[3*N*M*D-1]:
//--- df(i,j)/dy table.
//--- F[3*N*M*D]...F[4*N*M*D-1]:
//--- d2f(i,j)/dxdy table.
c.m_d=d;
c.m_n=n;
c.m_m=m;
c.m_stype=-3;
k=4*k;
c.m_x=x;
c.m_y=y;
c.m_x.Resize(c.m_n);
c.m_y.Resize(c.m_m);
c.m_f.Resize(k);
tf.Resize(c.m_m,c.m_n);
//--- Sort points
for(j=0; j<c.m_n; j++)
{
k=j;
for(i=j+1; i<c.m_n; i++)
if(c.m_x[i]<c.m_x[k])
k=i;
if(k!=j)
{
for(i=0; i<c.m_m; i++)
for(di=0; di<c.m_d; di++)
f.Swap(c.m_d*(i*c.m_n+j)+di,c.m_d*(i*c.m_n+k)+di);
c.m_x.Swap(j,k);
}
}
for(i=0; i<c.m_m; i++)
{
k=i;
for(j=i+1; j<c.m_m; j++)
if(c.m_y[j]<c.m_y[k])
k=j;
if(k!=i)
{
for(j=0; j<c.m_n; j++)
for(di=0; di<=c.m_d-1; di++)
f.Swap(c.m_d*(i*c.m_n+j)+di,c.m_d*(k*c.m_n+j)+di);
c.m_y.Swap(i,k);
}
}
for(di=0; di<c.m_d; di++)
{
for(i=0; i<c.m_m; i++)
for(j=0; j<c.m_n; j++)
tf.Set(i,j,f[c.m_d*(i*c.m_n+j)+di]);
BicubicCalcDerivatives(tf,c.m_x,c.m_y,c.m_m,c.m_n,dx,dy,dxy);
for(i=0; i<c.m_m; i++)
{
for(j=0; j<c.m_n; j++)
{
k=c.m_d*(i*c.m_n+j)+di;
c.m_f.Set(k,tf.Get(i,j));
c.m_f.Set(c.m_n*c.m_m*c.m_d+k,dx.Get(i,j));
c.m_f.Set(2*c.m_n*c.m_m*c.m_d+k,dy.Get(i,j));
c.m_f.Set(3*c.m_n*c.m_m*c.m_d+k,dxy.Get(i,j));
}
}
}
}
//+------------------------------------------------------------------+
//| This subroutine unpacks two-dimensional spline into the |
//| coefficients table |
//| Input parameters: |
//| C - spline interpolant. |
//| Result: |
//| M, N - grid size (x-axis and y-axis) |
//| D - number of components |
//| Tbl - coefficients table, unpacked format, |
//| D - components: [0..(N-1)*(M-1)*D-1, 0..19]. |
//| For T=0..D-1 (component index), I = 0...N-2 (x index), J=0..M-2 |
//| (y index): |
//| K := T + I*D + J*D*(N-1) |
//| K-th row stores decomposition for T-th component of the |
//| vector-valued function |
//| Tbl[K,0] = X[i] |
//| Tbl[K,1] = X[i+1] |
//| Tbl[K,2] = Y[j] |
//| Tbl[K,3] = Y[j+1] |
//| Tbl[K,4] = C00 |
//| Tbl[K,5] = C01 |
//| Tbl[K,6] = C02 |
//| Tbl[K,7] = C03 |
//| Tbl[K,8] = C10 |
//| Tbl[K,9] = C11 |
//| ... |
//| Tbl[K,19] = C33 |
//| On each grid square spline is equals to: |
//| S(x) = SUM(c[i,j]*(t^i)*(u^j), i=0..3, j=0..3) |
//| t = x-x[j] |
//| u = y-y[i] |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DUnpackV(CSpline2DInterpolant &c,
int &m,
int &n,
int &d,
CMatrixDouble &tbl)
{
//--- create variables
int p=0;
int s1=0;
int s2=0;
int s3=0;
int s4=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double y1=0;
double y2=0;
double y3=0;
double y4=0;
double dt=0;
double du=0;
int k0=0;
m=0;
n=0;
d=0;
tbl.Resize(0,0);
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
n=c.m_n;
m=c.m_m;
d=c.m_d;
tbl.Resize((n-1)*(m-1)*d,20);
sfx=n*m*d;
sfy=2*n*m*d;
sfxy=3*n*m*d;
for(int i=0; i<m-1; i++)
{
for(int j=0; j<n-1; j++)
{
for(int k=0; k<d; k++)
{
p=d*(i*(n-1)+j)+k;
tbl.Set(p,0,c.m_x[j]);
tbl.Set(p,1,c.m_x[j+1]);
tbl.Set(p,2,c.m_y[i]);
tbl.Set(p,3,c.m_y[i+1]);
dt=1/(tbl.Get(p,1)-tbl.Get(p,0));
du=1/(tbl.Get(p,3)-tbl.Get(p,2));
//---Bilinear interpolation
if(c.m_stype==-1)
{
for(k0=4; k0<=19; k0++)
tbl.Set(p,k0,0);
y1=c.m_f[d*(n*i+j)+k];
y2=c.m_f[d*(n*i+(j+1))+k];
y3=c.m_f[d*(n*(i+1)+(j+1))+k];
y4=c.m_f[d*(n*(i+1)+j)+k];
tbl.Set(p,4,y1);
tbl.Set(p,4+1*4+0,y2-y1);
tbl.Set(p,4+0*4+1,y4-y1);
tbl.Set(p,4+1*4+1,y3-y2-y4+y1);
}
//---Bicubic interpolation
if(c.m_stype==-3)
{
s1=d*(n*i+j)+k;
s2=d*(n*i+(j+1))+k;
s3=d*(n*(i+1)+(j+1))+k;
s4=d*(n*(i+1)+j)+k;
tbl.Set(p,4+0*4+0,c.m_f[s1]);
tbl.Set(p,4+0*4+1,c.m_f[sfy+s1]/du);
tbl.Set(p,4+0*4+2,-(3*c.m_f[s1])+3*c.m_f[s4]-2*c.m_f[sfy+s1]/du-c.m_f[sfy+s4]/du);
tbl.Set(p,4+0*4+3,2*c.m_f[s1]-2*c.m_f[s4]+c.m_f[sfy+s1]/du+c.m_f[sfy+s4]/du);
tbl.Set(p,4+1*4+0,c.m_f[sfx+s1]/dt);
tbl.Set(p,4+1*4+1,c.m_f[sfxy+s1]/(dt*du));
tbl.Set(p,4+1*4+2,-(3*c.m_f[sfx+s1]/dt)+3*c.m_f[sfx+s4]/dt-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+1*4+3,2*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s4]/dt+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+2*4+0,-(3*c.m_f[s1])+3*c.m_f[s2]-2*c.m_f[sfx+s1]/dt-c.m_f[sfx+s2]/dt);
tbl.Set(p,4+2*4+1,-(3*c.m_f[sfy+s1]/du)+3*c.m_f[sfy+s2]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du));
tbl.Set(p,4+2*4+2,9*c.m_f[s1]-9*c.m_f[s2]+9*c.m_f[s3]-9*c.m_f[s4]+6*c.m_f[sfx+s1]/dt+3*c.m_f[sfx+s2]/dt-3*c.m_f[sfx+s3]/dt-6*c.m_f[sfx+s4]/dt+6*c.m_f[sfy+s1]/du-6*c.m_f[sfy+s2]/du-3*c.m_f[sfy+s3]/du+3*c.m_f[sfy+s4]/du+4*c.m_f[sfxy+s1]/(dt*du)+2*c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+2*c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+2*4+3,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-4*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s2]/dt+2*c.m_f[sfx+s3]/dt+4*c.m_f[sfx+s4]/dt-3*c.m_f[sfy+s1]/du+3*c.m_f[sfy+s2]/du+3*c.m_f[sfy+s3]/du-3*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-2*c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+3*4+0,2*c.m_f[s1]-2*c.m_f[s2]+c.m_f[sfx+s1]/dt+c.m_f[sfx+s2]/dt);
tbl.Set(p,4+3*4+1,2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du));
tbl.Set(p,4+3*4+2,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-3*c.m_f[sfx+s1]/dt-3*c.m_f[sfx+s2]/dt+3*c.m_f[sfx+s3]/dt+3*c.m_f[sfx+s4]/dt-4*c.m_f[sfy+s1]/du+4*c.m_f[sfy+s2]/du+2*c.m_f[sfy+s3]/du-2*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-2*c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
tbl.Set(p,4+3*4+3,4*c.m_f[s1]-4*c.m_f[s2]+4*c.m_f[s3]-4*c.m_f[s4]+2*c.m_f[sfx+s1]/dt+2*c.m_f[sfx+s2]/dt-2*c.m_f[sfx+s3]/dt-2*c.m_f[sfx+s4]/dt+2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du-2*c.m_f[sfy+s3]/du+2*c.m_f[sfy+s4]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
}
//---Rescale Cij
for(int ci=0; ci<=3; ci++)
for(int cj=0; cj<=3; cj++)
tbl.Mul(p,4+ci*4+cj,MathPow(dt,ci)*MathPow(du,cj));
}
}
}
}
//+------------------------------------------------------------------+
//| This subroutine creates least squares solver used to fit 2D |
//| splines to irregularly sampled (scattered) data. |
//| Solver object is used to perform spline fits as follows: |
//| * solver object is created with Spline2DBuilderCreate() |
//| function |
//| * dataset is added with Spline2DBuilderSetPoints() function |
//| * fit area is chosen: |
//| * Spline2DBuilderSetArea() - for user-defined area |
//| * Spline2DBuilderSetAreaAuto()- for automatically chosen |
//| area |
//| * number of grid nodes is chosen with Spline2DBuilderSetGrid() |
//| * prior term is chosen with one of the following functions: |
//| * Spline2DBuilderSetLinTerm() to set linear prior |
//| * Spline2DBuilderSetConstTerm() to set constant prior |
//| * Spline2DBuilderSetZeroTerm() to set zero prior |
//| * Spline2DBuilderSetUserTerm() to set user-defined constant |
//| prior |
//| * solver algorithm is chosen with either: |
//| * Spline2DBuilderSetAlgoBlockLLS() - BlockLLS algorithm, |
//| medium-scale problems|
//| * Spline2DBuilderSetAlgoFastDDM() - FastDDM algorithm, |
//| large-scale problems |
//| * finally, fitting itself is performed with Spline2DFit() |
//| function. |
//| Most of the steps above can be omitted, solver is configured with|
//| good defaults. The minimum is to call: |
//| * Spline2DBuilderCreate() to create solver object |
//| * Spline2DBuilderSetPoints() to specify dataset |
//| * Spline2DBuilderSetGrid() to tell how many nodes you need |
//| * Spline2DFit() to perform fit |
//| INPUT PARAMETERS: |
//| D - positive number, number of Y-components: D=1 for |
//| simple scalar fit, D>1 for vector-valued spline |
//| fitting. |
//| OUTPUT PARAMETERS: |
//| S - solver object |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderCreate(int d,CSpline2DBuilder &State)
{
//--- check
if(!CAp::Assert(d>=1,__FUNCTION__+": D<=0"))
return;
//---NOTES:
//---1. Prior term is set to linear one (good default option)
//---2. Solver is set to BlockLLS - good enough for small-scale problems.
//---3. Refinement rounds: 5; enough to get good convergence.
State.m_priorterm=1;
State.m_priortermval=0;
State.m_areatype=0;
State.m_gridtype=0;
State.m_smoothing=0.0;
State.m_nlayers=0;
State.m_solvertype=1;
State.m_npoints=0;
State.m_d=d;
State.m_sx=1.0;
State.m_sy=1.0;
State.m_lsqrcnt=5;
//---Algorithm settings
State.m_adddegreeoffreedom=true;
State.m_maxcoresize=16;
State.m_interfacesize=5;
}
//+------------------------------------------------------------------+
//| This function sets constant prior term (model is a sum of bicubic|
//| spline and global prior, which can be linear, constant, |
//| user-defined constant or zero). |
//| Constant prior term is determined by least squares fitting. |
//| INPUT PARAMETERS: |
//| S - spline builder |
//| V - value for user-defined prior |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetUserTerm(CSpline2DBuilder &State,
double v)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": infinite/NAN value passed"))
return;
State.m_priorterm=0;
State.m_priortermval=v;
}
//+------------------------------------------------------------------+
//| This function sets linear prior term (model is a sum of bicubic |
//| spline and global prior, which can be linear, constant, |
//| user-defined constant or zero). |
//| Linear prior term is determined by least squares fitting. |
//| INPUT PARAMETERS: |
//| S - spline builder |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetLinTerm(CSpline2DBuilder &State)
{
State.m_priorterm=1;
}
//+------------------------------------------------------------------+
//| This function sets constant prior term (model is a sum of bicubic|
//| spline and global prior, which can be linear, constant, |
//| user-defined constant or zero). |
//| Constant prior term is determined by least squares fitting. |
//| INPUT PARAMETERS: |
//| S - spline builder |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetConstTerm(CSpline2DBuilder &State)
{
State.m_priorterm=2;
}
//+------------------------------------------------------------------+
//| This function sets zero prior term (model is a sum of bicubic |
//| spline and global prior, which can be linear, constant, |
//| user-defined constant or zero). |
//| INPUT PARAMETERS: |
//| S - spline builder |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetZeroTerm(CSpline2DBuilder &State)
{
State.m_priorterm=3;
}
//+------------------------------------------------------------------+
//| This function adds dataset to the builder object. |
//| This function overrides results of the previous calls, i.e. |
//| multiple calls of this function will result in only the last |
//| set being added. |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| XY - points, array[N,2+D]. One row corresponds to one |
//| point in the dataset. First 2 elements are |
//| coordinates, next D elements are function values. |
//| Array may be larger than specified, in this case |
//| only leading [N,NX+NY] elements will be used. |
//| N - number of points in the dataset |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetPoints(CSpline2DBuilder &State,
CMatrixDouble &xy,
int n)
{
int ew=0;
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<0"))
return;
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
return;
if(!CAp::Assert(CAp::Cols(xy)>=2+State.m_d,__FUNCTION__+": Cols(XY)<NX+NY"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,2+State.m_d),__FUNCTION__+": XY contains infinite or NaN values!"))
return;
State.m_npoints=n;
ew=2+State.m_d;
CApServ::RVectorSetLengthAtLeast(State.m_xy,n*ew);
for(int i=0; i<n; i++)
for(int j=0; j<ew; j++)
State.m_xy.Set(i*ew+j,xy.Get(i,j));
}
//+------------------------------------------------------------------+
//| This function sets area where 2D spline interpolant is built. |
//| "Auto" means that area extent is determined automatically from |
//| dataset extent. |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetAreaAuto(CSpline2DBuilder &State)
{
State.m_areatype=0;
}
//+------------------------------------------------------------------+
//| This function sets area where 2D spline interpolant is built to |
//| user-defined one: [XA,XB]*[YA,YB] |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| XA,XB - spatial extent in the first (X) dimension, XA<XB |
//| YA,YB - spatial extent in the second (Y) dimension, YA<YB |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetArea(CSpline2DBuilder &State,
double xa,
double xb,
double ya,
double yb)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(xa),__FUNCTION__+": XA is not finite"))
return;
if(!CAp::Assert(MathIsValidNumber(xb),__FUNCTION__+": XB is not finite"))
return;
if(!CAp::Assert(MathIsValidNumber(ya),__FUNCTION__+": YA is not finite"))
return;
if(!CAp::Assert(MathIsValidNumber(yb),__FUNCTION__+": YB is not finite"))
return;
if(!CAp::Assert(xa<xb,__FUNCTION__+": XA>=XB"))
return;
if(!CAp::Assert(ya<yb,__FUNCTION__+": YA>=YB"))
return;
State.m_areatype=1;
State.m_xa=xa;
State.m_xb=xb;
State.m_ya=ya;
State.m_yb=yb;
}
//+------------------------------------------------------------------+
//| This function sets nodes count for 2D spline interpolant. Fitting|
//| is performed on area defined with one of the "setarea" functions;|
//| this one sets number of nodes placed upon the fitting area. |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| KX - nodes count for the first (X) dimension; fitting |
//| interval [XA,XB] is separated into KX-1 |
//| subintervals, with KX nodes created at the |
//| boundaries. |
//| KY - nodes count for the first (Y) dimension; fitting |
//| interval [YA,YB] is separated into KY-1 |
//| subintervals, with KY nodes created at the |
//| boundaries. |
//| NOTE: at least 4 nodes is created in each dimension, so KX and KY|
//| are silently increased if needed. |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetGrid(CSpline2DBuilder &State,
int kx,
int ky)
{
//--- check
if(!CAp::Assert(kx>0,__FUNCTION__+":KX<=0"))
return;
if(!CAp::Assert(ky>0,__FUNCTION__+":KY<=0"))
return;
State.m_gridtype=1;
State.m_kx=MathMax(kx,4);
State.m_ky=MathMax(ky,4);
}
//+------------------------------------------------------------------+
//| This function allows you to choose least squares solver used to |
//| perform fitting. This function sets solver algorithm to "FastDDM"|
//| which performs fast parallel fitting by splitting problem into |
//| smaller chunks and merging results together. |
//| This solver is optimized for large-scale problems, starting from |
//| 256x256 grids, and up to 10000x10000 grids. Of course, it will |
//| work for smaller grids too. |
//| More detailed description of the algorithm is given below: |
//| * algorithm generates hierarchy of nested grids, ranging from |
//| ~16x16 (topmost "layer" of the model) to ~KX*KY one (final |
//| layer). Upper layers model global behavior of the function, |
//| lower layers are used to model fine details. Moving from |
//| layer to layer doubles grid density. |
//| * fitting is started from topmost layer, subsequent layers are |
//| fitted using residuals from previous ones. |
//| * user may choose to skip generation of upper layers and |
//| generate only a few bottom ones, which will result in much |
//| better performance and parallelization efficiency, at the |
//| cost of algorithm inability to "patch" large holes in the |
//| dataset. |
//| * every layer is regularized using progressively increasing |
//| regularization coefficient; thus, increasing LambdaV |
//| penalizes fine details first, leaving lower frequencies |
//| almost intact for a while. |
//| * after fitting is done, all layers are merged together into |
//| one bicubic spline |
//| IMPORTANT: regularization coefficient used by this solver is |
//| different from the one used by BlockLLS. Latter |
//| utilizes nonlinearity penalty, which is global in |
//| nature (large regularization results in global linear |
//| trend being extracted); this solver uses another, |
//| localized form of penalty, which is suitable for |
//| parallel processing. |
//| Notes on memory and performance: |
//| * memory requirements: most memory is consumed during modeling |
//| of the higher layers; ~[512*NPoints] bytes is required for a |
//| model with full hierarchy of grids being generated. However, |
//| if you skip a few topmost layers, you will get nearly |
//| constant (wrt. points count and grid size) memory consumption|
//| * serial running time: O(K*K)+O(NPoints) for a KxK grid |
//| * parallelism potential: good. You may get nearly linear |
//| speed-up when performing fitting with just a few layers. |
//| Adding more layers results in model becoming more global, |
//| which somewhat reduces efficiency of the parallel code. |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| NLayers - number of layers in the model: |
//| * NLayers>=1 means that up to chosen number of |
//| bottom layers is fitted |
//| * NLayers=0 means that maximum number of layers is |
//| chosen (according to current grid size) |
//| * NLayers<=-1 means that up to |NLayers| topmost |
//| layers is skipped |
//| Recommendations: |
//| *good "default" value is 2 layers |
//| * you may need more layers, if your dataset is very|
//| irregular and you want to "patch" large holes. |
//| For a grid step H (equal to AreaWidth/GridSize) |
//| you may expect that last layer reproduces |
//| variations at distance H (and can patch holes |
//| that wide); that higher layers operate at |
//| distances 2*H, 4*H, 8*H and so on. |
//| *good value for "bullletproof" mode is NLayers=0, |
//| which results in complete hierarchy of layers |
//| being generated. |
//| LambdaV - regularization coefficient, chosen in such a way |
//| that it penalizes bottom layers (fine details) |
//| first. LambdaV>=0, zero value means that no penalty|
//| is applied. |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetAlgoFastDDM(CSpline2DBuilder &State,
int nlayers,
double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite value"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV<0"))
return;
State.m_solvertype=3;
State.m_nlayers=nlayers;
State.m_smoothing=lambdav;
}
//+------------------------------------------------------------------+
//| This function allows you to choose least squares solver used to |
//| perform fitting. This function sets solver algorithm to |
//| "BlockLLS", which performs least squares fitting with fast sparse|
//| direct solver, with optional nonsmoothness penalty being applied.|
//| Nonlinearity penalty has the following form: |
//| [ ] |
//| P()~Lambda*integral[(d2S/dx2)^2+2*(d2S/dxdy)^2+(d2S/dy2)^2]dxdy |
//| [ ] |
//| here integral is calculated over entire grid, and "~" means |
//| "proportional" because integral is normalized after calcilation. |
//| Extremely large values of Lambda result in linear fit being |
//| performed. |
//| NOTE: this algorithm is the most robust and controllable one, but|
//| it is limited by 512x512 grids and (say) up to 1.000.000 |
//| points. However, ALGLIB has one more spline solver: FastDDM|
//| algorithm, which is intended for really large-scale |
//| problems (in 10M-100M range). FastDDM algorithm also has |
//| better parallelism properties. |
//| More information on BlockLLS solver: |
//| * memory requirements: ~[32*K^3+256*NPoints] bytes for KxK grid|
//| with NPoints-sized dataset |
//| * serial running time: O(K^4+NPoints) |
//| * parallelism potential: limited. You may get some sublinear |
//| gain when working with large grids (K's in 256..512 range) |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| LambdaNS - non-negative value: |
//| * positive value means that some smoothing is |
//| applied |
//| * zero value means that no smoothing is applied, |
//| and corresponding entries of design matrix are |
//| numerically zero and dropped from consideration. |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetAlgoBlockLLS(CSpline2DBuilder &State,
double lambdans)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is not finite value"))
return;
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0"))
return;
State.m_solvertype=1;
State.m_smoothing=lambdans;
}
//+------------------------------------------------------------------+
//| This function allows you to choose least squares solver used to |
//| perform fitting. This function sets solver algorithm to |
//| "NaiveLLS". |
//| IMPORTANT: NaiveLLS is NOT intended to be used in real life code!|
//| This algorithm solves problem by generated dense |
//| (K^2)x(K^2+NPoints) matrix and solves linear least |
//| squares problem with dense solver. |
//| It is here just to test BlockLLS against reference |
//| solver (and maybe for someone trying to compare well |
//| optimized solver against straightforward approach to |
//| the LLS problem). |
//| More information on naive LLS solver: |
//| * memory requirements: ~[8*K^4+256*NPoints] bytes for KxK grid.|
//| * serial running time: O(K^6+NPoints) for KxK grid |
//| * when compared with BlockLLS, NaiveLLS has ~K larger memory |
//| demand and ~K^2 larger running time. |
//| INPUT PARAMETERS: |
//| S - spline 2D builder object |
//| LambdaNS - nonsmoothness penalty |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DBuilderSetAlgoNaiveLLS(CSpline2DBuilder &State,
double lambdans)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is not finite value"))
return;
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0"))
return;
State.m_solvertype=2;
State.m_smoothing=lambdans;
}
//+------------------------------------------------------------------+
//| This function fits bicubic spline to current dataset, using |
//| current area/grid and current LLS solver. |
//| INPUT PARAMETERS: |
//| State - spline 2D builder object |
//| OUTPUT PARAMETERS: |
//| S - 2D spline, fit result |
//| Rep - fitting report, which provides some additional info|
//| about errors, R2 coefficient and so on. |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DFit(CSpline2DBuilder &State,
CSpline2DInterpolant &s,
CSpline2DFitReport &rep)
{
//--- create variables
double xa=0;
double xb=0;
double ya=0;
double yb=0;
double xaraw=0;
double xbraw=0;
double yaraw=0;
double ybraw=0;
int kx=0;
int ky=0;
double hx=0;
double hy=0;
double invhx=0;
double invhy=0;
int gridexpansion=0;
int nzwidth=0;
int bfrad=0;
int npoints=0;
int d=0;
int ew=0;
int i=0;
int j=0;
int k=0;
double v=0;
int k0=0;
int k1=0;
double vx=0;
double vy=0;
int arows=0;
int acopied=0;
int basecasex=0;
int basecasey=0;
double eps=0;
CRowDouble xywork;
CMatrixDouble vterm;
double tmpx[];
double tmpy[];
CRowDouble tmp0;
CRowDouble tmp1;
CRowDouble meany;
CRowInt xyindex;
CRowInt tmpi;
CSpline1DInterpolant basis1;
CSparseMatrix av;
CSparseMatrix ah;
CSpline2DXDesignMatrix xdesignmatrix;
CRowDouble z;
CSpline2DBlockLLSBuf blockllsbuf;
int sfx=0;
int sfy=0;
int sfxy=0;
double tss=0;
int dstidx=0;
nzwidth=4;
bfrad=2;
npoints=State.m_npoints;
d=State.m_d;
ew=2+d;
//---Integrity checks
if(!CAp::Assert(State.m_sx==1.0,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(State.m_sy==1.0,__FUNCTION__+": integrity error"))
return;
//---Determine actual area size and grid step
//---NOTE: initialize vars by zeros in order to avoid spurious
//--- compiler warnings.
xa=0;
xb=0;
ya=0;
yb=0;
if(State.m_areatype==0)
{
if(npoints>0)
{
xa=State.m_xy[0];
xb=State.m_xy[0];
ya=State.m_xy[1];
yb=State.m_xy[1];
for(i=1; i<npoints; i++)
{
xa=MathMin(xa,State.m_xy[i*ew+0]);
xb=MathMax(xb,State.m_xy[i*ew+0]);
ya=MathMin(ya,State.m_xy[i*ew+1]);
yb=MathMax(yb,State.m_xy[i*ew+1]);
}
}
else
{
xa=-1;
xb=1;
ya=-1;
yb=1;
}
}
else
{
if(State.m_areatype==1)
{
xa=State.m_xa;
xb=State.m_xb;
ya=State.m_ya;
yb=State.m_yb;
}
else
return;
}
if(xa==xb)
{
v=xa;
if(v>=0.0)
{
xa=v/2-1;
xb=v*2+1;
}
else
{
xa=v*2-1;
xb=v/2+1;
}
}
if(ya==yb)
{
v=ya;
if(v>=0.0)
{
ya=v/2-1;
yb=v*2+1;
}
else
{
ya=v*2-1;
yb=v/2+1;
}
}
//--- check
if(!CAp::Assert(xa<xb,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(ya<yb,__FUNCTION__+": integrity error"))
return;
kx=0;
ky=0;
switch(State.m_gridtype)
{
case 0:
kx=4;
ky=4;
break;
case 1:
kx=State.m_kx;
ky=State.m_ky;
break;
default:
return;
}
//--- check
if(!CAp::Assert(kx>0,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(ky>0,__FUNCTION__+": integrity error"))
return;
basecasex=-1;
basecasey=-1;
if(State.m_solvertype==3)
{
//---Large-scale solver with special requirements to grid size.
kx=MathMax(kx,nzwidth);
ky=MathMax(ky,nzwidth);
k=1;
while(MathMin(kx,ky)>State.m_maxcoresize+1)
{
kx=CApServ::IDivUp(kx-1,2)+1;
ky=CApServ::IDivUp(ky-1,2)+1;
k++;
}
basecasex=kx-1;
k0=1;
while(kx>State.m_maxcoresize+1)
{
basecasex=CApServ::IDivUp(kx-1,2);
kx=basecasex+1;
k0++;
}
while(k0>1)
{
kx=(kx-1)*2+1;
k0--;
}
basecasey=ky-1;
k1=1;
while(ky>State.m_maxcoresize+1)
{
basecasey=CApServ::IDivUp(ky-1,2);
ky=basecasey+1;
k1++;
}
while(k1>1)
{
ky=(ky-1)*2+1;
k1--;
}
while(k>1)
{
kx=(kx-1)*2+1;
ky=(ky-1)*2+1;
k--;
}
//---Grid is NOT expanded. We have very strict requirements on
//---grid size, and we do not want to overcomplicate it by
//---playing with grid size in order to add one more degree of
//---freedom. It is not relevant for such large tasks.
gridexpansion=0;
}
else
{
//---Medium-scale solvers which are tolerant to grid size.
kx=MathMax(kx,nzwidth);
ky=MathMax(ky,nzwidth);
//---Grid is expanded by 1 in order to add one more effective degree
//---of freedom to the spline. Having additional nodes outside of the
//---area allows us to emulate changes in the derivative at the bound
//---without having specialized "boundary" version of the basis function.
if(State.m_adddegreeoffreedom)
gridexpansion=1;
else
gridexpansion=0;
}
hx=CApServ::Coalesce(xb-xa,1.0)/(kx-1);
hy=CApServ::Coalesce(yb-ya,1.0)/(ky-1);
invhx=1/hx;
invhy=1/hy;
//---We determined "raw" grid size. Now perform a grid correction according
//---to current grid expansion size.
xaraw=xa;
yaraw=ya;
xbraw=xb;
ybraw=yb;
xa-=hx*gridexpansion;
ya-=hy*gridexpansion;
xb+=hx*gridexpansion;
yb+=hy*gridexpansion;
kx+=2*gridexpansion;
ky+=2*gridexpansion;
//---Create output spline using transformed (unit-scale)
//---coordinates, fill by zero values
s.m_d=d;
s.m_n=kx;
s.m_m=ky;
s.m_stype=-3;
sfx=s.m_n*s.m_m*d;
sfy=2*s.m_n*s.m_m*d;
sfxy=3*s.m_n*s.m_m*d;
s.m_x.Resize(s.m_n);
s.m_y.Resize(s.m_m);
s.m_f=vector<double>::Zeros(4*s.m_n*s.m_m*d);
for(i=0; i<s.m_n; i++)
s.m_x.Set(i,i);
for(i=0; i<s.m_m; i++)
s.m_y.Set(i,i);
//---Create local copy of dataset (only points in the grid are copied;
//---we allow small step out of the grid, by Eps*H, in order to deal
//---with numerical rounding errors).
//---An additional copy of Y-values is created at columns beyond 2+J;
//---it is preserved during all transformations. This copy is used
//---to calculate error-related metrics.
//---Calculate mean(Y), TSS
meany=vector<double>::Zeros(d);
CApServ::RVectorSetLengthAtLeast(xywork,npoints*ew);
acopied=0;
eps=1.0E-6;
for(i=0; i<npoints; i++)
{
vx=State.m_xy[i*ew+0];
vy=State.m_xy[i*ew+1];
if((xaraw-eps*hx)<=vx && vx<=(xbraw+eps*hx) && (yaraw-eps*hy)<=vy && vy<=(ybraw+eps*hy))
{
xywork.Set(acopied*ew+0,(vx-xa)*invhx);
xywork.Set(acopied*ew+1,(vy-ya)*invhy);
for(j=0; j<=d-1; j++)
{
v=State.m_xy[i*ew+2+j];
xywork.Set(acopied*ew+2+j,v);
meany.Add(j,v);
}
acopied++;
}
}
npoints=acopied;
meany/=CApServ::Coalesce(npoints,1);
tss=0.0;
for(i=0; i<npoints; i++)
{
for(j=0; j<d; j++)
tss+=CMath::Sqr(xywork[i*ew+2+j]-meany[j]);
}
tss=CApServ::Coalesce(tss,1.0);
//---Handle prior term.
//---Modify output spline.
//---Quick exit if dataset is empty.
CIntFitServ::BuildPriorTerm1(xywork,npoints,2,d,State.m_priorterm,State.m_priortermval,vterm);
if(npoints==0)
{
//---Quick exit
for(k=0; k<s.m_n*s.m_m; k++)
{
k0=k%s.m_n;
k1=k/s.m_n;
for(j=0; j<d; j++)
{
dstidx=d*(k1*s.m_n+k0)+j;
s.m_f.Add(dstidx,vterm.Get(j,0)*s.m_x[k0]+vterm.Get(j,1)*s.m_y[k1]+vterm.Get(j,2));
s.m_f.Add(sfx+dstidx,vterm.Get(j,0));
s.m_f.Add(sfy+dstidx,vterm.Get(j,1));
}
}
s.m_x=s.m_x*hx+xa;
s.m_y=s.m_y*hy+ya;
for(i=0; i<=s.m_n*s.m_m*d-1; i++)
{
s.m_f.Mul(sfx+i,invhx);
s.m_f.Mul(sfy+i,invhy);
s.m_f.Mul(sfxy+i,invhx*invhy);
}
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rep.m_r2=1.0;
return;
}
//---Build 1D compact basis function
//---Generate design matrix
ArrayResize(tmpx,7);
ArrayResize(tmpy,7);
tmpx[0]=-3;
tmpx[1]=-2;
tmpx[2]=-1;
tmpx[3]=0;
tmpx[4]=1;
tmpx[5]=2;
tmpx[6]=3;
tmpy[0]=0;
tmpy[1]=0;
tmpy[2]=1.0/12.0;
tmpy[3]=2.0/6.0;
tmpy[4]=1.0/12.0;
tmpy[5]=0;
tmpy[6]=0;
CSpline1D::Spline1DBuildCubic(tmpx,tmpy,CAp::Len(tmpx),2,0.0,2,0.0,basis1);
//---Solve.
//---Update spline.
switch(State.m_solvertype)
{
case 1:
//---BlockLLS
ReorderDatasetAndBuildIndex(xywork,npoints,d,tmp0,0,kx,ky,xyindex,tmpi);
XDesignGenerate(xywork,xyindex,0,kx,kx,0,ky,ky,d,m_lambdaregblocklls,State.m_smoothing,basis1,xdesignmatrix);
BlockLLSFit(xdesignmatrix,State.m_lsqrcnt,z,rep,tss,blockllsbuf);
UpdateSplineTable(z,kx,ky,d,basis1,bfrad,s.m_f,s.m_m,s.m_n,1);
break;
case 2:
//---NaiveLLS, reference implementation
GenerateDesignMatrix(xywork,npoints,d,kx,ky,State.m_smoothing,m_lambdaregblocklls,basis1,av,ah,arows);
NaiveLLSFit(av,ah,arows,xywork,kx,ky,npoints,d,State.m_lsqrcnt,z,rep,tss);
UpdateSplineTable(z,kx,ky,d,basis1,bfrad,s.m_f,s.m_m,s.m_n,1);
break;
case 3:
//---FastDDM method
if(!CAp::Assert(basecasex>0,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(basecasey>0,__FUNCTION__+": integrity error"))
return;
FastDDMFit(xywork,npoints,d,kx,ky,basecasex,basecasey,State.m_maxcoresize,State.m_interfacesize,State.m_nlayers,State.m_smoothing,State.m_lsqrcnt,basis1,s,rep,tss);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity error");
return;
}
//---Append prior term.
//---Transform spline to original coordinates
for(k=0; k<s.m_n*s.m_m; k++)
{
k0=k%s.m_n;
k1=k/s.m_n;
for(j=0; j<d; j++)
{
dstidx=d*(k1*s.m_n+k0)+j;
s.m_f.Add(dstidx,vterm.Get(j,0)*s.m_x[k0]+vterm.Get(j,1)*s.m_y[k1]+vterm.Get(j,2));
s.m_f.Add(sfx+dstidx,vterm.Get(j,0));
s.m_f.Add(sfy+dstidx,vterm.Get(j,1));
}
}
s.m_x=s.m_x*hx+xa;
s.m_y=s.m_y*hy+ya;
for(i=0; i<s.m_n*s.m_m*d; i++)
{
s.m_f.Mul(sfx+i,invhx);
s.m_f.Mul(sfy+i,invhy);
s.m_f.Mul(sfxy+i,invhx*invhy);
}
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DAlloc(CSerializer &s,CSpline2DInterpolant &spline)
{
//---Header
s.Alloc_Entry();
//---Data
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
CApServ::AllocRealArray(s,spline.m_x,-1);
CApServ::AllocRealArray(s,spline.m_y,-1);
CApServ::AllocRealArray(s,spline.m_f,-1);
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DSerialize(CSerializer &s,
CSpline2DInterpolant &spline)
{
//---Header
s.Serialize_Int(CSCodes::GetSpline2DSerializationCode());
//---Data
s.Serialize_Int(spline.m_stype);
s.Serialize_Int(spline.m_n);
s.Serialize_Int(spline.m_m);
s.Serialize_Int(spline.m_d);
CApServ::SerializeRealArray(s,spline.m_x,-1);
CApServ::SerializeRealArray(s,spline.m_y,-1);
CApServ::SerializeRealArray(s,spline.m_f,-1);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CSpline2D::Spline2DUnserialize(CSerializer &s,
CSpline2DInterpolant &spline)
{
//---Header
int scode=s.Unserialize_Int();
//--- check
if(!CAp::Assert(scode==CSCodes::GetSpline2DSerializationCode(),__FUNCTION__+": stream header corrupted"))
return;
//---Data
spline.m_stype=s.Unserialize_Int();
spline.m_n=s.Unserialize_Int();
spline.m_m=s.Unserialize_Int();
spline.m_d=s.Unserialize_Int();
CApServ::UnserializeRealArray(s,spline.m_x);
CApServ::UnserializeRealArray(s,spline.m_y);
CApServ::UnserializeRealArray(s,spline.m_f);
}
//+------------------------------------------------------------------+
//| Internal subroutine. |
//| Calculation of the first derivatives and the cross-derivative. |
//+------------------------------------------------------------------+
void CSpline2D::BicubicCalcDerivatives(CMatrixDouble &a,CRowDouble &x,
CRowDouble &y,const int m,
const int n,CMatrixDouble &dx,
CMatrixDouble &dy,CMatrixDouble &dxy)
{
//--- create variables
double s=0;
double ds=0;
double d2s=0;
//--- create arrays
double xt[];
double ft[];
//--- object of class
CSpline1DInterpolant c;
//--- allocation
dx.Resize(m,n);
dy.Resize(m,n);
dxy.Resize(m,n);
//--- dF/dX
ArrayResize(xt,n);
ArrayResize(ft,n);
for(int i=0; i<m; i++)
{
for(int j=0; j<n; j++)
{
xt[j]=x[j];
ft[j]=a.Get(i,j);
}
//--- function call
CSpline1D::Spline1DBuildCubic(xt,ft,n,0,0.0,0,0.0,c);
for(int j=0; j<n; j++)
{
//--- function call
CSpline1D::Spline1DDiff(c,x[j],s,ds,d2s);
dx.Set(i,j,ds);
}
}
//--- dF/dY
ArrayResize(xt,m);
ArrayResize(ft,m);
for(int j=0; j<n; j++)
{
for(int i=0; i<m; i++)
{
xt[i]=y[i];
ft[i]=a.Get(i,j);
}
//--- function call
CSpline1D::Spline1DBuildCubic(xt,ft,m,0,0.0,0,0.0,c);
for(int i=0; i<=m-1; i++)
{
//--- function call
CSpline1D::Spline1DDiff(c,y[i],s,ds,d2s);
dy.Set(i,j,ds);
}
}
//--- d2F/dXdY
ArrayResize(xt,n);
ArrayResize(ft,n);
for(int i=0; i<=m-1; i++)
{
for(int j=0; j<n; j++)
{
xt[j]=x[j];
ft[j]=dy.Get(i,j);
}
//--- function call
CSpline1D::Spline1DBuildCubic(xt,ft,n,0,0.0,0,0.0,c);
for(int j=0; j<n; j++)
{
//--- function call
CSpline1D::Spline1DDiff(c,x[j],s,ds,d2s);
dxy.Set(i,j,ds);
}
}
}
//+------------------------------------------------------------------+
//| This function generates design matrix for the problem (in fact, |
//| two design matrices are generated: "vertical" one and transposed |
//| (horizontal) one. |
//| INPUT PARAMETERS: |
//| XY - array[NPoints*(2+D)]; dataset after scaling in |
//| such way that grid step is equal to 1.0 in both |
//| dimensions. |
//| NPoints - dataset size, NPoints>=1 |
//| KX, KY - grid size, KX,KY>=4 |
//| Smoothing - nonlinearity penalty coefficient, >=0 |
//| LambdaReg - regularization coefficient, >=0 |
//| Basis1 - basis spline, expected to be non-zero only at |
//| [-2,+2] |
//| AV, AH - possibly preallocated buffers |
//| OUTPUT PARAMETERS: |
//| AV - sparse matrix[ARows,KX*KY]; design matrix |
//| AH - transpose of AV |
//| ARows - number of rows in design matrix |
//+------------------------------------------------------------------+
void CSpline2D::GenerateDesignMatrix(CRowDouble &xy,
int npoints,
int d,
int kx,
int ky,
double smoothing,
double lambdareg,
CSpline1DInterpolant &basis1,
CSparseMatrix &av,
CSparseMatrix &ah,
int &arows)
{
//--- create variables
int nzwidth=0;
int nzshift=0;
int ew=0;
int i=0;
int j0=0;
int j1=0;
int k0=0;
int k1=0;
int dstidx=0;
double v=0;
double v0=0;
double v1=0;
double v2=0;
double w0=0;
double w1=0;
double w2=0;
CRowInt crx;
CRowInt cry;
CRowInt nrs;
CMatrixDouble d2x;
CMatrixDouble d2y;
CMatrixDouble dxy;
arows=0;
nzwidth=4;
nzshift=1;
//--- check
if(!CAp::Assert(npoints>0,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
ew=2+d;
//---Determine canonical rectangle for every point. Every point of the dataset is
//---influenced by at most NZWidth*NZWidth basis functions, which form NZWidth*NZWidth
//---canonical rectangle.
//---Thus, we have (KX-NZWidth+1)*(KY-NZWidth+1) overlapping canonical rectangles.
//---Assigning every point to its rectangle simplifies creation of sparse basis
//---matrix at the next steps.
crx.Resize(npoints);
cry.Resize(npoints);
for(i=0; i<npoints; i++)
{
crx.Set(i,CApServ::BoundVal((int)MathFloor(xy[i*ew+0])-nzshift,0,kx-nzwidth));
cry.Set(i,CApServ::BoundVal((int)MathFloor(xy[i*ew+1])-nzshift,0,ky-nzwidth));
}
//---Create vertical and horizontal design matrices
arows=npoints+kx*ky;
if(smoothing!=0.0)
{
//--- check
if(!CAp::Assert(smoothing>0.0,__FUNCTION__+": integrity check failed"))
return;
arows+=3*(kx-2)*(ky-2);
}
nrs.Resize(arows);
dstidx=0;
nrs.Fill(nzwidth*nzwidth,dstidx+i,npoints);
dstidx+=npoints;
nrs.Fill(1,dstidx,kx*ky);
dstidx+=kx*ky;
if(smoothing!=0.0)
{
nrs.Fill(9,dstidx,3*(kx-2)*(ky-2));
dstidx+=3*(kx-2)*(ky-2);
}
//--- check
if(!CAp::Assert(dstidx==arows,__FUNCTION__+": integrity check failed"))
return;
CSparse::SparseCreateCRS(arows,kx*ky,nrs,av);
dstidx=0;
for(i=0; i<npoints; i++)
for(j1=0; j1<nzwidth; j1++)
for(j0=0; j0<nzwidth; j0++)
{
v0=CSpline1D::Spline1DCalc(basis1,xy[i*ew+0]-(crx[i]+j0));
v1=CSpline1D::Spline1DCalc(basis1,xy[i*ew+1]-(cry[i]+j1));
CSparse::SparseSet(av,dstidx+i,(cry[i]+j1)*kx+(crx[i]+j0),v0*v1);
}
dstidx=dstidx+npoints;
for(i=0; i<kx*ky; i++)
CSparse::SparseSet(av,dstidx+i,i,lambdareg);
dstidx+=kx*ky;
if(smoothing!=0.0)
{
//---Smoothing is applied. Because all grid nodes are same,
//---we apply same smoothing kernel, which is calculated only
//---once at the beginning of design matrix generation.
d2x=matrix<double>::Zeros(3,3);
d2y=matrix<double>::Zeros(3,3);
dxy=matrix<double>::Zeros(3,3);
for(k1=0; k1<=2; k1++)
{
for(k0=0; k0<=2; k0++)
{
CSpline1D::Spline1DDiff(basis1,-(k0-1),v0,v1,v2);
CSpline1D::Spline1DDiff(basis1,-(k1-1),w0,w1,w2);
d2x.Add(k0,k1,v2*w0);
d2y.Add(k0,k1,w2*v0);
dxy.Add(k0,k1,v1*w1);
}
}
//---Now, kernel is ready - apply it to all inner nodes of the grid.
for(j1=1; j1<ky-1; j1++)
{
for(j0=1; j0<kx-1; j0++)
{
//---d2F/dx2 term
v=smoothing;
for(k1=-1; k1<=1; k1++)
for(k0=-1; k0<=1; k0++)
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*d2x.Get(1+k0,1+k1));
dstidx++;
//---d2F/dy2 term
v=smoothing;
for(k1=-1; k1<=1; k1++)
for(k0=-1; k0<=1; k0++)
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*d2y.Get(1+k0,1+k1));
dstidx++;
//---2*d2F/dxdy term
v=MathSqrt(2)*smoothing;
for(k1=-1; k1<=1; k1++)
for(k0=-1; k0<=1; k0++)
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*dxy.Get(1+k0,1+k1));
dstidx=dstidx+1;
}
}
}
//--- check
if(!CAp::Assert(dstidx==arows,__FUNCTION__+": integrity check failed"))
return;
CSparse::SparseCopy(av,ah);
CSparse::SparseTransposeCRS(ah);
}
//+------------------------------------------------------------------+
//| This function updates table of spline values/derivatives using |
//| coefficients for a layer of basis functions. |
//+------------------------------------------------------------------+
void CSpline2D::UpdateSplineTable(CRowDouble &z,
int kx,
int ky,
int d,
CSpline1DInterpolant &basis1,
int bfrad,
CRowDouble &ftbl,
int m,
int n,
int scalexy)
{
//--- create variables
int k=0;
int k0=0;
int k1=0;
int j=0;
int j0=0;
int j1=0;
int j0a=0;
int j0b=0;
int j1a=0;
int j1b=0;
double v=0;
double v0=0;
double v1=0;
double v01=0;
double v11=0;
double rdummy=0;
int dstidx=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double invscalexy=0;
//--- check
if(!CAp::Assert(n==(kx-1)*scalexy+1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(m==(ky-1)*scalexy+1,__FUNCTION__+": integrity check failed"))
return;
invscalexy=1.0/(double)scalexy;
sfx=n*m*d;
sfy=2*n*m*d;
sfxy=3*n*m*d;
for(k=0; k<kx*ky; k++)
{
k0=k%kx;
k1=k/kx;
j0a=CApServ::BoundVal(k0*scalexy-(bfrad*scalexy-1),0,n-1);
j0b=CApServ::BoundVal(k0*scalexy+(bfrad*scalexy-1),0,n-1);
j1a=CApServ::BoundVal(k1*scalexy-(bfrad*scalexy-1),0,m-1);
j1b=CApServ::BoundVal(k1*scalexy+(bfrad*scalexy-1),0,m-1);
for(j1=j1a; j1<=j1b; j1++)
{
CSpline1D::Spline1DDiff(basis1,(j1-k1*scalexy)*invscalexy,v1,v11,rdummy);
v11=v11*invscalexy;
for(j0=j0a; j0<=j0b; j0++)
{
CSpline1D::Spline1DDiff(basis1,(j0-k0*scalexy)*invscalexy,v0,v01,rdummy);
v01*=invscalexy;
for(j=0; j<=d-1; j++)
{
dstidx=d*(j1*n+j0)+j;
v=z[j*kx*ky+k];
ftbl.Add(dstidx,v0*v1*v);
ftbl.Add(sfx+dstidx,v01*v1*v);
ftbl.Add(sfy+dstidx,v0*v11*v);
ftbl.Add(sfxy+dstidx,v01*v11*v);
}
}
}
}
}
//+------------------------------------------------------------------+
//| This function performs fitting with FastDDM solver. |
//| Internal function, never use it directly. |
//| INPUT PARAMETERS: |
//| XY - array[NPoints*(2+D)], dataset; destroyed in process|
//| KX, KY - grid size |
//| TileSize - tile size |
//| InterfaceSize - interface size |
//| NPoints - points count |
//| D - number of components in vector-valued spline, D>=1 |
//| LSQRCnt - number of iterations, non-zero: |
//| * LSQRCnt>0 means that specified amount of |
//| preconditioned LSQR iterations will be performed |
//| to solve problem; usually we need 2..5 its. |
//| Recommended option - best convergence and |
//| stability/quality. |
//| * LSQRCnt<0 means that instead of LSQR we use |
//| iterative refinement on normal equations. Again, |
//| 2..5 its is enough. |
//| Basis1 - basis spline, expected to be non-zero only |
//| at [-2,+2] |
//| Z - possibly preallocated buffer for solution |
//| Residuals- possibly preallocated buffer for residuals at |
//| dataset points |
//| Rep - report structure; fields which are not set by this |
//| function are left intact |
//| TSS - total sum of squares; used to calculate R2 |
//| OUTPUT PARAMETERS: |
//| XY - destroyed in process |
//| Z - array[KX*KY*D], filled by solution; KX*KY |
//| coefficients corresponding to each of D dimensions |
//| are stored contiguously. |
//| Rep - following fields are set: |
//| * Rep.m_rmserror |
//| * Rep.AvgError |
//| * Rep.MaxError |
//| * Rep.R2 |
//+------------------------------------------------------------------+
void CSpline2D::FastDDMFit(CRowDouble &xy,
int npoints,
int d,
int kx,
int ky,
int basecasex,
int basecasey,
int maxcoresize,
int interfacesize,
int nlayers,
double smoothing,
int lsqrcnt,
CSpline1DInterpolant &basis1,
CSpline2DInterpolant &spline,
CSpline2DFitReport &rep,
double tss)
{
//--- create variables
int i=0;
int j=0;
int nzwidth=0;
int xew=0;
int ntotallayers=0;
int scaleidx=0;
int scalexy=0;
double invscalexy=0;
int kxcur=0;
int kycur=0;
int tilescount0=0;
int tilescount1=0;
double v=0;
double rss=0;
CRowDouble yraw;
CRowInt xyindex;
CRowDouble tmp0;
CRowInt bufi;
CSpline2DFastDDMBuf seed;
CSpline2DFastDDMBuf pool;
CSpline2DXDesignMatrix xdesignmatrix;
CSpline2DBlockLLSBuf blockllsbuf;
CSpline2DFitReport dummyrep;
//---Dataset metrics and integrity checks
nzwidth=4;
xew=2+d;
//--- check
if(!CAp::Assert(maxcoresize>=2,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(interfacesize>=1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
//---Verify consistency of the grid size (KX,KY) with basecase sizes.
//---Determine full number of layers.
if(!CAp::Assert(basecasex<=maxcoresize,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(basecasey<=maxcoresize,__FUNCTION__+": integrity error"))
return;
ntotallayers=1;
scalexy=1;
kxcur=kx;
kycur=ky;
while(kxcur>basecasex+1 && kycur>basecasey+1)
{
if(!CAp::Assert(kxcur%2==1,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(kycur%2==1,__FUNCTION__+": integrity error"))
return;
kxcur=(kxcur-1)/2+1;
kycur=(kycur-1)/2+1;
scalexy=scalexy*2;
ntotallayers++;
}
invscalexy=1.0/(double)scalexy;
if(!CAp::Assert((kxcur<=maxcoresize+1 && kxcur==basecasex+1) || kxcur%basecasex==1,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert((kycur<=maxcoresize+1 && kycur==basecasey+1) || kycur%basecasey==1,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(kxcur==basecasex+1 || kycur==basecasey+1,__FUNCTION__+": integrity error"))
return;
//---Initial scaling of dataset.
//---Store original target values to YRaw.
CApServ::RVectorSetLengthAtLeast(yraw,npoints*d);
for(i=0; i<npoints; i++)
{
xy.Mul(xew*i,invscalexy);
xy.Mul(xew*i+1,invscalexy);
for(j=0; j<d; j++)
yraw.Set(i*d+j,xy[xew*i+2+j]);
}
kxcur=(kx-1)/scalexy+1;
kycur=(ky-1)/scalexy+1;
//---Build initial dataset index; area is divided into (KXCur-1)*(KYCur-1)
//---cells, with contiguous storage of points in the same cell.
//---Iterate over different scales
pool=seed;
ReorderDatasetAndBuildIndex(xy,npoints,d,yraw,d,kxcur,kycur,xyindex,bufi);
for(scaleidx=ntotallayers-1; scaleidx>=0; scaleidx--)
{
if((nlayers>0 && scaleidx<nlayers) || (nlayers<=0 && scaleidx<MathMax(ntotallayers+nlayers,1)))
{
//---Fit current layer
if(!CAp::Assert(kxcur%basecasex==1,__FUNCTION__+": integrity error"))
return;
if(!CAp::Assert(kycur%basecasey==1,__FUNCTION__+": integrity error"))
return;
tilescount0=kxcur/basecasex;
tilescount1=kycur/basecasey;
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,0,tilescount0,tilescount0,basecasey,0,tilescount1,tilescount1,maxcoresize,interfacesize,lsqrcnt,m_lambdaregfastddm+smoothing*MathPow(m_lambdadecay,scaleidx),basis1,pool,spline);
//---Compute residuals and update XY
ComputeResidualsFromScratch(xy,yraw,npoints,d,scalexy,spline);
}
//---Move to the next level
if(scaleidx!=0)
{
//---Transform dataset (multply everything by 2.0) and refine grid.
kxcur=2*kxcur-1;
kycur=2*kycur-1;
scalexy=scalexy/2;
invscalexy=1.0/(double)scalexy;
RescaleDatasetAndRefineIndex(xy,npoints,d,yraw,d,kxcur,kycur,xyindex,bufi);
}
}
//---Post-check
if(!CAp::Assert(kxcur==kx,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(kycur==ky,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(scalexy==1,__FUNCTION__+": integrity check failed"))
return;
//---Report
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rss=0.0;
for(i=0; i<npoints; i++)
{
for(j=0; j<=d-1; j++)
{
v=xy[i*xew+2+j];
rss=rss+v*v;
rep.m_rmserror+=CMath::Sqr(v);
rep.m_avgerror+=MathAbs(v);
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(npoints*d,1.0));
rep.m_avgerror=rep.m_avgerror/CApServ::Coalesce(npoints*d,1.0);
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
}
//+------------------------------------------------------------------+
//| Recursive fitting function for FastDDM algorithm. |
//| Works with KX*KY grid, with KX=BasecaseX*TilesCountX+1 and |
//| KY=BasecaseY*TilesCountY+1, which is partitioned into |
//| TilesCountX*TilesCountY tiles, each having size |
//| BasecaseX*BasecaseY. |
//| This function processes tiles in range |
//| [TileX0,TileX1)x[TileY0,TileY1) and recursively divides this |
//| range until we move down to single tile, which is processed with |
//| BlockLLS solver. |
//+------------------------------------------------------------------+
void CSpline2D::FastDDMFitLayer(CRowDouble &xy,int d,int scalexy,
CRowInt &xyindex,int basecasex,
int tilex0,int tilex1,int tilescountx,
int basecasey,int tiley0,int tiley1,
int tilescounty,int maxcoresize,
int interfacesize,int lsqrcnt,
double lambdareg,
CSpline1DInterpolant &basis1,
CSpline2DFastDDMBuf &pool,
CSpline2DInterpolant &spline)
{
//--- create variables
int kx=0;
int ky=0;
int i=0;
int j=0;
int j0=0;
int j1=0;
int bfrad=0;
int xa=0;
int xb=0;
int ya=0;
int yb=0;
int tile0=0;
int tile1=0;
int tilesize0=0;
int tilesize1=0;
int sfx=0;
int sfy=0;
int sfxy=0;
double dummytss=0;
double invscalexy=0;
int cnt0=0;
int cnt1=0;
int offs=0;
double vs=0;
double vsx=0;
double vsy=0;
double vsxy=0;
CSpline2DFastDDMBuf buf;
//---Dataset metrics and fast integrity checks;
//---no code with side effects is allowed before parallel split.
bfrad=2;
invscalexy=1.0/(double)scalexy;
kx=basecasex*tilescountx+1;
ky=basecasey*tilescounty+1;
if(MathMax(tiley1-tiley0,tilex1-tilex0)>=2)
{
if(tiley1-tiley0>tilex1-tilex0)
{
//---Split problem in Y dimension
//---NOTE: recursive calls to FastDDMFitLayer() compute
//--- residuals in the inner cells defined by XYIndex[],
//--- but we still have to compute residuals for cells
//--- BETWEEN two recursive subdivisions of the task.
CApServ::TiledSplit(tiley1-tiley0,1,j0,j1);
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex1,tilescountx,basecasey,tiley0,tiley0+j0,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex1,tilescountx,basecasey,tiley0+j0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
}
else
{
//---Split problem in X dimension
//---NOTE: recursive calls to FastDDMFitLayer() compute
//--- residuals in the inner cells defined by XYIndex[],
//--- but we still have to compute residuals for cells
//--- BETWEEN two recursive subdivisions of the task.
CApServ::TiledSplit(tilex1-tilex0,1,j0,j1);
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex0+j0,tilescountx,basecasey,tiley0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0+j0,tilex1,tilescountx,basecasey,tiley0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
}
return;
}
//--- check
if(!CAp::Assert(tiley0==tiley1-1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(tilex0==tilex1-1,__FUNCTION__+": integrity check failed"))
return;
tile1=tiley0;
tile0=tilex0;
//---Retrieve temporaries
buf=pool;
//---Analyze dataset
xa=CApServ::BoundVal(tile0*basecasex-interfacesize,0,kx);
xb=CApServ::BoundVal((tile0+1)*basecasex+interfacesize,0,kx);
ya=CApServ::BoundVal(tile1*basecasey-interfacesize,0,ky);
yb=CApServ::BoundVal((tile1+1)*basecasey+interfacesize,0,ky);
tilesize0=xb-xa;
tilesize1=yb-ya;
//---Solve current chunk with BlockLLS
dummytss=1.0;
XDesignGenerate(xy,xyindex,xa,xb,kx,ya,yb,ky,d,lambdareg,0.0,basis1,buf.m_xdesignmatrix);
BlockLLSFit(buf.m_xdesignmatrix,lsqrcnt,buf.m_tmpz,buf.m_dummyrep,dummytss,buf.m_blockllsbuf);
buf.m_localmodel.m_d=d;
buf.m_localmodel.m_m=tilesize1;
buf.m_localmodel.m_n=tilesize0;
buf.m_localmodel.m_stype=-3;
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_x,tilesize0);
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_y,tilesize1);
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_f,tilesize0*tilesize1*d*4);
for(i=0; i<=tilesize0-1; i++)
buf.m_localmodel.m_x.Set(i,xa+i);
for(i=0; i<=tilesize1-1; i++)
buf.m_localmodel.m_y.Set(i,ya+i);
for(i=0; i<=tilesize0*tilesize1*d*4-1; i++)
buf.m_localmodel.m_f.Set(i,0.0);
UpdateSplineTable(buf.m_tmpz,tilesize0,tilesize1,d,basis1,bfrad,buf.m_localmodel.m_f,tilesize1,tilesize0,1);
//---Transform local spline to original coordinates
sfx=buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
sfy=2*buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
sfxy=3*buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
for(i=0; i<tilesize0; i++)
buf.m_localmodel.m_x.Mul(i,scalexy);
for(i=0; i<tilesize1; i++)
buf.m_localmodel.m_y.Mul(i,scalexy);
for(i=0; i<tilesize0*tilesize1*d; i++)
{
buf.m_localmodel.m_f.Mul(sfx+i,invscalexy);
buf.m_localmodel.m_f.Mul(sfy+i,invscalexy);
buf.m_localmodel.m_f.Mul(sfxy+i,(invscalexy*invscalexy));
}
//---Output results; for inner and topmost/leftmost tiles we output only BasecaseX*BasecaseY
//---inner elements; for rightmost/bottom ones we also output one column/row of the interface
//---part.
//---Such complexity is explained by the fact that area size (by design) is not evenly divisible
//---by the tile size; it is divisible with remainder=1, and we expect that interface size is
//---at least 1, so we can fill the missing rightmost/bottom elements of Z by the interface
//---values.
//--- check
if(!CAp::Assert(interfacesize>=1,__FUNCTION__+": integrity check failed"))
return;
sfx=spline.m_n*spline.m_m*d;
sfy=2*spline.m_n*spline.m_m*d;
sfxy=3*spline.m_n*spline.m_m*d;
cnt0=basecasex*scalexy;
cnt1=basecasey*scalexy;
if(tile0==tilescountx-1)
cnt0++;
if(tile1==tilescounty-1)
cnt1++;
offs=d*(spline.m_n*tile1*basecasey*scalexy+tile0*basecasex*scalexy);
for(j1=0; j1<cnt1; j1++)
for(j0=0; j0<cnt0; j0++)
for(j=0; j<d; j++)
{
Spline2DDiffVi(buf.m_localmodel,tile0*basecasex*scalexy+j0,tile1*basecasey*scalexy+j1,j,vs,vsx,vsy,vsxy);
spline.m_f.Add(offs+d*(spline.m_n*j1+j0)+j,vs);
spline.m_f.Add(sfx+offs+d*(spline.m_n*j1+j0)+j,vsx);
spline.m_f.Add(sfy+offs+d*(spline.m_n*j1+j0)+j,vsy);
spline.m_f.Add(sfxy+offs+d*(spline.m_n*j1+j0)+j,vsxy);
}
//---Recycle temporaries
pool=buf;
}
//+------------------------------------------------------------------+
//| This function performs fitting with BlockLLS solver. Internal |
//| function, never use it directly. |
//| IMPORTANT: performance and memory requirements of this function |
//| are asymmetric w.r.t. KX and KY: it has |
//| * O(KY*KX^2) memory requirements |
//| * O(KY*KX^3) running time |
//| Thus, if you have large KY and small KX, simple |
//| transposition of your dataset may give you great |
//| speedup. |
//| INPUT PARAMETERS: |
//| AV - sparse matrix, [ARows,KX*KY] in size. "Vertical" |
//| version of design matrix, rows [0,NPoints) contain |
//| values of basis functions at dataset points. Other |
//| rows are used for nonlinearity penalty and other |
//| stuff like that. |
//| AH - transpose(AV), "horizontal" version of AV |
//| ARows - rows count |
//| XY - array[NPoints*(2+D)], dataset |
//| KX, KY - grid size |
//| NPoints - points count |
//| D - number of components in vector-valued spline, D>=1 |
//| LSQRCnt - number of iterations, non-zero: |
//| * LSQRCnt>0 means that specified amount of |
//| preconditioned LSQR iterations will be performed |
//| to solve problem; usually we need 2..5 its. |
//| Recommended option - best convergence and |
//| stability/quality. |
//| * LSQRCnt<0 means that instead of LSQR we use |
//| iterative refinement on normal equations. Again, |
//| 2..5 its is enough. |
//| Z - possibly preallocated buffer for solution |
//| Rep - report structure; fields which are not set by this |
//| function are left intact |
//| TSS - total sum of squares; used to calculate R2 |
//| OUTPUT PARAMETERS: |
//| XY - destroyed in process |
//| Z - array[KX*KY*D], filled by solution; KX*KY |
//| coefficients corresponding to each of D dimensions |
//| are stored contiguously. |
//| Rep - following fields are set: |
//| * Rep.RmsError |
//| * Rep.AvgError |
//| * Rep.MaxError |
//| * Rep.R2 |
//+------------------------------------------------------------------+
void CSpline2D::BlockLLSFit(CSpline2DXDesignMatrix &xdesign,
int lsqrcnt,
CRowDouble &z,
CSpline2DFitReport &rep,
double tss,
CSpline2DBlockLLSBuf &buf)
{
//--- create variables
int blockbandwidth=0;
int d=0;
int i=0;
int j=0;
double lambdachol=0;
double mxata;
double v=0;
int celloffset=0;
int i0=0;
int i1=0;
double rss=0;
int arows=0;
int bw2=0;
int kx=0;
int ky=0;
//--- check
if(!CAp::Assert(xdesign.m_blockwidth==4,__FUNCTION__+": integrity check failed"))
return;
blockbandwidth=3;
d=xdesign.m_d;
arows=xdesign.m_nrows;
kx=xdesign.m_kx;
ky=xdesign.m_ky;
bw2=xdesign.m_blockwidth*xdesign.m_blockwidth;
//---Initial values for Z/Residuals
z=vector<double>::Zeros(kx*ky*d);
//---Create and factorize design matrix. Add regularizer if
//---factorization failed (happens sometimes with zero
//---smoothing and sparsely populated datasets).
//---The algorithm below is refactoring of NaiveLLS algorithm,
//---which uses sparsity properties and compressed block storage.
//---Problem sparsity pattern results in block-band-diagonal
//---matrix (block matrix with limited bandwidth, equal to 3
//---for bicubic splines). Thus, we have KY*KY blocks, each
//---of them is KX*KX in size. Design matrix is stored in
//---large NROWS*KX matrix, with NROWS=(BlockBandwidth+1)*KY*KX.
//---We use adaptation of block skyline storage format, with
//---TOWERSIZE*KX skyline bands (towers) stored sequentially;
//---here TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY
//---"towers", stored one below other, in BlockATA matrix.
//---Every "tower" is a sequence of BlockBandwidth+1 cells,
//---each of them being KX*KX in size.
lambdachol=m_cholreg;
CApServ::RMatrixSetLengthAtLeast(buf.m_blockata,(blockbandwidth+1)*ky*kx,kx);
while(true)
{
//---Parallel generation of squared design matrix.
XDesignBlockATA(xdesign,buf.m_blockata,mxata);
//---Regularization
v=CApServ::Coalesce(mxata,1.0)*lambdachol;
for(i1=0; i1<ky; i1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i1);
for(i0=0; i0<=kx-1; i0++)
buf.m_blockata.Add(celloffset+i0,i0,v);
}
//---Try Cholesky factorization.
if(!BlockLLSCholesky(buf.m_blockata,kx,ky,buf.m_trsmbuf2,buf.m_cholbuf2,buf.m_cholbuf1))
{
//---Factorization failed, increase regularizer and repeat
lambdachol=CApServ::Coalesce(10*lambdachol,1.0E-12);
continue;
}
break;
}
//---Solve
rss=0.0;
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
//--- check
if(!CAp::Assert(lsqrcnt>0,__FUNCTION__+": integrity failure"))
return;
CApServ::RVectorSetLengthAtLeast(buf.m_tmp0,arows);
CApServ::RVectorSetLengthAtLeast(buf.m_tmp1,kx*ky);
CLinLSQR::LinLSQRCreateBuf(arows,kx*ky,buf.m_solver);
for(j=0; j<d; j++)
{
//---Preconditioned LSQR:
//---use Cholesky factor U of squared design matrix A'*A to
//---transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
//---Preconditioned problem is solved with LSQR solver, which
//---gives superior results than normal equations.
for(i=0; i<arows; i++)
{
if(i<xdesign.m_npoints)
buf.m_tmp0.Set(i,xdesign.m_vals.Get(i,bw2+j));
else
buf.m_tmp0.Set(i,0.0);
}
CLinLSQR::LinLSQRRestart(buf.m_solver);
CLinLSQR::LinLSQRSetB(buf.m_solver,buf.m_tmp0);
CLinLSQR::LinLSQRSetCond(buf.m_solver,1.0E-14,1.0E-14,lsqrcnt);
while(CLinLSQR::LinLSQRIteration(buf.m_solver))
{
if(buf.m_solver.m_needmv)
{
//---Use Cholesky factorization of the system matrix
//---as preconditioner: solve TRSV(U,Solver.X)
for(i=0; i<kx*ky; i++)
buf.m_tmp1.Set(i,buf.m_solver.m_x[i]);
BlockLLSTrsV(buf.m_blockata,kx,ky,false,buf.m_tmp1);
//---After preconditioning is done, multiply by A
XDesignMV(xdesign,buf.m_tmp1,buf.m_solver.m_mv);
}
if(buf.m_solver.m_needmtv)
{
//---Multiply by design matrix A
XDesignMTV(xdesign,buf.m_solver.m_x,buf.m_solver.m_mtv);
//---Multiply by preconditioner: solve TRSV(U',A*Solver.X)
BlockLLSTrsV(buf.m_blockata,kx,ky,true,buf.m_solver.m_mtv);
}
}
//---Get results and post-multiply by preconditioner to get
//---original variables.
CLinLSQR::LinLSQRResults(buf.m_solver,buf.m_tmp1,buf.m_solverrep);
BlockLLSTrsV(buf.m_blockata,kx,ky,false,buf.m_tmp1);
for(i=0; i<kx*ky; i++)
z.Set(kx*ky*j+i,buf.m_tmp1[i]);
//---Calculate model values
XDesignMV(xdesign,buf.m_tmp1,buf.m_tmp0);
for(i=0; i<xdesign.m_npoints; i++)
{
v=xdesign.m_vals.Get(i,bw2+j)-buf.m_tmp0[i];
rss+=v*v;
rep.m_rmserror+=CMath::Sqr(v);
rep.m_avgerror+=MathAbs(v);
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(xdesign.m_npoints*d,1.0));
rep.m_avgerror/=CApServ::Coalesce(xdesign.m_npoints*d,1.0);
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
}
//+------------------------------------------------------------------+
//| This function performs fitting with NaiveLLS solver. Internal |
//| function, never use it directly. |
//| INPUT PARAMETERS: |
//| AV - sparse matrix, [ARows,KX*KY] in size. "Vertical" |
//| version of design matrix, rows [0,NPoints] contain |
//| values of basis functions at dataset points. Other |
//| rows are used for nonlinearity penalty and other |
//| stuff like that. |
//| AH - transpose(AV), "horizontal" version of AV |
//| ARows - rows count |
//| XY - array[NPoints*(2+D)], dataset |
//| KX, KY - grid size |
//| NPoints - points count |
//| D - number of components in vector-valued spline, D>=1 |
//| LSQRCnt - number of iterations, non-zero: |
//| * LSQRCnt>0 means that specified amount of |
//| preconditioned LSQR iterations will be performed |
//| to solve problem; usually we need 2..5 its. |
//| Recommended option - best convergence and |
//| stability/quality. |
//| * LSQRCnt<0 means that instead of LSQR we use |
//| iterative refinement on normal equations. Again, |
//| 2..5 its is enough. |
//| Z - possibly preallocated buffer for solution |
//| Rep - report structure; fields which are not set by this |
//| function are left intact |
//| TSS - total sum of squares; used to calculate R2 |
//| OUTPUT PARAMETERS: |
//| XY - destroyed in process |
//| Z - array[KX*KY*D], filled by solution; KX*KY |
//| coefficients corresponding to each of D dimensions |
//| are stored contiguously. |
//| Rep - following fields are set: |
//| * Rep.m_rmserror |
//| * Rep.AvgError |
//| * Rep.MaxError |
//| * Rep.R2 |
//+------------------------------------------------------------------+
void CSpline2D::NaiveLLSFit(CSparseMatrix &av,
CSparseMatrix &ah,
int arows,
CRowDouble &xy,
int kx,
int ky,
int npoints,
int d,
int lsqrcnt,
CRowDouble &z,
CSpline2DFitReport &rep,
double tss)
{
//--- create variables
int ew=0;
int i=0;
int j=0;
int i0=0;
int i1=0;
int j0=0;
int j1=0;
double v=0;
int blockbandwidth=0;
double lambdareg=0;
int srci=0;
int srcj=0;
int idxi=0;
int idxj=0;
int endi=0;
int endj=0;
int rfsidx=0;
CMatrixDouble ata;
CRowDouble tmp0;
CRowDouble tmp1;
double mxata=0;
CLinLSQRState solver;
CLinLSQRReport solverrep;
double rss=0;
blockbandwidth=3;
ew=2+d;
//---Initial values for Z/Residuals
z=vector<double>::Zeros(kx*ky*d);
//---Create and factorize design matrix.
//---Add regularizer if factorization failed (happens sometimes
//---with zero smoothing and sparsely populated datasets).
lambdareg=m_cholreg;
while(true)
{
mxata=0.0;
//---Initialize by zero
ata=matrix<double>::Zeros(kx*ky,kx*ky);
for(i=0; i<kx*ky; i++)
{
for(j=i; j<kx*ky; j++)
{
//---Determine grid nodes corresponding to I and J;
//---skip if too far away
i0=i%kx;
i1=i/kx;
j0=j%kx;
j1=j/kx;
if(MathAbs(i0-j0)>blockbandwidth || MathAbs(i1-j1)>blockbandwidth)
continue;
//---Nodes are close enough, calculate product of columns I and J of A.
v=0;
srci=ah.m_RIdx[i];
srcj=ah.m_RIdx[j];
endi=ah.m_RIdx[i+1];
endj=ah.m_RIdx[j+1];
while(true)
{
if(srci>=endi || srcj>=endj)
break;
idxi=ah.m_Idx[srci];
idxj=ah.m_Idx[srcj];
if(idxi==idxj)
{
v+=ah.m_Vals[srci]*ah.m_Vals[srcj];
srci++;
srcj++;
continue;
}
if(idxi<idxj)
srci++;
else
srcj++;
}
ata.Set(i,j,v);
mxata=MathMax(mxata,MathAbs(v));
}
}
v=CApServ::Coalesce(mxata,1.0)*lambdareg;
for(i=0; i<kx*ky; i++)
ata.Add(i,i,v);
if(CTrFac::SPDMatrixCholesky(ata,kx*ky,true))
{
//---Success!
break;
}
//---Factorization failed, increase regularizer and repeat
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
}
//---Solve
//---NOTE: we expect that Z is zero-filled, and we treat it
//--- like initial approximation to solution.
CApServ::RVectorSetLengthAtLeast(tmp0,arows);
CApServ::RVectorSetLengthAtLeast(tmp1,kx*ky);
if(lsqrcnt>0)
CLinLSQR::LinLSQRCreate(arows,kx*ky,solver);
for(j=0; j<d; j++)
{
//--- check
if(!CAp::Assert(lsqrcnt!=0,__FUNCTION__+": integrity failure"))
return;
if(lsqrcnt>0)
{
//---Preconditioned LSQR:
//---use Cholesky factor U of squared design matrix A'*A to
//---transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
//---Preconditioned problem is solved with LSQR solver, which
//---gives superior results than normal equations.
CLinLSQR::LinLSQRCreate(arows,kx*ky,solver);
for(i=0; i<arows; i++)
{
if(i<npoints)
tmp0.Set(i,xy[i*ew+2+j]);
else
tmp0.Set(i,0.0);
}
CLinLSQR::LinLSQRSetB(solver,tmp0);
CLinLSQR::LinLSQRSetCond(solver,1.0E-14,1.0E-14,lsqrcnt);
while(CLinLSQR::LinLSQRIteration(solver))
{
if(solver.m_needmv)
{
//---Use Cholesky factorization of the system matrix
//---as preconditioner: solve TRSV(U,Solver.X)
tmp1=solver.m_x;
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
//---After preconditioning is done, multiply by A
CSparse::SparseMV(av,tmp1,solver.m_mv);
}
if(solver.m_needmtv)
{
//---Multiply by design matrix A
CSparse::SparseMV(ah,solver.m_x,solver.m_mtv);
//---Multiply by preconditioner: solve TRSV(U',A*Solver.X)
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,1,solver.m_mtv,0);
}
}
CLinLSQR::LinLSQRResults(solver,tmp1,solverrep);
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
for(i=0; i<kx*ky; i++)
z.Set(kx*ky*j+i,tmp1[i]);
//---Calculate model values
CSparse::SparseMV(av,tmp1,tmp0);
for(i=0; i<npoints; i++)
xy.Add(i*ew+2+j,-tmp0[i]);
}
else
{
//---Iterative refinement, inferior to LSQR
//---For each dimension D:
//---* fetch current estimate for solution from Z to Tmp1
//---* calculate residual r for current estimate, store in Tmp0
//---* calculate product of residual and design matrix A'*r, store it in Tmp1
//---* Cholesky solver
//---* update current estimate
for(rfsidx=1; rfsidx<=-lsqrcnt; rfsidx++)
{
for(i=0; i<=kx*ky-1; i++)
tmp1.Set(i,z[kx*ky*j+i]);
CSparse::SparseMV(av,tmp1,tmp0);
for(i=0; i<arows; i++)
{
if(i<npoints)
v=xy[i*ew+2+j];
else
v=0;
tmp0.Set(i,v-tmp0[i]);
}
CSparse::SparseMV(ah,tmp0,tmp1);
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,1,tmp1,0);
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
for(i=0; i<kx*ky; i++)
z.Add(kx*ky*j+i,tmp1[i]);
}
//---Calculate model values
for(i=0; i<kx*ky; i++)
tmp1.Set(i,z[kx*ky*j+i]);
CSparse::SparseMV(av,tmp1,tmp0);
for(i=0; i<npoints; i++)
xy.Add(i*ew+2+j,-tmp0[i]);
}
}
//---Generate report
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_maxerror=0;
rss=0.0;
for(i=0; i<npoints; i++)
{
for(j=0; j<d; j++)
{
v=xy[i*ew+2+j];
rss=rss+v*v;
rep.m_rmserror+=CMath::Sqr(v);
rep.m_avgerror+=MathAbs(v);
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(npoints*d,1.0));
rep.m_avgerror=rep.m_avgerror/CApServ::Coalesce(npoints*d,1.0);
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
}
//+------------------------------------------------------------------+
//| This is convenience function for band block storage format; it |
//| returns offset of KX*KX-sized block (I,J) in a compressed 2D |
//| array. |
//| For specific offset=OFFSET, block (I,J) will be stored in entries|
//| BlockMatrix[OFFSET:OFFSET+KX-1,0:KX-1] |
//+------------------------------------------------------------------+
int CSpline2D::GetCellOffset(int kx,
int ky,
int blockbandwidth,
int i,
int j)
{
int result=0;
//--- check
if(!CAp::Assert(i>=0 && i<ky,__FUNCTION__+": GetCellOffset() integrity error"))
return(0);
if(!CAp::Assert(j>=0 && j<ky,__FUNCTION__+": GetCellOffset() integrity error"))
return(0);
if(!CAp::Assert(j>=i && j<=i+blockbandwidth,__FUNCTION__+": GetCellOffset() integrity error"))
return(0);
result=j*(blockbandwidth+1)*kx;
result+=(blockbandwidth-(j-i))*kx;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This is convenience function for band block storage format; it |
//| copies cell (I,J) from compressed format to uncompressed general |
//| matrix, at desired position. |
//+------------------------------------------------------------------+
void CSpline2D::CopyCellTo(int kx,int ky,int blockbandwidth,
CMatrixDouble &blockata,
int i,int j,
CMatrixDouble &dst,
int dst0,
int dst1)
{
int celloffset=GetCellOffset(kx,ky,blockbandwidth,i,j);
for(int idx0=0; idx0<kx; idx0++)
for(int idx1=0; idx1<kx; idx1++)
dst.Set(dst0+idx0,dst1+idx1,blockata.Get(celloffset+idx0,idx1));
}
//+------------------------------------------------------------------+
//| This is convenience function for band block storage format; it |
//| truncates all elements of cell (I,J) which are less than Eps in |
//| magnitude. |
//+------------------------------------------------------------------+
void CSpline2D::FlushToZeroCell(int kx,int ky,int blockbandwidth,
CMatrixDouble &blockata,int i,
int j,double eps)
{
int celloffset=GetCellOffset(kx,ky,blockbandwidth,i,j);
double eps2=eps*eps;
for(int idx0=0; idx0<kx; idx0++)
{
for(int idx1=0; idx1<kx; idx1++)
{
double v=blockata.Get(celloffset+idx0,idx1);
if(v*v<eps2)
blockata.Set(celloffset+idx0,idx1,0);
}
}
}
//+------------------------------------------------------------------+
//| This function generates squared design matrix stored in block |
//| band format. |
//| We use adaptation of block skyline storage format, with |
//| TOWERSIZE*KX skyline bands (towers) stored sequentially; here |
//| TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY "towers", stored |
//| one below other, in BlockATA matrix. Every "tower" is a sequence |
//| of BlockBandwidth+1 cells, each of them being KX*KX in size. |
//| INPUT PARAMETERS: |
//| AH - sparse matrix, [KX*KY,ARows] in size. "Horizontal" |
//| version of design matrix, cols [0,NPoints] contain |
//| values of basis functions at dataset points. Other |
//| cols are used for nonlinearity penalty and other |
//| stuff like that. |
//| KY0, KY1 - subset of output matrix bands to process; on entry |
//| it MUST be set to 0 and KY respectively. |
//| KX, KY - grid size |
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], preallocated |
//| storage for output matrix in compressed block band |
//| format |
//| MXATA - on entry MUST be zero |
//| OUTPUT PARAMETERS: |
//| BlockATA - AH*AH', stored in compressed block band format |
//+------------------------------------------------------------------+
void CSpline2D::BlockLLSGenerateATA(CSparseMatrix &ah,int ky0,
int ky1,int kx,int ky,
CMatrixDouble &blockata,
double mxata)
{
//--- create variables
int blockbandwidth=3;
double avgrowlen=0;
double cellcost=0;
double totalcost=0;
double tmpmxata=0;
int i=0;
int j=0;
int i1=0;
int celloffset=0;
double v=0;
int srci=0;
int srcj=0;
int idxi=0;
int idxj=0;
int endi=0;
int endj=0;
//--- check
if(!CAp::Assert(mxata>=0.0,__FUNCTION__+": integrity check failed"))
return;
//---Determine problem cost, perform recursive subdivision
//---(with optional parallelization)
avgrowlen=(double)ah.m_RIdx[kx*ky]/(double)(kx*ky);
cellcost=kx*(1+2*blockbandwidth)*avgrowlen;
totalcost=(ky1-ky0)*(1+2*blockbandwidth)*cellcost;
if(ky1-ky0>=2)
{
//---Split X: X*A = (X1 X2)^T*A
j=(ky1-ky0)/2;
BlockLLSGenerateATA(ah,ky0,ky0+j,kx,ky,blockata,tmpmxata);
BlockLLSGenerateATA(ah,ky0+j,ky1,kx,ky,blockata,mxata);
mxata=MathMax(mxata,tmpmxata);
return;
}
//---Splitting in Y-dimension is done, fill I1-th "tower"
//--- check
if(!CAp::Assert(ky1==ky0+1,__FUNCTION__+": integrity check failed"))
return;
i1=ky0;
for(int j1=i1; j1<=MathMin(ky-1,i1+blockbandwidth); j1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,j1);
//---Clear cell (I1,J1)
for(int i0=0; i0<kx; i0++)
for(int j0=0; j0<kx; j0++)
blockata.Set(celloffset+i0,j0,0.0);
//---Initialize cell internals
for(int i0=0; i0<kx; i0++)
for(int j0=0; j0<kx; j0++)
if(MathAbs(i0-j0)<=blockbandwidth)
{
//---Nodes are close enough, calculate product of columns I and J of A.
v=0;
i=i1*kx+i0;
j=j1*kx+j0;
srci=ah.m_RIdx[i];
srcj=ah.m_RIdx[j];
endi=ah.m_RIdx[i+1];
endj=ah.m_RIdx[j+1];
while(true)
{
if(srci>=endi || srcj>=endj)
break;
idxi=ah.m_Idx[srci];
idxj=ah.m_Idx[srcj];
if(idxi==idxj)
{
v+=ah.m_Vals[srci]*ah.m_Vals[srcj];
srci++;
srcj++;
continue;
}
if(idxi<idxj)
srci++;
else
srcj++;
}
blockata.Set(celloffset+i0,j0,v);
mxata=MathMax(mxata,MathAbs(v));
}
}
}
//+------------------------------------------------------------------+
//| This function performs Cholesky decomposition of squared design |
//| matrix stored in block band format. |
//| INPUT PARAMETERS: |
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], matrix in |
//| compressed block band format |
//| KX, KY - grid size |
//| TrsmBuf2, |
//| CholBuf2, |
//| CholBuf1 - buffers; reused by this function on subsequent |
//| calls, automatically preallocated on the first |
//| call |
//| OUTPUT PARAMETERS: |
//| BlockATA - Cholesky factor, in compressed block band format|
//| Result: |
//| True on success, False on Cholesky failure |
//+------------------------------------------------------------------+
bool CSpline2D::BlockLLSCholesky(CMatrixDouble &blockata,int kx,
int ky,
CMatrixDouble &trsmbuf2,
CMatrixDouble &cholbuf2,
CRowDouble &cholbuf1)
{
//--- create variables
int blockbandwidth=3;
int celloffset=0;
int celloffset1=0;
CApServ::RMatrixSetLengthAtLeast(trsmbuf2,(blockbandwidth+1)*kx,(blockbandwidth+1)*kx);
CApServ::RMatrixSetLengthAtLeast(cholbuf2,kx,kx);
CApServ::RVectorSetLengthAtLeast(cholbuf1,kx);
for(int blockidx=0; blockidx<ky; blockidx++)
{
//---TRSM for TRAIL*TRAIL block matrix before current cell;
//---here TRAIL=MinInt(BlockIdx,BlockBandwidth).
for(int i=0; i<MathMin(blockidx,blockbandwidth); i++)
for(int j=i; j<MathMin(blockidx,blockbandwidth); j++)
CopyCellTo(kx,ky,blockbandwidth,blockata,MathMax(blockidx-blockbandwidth,0)+i,MathMax(blockidx-blockbandwidth,0)+j,trsmbuf2,i*kx,j*kx);
celloffset=GetCellOffset(kx,ky,blockbandwidth,MathMax(blockidx-blockbandwidth,0),blockidx);
CAblas::RMatrixLeftTrsM(MathMin(blockidx,blockbandwidth)*kx,kx,trsmbuf2,0,0,true,false,1,blockata,celloffset,0);
//---SYRK for diagonal cell: MaxInt(BlockIdx-BlockBandwidth,0)
//---cells above diagonal one are used for update.
celloffset=GetCellOffset(kx,ky,blockbandwidth,MathMax(blockidx-blockbandwidth,0),blockidx);
celloffset1=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
CAblas::RMatrixSyrk(kx,MathMin(blockidx,blockbandwidth)*kx,-1.0,blockata,celloffset,0,1,1.0,blockata,celloffset1,0,true);
//---Factorize diagonal cell
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
CAblas::RMatrixCopy(kx,kx,blockata,celloffset,0,cholbuf2,0,0);
if(!CTrFac::SPDMatrixCholeskyRec(cholbuf2,0,kx,true,cholbuf1))
return(false);
CAblas::RMatrixCopy(kx,kx,cholbuf2,0,0,blockata,celloffset,0);
//---PERFORMANCE TWEAK: drop nearly-denormals from last "tower".
//---Sparse matrices like these may produce denormal numbers on
//---sparse datasets, with significant (10x!) performance penalty
//---on Intel chips. In order to avoid it, we manually truncate
//---small enough numbers.
//---We use 1.0E-50 as clipping level (not really denormal, but
//---such small numbers are not actually important anyway).
for(int i=MathMax(blockidx-blockbandwidth,0); i<=blockidx; i++)
FlushToZeroCell(kx,ky,blockbandwidth,blockata,i,blockidx,1.0E-50);
}
//--- return result
return(true);
}
//+------------------------------------------------------------------+
//| This function performs TRSV on upper triangular Cholesky factor |
//| U, solving either U*x=b or U'*x=b. |
//| INPUT PARAMETERS: |
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], matrix U in |
//| compressed block band format |
//| KX, KY - grid size |
//| TransU - whether to transpose U or not |
//| B - array[KX*KY], on entry - stores right part B |
//| OUTPUT PARAMETERS: |
//| B - replaced by X |
//+------------------------------------------------------------------+
void CSpline2D::BlockLLSTrsV(CMatrixDouble &blockata,int kx,int ky,
bool transu,CRowDouble &b)
{
//--- create variables
int blockbandwidth=3;
int celloffset=0;
if(!transu)
{
//---Solve U*x=b
for(int blockidx=ky-1; blockidx>=0; blockidx--)
{
for(int blockidx1=1; blockidx1<=MathMin(ky-(blockidx+1),blockbandwidth); blockidx1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx+blockidx1);
CAblas::RMatrixGemVect(kx,kx,-1.0,blockata,celloffset,0,0,b,(blockidx+blockidx1)*kx,1.0,b,blockidx*kx);
}
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
CAblas::RMatrixTrsVect(kx,blockata,celloffset,0,true,false,0,b,blockidx*kx);
}
}
else
{
//---Solve U'*x=b
for(int blockidx=0; blockidx<ky; blockidx++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
CAblas::RMatrixTrsVect(kx,blockata,celloffset,0,true,false,1,b,blockidx*kx);
for(int blockidx1=1; blockidx1<=MathMin(ky-(blockidx+1),blockbandwidth); blockidx1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx+blockidx1);
CAblas::RMatrixGemVect(kx,kx,-1.0,blockata,celloffset,0,1,b,blockidx*kx,1.0,b,(blockidx+blockidx1)*kx);
}
}
}
}
//+------------------------------------------------------------------+
//| This function computes residuals for dataset XY[], using array of|
//| original values YRaw[], and loads residuals to XY. |
//| Processing is performed in parallel manner. |
//+------------------------------------------------------------------+
void CSpline2D::ComputeResidualsFromScratch(CRowDouble &xy,
CRowDouble &yraw,
int npoints,int d,
int scalexy,
CSpline2DInterpolant &spline)
{
//--- create variables
CRowDouble seed;
CRowDouble pool;
int chunksize=0;
double pointcost=0;
//---Setting up
chunksize=1000;
pointcost=100.0;
pool=seed;
//---Call compute workhorse
ComputeResidualsFromScratchRec(xy,yraw,0,npoints,chunksize,d,scalexy,spline,pool);
}
//+------------------------------------------------------------------+
//| Recursive workhorse for ComputeResidualsFromScratch. |
//+------------------------------------------------------------------+
void CSpline2D::ComputeResidualsFromScratchRec(CRowDouble &xy,
CRowDouble &yraw,
int pt0,
int pt1,
int chunksize,
int d,
int scalexy,
CSpline2DInterpolant &spline,
CRowDouble &pool)
{
//--- create variables
int xew=2+d;
int i=0;
int j=0;
CRowDouble pbuf;
//---Parallelism
if(pt1-pt0>chunksize)
{
CApServ::TiledSplit(pt1-pt0,chunksize,i,j);
ComputeResidualsFromScratchRec(xy,yraw,pt0,pt0+i,chunksize,d,scalexy,spline,pool);
ComputeResidualsFromScratchRec(xy,yraw,pt0+i,pt1,chunksize,d,scalexy,spline,pool);
return;
}
//---Serial execution
pbuf=pool;
for(i=pt0; i<pt1; i++)
{
Spline2DCalcVBuf(spline,xy[i*xew+0]*scalexy,xy[i*xew+1]*scalexy,pbuf);
for(j=0; j<d; j++)
xy.Set(i*xew+2+j,yraw[i*d+j]-pbuf[j]);
}
pool=pbuf;
}
//+------------------------------------------------------------------+
//| This function reorders dataset and builds index: |
//| * it is assumed that all points have X in [0,KX-1], |
//| Y in [0,KY-1] |
//| * area is divided into (KX-1)*(KY-1) cells |
//| * all points are reordered in such way that points in same cell|
//| are stored contiguously |
//| * dataset index, array[(KX-1)*(KY-1)+1], is generated. Points |
//| of cell I now have indexes XYIndex[I]..XYIndex[I+1]-1; |
//| INPUT PARAMETERS: |
//| XY - array[NPoints*(2+D)], dataset |
//| KX, KY, D - grid size and dimensionality of the outputs |
//| Shadow - shadow array[NPoints*NS], which is sorted |
//| together with XY; if NS=0, it is not referenced |
//| at all. |
//| NS - entry width of shadow array |
//| BufI - possibly preallocated temporary buffer; resized |
//| if needed. |
//| OUTPUT PARAMETERS: |
//| XY - reordered |
//| XYIndex - array[(KX-1)*(KY-1)+1], dataset index |
//+------------------------------------------------------------------+
void CSpline2D::ReorderDatasetAndBuildIndex(CRowDouble &xy,
int npoints,
int d,
CRowDouble &shadow,
int ns,int kx,
int ky,
CRowInt &xyindex,
CRowInt &bufi)
{
//--- create variables
int i0=0;
int i1=0;
int entrywidth=0;
//---Set up
//--- check
if(!CAp::Assert(kx>=2,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(ky>=2,__FUNCTION__+": integrity check failed"))
return;
entrywidth=2+d;
CApServ::IVectorSetLengthAtLeast(xyindex,(kx-1)*(ky-1)+1);
CApServ::IVectorSetLengthAtLeast(bufi,npoints);
for(int i=0; i<npoints; i++)
{
i0=CApServ::BoundVal((int)MathFloor(xy[i*entrywidth+0]),0,kx-2);
i1=CApServ::BoundVal((int)MathFloor(xy[i*entrywidth+1]),0,ky-2);
bufi.Set(i,i1*(kx-1)+i0);
}
//---Reorder
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,bufi,0,npoints,xyindex,0,(kx-1)*(ky-1),true);
xyindex.Set((kx-1)*(ky-1),npoints);
}
//+------------------------------------------------------------------+
//| This function multiplies all points in dataset by 2.0 and |
//| rebuilds index, given previous index built for KX_prev=(KX-1)/2 |
//| and KY_prev=(KY-1)/2 |
//| INPUT PARAMETERS: |
//| XY - array[NPoints*(2+D)], dataset BEFORE scaling |
//| NPoints, D - dataset size and dimensionality of the outputs |
//| Shadow - shadow array[NPoints*NS], which is sorted |
//| together with XY; if NS=0, it is not referenced |
//| at all. |
//| NS - entry width of shadow array |
//| KX, KY - new grid dimensionality |
//| XYIndex - index built for previous values of KX and KY |
//| BufI - possibly preallocated temporary buffer; resized |
//| if needed. |
//| OUTPUT PARAMETERS: |
//| XY - reordered and multiplied by 2.0 |
//| XYIndex - array[(KX-1)*(KY-1)+1], dataset index |
//+------------------------------------------------------------------+
void CSpline2D::RescaleDatasetAndRefineIndex(CRowDouble &xy,
int npoints,int d,
CRowDouble &shadow,
int ns,int kx,int ky,
CRowInt &xyindex,
CRowInt &bufi)
{
CRowInt xyindexprev;
//---Set up
//--- check
if(!CAp::Assert(kx>=2,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(ky>=2,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert((kx-1)%2==0,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert((ky-1)%2==0,__FUNCTION__+": integrity check failed"))
return;
CAp::Swap(xyindex,xyindexprev);
CApServ::IVectorSetLengthAtLeast(xyindex,(kx-1)*(ky-1)+1);
CApServ::IVectorSetLengthAtLeast(bufi,npoints);
//---Refine
ExpandIndexRows(xy,d,shadow,ns,bufi,0,npoints,xyindexprev,0,(ky+1)/2-1,xyindex,kx,ky,true);
xyindex.Set((kx-1)*(ky-1),npoints);
}
//+------------------------------------------------------------------+
//| Recurrent divide-and-conquer indexing function |
//+------------------------------------------------------------------+
void CSpline2D::ExpandIndexRows(CRowDouble &xy,int d,
CRowDouble &shadow,int ns,
CRowInt &cidx,int pt0,int pt1,
CRowInt &xyindexprev,int row0,
int row1,CRowInt &xyindexnew,
int kxnew,int kynew,bool rootcall)
{
//--- create variables
int entrywidth=0;
int kxprev=0;
double v=0;
int i0=0;
int i1=0;
double efficiency=0;
double cost=0;
int rowmid=0;
kxprev=(kxnew+1)/2;
entrywidth=2+d;
efficiency=0.1;
cost=d*(pt1-pt0+1)*(MathLog(kxnew)/MathLog(2))/efficiency;
//--- check
if(!CAp::Assert(xyindexprev[row0*(kxprev-1)+0]==pt0,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(xyindexprev[row1*(kxprev-1)+0]==pt1,__FUNCTION__+": integrity check failed"))
return;
//---Partition
if(row1-row0>=2)
{
CApServ::TiledSplit(row1-row0,1,i0,i1);
rowmid=row0+i0;
ExpandIndexRows(xy,d,shadow,ns,cidx,pt0,xyindexprev[rowmid*(kxprev-1)+0],xyindexprev,row0,rowmid,xyindexnew,kxnew,kynew,false);
ExpandIndexRows(xy,d,shadow,ns,cidx,xyindexprev[rowmid*(kxprev-1)+0],pt1,xyindexprev,rowmid,row1,xyindexnew,kxnew,kynew,false);
return;
}
//---Serial execution
for(int i=pt0; i<pt1; i++)
{
v=2*xy[i*entrywidth+0];
xy.Set(i*entrywidth,v);
i0=CApServ::BoundVal((int)MathFloor(v),0,kxnew-2);
v=2*xy[i*entrywidth+1];
xy.Set(i*entrywidth+1,v);
i1=CApServ::BoundVal((int)MathFloor(v),0,kynew-2);
cidx.Set(i,i1*(kxnew-1)+i0);
}
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,pt0,pt1,xyindexnew,2*row0*(kxnew-1)+0,2*row1*(kxnew-1)+0,false);
}
//+------------------------------------------------------------------+
//| Recurrent divide-and-conquer indexing function |
//+------------------------------------------------------------------+
void CSpline2D::ReorderDatasetAndBuildIndexRec(CRowDouble &xy,
int d,
CRowDouble &shadow,
int ns,
CRowInt &cidx,
int pt0,int pt1,
CRowInt &xyindex,
int idx0,int idx1,
bool rootcall)
{
//--- create variables
int idxmid=0;
int wrk0=0;
int wrk1=0;
//---Efficiency - performance of the code when compared with that
//---of linear algebra code.
int entrywidth=2+d;
double efficiency=0.1;
double cost=d*(pt1-pt0+1)*MathLog(idx1-idx0+1)/MathLog(2)/efficiency;
//---Store left bound to XYIndex
xyindex.Set(idx0,pt0);
//---Quick exit strategies
if(idx1<=idx0+1)
return;
if(pt0==pt1)
{
xyindex.Fill(pt1,idx0+1,idx1-idx0);
return;
}
//---Select middle element
idxmid=idx0+(idx1-idx0)/2;
//--- check
if(!CAp::Assert(idx0<idxmid && idxmid<idx1,__FUNCTION__+": integrity check failed"))
return;
wrk0=pt0;
wrk1=pt1-1;
while(true)
{
while(wrk0<pt1 && cidx[wrk0]<idxmid)
wrk0++;
while(wrk1>=pt0 && cidx[wrk1]>=idxmid)
wrk1--;
if(wrk1<=wrk0)
break;
CApServ::SwapEntries(xy,wrk0,wrk1,entrywidth);
if(ns>0)
CApServ::SwapEntries(shadow,wrk0,wrk1,ns);
CApServ::SwapElementsI(cidx,wrk0,wrk1);
}
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,pt0,wrk0,xyindex,idx0,idxmid,false);
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,wrk0,pt1,xyindex,idxmid,idx1,false);
}
//+------------------------------------------------------------------+
//| This function performs fitting with BlockLLS solver. Internal |
//| function, never use it directly. |
//| INPUT PARAMETERS: |
//| XY - dataset, array[NPoints,2+D] |
//| XYIndex - dataset index, see ReorderDatasetAndBuildIndex() |
//| for more info |
//| KX0, KX1 - X-indices of basis functions to select and fit; |
//| range [KX0,KX1) is processed |
//| KXTotal - total number of indexes in the entire grid |
//| KY0, KY1 - Y-indices of basis functions to select and fit; |
//| range [KY0,KY1) is processed |
//| KYTotal - total number of indexes in the entire grid |
//| D - number of components in vector-valued spline, D>=1 |
//| LambdaReg- regularization coefficient |
//| LambdaNS - nonlinearity penalty, exactly zero value is |
//| specially handled (entire set of rows is not added |
//| to the matrix) |
//| Basis1 - single-dimensional B-spline |
//| OUTPUT PARAMETERS: |
//| A - design matrix |
//+------------------------------------------------------------------+
void CSpline2D::XDesignGenerate(CRowDouble &xy,CRowInt &xyindex,
int kx0,int kx1,int kxtotal,
int ky0,int ky1,int kytotal,
int d,double lambdareg,
double lambdans,
CSpline1DInterpolant &basis1,
CSpline2DXDesignMatrix &a)
{
//--- create variables
int entrywidth=0;
int i=0;
int j=0;
int j0=0;
int j1=0;
int k0=0;
int k1=0;
int kx=0;
int ky=0;
int rowsdone=0;
int batchesdone=0;
int pt0=0;
int pt1=0;
int base0=0;
int base1=0;
int baseidx=0;
int nzshift=0;
int nzwidth=0;
CMatrixDouble d2x;
CMatrixDouble d2y;
CMatrixDouble dxy;
double v=0;
double v0=0;
double v1=0;
double v2=0;
double w0=0;
double w1=0;
double w2=0;
nzshift=1;
nzwidth=4;
entrywidth=2+d;
kx=kx1-kx0;
ky=ky1-ky0;
a.m_lambdareg=lambdareg;
a.m_blockwidth=4;
a.m_kx=kx;
a.m_ky=ky;
a.m_d=d;
a.m_npoints=0;
a.m_ndenserows=0;
a.m_ndensebatches=0;
a.m_maxbatch=0;
for(j1=ky0; j1<ky1-1; j1++)
{
for(j0=kx0; j0<kx1-1; j0++)
{
i=xyindex[j1*(kxtotal-1)+j0+1]-xyindex[j1*(kxtotal-1)+j0];
a.m_npoints+=i;
a.m_ndenserows+=i;
a.m_ndensebatches+=1;
a.m_maxbatch=MathMax(a.m_maxbatch,i);
}
}
if(lambdans!=0.0)
{
//--- check
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": integrity check failed"))
return;
a.m_ndenserows+=3*(kx-2)*(ky-2);
a.m_ndensebatches+=(kx-2)*(ky-2);
a.m_maxbatch=MathMax(a.m_maxbatch,3);
}
a.m_nrows=a.m_ndenserows+kx*ky;
CApServ::RMatrixSetLengthAtLeast(a.m_vals,a.m_ndenserows,a.m_blockwidth*a.m_blockwidth+d);
CApServ::IVectorSetLengthAtLeast(a.m_batches,a.m_ndensebatches+1);
CApServ::IVectorSetLengthAtLeast(a.m_batchbases,a.m_ndensebatches);
//---Setup output counters
batchesdone=0;
rowsdone=0;
//---Generate rows corresponding to dataset points
//--- check
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
return;
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth);
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,nzwidth);
a.m_batches.Set(batchesdone,0);
for(j1=ky0; j1<ky1-1; j1++)
{
for(j0=kx0; j0<kx1-1; j0++)
{
pt0=xyindex[j1*(kxtotal-1)+j0];
pt1=xyindex[j1*(kxtotal-1)+j0+1];
base0=CApServ::BoundVal(j0-kx0-nzshift,0,kx-nzwidth);
base1=CApServ::BoundVal(j1-ky0-nzshift,0,ky-nzwidth);
baseidx=base1*kx+base0;
a.m_batchbases.Set(batchesdone,baseidx);
for(i=pt0; i<pt1; i++)
{
for(k0=0; k0<nzwidth; k0++)
a.m_tmp0.Set(k0,CSpline1D::Spline1DCalc(basis1,xy[i*entrywidth]-(base0+kx0+k0)));
for(k1=0; k1<nzwidth; k1++)
a.m_tmp1.Set(k1,CSpline1D::Spline1DCalc(basis1,xy[i*entrywidth+1]-(base1+ky0+k1)));
for(k1=0; k1<nzwidth; k1++)
for(k0=0; k0<nzwidth; k0++)
a.m_vals.Set(rowsdone,k1*nzwidth+k0,a.m_tmp0[k0]*a.m_tmp1[k1]);
for(j=0; j<d; j++)
a.m_vals.Set(rowsdone,nzwidth*nzwidth+j,xy[i*entrywidth+2+j]);
rowsdone++;
}
batchesdone++;
a.m_batches.Set(batchesdone,rowsdone);
}
}
//---Generate rows corresponding to nonlinearity penalty
if(lambdans>0.0)
{
//---Smoothing is applied. Because all grid nodes are same,
//---we apply same smoothing kernel, which is calculated only
//---once at the beginning of design matrix generation.
d2x=matrix<double>::Zeros(3,3);
d2y=matrix<double>::Zeros(3,3);
dxy=matrix<double>::Zeros(3,3);
for(k1=0; k1<=2; k1++)
for(k0=0; k0<=2; k0++)
{
CSpline1D::Spline1DDiff(basis1,-(k0-1),v0,v1,v2);
CSpline1D::Spline1DDiff(basis1,-(k1-1),w0,w1,w2);
d2x.Add(k0,k1,v2*w0);
d2y.Add(k0,k1,w2*v0);
dxy.Add(k0,k1,v1*w1);
}
//---Now, kernel is ready - apply it to all inner nodes of the grid.
for(j1=1; j1<=ky-2; j1++)
{
for(j0=1; j0<=kx-2; j0++)
{
base0=MathMax(j0-2,0);
base1=MathMax(j1-2,0);
baseidx=base1*kx+base0;
a.m_batchbases.Set(batchesdone,baseidx);
//---d2F/dx2 term
v=lambdans;
for(j=0; j<nzwidth*nzwidth+d; j++)
a.m_vals.Set(rowsdone,j,0);
for(k1=j1-1; k1<=j1+1; k1++)
{
for(k0=j0-1; k0<=j0+1; k0++)
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*d2x.Get(1+(k0-j0),1+(k1-j1)));
}
rowsdone++;
//---d2F/dy2 term
v=lambdans;
for(j=0; j<nzwidth*nzwidth+d; j++)
a.m_vals.Set(rowsdone,j,0);
for(k1=j1-1; k1<=j1+1; k1++)
{
for(k0=j0-1; k0<=j0+1; k0++)
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*d2y.Get(1+(k0-j0),1+(k1-j1)));
}
rowsdone++;
//---2*d2F/dxdy term
v=MathSqrt(2)*lambdans;
for(j=0; j<nzwidth*nzwidth+d; j++)
a.m_vals.Set(rowsdone,j,0);
for(k1=j1-1; k1<=j1+1; k1++)
{
for(k0=j0-1; k0<=j0+1; k0++)
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*dxy.Get(1+(k0-j0),1+(k1-j1)));
}
rowsdone++;
batchesdone++;
a.m_batches.Set(batchesdone,rowsdone);
}
}
}
//---Integrity post-check
if(!CAp::Assert(batchesdone==a.m_ndensebatches,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(rowsdone==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
return;
}
//+------------------------------------------------------------------+
//| This function performs matrix-vector product of design matrix and|
//| dense vector. |
//| INPUT PARAMETERS: |
//| A - design matrix, (a.m_nrows) X (a.m_kx*a.m_ky); |
//| some fields of A are used for temporaries, so it |
//| is non-constant. |
//| X - array[A.KX*A.KY] |
//| OUTPUT PARAMETERS: |
//| Y - product, array[A.NRows], automatically allocated |
//+------------------------------------------------------------------+
void CSpline2D::XDesignMV(CSpline2DXDesignMatrix &a,CRowDouble &x,
CRowDouble &y)
{
//--- create variables
int cnt=0;
double v=0;
int baseidx=0;
int outidx=0;
int batchsize=0;
int kx=0;
int k0=0;
int k1=0;
int nzwidth=4;
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(CAp::Len(x)>=a.m_kx*a.m_ky,__FUNCTION__+": integrity check failed"))
return;
//---Prepare
CApServ::RVectorSetLengthAtLeast(y,a.m_nrows);
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth*nzwidth);
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,a.m_maxbatch);
kx=a.m_kx;
outidx=0;
//---Process dense part
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
{
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
{
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
baseidx=a.m_batchbases[bidx];
for(k1=0; k1<nzwidth; k1++)
{
for(k0=0; k0<nzwidth; k0++)
a.m_tmp0.Set(k1*nzwidth+k0,x[baseidx+k1*kx+k0]);
}
CAblas::RMatrixGemVect(batchsize,nzwidth*nzwidth,1.0,a.m_vals,a.m_batches[bidx],0,0,a.m_tmp0,0,0.0,a.m_tmp1,0);
for(int i=0; i<batchsize; i++)
y.Set(outidx+i,a.m_tmp1[i]);
outidx+=batchsize;
}
}
//--- check
if(!CAp::Assert(outidx==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
return;
//---Process regularizer
v=a.m_lambdareg;
cnt=a.m_kx*a.m_ky;
for(int i=0; i<cnt; i++)
y.Set(outidx+i,v*x[i]);
outidx+=cnt;
//---Post-check
if(!CAp::Assert(outidx==a.m_nrows,__FUNCTION__+": integrity check failed"))
return;
}
//+------------------------------------------------------------------+
//| This function performs matrix-vector product of transposed design|
//| matrix and dense vector. |
//| INPUT PARAMETERS: |
//| A - design matrix, (a.m_nrows) X (a.m_kx*a.m_ky); |
//| some fields of A are used for temporaries, so |
//| it is non-constant. |
//| X - array[A.NRows] |
//| OUTPUT PARAMETERS: |
//| Y - product, array[A.KX*A.KY], automatically |
//| allocated |
//+------------------------------------------------------------------+
void CSpline2D::XDesignMTV(CSpline2DXDesignMatrix &a,
CRowDouble &x,
CRowDouble &y)
{
//--- create variables
int cnt=0;
double v=0;
int baseidx=0;
int inidx=0;
int batchsize=0;
int kx=0;
int nzwidth=4;
//--- check
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(CAp::Len(x)>=a.m_nrows,__FUNCTION__+": integrity check failed"))
return;
//---Prepare
CApServ::RVectorSetLengthAtLeast(y,a.m_kx*a.m_ky);
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth*nzwidth);
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,a.m_maxbatch);
kx=a.m_kx;
inidx=0;
cnt=a.m_kx*a.m_ky;
y.Fill(0);
//---Process dense part
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
{
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
{
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
baseidx=a.m_batchbases[bidx];
for(int i=0; i<batchsize ; i++)
a.m_tmp1.Set(i,x[inidx+i]);
CAblas::RMatrixGemVect(nzwidth*nzwidth,batchsize,1.0,a.m_vals,a.m_batches[bidx],0,1,a.m_tmp1,0,0.0,a.m_tmp0,0);
for(int k1=0; k1<nzwidth; k1++)
{
for(int k0=0; k0<nzwidth; k0++)
y.Add(baseidx+k1*kx+k0,a.m_tmp0[k1*nzwidth+k0]);
}
inidx=inidx+batchsize;
}
}
//--- check
if(!CAp::Assert(inidx==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
return;
//---Process regularizer
v=a.m_lambdareg;
cnt=a.m_kx*a.m_ky;
for(int i=0; i<cnt; i++)
y.Add(i,v*x[inidx+i]);
inidx+=cnt;
//---Post-check
CAp::Assert(inidx==a.m_nrows,__FUNCTION__+": integrity check failed");
}
//+------------------------------------------------------------------+
//| This function generates squared design matrix stored in block |
//| band format. |
//| We use an adaptation of block skyline storage format, with|
//| TOWERSIZE*KX skyline bands (towers) stored sequentially; here |
//| TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY "towers", stored |
//| one below other, in BlockATA matrix. Every "tower" is a sequence |
//| of BlockBandwidth+1 cells, each of them being KX*KX in size. |
//| INPUT PARAMETERS: |
//| A - design matrix; some of its fields are used for |
//| temporaries |
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], preallocated |
//| storage for output matrix in compressed block band |
//| format |
//| OUTPUT PARAMETERS: |
//| BlockATA - AH*AH', stored in compressed block band format |
//| MXATA - max(|AH*AH'|), elementwise |
//+------------------------------------------------------------------+
void CSpline2D::XDesignBlockATA(CSpline2DXDesignMatrix &a,
CMatrixDouble &blockata,
double &mxata)
{
//--- create variables
int blockbandwidth=3;
int nzwidth=4;
int kx=a.m_kx;
int ky=a.m_ky;
int celloffset=0;
int baseidx=0;
int batchsize=0;
int offs0=0;
int offs1=0;
double v=0;
//--- check
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
return;
CApServ::RMatrixSetLengthAtLeast(a.m_tmp2,nzwidth*nzwidth,nzwidth*nzwidth);
//---Initial zero-fill:
//---* zero-fill ALL elements of BlockATA
//---* zero-fill ALL elements of Tmp2
//---Filling ALL elements, including unused ones, is essential for the
//---purposes of calculating max(BlockATA).
for(int i1=0; i1<=ky-1; i1++)
{
for(int i0=i1; i0<=MathMin(ky-1,i1+blockbandwidth); i0++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i0);
for(int j1=0; j1<=kx-1; j1++)
for(int j0=0; j0<=kx-1; j0++)
blockata.Set(celloffset+j1,j0,0.0);
}
}
for(int j1=0; j1<nzwidth*nzwidth; j1++)
{
for(int j0=0; j0<nzwidth*nzwidth; j0++)
a.m_tmp2.Set(j1,j0,0.0);
}
//---Process dense part of A
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
{
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
{
//---Generate 16x16 U = BATCH'*BATCH and add it to ATA.
//---NOTE: it is essential that lower triangle of Tmp2 is
//--- filled by zeros.
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
CAblas::RMatrixSyrk(nzwidth*nzwidth,batchsize,1.0,a.m_vals,a.m_batches[bidx],0,2,0.0,a.m_tmp2,0,0,true);
baseidx=a.m_batchbases[bidx];
for(int i1=0; i1<nzwidth; i1++)
{
for(int j1=i1; j1<nzwidth; j1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,baseidx/kx+i1,baseidx/kx+j1);
offs0=baseidx%kx;
offs1=baseidx%kx;
for(int i0=0; i0<nzwidth; i0++)
for(int j0=0; j0<nzwidth; j0++)
{
v=a.m_tmp2.Get(i1*nzwidth+i0,j1*nzwidth+j0);
blockata.Set(celloffset+offs1+i0,offs0+j0,blockata.Get(celloffset+offs1+i0,offs0+j0)+v);
}
}
}
}
}
//---Process regularizer term
for(int i1=0; i1<ky; i1++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i1);
for(int j1=0; j1<kx; j1++)
blockata.Add(celloffset+j1,j1,CMath::Sqr(a.m_lambdareg));
}
//---Calculate max(ATA)
//---NOTE: here we rely on zero initialization of unused parts of
//--- BlockATA and Tmp2.
mxata=0.0;
for(int i1=0; i1<ky; i1++)
{
for(int i0=i1; i0<=MathMin(ky-1,i1+blockbandwidth); i0++)
{
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i0);
for(int j1=0; j1<kx; j1++)
for(int j0=0; j0<kx; j0++)
mxata=MathMax(mxata,MathAbs(blockata.Get(celloffset+j1,j0)));
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CIntFitServ
{
public:
static void LSFitScaleXY(CRowDouble&x,CRowDouble&y,CRowDouble&w,int n,CRowDouble&xc,CRowDouble&yc,CRowInt&dc,int k,double&xa,double&xb,double&sa,double&sb,CRowDouble&xoriginal,CRowDouble&yoriginal);
static void BuildPriorTerm(CMatrixDouble&xy,int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
static void BuildPriorTerm1(CRowDouble&xy1,int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
static void BuildPriorTerm1(double &xy1[],int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
};
//+------------------------------------------------------------------+
//| Internal subroutine: automatic scaling for LLS tasks. |
//| NEVER CALL IT DIRECTLY! |
//| Maps abscissas to [-1,1], standartizes ordinates and |
//| correspondingly scales constraints. It also scales weights so |
//| that max(W[i])=1 |
//| Transformations performed: |
//| * X, XC [XA,XB] => [-1,+1] |
//| transformation makes min(X)=-1, max(X)=+1 |
//| * Y [SA,SB] => [0,1] |
//| transformation makes mean(Y)=0, stddev(Y)=1 |
//| * YC transformed accordingly to SA, SB, DC[I] |
//+------------------------------------------------------------------+
void CIntFitServ::LSFitScaleXY(CRowDouble &X,CRowDouble &Y,
CRowDouble &w,int n,
CRowDouble &XC,CRowDouble &YC,
CRowInt &dc,int k,double &xa,
double &xb,double &sa,
double &sb,CRowDouble &xoriginal,
CRowDouble &yoriginal)
{
//--- create variables
double xmin=0;
double xmax=0;
double mx=0;
vector<double> x=X.ToVector();
vector<double> xc=XC.ToVector();
vector<double> yc=YC.ToVector();
vector<double> y=Y.ToVector();
xa=0;
xb=0;
sa=0;
sb=0;
xoriginal.Resize(0);
yoriginal.Resize(0);
x.Resize(n);
y.Resize(n);
xc.Resize(k);
yc.Resize(k);
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": incorrect N"))
return;
if(!CAp::Assert(k>=0,__FUNCTION__+": incorrect K"))
return;
xmin=MathMin(x.Min(),xc.Min());
xmax=MathMax(x.Max(),xc.Max());
if(xmin==xmax)
{
if(xmin==0.0)
{
xmin=-1;
xmax=1;
}
else
{
if(xmin>0.0)
xmin=0.5*xmin;
else
xmax=0.5*xmax;
}
}
xoriginal=x;
xa=xmin;
xb=xmax;
x=(x-0.5*(xa+xb))*2/(xb-xa);
for(int i=0; i<k; i++)
{
//--- check
if(!CAp::Assert(dc[i]>=0,__FUNCTION__+": internal error!"))
return;
}
xc=(xc-0.5*(xa+xb))*2.0/(xb-xa);
yc=yc*MathPow(0.5*(xb-xa),dc[0]);
yoriginal=y;
sa=y.Mean();
sb=MathPow(y-sa,2).Sum();
sb=MathSqrt(sb/n)+sa;
if(sb==sa)
sb=2*sa;
if(sb==sa)
sb=sa+1;
y=(y-sa)/(sb-sa);
for(int i=0; i<k; i++)
{
if(dc[i]==0)
YC.Set(i,(yc[i]-sa)/(sb-sa));
else
YC.Set(i,yc[i]/(sb-sa));
}
yc=w.Abs();
yc.Resize(n);
mx=yc.Max();
if(mx!=0.0)
{
if(w.Size()==yc.Size())
w=yc/mx;
else
for(int i=0; i<n; i++)
w.Set(i,yc[i]/mx);
}
//--- copy back
X.Copy(x,n);
Y.Copy(y,n);
XC.Copy(xc,k);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CIntFitServ::BuildPriorTerm(CMatrixDouble &xy,int n,int nx,int ny,
int modeltype,double priorval,CMatrixDouble &v)
{
//--- create variables
int i=0;
int j=0;
int j0=0;
int j1=0;
double rj=0;
CMatrixDouble araw;
CMatrixDouble amod;
CMatrixDouble braw;
CRowDouble tmp0;
double lambdareg=0;
int rfsits=0;
v.Resize(0,0);
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return;
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
return;
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
return;
v=matrix<double>::Zeros(ny,nx+1);
if(n==0)
{
switch(modeltype)
{
case 0:
v.Col(nx,vector<double>::Full(ny,priorval));
return;
case 1:
return;
case 2:
return;
case 3:
return;
default:
CAp::Assert(false,__FUNCTION__+": unexpected model type");
return;
}
}
switch(modeltype)
{
case 0:
v.Col(nx,vector<double>::Full(ny,priorval));
xy-=priorval;
return;
case 2:
for(i=0; i<n; i++)
for(j=0; j<ny; j++)
v.Add(j,nx,xy.Get(i,nx+j));
v.Col(nx,v.Col(nx)/CApServ::Coalesce(n,1));
for(i=0; i<n; i++)
{
for(j=0; j<ny; j++)
xy.Add(i,nx+j,- v.Get(j,nx));
}
return;
case 3:
return;
}
//--- check
if(!CAp::Assert(modeltype==1,__FUNCTION__+": unexpected model type"))
return;
lambdareg=0.0;
araw=matrix<double>::Zeros(nx+1,nx+1);
braw.Resize(nx+1,ny);
tmp0.Resize(nx+1);
amod.Resize(nx+1,nx+1);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy.Get(i,j));
tmp0.Set(nx,1.0);
for(j0=0; j0<=nx; j0++)
for(j1=0; j1<=nx; j1++)
araw.Add(j0,j1,tmp0[j0]*tmp0[j1]);
}
for(rfsits=1; rfsits<=3; rfsits++)
{
braw.Fill(0);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy.Get(i,j));
tmp0.Set(nx,1.0);
for(j=0; j<ny; j++)
{
rj=xy.Get(i,nx+j);
for(j0=0; j0<=nx; j0++)
rj-=tmp0[j0]*v.Get(j,j0);
braw.Col(j,braw.Col(j)+tmp0*rj);
}
}
while(true)
{
for(i=0; i<=nx; i++)
{
amod.Row(i,araw,i);
amod.Add(i,i,lambdareg*CApServ::Coalesce(amod.Get(i,i),1));
}
if(CTrFac::SPDMatrixCholesky(amod,nx+1,true))
break;
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
}
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,1,braw,0,0);
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,0,braw,0,0);
v+=braw.Transpose();
}
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy.Get(i,j));
tmp0.Set(nx,1.0);
for(j=0; j<ny; j++)
{
rj=tmp0.DotR(v,j);
xy.Add(i,nx+j,-rj);
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CIntFitServ::BuildPriorTerm1(double &xy1[],int n,int nx,int ny,
int modeltype,double priorval,CMatrixDouble &v)
{
CRowDouble XY=xy1;
BuildPriorTerm1(XY,n,nx,ny,modeltype,priorval,v);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CIntFitServ::BuildPriorTerm1(CRowDouble &xy1,int n,int nx,int ny,
int modeltype,double priorval,CMatrixDouble &v)
{
//--- create variables
int i=0;
int j=0;
int j0=0;
int j1=0;
int ew=0;
double rj=0;
CMatrixDouble araw;
CMatrixDouble amod;
CMatrixDouble braw;
CRowDouble tmp0;
double lambdareg=0;
int rfsits=0;
v.Resize(0,0);
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return;
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
return;
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
return;
ew=nx+ny;
v=matrix<double>::Zeros(ny,nx+1);
if(n==0)
{
switch(modeltype)
{
case 0:
v.Col(nx,vector<double>::Full(ny,priorval));
return;
case 1:
return;
case 2:
return;
case 3:
return;
default:
CAp::Assert(false,__FUNCTION__+": unexpected model type");
return;
}
}
switch(modeltype)
{
case 0:
v.Col(nx,vector<double>::Full(ny,priorval));
for(i=0; i<n; i++)
{
for(j=0; j<ny; j++)
xy1.Add(i*ew+nx+j,-priorval);
}
return;
case 2:
for(i=0; i<n; i++)
{
for(j=0; j<ny; j++)
v.Add(j,nx,xy1[i*ew+nx+j]);
}
v.Col(nx,v.Col(nx)/CApServ::Coalesce(n,1));
for(i=0; i<n; i++)
{
for(j=0; j<ny; j++)
xy1.Add(i*ew+nx+j,-v.Get(j,nx));
}
return;
case 3:
return;
}
//--- check
if(!CAp::Assert(modeltype==1,__FUNCTION__+": unexpected model type"))
return;
lambdareg=0.0;
araw=matrix<double>::Zeros(nx+1,nx+1);
braw.Resize(nx+1,ny);
tmp0.Resize(nx+1);
amod.Resize(nx+1,nx+1);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy1[i*ew+j]);
tmp0.Set(nx,1.0);
for(j0=0; j0<=nx; j0++)
{
for(j1=0; j1<=nx; j1++)
araw.Add(j0,j1,tmp0[j0]*tmp0[j1]);
}
}
for(rfsits=1; rfsits<=3; rfsits++)
{
braw=matrix<double>::Zeros(nx+1,ny);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy1[i*ew+j]);
tmp0.Set(nx,1.0);
for(j=0; j<ny; j++)
{
rj=xy1[i*ew+nx+j];
for(j0=0; j0<=nx; j0++)
rj-=tmp0[j0]*v.Get(j,j0);
braw.Col(j,braw.Col(j)+tmp0*rj);
}
}
while(true)
{
amod=araw;
for(i=0; i<=nx; i++)
amod.Add(i,i,lambdareg*CApServ::Coalesce(amod.Get(i,i),1));
if(CTrFac::SPDMatrixCholesky(amod,nx+1,true))
break;
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
}
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,1,braw,0,0);
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,0,braw,0,0);
v+= braw.Transpose()+0;
}
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
tmp0.Set(j,xy1[i*ew+j]);
tmp0.Set(nx,1.0);
for(j=0; j<=ny-1; j++)
{
rj=tmp0.DotR(v,j);
xy1.Add(i*ew+nx+j,-rj);
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
struct CFitSphereInternalReport
{
public:
int m_nfev;
int m_iterationscount;
//--- constructor / destructor
CFitSphereInternalReport(void) { m_nfev=0; m_iterationscount=0; }
~CFitSphereInternalReport(void) {}
void Copy(const CFitSphereInternalReport&obj) { m_nfev=obj.m_nfev; m_iterationscount=obj.m_iterationscount; }
//--- overloading
void operator=(const CFitSphereInternalReport&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CFitSphere
{
public:
static void FitSphereLS(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&r);
static void FitSphereMC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rhi);
static void FitSphereMI(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo);
static void FitSphereMZ(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo,double&rhi);
static void FitSphereX(CMatrixDouble&xy,int npoints,int nx,int problemtype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi);
static void FitSphereInternal(CMatrixDouble&xy,int npoints,int nx,int problemtype,int solvertype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi,CFitSphereInternalReport&rep);
};
//+------------------------------------------------------------------+
//| Fits least squares (LS) circle (or NX-dimensional sphere) to data|
//| (a set of points in NX-dimensional space). |
//| Least squares circle minimizes sum of squared deviations between |
//| distances from points to the center and some "candidate" radius,|
//| which is also fitted to the data. |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| R - radius |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereLS(CMatrixDouble &xy,
int npoints,
int nx,
CRowDouble &cx,
double &r)
{
double dummy=0;
cx.Resize(0);
r=0;
//--- function call
FitSphereX(xy,npoints,nx,0,0.0,0,0.0,cx,dummy,r);
}
//+------------------------------------------------------------------+
//| Fits minimum circumscribed (MC) circle (or NX-dimensional sphere)|
//| to data (a set of points in NX-dimensional space). |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| RHi - radius |
//| NOTE: this function is an easy-to-use wrapper around more |
//| powerful "expert" function FitSphereX(). |
//| This wrapper is optimized for ease of use and stability - at the |
//| cost of somewhat lower performance (we have to use very tight |
//| stopping criteria for inner optimizer because we want to make |
//| sure that it will converge on any dataset). |
//| If you are ready to experiment with settings of "expert" |
//| function, you can achieve ~2-4x speedup over standard |
//| "bulletproof" settings. |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereMC(CMatrixDouble &xy,
int npoints,
int nx,
CRowDouble &cx,
double &rhi)
{
double dummy=0;
cx.Resize(0);
rhi=0;
//--- function call
FitSphereX(xy,npoints,nx,1,0.0,0,0.0,cx,dummy,rhi);
}
//+------------------------------------------------------------------+
//| Fits maximum inscribed circle (or NX-dimensional sphere) to data |
//| (a set of points in NX-dimensional space). |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| RLo - radius |
//| NOTE: this function is an easy-to-use wrapper around more |
//| powerful "expert" function FitSphereX(). |
//| This wrapper is optimized for ease of use and stability - at |
//| the cost of somewhat lower performance (we have to use very|
//| tight stopping criteria for inner optimizer because we want to |
//| make sure that it will converge on any dataset). |
//| If you are ready to experiment with settings of "expert" |
//| function, you can achieve ~2-4x speedup over standard |
//| "bulletproof" settings. |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereMI(CMatrixDouble &xy,int npoints,int nx,
CRowDouble &cx,double &rlo)
{
double dummy=0;
cx.Resize(0);
rlo=0;
//--- function call
FitSphereX(xy,npoints,nx,2,0.0,0,0.0,cx,rlo,dummy);
}
//+------------------------------------------------------------------+
//| Fits minimum zone circle (or NX-dimensional sphere) to data (a |
//| set of points in NX-dimensional space). |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| RLo - radius of inscribed circle |
//| RHo - radius of circumscribed circle |
//| NOTE: this function is an easy-to-use wrapper around more |
//| powerful "expert" function FitSphereX(). |
//| This wrapper is optimized for ease of use and stability - at |
//| the cost of somewhat lower performance (we have to use very|
//| tight stopping criteria for inner optimizer because we want to |
//| make sure that it will converge on any dataset). |
//| If you are ready to experiment with settings of "expert" |
//| function, you can achieve ~2-4x speedup over standard |
//| "bulletproof" settings. |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereMZ(CMatrixDouble &xy,int npoints,int nx,
CRowDouble &cx,double &rlo,double &rhi)
{
cx.Resize(0);
rlo=0;
rhi=0;
//--- function call
FitSphereX(xy,npoints,nx,3,0.0,0,0.0,cx,rlo,rhi);
}
//+------------------------------------------------------------------+
//| Fitting minimum circumscribed, maximum inscribed or minimum zone |
//| circles (or NX-dimensional spheres) to data (a set of points |
//| in NX-dimensional space). |
//| This is expert function which allows to tweak many parameters of |
//| underlying nonlinear solver: |
//| * stopping criteria for inner iterations |
//| * number of outer iterations |
//| * penalty coefficient used to handle nonlinear constraints (we |
//| convert unconstrained nonsmooth optimization problem ivolving|
//| max() and/or min() operations to quadratically constrained |
//| smooth one). |
//| You may tweak all these parameters or only some of them, leaving |
//| other ones at their default State - just specify zero value, and |
//| solver will fill it with appropriate default one. |
//| These comments also include some discussion of approach used to |
//| handle such unusual fitting problem, its stability, drawbacks of |
//| alternative methods, and convergence properties. |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| ProblemType - used to encode problem type: |
//| * 0 for least squares circle |
//| * 1 for minimum circumscribed circle/sphere fitting|
//| (MC) |
//| * 2 for maximum inscribed circle/sphere fitting(MI)|
//| * 3 for minimum zone circle fitting (difference |
//| between Rhi and Rlo is minimized), denoted as |
//| MZ |
//| EpsX - stopping condition for NLC optimizer: |
//| * must be non-negative |
//| * use 0 to choose default value (1.0E-12 is used by|
//| default) |
//| * you may specify larger values, up to 1.0E-6, if |
//| you want to speed-up solver; NLC solver performs |
//| several preconditioned outer iterations, so final|
//| result typically has precision much better than |
//| EpsX. |
//| AULIts - number of outer iterations performed by NLC |
//| optimizer: |
//| * must be non-negative |
//| * use 0 to choose default value (20 is used by |
//| default) |
//| * you may specify values smaller than 20 if you |
//| want to speed up solver; 10 often results in good|
//| combination of precision and speed; sometimes you|
//| may get good results with just 6 outer iterations|
//| Ignored for ProblemType=0. |
//| Penalty - penalty coefficient for NLC optimizer: |
//| * must be non-negative |
//| * use 0 to choose default value (1.0E6 in current |
//| version) |
//| * it should be really large, 1.0E6...1.0E7 is a |
//| good value to start from; |
//| * generally, default value is good enough |
//| Ignored for ProblemType=0. |
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| RLo - radius: |
//| * for ProblemType=2,3, radius of the inscribed |
//| sphere |
//| * for ProblemType=0 - radius of the least squares |
//| sphere |
//| * for ProblemType=1 - zero |
//| RHo - radius: |
//| * for ProblemType=1,3, radius of the circumscribed |
//| sphere |
//| * for ProblemType=0 - radius of the least squares |
//| sphere |
//| * for ProblemType=2 - zero |
//| NOTE: ON THE UNIQUENESS OF SOLUTIONS |
//| ALGLIB provides solution to several related circle fitting |
//| problems: MC (minimum circumscribed), MI (maximum inscribed) and |
//| MZ (minimum zone) fitting, LS (least squares) fitting. |
//| It is important to note that among these problems only MC and LS |
//| are convex and have unique solution independently from starting |
//| point. |
//| As for MI, it may (or may not, depending on dataset properties) |
//| have multiple solutions, and it always has one degenerate |
//| solution C=infinity which corresponds to infinitely large radius.|
//| Thus, there are no guarantees that solution to MI returned by |
//| this solver will be the best one (and no one can provide you with|
//| such guarantee because problem is NP-hard). The only guarantee |
//| you have is that this solution is locally optimal, i.e. it can |
//| not be improved by infinitesimally small tweaks in the parameters|
//| It is also possible to "run away" to infinity when started from |
//| bad initial point located outside of point cloud (or when point |
//| cloud does not span entire circumference/surface of the sphere). |
//| Finally, MZ (minimum zone circle) stands somewhere between MC and|
//| MI in stability. It is somewhat regularized by "circumscribed" |
//| term of the merit function; however, solutions to MZ may be |
//| non-unique, and in some unlucky cases it is also possible to "run|
//| away to infinity". |
//| NOTE: ON THE NONLINEARLY CONSTRAINED PROGRAMMING APPROACH |
//| The problem formulation for MC (minimum circumscribed circle; for|
//| the sake of simplicity we omit MZ and MI here) is: |
//| [ [ ]2 ] |
//| min [ max [ XY[i]-C ] ] |
//| C [ i [ ] ] |
//| i.e. it is unconstrained nonsmooth optimization problem of |
//| finding "best" central point, with radius R being unambiguously |
//| determined from C. In order to move away from non-smoothness we |
//| use following reformulation: |
//| [ ] [ ]2 |
//| min [ R ] subject to R>=0, [ XY[i]-C ] <= R^2 |
//| C,R [ ] [ ] |
//| i.e. it becomes smooth quadratically constrained optimization |
//| problem with linear target function. Such problem statement is |
//| 100% equivalent to the original nonsmooth one, but much easier |
//| to approach. We solve it with MinNLC solver provided by ALGLIB. |
//| NOTE: ON INSTABILITY OF SEQUENTIAL LINEARIZATION APPROACH |
//| ALGLIB has nonlinearly constrained solver which proved to be |
//| stable on such problems. However, some authors proposed to |
//| linearize constraints in the vicinity of current approximation |
//| (Ci,Ri) and to get next approximate solution (Ci+1,Ri+1) as |
//| solution to linear programming problem. Obviously, LP problems |
//| are easier than nonlinearly constrained ones. |
//| Indeed, such approach to MC/MI/MZ resulted in ~10-20x increase in|
//| performance (when compared with NLC solver). However, it turned |
//| out that in some cases linearized model fails to predict correct |
//| direction for next step and tells us that we converged to |
//| solution even when we are still 2-4 digits of precision away from|
//| it. |
//| It is important that it is not failure of LP solver - it is |
//| failure of the linear model; even when solved exactly, it fails |
//| to handle subtle nonlinearities which arise near the solution. |
//| We validated it by comparing results returned by ALGLIB linear |
//| solver with that of MATLAB. |
//| In our experiments with linearization: |
//| * MC failed most often, at both realistic and synthetic |
//| datasets |
//| * MI sometimes failed, but sometimes succeeded |
//| * MZ often succeeded; our guess is that presence of two |
//| independent sets of constraints (one set for Rlo and another |
//| one for Rhi) and two terms in the target function (Rlo and |
//| Rhi) regularizes task, so when linear model fails to handle |
//| nonlinearities from Rlo, it uses Rhi as a hint (and vice |
//| versa). |
//| Because linearization approach failed to achieve stable results, |
//| we do not include it in ALGLIB. |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereX(CMatrixDouble &xy,int npoints,int nx,
int problemtype,double epsx,int aulits,
double penalty,CRowDouble &cx,double &rlo,
double &rhi)
{
CFitSphereInternalReport rep;
cx.Resize(0);
rlo=0;
rhi=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(penalty) && penalty>=0.0,__FUNCTION__+": Penalty<0 or is not finite"))
return;
if(!CAp::Assert(MathIsValidNumber(epsx) && (double)(epsx)>=0.0,__FUNCTION__+": EpsX<0 or is not finite"))
return;
if(!CAp::Assert(aulits>=0,__FUNCTION__+": AULIts<0"))
return;
//--- function call
FitSphereInternal(xy,npoints,nx,problemtype,0,epsx,aulits,penalty,cx,rlo,rhi,rep);
}
//+------------------------------------------------------------------+
//| Fitting minimum circumscribed, maximum inscribed or minimum zone |
//| circles (or NX-dimensional spheres) to data (a set of points in |
//| NX-dimensional space). |
//| Internal computational function. |
//| INPUT PARAMETERS: |
//| XY - array[NPoints,NX] (or larger), contains dataset. |
//| One row = one point in NX-dimensional space. |
//| NPoints - dataset size, NPoints>0 |
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
//| ProblemType - used to encode problem type: |
//| * 0 for least squares circle |
//| * 1 for minimum circumscribed circle/sphere fitting|
//| (MC) |
//| * 2 for maximum inscribed circle/sphere fitting(MI)|
//| * 3 for minimum zone circle fitting (difference |
//| between Rhi and Rlo is minimized), denoted as |
//| MZ |
//| SolverType - solver to use: |
//| * 0 use best solver available(1 in current version)|
//| * 1 use nonlinearly constrained optimization |
//| approach, AUL (it is roughly 10-20 times slower|
//| than SPC-LIN, but much more stable) |
//| * 2 use special fast IMPRECISE solver, SPC-LIN |
//| sequential linearization approach; SPC-LIN is |
//| fast, but sometimes fails to converge with more|
//| than 3 digits of precision; see comments below.|
//| NOT RECOMMENDED UNLESS YOU REALLY NEED HIGH |
//| PERFORMANCE AT THE COST OF SOME PRECISION. |
//| * 3 use nonlinearly constrained optimization |
//| approach, SLP (most robust one, but somewhat |
//| slower than AUL) |
//| Ignored for ProblemType=0. |
//| EpsX - stopping criteria for SLP and NLC optimizers: |
//| * must be non-negative |
//| * use 0 to choose default value (1.0E-12 is used by|
//| default) |
//| * if you use SLP solver, you should use default |
//| values |
//| * if you use NLC solver, you may specify larger |
//| values, up to 1.0E-6, if you want to speed-up |
//| solver;NLC solver performs several preconditioned|
//| outer iterations, so final result typically has |
//| precision much better than EpsX. |
//| AULIts - number of iterations performed by NLC optimizer: |
//| * must be non-negative |
//| * use 0 to choose default value (20 is used by |
//| default) |
//| * you may specify values smaller than 20 if you |
//| want to speed up solver; 10 often results in |
//| good combination of precision and speed |
//| Ignored for ProblemType=0. |
//| Penalty - penalty coefficient for NLC optimizer (ignored for |
//| SLP): |
//| * must be non-negative |
//| * use 0 to choose default value (1.0E6 in current |
//| version) |
//| * it should be really large, 1.0E6...1.0E7 is a |
//| good value to start from; |
//| * generally, default value is good enough |
//| * ignored by SLP optimizer |
//| Ignored for ProblemType=0. |
//| OUTPUT PARAMETERS: |
//| CX - central point for a sphere |
//| RLo - radius: |
//| * for ProblemType=2,3, radius of the inscribed |
//| sphere |
//| * for ProblemType=0 - radius of the least squares |
//| sphere |
//| * for ProblemType=1 - zero |
//| RHo - radius: |
//| * for ProblemType=1,3, radius of the circumscribed |
//| sphere |
//| * for ProblemType=0 - radius of the least squares |
//| sphere |
//| * for ProblemType=2 - zero |
//+------------------------------------------------------------------+
void CFitSphere::FitSphereInternal(CMatrixDouble &XY,int npoints,
int nx,int problemtype,
int solvertype,double epsx,
int aulits,double penalty,
CRowDouble &cx,double &rlo,
double &rhi,
CFitSphereInternalReport &rep)
{
//--- create variables
int i=0;
int j=0;
double v=0;
double vv=0;
int cpr=0;
bool userlo=false;
bool userhi=false;
double vlo=0;
double vhi=0;
vector<double> vmin;
vector<double> vmax;
vector<double> std;
double spread=0;
CRowDouble pcr;
CRowDouble scr;
CRowDouble bl;
CRowDouble bu;
int suboffset=0;
int dstrow=0;
CMinNLCState nlcstate;
CMinNLCReport nlcrep;
CMatrixDouble cmatrix;
CRowInt ct;
int outeridx=0;
int maxouterits=0;
int maxits=0;
double safeguard=0;
double bi=0;
CMinBLEICState blcstate;
CMinBLEICReport blcrep;
CRowDouble prevc;
CMinLMState lmstate;
CMinLMReport lmrep;
matrix<double> xy=XY.ToMatrix();
cx.Resize(0);
xy.Resize(npoints,nx);
rlo=0;
rhi=0;
//--- Check input parameters
if(!CAp::Assert(npoints>0,__FUNCTION__+": NPoints<=0"))
return;
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(XY,npoints,nx),__FUNCTION__+": XY contains infinite or NAN values"))
return;
if(!CAp::Assert(problemtype>=0 && problemtype<=3,__FUNCTION__+": ProblemType is neither 0,1,2 or 3"))
return;
if(!CAp::Assert(solvertype>=0 && solvertype<=3,__FUNCTION__+": ProblemType is neither 1,2 or 3"))
return;
if(!CAp::Assert(MathIsValidNumber(penalty) && penalty>=0.0,__FUNCTION__+": Penalty<0 or is not finite"))
return;
if(!CAp::Assert(MathIsValidNumber(epsx) && epsx>=0.0,__FUNCTION__+": EpsX<0 or is not finite"))
return;
if(!CAp::Assert(aulits>=0,__FUNCTION__+": AULIts<0"))
return;
if(solvertype==0)
solvertype=1;
if(penalty==0.0)
penalty=1.0E6;
if(epsx==0.0)
epsx=1.0E-12;
if(aulits==0)
aulits=20;
safeguard=10;
maxouterits=10;
maxits=10000;
rep.m_nfev=0;
rep.m_iterationscount=0;
//--- Determine initial values, initial estimates and spread of the points
vmin=xy.Min(0)+0;
vmax=xy.Max(0)+0;
cx=xy.Mean(0);
std=xy.Std(0);
spread=(vmax-vmin).Max();
rlo=std.Min();
rhi=std.Max();
//--- Handle degenerate case of zero spread
if(spread==0.0)
{
cx=vmin;
rhi=0;
rlo=0;
return;
}
//--- Prepare initial point for optimizer, scale vector and box constraints
pcr=cx;
bl=cx.ToVector()-safeguard*spread;
bu=cx.ToVector()+safeguard*spread;
scr=vector<double>::Full(nx+2,0.1*spread);
pcr.Resize(nx+2);
bl.Resize(nx+2);
bu.Resize(nx+2);
pcr.Set(nx+0,rlo);
pcr.Set(nx+1,rhi);
scr.Set(nx+0,0.5*spread);
scr.Set(nx+1,0.5*spread);
bl.Set(nx+0,0);
bl.Set(nx+1,0);
bu.Set(nx+0,safeguard*rhi);
bu.Set(nx+1,safeguard*rhi);
//--- First branch: least squares fitting vs MI/MC/MZ fitting
if(problemtype==0)
{
//--- Solve problem with Levenberg-Marquardt algorithm
pcr.Set(nx,rhi);
CMinLM::MinLMCreateVJ(nx+1,npoints,pcr,lmstate);
CMinLM::MinLMSetScale(lmstate,scr);
CMinLM::MinLMSetBC(lmstate,bl,bu);
CMinLM::MinLMSetCond(lmstate,epsx,maxits);
while(CMinLM::MinLMIteration(lmstate))
{
if(lmstate.m_needfij || lmstate.m_needfi)
{
rep.m_nfev++;
for(i=0; i<npoints; i++)
{
v=0;
for(j=0; j<nx; j++)
v+=CMath::Sqr(lmstate.m_x[j]-xy[i,j]);
lmstate.m_fi.Set(i,MathSqrt(v)-lmstate.m_x[nx]);
if(lmstate.m_needfij)
{
for(j=0; j<nx; j++)
lmstate.m_j.Set(i,j,0.5/(1.0E-9*spread+MathSqrt(v))*2*(lmstate.m_x[j]-xy[i,j]));
lmstate.m_j.Set(i,nx,-1);
}
}
}
else
return;
}
CMinLM::MinLMResults(lmstate,pcr,lmrep);
//--- check
if(!CAp::Assert(lmrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of LM solver"))
return;
rep.m_iterationscount+=lmrep.m_iterationscount;
//--- Offload center coordinates from PCR to CX,
//--- re-calculate exact value of RLo/RHi using CX.
for(j=0; j<nx; j++)
cx.Set(j,pcr[j]);
vv=0;
for(i=0; i<npoints; i++)
{
v=0;
for(j=0; j<=nx-1; j++)
v=CMath::Sqr(xy[i,j]-cx[j]);
v=MathSqrt(v);
vv=vv+v/npoints;
}
rlo=vv;
rhi=vv;
}
else
{
//--- MI, MC, MZ fitting.
//--- Prepare problem metrics
userlo=problemtype==2 || problemtype==3;
userhi=problemtype==1 || problemtype==3;
if(userlo && userhi)
cpr=2;
else
cpr=1;
if(userlo)
vlo=1;
else
vlo=0;
if(userhi)
vhi=1;
else
vhi=0;
//--- Solve with NLC solver; problem is treated as general nonlinearly constrained
//--- programming, with augmented Lagrangian solver or SLP being used.
if(solvertype==1 || solvertype==3)
{
CMinNLC::MinNLCCreate(nx+2,pcr,nlcstate);
CMinNLC::MinNLCSetScale(nlcstate,scr);
CMinNLC::MinNLCSetBC(nlcstate,bl,bu);
CMinNLC::MinNLCSetNLC(nlcstate,0,cpr*npoints);
CMinNLC::MinNLCSetCond(nlcstate,epsx,maxits);
CMinNLC::MinNLCSetPrecExactRobust(nlcstate,5);
CMinNLC::MinNLCSetSTPMax(nlcstate,0.1);
if(solvertype==1)
CMinNLC::MinNLCSetAlgoAUL(nlcstate,penalty,aulits);
else
CMinNLC::MinNLCSetAlgoSLP(nlcstate);
CMinNLC::MinNLCRestartFrom(nlcstate,pcr);
while(CMinNLC::MinNLCIteration(nlcstate))
{
if(nlcstate.m_needfij)
{
rep.m_nfev++;
nlcstate.m_fi.Set(0,vhi*nlcstate.m_x[nx+1]-vlo*nlcstate.m_x[nx+0]);
nlcstate.m_j.Row(0,vector<double>::Zeros(nx+2));
nlcstate.m_j.Set(0,nx+0,-(1*vlo));
nlcstate.m_j.Set(0,nx+1,1*vhi);
for(i=0; i<npoints; i++)
{
suboffset=0;
if(userhi)
{
dstrow=1+cpr*i+suboffset;
v=0;
for(j=0; j<nx; j++)
{
vv=nlcstate.m_x[j]-xy[i,j];
v=v+vv*vv;
nlcstate.m_j.Set(dstrow,j,2*vv);
}
vv=nlcstate.m_x[nx+1];
v=v-vv*vv;
nlcstate.m_j.Set(dstrow,nx+0,0);
nlcstate.m_j.Set(dstrow,nx+1,-(2*vv));
nlcstate.m_fi.Set(dstrow,v);
suboffset++;
}
if(userlo)
{
dstrow=1+cpr*i+suboffset;
v=0;
for(j=0; j<nx; j++)
{
vv=nlcstate.m_x[j]-xy[i,j];
v=v-vv*vv;
nlcstate.m_j.Set(dstrow,j,-(2*vv));
}
vv=nlcstate.m_x[nx+0];
v=v+vv*vv;
nlcstate.m_j.Set(dstrow,nx+0,2*vv);
nlcstate.m_j.Set(dstrow,nx+1,0);
nlcstate.m_fi.Set(dstrow,v);
suboffset++;
}
//--- check
if(!CAp::Assert(suboffset==cpr))
return;
}
}
else
return;
}
CMinNLC::MinNLCResults(nlcstate,pcr,nlcrep);
//--- check
if(!CAp::Assert(nlcrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of NLC solver"))
return;
rep.m_iterationscount=rep.m_iterationscount+nlcrep.m_iterationscount;
//--- Offload center coordinates from PCR to CX,
//--- re-calculate exact value of RLo/RHi using CX.
for(j=0; j<nx; j++)
cx.Set(j,pcr[j]);
rlo=CMath::m_maxrealnumber;
rhi=0;
for(i=0; i<npoints; i++)
{
v=0;
for(j=0; j<nx; j++)
v+=MathPow(xy[i,j]-cx[j],2.0);
v=MathSqrt(v);
rhi=MathMax(rhi,v);
rlo=MathMin(rlo,v);
}
if(!userlo)
rlo=0;
if(!userhi)
rhi=0;
return;
}
//--- Solve problem with SLP (sequential LP) approach; this approach
//--- is much faster than NLP, but often fails for MI and MC (for MZ
//--- it performs well enough).
//--- REFERENCE: "On a sequential linear programming approach to finding
//--- the smallest circumscribed, largest inscribed, and minimum
//--- zone circle or sphere", Helmuth Spath and G.A.Watson
if(solvertype==2)
{
cmatrix.Resize(cpr*npoints,nx+3);
ct.Resize(cpr*npoints);
prevc.Resize(nx);
CMinBLEIC::MinBLEICCreate(nx+2,pcr,blcstate);
CMinBLEIC::MinBLEICSetScale(blcstate,scr);
CMinBLEIC::MinBLEICSetBC(blcstate,bl,bu);
CMinBLEIC::MinBLEICSetCond(blcstate,0,0,epsx,maxits);
for(outeridx=0; outeridx<maxouterits; outeridx++)
{
//--- Prepare initial point for algorithm; center coordinates at
//--- PCR are used to calculate RLo/RHi and update PCR with them.
rlo=CMath::m_maxrealnumber;
rhi=0;
for(i=0; i<npoints; i++)
{
v=0;
for(j=0; j<nx; j++)
v+=CMath::Sqr(xy[i,j]-pcr[j]);
v=MathSqrt(v);
rhi=MathMax(rhi,v);
rlo=MathMin(rlo,v);
}
pcr.Set(nx+0,rlo*0.99999);
pcr.Set(nx+1,rhi/0.99999);
//--- Generate matrix of linear constraints
for(i=0; i<npoints; i++)
{
v=MathPow(xy.Row(i)+0,2.0).Sum();
bi=-(v/2);
suboffset=0;
if(userhi)
{
dstrow=cpr*i+suboffset;
for(j=0; j<nx; j++)
cmatrix.Set(dstrow,j,pcr[j]/2-xy[i,j]);
cmatrix.Set(dstrow,nx+0,0);
cmatrix.Set(dstrow,nx+1,-(rhi/2));
cmatrix.Set(dstrow,nx+2,bi);
ct.Set(dstrow,-1);
suboffset++;
}
if(userlo)
{
dstrow=cpr*i+suboffset;
for(j=0; j<nx; j++)
cmatrix.Set(dstrow,j,-(pcr[j]/2-xy[i,j]));
cmatrix.Set(dstrow,nx+0,rlo/2);
cmatrix.Set(dstrow,nx+1,0);
cmatrix.Set(dstrow,nx+2,-bi);
ct.Set(dstrow,-1);
suboffset++;
}
//--- check
if(!CAp::Assert(suboffset==cpr))
return;
}
//--- Solve LP subproblem with MinBLEIC
prevc=pcr;
prevc.Resize(nx);
CMinBLEIC::MinBLEICSetLC(blcstate,cmatrix,ct,cpr*npoints);
CMinBLEIC::MinBLEICRestartFrom(blcstate,pcr);
while(CMinBLEIC::MinBLEICIteration(blcstate))
{
if(blcstate.m_needfg)
{
rep.m_nfev++;
blcstate.m_f=vhi*blcstate.m_x[nx+1]-vlo*blcstate.m_x[nx+0];
for(j=0; j<nx; j++)
blcstate.m_g.Set(j,0);
blcstate.m_g.Set(nx+0,-(1*vlo));
blcstate.m_g.Set(nx+1,1*vhi);
continue;
}
}
CMinBLEIC::MinBLEICResults(blcstate,pcr,blcrep);
//--- check
if(!CAp::Assert(blcrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of BLEIC solver"))
return;
rep.m_iterationscount+=blcrep.m_iterationscount;
//--- Terminate iterations early if we converged
v=0;
for(j=0; j<nx; j++)
v+=CMath::Sqr(prevc[j]-pcr[j]);
v=MathSqrt(v);
if(v<=epsx)
break;
}
//--- Offload center coordinates from PCR to CX,
//--- re-calculate exact value of RLo/RHi using CX.
for(j=0; j<nx; j++)
cx.Set(j,pcr[j]);
rlo=CMath::m_maxrealnumber;
rhi=0;
for(i=0; i<npoints; i++)
{
v=MathPow(xy.Row(i)-cx.ToVector(),2.0).Sum();
v=MathSqrt(v);
rhi=MathMax(rhi,v);
rlo=MathMin(rlo,v);
}
if(!userlo)
rlo=0;
if(!userhi)
rhi=0;
return;
}
//--- Oooops...!
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed"))
return;
}
}
//+------------------------------------------------------------------+
//| Buffer object which is used to perform nearest neighbor requests |
//| in the multithreaded mode (multiple threads working with same |
//| KD-tree object). |
//| This object should be created with KDTreeCreateBuffer(). |
//+------------------------------------------------------------------+
struct CRBFV1CalcBuffer
{
CRowInt m_calcbuftags;
CRowDouble m_calcbufxcx;
CMatrixDouble m_calcbufx;
CKDTreeRequestBuffer m_requestbuffer;
//--- constructor / destructor
CRBFV1CalcBuffer(void) {}
~CRBFV1CalcBuffer(void) {}
void Copy(const CRBFV1CalcBuffer&obj);
//--- overloading
void operator=(const CRBFV1CalcBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1CalcBuffer::Copy(const CRBFV1CalcBuffer &obj)
{
m_calcbuftags=obj.m_calcbuftags;
m_calcbufxcx=obj.m_calcbufxcx;
m_calcbufx=obj.m_calcbufx;
m_requestbuffer=obj.m_requestbuffer;
}
//+------------------------------------------------------------------+
//| RBF model. |
//| Never try to directly work with fields of this object - always |
//| use ALGLIB functions to use this object. |
//+------------------------------------------------------------------+
struct CRBFV1Model
{
int m_nc;
int m_nl;
int m_nx;
int m_ny;
double m_rmax;
CRowInt m_calcbuftags;
CRowDouble m_calcbufxcx;
CMatrixDouble m_calcbufx;
CMatrixDouble m_v;
CMatrixDouble m_wr;
CMatrixDouble m_xc;
CKDTree m_tree;
//--- constructor / destructor
CRBFV1Model(void);
~CRBFV1Model(void) {}
//---
void Copy(const CRBFV1Model&obj);
//--- overloading
void operator=(const CRBFV1Model&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBFV1Model::CRBFV1Model(void)
{
m_nc=0;
m_nl=0;
m_nx=0;
m_ny=0;
m_rmax=0;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1Model::Copy(const CRBFV1Model &obj)
{
m_nc=obj.m_nc;
m_nl=obj.m_nl;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_rmax=obj.m_rmax;
m_calcbuftags=obj.m_calcbuftags;
m_calcbufxcx=obj.m_calcbufxcx;
m_calcbufx=obj.m_calcbufx;
m_v=obj.m_v;
m_wr=obj.m_wr;
m_xc=obj.m_xc;
m_tree=obj.m_tree;
}
//+------------------------------------------------------------------+
//| Internal buffer for GridCalc3 |
//+------------------------------------------------------------------+
struct CGridCalc3v1Buf
{
bool m_flag0[];
bool m_flag12[];
bool m_flag1[];
bool m_flag2[];
CRowInt m_calcbuftags;
CRowDouble m_cx;
CRowDouble m_expbuf0;
CRowDouble m_expbuf1;
CRowDouble m_expbuf2;
CRowDouble m_tx;
CRowDouble m_ty;
CMatrixDouble m_calcbufx;
CKDTreeRequestBuffer m_requestbuf;
//--- constructor / destructor
CGridCalc3v1Buf(void) {}
~CGridCalc3v1Buf(void) {}
//---
void Copy(const CGridCalc3v1Buf&obj);
//--- overloading
void operator=(const CGridCalc3v1Buf&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CGridCalc3v1Buf::Copy(const CGridCalc3v1Buf &obj)
{
ArrayCopy(m_flag0,obj.m_flag0);
ArrayCopy(m_flag12,obj.m_flag12);
ArrayCopy(m_flag1,obj.m_flag1);
ArrayCopy(m_flag2,obj.m_flag2);
m_calcbuftags=obj.m_calcbuftags;
m_cx=obj.m_cx;
m_expbuf0=obj.m_expbuf0;
m_expbuf1=obj.m_expbuf1;
m_expbuf2=obj.m_expbuf2;
m_tx=obj.m_tx;
m_ty=obj.m_ty;
m_calcbufx=obj.m_calcbufx;
m_requestbuf=obj.m_requestbuf;
}
//+------------------------------------------------------------------+
//| RBF solution report: |
//| * TerminationType - termination type, positive values-success,|
//| non-positive - failure. |
//+------------------------------------------------------------------+
class CRBFV1Report
{
public:
int m_acols;
int m_annz;
int m_arows;
int m_iterationscount;
int m_nmv;
int m_terminationtype;
//--- constructor /destructor
CRBFV1Report(void) { ZeroMemory(this); }
~CRBFV1Report(void) {}
//---
void Copy(const CRBFV1Report&obj);
//--- overloading
void operator=(const CRBFV1Report&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1Report::Copy(const CRBFV1Report &obj)
{
m_acols=obj.m_acols;
m_annz=obj.m_annz;
m_arows=obj.m_arows;
m_iterationscount=obj.m_iterationscount;
m_nmv=obj.m_nmv;
m_terminationtype=obj.m_terminationtype;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CRBFV1
{
public:
//--- constants
static const int m_mxnx;
static const double m_rbffarradius;
static const double m_rbfnearradius;
static const double m_rbfmlradius;
static const double m_minbasecasecost;
//---
static void RBFV1Create(int nx,int ny,CRBFV1Model&s);
static void RBFV1CreateCalcBuffer(CRBFV1Model&s,CRBFV1CalcBuffer&buf);
static void RBFV1BuildModel(CMatrixDouble&x,CMatrixDouble&y,int n,int aterm,int algorithmtype,int nlayers,double radvalue,double radzvalue,double lambdav,double epsort,double epserr,int maxits,CRBFV1Model&s,CRBFV1Report&rep);
static void RBFV1Alloc(CSerializer&s,CRBFV1Model&model);
static void RBFV1Serialize(CSerializer&s,CRBFV1Model&model);
static void RBFV1Unserialize(CSerializer&s,CRBFV1Model&model);
static double RBFV1Calc2(CRBFV1Model&s,double x0,double x1);
static double RBFV1Calc3(CRBFV1Model&s,double x0,double x1,double x2);
static void RBFV1CalcBuf(CRBFV1Model&s,CRowDouble&x,CRowDouble&y);
static void RBFV1TSCalcBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
static void RBFV1TSDiffBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFV1TSHessBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFV1GridCalc2(CRBFV1Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
static void RBFV1GridCalc3VRec(CRBFV1Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowInt&blocks0,int block0a,int block0b,CRowInt&blocks1,int block1a,int block1b,CRowInt&blocks2,int block2a,int block2b,bool &flagy[],bool sparsey,double searchradius,double avgfuncpernode,CGridCalc3v1Buf&bufpool,CRowDouble&y);
static void RBFV1Unpack(CRBFV1Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
private:
static bool RBFV1BuildLinearModel(CMatrixDouble&x,CMatrixDouble&y,int n,int ny,int modeltype,CMatrixDouble&v);
static void BuildRDFModellSQR(CMatrixDouble&x,CMatrixDouble&y,CMatrixDouble&xc,CRowDouble&r,int n,int nc,int ny,CKDTree&pointstree,CKDTree&centerstree,double epsort,double epserr,int maxits,int &gnnz,int &snnz,CMatrixDouble&w,int &info,int &iterationscount,int &nmv);
static void BuildRBFMLayersModellSQR(CMatrixDouble&x,CMatrixDouble&y,CMatrixDouble&xc,double rval,CRowDouble&r,int n,int &nc,int ny,int nlayers,CKDTree&centerstree,double epsort,double epserr,int maxits,double lambdav,int &annz,CMatrixDouble&w,int &info,int &iterationscount,int &nmv);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
const int CRBFV1::m_mxnx=3;
const double CRBFV1::m_rbffarradius=6;
const double CRBFV1::m_rbfnearradius=2.1;
const double CRBFV1::m_rbfmlradius=3;
const double CRBFV1::m_minbasecasecost=100000;
//+------------------------------------------------------------------+
//| This function creates RBF model for a scalar (NY=) or vector |
//| (NY>1) function in a NX-dimensional space (NX=2 or NX=3). |
//| INPUT PARAMETERS: |
//| NX - dimension of the space, NX=2 or NX=3 |
//| NY - function dimension, NY>=1 |
//| OUTPUT PARAMETERS: |
//| S - RBF model (initially equals to zero) |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1Create(int nx,int ny,CRBFV1Model &s)
{
//--- check
if(!CAp::Assert(nx==2 || nx==3,__FUNCTION__+": NX<>2 and NX<>3"))
return;
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
return;
s.m_nx=nx;
s.m_ny=ny;
s.m_nl=0;
s.m_nc=0;
s.m_v=matrix<double>::Zeros(ny,m_mxnx+1);
s.m_rmax=0;
}
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to|
//| perform parallel RBF model evaluations (with one RBF model|
//| instance being used from multiple threads, as long as different|
//| threads use different instances of buffer). |
//| This buffer object can be used with RBFTSCalcBuf() function (here|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
//| function which reuses previously allocated output space). |
//| How to use it: |
//| * create RBF model structure with RBFV1Create() |
//| * load data, tune parameters |
//| * call RBFV1BuildModel() |
//| * call RBFV1CreateCalcBuffer(), once per thread working with |
//| RBF model (you should call this function only AFTER call to |
//| RBFV1BuildModel(), see below for more information) |
//| * call RBFTSCalcBuf() from different threads, with each thread |
//| working with its own copy of buffer object. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| Buf - external buffer. |
//| IMPORTANT: buffer object should be used only with RBF model |
//| object which was used to initialize buffer. Any |
//| attempt to use buffer with different object is |
//| dangerous - you may get memory violation error because|
//| sizes of internal arrays do not fit to dimensions of |
//| RBF structure. |
//| IMPORTANT: you should call thisfunction only for model which was |
//| built with RBFV1BuildModel() function, after |
//| successful invocation of RBFV1BuildModel(). Sizes of |
//| some internal structures are determined only after |
//| model is built, so buffer object created before model|
//| construction stage will be useless (and any attempt to|
//| use it will result in exception). |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1CreateCalcBuffer(CRBFV1Model &s,CRBFV1CalcBuffer &buf)
{
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_tree,buf.m_requestbuffer);
}
//+------------------------------------------------------------------+
//| This function builds RBF model and returns report (contains some|
//| information which can be used for evaluation of the algorithm |
//| properties). |
//| Call to this function modifies RBF model by calculating its |
//| centers/radii/weights and saving them into RBFModel structure. |
//| Initially RBFModel contain zero coefficients, but after call to |
//| this function we will have coefficients which were calculated in |
//| order to fit our dataset. |
//| After you called this function you can call RBFCalc(), |
//| RBFGridCalc() and other model calculation functions. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFV1Create() call |
//| Rep - report: |
//| * Rep.TerminationType: |
//| * -5 - non-distinct basis function centers were |
//| detected, interpolation aborted |
//| * -4 - nonconvergence of the internal SVD solver|
//| * 1 - successful termination |
//| Fields are used for debugging purposes: |
//| * Rep.IterationsCount - iterations count of the |
//| LSQR solver |
//| * Rep.NMV - number of matrix-vector products |
//| * Rep.ARows - rows count for the system matrix |
//| * Rep.ACols - columns count for the system matrix |
//| * Rep.ANNZ - number of significantly non-zero |
//| elements (elements above some |
//| algorithm-determined threshold) |
//| NOTE: failure to build model will leave current State of the |
//| structure unchanged. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1BuildModel(CMatrixDouble &x,CMatrixDouble &y,
int n,int aterm,int algorithmtype,
int nlayers,double radvalue,
double radzvalue,double lambdav,
double epsort,double epserr,
int maxits,CRBFV1Model &s,
CRBFV1Report &rep)
{
//--- create variables
CKDTree tree;
CKDTree ctree;
CRowDouble dist;
CRowDouble xcx;
CMatrixDouble a;
CMatrixDouble v;
CMatrixDouble omega;
CMatrixDouble residualy;
CRowDouble radius;
CMatrixDouble xc;
int nc=0;
double rmax=0;
CRowInt tags;
CRowInt ctags;
int i=0;
int j=0;
int k=0;
int snnz=0;
CRowDouble tmp0;
CRowDouble tmp1;
int layerscnt=0;
bool modelstatus=false;
//--- check
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": S.NX<>2 or S.NX<>3!"))
return;
//--- Quick exit when we have no points
if(n==0)
{
rep.m_terminationtype=1;
rep.m_iterationscount=0;
rep.m_nmv=0;
rep.m_arows=0;
rep.m_acols=0;
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,0,m_mxnx,0,2,s.m_tree);
s.m_xc.Resize(0,0);
s.m_wr.Resize(0,0);
s.m_nc=0;
s.m_rmax=0;
s.m_v=matrix<double>::Zeros(s.m_ny,m_mxnx+1);
return;
}
//--- General case, N>0
rep.m_annz=0;
rep.m_iterationscount=0;
rep.m_nmv=0;
xcx.Resize(m_mxnx);
//--- First model in a sequence - linear model.
//--- Residuals from linear regression are stored in the ResidualY variable
//--- (used later to build RBF models).
residualy=y;
residualy.Resize(n,s.m_ny);
if(!RBFV1BuildLinearModel(x,residualy,n,s.m_ny,aterm,v))
{
rep.m_terminationtype=-5;
return;
}
//--- Handle special case: multilayer model with NLayers=0.
//--- Quick exit.
if(algorithmtype==2 && nlayers==0)
{
rep.m_terminationtype=1;
rep.m_iterationscount=0;
rep.m_nmv=0;
rep.m_arows=0;
rep.m_acols=0;
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,0,m_mxnx,0,2,s.m_tree);
s.m_xc.Resize(0,0);
s.m_wr.Resize(0,0);
s.m_nc=0;
s.m_rmax=0;
s.m_v=v;
s.m_v.Resize(s.m_ny,m_mxnx+1);
return;
}
//--- Second model in a sequence - RBF term.
//--- NOTE: assignments below are not necessary, but without them
//--- MSVC complains about unitialized variables.
nc=0;
rmax=0;
layerscnt=0;
modelstatus=false;
if(algorithmtype==1)
{
//--- Add RBF model.
//--- This model uses local KD-trees to speed-up nearest neighbor searches.
nc=n;
xc=x;
xc.Resize(nc,m_mxnx);
rmax=0;
radius=vector<double>::Zeros(nc);
ctags.Resize(nc);
for(i=0; i<nc; i++)
ctags.Set(i,i);
CNearestNeighbor::KDTreeBuildTagged(xc,ctags,nc,m_mxnx,0,2,ctree);
if(nc==0)
rmax=1;
else
{
if(nc==1)
{
radius.Set(0,radvalue);
rmax=radius[0];
}
else
{
//--- NC>1, calculate radii using distances to nearest neigbors
for(i=0; i<nc; i++)
{
xcx=xc[i]+0;
if(CNearestNeighbor::KDTreeQueryKNN(ctree,xcx,1,false)>0)
{
CNearestNeighbor::KDTreeQueryResultsDistances(ctree,dist);
radius.Set(i,radvalue*dist[0]);
}
else
{
//--- No neighbors found (it will happen when we have only one center).
//--- Initialize radius with default value.
radius.Set(i,1.0);
}
}
//--- Apply filtering
tmp0=radius;
CTSort::TagSortFast(tmp0,tmp1,nc);
for(i=0; i<nc; i++)
radius.Set(i,MathMin(radius[i],radzvalue*tmp0[nc/2]));
//--- Calculate RMax, check that all radii are non-zero
for(i=0; i<nc; i++)
rmax=MathMax(rmax,radius[i]);
for(i=0; i<nc; i++)
{
if(radius[i]==0.0)
{
rep.m_terminationtype=-5;
return;
}
}
}
}
tags.Resize(n);
for(i=0; i<n; i++)
{
tags.Set(i,i);
}
CNearestNeighbor::KDTreeBuildTagged(x,tags,n,m_mxnx,0,2,tree);
BuildRDFModellSQR(x,residualy,xc,radius,n,nc,s.m_ny,tree,ctree,epsort,epserr,maxits,rep.m_annz,snnz,omega,rep.m_terminationtype,rep.m_iterationscount,rep.m_nmv);
layerscnt=1;
modelstatus=true;
}
if(algorithmtype==2)
{
rmax=radvalue;
BuildRBFMLayersModellSQR(x,residualy,xc,radvalue,radius,n,nc,s.m_ny,nlayers,ctree,1.0E-6,1.0E-6,50,lambdav,rep.m_annz,omega,rep.m_terminationtype,rep.m_iterationscount,rep.m_nmv);
layerscnt=nlayers;
modelstatus=true;
}
//--- check
if(!CAp::Assert(modelstatus,__FUNCTION__+": integrity error"))
return;
if(rep.m_terminationtype<=0)
return;
//--- Model is built
s.m_nc=nc/layerscnt;
s.m_rmax=rmax;
s.m_nl=layerscnt;
s.m_xc=xc;
s.m_xc.Resize(s.m_nc,m_mxnx);
s.m_wr=matrix<double>::Zeros(s.m_nc,1+s.m_nl*s.m_ny);
tags.Resize(s.m_nc);
for(i=0; i<s.m_nc; i++)
tags.Set(i,i);
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,s.m_nc,m_mxnx,0,2,s.m_tree);
s.m_wr.Col(0,radius);
for(i=0; i<s.m_nc; i++)
for(k=0; k<layerscnt; k++)
for(j=0; j<s.m_ny; j++)
s.m_wr.Set(i,1+k*s.m_ny+j,omega.Get(k*s.m_nc+i,j));
s.m_v=v;
s.m_v.Resize(s.m_ny,m_mxnx+1);
rep.m_terminationtype=1;
rep.m_arows=n;
rep.m_acols=s.m_nc;
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1Alloc(CSerializer &s,CRBFV1Model &model)
{
//--- Data
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
CApServ::AllocRealMatrix(s,model.m_xc,-1,-1);
CApServ::AllocRealMatrix(s,model.m_wr,-1,-1);
s.Alloc_Entry();
CApServ::AllocRealMatrix(s,model.m_v,-1,-1);
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1Serialize(CSerializer &s,CRBFV1Model &model)
{
//--- Data
//
s.Serialize_Int(model.m_nx);
s.Serialize_Int(model.m_ny);
s.Serialize_Int(model.m_nc);
s.Serialize_Int(model.m_nl);
CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
CApServ::SerializeRealMatrix(s,model.m_xc,-1,-1);
CApServ::SerializeRealMatrix(s,model.m_wr,-1,-1);
s.Serialize_Double(model.m_rmax);
CApServ::SerializeRealMatrix(s,model.m_v,-1,-1);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1Unserialize(CSerializer &s,CRBFV1Model &model)
{
//--- create variables
int nx=0;
int ny=0;
//--- Unserialize primary model parameters, initialize model.
//--- It is necessary to call RBFV1Create() because some internal fields
//--- which are NOT unserialized will need initialization.
nx=s.Unserialize_Int();
ny=s.Unserialize_Int();
RBFV1Create(nx,ny,model);
model.m_nc=s.Unserialize_Int();
model.m_nl=s.Unserialize_Int();
CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
CApServ::UnserializeRealMatrix(s,model.m_xc);
CApServ::UnserializeRealMatrix(s,model.m_wr);
model.m_rmax=s.Unserialize_Double();
CApServ::UnserializeRealMatrix(s,model.m_v);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY=1 (scalar function) |
//| and NX=2 (2-dimensional space). If you have 3-dimensional space, |
//| use RBFCalc3(). If you have general situation (NX-dimensional |
//| space, NY-dimensional function) you should use general, less |
//| efficient implementation RBFCalc(). |
//| If you want to calculate function values many times, consider |
//| using RBFGridCalc2(), which is far more efficient than many |
//| subsequent calls to RBFCalc2(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV1::RBFV1Calc2(CRBFV1Model &s,double x0,double x1)
{
//--- create variables
double result=0;
int lx=0;
int tg=0;
double d2=0;
double t=0;
double bfcur=0;
double rcur=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=2)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,m_mxnx);
if(s.m_nc==0)
return(result);
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
s.m_calcbufxcx.Set(0,x0);
s.m_calcbufxcx.Set(1,x1);
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
for(int i=0; i<lx; i++)
{
tg=s.m_calcbuftags[i];
d2=CMath::Sqr(x0-s.m_calcbufx.Get(i,0))+CMath::Sqr(x1-s.m_calcbufx.Get(i,1));
rcur=s.m_wr.Get(tg,0);
bfcur=MathExp(-(d2/(rcur*rcur)));
for(int j=0; j<s.m_nl; j++)
{
result=result+bfcur*s.m_wr.Get(tg,1+j);
rcur=0.5*rcur;
t=bfcur*bfcur;
bfcur=t*t;
}
}
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY=1 (scalar function) |
//| and NX=3 (3-dimensional space). If you have 2-dimensional space, |
//| use RBFCalc2(). If you have general situation (NX-dimensional |
//| space, NY-dimensional function) you should use general, less |
//| efficient implementation RBFCalc(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| X2 - third coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV1::RBFV1Calc3(CRBFV1Model &s,double x0,double x1,double x2)
{
//--- create variables
double result=0;
int lx=0;
int tg=0;
double t=0;
double rcur=0;
double bf=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=3)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,m_mxnx);
if(s.m_nc==0)
return(result);
//--- calculating value for F(X)
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
s.m_calcbufxcx.Set(0,x0);
s.m_calcbufxcx.Set(1,x1);
s.m_calcbufxcx.Set(2,x2);
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
for(int i=0; i<lx; i++)
{
tg=s.m_calcbuftags[i];
rcur=s.m_wr.Get(tg,0);
bf=MathExp(-((CMath::Sqr(x0-s.m_calcbufx.Get(i,0))+CMath::Sqr(x1-s.m_calcbufx.Get(i,1))+CMath::Sqr(x2-s.m_calcbufx.Get(i,2)))/CMath::Sqr(rcur)));
for(int j=0; j<s.m_nl; j++)
{
result+=bf*s.m_wr.Get(tg,1+j);
t=bf*bf;
bf=t*t;
}
}
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point. |
//| Same as RBFCalc(), but does not reallocate Y when in is large |
//| enough to store function values. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1CalcBuf(CRBFV1Model &s,CRowDouble &x,CRowDouble &y)
{
//--- create variables
int lx=0;
int tg=0;
double t=0;
double rcur=0;
double bf=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
{
y.Resize(s.m_ny);
}
for(int i=0; i<s.m_ny; i++)
{
y.Set(i,s.m_v.Get(i,m_mxnx));
for(int j=0; j<s.m_nx; j++)
y.Add(i,x[j]*s.m_v.Get(i,j));
}
if(s.m_nc==0)
return;
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
for(int i=0; i<s.m_nx; i++)
s.m_calcbufxcx.Set(i,x[i]);
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
for(int i=0; i<s.m_ny; i++)
{
for(int j=0; j<lx; j++)
{
tg=s.m_calcbuftags[j];
rcur=s.m_wr.Get(tg,0);
bf=MathExp(-((CMath::Sqr(s.m_calcbufxcx[0]-s.m_calcbufx.Get(j,0))+CMath::Sqr(s.m_calcbufxcx[1]-s.m_calcbufx.Get(j,1))+CMath::Sqr(s.m_calcbufxcx[2]-s.m_calcbufx.Get(j,2)))/CMath::Sqr(rcur)));
for(int k=0; k<s.m_nl; k++)
{
y.Add(i,bf*s.m_wr.Get(tg,1+k*s.m_ny+i));
t=bf*bf;
bf=t*t;
}
}
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point, using external buffer object (internal temporaries of|
//| RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different |
//| instances of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFV1CreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1TSCalcBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
CRowDouble &x,CRowDouble &y)
{
//--- create variables
int lx=0;
int tg=0;
double t=0;
double rcur=0;
double bf=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
for(int i=0; i<s.m_ny; i++)
{
y.Set(i,s.m_v.Get(i,m_mxnx));
for(int j=0; j<s.m_nx; j++)
y.Add(i,s.m_v.Get(i,j)*x[j]);
}
if(s.m_nc==0)
return;
buf.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
for(int i=0; i<s.m_nx; i++)
buf.m_calcbufxcx.Set(i,x[i]);
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
for(int i=0; i<s.m_ny; i++)
{
for(int j=0; j<lx; j++)
{
tg=buf.m_calcbuftags[j];
rcur=s.m_wr.Get(tg,0);
bf=MathExp(-((CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))/CMath::Sqr(rcur)));
for(int k=0; k<s.m_nl; k++)
{
y.Add(i,bf*s.m_wr.Get(tg,1+k*s.m_ny+i));
t=bf*bf;
bf=t*t;
}
}
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its derivatives, using external buffer object (internal|
//| temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances |
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFV1CreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y, DY - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1TSDiffBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,CRowDouble &dy)
{
//--- create variables
int kk=0;
int lx=0;
int tg=0;
double rcur=0;
double invrcur2=0;
double f=0;
double df=0;
double w=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
for(int i=0; i<s.m_ny; i++)
{
y.Set(i,s.m_v.Get(i,m_mxnx));
for(int j=0; j<s.m_nx; j++)
{
y.Add(i,s.m_v.Get(i,j)*x[j]);
dy.Set(i*s.m_nx+j,s.m_v.Get(i,j));
}
}
if(s.m_nc==0)
return;
buf.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
for(int i=0; i<s.m_nx; i++)
buf.m_calcbufxcx.Set(i,x[i]);
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
for(int i=0; i<s.m_ny; i++)
{
for(int j=0; j<lx; j++)
{
tg=buf.m_calcbuftags[j];
rcur=s.m_wr.Get(tg,0);
invrcur2=1/(rcur*rcur);
f=MathExp(-(CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))*invrcur2);
df=-f;
for(int k=0; k<s.m_nl; k++)
{
w=s.m_wr.Get(tg,1+k*s.m_ny+i);
y.Add(i,f*w);
for(kk=0; kk<s.m_nx; kk++)
dy.Add(i*s.m_nx+kk,w*df*invrcur2*2*(buf.m_calcbufxcx[kk]-buf.m_calcbufx.Get(j,kk)));
f=MathPow(f,4.0);
df=-f;
invrcur2*=4;
}
}
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its first/second derivatives, using external buffer|
//| object (internal temporaries of the RBF model are not modified).|
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different |
//| instances of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFV1CreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y, DY, D2Y - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//| D2Y - derivatives, array[NY*NX*NX]. D2Y is not |
//| reallocated when it is larger than NY*NX*NX. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1TSHessBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,CRowDouble &dy,
CRowDouble &d2y)
{
//--- create variables
int lx=0;
int tg=0;
double t=0;
double rcur=0;
double invrcur2=0;
double f=0;
double df=0;
double d2f=0;
double w=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
if(CAp::Len(d2y)<s.m_ny*s.m_nx*s.m_nx)
d2y.Resize(s.m_ny*s.m_nx*s.m_nx);
for(int i=0; i<s.m_ny; i++)
{
y.Set(i,s.m_v.Get(i,m_mxnx));
for(int j=0; j<s.m_nx; j++)
{
y.Add(i,s.m_v.Get(i,j)*x[j]);
dy.Set(i*s.m_nx+j,s.m_v.Get(i,j));
}
}
for(int i=0; i<(s.m_ny*s.m_nx*s.m_nx); i++)
d2y.Set(i,0);
if(s.m_nc==0)
return;
for(int i=0; i<(m_mxnx); i++)
buf.m_calcbufxcx.Set(i,0);
for(int i=0; i<s.m_nx; i++)
buf.m_calcbufxcx.Set(i,x[i]);
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
for(int i=0; i<s.m_ny; i++)
{
for(int j=0; j<lx; j++)
{
tg=buf.m_calcbuftags[j];
rcur=s.m_wr.Get(tg,0);
invrcur2=1/(rcur*rcur);
f=MathExp(-((CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))*invrcur2));
df=-f;
d2f=f;
for(int k=0; k<s.m_nl; k++)
{
w=s.m_wr.Get(tg,1+k*s.m_ny+i);
y.Add(i,f*w);
for(int i0=0; i0<s.m_nx; i0++)
{
for(int i1=0; i1<s.m_nx; i1++)
{
if(i0==i1)
{
//--- Compute derivative and diagonal element of the Hessian
dy.Add(i*s.m_nx+i0,w*df*invrcur2*2*(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0)));
d2y.Add(i*s.m_nx*s.m_nx+i0*s.m_nx+i1,w*(d2f*invrcur2*invrcur2*4*CMath::Sqr(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0))+df*invrcur2*2));
}
else
{
//--- Compute off-diagonal element of the Hessian
d2y.Add(i*s.m_nx*s.m_nx+i0*s.m_nx+i1,w*d2f*invrcur2*invrcur2*4*(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0))*(buf.m_calcbufxcx[i1]-buf.m_calcbufx.Get(j,i1)));
}
}
}
t=f*f;
f=MathPow(f,4.0);
df=-f;
d2f=f;
invrcur2*=4;
}
}
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the regular |
//| grid. |
//| Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J]) |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - array of grid nodes, first coordinates, array[N0] |
//| N0 - grid size (number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| N1 - grid size (number of nodes) in the second dimension|
//| OUTPUT PARAMETERS: |
//| Y - function values, array[N0,N1]. Y is out-variable |
//| and is reallocated by this function. |
//| NOTE: as a special exception, this function supports unordered |
//| arrays X0 and X1. However, future versions may be more |
//| efficient for X0/X1 ordered by ascending. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1GridCalc2(CRBFV1Model &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CMatrixDouble &y)
{
//--- create variables
CRowDouble cpx0;
CRowDouble cpx1;
CRowInt p01;
CRowInt p11;
CRowInt p2;
double rlimit=0;
double xcnorm2=0;
int hp01=0;
double hcpx0=0;
double xc0=0;
double xc1=0;
double omega=0;
double radius=0;
int i00=0;
int i01=0;
int i10=0;
int i11=0;
y.Resize(0,0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
y=matrix<double>::Zeros(n0,n1);
if((s.m_ny!=1 || s.m_nx!=2) || s.m_nc==0)
return;
//--- create and sort arrays
cpx0.Resize(n0);
for(int i=0; i<n0; i++)
cpx0.Set(i,x0[i]);
CTSort::TagSort(cpx0,n0,p01,p2);
cpx1.Resize(n1);
for(int i=0; i<n1; i++)
cpx1.Set(i,x1[i]);
CTSort::TagSort(cpx1,n1,p11,p2);
//--- calculate function's value
for(int i=0; i<s.m_nc; i++)
{
radius=s.m_wr.Get(i,0);
for(int d=0; d<s.m_nl; d++)
{
omega=s.m_wr.Get(i,1+d);
rlimit=radius*m_rbffarradius;
//search lower and upper indexes
i00=CTSort::LowerBound(cpx0,n0,s.m_xc.Get(i,0)-rlimit);
i01=CTSort::UpperBound(cpx0,n0,s.m_xc.Get(i,0)+rlimit);
i10=CTSort::LowerBound(cpx1,n1,s.m_xc.Get(i,1)-rlimit);
i11=CTSort::UpperBound(cpx1,n1,s.m_xc.Get(i,1)+rlimit);
xc0=s.m_xc.Get(i,0);
xc1=s.m_xc.Get(i,1);
for(int j=i00; j<i01 ; j++)
{
hcpx0=cpx0[j];
hp01=p01[j];
for(int k=i10; k<=i11-1; k++)
{
xcnorm2=CMath::Sqr(hcpx0-xc0)+CMath::Sqr(cpx1[k]-xc1);
if((double)(xcnorm2)<=(double)(rlimit*rlimit))
y.Add(hp01,p11[k],MathExp(-(xcnorm2/CMath::Sqr(radius)))*omega);
}
}
radius=0.5*radius;
}
}
//--- add linear term
for(int i=0; i<n0; i++)
{
for(int j=0; j<n1; j++)
y.Add(i,j,s.m_v.Get(0,0)*x0[i]+s.m_v.Get(0,1)*x1[j]+s.m_v.Get(0,m_mxnx));
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1GridCalc3VRec(CRBFV1Model &s,CRowDouble &x0,
int n0,CRowDouble &x1,int n1,
CRowDouble &x2,int n2,
CRowInt &blocks0,int block0a,
int block0b,CRowInt &blocks1,
int block1a,int block1b,
CRowInt &blocks2,int block2a,
int block2b,bool &flagy[],
bool sparsey,double searchradius,
double avgfuncpernode,
CGridCalc3v1Buf &bufpool,CRowDouble &y)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int t=0;
int l=0;
int i0=0;
int i1=0;
int i2=0;
int ic=0;
CGridCalc3v1Buf pbuf;
int flag12dim1=0;
int flag12dim2=0;
double problemcost=0;
int maxbs=0;
int nx=s.m_nx;
int ny=s.m_ny;
double v=0;
int kc=0;
int tg=0;
double rcur=0;
double rcur2=0;
double basisfuncval=0;
int dstoffs=0;
int srcoffs=0;
int ubnd=0;
double w0=0;
double w1=0;
double w2=0;
bool allnodes=false;
bool somenodes=false;
pbuf=bufpool;
//--- Calculate RBF model
for(i2=block2a; i2<=block2b-1; i2++)
{
for(i1=block1a; i1<=block1b-1; i1++)
{
for(i0=block0a; i0<=block0b-1; i0++)
{
//--- Analyze block - determine what elements are needed and what are not.
//--- After this block is done, two flag variables can be used:
//--- * SomeNodes, which is True when there are at least one node which have
//--- to be calculated
//--- * AllNodes, which is True when all nodes are required
somenodes=true;
allnodes=true;
flag12dim1=blocks1[i1+1]-blocks1[i1];
flag12dim2=blocks2[i2+1]-blocks2[i2];
if(sparsey)
{
//--- Use FlagY to determine what is required.
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag0,n0);
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag1,n1);
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag2,n2);
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag12,flag12dim1*flag12dim2);
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
pbuf.m_flag0[i]=false;
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
pbuf.m_flag1[j]=false;
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
pbuf.m_flag2[k]=false;
for(i=0; i<flag12dim1*flag12dim2; i++)
pbuf.m_flag12[i]=false;
somenodes=false;
allnodes=true;
for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
{
for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
{
dstoffs=j-blocks1[i1]+flag12dim1*(k-blocks2[i2]);
srcoffs=j*n0+k*n0*n1;
for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
if(flagy[srcoffs+i])
{
pbuf.m_flag0[i]=true;
pbuf.m_flag1[j]=true;
pbuf.m_flag2[k]=true;
pbuf.m_flag12[dstoffs]=true;
somenodes=true;
}
else
allnodes=false;
}
}
}
//--- Skip block if it is completely empty.
if(!somenodes)
continue;
//--- compute linear term for block (I0,I1,I2)
for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
{
for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
{
//--- do we need this micro-row?
if(!allnodes && !pbuf.m_flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])])
continue;
//--- Compute linear term
for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
{
pbuf.m_tx.Set(0,x0[i]);
pbuf.m_tx.Set(1,x1[j]);
pbuf.m_tx.Set(2,x2[k]);
for(l=0; l<s.m_ny; l++)
{
v=s.m_v.Get(l,m_mxnx);
for(t=0; t<nx; t++)
v+=s.m_v.Get(l,t)*pbuf.m_tx[t];
y.Set(l+ny*(i+j*n0+k*n0*n1),v);
}
}
}
}
//--- compute RBF term for block (I0,I1,I2)
pbuf.m_tx.Set(0,0.5*(x0[blocks0[i0]]+x0[blocks0[i0+1]-1]));
pbuf.m_tx.Set(1,0.5*(x1[blocks1[i1]]+x1[blocks1[i1+1]-1]));
pbuf.m_tx.Set(2,0.5*(x2[blocks2[i2]]+x2[blocks2[i2+1]-1]));
kc=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,pbuf.m_requestbuf,pbuf.m_tx,searchradius,true);
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,pbuf.m_requestbuf,pbuf.m_calcbufx);
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,pbuf.m_requestbuf,pbuf.m_calcbuftags);
for(ic=0; ic<kc; ic++)
{
pbuf.m_cx.Set(0,pbuf.m_calcbufx.Get(ic,0));
pbuf.m_cx.Set(1,pbuf.m_calcbufx.Get(ic,1));
pbuf.m_cx.Set(2,pbuf.m_calcbufx.Get(ic,2));
tg=pbuf.m_calcbuftags[ic];
rcur=s.m_wr.Get(tg,0);
rcur2=rcur*rcur;
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
{
if(allnodes || pbuf.m_flag0[i])
pbuf.m_expbuf0.Set(i,MathExp(-(CMath::Sqr(x0[i]-pbuf.m_cx[0])/rcur2)));
else
pbuf.m_expbuf0.Set(i,0.0);
}
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
{
if(allnodes || pbuf.m_flag1[j])
pbuf.m_expbuf1.Set(j,MathExp(-(CMath::Sqr(x1[j]-pbuf.m_cx[1])/rcur2)));
else
pbuf.m_expbuf1.Set(j,0.0);
}
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
{
if(allnodes || pbuf.m_flag2[k])
pbuf.m_expbuf2.Set(k,MathExp(-(CMath::Sqr(x2[k]-pbuf.m_cx[2])/rcur2)));
else
pbuf.m_expbuf2.Set(k,0.0);
}
for(t=0; t<s.m_nl; t++)
{
//--- Calculate
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
{
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
{
//--- do we need this micro-row?
if(!allnodes && !pbuf.m_flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])])
continue;
//--- Prepare local variables
dstoffs=ny*(blocks0[i0]+j*n0+k*n0*n1);
v=pbuf.m_expbuf1[j]*pbuf.m_expbuf2[k];
//--- Optimized for NY=1
if(s.m_ny==1)
{
w0=s.m_wr.Get(tg,1+t*s.m_ny+0);
ubnd=blocks0[i0+1]-1;
for(i=blocks0[i0]; i<=ubnd; i++)
{
basisfuncval=pbuf.m_expbuf0[i]*v;
y.Add(dstoffs,basisfuncval*w0);
dstoffs++;
}
continue;
}
//--- Optimized for NY=2
if(s.m_ny==2)
{
w0=s.m_wr.Get(tg,1+t*s.m_ny);
w1=s.m_wr.Get(tg,1+t*s.m_ny+1);
ubnd=blocks0[i0+1]-1;
for(i=blocks0[i0]; i<=ubnd; i++)
{
basisfuncval=pbuf.m_expbuf0[i]*v;
y.Add(dstoffs,basisfuncval*w0);
y.Add(dstoffs+1,basisfuncval*w1);
dstoffs+=2;
}
continue;
}
//--- Optimized for NY=3
if(s.m_ny==3)
{
w0=s.m_wr.Get(tg,1+t*s.m_ny);
w1=s.m_wr.Get(tg,1+t*s.m_ny+1);
w2=s.m_wr.Get(tg,1+t*s.m_ny+2);
ubnd=blocks0[i0+1]-1;
for(i=blocks0[i0]; i<=ubnd; i++)
{
basisfuncval=pbuf.m_expbuf0[i]*v;
y.Add(dstoffs,basisfuncval*w0);
y.Add(dstoffs+1,basisfuncval*w1);
y.Add(dstoffs+2,basisfuncval*w2);
dstoffs+=3;
}
continue;
}
//--- General case
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
{
basisfuncval=pbuf.m_expbuf0[i]*v;
for(l=0; l<s.m_ny; l++)
y.Add(l+dstoffs,basisfuncval*s.m_wr.Get(tg,1+t*s.m_ny+l));
dstoffs+=ny;
}
}
}
//--- Update basis functions
if(t!=s.m_nl-1)
{
ubnd=blocks0[i0+1]-1;
for(i=blocks0[i0]; i<=ubnd; i++)
{
if(allnodes || pbuf.m_flag0[i])
pbuf.m_expbuf0.Set(i,MathPow(pbuf.m_expbuf0[i],4));
}
ubnd=blocks1[i1+1]-1;
for(j=blocks1[i1]; j<=ubnd; j++)
{
if(allnodes || pbuf.m_flag1[j])
pbuf.m_expbuf1.Set(j,MathPow(pbuf.m_expbuf1[j],4));
}
ubnd=blocks2[i2+1]-1;
for(k=blocks2[i2]; k<=ubnd; k++)
{
if(allnodes || pbuf.m_flag2[k])
pbuf.m_expbuf2.Set(k,MathPow(pbuf.m_expbuf2[k],4));
}
}
}
}
}
}
}
//--- Recycle buffer object back to pool
bufpool=pbuf;
}
//+------------------------------------------------------------------+
//| This function "unpacks" RBF model by extracting its coefficients.|
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| NX - dimensionality of argument |
//| NY - dimensionality of the target function |
//| XWR - model information, array[NC,NX+NY+1]. |
//| One row of the array corresponds to one basis |
//| function: |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of|
//| the function being modelled |
//| * last column - radius, same for all dimensions|
//| of the function being modelled |
//| NC - number of the centers |
//| V - polynomial term , array[NY,NX+1]. One row per one |
//| dimension of the function being modelled. First NX |
//| elements are linear coefficients, V[NX] is equal to|
//| the constant part. |
//+------------------------------------------------------------------+
void CRBFV1::RBFV1Unpack(CRBFV1Model &s,int &nx,int &ny,
CMatrixDouble &xwr,int &nc,CMatrixDouble &v)
{
//--- create variables
double rcur=0;
int i1_=0;
nx=0;
ny=0;
xwr.Resize(0,0);
nc=0;
v.Resize(0,0);
nx=s.m_nx;
ny=s.m_ny;
nc=s.m_nc;
//--- Fill V
v=s.m_v;
v.Resize(s.m_ny,s.m_nx+1);
v.Col(s.m_nx,s.m_v.Col(m_mxnx)+0);
//--- Fill XWR and V
if(nc*s.m_nl>0)
{
xwr.Resize(s.m_nc*s.m_nl,s.m_nx+s.m_ny+1);
for(int i=0; i<s.m_nc; i++)
{
rcur=s.m_wr.Get(i,0);
for(int j=0; j<s.m_nl; j++)
{
for(int i_=0; i_<s.m_nx; i_++)
xwr.Set(i*s.m_nl+j,i_,s.m_xc.Get(i,i_));
i1_=(1+j*s.m_ny)-(s.m_nx);
for(int i_=s.m_nx; i_<=s.m_nx+s.m_ny-1; i_++)
xwr.Set(i*s.m_nl+j,i_,s.m_wr.Get(i,i_+i1_));
xwr.Set(i*s.m_nl+j,s.m_nx+s.m_ny,rcur);
rcur=0.5*rcur;
}
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CRBFV1::RBFV1BuildLinearModel(CMatrixDouble &x,CMatrixDouble &y,
int n,int ny,int modeltype,CMatrixDouble &v)
{
//--- create variables
bool result=false;
CRowDouble tmpy;
CMatrixDouble a;
double scaling=0;
CRowDouble shifting;
double mn=0;
double mx=0;
CRowDouble c;
CLSFitReport rep;
int i=0;
int j=0;
int k=0;
int info=0;
v.Resize(0,0);
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return(false);
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
return(false);
//--- Handle degenerate case (N=0)
result=true;
v=matrix<double>::Zeros(ny,m_mxnx+1);
if(n==0)
return(result);
//--- Allocate temporaries
tmpy.Resize(n);
//--- General linear model.
switch(modeltype)
{
case 1:
//--- Calculate scaling/shifting, transform variables, prepare LLS problem
a.Resize(n,m_mxnx+1);
shifting.Resize(m_mxnx);
scaling=0;
for(i=0; i<m_mxnx; i++)
{
mn=x.Get(0,i);
mx=mn;
for(j=1; j<n; j++)
{
double value=x.Get(j,i);
if(mn>value)
mn=value;
if(mx<value)
mx=value;
}
scaling=MathMax(scaling,mx-mn);
shifting.Set(i,0.5*(mx+mn));
}
if(scaling==0.0)
scaling=1;
else
scaling/=2;
for(i=0; i<n; i++)
{
for(j=0; j<m_mxnx; j++)
a.Set(i,j,(x.Get(i,j)-shifting[j])/scaling);
a.Set(i,m_mxnx,1);
}
//--- Solve linear system in transformed variables, make backward
for(i=0; i<ny; i++)
{
tmpy=y.Col(i)+0;
CLSFit::LSFitLinear(tmpy,a,n,m_mxnx+1,info,c,rep);
if(info<=0)
{
result=false;
return(result);
}
for(j=0; j<m_mxnx; j++)
v.Set(i,j,c[j]/scaling);
v.Set(i,m_mxnx,c[m_mxnx]-CAblasF::RDotVR(m_mxnx,shifting,v,i));
for(j=0; j<n; j++)
y.Add(j,i,-(CAblasF::RDotRR(m_mxnx,x,j,v,i)+v.Get(i,m_mxnx)));
}
break;
//--- Constant model, very simple
case 2:
v.Fill(0,ny,m_mxnx+1);
for(i=0; i<ny; i++)
{
for(j=0; j<n; j++)
v.Add(i,m_mxnx,y.Get(j,i));
if(n>0)
v.Mul(i,m_mxnx,1.0/n);
for(j=0; j<n; j++)
y.Add(j,i,-v.Get(i,m_mxnx));
}
break;
case 3:
//--- Zero model
v.Fill(0,ny,m_mxnx+1);
break;
default:
CAp::Assert(false,__FUNCTION__+": unknown model type");
return(false);
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1::BuildRDFModellSQR(CMatrixDouble &x,CMatrixDouble &y,
CMatrixDouble &xc,CRowDouble &r,
int n,int nc,int ny,CKDTree &pointstree,
CKDTree &centerstree,double epsort,
double epserr,int maxits,int &gnnz,
int &snnz,CMatrixDouble &w,int &info,
int &iterationscount,int &nmv)
{
//--- create variables
CLinLSQRState State;
CLinLSQRReport lsqrrep;
CSparseMatrix spg;
CSparseMatrix sps;
CRowInt nearcenterscnt;
CRowInt nearpointscnt;
CRowInt skipnearpointscnt;
CRowInt farpointscnt;
int maxnearcenterscnt=0;
int maxnearpointscnt=0;
int maxfarpointscnt=0;
int sumnearcenterscnt=0;
int sumnearpointscnt=0;
int sumfarpointscnt=0;
double maxrad=0;
CRowInt pointstags;
CRowInt centerstags;
CMatrixDouble nearpoints;
CMatrixDouble nearcenters;
CMatrixDouble farpoints;
int tmpi=0;
int pointscnt=0;
int centerscnt=0;
CRowDouble xcx;
CRowDouble tmpy;
CRowDouble tc;
CRowDouble g;
CRowDouble c;
int i=0;
int j=0;
int k=0;
int sind=0;
CMatrixDouble a;
double vv=0;
double vx=0;
double vy=0;
double vz=0;
double vr=0;
double gnorm2=0;
CRowDouble tmp0;
CRowDouble tmp1;
CRowDouble tmp2;
double fx=0;
CMatrixDouble xx;
CMatrixDouble cx;
double mrad=0;
int i_=0;
gnnz=0;
snnz=0;
w.Resize(0,0);
info=0;
iterationscount=0;
nmv=0;
//--- Handle special cases: NC=0
if(nc==0)
{
info=1;
iterationscount=0;
nmv=0;
return;
}
//--- Prepare for general case, NC>0
xcx=vector<double>::Zeros(m_mxnx);
pointstags.Resize(n);
centerstags.Resize(nc);
info=-1;
iterationscount=0;
nmv=0;
//--- This block prepares quantities used to compute approximate cardinal basis functions (ACBFs):
//--- * NearCentersCnt[] - array[NC], whose elements store number of near centers used to build ACBF
//--- * NearPointsCnt[] - array[NC], number of near points used to build ACBF
//--- * FarPointsCnt[] - array[NC], number of far points (ones where ACBF is nonzero)
//--- * MaxNearCentersCnt - max(NearCentersCnt)
//--- * MaxNearPointsCnt - max(NearPointsCnt)
//--- * SumNearCentersCnt - sum(NearCentersCnt)
//--- * SumNearPointsCnt - sum(NearPointsCnt)
//--- * SumFarPointsCnt - sum(FarPointsCnt)
nearcenterscnt.Resize(nc);
nearpointscnt.Resize(nc);
skipnearpointscnt.Resize(nc);
farpointscnt.Resize(nc);
maxnearcenterscnt=0;
maxnearpointscnt=0;
maxfarpointscnt=0;
sumnearcenterscnt=0;
sumnearpointscnt=0;
sumfarpointscnt=0;
for(i=0; i<nc; i++)
{
xcx=xc[i]+0;
//--- Determine number of near centers and maximum radius of near centers
nearcenterscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(centerstree,xcx,r[i]*m_rbfnearradius,true));
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
maxrad=0;
for(j=0; j<nearcenterscnt[i]; j++)
maxrad=MathMax(maxrad,MathAbs(r[centerstags[j]]));
//--- Determine number of near points (ones which used to build ACBF)
//--- and skipped points (the most near points which are NOT used to build ACBF
//--- and are NOT included in the near points count
skipnearpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,0.1*r[i],true));
nearpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,(r[i]+maxrad)*m_rbfnearradius,true)-skipnearpointscnt[i]);
//--- check
if(!CAp::Assert(nearpointscnt[i]>=0,__FUNCTION__+": internal error"))
return;
//--- Determine number of far points
farpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,MathMax(r[i]*m_rbfnearradius+maxrad*m_rbffarradius,r[i]*m_rbffarradius),true));
//--- calculate sum and max, make some basic checks
//--- check
if(!CAp::Assert(nearcenterscnt[i]>0,__FUNCTION__+": internal error"))
return;
maxnearcenterscnt=MathMax(maxnearcenterscnt,nearcenterscnt[i]);
maxnearpointscnt=MathMax(maxnearpointscnt,nearpointscnt[i]);
maxfarpointscnt=MathMax(maxfarpointscnt,farpointscnt[i]);
sumnearcenterscnt=sumnearcenterscnt+nearcenterscnt[i];
sumnearpointscnt=sumnearpointscnt+nearpointscnt[i];
sumfarpointscnt=sumfarpointscnt+farpointscnt[i];
}
snnz=sumnearcenterscnt;
gnnz=sumfarpointscnt;
//--- check
if(!CAp::Assert(maxnearcenterscnt>0,__FUNCTION__+": internal error"))
return;
//--- Allocate temporaries.
//--- NOTE: we want to avoid allocation of zero-size arrays, so we
//--- use max(desired_size,1) instead of desired_size when performing
//--- memory allocation.
a=matrix<double>::Zeros(maxnearpointscnt+maxnearcenterscnt,maxnearcenterscnt);
tmpy=vector<double>::Zeros(maxnearpointscnt+maxnearcenterscnt);
g=vector<double>::Zeros(maxnearcenterscnt);
c=vector<double>::Zeros(maxnearcenterscnt);
nearcenters=matrix<double>::Zeros(maxnearcenterscnt,m_mxnx);
nearpoints=matrix<double>::Zeros(MathMax(maxnearpointscnt,1),m_mxnx);
farpoints=matrix<double>::Zeros(MathMax(maxfarpointscnt,1),m_mxnx);
//--- fill matrix SpG
CSparse::SparseCreate(n,nc,gnnz,spg);
CSparse::SparseCreate(nc,nc,snnz,sps);
for(i=0; i<nc; i++)
{
centerscnt=nearcenterscnt[i];
//--- main center
xcx=xc[i]+0;
//--- center's tree
tmpi=CNearestNeighbor::KDTreeQueryKNN(centerstree,xcx,centerscnt,true);
//--- check
if(!CAp::Assert(tmpi==centerscnt,__FUNCTION__+": internal error"))
return;
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
//--- point's tree
mrad=0;
for(j=0; j<centerscnt; j++)
mrad=MathMax(mrad,r[centerstags[j]]);
//--- we need to be sure that 'CTree' contains
//--- at least one side center
CSparse::SparseSet(sps,i,i,1);
c.Fill(0);
c.Set(0,1.0);
//---
if(centerscnt>1 && nearpointscnt[i]>0)
{
//--- first KDTree request for points
pointscnt=nearpointscnt[i];
tmpi=CNearestNeighbor::KDTreeQueryKNN(pointstree,xcx,skipnearpointscnt[i]+nearpointscnt[i],true);
//--- check
if(!CAp::Assert(tmpi==skipnearpointscnt[i]+nearpointscnt[i],__FUNCTION__+": internal error"))
return;
CNearestNeighbor::KDTreeQueryResultsX(pointstree,xx);
sind=skipnearpointscnt[i];
for(j=0; j<pointscnt; j++)
{
vx=xx.Get(sind+j,0);
vy=xx.Get(sind+j,1);
vz=xx.Get(sind+j,2);
for(k=0; k<centerscnt; k++)
{
vv=vx-cx.Get(k,0);
vr=vv*vv;
vv=vy-cx.Get(k,1);
vr+=vv*vv;
vv=vz-cx.Get(k,2);
vr+=vv*vv;
vv=r[centerstags[k]];
a.Set(j,k,MathExp(-(vr/(vv*vv))));
}
}
for(j=0; j<centerscnt; j++)
g.Set(j,MathExp(-(CMath::Sqr(xcx[0]-cx.Get(j,0))+CMath::Sqr(xcx[1]-cx.Get(j,1))+CMath::Sqr(xcx[2]-cx.Get(j,2)))/CMath::Sqr(r[centerstags[j]])));
//--- calculate the problem
gnorm2=CAblasF::RDotV2(centerscnt,g);
for(j=0; j<pointscnt; j++)
{
vv=CAblasF::RDotVR(centerscnt,g,a,j);
vv=vv/gnorm2;
tmpy.Set(j,-vv);
for(i_=0; i_<centerscnt; i_++)
a.Add(j,i_,-vv*g[i_]);
}
for(j=pointscnt; j<pointscnt+centerscnt; j++)
{
for(k=0; k<centerscnt; k++)
a.Set(j,k,0.0);
a.Set(j,j-pointscnt,1.0E-6);
tmpy.Set(j,0.0);
}
CFbls::FblsSolveLS(a,tmpy,pointscnt+centerscnt,centerscnt,tmp0,tmp1,tmp2);
for(i_=0; i_<centerscnt; i_++)
c.Set(i_,tmpy[i_]);
vv=CAblasF::RDotV(centerscnt,g,c);
vv=vv/gnorm2;
for(i_=0; i_<centerscnt; i_++)
c.Add(i_,-vv*g[i_]);
vv=1/gnorm2;
for(i_=0; i_<centerscnt; i_++)
c.Add(i_,vv*g[i_]);
for(j=0; j<centerscnt; j++)
CSparse::SparseSet(sps,i,centerstags[j],c[j]);
}
//--- second KDTree request for points
pointscnt=farpointscnt[i];
tmpi=CNearestNeighbor::KDTreeQueryKNN(pointstree,xcx,pointscnt,true);
//--- check
if(!CAp::Assert(tmpi==pointscnt,__FUNCTION__+": internal error"))
return;
CNearestNeighbor::KDTreeQueryResultsX(pointstree,xx);
CNearestNeighbor::KDTreeQueryResultsTags(pointstree,pointstags);
//fill SpG matrix
for(j=0; j<pointscnt; j++)
{
fx=0;
vx=xx.Get(j,0);
vy=xx.Get(j,1);
vz=xx.Get(j,2);
for(k=0; k<centerscnt; k++)
{
vv=vx-cx.Get(k,0);
vr=vv*vv;
vv=vy-cx.Get(k,1);
vr+=vv*vv;
vv=vz-cx.Get(k,2);
vr+=vv*vv;
vv=r[centerstags[k]];
vv=vv*vv;
fx+=c[k]*MathExp(-(vr/vv));
}
CSparse::SparseSet(spg,pointstags[j],i,fx);
}
}
CSparse::SparseConvertToCRS(spg);
CSparse::SparseConvertToCRS(sps);
//--- solve by LSQR method
tmpy.Resize(n);
tc.Resize(nc);
w=matrix<double>::Zeros(nc,ny);
CLinLSQR::LinLSQRCreate(n,nc,State);
CLinLSQR::LinLSQRSetCond(State,epsort,epserr,maxits);
for(i=0; i<ny; i++)
{
tmpy=y.Col(i)+0;
CLinLSQR::LinLSQRSolveSparse(State,spg,tmpy);
CLinLSQR::LinLSQRResults(State,c,lsqrrep);
if(lsqrrep.m_terminationtype<=0)
{
info=-4;
return;
}
CSparse::SparseMTV(sps,c,tc);
for(j=0; j<nc; j++)
w.Set(j,i,tc[j]);
iterationscount+=lsqrrep.m_iterationscount;
nmv+=lsqrrep.m_nmv;
}
info=1;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV1::BuildRBFMLayersModellSQR(CMatrixDouble &x,CMatrixDouble &y,
CMatrixDouble &xc,double rval,
CRowDouble &r,int n,int &nc,
int ny,int nlayers,
CKDTree &centerstree,double epsort,
double epserr,int maxits,
double lambdav,int &annz,
CMatrixDouble &w,int &info,
int &iterationscount,int &nmv)
{
//--- create variables
CLinLSQRState State;
CLinLSQRReport lsqrrep;
CSparseMatrix spa;
double anorm=0;
CRowDouble omega;
CRowDouble xx;
CRowDouble tmpy;
CMatrixDouble cx;
double yval=0;
int nec=0;
CRowInt centerstags;
int layer=0;
int i=0;
int j=0;
int k=0;
double v=0;
double rmaxbefore=0;
double rmaxafter=0;
xc.Resize(0,0);
r.Resize(0);
nc=0;
annz=0;
w.Resize(0,0);
info=0;
iterationscount=0;
nmv=0;
//--- check
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": invalid argument(NLayers<0)"))
return;
if(!CAp::Assert(n>=0,__FUNCTION__+": invalid argument(N<0)"))
return;
if(!CAp::Assert(m_mxnx>0 && m_mxnx<=3,__FUNCTION__+": internal error(invalid global const MxNX: either MxNX<=0 or MxNX>3)"))
return;
annz=0;
if(n==0 || nlayers==0)
{
info=1;
iterationscount=0;
nmv=0;
return;
}
nc=n*nlayers;
xx.Resize(m_mxnx);
centerstags.Resize(n);
xc.Resize(nc,m_mxnx);
r.Resize(nc);
for(i=0; i<nc; i++)
{
for(j=0; j<m_mxnx; j++)
xc.Set(i,j,x.Get(i%n,j));
}
for(i=0; i<nc; i++)
r.Set(i,rval/MathPow(2,i/n));
for(i=0; i<n; i++)
centerstags.Set(i,i);
CNearestNeighbor::KDTreeBuildTagged(xc,centerstags,n,m_mxnx,0,2,centerstree);
omega.Resize(n);
tmpy.Resize(n);
w.Resize(nc,ny);
info=-1;
iterationscount=0;
nmv=0;
CLinLSQR::LinLSQRCreate(n,n,State);
CLinLSQR::LinLSQRSetCond(State,epsort,epserr,maxits);
CLinLSQR::LinLSQRSetLambdaI(State,1.0E-6);
//--- calculate number of non-zero elements for sparse matrix
for(i=0; i<n; i++)
{
xx=x[i]+0;
annz+=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[0]*m_rbfmlradius,true);
}
for(layer=0; layer<nlayers; layer++)
{
//--- Fill sparse matrix, calculate norm(A)
anorm=0.0;
CSparse::SparseCreate(n,n,annz,spa);
for(i=0; i<n; i++)
{
xx=x[i]+0;
nec=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[layer*n]*m_rbfmlradius,true);
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
for(j=0; j<nec; j++)
{
v=MathExp(-((CMath::Sqr(xx[0]-cx.Get(j,0))+CMath::Sqr(xx[1]-cx.Get(j,1))+CMath::Sqr(xx[2]-cx.Get(j,2)))/CMath::Sqr(r[layer*n+centerstags[j]])));
CSparse::SparseSet(spa,i,centerstags[j],v);
anorm+=CMath::Sqr(v);
}
}
anorm=MathSqrt(anorm);
CSparse::SparseConvertToCRS(spa);
//--- Calculate maximum residual before adding new layer.
//--- This value is not used by algorithm, the only purpose is to make debugging easier.
rmaxbefore=0.0;
for(j=0; j<n; j++)
{
for(i=0; i<ny; i++)
rmaxbefore=MathMax(rmaxbefore,MathAbs(y.Get(j,i)));
}
//--- Process NY dimensions of the target function
for(i=0; i<ny; i++)
{
tmpy=y.Col(i)+0;
//--- calculate Omega for current layer
CLinLSQR::LinLSQRSetLambdaI(State,lambdav*anorm/n);
CLinLSQR::LinLSQRSolveSparse(State,spa,tmpy);
CLinLSQR::LinLSQRResults(State,omega,lsqrrep);
if(lsqrrep.m_terminationtype<=0)
{
info=-4;
return;
}
//--- calculate error for current layer
for(j=0; j<n; j++)
{
yval=0;
for(k=0; k<m_mxnx; k++)
xx.Set(k,x.Get(j,k));
nec=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[layer*n]*m_rbffarradius,true);
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
for(k=0; k<nec; k++)
yval+=omega[centerstags[k]]*MathExp(-((CMath::Sqr(xx[0]-cx.Get(k,0))+CMath::Sqr(xx[1]-cx.Get(k,1))+CMath::Sqr(xx[2]-cx.Get(k,2)))/CMath::Sqr(r[layer*n+centerstags[k]])));
y.Add(j,i,-yval);
}
//--- write Omega in out parameter W
for(j=0; j<n; j++)
w.Set(layer*n+j,i,omega[j]);
iterationscount+=lsqrrep.m_iterationscount;
nmv+=lsqrrep.m_nmv;
}
//--- Calculate maximum residual before adding new layer.
//--- This value is not used by algorithm, the only purpose is to make debugging easier.
rmaxafter=0.0;
for(j=0; j<n; j++)
{
for(i=0; i<ny; i++)
rmaxafter=MathMax(rmaxafter,MathAbs(y.Get(j,i)));
}
}
info=1;
}
//+------------------------------------------------------------------+
//| Buffer object which is used to perform nearest neighbor requests |
//| in the multithreaded mode (multiple threads working with same |
//| KD-tree object). |
//| This object should be created with KDTreeCreateBuffer(). |
//+------------------------------------------------------------------+
struct CRBFV2CalcBuffer
{
double m_curdist2;
CRowDouble m_curboxmax;
CRowDouble m_curboxmin;
CRowDouble m_x123;
CRowDouble m_x;
CRowDouble m_y123;
//--- constructor / destructor
CRBFV2CalcBuffer(void) { m_curdist2=0; }
~CRBFV2CalcBuffer(void) {}
//---
void Copy(const CRBFV2CalcBuffer&obj);
//--- overloading
void operator=(const CRBFV2CalcBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV2CalcBuffer::Copy(const CRBFV2CalcBuffer &obj)
{
m_curdist2=obj.m_curdist2;
m_curboxmax=obj.m_curboxmax;
m_curboxmin=obj.m_curboxmin;
m_x123=obj.m_x123;
m_x=obj.m_x;
m_y123=obj.m_y123;
}
//+------------------------------------------------------------------+
//| RBF model. |
//| Never try to work with fields of this object directly - always |
//| use ALGLIB functions to use this object. |
//+------------------------------------------------------------------+
struct CRBFV2Model
{
int m_basisfunction;
int m_bf;
int m_maxits;
int m_nh;
int m_nx;
int m_ny;
double m_lambdareg;
double m_supportr;
CRowInt m_kdnodes;
CRowInt m_kdroots;
CRowDouble m_cw;
CRowDouble m_kdboxmax;
CRowDouble m_kdboxmin;
CRowDouble m_kdsplits;
CRowDouble m_ri;
CRowDouble m_s;
CRBFV2CalcBuffer m_calcbuf;
CMatrixDouble m_v;
//--- constructor / destructor
CRBFV2Model(void);
~CRBFV2Model(void) {}
//---
void Copy(const CRBFV2Model&obj);
//--- overloading
void operator=(const CRBFV2Model&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBFV2Model::CRBFV2Model(void)
{
m_basisfunction=0;
m_bf=0;
m_maxits=0;
m_nh=0;
m_nx=0;
m_ny=0;
m_lambdareg=0;
m_supportr=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV2Model::Copy(const CRBFV2Model &obj)
{
m_basisfunction=obj.m_basisfunction;
m_bf=obj.m_bf;
m_maxits=obj.m_maxits;
m_nh=obj.m_nh;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_lambdareg=obj.m_lambdareg;
m_supportr=obj.m_supportr;
m_kdnodes=obj.m_kdnodes;
m_kdroots=obj.m_kdroots;
m_cw=obj.m_cw;
m_kdboxmax=obj.m_kdboxmax;
m_kdboxmin=obj.m_kdboxmin;
m_kdsplits=obj.m_kdsplits;
m_ri=obj.m_ri;
m_s=obj.m_s;
m_calcbuf=obj.m_calcbuf;
m_v=obj.m_v;
}
//+------------------------------------------------------------------+
//| Internal buffer for GridCalc3 |
//+------------------------------------------------------------------+
struct CRBFV2GridCalcBuffer
{
bool m_rf[];
CRowDouble m_cx;
CRowDouble m_rx;
CRowDouble m_ry;
CRowDouble m_tx;
CRowDouble m_ty;
CRBFV2CalcBuffer m_calcbuf;
//--- constructor / destructor
CRBFV2GridCalcBuffer(void) {}
~CRBFV2GridCalcBuffer(void) {}
//---
void Copy(const CRBFV2GridCalcBuffer&obj);
//--- overloading
void operator=(const CRBFV2GridCalcBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV2GridCalcBuffer::Copy(const CRBFV2GridCalcBuffer &obj)
{
ArrayCopy(m_rf,obj.m_rf);
m_cx=obj.m_cx;
m_rx=obj.m_rx;
m_ry=obj.m_ry;
m_tx=obj.m_tx;
m_ty=obj.m_ty;
m_calcbuf=obj.m_calcbuf;
}
//+------------------------------------------------------------------+
//| RBF solution report: |
//| * TerminationType - termination type, positive values-success,|
//| non-positive - failure. |
//+------------------------------------------------------------------+
struct CRBFV2Report
{
int m_terminationtype;
double m_maxerror;
double m_rmserror;
//--- constructor / destructor
CRBFV2Report(void) { ZeroMemory(this); }
~CRBFV2Report(void) {}
//---
void Copy(const CRBFV2Report&obj);
//--- overloading
void operator=(const CRBFV2Report&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV2Report::Copy(const CRBFV2Report &obj)
{
m_terminationtype=obj.m_terminationtype;
m_maxerror=obj.m_maxerror;
m_rmserror=obj.m_rmserror;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CRBFV2
{
public:
//--- constants
static const double m_defaultlambdareg;
static const double m_defaultsupportr;
static const int m_defaultmaxits;
static const int m_defaultbf;
static const int m_maxnodesize;
static const double m_complexitymultiplier;
//---
static void RBFV2Create(int nx,int ny,CRBFV2Model&s);
static void RBFV2CreateCalcBuffer(CRBFV2Model&s,CRBFV2CalcBuffer&buf);
static void RBFV2BuildHierarchical(CMatrixDouble&x,CMatrixDouble&y,int n,CRowDouble&scalevec,int aterm,int nh,double rbase,double lambdans,CRBFV2Model&s,int &progress10000,bool&terminationrequest,CRBFV2Report&rep);
static void RBFV2Alloc(CSerializer&s,CRBFV2Model&model);
static void RBFV2Serialize(CSerializer&s,CRBFV2Model&model);
static void RBFV2Unserialize(CSerializer&s,CRBFV2Model&model);
static double RBFV2FarRadius(int bf);
static double RBFV2NearRadius(int bf);
static double RBFV2BasisFunc(int bf,double d2);
static void RBFV2BasisFuncDiff2(int bf,double d2,double&f,double&df,double&d2f);
static double RBFV2Calc1(CRBFV2Model&s,double x0);
static double RBFV2Calc2(CRBFV2Model&s,double x0,double x1);
static double RBFV2Calc3(CRBFV2Model&s,double x0,double x1,double x2);
static void RBFV2CalcBuf(CRBFV2Model&s,CRowDouble&x,CRowDouble&y);
static void RBFV2TsCalcBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
static void RBFV2TsDiffBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFV2TSHessBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFV2GridCalc2(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
static void RBFV2GridCalcVX(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y);
static void RBFV2PartialGridCalcRec(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,CRowInt&blocks0,int block0a,int block0b,CRowInt&blocks1,int block1a,int block1b,CRowInt&blocks2,int block2a,int block2b,CRowInt&blocks3,int block3a,int block3b,bool &flagy[],bool sparsey,int levelidx,double avgfuncpernode,CRBFV2GridCalcBuffer &bufpool[],CRowDouble&y);
static void RBFV2Unpack(CRBFV2Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
private:
static bool RBFV2BuildLinearModel(CMatrixDouble&x,CMatrixDouble&y,int n,int nx,int ny,int modeltype,CMatrixDouble&v);
static void AllocateCalcBuffer(CRBFV2Model&s,CRBFV2CalcBuffer&buf);
static void ConvertAndAppendTree(CKDTree&curtree,int n,int nx,int ny,CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw);
static void ConvertTreeRec(CKDTree&curtree,int n,int nx,int ny,int nodeoffset,int nodesbase,int splitsbase,int cwbase,CRowInt&localnodes,int &localnodessize,CRowDouble&localsplits,int &localsplitssize,CRowDouble&localcw,int &localcwsize,CMatrixDouble&xybuf);
static void PartialCalcRec(CRBFV2Model&s,CRBFV2CalcBuffer&buf,int rootidx,double invr2,double queryr2,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y,int needdy);
static void PartialRowCalcRec(CRBFV2Model&s,CRBFV2CalcBuffer&buf,int rootidx,double invr2,double rquery2,double rfar2,CRowDouble&cx,CRowDouble&rx,bool &rf[],int rowsize,CRowDouble&ry);
static void PreparePartialQuery(CRowDouble&x,CRowDouble&kdboxmin,CRowDouble&kdboxmax,int nx,CRBFV2CalcBuffer&buf,int &cnt);
static void PartialQueryRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,int nx,int ny,CRBFV2CalcBuffer&buf,int rootidx,double queryr2,CRowDouble&x,CRowDouble&r2,CRowInt&offs,int &k);
static int PartialCountRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,int nx,int ny,CRBFV2CalcBuffer&buf,int rootidx,double queryr2,CRowDouble&x);
static void PartialUnpackRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&s,int nx,int ny,int rootidx,double r,CMatrixDouble&xwr,int &k);
static int DesignMatrixRowSize(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&ri,CRowInt&kdroots,CRowDouble&kdboxmin,CRowDouble&kdboxmax,int nx,int ny,int nh,int level,double rcoeff,CRowDouble&x0,CRBFV2CalcBuffer&calcbuf);
static void DesignMatrixGenerateRow(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&ri,CRowInt&kdroots,CRowDouble&kdboxmin,CRowDouble&kdboxmax,CRowInt&cwrange,int nx,int ny,int nh,int level,int bf,double rcoeff,int rowsperpoint,double penalty,CRowDouble&x0,CRBFV2CalcBuffer&calcbuf,CRowDouble&tmpr2,CRowInt&tmpoffs,CRowInt&rowidx,CRowDouble&rowval,int &rowsize);
static void ZeroFill(CRBFV2Model&s,int nx,int ny,int bf);
};
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
const double CRBFV2::m_defaultlambdareg=1.0E-6;
const double CRBFV2::m_defaultsupportr=0.10;
const int CRBFV2::m_defaultmaxits=400;
const int CRBFV2::m_defaultbf=1;
const int CRBFV2::m_maxnodesize=6;
const double CRBFV2::m_complexitymultiplier=100.0;
//+------------------------------------------------------------------+
//| This function creates RBF model for a scalar (NY=1) or vector |
//| (NY>1) function in a NX-dimensional space (NX=2 or NX=3). |
//| INPUT PARAMETERS: |
//| NX - dimension of the space, NX=2 or NX=3 |
//| NY - function dimension, NY>=1 |
//| OUTPUT PARAMETERS: |
//| S - RBF model (initially equals to zero) |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2Create(int nx,int ny,CRBFV2Model &s)
{
//--- check
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
return;
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
return;
//--- Serializable parameters
s.m_nx=nx;
s.m_ny=ny;
s.m_bf=0;
s.m_nh=0;
s.m_v=matrix<double>::Zeros(ny,nx+1);
//--- Non-serializable parameters
s.m_lambdareg=m_defaultlambdareg;
s.m_maxits=m_defaultmaxits;
s.m_supportr=m_defaultsupportr;
s.m_basisfunction=m_defaultbf;
}
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to |
//| perform parallel RBF model evaluations (with one RBF model |
//| instance being used from multiple threads, as long as different |
//| threads use different instances of buffer). |
//| This buffer object can be used with RBFTSCalcBuf() function (here|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
//| function which reuses previously allocated output space). |
//| How to use it: |
//| * create RBF model structure with RBFCreate() |
//| * load data, tune parameters |
//| * call RBFBuildModel() |
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
//| model (you should call this function only AFTER call to |
//| RBFBuildModel(), see below for more information) |
//| * call RBFTSCalcBuf() from different threads, with each thread |
//| working with its own copy of buffer object. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| Buf - external buffer. |
//| IMPORTANT: buffer object should be used only with RBF model |
//| object which was used to initialize buffer. Any |
//| attempt to use buffer with different object is |
//| dangerous - you may get memory violation error because|
//| sizes of internal arrays do not fit to dimensions of |
//| RBF structure. |
//| IMPORTANT: you should call this function only for model which was|
//| built with RBFBuildModel() function, after successful |
//| invocation of RBFBuildModel(). Sizes of some internal |
//| structures are determined only after model is built, |
//| so buffer object created before model construction |
//| stage will be useless (and any attempt to use it will |
//| result in exception). |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2CreateCalcBuffer(CRBFV2Model &s,
CRBFV2CalcBuffer &buf)
{
AllocateCalcBuffer(s,buf);
}
//+------------------------------------------------------------------+
//| This function builds hierarchical RBF model. |
//| INPUT PARAMETERS: |
//| X - array[N,S.NX], X-values |
//| Y - array[N,S.NY], Y-values |
//| ScaleVec - array[S.NX], vector of per-dimension scales |
//| N - points count |
//| ATerm - linear term type, 1 for linear, 2 for constant, |
//| 3 for zero. |
//| NH - hierarchy height |
//| RBase - base RBF radius |
//| BF - basis function type: 0 for Gaussian, 1 for compact |
//| LambdaNS - non-smoothness penalty coefficient. Exactly zero |
//| value means that no penalty is applied, and even |
//| system matrix does not contain penalty-related rows|
//| Value of 1 means |
//| S - RBF model, initialized by RBFCreate() call. |
//| progress10000 - variable used for progress reports, it is |
//| regularly set to the current progress multiplied by|
//| 10000, in order to get value in [0,10000] range. |
//| The rationale for such scaling is that it allows us|
//| to use integer type to store progress, which has |
//| less potential for non-atomic corruption on |
//| unprotected reads from another threads. You can |
//| read this variable from some other thread to get |
//| estimate of the current progress. Initial value of |
//| this variable is ignored, it is written by this |
//| function, but not read. |
//| terminationrequest - variable used for termination requests; its |
//| initial value must be False, and you can set it to |
//| True from some other thread. This routine regularly|
//| checks this variable and will terminate model |
//| construction shortly upon discovering that |
//| termination was requested. |
//| OUTPUT PARAMETERS: |
//| S - updated model (for rep.m_terminationtype>0, unchanged|
//| otherwise) |
//| Rep - report: |
//| * Rep.TerminationType: |
//| * -5 - non-distinct basis function centers were |
//| detected, interpolation aborted |
//| * -4 - nonconvergence of the internal SVD solver|
//| * 1 - successful termination |
//| * 8 terminated by user via RBFRequestTermination|
//| Fields are used for debugging purposes: |
//| * Rep.IterationsCount - iterations count of the |
//| LSQR solver |
//| * Rep.NMV - number of matrix-vector products |
//| * Rep.ARows - rows count for the system matrix |
//| * Rep.ACols - columns count for the system matrix |
//| * Rep.ANNZ - number of significantly non-zero |
//| elements (elements above some |
//| algorithm-determined threshold) |
//| NOTE: failure to build model will leave current State of the |
//| structure unchanged. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2BuildHierarchical(CMatrixDouble &x,
CMatrixDouble &y,
int n,
CRowDouble &scalevec,
int aterm,
int nh,
double rbase,
double lambdans,
CRBFV2Model &s,
int &progress10000,
bool &terminationrequest,
CRBFV2Report &rep)
{
//--- create variables
int nx=0;
int ny=0;
int bf=0;
CMatrixDouble rhs;
CMatrixDouble residualy;
CMatrixDouble v;
int rowsperpoint=0;
CRowInt hidx;
CRowDouble xr;
CRowDouble ri;
CRowInt kdroots;
CRowInt kdnodes;
CRowDouble kdsplits;
CRowDouble kdboxmin;
CRowDouble kdboxmax;
CRowDouble cw;
CRowInt cwrange;
CMatrixDouble curxy;
int curn=0;
int nbasis=0;
CKDTree curtree;
CKDTree globaltree;
CRowDouble x0;
CRowDouble x1;
CRowInt tags;
CRowDouble dist;
CRowInt nncnt;
CRowInt rowsizes;
CRowDouble diagata;
CRowDouble prec;
CRowDouble tmpx;
int i=0;
int j=0;
int k=0;
int k2=0;
int levelidx=0;
int offsi=0;
int offsj=0;
double val=0;
double criticalr=0;
int cnt=0;
double avgdiagata=0;
CRowDouble avgrowsize;
double sumrowsize=0;
double rprogress=0;
int maxits=0;
CLinLSQRState linstate;
CLinLSQRReport lsqrrep;
CSparseMatrix sparseacrs;
CRowDouble densew1;
CRowDouble denseb1;
CRBFV2CalcBuffer calcbuf;
CRowDouble vr2;
CRowInt voffs;
CRowInt rowindexes;
CRowDouble rowvals;
double penalty=0;
//--- check
if(!CAp::Assert(s.m_nx>0,__FUNCTION__+": incorrect NX"))
return;
if(!CAp::Assert(s.m_ny>0,__FUNCTION__+": incorrect NY"))
return;
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": incorrect LambdaNS"))
return;
for(j=0; j<s.m_nx; j++)
if(!CAp::Assert(scalevec[j]>0.0,__FUNCTION__+": incorrect ScaleVec"))
return;
nx=s.m_nx;
ny=s.m_ny;
bf=s.m_basisfunction;
//--- check
if(!CAp::Assert(bf==0 || bf==1,__FUNCTION__+": incorrect BF"))
return;
//--- Clean up communication and report fields
progress10000=0;
rep.m_maxerror=0;
rep.m_rmserror=0;
//--- Quick exit when we have no points
if(n==0)
{
ZeroFill(s,nx,ny,bf);
rep.m_terminationtype=1;
progress10000=10000;
return;
}
//--- First model in a sequence - linear model.
//--- Residuals from linear regression are stored in the ResidualY variable
//--- (used later to build RBF models).
residualy=y;
residualy.Resize(n,ny);
if(!RBFV2BuildLinearModel(x,residualy,n,nx,ny,aterm,v))
{
ZeroFill(s,nx,ny,bf);
rep.m_terminationtype=-5;
progress10000=10000;
return;
}
//--- Handle special case: multilayer model with NLayers=0.
//--- Quick exit.
if(nh==0)
{
rep.m_terminationtype=1;
ZeroFill(s,nx,ny,bf);
s.m_v=v;
rep.m_maxerror=0;
rep.m_rmserror=0;
for(i=0; i<n; i++)
for(j=0; j<ny; j++)
{
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(residualy.Get(i,j)));
rep.m_rmserror+=CMath::Sqr(residualy.Get(i,j));
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/(n*ny));
progress10000=10000;
return;
}
//--- Penalty coefficient is set to LambdaNS*RBase^2.
//--- We use such normalization because VALUES of radial basis
//--- functions have roughly unit magnitude, but their DERIVATIVES
//--- are (roughly) inversely proportional to the radius. Thus,
//--- without additional scaling, regularization coefficient
//--- looses invariancy w.m_r.t. scaling of variables.
if(lambdans==0.0)
rowsperpoint=1;
else
{
//--- NOTE: simplified penalty function is used, which does not provide rotation invariance
rowsperpoint=1+nx;
}
penalty=lambdans*CMath::Sqr(rbase);
//--- Prepare temporary structures
rhs=matrix<double>::Zeros(n*rowsperpoint,ny);
curxy.Resize(n,nx+ny);
x0.Resize(nx);
x1.Resize(nx);
tags.Resize(n);
dist.Resize(n);
vr2.Resize(n);
voffs.Resize(n);
nncnt.Resize(n);
rowsizes.Resize(n*rowsperpoint);
denseb1.Resize(n*rowsperpoint);
for(i=0; i<n; i++)
{
curxy.Row(i,x[i]/scalevec.ToVector());
rhs.Row(i*rowsperpoint,residualy[i]+0);
tags.Set(i,i);
}
CNearestNeighbor::KDTreeBuildTagged(curxy,tags,n,nx,0,2,globaltree);
//--- Generate sequence of layer radii.
//--- Prepare assignment of different levels to points.
if(!CAp::Assert(n>0,__FUNCTION__+": integrity check failed"))
return;
ri.Resize(nh);
for(levelidx=0; levelidx<nh; levelidx++)
ri.Set(levelidx,rbase*MathPow(2,-levelidx));
hidx.Resize(n);
hidx.Fill(nh);
xr=vector<double>::Full(n,CMath::m_maxrealnumber);
for(i=0; i<n; i++)
{
//--- check
if(!CAp::Assert(xr[i]>ri[0],__FUNCTION__+": integrity check failed"))
return;
}
for(levelidx=0; levelidx<nh; levelidx++)
{
//--- Scan dataset points, for each such point that distance to nearest
//--- "support" point is larger than SupportR*Ri[LevelIdx] we:
//--- * set distance of current point to 0 (it is support now) and update HIdx
//--- * perform R-NN request with radius SupportR*Ri[LevelIdx]
//--- * for each point in request update its distance
criticalr=s.m_supportr*ri[levelidx];
for(i=0; i<n; i++)
{
if(xr[i]>criticalr)
{
//--- Mark point as support
if(!CAp::Assert(hidx[i]==nh,__FUNCTION__+": integrity check failed"))
return;
hidx.Set(i,levelidx);
xr.Set(i,0);
//--- Update neighbors
x0=x[i]/scalevec.ToVector();
k=CNearestNeighbor::KDTreeQueryRNN(globaltree,x0,criticalr,true);
CNearestNeighbor::KDTreeQueryResultsTags(globaltree,tags);
CNearestNeighbor::KDTreeQueryResultsDistances(globaltree,dist);
for(j=0; j<k; j++)
xr.Set(tags[j],MathMin(xr[tags[j]],dist[j]));
}
}
}
//--- Build multitree (with zero weights) according to hierarchy.
//--- NOTE: this code assumes that during every iteration kdNodes,
//--- kdSplits and CW have size which EXACTLY fits their
//--- contents, and that these variables are resized at each
//--- iteration when we add new hierarchical model.
kdroots.Resize(nh+1);
kdnodes.Resize(0);
kdsplits.Resize(0);
kdboxmin.Resize(nx);
kdboxmax.Resize(nx);
cw.Resize(0);
cwrange.Resize(nh+1);
CNearestNeighbor::KDTreeExploreBox(globaltree,kdboxmin,kdboxmax);
cwrange.Set(0,0);
for(levelidx=0; levelidx<nh; levelidx++)
{
//--- Prepare radius and root offset
kdroots.Set(levelidx,CAp::Len(kdnodes));
//--- Generate LevelIdx-th tree and append to multi-tree
curn=0;
for(i=0; i<n; i++)
{
if(hidx[i]<=levelidx)
{
for(j=0; j<nx; j++)
curxy.Set(curn,j,x.Get(i,j)/scalevec[j]);
for(j=0; j<ny; j++)
curxy.Set(curn,nx+j,0);
curn++;
}
}
//--- check
if(!CAp::Assert(curn>0,__FUNCTION__+": integrity check failed"))
return;
CNearestNeighbor::KDTreeBuild(curxy,curn,nx,ny,2,curtree);
ConvertAndAppendTree(curtree,curn,nx,ny,kdnodes,kdsplits,cw);
//--- Fill entry of CWRange (we assume that length of CW exactly fits its actual size)
cwrange.Set(levelidx+1,CAp::Len(cw));
}
kdroots.Set(nh,CAp::Len(kdnodes));
//--- Prepare buffer and scaled dataset
AllocateCalcBuffer(s,calcbuf);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
curxy.Set(i,j,x.Get(i,j)/scalevec[j]);
}
//--- Calculate average row sizes for each layer; these values are used
//--- for smooth progress reporting (it adds some overhead, but in most
//--- cases - insignificant one).
CApServ::RVectorSetLengthAtLeast(avgrowsize,nh);
sumrowsize=0;
for(levelidx=0; levelidx<nh; levelidx++)
{
cnt=0;
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
x0.Set(j,curxy.Get(i,j));
cnt+=DesignMatrixRowSize(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,nx,ny,nh,levelidx,RBFV2NearRadius(bf),x0,calcbuf);
}
avgrowsize.Set(levelidx,CApServ::Coalesce(cnt,1)/CApServ::Coalesce(n,1));
sumrowsize+=avgrowsize[levelidx];
}
//--- Build unconstrained model with LSQR solver, applied layer by layer
for(levelidx=0; levelidx<nh; levelidx++)
{
//--- Generate A - matrix of basis functions (near radius is used)
//--- NOTE: AvgDiagATA is average value of diagonal element of A^T*A.
//--- It is used to calculate value of Tikhonov regularization
//--- coefficient.
nbasis=(cwrange[levelidx+1]-cwrange[levelidx])/(nx+ny);
//--- check
if(!CAp::Assert(cwrange[levelidx+1]-cwrange[levelidx]==nbasis*(nx+ny)))
return;
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
x0.Set(j,curxy.Get(i,j));
cnt=DesignMatrixRowSize(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,nx,ny,nh,levelidx,RBFV2NearRadius(bf),x0,calcbuf);
nncnt.Set(i,cnt);
for(j=0; j<rowsperpoint; j++)
rowsizes.Set(i*rowsperpoint+j,cnt);
}
CApServ::IVectorSetLengthAtLeast(rowindexes,nbasis);
CApServ::RVectorSetLengthAtLeast(rowvals,nbasis*rowsperpoint);
CApServ::RVectorSetLengthAtLeast(diagata,nbasis);
CSparse::SparseCreateCRSBuf(n*rowsperpoint,nbasis,rowsizes,sparseacrs);
avgdiagata=0.0;
diagata.Fill(0);
for(i=0; i<n; i++)
{
//--- Fill design matrix row, diagonal of A^T*A
for(j=0; j<nx; j++)
x0.Set(j,curxy.Get(i,j));
DesignMatrixGenerateRow(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,cwrange,nx,ny,nh,levelidx,bf,RBFV2NearRadius(bf),rowsperpoint,penalty,x0,calcbuf,vr2,voffs,rowindexes,rowvals,cnt);
//--- check
if(!CAp::Assert(cnt==nncnt[i],__FUNCTION__+": integrity check failed"))
return;
for(k=0; k<rowsperpoint; k++)
{
for(j=0; j<cnt; j++)
{
val=rowvals[j*rowsperpoint+k];
CSparse::SparseSet(sparseacrs,i*rowsperpoint+k,rowindexes[j],val);
avgdiagata+=CMath::Sqr(val);
diagata.Add(rowindexes[j],CMath::Sqr(val));
}
}
//--- Handle possible termination requests
if(terminationrequest)
{
//--- Request for termination was submitted, terminate immediately
ZeroFill(s,nx,ny,bf);
rep.m_terminationtype=8;
progress10000=10000;
return;
}
}
avgdiagata=avgdiagata/nbasis;
CApServ::RVectorSetLengthAtLeast(prec,nbasis);
for(j=0; j<nbasis; j++)
prec.Set(j,1/CApServ::Coalesce(MathSqrt(diagata[j]),1));
//--- solve
maxits=CApServ::CoalesceI(s.m_maxits,m_defaultmaxits);
CApServ::RVectorSetLengthAtLeast(tmpx,nbasis);
CLinLSQR::LinLSQRCreate(n*rowsperpoint,nbasis,linstate);
CLinLSQR::LinLSQRSetCond(linstate,0.0,0.0,maxits);
CLinLSQR::LinLSQRSetLambdaI(linstate,MathSqrt(s.m_lambdareg*avgdiagata));
for(j=0; j<ny; j++)
{
for(i=0; i<n*rowsperpoint; i++)
denseb1.Set(i,rhs.Get(i,j));
CLinLSQR::LinLSQRSetB(linstate,denseb1);
CLinLSQR::LinLSQRRestart(linstate);
CLinLSQR::LinLSQRSetXRep(linstate,true);
while(CLinLSQR::LinLSQRIteration(linstate))
{
if(terminationrequest)
{
//--- Request for termination was submitted, terminate immediately
ZeroFill(s,nx,ny,bf);
rep.m_terminationtype=8;
progress10000=10000;
return;
}
if(linstate.m_needmv)
{
for(i=0; i<nbasis; i++)
tmpx.Set(i,prec[i]*linstate.m_x[i]);
CSparse::SparseMV(sparseacrs,tmpx,linstate.m_mv);
continue;
}
if(linstate.m_needmtv)
{
CSparse::SparseMTV(sparseacrs,linstate.m_x,linstate.m_mtv);
for(i=0; i<nbasis; i++)
linstate.m_mtv.Mul(i,prec[i]);
continue;
}
if(linstate.m_xupdated)
{
rprogress=0;
for(i=0; i<levelidx; i++)
rprogress+=maxits*ny*avgrowsize[i];
rprogress+=(CLinLSQR::LinLSQRPeekIterationsCount(linstate)+j*maxits)*avgrowsize[levelidx];
rprogress/=(sumrowsize*maxits*ny);
rprogress*=10000;
rprogress=MathMax(rprogress,0);
rprogress=MathMin(rprogress,10000);
//--- check
if(!CAp::Assert(progress10000<=(int)MathRound(rprogress)+1,"HRBF: integrity check failed (progress indicator) even after +1 safeguard correction"))
return;
progress10000=(int)MathRound(rprogress);
continue;
}
CAp::Assert(false,"HRBF: unexpected request from LSQR solver");
return;
}
CLinLSQR::LinLSQRResults(linstate,densew1,lsqrrep);
//--- check
if(!CAp::Assert(lsqrrep.m_terminationtype>0,__FUNCTION__+": integrity check failed"))
return;
for(i=0; i<nbasis; i++)
densew1.Mul(i,prec[i]);
for(i=0; i<nbasis; i++)
{
offsi=cwrange[levelidx]+(nx+ny)*i;
cw.Set(offsi+nx+j,densew1[i]);
}
}
//--- Update residuals (far radius is used)
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
x0.Set(j,curxy.Get(i,j));
DesignMatrixGenerateRow(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,cwrange,nx,ny,nh,levelidx,bf,RBFV2FarRadius(bf),rowsperpoint,penalty,x0,calcbuf,vr2,voffs,rowindexes,rowvals,cnt);
for(j=0; j<cnt; j++)
{
offsj=cwrange[levelidx]+(nx+ny)*rowindexes[j]+nx;
for(k=0; k<rowsperpoint; k++)
{
val=rowvals[j*rowsperpoint+k];
for(k2=0; k2<ny; k2++)
rhs.Add(i*rowsperpoint+k,k2,-val*cw[offsj+k2]);
}
}
}
}
//--- Model is built.
//--- Copy local variables by swapping, global ones (ScaleVec) are copied
//--- explicitly.
s.m_bf=bf;
s.m_nh=nh;
CAp::Swap(s.m_ri,ri);
CAp::Swap(s.m_kdroots,kdroots);
CAp::Swap(s.m_kdnodes,kdnodes);
CAp::Swap(s.m_kdsplits,kdsplits);
CAp::Swap(s.m_kdboxmin,kdboxmin);
CAp::Swap(s.m_kdboxmax,kdboxmax);
CAp::Swap(s.m_cw,cw);
CAp::Swap(s.m_v,v);
s.m_s=scalevec;
s.m_s.Resize(nx);
rep.m_terminationtype=1;
//--- Calculate maximum and RMS errors
rep.m_maxerror=0;
rep.m_rmserror=0;
for(i=0; i<n; i++)
{
for(j=0; j<ny; j++)
{
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(rhs.Get(i*rowsperpoint,j)));
rep.m_rmserror+=CMath::Sqr(rhs.Get(i*rowsperpoint,j));
}
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/(n*ny));
//--- Update progress reports
progress10000=10000;
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2Alloc(CSerializer &s,CRBFV2Model &model)
{
//--- Data
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
CApServ::AllocRealArray(s,model.m_ri,-1);
CApServ::AllocRealArray(s,model.m_s,-1);
CApServ::AllocIntegerArray(s,model.m_kdroots,-1);
CApServ::AllocIntegerArray(s,model.m_kdnodes,-1);
CApServ::AllocRealArray(s,model.m_kdsplits,-1);
CApServ::AllocRealArray(s,model.m_kdboxmin,-1);
CApServ::AllocRealArray(s,model.m_kdboxmax,-1);
CApServ::AllocRealArray(s,model.m_cw,-1);
CApServ::AllocRealMatrix(s,model.m_v,-1,-1);
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2Serialize(CSerializer &s,CRBFV2Model &model)
{
//--- Data
s.Serialize_Int(model.m_nx);
s.Serialize_Int(model.m_ny);
s.Serialize_Int(model.m_nh);
s.Serialize_Int(model.m_bf);
CApServ::SerializeRealArray(s,model.m_ri,-1);
CApServ::SerializeRealArray(s,model.m_s,-1);
CApServ::SerializeIntegerArray(s,model.m_kdroots,-1);
CApServ::SerializeIntegerArray(s,model.m_kdnodes,-1);
CApServ::SerializeRealArray(s,model.m_kdsplits,-1);
CApServ::SerializeRealArray(s,model.m_kdboxmin,-1);
CApServ::SerializeRealArray(s,model.m_kdboxmax,-1);
CApServ::SerializeRealArray(s,model.m_cw,-1);
CApServ::SerializeRealMatrix(s,model.m_v,-1,-1);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2Unserialize(CSerializer &s,CRBFV2Model &model)
{
//--- Unserialize primary model parameters, initialize model.
//--- It is necessary to call RBFCreate() because some internal fields
//--- which are NOT unserialized will need initialization.
int nx=s.Unserialize_Int();
int ny=s.Unserialize_Int();
RBFV2Create(nx,ny,model);
model.m_nh=s.Unserialize_Int();
model.m_bf=s.Unserialize_Int();
CApServ::UnserializeRealArray(s,model.m_ri);
CApServ::UnserializeRealArray(s,model.m_s);
CApServ::UnserializeIntegerArray(s,model.m_kdroots);
CApServ::UnserializeIntegerArray(s,model.m_kdnodes);
CApServ::UnserializeRealArray(s,model.m_kdsplits);
CApServ::UnserializeRealArray(s,model.m_kdboxmin);
CApServ::UnserializeRealArray(s,model.m_kdboxmax);
CApServ::UnserializeRealArray(s,model.m_cw);
CApServ::UnserializeRealMatrix(s,model.m_v);
}
//+------------------------------------------------------------------+
//| Returns far radius for basis function type |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2FarRadius(int bf)
{
double result=0;
switch(bf)
{
case 0:
result=5.0;
break;
case 1:
result=3;
break;
default:
result=1;
break;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Returns near radius for basis function type |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2NearRadius(int bf)
{
double result=0;
switch(bf)
{
case 0:
result=3.0;
break;
case 1:
result=3;
break;
default:
result=1;
break;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Returns basis function value. |
//| Assumes that D2>=0 |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2BasisFunc(int bf,double d2)
{
//--- create variables
double result=0;
double v=0;
switch(bf)
{
case 0:
result=MathExp(-d2);
break;
case 1:
//--- if D2<3:
//--- Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
//--- else:
//--- 0
v=1-d2/9;
if(v<=0.0)
{
result=0;
break;
}
result=2.718281828459045*MathExp(-d2)*MathExp(-(1/v));
break;
default:
CAp::Assert(false,"RBFV2BasisFunc: unknown BF type");
break;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Returns basis function value, first and second derivatives |
//| Assumes that D2>=0 |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2BasisFuncDiff2(int bf,double d2,double &f,
double &df,double &d2f)
{
double v=0;
f=0;
df=0;
d2f=0;
switch(bf)
{
case 0:
f=MathExp(-d2);
df=-f;
d2f=f;
break;
case 1:
//--- if D2<3:
//--- F = Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
//--- dF = -F * [pow(D2/9-1,-2)/9 + 1]
//--- d2F = -dF * [pow(D2/9-1,-2)/9 + 1] - F*(2/81)*pow(D2/9-1,-3)
//--- else:
//--- 0
v=1-d2/9;
if(v<=0.0)
{
f=0;
df=0;
d2f=0;
break;
}
f=MathExp(1)*MathExp(-d2)*MathExp(-(1/v));
df=-(f*(1/(9*v*v)+1));
d2f=-(df*(1/(9*v*v)+1))-f*((double)2/(double)81)/(v*v*v);
break;
default:
CAp::Assert(false,"RBFV2BasisFuncDiff2: unknown BF type");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY=1 (scalar function) |
//| and NX=1 (1-dimensional space). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>1 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - X-coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2Calc1(CRBFV2Model &s,double x0)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return (0);
if(s.m_ny!=1 || s.m_nx!=1)
return(0);
result=s.m_v.Get(0,0)*x0-s.m_v.Get(0,1);
if(s.m_nh==0)
return(result);
AllocateCalcBuffer(s,s.m_calcbuf);
s.m_calcbuf.m_x123.Set(0,x0);
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY=1 (scalar function) |
//| and NX=2 (2-dimensional space). If you have 3-dimensional space, |
//| use RBFCalc3(). If you have general situation (NX-dimensional |
//| space, NY-dimensional function) you should use general, less |
//| efficient implementation RBFCalc(). |
//| If you want to calculate function values many times, consider |
//| using RBFGridCalc2(), which is far more efficient than many |
//| subsequent calls to RBFCalc2(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2Calc2(CRBFV2Model &s,double x0,double x1)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=2)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2);
if(s.m_nh==0)
return(result);
AllocateCalcBuffer(s,s.m_calcbuf);
s.m_calcbuf.m_x123.Set(0,x0);
s.m_calcbuf.m_x123.Set(1,x1);
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY=1 (scalar function) |
//| and NX=3 (3-dimensional space). If you have 2-dimensional space, |
//| use RBFCalc2(). If you have general situation (NX-dimensional |
//| space, NY-dimensional function) you should use general, less |
//| efficient implementation RBFCalc(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| X2 - third coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV2::RBFV2Calc3(CRBFV2Model &s,double x0,double x1,
double x2)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=3)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,3);
if(s.m_nh==0)
return(result);
AllocateCalcBuffer(s,s.m_calcbuf);
s.m_calcbuf.m_x123.Set(0,x0);
s.m_calcbuf.m_x123.Set(1,x1);
s.m_calcbuf.m_x123.Set(2,x2);
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point. |
//| Same as RBFCalc(), but does not reallocate Y when in is large |
//| enough to store function values. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2CalcBuf(CRBFV2Model &s,CRowDouble &x,CRowDouble &y)
{
RBFV2TsCalcBuf(s,s.m_calcbuf,x,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point, using external buffer object (internal temporaries of RBF |
//| model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2TsCalcBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
CRowDouble &x,CRowDouble &y)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
double rcur=0;
double rquery2=0;
double invrc2=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
//--- Handle linear term
if(CAp::Len(y)<ny)
y.Resize(ny);
for(int i=0; i<ny; i++)
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
if(s.m_nh==0)
return;
//--- Handle nonlinear term
AllocateCalcBuffer(s,buf);
for(int j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
for(int levelidx=0; levelidx<s.m_nh; levelidx++)
{
//--- Prepare fields of Buf required by PartialCalcRec()
buf.m_curdist2=0;
for(int j=0; j<nx; j++)
{
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
if(buf.m_x[j]<buf.m_curboxmin[j])
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
else
{
if(buf.m_x[j]>buf.m_curboxmax[j])
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
}
}
//--- Call PartialCalcRec()
rcur=s.m_ri[levelidx];
invrc2=1/(rcur*rcur);
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,y,y,0);
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its derivatives, using external buffer object (internal|
//| temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y, DY - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2TsDiffBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
double rcur=0;
double rquery2=0;
double invrc2=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
//--- Handle linear term
for(int i=0; i<ny; i++)
{
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,s.m_v.Get(i,j));
}
if(s.m_nh==0)
return;
//--- Handle nonlinear term
AllocateCalcBuffer(s,buf);
for(int j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
for(int i=0; i<ny; i++)
{
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
}
for(int levelidx=0; levelidx<s.m_nh; levelidx++)
{
//--- Prepare fields of Buf required by PartialCalcRec()
buf.m_curdist2=0;
for(int j=0; j<nx; j++)
{
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
if(buf.m_x[j]<buf.m_curboxmin[j])
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
else
{
if(buf.m_x[j]>buf.m_curboxmax[j])
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
}
}
//--- Call PartialCalcRec()
rcur=s.m_ri[levelidx];
invrc2=1/(rcur*rcur);
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,dy,dy,1);
}
for(int i=0; i<ny; i++)
{
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its first and second derivatives, using external buffer|
//| object (internal temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. |
//| X may have more than NX elements, in this case only|
//| leading NX will be used. |
//| Y,DY,D2Y - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//| D2Y - second derivatives, array[NY*NX*NX] |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2TSHessBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy,
CRowDouble &d2y)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
double rcur=0;
double rquery2=0;
double invrc2=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
if(CAp::Len(d2y)<ny*nx*nx)
d2y.Resize(ny*nx*nx);
//--- Handle linear term
for(int i=0; i<ny; i++)
{
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,s.m_v.Get(i,j));
}
d2y.Fill(0);
if(s.m_nh==0)
return;
//--- Handle nonlinear term
AllocateCalcBuffer(s,buf);
for(int j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
for(int i=0; i<ny; i++)
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
for(int levelidx=0; levelidx<=s.m_nh-1; levelidx++)
{
//--- Prepare fields of Buf required by PartialCalcRec()
buf.m_curdist2=0;
for(int j=0; j<nx; j++)
{
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
if(buf.m_x[j]<buf.m_curboxmin[j])
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
else
{
if(buf.m_x[j]>buf.m_curboxmax[j])
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
}
}
//--- Call PartialCalcRec()
rcur=s.m_ri[levelidx];
invrc2=1/(rcur*rcur);
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,dy,d2y,2);
}
for(int i=0; i<ny; i++)
for(int j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
for(int i=0; i<ny; i++)
for(int j=0; j<nx; j++)
for(int k=0; k<nx; k++)
d2y.Set(i*nx*nx+j*nx+k,d2y[i*nx*nx+j*nx+k]/(s.m_s[j]*s.m_s[k]));
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the regular |
//| grid. |
//| Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J]) |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - array of grid nodes, first coordinates, array[N0] |
//| N0 - grid size (number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| N1 - grid size (number of nodes) in the second dimension|
//| OUTPUT PARAMETERS: |
//| Y - function values, array[N0,N1]. Y is out-variable |
//| and is reallocated by this function. |
//| NOTE: as a special exception, this function supports unordered |
//| arrays X0 and X1. However, future versions may be more |
//| efficient for X0/X1 ordered by ascending. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2GridCalc2(CRBFV2Model &s,CRowDouble &x0,
int n0,CRowDouble &x1,int n1,
CMatrixDouble &y)
{
//--- create variables
CRowDouble cpx0;
CRowDouble cpx1;
CRowDouble dummyx2;
CRowDouble dummyx3;
bool dummyflag[];
CRowInt p01;
CRowInt p11;
CRowInt p2;
CRowDouble vy;
y.Resize(0,0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
y=matrix<double>::Zeros(n0,n1);
if(s.m_ny!=1 || s.m_nx!=2)
return;
//create and sort arrays
cpx0=x0;
cpx0.Resize(n0);
CTSort::TagSort(cpx0,n0,p01,p2);
cpx1=x1;
cpx1.Resize(n1);
CTSort::TagSort(cpx1,n1,p11,p2);
dummyx2=vector<double>::Zeros(1);
dummyx3=vector<double>::Zeros(1);
vy.Resize(n0*n1);
RBFV2GridCalcVX(s,cpx0,n0,cpx1,n1,dummyx2,1,dummyx3,1,dummyflag,false,vy);
for(int i=0; i<n0; i++)
for(int j=0; j<n1; j++)
y.Set(i,j,vy[i+j*n0]);
}
//+------------------------------------------------------------------+
//| This function is used to perform gridded calculation for 2D, 3D |
//| or 4D problems. It accepts parameters X0...X3 and counters |
//| N0...N3. If RBF model has dimensionality less than 4, |
//| corresponding arrays should contain just one element equal to |
//| zero, and corresponding N's should be equal to 1. |
//| NOTE: array Y should be preallocated by caller. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2GridCalcVX(CRBFV2Model &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,
CRowDouble &x2,int n2,
CRowDouble &x3,int n3,
bool &flagy[],bool sparsey,
CRowDouble &y)
{
//--- create variables
int nx=0;
int ny=0;
int i=0;
int j=0;
int k=0;
CRowDouble tx;
CRowDouble ty;
CRowDouble z;
int dstoffs=0;
int dummy=0;
CRBFV2GridCalcBuffer bufseedv2;
CRBFV2GridCalcBuffer bufpool[];
int rowidx=0;
int rowcnt=0;
double v=0;
double rcur=0;
int levelidx=0;
double searchradius2=0;
int ntrials=0;
double avgfuncpernode=0;
CHighQualityRandState rs;
CRowInt blocks0;
CRowInt blocks1;
CRowInt blocks2;
CRowInt blocks3;
int blockscnt0=0;
int blockscnt1=0;
int blockscnt2=0;
int blockscnt3=0;
double blockwidth0=0;
double blockwidth1=0;
double blockwidth2=0;
double blockwidth3=0;
int maxblocksize=0;
nx=s.m_nx;
ny=s.m_ny;
CHighQualityRand::HQRndSeed(532,54734,rs);
//--- Perform integrity checks
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=4 || (CAp::Len(x3)>=1 && x3[0]==0.0 && n3==1),__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=3 || (CAp::Len(x2)>=1 && x2[0]==0.0 && n2==1),__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=2 || (CAp::Len(x1)>=1 && x1[0]==0.0 && n1==1),__FUNCTION__+": integrity check failed"))
return;
//--- Allocate arrays
if(!CAp::Assert(s.m_nx<=4,__FUNCTION__+": integrity check failed"))
return;
z.Resize(ny);
tx.Resize(4);
ty.Resize(ny);
//--- Calculate linear term
rowcnt=n1*n2*n3;
for(rowidx=0; rowidx<rowcnt; rowidx++)
{
//--- Calculate TX - current position
k=rowidx;
tx.Set(0,0);
tx.Set(1,x1[k%n1]);
k=k/n1;
tx.Set(2,x2[k%n2]);
k=k/n2;
tx.Set(3,x3[k%n3]);
k=k/n3;
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
return;
for(j=0; j<ny; j++)
{
v=s.m_v.Get(j,nx);
for(k=1; k<nx; k++)
v=v+tx[k]*s.m_v.Get(j,k);
z.Set(j,v);
}
for(i=0; i<n0; i++)
{
dstoffs=ny*(rowidx*n0+i);
if(sparsey && !flagy[rowidx*n0+i])
{
for(j=0; j<ny; j++)
y.Set(j+dstoffs,0);
continue;
}
v=x0[i];
for(j=0; j<ny; j++)
y.Set(j+dstoffs,z[j]+v*s.m_v.Get(j,0));
}
}
if(s.m_nh==0)
return;
//--- Process RBF terms, layer by layer
ArrayResize(bufpool,s.m_nh);
for(levelidx=0; levelidx<s.m_nh; levelidx++)
{
rcur=s.m_ri[levelidx];
blockwidth0=1;
blockwidth1=1;
blockwidth2=1;
blockwidth3=1;
if(nx>=1)
blockwidth0=rcur*s.m_s[0];
if(nx>=2)
blockwidth1=rcur*s.m_s[1];
if(nx>=3)
blockwidth2=rcur*s.m_s[2];
if(nx>=4)
blockwidth3=rcur*s.m_s[3];
maxblocksize=8;
//--- Group grid nodes into blocks according to current radius
blocks0.Resize(n0+1);
blockscnt0=0;
blocks0.Set(0,0);
for(i=1; i<n0; i++)
{
if((x0[i]-x0[blocks0[blockscnt0]])>blockwidth0 || i-blocks0[blockscnt0]>=maxblocksize)
{
blockscnt0++;
blocks0.Set(blockscnt0,i);
}
}
blockscnt0++;
blocks0.Set(blockscnt0,n0);
blocks1.Resize(n1+1);
blockscnt1=0;
blocks1.Set(0,0);
for(i=1; i<n1; i++)
{
if((x1[i]-x1[blocks1[blockscnt1]])>blockwidth1 || i-blocks1[blockscnt1]>=maxblocksize)
{
blockscnt1++;
blocks1.Set(blockscnt1,i);
}
}
blockscnt1++;
blocks1.Set(blockscnt1,n1);
blocks2.Resize(n2+1);
blockscnt2=0;
blocks2.Set(0,0);
for(i=1; i<n2; i++)
{
if((x2[i]-x2[blocks2[blockscnt2]])>blockwidth2 || i-blocks2[blockscnt2]>=maxblocksize)
{
blockscnt2++;
blocks2.Set(blockscnt2,i);
}
}
blockscnt2++;
blocks2.Set(blockscnt2,n2);
blocks3.Resize(n3+1);
blockscnt3=0;
blocks3.Set(0,0);
for(i=1; i<n3; i++)
{
if((x3[i]-x3[blocks3[blockscnt3]])>blockwidth3 || i-blocks3[blockscnt3]>=maxblocksize)
{
blockscnt3++;
blocks3.Set(blockscnt3,i);
}
}
blockscnt3++;
blocks3.Set(blockscnt3,n3);
//--- Prepare seed for shared pool
AllocateCalcBuffer(s,bufseedv2.m_calcbuf);
bufpool[levelidx]=bufseedv2;
//--- Determine average number of neighbor per node
searchradius2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
ntrials=100;
avgfuncpernode=0.0;
for(i=0; i<=ntrials-1; i++)
{
tx.Set(0,x0[CHighQualityRand::HQRndUniformI(rs,n0)]);
tx.Set(1,x1[CHighQualityRand::HQRndUniformI(rs,n1)]);
tx.Set(2,x2[CHighQualityRand::HQRndUniformI(rs,n2)]);
tx.Set(3,x3[CHighQualityRand::HQRndUniformI(rs,n3)]);
PreparePartialQuery(tx,s.m_kdboxmin,s.m_kdboxmax,nx,bufseedv2.m_calcbuf,dummy);
avgfuncpernode+=(double)PartialCountRec(s.m_kdnodes,s.m_kdsplits,s.m_cw,nx,ny,bufseedv2.m_calcbuf,s.m_kdroots[levelidx],searchradius2,tx)/(double)ntrials;
}
//--- Perform calculation in multithreaded mode
RBFV2PartialGridCalcRec(s,x0,n0,x1,n1,x2,n2,x3,n3,blocks0,0,blockscnt0,blocks1,0,blockscnt1,blocks2,0,blockscnt2,blocks3,0,blockscnt3,flagy,sparsey,levelidx,avgfuncpernode,bufpool,y);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2PartialGridCalcRec(CRBFV2Model &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,
CRowDouble &x2,int n2,
CRowDouble &x3,int n3,
CRowInt &blocks0,int block0a,int block0b,
CRowInt &blocks1,int block1a,int block1b,
CRowInt &blocks2,int block2a,int block2b,
CRowInt &blocks3,int block3a,int block3b,
bool &flagy[],bool sparsey,int levelidx,
double avgfuncpernode,CRBFV2GridCalcBuffer &bufpool[],
CRowDouble &y)
{
//--- create variables
int nx=0;
int ny=0;
int k=0;
int l=0;
int blkidx=0;
int blkcnt=0;
int nodeidx=0;
int nodescnt=0;
int rowidx=0;
int rowscnt=0;
int i0=0;
int i1=0;
int i2=0;
int i3=0;
int j0=0;
int j1=0;
int j2=0;
int j3=0;
double rcur=0;
double invrc2=0;
double rquery2=0;
double rfar2=0;
int dstoffs=0;
int srcoffs=0;
int dummy=0;
double rowwidth=0;
double maxrowwidth=0;
double problemcost=0;
int maxbs=0;
int midpoint=0;
bool emptyrow=false;
CRBFV2GridCalcBuffer buf;
nx=s.m_nx;
ny=s.m_ny;
//--- Integrity checks
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
return;
//--- Retrieve buffer object from pool (it will be returned later)
buf=bufpool[levelidx];
//--- Calculate RBF model
if(!CAp::Assert(nx<=4,__FUNCTION__+": integrity check failed"))
return;
buf.m_tx.Resize(4);
buf.m_cx.Resize(4);
buf.m_ty.Resize(ny);
rcur=s.m_ri[levelidx];
invrc2=1/(rcur*rcur);
blkcnt=(block3b-block3a)*(block2b-block2a)*(block1b-block1a)*(block0b-block0a);
for(blkidx=0; blkidx<blkcnt; blkidx++)
{
//--- Select block (I0,I1,I2,I3).
//--- NOTE: for problems with NX<4 corresponding I_? are zero.
k=blkidx;
i0=block0a+k%(block0b-block0a);
k=k/(block0b-block0a);
i1=block1a+k%(block1b-block1a);
k=k/(block1b-block1a);
i2=block2a+k%(block2b-block2a);
k=k/(block2b-block2a);
i3=block3a+k%(block3b-block3a);
k=k/(block3b-block3a);
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
return;
//--- We partitioned grid into blocks and selected block with
//--- index (I0,I1,I2,I3). This block is a 4D cube (some dimensions
//--- may be zero) of nodes with indexes (J0,J1,J2,J3), which is
//--- further partitioned into a set of rows, each row corresponding
//--- to indexes J1...J3 being fixed.
//--- We process block row by row, and each row may be handled
//--- by either "generic" (nodes are processed separately) or
//--- batch algorithm (that's the reason to use rows, after all).
//--- Process nodes of the block
rowscnt=(blocks3[i3+1]-blocks3[i3])*(blocks2[i2+1]-blocks2[i2])*(blocks1[i1+1]-blocks1[i1]);
for(rowidx=0; rowidx<rowscnt; rowidx++)
{
//--- Find out node indexes (*,J1,J2,J3).
//--- NOTE: for problems with NX<4 corresponding J_? are zero.
k=rowidx;
j1=blocks1[i1]+k%(blocks1[i1+1]-blocks1[i1]);
k=k/(blocks1[i1+1]-blocks1[i1]);
j2=blocks2[i2]+k%(blocks2[i2+1]-blocks2[i2]);
k=k/(blocks2[i2+1]-blocks2[i2]);
j3=blocks3[i3]+k%(blocks3[i3+1]-blocks3[i3]);
k=k/(blocks3[i3+1]-blocks3[i3]);
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
return;
//--- Analyze row, skip completely empty rows
nodescnt=blocks0[i0+1]-blocks0[i0];
srcoffs=blocks0[i0]+(j1+(j2+j3*n2)*n1)*n0;
emptyrow=true;
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
emptyrow=emptyrow && (sparsey && !flagy[srcoffs+nodeidx]);
if(emptyrow)
continue;
//--- Process row-use either "batch" (rowsize>1) or "generic"
//--- (row size is 1) algorithm.
//--- NOTE: "generic" version may also be used as fallback code for
//--- situations when we do not want to use batch code.
maxrowwidth=0.5*RBFV2NearRadius(s.m_bf)*rcur*s.m_s[0];
rowwidth=x0[blocks0[i0+1]-1]-x0[blocks0[i0]];
if(nodescnt>1 && rowwidth<=maxrowwidth)
{
//--- "Batch" code which processes entire row at once, saving
//--- some time in kd-tree search code.
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf)+0.5*rowwidth/s.m_s[0]);
rfar2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
j0=blocks0[i0];
if(nx>0)
buf.m_cx.Set(0,(x0[j0]+0.5*rowwidth)/s.m_s[0]);
if(nx>1)
buf.m_cx.Set(1,x1[j1]/s.m_s[1]);
if(nx>2)
buf.m_cx.Set(2,x2[j2]/s.m_s[2]);
if(nx>3)
buf.m_cx.Set(3,x3[j3]/s.m_s[3]);
srcoffs=j0+(j1+(j2+j3*n2)*n1)*n0;
dstoffs=ny*srcoffs;
CApServ::RVectorSetLengthAtLeast(buf.m_rx,nodescnt);
CApServ::BVectorSetLengthAtLeast(buf.m_rf,nodescnt);
CApServ::RVectorSetLengthAtLeast(buf.m_ry,nodescnt*ny);
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
{
buf.m_rx.Set(nodeidx,x0[j0+nodeidx]/s.m_s[0]);
buf.m_rf[nodeidx]=!sparsey || flagy[srcoffs+nodeidx];
}
for(k=0; k<nodescnt*ny; k++)
buf.m_ry.Set(k,0);
PreparePartialQuery(buf.m_cx,s.m_kdboxmin,s.m_kdboxmax,nx,buf.m_calcbuf,dummy);
PartialRowCalcRec(s,buf.m_calcbuf,s.m_kdroots[levelidx],invrc2,rquery2,rfar2,buf.m_cx,buf.m_rx,buf.m_rf,nodescnt,buf.m_ry);
for(k=0; k<nodescnt*ny; k++)
y.Add(dstoffs+k,buf.m_ry[k]);
}
else
{
//--- "Generic" code. Although we usually move here
//--- only when NodesCnt=1, we still use a loop on
//--- NodeIdx just to be able to use this branch as
//--- fallback code without any modifications.
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
{
//--- Prepare TX - current point
j0=blocks0[i0]+nodeidx;
if(nx>0)
buf.m_tx.Set(0,x0[j0]/s.m_s[0]);
if(nx>1)
buf.m_tx.Set(1,x1[j1]/s.m_s[1]);
if(nx>2)
buf.m_tx.Set(2,x2[j2]/s.m_s[2]);
if(nx>3)
buf.m_tx.Set(3,x3[j3]/s.m_s[3]);
//--- Evaluate and add to Y
srcoffs=j0+(j1+(j2+j3*n2)*n1)*n0;
dstoffs=ny*srcoffs;
for(l=0; l<ny; l++)
buf.m_ty.Set(l,0);
if(!sparsey || flagy[srcoffs])
{
PreparePartialQuery(buf.m_tx,s.m_kdboxmin,s.m_kdboxmax,nx,buf.m_calcbuf,dummy);
PartialCalcRec(s,buf.m_calcbuf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_tx,buf.m_ty,buf.m_ty,buf.m_ty,0);
}
for(l=0; l<ny; l++)
y.Add(dstoffs+l,buf.m_ty[l]);
}
}
}
}
//--- Recycle buffer object back to pool
bufpool[levelidx]=buf;
}
//+------------------------------------------------------------------+
//| This function "unpacks" RBF model by extracting its coefficients.|
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| NX - dimensionality of argument |
//| NY - dimensionality of the target function |
//| XWR - model information, array[NC,NX+NY+1]. |
//| One row of the array corresponds to one basis |
//| function: |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of |
//| the function being modelled |
//| * last NX columns - radii, per dimension |
//| NC - number of the centers |
//| V - polynomial term , array[NY,NX+1]. One row per one |
//| dimension of the function being modelled. First NX |
//| elements are linear coefficients, V[NX] is equal to|
//| the constant part. |
//+------------------------------------------------------------------+
void CRBFV2::RBFV2Unpack(CRBFV2Model &s,int &nx,int &ny,CMatrixDouble &xwr,
int &nc,CMatrixDouble &v)
{
int ncactual=0;
nx=0;
ny=0;
xwr.Resize(0,0);
nc=0;
v.Resize(0,0);
nx=s.m_nx;
ny=s.m_ny;
nc=0;
//--- Fill V
v=s.m_v;
v.Resize(s.m_ny,s.m_nx+1);
//--- Fill XWR
if(!CAp::Assert(CAp::Len(s.m_cw)%(s.m_nx+s.m_ny)==0,__FUNCTION__+": integrity error"))
return;
nc=CAp::Len(s.m_cw)/(s.m_nx+s.m_ny);
ncactual=0;
if(nc>0)
{
xwr.Resize(nc,s.m_nx+s.m_ny+s.m_nx);
for(int i=0; i<s.m_nh; i++)
PartialUnpackRec(s.m_kdnodes,s.m_kdsplits,s.m_cw,s.m_s,s.m_nx,s.m_ny,s.m_kdroots[i],s.m_ri[i],xwr,ncactual);
}
CAp::Assert(nc==ncactual,__FUNCTION__+": integrity error");
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CRBFV2::RBFV2BuildLinearModel(CMatrixDouble &x,CMatrixDouble &y,
int n,int nx,int ny,int modeltype,
CMatrixDouble &v)
{
//--- create variables
bool result=false;
CRowDouble tmpy;
CMatrixDouble a;
double scaling=0;
CRowDouble shifting;
double mn=0;
double mx=0;
CRowDouble c;
CLSFitReport rep;
int i=0;
int j=0;
int k=0;
int info=0;
v.Resize(0,0);
//--- check
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
return(false);
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
return(false);
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
return(false);
//--- Handle degenerate case (N=0)
result=true;
v=matrix<double>::Zeros(ny,nx+1);
if(n==0)
return(result);
//--- Allocate temporaries
tmpy.Resize(n);
//--- General linear model.
switch(modeltype)
{
case 1:
//--- Calculate scaling/shifting, transform variables, prepare LLS problem
a.Resize(n,nx+1);
shifting.Resize(nx);
scaling=0;
for(i=0; i<nx; i++)
{
mn=x.Get(0,i);
mx=mn;
for(j=1; j<n; j++)
{
if(mn>x.Get(j,i))
mn=x.Get(j,i);
if(mx<x.Get(j,i))
mx=x.Get(j,i);
}
scaling=MathMax(scaling,mx-mn);
shifting.Set(i,0.5*(mx+mn));
}
if(scaling==0.0)
scaling=1;
else
scaling=0.5*scaling;
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
a.Set(i,j,(x.Get(i,j)-shifting[j])/scaling);
}
for(i=0; i<n; i++)
a.Set(i,nx,1);
//--- Solve linear system in transformed variables, make backward
for(i=0; i<ny; i++)
{
for(j=0; j<n; j++)
tmpy.Set(j,y.Get(j,i));
CLSFit::LSFitLinear(tmpy,a,n,nx+1,info,c,rep);
if(info<=0)
return(false);
for(j=0; j<nx; j++)
v.Set(i,j,c[j]/scaling);
v.Set(i,nx,c[nx]);
v.Add(i,nx,- CAblasF::RDotVR(nx,shifting,v,i));
for(j=0; j<n; j++)
y.Add(j,i,- CAblasF::RDotRR(nx,x,j,v,i)-v.Get(i,nx));
}
break;
//--- Constant model, very simple
case 2:
for(i=0; i<ny; i++)
{
for(j=0; j<n; j++)
v.Add(i,nx,y.Get(j,i));
if(n>0)
v.Mul(i,nx,1.0/(double)n);
for(j=0; j<n; j++)
y.Add(j,i,- v.Get(i,nx));
}
break;
//--- Zero model
case 3:
break;
//---
default:
CAp::Assert(false,__FUNCTION__+": unknown model type");
result=false;
break;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Reallocates calcBuf if necessary, reuses previously allocated |
//| space if possible. |
//+------------------------------------------------------------------+
void CRBFV2::AllocateCalcBuffer(CRBFV2Model &s,CRBFV2CalcBuffer &buf)
{
if(CAp::Len(buf.m_x)<s.m_nx)
buf.m_x.Resize(s.m_nx);
if(CAp::Len(buf.m_curboxmin)<s.m_nx)
buf.m_curboxmin.Resize(s.m_nx);
if(CAp::Len(buf.m_curboxmax)<s.m_nx)
buf.m_curboxmax.Resize(s.m_nx);
if(CAp::Len(buf.m_x123)<s.m_nx)
buf.m_x123.Resize(s.m_nx);
if(CAp::Len(buf.m_y123)<s.m_ny)
buf.m_y123.Resize(s.m_ny);
}
//+------------------------------------------------------------------+
//| Extracts structure (and XY - values too) from kd-tree built for |
//| a small subset of points and appends it to multi-tree. |
//+------------------------------------------------------------------+
void CRBFV2::ConvertAndAppendTree(CKDTree &curtree,int n,int nx,int ny,
CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw)
{
//--- create variables
int nodesbase=0;
int splitsbase=0;
int cwbase=0;
CRowInt localnodes;
CRowDouble localsplits;
CRowDouble localcw;
CMatrixDouble xybuf;
int localnodessize=0;
int localsplitssize=0;
int localcwsize=0;
//--- Calculate base offsets
nodesbase=CAp::Len(kdnodes);
splitsbase=CAp::Len(kdsplits);
cwbase=CAp::Len(cw);
//--- Prepare local copy of tree
localnodes.Resize(n*m_maxnodesize);
localsplits.Resize(n);
localcw.Resize((nx+ny)*n);
localnodessize=0;
localsplitssize=0;
localcwsize=0;
ConvertTreeRec(curtree,n,nx,ny,0,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
//--- Append to multi-tree
kdnodes.Resize(CAp::Len(kdnodes)+localnodessize);
kdsplits.Resize(CAp::Len(kdsplits)+localsplitssize);
cw.Resize(CAp::Len(cw)+localcwsize);
for(int i=0; i<localnodessize; i++)
kdnodes.Set(nodesbase+i,localnodes[i]);
for(int i=0; i<localsplitssize; i++)
kdsplits.Set(splitsbase+i,localsplits[i]);
for(int i=0; i<localcwsize; i++)
cw.Set(cwbase+i,localcw[i]);
}
//+------------------------------------------------------------------+
//| Recurrent tree conversion |
//| CurTree - tree to convert |
//| N, NX, NY - dataset metrics |
//| NodeOffset - offset of current tree node, 0 for root |
//| NodesBase - a value which is added to intra-tree node |
//| indexes; although this tree is stored in |
//| separate array, it is intended to be stored in |
//| the larger tree, with localNodes being moved to |
//| offset NodesBase. |
//| SplitsBase - similarly, offset of localSplits in the final |
//| tree |
//| CWBase - similarly, offset of localCW in the final tree |
//+------------------------------------------------------------------+
void CRBFV2::ConvertTreeRec(CKDTree &curtree,int n,int nx,int ny,
int nodeoffset,int nodesbase,int splitsbase,
int cwbase,CRowInt &localnodes,int &localnodessize,
CRowDouble &localsplits,int &localsplitssize,
CRowDouble &localcw,int &localcwsize,
CMatrixDouble &xybuf)
{
//--- create variables
int nodetype=0;
int cnt=0;
int d=0;
double s=0;
int nodele=0;
int nodege=0;
int oldnodessize=0;
CNearestNeighbor::KDTreeExploreNodeType(curtree,nodeoffset,nodetype);
//--- Leaf node
switch(nodetype)
{
case 0:
CNearestNeighbor::KDTreeExploreLeaf(curtree,nodeoffset,xybuf,cnt);
//--- check
if(!CAp::Assert(CAp::Len(localnodes)>=localnodessize+2,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(CAp::Len(localcw)>=localcwsize+cnt*(nx+ny),__FUNCTION__+": integrity check failed"))
return;
localnodes.Set(localnodessize,cnt);
localnodes.Set(localnodessize+1,cwbase+localcwsize);
localnodessize+=2;
for(int i=0; i<cnt; i++)
{
for(int j=0; j<nx+ny; j++)
localcw.Set(localcwsize+i*(nx+ny)+j,xybuf.Get(i,j));
}
localcwsize+=cnt*(nx+ny);
break;
//--- Split node
case 1:
CNearestNeighbor::KDTreeExploreSplit(curtree,nodeoffset,d,s,nodele,nodege);
//--- check
if(!CAp::Assert(CAp::Len(localnodes)>=localnodessize+m_maxnodesize,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(CAp::Len(localsplits)>=localsplitssize+1,__FUNCTION__+": integrity check failed"))
return;
oldnodessize=localnodessize;
localnodes.Set(localnodessize,0);
localnodes.Set(localnodessize+1,d);
localnodes.Set(localnodessize+2,splitsbase+localsplitssize);
localnodes.Set(localnodessize+3,-1);
localnodes.Set(localnodessize+4,-1);
localnodessize+=5;
localsplits.Set(localsplitssize,s);
localsplitssize++;
localnodes.Set(oldnodessize+3,nodesbase+localnodessize);
ConvertTreeRec(curtree,n,nx,ny,nodele,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
localnodes.Set(oldnodessize+4,nodesbase+localnodessize);
ConvertTreeRec(curtree,n,nx,ny,nodege,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
break;
default:
//--- Integrity error
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function performs partial calculation of hierarchical model:|
//| given evaluation point X and partially computed value Y, it |
//| updates Y by values computed using part of multi-tree given by |
//| RootIdx. |
//| INPUT PARAMETERS: |
//| S - V2 model |
//| Buf - calc - buffer, this function uses following fields:|
//| * Buf.CurBoxMin - should be set by caller |
//| * Buf.CurBoxMax - should be set by caller |
//| * Buf.CurDist2 - squared distance from X to |
//| current bounding box, should be |
//| set by caller |
//| RootIdx - offset of partial kd-tree |
//| InvR2 - 1 / R ^ 2, where R is basis function radius |
//| QueryR2 - squared query radius, usually it is |
//| (R*FarRadius(BasisFunction)) ^ 2 |
//| X - evaluation point, array[NX] |
//| Y - current value for target, array[NY] |
//| DY - current value for derivative, array[NY * NX], if |
//| NeedDY >= 1 |
//| D2Y - current value for derivative, array[NY * NX * NX], |
//| if NeedDY >= 2 |
//| NeedDY - whether derivatives are required or not: |
//| * 0 if only Y is needed |
//| * 1 if Y and DY are needed |
//| * 2 if Y, DY, D2Y are needed |
//| OUTPUT PARAMETERS: |
//| Y - updated partial value |
//| DY - updated derivatives, if NeedDY >= 1 |
//| D2Y - updated Hessian, if NeedDY >= 2 |
//+------------------------------------------------------------------+
void CRBFV2::PartialCalcRec(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
int rootidx,double invr2,double queryr2,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy,CRowDouble &d2y,
int needdy)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int k0=0;
int k1=0;
double ptdist2=0;
double w=0;
double v=0;
double v0=0;
double v1=0;
int cwoffs=0;
int cwcnt=0;
int itemoffs=0;
double arg=0;
double val=0;
double df=0;
double d2f=0;
int d=0;
double split=0;
int childle=0;
int childge=0;
int childoffs=0;
bool updatemin=false;
double prevdist2=0;
double t1=0;
int nx=s.m_nx;;
int ny=s.m_ny;
//--- Helps to avoid spurious warnings
val=0;
//--- Leaf node.
if(s.m_kdnodes[rootidx]>0)
{
cwcnt=s.m_kdnodes[rootidx+0];
cwoffs=s.m_kdnodes[rootidx+1];
for(i=0; i<cwcnt; i++)
{
//--- Calculate distance
itemoffs=cwoffs+i*(nx+ny);
ptdist2=0;
for(j=0; j<nx; j++)
{
v=s.m_cw[itemoffs+j]-x[j];
ptdist2+=v*v;
}
//--- Skip points if distance too large
if(ptdist2>=queryr2)
continue;
//--- Update Y
arg=ptdist2*invr2;
val=0;
df=0;
d2f=0;
if(needdy==2)
{
if(s.m_bf==0)
{
val=MathExp(-arg);
df=-val;
d2f=val;
}
else
{
if(s.m_bf==1)
RBFV2BasisFuncDiff2(s.m_bf,arg,val,df,d2f);
else
{
CAp::Assert(false,__FUNCTION__+": integrity check failed");
return;
}
}
for(j=0; j<ny; j++)
{
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
w=s.m_cw[itemoffs+nx+j];
v=w*df*invr2*2;
for(k0=0; k0<nx; k0++)
{
for(k1=0; k1<nx; k1++)
{
if(k0==k1)
{
//--- Compute derivative and diagonal element of the Hessian
dy.Add(j*nx+k0,v*(x[k0]-s.m_cw[itemoffs+k0]));
d2y.Add(j*nx*nx+k0*nx+k1,w*(d2f*invr2*invr2*4*CMath::Sqr(x[k0]-s.m_cw[itemoffs+k0])+df*invr2*2));
}
else
{
//--- Compute offdiagonal element of the Hessian
d2y.Add(j*nx*nx+k0*nx+k1,w*d2f*invr2*invr2*4*(x[k0]-s.m_cw[itemoffs+k0])*(x[k1]-s.m_cw[itemoffs+k1]));
}
}
}
}
}
if(needdy==1)
{
if(s.m_bf==0)
{
val=MathExp(-arg);
df=-val;
}
else
{
if(s.m_bf==1)
RBFV2BasisFuncDiff2(s.m_bf,arg,val,df,d2f);
else
{
CAp::Assert(false,__FUNCTION__+": integrity check failed");
return;
}
}
for(j=0; j<ny; j++)
{
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
v=s.m_cw[itemoffs+nx+j]*df*invr2*2;
for(k=0; k<nx; k++)
dy.Add(j*nx+k,v*(x[k]-s.m_cw[itemoffs+k]));
}
}
if(needdy==0)
{
if(s.m_bf==0)
val=MathExp(-arg);
else
{
if(s.m_bf==1)
val=RBFV2BasisFunc(s.m_bf,arg);
else
{
CAp::Assert(false,__FUNCTION__+": integrity check failed");
return;
}
}
for(j=0; j<ny; j++)
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
}
}
return;
}
//--- Simple split
if(s.m_kdnodes[rootidx]==0)
{
//--- Load:
//--- * D dimension to split
//--- * Split split position
//--- * ChildLE, ChildGE - indexes of childs
d=s.m_kdnodes[rootidx+1];
split=s.m_kdsplits[s.m_kdnodes[rootidx+2]];
childle=s.m_kdnodes[rootidx+3];
childge=s.m_kdnodes[rootidx+4];
//--- Navigate through childs
for(i=0; i<=1; i++)
{
//--- Select child to process:
//--- * ChildOffs current child offset in Nodes[]
//--- * UpdateMin whether minimum or maximum value
//--- of bounding box is changed on update
updatemin=i!=0;
if(i==0)
childoffs=childle;
else
childoffs=childge;
//--- Update bounding box and current distance
prevdist2=buf.m_curdist2;
t1=x[d];
if(updatemin)
{
v=buf.m_curboxmin[d];
if(t1<=split)
{
v0=v-t1;
if(v0<0)
v0=0;
v1=split-t1;
buf.m_curdist2-=v0*v0-v1*v1;
}
buf.m_curboxmin.Set(d,split);
}
else
{
v=buf.m_curboxmax[d];
if(t1>=split)
{
v0=t1-v;
if(v0<0)
v0=0;
v1=t1-split;
buf.m_curdist2-=v0*v0-v1*v1;
}
buf.m_curboxmax.Set(d,split);
}
//--- Decide: to dive into cell or not to dive
if(buf.m_curdist2<queryr2)
PartialCalcRec(s,buf,childoffs,invr2,queryr2,x,y,dy,d2y,needdy);
//--- Restore bounding box and distance
if(updatemin)
buf.m_curboxmin.Set(d,v);
else
buf.m_curboxmax.Set(d,v);
buf.m_curdist2=prevdist2;
}
return;
}
//--- Integrity failure
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//+------------------------------------------------------------------+
//| This function performs same operation as PartialCalcRec(), but |
//| for entire row of the grid. "Row" is a set of nodes(x0, x1, x2, |
//| x3) which share x1..x3, but have different x0's. (note: for 2D/3D|
//| problems x2..x3 are zero). |
//| Row is given by: |
//| * central point XC, which is located at the center of the row, |
//| and used to perform kd-tree requests |
//| * set of x0 coordinates stored in RX array (array may be |
//| unordered, but it is expected that spread of x0 is no more |
//| than R; function may be inefficient for larger spreads). |
//| * set of YFlag values stored in RF |
//| INPUT PARAMETERS: |
//| S - V2 model |
//| Buf - calc - buffer, this function uses following fields:|
//| * Buf.CurBoxMin - should be set by caller |
//| * Buf.CurBoxMax - should be set by caller |
//| * Buf.CurDist2 - squared distance from X to |
//| current bounding box, should be |
//| set by caller |
//| RootIdx - offset of partial kd-tree |
//| InvR2 - 1 / R ^ 2, where R is basis function radius |
//| RQuery2 - squared query radius, usually it is |
//| (R*FarRadius(BasisFunction) + 0.5 * RowWidth) ^ 2, |
//| where RowWidth is its spatial extent (after scaling|
//| of variables). This radius is used to perform |
//| initial query for neighbors of CX. |
//| RFar2 - squared far radius; far radius is used to perform |
//| actual filtering of results of query made with |
//| RQuery2. |
//| CX - central point, array[NX], used for queries |
//| RX - x0 coordinates, array[RowSize] |
//| RF - sparsity flags, array[RowSize] |
//| RowSize - row size in elements |
//| RY - input partial value, array[NY] |
//| OUTPUT PARAMETERS: |
//| RY - updated partial value (function adds its results |
//| to RY) |
//+------------------------------------------------------------------+
void CRBFV2::PartialRowCalcRec(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
int rootidx,double invr2,
double rquery2,double rfar2,
CRowDouble &cx,CRowDouble &rx,
bool &rf[],int rowsize,
CRowDouble &ry)
{
//--- create variables
int i=0;
int j=0;
int i0=0;
int i1=0;
double partialptdist2=0;
double ptdist2=0;
double v=0;
double v0=0;
double v1=0;
int cwoffs=0;
int cwcnt=0;
int itemoffs=0;
int woffs=0;
double val=0;
int d=0;
double split=0;
int childle=0;
int childge=0;
int childoffs=0;
bool updatemin=false;
double prevdist2=0;
double t1=0;
int nx=s.m_nx;
int ny=s.m_ny;
//--- Leaf node.
if(s.m_kdnodes[rootidx]>0)
{
cwcnt=s.m_kdnodes[rootidx+0];
cwoffs=s.m_kdnodes[rootidx+1];
for(i0=0; i0<cwcnt; i0++)
{
//--- Calculate partial distance (components from 1 to NX-1)
itemoffs=cwoffs+i0*(nx+ny);
partialptdist2=0;
for(j=1; j<nx; j++)
{
v=s.m_cw[itemoffs+j]-cx[j];
partialptdist2+=v*v;
}
//--- Process each element of the row
for(i1=0; i1<rowsize; i1++)
{
if(rf[i1])
{
//--- Calculate distance
v=s.m_cw[itemoffs]-rx[i1];
ptdist2=partialptdist2+v*v;
//--- Skip points if distance too large
if(ptdist2>=rfar2)
continue;
//--- Update Y
val=RBFV2BasisFunc(s.m_bf,ptdist2*invr2);
woffs=itemoffs+nx;
for(j=0; j<ny; j++)
ry.Add(j+i1*ny,val*s.m_cw[woffs+j]);
}
}
}
return;
}
//--- Simple split
if(s.m_kdnodes[rootidx]==0)
{
//--- Load:
//--- * D dimension to split
//--- * Split split position
//--- * ChildLE, ChildGE - indexes of childs
d=s.m_kdnodes[rootidx+1];
split=s.m_kdsplits[s.m_kdnodes[rootidx+2]];
childle=s.m_kdnodes[rootidx+3];
childge=s.m_kdnodes[rootidx+4];
//--- Navigate through childs
for(i=0; i<=1; i++)
{
//--- Select child to process:
//--- * ChildOffs current child offset in Nodes[]
//--- * UpdateMin whether minimum or maximum value
//--- of bounding box is changed on update
updatemin=i!=0;
if(i==0)
childoffs=childle;
else
childoffs=childge;
//--- Update bounding box and current distance
prevdist2=buf.m_curdist2;
t1=cx[d];
if(updatemin)
{
v=buf.m_curboxmin[d];
if(t1<=split)
{
v0=v-t1;
if(v0<0)
v0=0;
v1=split-t1;
buf.m_curdist2-=v0*v0+v1*v1;
}
buf.m_curboxmin.Set(d,split);
}
else
{
v=buf.m_curboxmax[d];
if(t1>=split)
{
v0=t1-v;
if(v0<0)
v0=0;
v1=t1-split;
buf.m_curdist2-=v0*v0+v1*v1;
}
buf.m_curboxmax.Set(d,split);
}
//--- Decide: to dive into cell or not to dive
if(buf.m_curdist2<rquery2)
PartialRowCalcRec(s,buf,childoffs,invr2,rquery2,rfar2,cx,rx,rf,rowsize,ry);
//--- Restore bounding box and distance
if(updatemin)
buf.m_curboxmin.Set(d,v);
else
buf.m_curboxmax.Set(d,v);
buf.m_curdist2=prevdist2;
}
return;
}
//--- Integrity failure
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//+------------------------------------------------------------------+
//| This function prepares partial query |
//| INPUT PARAMETERS: |
//| X - query point |
//| kdBoxMin, kdBoxMax - current bounding box |
//| NX - problem size |
//| Buf - preallocated buffer; this function just loads data,|
//| but does not allocate place for them. |
//| Cnt - counter variable which is set to zery by this |
//| function, as convenience, and to remember about |
//| necessity to zero counter prior to calling |
//| PartialQueryRec(). |
//| OUTPUT PARAMETERS: |
//| Buf - calc-buffer: |
//| * Buf.CurBoxMin - current box |
//| * Buf.CurBoxMax - current box |
//| * Buf.CurDist2 - squared distance from X to |
//| current box |
//| Cnt - set to zero |
//+------------------------------------------------------------------+
void CRBFV2::PreparePartialQuery(CRowDouble &x,
CRowDouble &kdboxmin,
CRowDouble &kdboxmax,int nx,
CRBFV2CalcBuffer &buf,int &cnt)
{
cnt=0;
buf.m_curdist2=0;
for(int j=0; j<nx; j++)
{
buf.m_curboxmin.Set(j,kdboxmin[j]);
buf.m_curboxmax.Set(j,kdboxmax[j]);
if(x[j]<buf.m_curboxmin[j])
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-x[j]);
else
{
if(x[j]>buf.m_curboxmax[j])
buf.m_curdist2+=CMath::Sqr(x[j]-buf.m_curboxmax[j]);
}
}
}
//+------------------------------------------------------------------+
//| This function performs partial(for just one subtree of |
//| multi-tree) query for neighbors located in R-sphere around X. It |
//| returns squared distances from X to points and offsets in S.CW[] |
//| array for points being found. |
//| INPUT PARAMETERS: |
//| kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 |
//| model |
//| Buf - calc - buffer, this function uses following fields:|
//| * Buf.CurBoxMin - should be set by caller |
//| * Buf.CurBoxMax - should be set by caller |
//| * Buf.CurDist2 - squared distance from X to |
//| current bounding box, should be |
//| set by caller |
//| You may use PreparePartialQuery() function to |
//| initialize these fields. |
//| RootIdx - offset of partial kd-tree |
//| QueryR2 - squared query radius |
//| X - array[NX], point being queried |
//| R2 - preallocated output buffer; it is caller's |
//| responsibility to make sure that R2 has enough |
//| space. |
//| Offs - preallocated output buffer; it is caller's |
//| responsibility to make sure that Offs has enough |
//| space. |
//| K - MUST BE ZERO ON INITIAL CALL. This variable is |
//| incremented, not set. So, any no-zero value will |
//| result in the incorrect points count being returned|
//| OUTPUT PARAMETERS: |
//| R2 - squared distances in first K elements |
//| Offs - offsets in S.CW in first K elements |
//| K - points count |
//+------------------------------------------------------------------+
void CRBFV2::PartialQueryRec(CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw,int nx,int ny,
CRBFV2CalcBuffer &buf,int rootidx,
double queryr2,CRowDouble &x,
CRowDouble &r2,CRowInt &offs,int &k)
{
//--- create variables
double ptdist2=0;
double v=0;
int cwoffs=0;
int cwcnt=0;
int itemoffs=0;
int d=0;
double split=0;
int childle=0;
int childge=0;
int childoffs=0;
bool updatemin=false;
double prevdist2=0;
double t1=0;
//--- Leaf node.
if(kdnodes[rootidx]>0)
{
cwcnt=kdnodes[rootidx+0];
cwoffs=kdnodes[rootidx+1];
for(int i=0; i<cwcnt; i++)
{
//--- Calculate distance
itemoffs=cwoffs+i*(nx+ny);
ptdist2=0;
for(int j=0; j<nx; j++)
{
v=cw[itemoffs+j]-x[j];
ptdist2+=v*v;
}
//--- Skip points if distance too large
if(ptdist2>=queryr2)
continue;
//--- Output
r2.Set(k,ptdist2);
offs.Set(k,itemoffs);
k++;
}
return;
}
//--- Simple split
if(kdnodes[rootidx]==0)
{
//--- Load:
//--- * D dimension to split
//--- * Split split position
//--- * ChildLE, ChildGE - indexes of childs
d=kdnodes[rootidx+1];
split=kdsplits[kdnodes[rootidx+2]];
childle=kdnodes[rootidx+3];
childge=kdnodes[rootidx+4];
//--- Navigate through childs
for(int i=0; i<=1; i++)
{
//--- Select child to process:
//--- * ChildOffs current child offset in Nodes[]
//--- * UpdateMin whether minimum or maximum value
//--- of bounding box is changed on update
updatemin=i!=0;
if(i==0)
childoffs=childle;
else
childoffs=childge;
//--- Update bounding box and current distance
prevdist2=buf.m_curdist2;
t1=x[d];
if(updatemin)
{
v=buf.m_curboxmin[d];
if(t1<=split)
buf.m_curdist2-=CMath::Sqr(MathMax(v-t1,0))+CMath::Sqr(split-t1);
buf.m_curboxmin.Set(d,split);
}
else
{
v=buf.m_curboxmax[d];
if(t1>=split)
buf.m_curdist2-=CMath::Sqr(MathMax(t1-v,0))+CMath::Sqr(t1-split);
buf.m_curboxmax.Set(d,split);
}
//--- Decide: to dive into cell or not to dive
if(buf.m_curdist2<queryr2)
PartialQueryRec(kdnodes,kdsplits,cw,nx,ny,buf,childoffs,queryr2,x,r2,offs,k);
//--- Restore bounding box and distance
if(updatemin)
buf.m_curboxmin.Set(d,v);
else
buf.m_curboxmax.Set(d,v);
buf.m_curdist2=prevdist2;
}
return;
}
//--- Integrity failure
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//+------------------------------------------------------------------+
//| This function performs partial(for just one subtree of |
//| multi-tree) counting of neighbors located in R-sphere around X. |
//| This function does not guarantee consistency of results with |
//| other partial queries, it should be used only to get approximate |
//| estimates (well, we do not use approximate algorithms, but |
//| rounding errors may give us inconsistent results in |
//| just-at-the-boundary cases). |
//| INPUT PARAMETERS: |
//| kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 |
//| model |
//| Buf - calc - buffer, this function uses following fields:|
//| * Buf.CurBoxMin - should be set by caller |
//| * Buf.CurBoxMax - should be set by caller |
//| * Buf.CurDist2 - squared distance from X to |
//| current bounding box, should be |
//| set by caller |
//| You may use PreparePartialQuery() function to |
//| initialize these fields. |
//| RootIdx - offset of partial kd-tree |
//| QueryR2 - squared query radius |
//| X - array[NX], point being queried |
//| RESULT: |
//| points count |
//+------------------------------------------------------------------+
int CRBFV2::PartialCountRec(CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw,int nx,int ny,
CRBFV2CalcBuffer &buf,int rootidx,
double queryr2,CRowDouble &x)
{
//--- create variables
int result=0;
double ptdist2=0;
double v=0;
int cwoffs=0;
int cwcnt=0;
int itemoffs=0;
int d=0;
double split=0;
int childle=0;
int childge=0;
int childoffs=0;
bool updatemin=false;
double prevdist2=0;
double t1=0;
//--- Leaf node.
if(kdnodes[rootidx]>0)
{
cwcnt=kdnodes[rootidx+0];
cwoffs=kdnodes[rootidx+1];
for(int i=0; i<cwcnt; i++)
{
//--- Calculate distance
itemoffs=cwoffs+i*(nx+ny);
ptdist2=0;
for(int j=0; j<nx; j++)
{
v=cw[itemoffs+j]-x[j];
ptdist2+=v*v;
}
//--- Skip points if distance too large
if(ptdist2>=queryr2)
continue;
//--- Output
result++;
}
return(result);
}
//--- Simple split
if(kdnodes[rootidx]==0)
{
//--- Load:
//--- * D dimension to split
//--- * Split split position
//--- * ChildLE, ChildGE - indexes of childs
d=kdnodes[rootidx+1];
split=kdsplits[kdnodes[rootidx+2]];
childle=kdnodes[rootidx+3];
childge=kdnodes[rootidx+4];
//--- Navigate through childs
for(int i=0; i<=1; i++)
{
//--- Select child to process:
//--- * ChildOffs current child offset in Nodes[]
//--- * UpdateMin whether minimum or maximum value
//--- of bounding box is changed on update
updatemin=i!=0;
if(i==0)
childoffs=childle;
else
childoffs=childge;
//--- Update bounding box and current distance
prevdist2=buf.m_curdist2;
t1=x[d];
if(updatemin)
{
v=buf.m_curboxmin[d];
if(t1<=split)
buf.m_curdist2-=CMath::Sqr(MathMax(v-t1,0))+CMath::Sqr(split-t1);
buf.m_curboxmin.Set(d,split);
}
else
{
v=buf.m_curboxmax[d];
if(t1>=split)
buf.m_curdist2-=CMath::Sqr(MathMax(t1-v,0))+CMath::Sqr(t1-split);
buf.m_curboxmax.Set(d,split);
}
//--- Decide: to dive into cell or not to dive
if(buf.m_curdist2<queryr2)
result+=PartialCountRec(kdnodes,kdsplits,cw,nx,ny,buf,childoffs,queryr2,x);
//--- Restore bounding box and distance
if(updatemin)
buf.m_curboxmin.Set(d,v);
else
buf.m_curboxmax.Set(d,v);
buf.m_curdist2=prevdist2;
}
return(result);
}
//--- Integrity failure
CAp::Assert(false,"PartialCountRec: integrity check failed");
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function performs partial (for just one subtree of |
//| multi-tree) unpack for RBF model. It appends center coordinates, |
//| weights and per-dimension radii (according to current scaling) |
//| to preallocated output array. |
//| INPUT PARAMETERS: |
//| kdNodes, kdSplits, CW, S, NX, NY - corresponding fields of V2 |
//| model |
//| RootIdx - offset of partial kd-tree |
//| R - radius for current partial tree |
//| XWR - preallocated output buffer; it is caller's |
//| responsibility to make sure that XWR has enough |
//| space. First K rows are already occupied. |
//| K - number of already occupied rows in XWR. |
//| OUTPUT PARAMETERS: |
//| XWR - updated XWR |
//| K - updated rows count |
//+------------------------------------------------------------------+
void CRBFV2::PartialUnpackRec(CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw,CRowDouble &s,int nx,
int ny,int rootidx,double r,
CMatrixDouble &xwr,int &k)
{
//--- create variables
int childle=0;
int childge=0;
int itemoffs=0;
int cwoffs=0;
int cwcnt=0;
//--- Leaf node.
if(kdnodes[rootidx]>0)
{
cwcnt=kdnodes[rootidx+0];
cwoffs=kdnodes[rootidx+1];
for(int i=0; i<cwcnt; i++)
{
itemoffs=cwoffs+i*(nx+ny);
for(int j=0; j<nx+ny; j++)
xwr.Set(k,j,cw[itemoffs+j]);
for(int j=0; j<nx; j++)
xwr.Mul(k,j,s[j]);
for(int j=0; j<nx; j++)
xwr.Set(k,nx+ny+j,r*s[j]);
k++;
}
return;
}
//--- Simple split
if(kdnodes[rootidx]==0)
{
//--- Load:
//--- * ChildLE, ChildGE - indexes of childs
childle=kdnodes[rootidx+3];
childge=kdnodes[rootidx+4];
//--- Process both parts of split
PartialUnpackRec(kdnodes,kdsplits,cw,s,nx,ny,childle,r,xwr,k);
PartialUnpackRec(kdnodes,kdsplits,cw,s,nx,ny,childge,r,xwr,k);
return;
}
//--- Integrity failure
CAp::Assert(false,"PartialUnpackRec: integrity check failed");
}
//+------------------------------------------------------------------+
//| This function returns size of design matrix row for evaluation |
//| point X0, given: |
//| * query radius multiplier (either RBFV2NearRadius() or |
//| RBFV2FarRadius()) |
//| * hierarchy level: value in [0, NH) for single-level model, or |
//| negative value for multilevel model (all levels of hierarchy |
//| in single matrix, like one used by nonnegative RBF) |
//| INPUT PARAMETERS: |
//| kdNodes, kdSplits, |
//| CW, Ri, kdRoots, |
//| kdBoxMin, kdBoxMax, |
//| NX, NY, NH - corresponding fields of V2 model |
//| Level - value in [0, NH) for single-level |
//| design matrix, negative value for |
//| multilevel design matrix |
//| RCoeff - radius coefficient, either |
//| RBFV2NearRadius() or RBFV2FarRadius() |
//| X0 - query point |
//| CalcBuf - buffer for PreparePartialQuery(), |
//| allocated by caller |
//| RESULT: |
//| row size |
//+------------------------------------------------------------------+
int CRBFV2::DesignMatrixRowSize(CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw,CRowDouble &ri,
CRowInt &kdroots,CRowDouble &kdboxmin,
CRowDouble &kdboxmax,int nx,int ny,
int nh,int level,double rcoeff,
CRowDouble &x0,CRBFV2CalcBuffer &calcbuf)
{
//--- create variables
int result=0;
int dummy=0;
int level0=0;
int level1=0;
double curradius2=0;
//--- check
if(!CAp::Assert(nh>0,__FUNCTION__+": integrity failure"))
return (0);
if(level>=0)
{
level0=level;
level1=level;
}
else
{
level0=0;
level1=nh-1;
}
result=0;
for(int levelidx=level0; levelidx<=level1; levelidx++)
{
curradius2=CMath::Sqr(ri[levelidx]*rcoeff);
PreparePartialQuery(x0,kdboxmin,kdboxmax,nx,calcbuf,dummy);
result+=PartialCountRec(kdnodes,kdsplits,cw,nx,ny,calcbuf,kdroots[levelidx],curradius2,x0);
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function generates design matrix row for evaluation point |
//| X0, given: |
//| * query radius multiplier (either RBFV2NearRadius() or |
//| RBFV2FarRadius()) |
//| * hierarchy level: value in [0, NH) for single-level model, or |
//| negative value for multilevel model (all levels of hierarchy |
//| in single matrix, like one used by nonnegative RBF) |
//| INPUT PARAMETERS: |
//| kdNodes, kdSplits, |
//| CW, Ri, kdRoots, |
//| kdBoxMin, kdBoxMax, |
//| NX, NY, NH - corresponding fields of V2 model |
//| CWRange - internal array[NH + 1] used by RBF |
//| construction function, stores ranges of|
//| CW occupied by NH trees. |
//| Level - value in [0, NH) for single-level |
//| design matrix, negative value for |
//| multilevel design matrix |
//| BF - basis function type |
//| RCoeff - radius coefficient, either |
//| RBFV2NearRadius() or RBFV2FarRadius() |
//| RowsPerPoint - equal to: |
//| * 1 for unpenalized regression model |
//| * 1 + NX for basic form of nonsmoothness penalty|
//| Penalty - nonsmoothness penalty coefficient |
//| X0 - query point |
//| CalcBuf - buffer for PreparePartialQuery(), |
//| allocated by caller |
//| R2 - preallocated temporary buffer, size is |
//| at least NPoints; it is caller's |
//| responsibility to make sure that R2 has|
//| enough space. |
//| Offs - preallocated temporary buffer; size is |
//| at least NPoints; it is caller's |
//| responsibility to make sure that Offs |
//| has enough space. |
//| K - MUST BE ZERO ON INITIAL CALL. This |
//| variable is incremented, not set. So, |
//| any no-zero value will result in the |
//| incorrect points count being returned. |
//| RowIdx - preallocated array, at least RowSize |
//| elements |
//| RowVal - preallocated array, at least |
//| RowSize*RowsPerPoint elements |
//| RESULT: |
//| RowIdx - RowSize elements are filled with column|
//| indexes of non-zero design matrix |
//| entries |
//| RowVal - RowSize*RowsPerPoint elements are |
//| filled with design matrix values, with |
//| column RowIdx[0] being stored in first |
//| RowsPerPoint elements of RowVal, column|
//| RowIdx[1] being stored in next |
//| RowsPerPoint elements, and so on. First|
//| element in contiguous set of |
//| RowsPerPoint elements corresponds to |
//| RowSize - number of columns per row |
//+------------------------------------------------------------------+
void CRBFV2::DesignMatrixGenerateRow(CRowInt &kdnodes,CRowDouble &kdsplits,
CRowDouble &cw,CRowDouble &ri,
CRowInt &kdroots,CRowDouble &kdboxmin,
CRowDouble &kdboxmax,CRowInt &cwrange,
int nx,int ny,int nh,int level,
int bf,double rcoeff,int rowsperpoint,
double penalty,CRowDouble &x0,
CRBFV2CalcBuffer &calcbuf,
CRowDouble &tmpr2,
CRowInt &tmpoffs,
CRowInt &rowidx,
CRowDouble &rowval,
int &rowsize)
{
//--- create variables
int cnt=0;
int level0=0;
int level1=0;
double invri2=0;
double curradius2=0;
double val=0;
double dval=0;
double d2val=0;
rowsize=0;
//--- check
if(!CAp::Assert(nh>0,__FUNCTION__+": integrity failure (a)"))
return;
if(!CAp::Assert(rowsperpoint==1 || rowsperpoint==1+nx,__FUNCTION__+": integrity failure (b)"))
return;
if(level>=0)
{
level0=level;
level1=level;
}
else
{
level0=0;
level1=nh-1;
}
for(int levelidx=level0; levelidx<=level1; levelidx++)
{
curradius2=CMath::Sqr(ri[levelidx]*rcoeff);
invri2=1/CMath::Sqr(ri[levelidx]);
PreparePartialQuery(x0,kdboxmin,kdboxmax,nx,calcbuf,cnt);
PartialQueryRec(kdnodes,kdsplits,cw,nx,ny,calcbuf,kdroots[levelidx],curradius2,x0,tmpr2,tmpoffs,cnt);
//--- check
if(!CAp::Assert(CAp::Len(tmpr2)>=cnt,__FUNCTION__+": integrity failure (c)"))
return;
if(!CAp::Assert(CAp::Len(tmpoffs)>=cnt,__FUNCTION__+": integrity failure (d)"))
return;
if(!CAp::Assert(CAp::Len(rowidx)>=rowsize+cnt,__FUNCTION__+": integrity failure (e)"))
return;
if(!CAp::Assert(CAp::Len(rowval)>=rowsperpoint*(rowsize+cnt),__FUNCTION__+": integrity failure (f)"))
return;
for(int j=0; j<cnt; j++)
{
//--- Generate element corresponding to fitting error.
//--- Store derivative information which may be required later.
if(!CAp::Assert((tmpoffs[j]-cwrange[level0])%(nx+ny)==0,__FUNCTION__+": integrity failure (g)"))
return;
RBFV2BasisFuncDiff2(bf,tmpr2[j]*invri2,val,dval,d2val);
rowidx.Set(rowsize+j,(tmpoffs[j]-cwrange[level0])/(nx+ny));
rowval.Set((rowsize+j)*rowsperpoint+0,val);
if(rowsperpoint==1)
continue;
//--- Generate elements corresponding to nonsmoothness penalty
if(!CAp::Assert(rowsperpoint==1+nx,__FUNCTION__+": integrity failure (h)"))
return;
for(int k=0; k<nx; k++)
rowval.Set((rowsize+j)*rowsperpoint+1+k,penalty*(dval*2*invri2+d2val*CMath::Sqr(2*(x0[k]-cw[tmpoffs[j]+k])*invri2)));
}
//--- Update columns counter
rowsize+=cnt;
}
}
//+------------------------------------------------------------------+
//| This function fills RBF model by zeros. |
//+------------------------------------------------------------------+
void CRBFV2::ZeroFill(CRBFV2Model &s,int nx,int ny,int bf)
{
s.m_bf=bf;
s.m_nh=0;
s.m_ri.Resize(0);
s.m_s.Resize(0);
s.m_kdroots.Resize(0);
s.m_kdnodes.Resize(0);
s.m_kdsplits.Resize(0);
s.m_kdboxmin.Resize(0);
s.m_kdboxmax.Resize(0);
s.m_cw.Resize(0);
s.m_v=matrix<double>::Zeros(ny,nx+1);
}
//+------------------------------------------------------------------+
//| Buffer object for parallel evaluation on the model matrix |
//+------------------------------------------------------------------+
struct CRBF3EvaluatorBuffer
{
CRowDouble m_coeffbuf;
CRowDouble m_df1;
CRowDouble m_df2;
CRowDouble m_funcbuf;
CRowDouble m_mindist2;
CRowDouble m_wrkbuf;
CRowDouble m_x;
CMatrixDouble m_deltabuf;
//--- constructor / destructor
CRBF3EvaluatorBuffer(void) {}
~CRBF3EvaluatorBuffer(void) {}
//---
void Copy(const CRBF3EvaluatorBuffer&obj);
//--- overloading
void operator=(const CRBF3EvaluatorBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBF3EvaluatorBuffer::Copy(const CRBF3EvaluatorBuffer &obj)
{
m_coeffbuf=obj.m_coeffbuf;
m_df1=obj.m_df1;
m_df2=obj.m_df2;
m_funcbuf=obj.m_funcbuf;
m_mindist2=obj.m_mindist2;
m_wrkbuf=obj.m_wrkbuf;
m_x=obj.m_x;
m_deltabuf=obj.m_deltabuf;
}
//+------------------------------------------------------------------+
//| Model evaluator: |
//+------------------------------------------------------------------+
struct CRBF3Evaluator
{
int m_chunksize;
int m_functype;
int m_n;
int m_nx;
int m_storagetype;
double m_funcparam;
CRowInt m_entireset;
CRowDouble m_chunk1;
CRBF3EvaluatorBuffer m_bufferpool;
CMatrixDouble m_f;
CMatrixDouble m_x;
CMatrixDouble m_xtchunked;
//--- constructor / destructor
CRBF3Evaluator(void);
~CRBF3Evaluator(void) {}
//---
void Copy(const CRBF3Evaluator&obj);
//--- overloading
void operator=(const CRBF3Evaluator&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBF3Evaluator::CRBF3Evaluator(void)
{
m_chunksize=0;
m_functype=0;
m_n=0;
m_nx=0;
m_storagetype=0;
m_funcparam=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBF3Evaluator::Copy(const CRBF3Evaluator &obj)
{
m_chunksize=obj.m_chunksize;
m_functype=obj.m_functype;
m_n=obj.m_n;
m_nx=obj.m_nx;
m_storagetype=obj.m_storagetype;
m_funcparam=obj.m_funcparam;
m_entireset=obj.m_entireset;
m_chunk1=obj.m_chunk1;
m_f=obj.m_f;
m_x=obj.m_x;
m_xtchunked=obj.m_xtchunked;
m_bufferpool=obj.m_bufferpool;
}
//+------------------------------------------------------------------+
//| Buffer object which is used to perform evaluation requests in the|
//| multithreaded mode(multiple threads working with same RBF object)|
//+------------------------------------------------------------------+
struct CRBFV3CalcBuffer
{
CRowDouble m_x123;
CRowDouble m_x;
CRowDouble m_xg;
CRowDouble m_y123;
CRowDouble m_yg;
CRBF3EvaluatorBuffer m_evalbuf;
//--- constructor / destructor
CRBFV3CalcBuffer(void) {}
~CRBFV3CalcBuffer(void) {}
//--- copy
void Copy(const CRBFV3CalcBuffer&obj);
//--- overloading
void operator=(const CRBFV3CalcBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV3CalcBuffer::Copy(const CRBFV3CalcBuffer &obj)
{
m_x123=obj.m_x123;
m_x=obj.m_x;
m_xg=obj.m_xg;
m_y123=obj.m_y123;
m_yg=obj.m_yg;
m_evalbuf=obj.m_evalbuf;
}
//+------------------------------------------------------------------+
//| Temporary buffers used by divide-and-conquer ACBF preconditioner.|
//| This structure is initialized at the beginning of DC procedure |
//| and put into shared pool. Basecase handling routine retrieves it |
//| from the bool and returns back. |
//| Following fields can be used: |
//| * bFlags - boolean array[N], all values are set to False on|
//| the retrieval, and MUST be False when the buffer|
//| is returned to the pool |
//| * KDTBuf - KD-tree request buffer for thread-safe requests |
//| * KDT1Buf, KDT2Buf - buffers for simplified KD-trees |
//| Additional preallocated temporaries are provided: |
//| * tmpBoxMin - array[NX], no special properties |
//| * tmpBoxMax - array[NX], no special properties |
//| * TargetNodes - dynamically resized as needed |
//+------------------------------------------------------------------+
struct CACBFBuffer
{
bool m_bflags[];
CRowInt m_chosenneighbors;
CRowInt m_currentnodes;
CRowInt m_neighbors;
CRowInt m_perm;
CRowDouble m_choltmp;
CRowDouble m_d;
CRowDouble m_tau;
CRowDouble m_tmpboxmax;
CRowDouble m_tmpboxmin;
CRowDouble m_y;
CRowDouble m_z;
CMatrixDouble m_atwrk;
CMatrixDouble m_b;
CMatrixDouble m_c;
CMatrixDouble m_q1;
CMatrixDouble m_q;
CMatrixDouble m_r;
CMatrixDouble m_wrkq;
CMatrixDouble m_xq;
CKDTreeRequestBuffer m_kdt1buf;
CKDTreeRequestBuffer m_kdt2buf;
CKDTreeRequestBuffer m_kdtbuf;
//--- constructor / destructor
CACBFBuffer(void) {}
~CACBFBuffer(void) {}
//--- copy
void Copy(const CACBFBuffer&obj);
//--- overloading
void operator=(const CACBFBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CACBFBuffer::Copy(const CACBFBuffer &obj)
{
ArrayCopy(m_bflags,obj.m_bflags);
m_chosenneighbors=obj.m_chosenneighbors;
m_currentnodes=obj.m_currentnodes;
m_neighbors=obj.m_neighbors;
m_perm=obj.m_perm;
m_choltmp=obj.m_choltmp;
m_d=obj.m_d;
m_tau=obj.m_tau;
m_tmpboxmax=obj.m_tmpboxmax;
m_tmpboxmin=obj.m_tmpboxmin;
m_y=obj.m_y;
m_z=obj.m_z;
m_atwrk=obj.m_atwrk;
m_b=obj.m_b;
m_c=obj.m_c;
m_q1=obj.m_q1;
m_q=obj.m_q;
m_r=obj.m_r;
m_wrkq=obj.m_wrkq;
m_xq=obj.m_xq;
m_kdt1buf=obj.m_kdt1buf;
m_kdt2buf=obj.m_kdt2buf;
m_kdtbuf=obj.m_kdtbuf;
}
//+------------------------------------------------------------------+
//| Several rows of the ACBF preconditioner |
//+------------------------------------------------------------------+
struct CACBFChunk
{
int m_ntargetcols;
int m_ntargetrows;
CRowInt m_targetcols;
CRowInt m_targetrows;
CMatrixDouble m_s;
//--- constructor / destructor
CACBFChunk(void);
~CACBFChunk(void) {}
//--- copy
void Copy(const CACBFChunk&obj);
//--- overloading
void operator=(const CACBFChunk&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CACBFChunk::CACBFChunk(void)
{
m_ntargetcols=0;
m_ntargetrows=0;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CACBFChunk::Copy(const CACBFChunk &obj)
{
m_ntargetcols=obj.m_ntargetcols;
m_ntargetrows=obj.m_ntargetrows;
m_targetcols=obj.m_targetcols;
m_targetrows=obj.m_targetrows;
m_s=obj.m_s;
}
//+------------------------------------------------------------------+
//| Approximate Cardinal Basis Function builder object |
//| Following fields store problem formulation: |
//| * NTotal - total points count in the dataset |
//| * NX - dimensions count |
//| * XX - array[NTotal, NX], points |
//| * FuncType - basis function type |
//| * FuncParam - basis function parameter |
//| * RoughDatasetDiameter - a rough upper bound on the dataset |
//| diameter |
//| Following global parameters are set: |
//| * NGlobal - global nodes count, >= 0 |
//| * GlobalGrid - global nodes |
//| * GlobalGridSeparation - maximum distance between any pair of |
//| grid nodes; also an upper bound on distance |
//| between any random point in the dataset and a |
//| nearest grid node |
//| * NLocal - number of nearest neighbors select for each |
//| node. |
//| * NCorrection - nodes count for each corrector layer |
//| * CorrectorGrowth - growth factor for corrector layer |
//| * BatchSize - batch size for ACBF construction |
//| * LambdaV - smoothing coefficient, LambdaV >= 0 |
//| * ATerm - linear term for basis functions: |
//| * 1 = linear polynomial(STRONGLY RECOMMENDED) |
//| * 2 = constant polynomial term (may break |
//| convergence for thin plate splines) |
//| * 3 = zero polynomial term (may break |
//| convergence for all types of splines) |
//| Following fields are initialized: |
//| * KDT - KD-tree search structure for the entire dataset |
//| * KDT1, KDT2 - simplified KD-trees (build with progressively |
//| sparsified dataset) |
//| * BufferPool - shared pool for ACBFBuffer instances |
//| * ChunksProducer - shared pool seeded with an instance of |
//| ACBFChunk object(several rows of the |
//| preconditioner) |
//| * ChunksPool - shared pool that contains computed |
//| preconditioner chunks as recycled entries |
//| Temporaries: |
//| * WrkIdx |
//+------------------------------------------------------------------+
struct CACBFBuilder
{
int m_aterm;
int m_batchsize;
int m_functype;
int m_ncorrection;
int m_nglobal;
int m_nlocal;
int m_ntotal;
int m_nx;
double m_correctorgrowth;
double m_funcparam;
double m_globalgridseparation;
double m_lambdav;
double m_roughdatasetdiameter;
bool m_dodetailedtrace;
CRowInt m_globalgrid;
CRowInt m_wrkidx;
CMatrixDouble m_xx;
CKDTree m_kdt1;
CKDTree m_kdt2;
CKDTree m_kdt;
CACBFChunk m_chunkspool;
CACBFChunk m_chunksproducer;
CACBFBuffer m_bufferpool;
//--- constructor / destructor
CACBFBuilder(void);
~CACBFBuilder(void) {}
//--- copy
void Copy(const CACBFBuilder&obj);
//--- overloading
void operator=(const CACBFBuilder&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CACBFBuilder::CACBFBuilder(void)
{
m_aterm=0;
m_batchsize=0;
m_functype=0;
m_ncorrection=0;
m_nglobal=0;
m_nlocal=0;
m_ntotal=0;
m_nx=0;
m_correctorgrowth=0;
m_funcparam=0;
m_globalgridseparation=0;
m_lambdav=0;
m_roughdatasetdiameter=0;
m_dodetailedtrace=false;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CACBFBuilder::Copy(const CACBFBuilder &obj)
{
m_aterm=obj.m_aterm;
m_batchsize=obj.m_batchsize;
m_functype=obj.m_functype;
m_ncorrection=obj.m_ncorrection;
m_nglobal=obj.m_nglobal;
m_nlocal=obj.m_nlocal;
m_ntotal=obj.m_ntotal;
m_nx=obj.m_nx;
m_correctorgrowth=obj.m_correctorgrowth;
m_funcparam=obj.m_funcparam;
m_globalgridseparation=obj.m_globalgridseparation;
m_lambdav=obj.m_lambdav;
m_roughdatasetdiameter=obj.m_roughdatasetdiameter;
m_dodetailedtrace=obj.m_dodetailedtrace;
m_globalgrid=obj.m_globalgrid;
m_wrkidx=obj.m_wrkidx;
m_xx=obj.m_xx;
m_kdt1=obj.m_kdt1;
m_kdt2=obj.m_kdt2;
m_kdt=obj.m_kdt;
m_chunkspool=obj.m_chunkspool;
m_chunksproducer=obj.m_chunksproducer;
m_bufferpool=obj.m_bufferpool;
}
//+------------------------------------------------------------------+
//| Temporary buffers used by divide-and-conquer DDM solver |
//| This structure is initialized at the beginning of DC procedure |
//| and put into shared pool. Basecase handling routine retrieves it |
//| from the pool and returns back. |
//| Following fields can be used: |
//| * bFlags - boolean array[N], all values are set to False on|
//| the retrieval, and MUST be False when the buffer|
//| is returned to the pool |
//| * KDTBuf - KD-tree request buffer for thread-safe requests |
//| Additional preallocated temporaries are provided: |
//| * Idx2PrecCol - integer array[N + NX + 1], no special |
//| properties |
//| * tmpBoxMin - array[NX], no special properties |
//| * tmpBoxMax - array[NX], no special properties |
//+------------------------------------------------------------------+
struct CRBF3DDMBuffer
{
bool m_bflags[];
CKDTreeRequestBuffer m_kdtbuf;
CRowInt m_idx2preccol;
CRowDouble m_tmpboxmax;
CRowDouble m_tmpboxmin;
//--- constructor / destructor
CRBF3DDMBuffer(void) {}
~CRBF3DDMBuffer(void) {}
//--- copy
void Copy(const CRBF3DDMBuffer&obj);
//--- overloading
void operator=(const CRBF3DDMBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBF3DDMBuffer::Copy(const CRBF3DDMBuffer &obj)
{
ArrayCopy(m_bflags,obj.m_bflags);
m_kdtbuf=obj.m_kdtbuf;
m_idx2preccol=obj.m_idx2preccol;
m_tmpboxmax=obj.m_tmpboxmax;
m_tmpboxmin=obj.m_tmpboxmin;
}
//+------------------------------------------------------------------+
//| Subproblem for DDM algorithm, stores precomputed factorization |
//| and other information. |
//| Following fields are set during construction: |
//| * IsValid - whether instance is valid subproblem or not |
//| * NTarget - number of target nodes in the subproblem, |
//| NTarget >= 1 |
//| * TargetNodes - array containing target node indexes |
//| * NWork - number of working nodes in the subproblem, |
//| NWork >= NTarget |
//| * WorkingNodes - array containing working node indexes |
//| * RegSystem - smoothed (regularized) working system |
//| * Decomposition - decomposition type: |
//| * 0 for LU |
//| * 1 for regularized QR |
//| * WrkLU - NWork*NWork sized LU factorization of the |
//| subproblem |
//| * WrkP - pivots for the LU decomposition |
//| * WrkQ, WrkR - NWork*NWork sized matrices, factors of QR |
//| decomposition of RegSystem. Due to |
//| regularization rows added, the Q factor is |
//| actually an 2NWork * NWork matrix, but in |
//| order to solve the system we need only |
//| leading NWork rows, so the rest is not stored|
//+------------------------------------------------------------------+
struct CRBF3DDMSubproblem
{
int m_decomposition;
int m_ntarget;
int m_nwork;
bool m_isvalid;
CRowInt m_targetnodes;
CRowInt m_workingnodes;
CRowInt m_wrkp;
CMatrixDouble m_pred;
CMatrixDouble m_qtrhs;
CMatrixDouble m_regsystem;
CMatrixDouble m_rhs;
CMatrixDouble m_sol;
CMatrixDouble m_wrklu;
CMatrixDouble m_wrkq;
CMatrixDouble m_wrkr;
//--- constructor / destructor
CRBF3DDMSubproblem(void);
~CRBF3DDMSubproblem(void) {}
//--- copy
void Copy(const CRBF3DDMSubproblem&obj);
//--- overloading
void operator=(const CRBF3DDMSubproblem&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBF3DDMSubproblem::CRBF3DDMSubproblem(void)
{
m_decomposition=0;
m_ntarget=0;
m_nwork=0;
m_isvalid=false;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBF3DDMSubproblem::Copy(const CRBF3DDMSubproblem &obj)
{
m_decomposition=obj.m_decomposition;
m_ntarget=obj.m_ntarget;
m_nwork=obj.m_nwork;
m_isvalid=obj.m_isvalid;
m_targetnodes=obj.m_targetnodes;
m_workingnodes=obj.m_workingnodes;
m_wrkp=obj.m_wrkp;
m_pred=obj.m_pred;
m_qtrhs=obj.m_qtrhs;
m_regsystem=obj.m_regsystem;
m_rhs=obj.m_rhs;
m_sol=obj.m_sol;
m_wrklu=obj.m_wrklu;
m_wrkq=obj.m_wrkq;
m_wrkr=obj.m_wrkr;
}
//+------------------------------------------------------------------+
//| DDM solver |
//| Following fields store information about problem: |
//| LambdaV - smoothing coefficient |
//| Following fields related to DDM part are present: |
//| SubproblemsCnt - number of subproblems created, |
//| SubproblemCnt >= 1 |
//| SubproblemsPool - shared pool seeded with instance of |
//| RBFV3DDMSubproblem class (default seed has |
//| Seed.IsValid = False). It also contains exactly |
//| SubproblemCnt subproblem instances as recycled |
//| entries, each of these instances has |
//| Seed.IsValid = True and contains a partition of |
//| the complete problem into subproblems and |
//| precomputed factorization |
//| SubproblemsBuffer - shared pool seeded with instance of |
//| RBFV3DDMSubproblem class (default seed has |
//| Seed.IsValid = False). Contains no recycled |
//| entries, should be used just for temporary |
//| storage of the already processed subproblems. |
//| Following fields store information about corrector spline: |
//| NCorrector - corrector nodes count, NCorrector > 0 |
//| CorrQ - Q factor from the QR decomposition of the |
//| corrector linear system, |
//| array[NCorrector, NCorrector] |
//| CorrR - R factor from the QR decomposition of the |
//| corrector linear system, |
//| array[NCorrector, NCorrector] |
//| CorrNodes - array[NCorrector], indexes of dataset nodes |
//| chosen for the corrector spline |
//| CorrX - array[NCorrector, NX], dataset points |
//| Following fields store information that is used for logging and |
//| testing: |
//| CntLU - number of subproblems solved with LU (well |
//| conditioned) |
//| CntRegQR - number of subproblems solved with Reg-QR (badly |
//| conditioned) |
//+------------------------------------------------------------------+
struct CRBF3DDMSolver
{
int m_cntlu;
int m_cntregqr;
int m_ncorrector;
int m_subproblemscnt;
double m_lambdav;
CRowInt m_corrnodes;
CRBF3DDMSubproblem m_subproblemsbuffer;
CRBF3DDMSubproblem m_subproblemspool;
CRBF3DDMBuffer m_bufferpool;
CMatrixDouble m_corrq;
CMatrixDouble m_corrr;
CMatrixDouble m_corrx;
CMatrixDouble m_tmpres1;
CMatrixDouble m_tmpupd1;
CKDTree m_kdt;
//--- constructor / destructor
CRBF3DDMSolver(void);
~CRBF3DDMSolver(void) {}
//--- copy
void Copy(const CRBF3DDMSolver&obj);
//--- overloading
void operator=(const CRBF3DDMSolver&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBF3DDMSolver::CRBF3DDMSolver(void)
{
m_cntlu=0;
m_cntregqr=0;
m_ncorrector=0;
m_subproblemscnt=0;
m_lambdav=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBF3DDMSolver::Copy(const CRBF3DDMSolver &obj)
{
m_cntlu=obj.m_cntlu;
m_cntregqr=obj.m_cntregqr;
m_ncorrector=obj.m_ncorrector;
m_subproblemscnt=obj.m_subproblemscnt;
m_lambdav=obj.m_lambdav;
m_corrnodes=obj.m_corrnodes;
m_subproblemsbuffer=obj.m_subproblemsbuffer;
m_subproblemspool=obj.m_subproblemspool;
m_bufferpool=obj.m_bufferpool;
m_corrq=obj.m_corrq;
m_corrr=obj.m_corrr;
m_corrx=obj.m_corrx;
m_tmpres1=obj.m_tmpres1;
m_tmpupd1=obj.m_tmpupd1;
m_kdt=obj.m_kdt;
}
//+------------------------------------------------------------------+
//| RBF model. |
//| Never try to work with fields of this object directly - always |
//| use ALGLIB functions to use this object. |
//+------------------------------------------------------------------+
struct CRBFV3Model
{
int m_bftype;
int m_nc;
int m_nx;
int m_ny;
double m_bfparam;
bool m_dbgregqrusedforddm;
CRowInt m_pointindexes;
CRowDouble m_cw;
CRowDouble m_s;
CRBFV3CalcBuffer m_calcbuf;
CRBF3Evaluator m_evaluator;
CMatrixDouble m_v;
CMatrixDouble m_wchunked;
//--- constructor / destructor
CRBFV3Model(void);
~CRBFV3Model(void) {}
//---
void Copy(const CRBFV3Model&obj);
//--- overloading
void operator=(const CRBFV3Model&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBFV3Model::CRBFV3Model(void)
{
m_bftype=0;
m_nc=0;
m_nx=0;
m_ny=0;
m_bfparam=0;
m_dbgregqrusedforddm=false;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV3Model::Copy(const CRBFV3Model &obj)
{
m_bftype=obj.m_bftype;
m_nc=obj.m_nc;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_bfparam=obj.m_bfparam;
m_dbgregqrusedforddm=obj.m_dbgregqrusedforddm;
m_pointindexes=obj.m_pointindexes;
m_cw=obj.m_cw;
m_s=obj.m_s;
m_calcbuf=obj.m_calcbuf;
m_evaluator=obj.m_evaluator;
m_v=obj.m_v;
m_wchunked=obj.m_wchunked;
}
//+------------------------------------------------------------------+
//| RBF solution report: |
//| * TerminationType - termination type, positive values-success,|
//| non-positive - failure. |
//+------------------------------------------------------------------+
struct CRBFV3Report
{
int m_iterationscount;
int m_terminationtype;
double m_maxerror;
double m_rmserror;
//--- constructor / destructor
CRBFV3Report(void) { ZeroMemory(this); }
~CRBFV3Report(void) {}
//--- copy
void Copy(const CRBFV3Report&obj);
//--- overloading
void operator=(const CRBFV3Report&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CRBFV3Report::Copy(const CRBFV3Report &obj)
{
m_iterationscount=obj.m_iterationscount;
m_terminationtype=obj.m_terminationtype;
m_maxerror=obj.m_maxerror;
m_rmserror=obj.m_rmserror;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CRBFV3
{
public:
//--- constants
static const double m_epsred;
static const int m_maxddmits;
static const double m_polyharmonic2scale;
static const int m_acbfparallelthreshold;
static const int m_ddmparallelthreshold;
static const int m_bfparallelthreshold;
//---
static void RBFV3Create(int nx,int ny,int bf,double bfp,CRBFV3Model&s);
static void RBFV3CreateCalcBuffer(CRBFV3Model&s,CRBFV3CalcBuffer&buf);
static void RBFV3Build(CMatrixDouble&xraw,CMatrixDouble&yraw,int nraw,CRowDouble&scaleraw,int bftype,double bfparamraw,double lambdavraw,int aterm,CRBFV3Model&s,int &progress10000,bool&terminationrequest,CRBFV3Report&rep);
static void RBFV3Alloc(CSerializer&s,CRBFV3Model&model);
static void RBFV3Serialize(CSerializer&s,CRBFV3Model&model);
static void RBFV3Unserialize(CSerializer&s,CRBFV3Model&model);
static double RBFV3Calc1(CRBFV3Model&s,double x0);
static double RBFV3Calc2(CRBFV3Model&s,double x0,double x1);
static double RBFV3Calc3(CRBFV3Model&s,double x0,double x1,double x2);
static void RBFV3CalcBuf(CRBFV3Model&s,CRowDouble&x,CRowDouble&y);
static void RBFV3TsCalcBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
static void RBFV3TsDiffBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFV3TSHessBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFV3GridCalcVX(CRBFV3Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y);
static void RBFV3Unpack(CRBFV3Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
private:
static void CreateFastEvaluator(CRBFV3Model&model);
static void GridCalcRec(CRBFV3Model&s,int simdwidth,int tileidx0,int tileidx1,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y,CRBFV3CalcBuffer &calcpool[]);
static void ZeroFill(CRBFV3Model&s,int nx,int ny);
static void AllocateCalcBuffer(CRBFV3Model&s,CRBFV3CalcBuffer&buf);
static void PreprocessDatasetRec(CMatrixDouble&xbuf,CMatrixDouble&ybuf,CRowInt&initidx,int wrk0,int wrk1,int nx,int ny,double mergetol,CRowDouble&tmpboxmin,CRowDouble&tmpboxmax,CMatrixDouble&xout,CMatrixDouble&yout,CRowInt&raw2wrkmap,CRowInt&wrk2rawmap,int &nout);
static void PreprocessDataSet(CMatrixDouble&xraw,double mergetol,CMatrixDouble&yraw,CRowDouble&xscaleraw,int nraw,int nx,int ny,int bftype,double bfparamraw,double lambdavraw,CMatrixDouble&xwrk,CMatrixDouble&ywrk,CRowInt&raw2wrkmap,CRowInt&wrk2rawmap,int &nwrk,CRowDouble&xscalewrk,CRowDouble&xshift,double&bfparamwrk,double&lambdavwrk,double&addxrescaleaplied);
static void SelectGlobalNodes(CMatrixDouble&xx,int n,int nx,CRowInt&existingnodes,int nexisting,int nspec,CRowInt&nodes,int &nchosen,double&maxdist);
static void BuildSimplifiedKDTree(CMatrixDouble&xx,int n,int nx,int reducefactor,int minsize,CKDTree&kdt);
static void ComputeTargetScatterDesignMatrices(CMatrixDouble&xx,int ntotal,int nx,int functype,double funcparam,CRowInt&workingnodes,int nwrk,CRowInt&scatternodes,int nscatter,CMatrixDouble&atwrk,CMatrixDouble&atsctr);
static void ComputeACBFPreconditionerBasecase(CACBFBuilder&builder,CACBFBuffer&buf,int wrk0,int wrk1);
static void ComputeACBFPreconditionerRecV2(CACBFBuilder&builder,int wrk0,int wrk1);
static void ComputeACBFPreconditioner(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,int aterm,int batchsize,int nglobal,int nlocal,int ncorrection,int correctorgrowth,int simplificationfactor,double lambdav,CSparseMatrix&sp);
static void DDMSolverInitBasecase(CRBF3DDMSolver&solver,CMatrixDouble&x,int n,int nx,CRBF3Evaluator&bfmatrix,double lambdav,CSparseMatrix&sp,CRBF3DDMBuffer&buf,CRowInt&tgtidx,int tgt0,int tgt1,int nneighbors,bool dodetailedtrace);
static void DDMSolverInitRec(CRBF3DDMSolver&solver,CMatrixDouble&x,int n,int nx,CRBF3Evaluator&bfmatrix,double lambdav,CSparseMatrix&sp,CRowInt&wrkidx,int wrk0,int wrk1,int nneighbors,int nbatch,bool dodetailedtrace);
static void DDMSolverInit(CMatrixDouble&x,double rescaledby,int n,int nx,CRBF3Evaluator&bfmatrix,int bftype,double bfparam,double lambdav,int aterm,CSparseMatrix&sp,int nneighbors,int nbatch,int ncorrector,bool dotrace,bool dodetailedtrace,CRBF3DDMSolver&solver,int &timeddminit,int &timecorrinit);
static void DDMSolverRunRec(CRBF3DDMSolver&solver,CMatrixDouble&res,int n,int nx,int ny,CMatrixDouble&c,int cnt);
static void DDMSolverRun(CRBF3DDMSolver&solver,CMatrixDouble&res,int n,int nx,int ny,CSparseMatrix&sp,CRBF3Evaluator&bfmatrix,CMatrixDouble&upd,int &timeddmsolve,int &timecorrsolve);
static void DDMSolverRun1(CRBF3DDMSolver&solver,CRowDouble&res,int n,int nx,CSparseMatrix&sp,CRBF3Evaluator&bfmatrix,CRowDouble&upd,int &timeddmsolve,int &timecorrsolve);
static double AutoDetectScaleParameter(CMatrixDouble&xx,int n,int nx);
static void ComputeBFMatrixRec(CMatrixDouble&xx,int range0,int range1,int n,int nx,int functype,double funcparam,CMatrixDouble&f);
static void ComputeBFMatrix(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,CMatrixDouble&f);
static void ModelMatrixInit(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,int storagetype,CRBF3Evaluator&modelmatrix);
static void ModelMatrixComputePartial(CRBF3Evaluator&modelmatrix,CRowInt&ridx,int m0,CRowInt&cidx,int m1,CMatrixDouble&r);
static void ComputeRowChunk(CRBF3Evaluator&evaluator,CRowDouble&x,CRBF3EvaluatorBuffer&buf,int chunksize,int chunkidx,double distance0,int needgradinfo);
static void ModelMatrixComputeProductRec(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowInt&rowidx,CRowDouble&r,int idx0,int idx1,bool toplevelcall);
static void ModelMatrixComputeProduct(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowDouble&r);
static void ModelMatrixComputeProductAtNodes(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowInt&idx,int m,CRowDouble&r);
static bool IsCPDFunction(int functype,int aterm);
};
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
const double CRBFV3::m_epsred=0.999999;
const int CRBFV3::m_maxddmits=50;
const double CRBFV3::m_polyharmonic2scale=4.0;
const int CRBFV3::m_acbfparallelthreshold=512;
const int CRBFV3::m_ddmparallelthreshold=512;
const int CRBFV3::m_bfparallelthreshold=512;
//+------------------------------------------------------------------+
//| This function creates RBF model for a scalar(NY = 1) or vector |
//| (NY > 1) function in a NX - dimensional space(NX >= 1). |
//| INPUT PARAMETERS: |
//| NX - dimension of the space, NX >= 1 |
//| NY - function dimension, NY >= 1 |
//| BF - basis function type: |
//| * 1 for biharmonic/multiquadric |
//| f = sqrt(r ^ 2 + alpha ^ 2) |
//| (with f = r being a special case) |
//| * 2 for polyharmonic f = r ^ 2 * ln(r) |
//| BFP - basis function parameter: |
//| * BF = 0 parameter ignored |
//| OUTPUT PARAMETERS: |
//| S - RBF model (initially equals to zero) |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Create(int nx,int ny,int bf,double bfp,CRBFV3Model &s)
{
//--- check
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
return;
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
return;
if(!CAp::Assert(bf==1 || bf==2,__FUNCTION__+": unsupported basis function type"))
return;
if(!CAp::Assert(MathIsValidNumber(bfp) && bfp>=0.0,__FUNCTION__+": infinite or negative basis function parameter"))
return;
//--- Serializable parameters
s.m_nx=nx;
s.m_ny=ny;
s.m_bftype=bf;
s.m_bfparam=bfp;
s.m_nc=0;
s.m_s=vector<double>::Ones(nx);
s.m_v=matrix<double>::Zeros(ny,nx+1);
AllocateCalcBuffer(s,s.m_calcbuf);
//--- Debug counters
s.m_dbgregqrusedforddm=false;
}
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to |
//| perform parallel RBF model evaluations (with one RBF model |
//| instance being used from multiple threads, as long as different |
//| threads use different instances of buffer). |
//| This buffer object can be used with RBFTSCalcBuf() function (here|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
//| function which reuses previously allocated output space). |
//| How to use it: |
//| * create RBF model structure with RBFCreate() |
//| * load data, tune parameters |
//| * call RBFBuildModel() |
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
//| model (you should call this function only AFTER call to |
//| RBFBuildModel(), see below for more information) |
//| * call RBFTSCalcBuf() from different threads, with each thread |
//| working with its own copy of buffer object. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| Buf - external buffer. |
//| IMPORTANT: buffer object should be used only with RBF model |
//| object which was used to initialize buffer. Any |
//| attempt to use buffer with different object is |
//| dangerous - you may get memory violation error because|
//| sizes of internal arrays do not fit to dimensions of |
//| RBF structure. |
//| IMPORTANT: you should call this function only for model which was|
//| built with RBFBuildModel() function, after successful |
//| invocation of RBFBuildModel(). Sizes of some internal |
//| structures are determined only after model is built, |
//| so buffer object created before model construction |
//| stage will be useless (and any attempt to use it will |
//| result in exception). |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3CreateCalcBuffer(CRBFV3Model &s,CRBFV3CalcBuffer &buf)
{
AllocateCalcBuffer(s,buf);
}
//+------------------------------------------------------------------+
//| This function builds hierarchical RBF model. |
//| INPUT PARAMETERS: |
//| X - array[N, S.NX], X - values |
//| Y - array[N, S.NY], Y - values |
//| ScaleVec - array[S.NX], vector of per-dimension scales |
//| N - points count |
//| BFtype - basis function type: |
//| * 1 for biharmonic spline f = r or multiquadric |
//| f = sqrt(r ^ 2 + param ^ 2) |
//| * 2 for thin plate spline f = r ^ 2 * ln(r) |
//| BFParam - for BFType = 1 zero value means biharmonic, nonzero|
//| means multiquadric ignored for BFType = 2 |
//| LambdaV - regularization parameter |
//| ATerm - polynomial term type: |
//| * 1 for linear term (STRONGLY RECOMMENDED) |
//| * 2 for constant term (may break convergence |
//| guarantees for thin plate splines) |
//| * 3 for zero term (may break convergence guarantees|
//| for all types of splines) |
//| S - RBF model, already initialized by RBFCreate() call.|
//| progress10000 - variable used for progress reports, it is |
//| regularly set to the current progress multiplied by|
//| 10000, in order to get value in [0, 10000] range. |
//| The rationale for such scaling is that it allows us|
//| to use integer type to store progress, which has |
//| less potential for non - atomic corruption on |
//| unprotected reads from another threads. |
//| You can read this variable from some other thread |
//| to get estimate of the current progress. Initial |
//| value of this variable is ignored, it is written by|
//| this function, but not read. |
//| terminationrequest - variable used for termination requests; |
//| its initial value must be False, and you can set it|
//| to True from some other thread. This routine |
//| regularly checks this variable and will terminate |
//| model construction shortly upon discovering that |
//| termination was requested. |
//| OUTPUT PARAMETERS: |
//| S - updated model (for rep.m_terminationtype > 0, |
//| unchanged otherwise) |
//| Rep - report: |
//| * Rep.TerminationType: |
//| * 1 - successful termination |
//| * 8 terminated by user via |
//| RBFRequestTermination() |
//| Fields are used for debugging purposes: |
//| * Rep.IterationsCount - iterations count of the GMRES solver |
//| NOTE: failure to build model will leave current State of the |
//| structure unchanged. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Build(CMatrixDouble &xraw,
CMatrixDouble &yraw,
int nraw,
CRowDouble &scaleraw,
int bftype,
double bfparamraw,
double lambdavraw,
int aterm,
CRBFV3Model &s,
int &progress10000,
bool &terminationrequest,
CRBFV3Report &rep)
{
//--- create variables
double tol=0;
int n=0;
int nx=0;
int ny=0;
double bfparamscaled=0;
double lambdavwrk=0;
double rescaledby=0;
double mergetol=0;
int matrixformat=0;
int acbfbatch=0;
int nglobal=0;
int nlocal=0;
int ncorrection=0;
int nbatch=0;
int nneighbors=0;
int ncoarse=0;
CMatrixDouble xscaled;
CMatrixDouble yscaled;
CMatrixDouble xcoarse;
CMatrixDouble x1t;
CRBF3Evaluator bfmatrix;
CRowDouble b;
CRowDouble x0;
CRowDouble x1;
CRowDouble y0;
CRowDouble y1;
CRowDouble sft;
CRowDouble scalewrk;
CMatrixDouble c2;
CMatrixDouble res;
CMatrixDouble upd0;
CMatrixDouble upd1;
CMatrixDouble ortbasis;
int ortbasissize=0;
CRowInt raw2wrkmap;
CRowInt wrk2rawmap;
CRowInt idummy;
CSparseMatrix sp;
CSparseSolverState ss;
CSparseSolverReport ssrep;
CRBF3DDMSolver ddmsolver;
double resnrm=0;
double res0nrm=0;
int iteridx=0;
int yidx=0;
bool dotrace=false;
bool dodetailedtrace=false;
CFblsGMRESState gmressolver;
double orterr=0;
int timeprec=0;
int timedesign=0;
int timeddminit=0;
int timeddmsolve=0;
int timecorrinit=0;
int timecorrsolve=0;
int timereeval=0;
int timetotal=0;
int i=0;
int j=0;
int k=0;
double v=0;
double vv=0;
CMatrixDouble refrhs;
CRowDouble refrhs1;
CRowDouble refsol1;
mergetol=1000*CMath::m_machineepsilon;
tol=1.0E-6;
//--- check
if(!CAp::Assert(s.m_nx>0,__FUNCTION__+": incorrect NX"))
return;
if(!CAp::Assert(s.m_ny>0,__FUNCTION__+": incorrect NY"))
return;
if(!CAp::Assert(bftype==1 || bftype==2 || bftype==3,__FUNCTION__+": incorrect BFType"))
return;
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": incorrect BFType"))
return;
for(j=0; j<s.m_nx; j++)
{
if(!CAp::Assert(scaleraw[j]>0.0,__FUNCTION__+": incorrect ScaleVec"))
return;
}
nx=s.m_nx;
ny=s.m_ny;
bfparamscaled=bfparamraw;
//--- Trace output (if needed)
dotrace=CAp::IsTraceEnabled("RBF");
dodetailedtrace=dotrace && CAp::IsTraceEnabled("RBF.DETAILED");
if(dotrace)
{
CAp::Trace("\n\n");
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
CAp::Trace("//--- DDM-RBF builder started //\n");
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
}
//--- Clean up communication and report fields
progress10000=0;
rep.m_maxerror=0;
rep.m_rmserror=0;
rep.m_iterationscount=0;
timeprec=0;
timedesign=0;
timeddminit=0;
timeddmsolve=0;
timecorrinit=0;
timecorrsolve=0;
timereeval=0;
timetotal=0-(int)(GetTickCount()/10000);
//--- Quick exit when we have no points
if(nraw==0)
{
ZeroFill(s,nx,ny);
rep.m_terminationtype=1;
progress10000=10000;
return;
}
//--- Preprocess dataset (scale points, merge nondistinct ones)
PreprocessDataSet(xraw,mergetol,yraw,scaleraw,nraw,nx,ny,bftype,bfparamraw,lambdavraw,xscaled,yscaled,raw2wrkmap,wrk2rawmap,n,scalewrk,sft,bfparamscaled,lambdavwrk,rescaledby);
x1t.Resize(nx+1,n);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
x1t.Set(j,i,xscaled.Get(i,j));
x1t.Set(nx,i,1.0);
}
//--- Compute design matrix
matrixformat=1;
if(dotrace)
{
CAp::Trace("=== MODEL MATRIX INITIALIZATION STARTED ============================================================\n");
CAp::Trace(StringFormat("N = %d\nNX = %d\nNY = %d\n",n,nx,ny));
CAp::Trace(StringFormat("BFType = %d",bftype));
if(bftype==1 && bfparamraw>0.0)
CAp::Trace(StringFormat(" ( f=sqrt(r^2+alpha^2),alpha=%.3f,multiquadric with manual radius)",bfparamraw));
if(bftype==1 && bfparamraw==0.0)
CAp::Trace(" ( f=r,biharmonic spline )");
if(bftype==1 && bfparamraw<0.0)
CAp::Trace(StringFormat(" ( f=sqrt(r^2+alpha^2),alpha=AUTO*%.3f=%.2E,multiquadric )",-bfparamraw,bfparamscaled));
if(bftype==2)
CAp::Trace(" ( f=log(r)*r^2,thin plate spline )");
if(bftype==3)
CAp::Trace(" ( f=r^3 )");
CAp::Trace("\n");
CAp::Trace(StringFormat("Polinom.term= %d ",aterm));
if(aterm==1)
CAp::Trace("(linear term)");
if(aterm==2)
CAp::Trace("(constant term)");
if(aterm==3)
CAp::Trace("(zero term)");
CAp::Trace("\n");
CAp::Trace(StringFormat("LambdaV = %.2E (raw value of the smoothing parameter; effective value after adjusting for data spread is %.2E)\n",lambdavraw,lambdavwrk));
CAp::Trace("VarScales = ");
CApServ::TraceVectorE3(scaleraw,0,nx);
CAp::Trace(" (raw values of variable scales)\n");
}
timedesign-=(int)(GetTickCount()/10000);
ModelMatrixInit(xscaled,n,nx,bftype,bfparamscaled,matrixformat,bfmatrix);
timedesign+=(int)(GetTickCount()/10000);
if(dotrace)
CAp::Trace(StringFormat("> model matrix initialized in %d ms\n",timedesign));
//--- Build orthogonal basis of the subspace spanned by polynomials of 1st degree.
//--- This basis is used later to check orthogonality conditions for the coefficients.
ortbasis.Resize(nx+1,n);
CAblasF::RSetR(n,1/MathSqrt(n),ortbasis,0);
ortbasissize=1;
x0.Resize(n);
for(k=0; k<nx; k++)
{
for(j=0; j<n; j++)
x0.Set(j,xscaled.Get(j,k));
v=MathSqrt(CAblasF::RDotV2(n,x0));
CAblas::RowWiseGramSchmidt(ortbasis,ortbasissize,n,x0,x0,false);
vv=MathSqrt(CAblasF::RDotV2(n,x0));
if(vv>(MathSqrt(CMath::m_machineepsilon)*(v+1)))
{
CAblasF::RCopyMulVR(n,1/vv,x0,ortbasis,ortbasissize);
ortbasissize++;
}
}
//--- Build preconditioner
nglobal=0;
nlocal=(int)MathMax(MathRound(MathPow(5.5,nx)),25);
ncorrection=(int)MathRound(MathPow(5,nx));
acbfbatch=32;
if(dotrace)
{
CAp::Trace("=== PRECONDITIONER CONSTRUCTION STARTED ============================================================\n");
CAp::Trace(StringFormat("nglobal = %d\nnlocal = %d\nncorrection = %d\nnbatch = %d\n",nglobal,nlocal,ncorrection,acbfbatch));
}
timeprec-=(int)(GetTickCount()/10000);
ComputeACBFPreconditioner(xscaled,n,nx,bftype,bfparamscaled,aterm,acbfbatch,nglobal,nlocal,ncorrection,5,2,lambdavwrk,sp);
timeprec+=(int)(GetTickCount()/10000);
if(dotrace)
CAp::Trace(StringFormat("> ACBF preconditioner computed in %d ms\n",timeprec));
//--- DDM
if(dotrace)
CAp::Trace("=== DOMAIN DECOMPOSITION METHOD STARTED ============================================================\n");
CAblasF::RSetAllocM(n+nx+1,ny,0.0,c2);
nneighbors=(int)MathRound(MathPow(5,nx));
if(nx==1)
nbatch=MathMin(100,n);
else
{
if(nx==2)
nbatch=MathMin(100,n);
else
nbatch=MathMin((int)MathRound(MathPow(10,nx)),MathMin(1000,n));
}
ncoarse=(int)MathRound(MathMax(4,MathPow(3.0,nx))*((double)n/(double)nbatch+1));
ncoarse=MathMax(ncoarse,(int)MathRound(MathPow(4,nx)));
ncoarse=MathMin(ncoarse,n);
if(dotrace)
{
CAp::Trace("> problem metrics and settings\n");
CAp::Trace(StringFormat("NNeighbors = %d\n",nneighbors));
CAp::Trace(StringFormat("NBatch = %d\n",nbatch));
CAp::Trace(StringFormat("NCoarse = %d\n",ncoarse));
}
DDMSolverInit(xscaled,rescaledby,n,nx,bfmatrix,bftype,bfparamscaled,lambdavwrk,aterm,sp,nneighbors,nbatch,ncoarse,dotrace,dodetailedtrace,ddmsolver,timeddminit,timecorrinit);
if(dotrace)
CAp::Trace(StringFormat("> DDM initialization done in %d ms,%d subproblems solved (%d well-conditioned,%d ill-conditioned)\n",timeddminit,ddmsolver.m_subproblemscnt,ddmsolver.m_cntlu,ddmsolver.m_cntregqr));
//--- Use preconditioned GMRES
rep.m_rmserror=0;
rep.m_maxerror=0;
rep.m_iterationscount=0;
for(yidx=0; yidx<ny; yidx++)
{
if(dotrace)
CAp::Trace(StringFormat("> solving for component %d:\n",yidx));
CAblasF::RSetAllocV(n+nx+1,0.0,y0);
CAblasF::RSetAllocV(n+nx+1,0.0,y1);
CAblasF::RCopyCV(n,yscaled,yidx,y0);
CFbls::FblsGMRESCreate(y0,n,MathMin(m_maxddmits,n),gmressolver);
gmressolver.m_epsres=tol;
gmressolver.m_epsred=m_epsred;
iteridx=0;
while(CFbls::FblsGMRESIteration(gmressolver))
{
if(dotrace)
CAp::Trace(StringFormat(">> DDM iteration %d: %.2E relative residual\n",iteridx,gmressolver.m_reprelres));
CAblasF::RAllocV(n+nx+1,y0);
CAblasF::RAllocV(n+nx+1,y1);
DDMSolverRun1(ddmsolver,gmressolver.m_x,n,nx,sp,bfmatrix,y0,timeddmsolve,timecorrsolve);
timereeval-=(int)(GetTickCount()/10000);
ModelMatrixComputeProduct(bfmatrix,y0,y1);
CAblasF::RGemVX(n,nx+1,1.0,x1t,0,0,1,y0,n,1.0,y1,0);
for(i=0; i<n; i++)
y1.Add(i,lambdavwrk*y0[i]);
timereeval+=(int)(GetTickCount()/10000);
CAblasF::RCopyV(n,y1,gmressolver.m_ax);
rep.m_iterationscount++;
iteridx++;
}
DDMSolverRun1(ddmsolver,gmressolver.m_xs,n,nx,sp,bfmatrix,x1,timeddmsolve,timecorrsolve);
CAblasF::RCopyVC(n+nx+1,x1,c2,yidx);
//--- Compute predictions and errors
//--- NOTE: because dataset preprocessing may reorder and merge points we have
//--- to use raw-to-work mapping in order to be able to compute correct
//--- error metrics.
timereeval-=((int)(GetTickCount()/10000));
ModelMatrixComputeProduct(bfmatrix,x1,y1);
CAblasF::RGemVX(n,nx+1,1.0,x1t,0,0,1,x1,n,1.0,y1,0);
timereeval+=((int)(GetTickCount()/10000));
resnrm=0;
res0nrm=0;
for(i=0; i<n; i++)
{
resnrm+=CMath::Sqr(yscaled.Get(i,yidx)-y1[i]-lambdavwrk*x1[i]);
res0nrm+=CMath::Sqr(yscaled.Get(i,yidx));
}
resnrm=MathSqrt(resnrm);
res0nrm=MathSqrt(res0nrm);
for(i=0; i<nraw; i++)
{
v=yraw.Get(i,yidx)-y1[raw2wrkmap[i]];
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
rep.m_rmserror=rep.m_rmserror+v*v;
}
if(dotrace)
CAp::Trace(StringFormat(">> done with %.2E relative residual,GMRES completion code %d\n",resnrm / CApServ::Coalesce(res0nrm,1),gmressolver.m_retcode));
}
rep.m_rmserror=MathSqrt(rep.m_rmserror/(nraw*ny));
timetotal+=(int)(GetTickCount()/10000);
if(dotrace)
{
CAblasF::RAllocV(n,y0);
orterr=0;
for(k=0; k<ny; k++)
{
CAblasF::RCopyCV(n,c2,k,y0);
for(i=0; i<ortbasissize; i++)
orterr=MathMax(orterr,MathAbs(CAblasF::RDotVR(n,y0,ortbasis,i)));
}
CAp::Trace("=== PRINTING RBF SOLVER RESULTS ====================================================================\n");
CAp::Trace("> errors\n");
CAp::Trace(StringFormat("RMS.err = %.2E\n",rep.m_rmserror));
CAp::Trace(StringFormat("MAX.err = %.2E\n",rep.m_maxerror));
CAp::Trace(StringFormat("ORT.err = %.2E (orthogonality condition)\n",orterr));
CAp::Trace("> DDM iterations\n");
CAp::Trace(StringFormat("ItsCnt = %d\n",rep.m_iterationscount));
CAp::Trace(StringFormat("> total running time is %d ms,including:\n",timetotal));
CAp::Trace(StringFormat(">> model matrix generation %8d ms\n",timedesign));
CAp::Trace(StringFormat(">> ACBF preconditioner construction %8d ms\n",timeprec));
CAp::Trace(StringFormat(">> DDM solver initialization %8d ms\n",timeddminit));
CAp::Trace(StringFormat(">> DDM corrector initialization %8d ms\n",timecorrinit));
CAp::Trace(StringFormat(">> DDM solution phase %8d ms\n",timeddmsolve));
CAp::Trace(StringFormat(">> DDM correction phase %8d ms\n",timecorrsolve));
CAp::Trace(StringFormat(">> DDM solver model reevaluation %8d ms\n",timereeval));
}
s.m_bftype=bftype;
s.m_bfparam=bfparamscaled;
CAblasF::RCopyAllocV(nx,scalewrk,s.m_s);
for(j=0; j<ny; j++)
{
s.m_v.Set(j,nx,c2.Get(n+nx,j));
for(i=0; i<nx; i++)
{
s.m_v.Set(j,i,c2.Get(n+i,j)/scalewrk[i]);
s.m_v.Set(j,nx,s.m_v.Get(j,nx)-c2.Get(n+i,j)*sft[i]/scalewrk[i]);
}
}
CAblasF::RAllocV(n*(nx+ny),s.m_cw);
for(i=0; i<n; i++)
{
for(j=0; j<nx; j++)
s.m_cw.Set(i*(nx+ny)+j,xscaled.Get(i,j)+sft[j]/scalewrk[j]);
for(j=0; j<ny; j++)
s.m_cw.Set(i*(nx+ny)+nx+j,c2.Get(i,j));
}
CAblasF::ICopyAllocV(n,wrk2rawmap,s.m_pointindexes);
s.m_nc=n;
CreateFastEvaluator(s);
//--- Set up debug fields
s.m_dbgregqrusedforddm=ddmsolver.m_cntregqr>0;
//--- Update progress reports
rep.m_terminationtype=1;
progress10000=10000;
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Alloc(CSerializer &s,CRBFV3Model &model)
{
//--- Data
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
CApServ::AllocRealArray(s,model.m_s,model.m_nx);
CApServ::AllocRealMatrix(s,model.m_v,model.m_ny,model.m_nx+1);
CApServ::AllocRealArray(s,model.m_cw,model.m_nc*(model.m_nx+model.m_ny));
CApServ::AllocIntegerArray(s,model.m_pointindexes,model.m_nc);
//--- End of stream, no additional data
s.Alloc_Entry();
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Serialize(CSerializer &s,CRBFV3Model &model)
{
//--- Data
s.Serialize_Int(model.m_nx);
s.Serialize_Int(model.m_ny);
s.Serialize_Int(model.m_bftype);
s.Serialize_Double(model.m_bfparam);
s.Serialize_Int(model.m_nc);
CApServ::SerializeRealArray(s,model.m_s,model.m_nx);
CApServ::SerializeRealMatrix(s,model.m_v,model.m_ny,model.m_nx+1);
CApServ::SerializeRealArray(s,model.m_cw,model.m_nc*(model.m_nx+model.m_ny));
CApServ::SerializeIntegerArray(s,model.m_pointindexes,model.m_nc);
//--- End of stream, no additional data
s.Serialize_Int(117256);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Unserialize(CSerializer &s,CRBFV3Model &model)
{
//--- create variables
int nx=0;
int ny=0;
int bftype=0;
int k=0;
double bfparam=0;
//--- Unserialize primary model parameters, initialize model.
//--- It is necessary to call RBFCreate() because some internal fields
//--- which are NOT unserialized will need initialization.
nx=s.Unserialize_Int();
ny=s.Unserialize_Int();
bftype=s.Unserialize_Int();
bfparam=s.Unserialize_Double();
RBFV3Create(nx,ny,bftype,bfparam,model);
model.m_nc=s.Unserialize_Int();
CApServ::UnserializeRealArray(s,model.m_s);
CApServ::UnserializeRealMatrix(s,model.m_v);
CApServ::UnserializeRealArray(s,model.m_cw);
CApServ::UnserializeIntegerArray(s,model.m_pointindexes);
//--- End of stream, check that no additional data is present
k=s.Unserialize_Int();
//--- check
if(!CAp::Assert(k==117256,__FUNCTION__+": unexpected payload detected in the data stream. Integrity check failed"))
return;
//--- Finalize construction
CreateFastEvaluator(model);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY = 1(scalar function)|
//| and NX = 1 (1-dimensional space). |
//| This function returns 0.0 when: |
//| * the model is not initialized |
//| * NX<>1 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - X - coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBFV3::RBFV3Calc1(CRBFV3Model &s,double x0)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=1)
return(0);
result=s.m_v.Get(0,0)*x0-s.m_v.Get(0,1);
s.m_calcbuf.m_x123.Set(0,x0);
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY = 1(scalar function)|
//| and NX = 2 (2-dimensional space). If you have 3-dimensional |
//| space, use RBFCalc3(). If you have general situation |
//| (NX-dimensional space, NY-dimensional function) you should use |
//| general, less efficient implementation RBFCalc(). |
//| If you want to calculate function values many times, consider |
//| using RBFGridCalc2(), which is far more efficient than many |
//| subsequent calls to RBFCalc2(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0(as defined above) |
//+------------------------------------------------------------------+
double CRBFV3::RBFV3Calc2(CRBFV3Model &s,double x0,double x1)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=2)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2);
if(s.m_nc==0)
return(result);
s.m_calcbuf.m_x123.Set(0,x0);
s.m_calcbuf.m_x123.Set(1,x1);
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model in the given |
//| point. |
//| This function should be used when we have NY = 1(scalar function)|
//| and NX = 3 (3-dimensional space). If you have 2-dimensional s |
//| pace, use RBFCalc2(). If you have general situation |
//| (NX-dimensional space, NY-dimensional function) you should use |
//| general, less efficient implementation RBFCalc(). |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| X2 - third coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0(as defined above) |
//+------------------------------------------------------------------+
double CRBFV3::RBFV3Calc3(CRBFV3Model &s,double x0,double x1,double x2)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=3)
return(0);
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,3);
if(s.m_nc==0)
return(result);
s.m_calcbuf.m_x123.Set(0,x0);
s.m_calcbuf.m_x123.Set(1,x1);
s.m_calcbuf.m_x123.Set(2,x2);
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
result=s.m_calcbuf.m_y123[0];
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point. |
//| Same as RBFCalc(), but does not reallocate Y when in is large |
//| enough to store function values. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3CalcBuf(CRBFV3Model &s,CRowDouble &x,CRowDouble &y)
{
RBFV3TsCalcBuf(s,s.m_calcbuf,x,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point, using external buffer object (internal temporaries of RBF |
//| model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3TsCalcBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
CRowDouble &x,CRowDouble &y)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
double distance0=0;
int colidx=0;
int srcidx=0;
int widx=0;
int curchunk=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
//--- Handle linear term
if(CAp::Len(y)<ny)
y.Resize(ny);
for(int i=0; i<ny; i++)
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
if(s.m_nc==0)
return;
//--- Handle RBF term
//--- check
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2 || s.m_bftype==3,__FUNCTION__+": unsupported basis function type"))
return;
for(int j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
CAblasF::RAllocV(s.m_evaluator.m_chunksize,buf.m_evalbuf.m_funcbuf);
CAblasF::RAllocV(s.m_evaluator.m_chunksize,buf.m_evalbuf.m_wrkbuf);
colidx=0;
srcidx=0;
widx=0;
distance0=1.0E-50;
if(s.m_bftype==1)
{
//--- Kernels that add squared parameter to the squared distance
distance0=CMath::Sqr(s.m_bfparam);
}
while(colidx<s.m_nc)
{
//--- Handle basecase with size at most ChunkSize*ChunkSize
curchunk=MathMin(s.m_evaluator.m_chunksize,s.m_nc-colidx);
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,0);
for(int i=0; i<ny; i++)
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
colidx+=curchunk;
srcidx+=nx;
widx+=ny;
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its derivatives, using external buffer object (internal|
//| temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance of |
//| RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used. |
//| Y, DY - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated when |
//| it is larger than NY. |
//| DY - derivatives, array[NY * NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3TsDiffBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
int i=0;
int j=0;
double smalldist2=0;
bool nograd=false;
int colidx=0;
int srcidx=0;
int widx=0;
int curchunk=0;
int maxchunksize=0;
double distance0=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
//--- Handle linear term
if(CAp::Len(y)<ny)
y.Resize(ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
for(i=0; i<ny; i++)
{
y.Set(i,s.m_v.Get(i,nx));
for(j=0; j<nx; j++)
{
y.Add(i,s.m_v.Get(i,j)*x[j]);
dy.Set(i*nx+j,s.m_v.Get(i,j));
}
}
if(s.m_nc==0)
return;
//--- Rescale X and DY to the internal scaling used by the RBF model
for(j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
for(i=0; i<ny; i++)
for(j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
//--- Prepare information necessary for the detection of the nonexistent gradient
nograd=false;
smalldist2=(CAblasF::RDotV2(nx,buf.m_x)+1.0)*CMath::Sqr(100*CMath::m_machineepsilon);
//--- Handle RBF term
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2 || s.m_bftype==3,__FUNCTION__+": unsupported basis function type"))
return;
if(!CAp::Assert(s.m_bftype!=1 || s.m_bfparam>=0.0,__FUNCTION__+": inconsistent BFType/BFParam"))
return;
maxchunksize=s.m_evaluator.m_chunksize;
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_funcbuf);
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_wrkbuf);
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df1);
CAblasF::RAllocM(nx,maxchunksize,buf.m_evalbuf.m_deltabuf);
CAblasF::RSetAllocV(maxchunksize,1.0E50,buf.m_evalbuf.m_mindist2);
colidx=0;
srcidx=0;
widx=0;
distance0=1.0E-50;
if(s.m_bftype==1)
{
//--- Kernels that add squared parameter to the squared distance
distance0=CMath::Sqr(s.m_bfparam);
}
while(colidx<s.m_nc)
{
//--- Handle basecase with size at most ChunkSize*ChunkSize
curchunk=MathMin(maxchunksize,s.m_nc-colidx);
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,1);
for(j=0; j<nx; j++)
CAblasF::RMergeMulVR(curchunk,buf.m_evalbuf.m_df1,buf.m_evalbuf.m_deltabuf,j);
for(i=0; i<ny; i++)
{
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
for(j=0; j<nx; j++)
dy.Add(i*nx+j,2*CAblasF::RDotRR(curchunk,s.m_wchunked,widx+i,buf.m_evalbuf.m_deltabuf,j));
}
colidx+=curchunk;
srcidx+=nx;
widx+=ny;
}
if(s.m_bftype==1 && s.m_bfparam==0.0)
{
//--- The kernel function is nondifferentiable at nodes, check whether we are close to one of the nodes or not
for(i=0; i<maxchunksize; i++)
nograd=nograd || buf.m_evalbuf.m_mindist2[i]<=smalldist2;
if(nograd)
{
//--- The gradient is undefined at the trial point, flush it to zero
CAblasF::RSetV(ny*nx,0.0,dy);
}
}
//--- Rescale derivatives back
for(i=0; i<ny; i++)
for(j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point and its first and second derivatives, using external buffer|
//| object (internal temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be |
//| used. |
//| Y, DY, D2Y - possibly preallocated arrays |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//| DY - derivatives, array[NY * NX]. DY is not reallocated |
//| when it is larger than NY*NX. |
//| D2Y - second derivatives, array[NY * NX * NX]. D2Y is not|
//| reallocated when it is larger than NY*NX*NX. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3TSHessBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
CRowDouble &x,CRowDouble &y,CRowDouble &dy,
CRowDouble &d2y)
{
//--- create variables
int nx=s.m_nx;
int ny=s.m_ny;
int i=0;
int j=0;
int k=0;
int k0=0;
int k1=0;
bool nearnode=false;
bool nograd=false;
bool nohess=false;
double smalldist2=0;
int colidx=0;
int srcidx=0;
int widx=0;
int curchunk=0;
int maxchunksize=0;
double distance0=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
//--- Handle linear term
if(CAp::Len(y)<ny)
y.Resize(ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
if(CAp::Len(d2y)<ny*nx*nx)
d2y.Resize(ny*nx*nx);
for(i=0; i<ny; i++)
{
y.Set(i,s.m_v.Get(i,nx));
for(j=0; j<nx; j++)
{
y.Add(i,s.m_v.Get(i,j)*x[j]);
dy.Set(i*nx+j,s.m_v.Get(i,j));
}
}
CAblasF::RSetV(ny*nx*nx,0.0,d2y);
if(s.m_nc==0)
return;
//--- Rescale X and DY to the internal scaling used by the RBF model (D2Y is zero,
//--- so it does not need rescaling).
for(j=0; j<nx; j++)
buf.m_x.Set(j,x[j]/s.m_s[j]);
for(i=0; i<ny; i++)
for(j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
//--- Prepare information necessary for the detection of the nonexistent Hessian
nograd=false;
nohess=false;
smalldist2=(CAblasF::RDotV2(nx,buf.m_x)+1.0)*CMath::Sqr(100*CMath::m_machineepsilon);
//--- Handle RBF term
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2,__FUNCTION__+": unsupported basis function type"))
return;
if(!CAp::Assert(s.m_bftype!=1 || s.m_bfparam>=0.0,__FUNCTION__+": inconsistent BFType/BFParam"))
return;
maxchunksize=s.m_evaluator.m_chunksize;
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_funcbuf);
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_wrkbuf);
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df1);
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df2);
CAblasF::RAllocM(nx,maxchunksize,buf.m_evalbuf.m_deltabuf);
CAblasF::RSetAllocV(maxchunksize,1.0E50,buf.m_evalbuf.m_mindist2);
colidx=0;
srcidx=0;
widx=0;
distance0=1.0E-50;
if(s.m_bftype==1)
{
//--- Kernels that add squared parameter to the squared distance
distance0=CMath::Sqr(s.m_bfparam);
}
while(colidx<s.m_nc)
{
//--- Handle basecase with size at most ChunkSize*ChunkSize
curchunk=MathMin(maxchunksize,s.m_nc-colidx);
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,2);
for(i=0; i<ny; i++)
{
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
for(k0=0; k0<nx; k0++)
{
CAblasF::RCopyRV(curchunk,buf.m_evalbuf.m_deltabuf,k0,buf.m_evalbuf.m_wrkbuf);
CAblasF::RMergeMulV(curchunk,buf.m_evalbuf.m_df1,buf.m_evalbuf.m_wrkbuf);
dy.Add(i*nx+k0,2*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_wrkbuf,s.m_wchunked,widx+i));
}
for(k0=0; k0<nx; k0++)
for(k1=0; k1<nx; k1++)
{
CAblasF::RCopyV(curchunk,buf.m_evalbuf.m_df2,buf.m_evalbuf.m_wrkbuf);
CAblasF::RMergeMulRV(curchunk,buf.m_evalbuf.m_deltabuf,k0,buf.m_evalbuf.m_wrkbuf);
CAblasF::RMergeMulRV(curchunk,buf.m_evalbuf.m_deltabuf,k1,buf.m_evalbuf.m_wrkbuf);
d2y.Add(i*nx*nx+k0*nx+k1,4*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_wrkbuf,s.m_wchunked,widx+i));
if(k0==k1)
d2y.Add(i*nx*nx+k0*nx+k1,2*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_df1,s.m_wchunked,widx+i));
}
}
colidx+=curchunk;
srcidx+=nx;
widx+=ny;
}
nearnode=false;
if((s.m_bftype==1 && s.m_bfparam==0.0) || s.m_bftype==2)
{
//--- The kernel function is nondifferentiable at nodes, check whether we are close to one of the nodes or not
for(i=0; i<maxchunksize; i++)
nearnode=nearnode || buf.m_evalbuf.m_mindist2[i]<=smalldist2;
}
nograd=nearnode && (s.m_bftype==1 && s.m_bfparam==0.0);
nohess=nearnode && ((s.m_bftype==1 && s.m_bfparam==0.0) || s.m_bftype==2);
if(nograd)
{
//--- The gradient is undefined at the trial point, flush it to zero
CAblasF::RSetV(ny*nx,0.0,dy);
}
if(nohess)
{
//--- The Hessian is undefined at the trial point, flush it to zero
CAblasF::RSetV(ny*nx*nx,0.0,d2y);
}
//--- Rescale derivatives back
for(i=0; i<ny; i++)
for(j=0; j<nx; j++)
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
for(i=0; i<ny; i++)
for(j=0; j<nx; j++)
for(k=0; k<nx; k++)
d2y.Set(i*nx*nx+j*nx+k,d2y[i*nx*nx+j*nx+k]/(s.m_s[j]*s.m_s[k]));
}
//+------------------------------------------------------------------+
//| This function is used to perform gridded calculation for 2D, 3D |
//| or 4D problems. It accepts parameters X0..X3 and counters N0..N3.|
//| If RBF model has dimensionality less than 4, corresponding arrays|
//| should contain just one element equal to zero, and corresponding |
//| N's should be equal to 1. |
//| NOTE: array Y should be preallocated by caller. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3GridCalcVX(CRBFV3Model &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CRowDouble &x2,
int n2,CRowDouble &x3,int n3,
bool &flagy[],bool sparsey,CRowDouble &y)
{
CRBFV3CalcBuffer bufseed;
CRBFV3CalcBuffer bufpool[];
//--- Perform integrity checks
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(n0>=1 && n1>=1 && n2>=1 && n3>=1,__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=4 || (CAp::Len(x3)>=1 && x3[0]==0.0 && n3==1),__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=3 || (CAp::Len(x2)>=1 && x2[0]==0.0 && n2==1),__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(s.m_nx>=2 || (CAp::Len(x1)>=1 && x1[0]==0.0 && n1==1),__FUNCTION__+": integrity check failed"))
return;
if(!CAp::Assert(!sparsey || CAp::Len(flagy)>=n0*n1*n2*n3,__FUNCTION__+": integrity check failed"))
return;
//--- Prepare shared pool
RBFV3CreateCalcBuffer(s,bufseed);
//--- Call worker function
int simdwidth=8;
int tilescnt=CApServ::IDivUp(n0,simdwidth)*CApServ::IDivUp(n1,simdwidth)*CApServ::IDivUp(n2,simdwidth)*CApServ::IDivUp(n3,simdwidth);
if(!CAp::Assert(ArrayResize(bufpool,tilescnt)>0,__FUNCTION__+": integrity check failed"))
return;
for(int i=0; i<tilescnt; i++)
bufpool[i]=bufseed;
//--- function call
GridCalcRec(s,simdwidth,0,tilescnt,x0,n0,x1,n1,x2,n2,x3,n3,flagy,sparsey,y,bufpool);
}
//+------------------------------------------------------------------+
//| This function "unpacks" RBF model by extracting its coefficients.|
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| NX - dimensionality of argument |
//| NY - dimensionality of the target function |
//| XWR - model information, array[NC, NX + NY + NX + 2]. |
//| One row of the array corresponds to one basis function |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of the function |
//| being modeled |
//| * next NX columns - radii, one per dimension |
//| * next column - basis function type: |
//| * 1 for f = r |
//| * 2 for f = r ^ 2 * ln(r) |
//| * 10 for multiquadric f = sqrt(r ^ 2 + alpha ^ 2) |
//| * next column - basis function parameter: |
//| * alpha, for basis function type 10 |
//| * ignored(zero) for other basis function types |
//| * next column - point index in the original dataset, or |
//| -1 for an artificial node created by the |
//| solver. The algorithm may reorder the |
//| nodes, drop some nodes or add artificial |
//| nodes. Thus, one parsing this column |
//| should expect all these kinds of |
//| alterations in the dataset. |
//| NC - number of the centers |
//| V - polynomial term, array[NY, NX + 1]. One row per one|
//| dimension of the function being modelled. First NX |
//| elements are linear coefficients, V[NX] is equal to|
//| the constant part. |
//+------------------------------------------------------------------+
void CRBFV3::RBFV3Unpack(CRBFV3Model &s,int &nx,int &ny,
CMatrixDouble &xwr,int &nc,CMatrixDouble &v)
{
//--- create variables
int cwwidth=0;
bool recognized=false;
nx=0;
ny=0;
xwr.Resize(0,0);
nc=0;
v.Resize(0,0);
nx=s.m_nx;
ny=s.m_ny;
nc=s.m_nc;
//--- Fill V
v.Resize(s.m_ny,s.m_nx+1);
for(int i=0; i<s.m_ny; i++)
CAblasF::RCopyRR(nx+1,s.m_v,i,v,i);
//--- Fill XWR
if(nc>0)
{
cwwidth=nx+ny;
xwr.Resize(nc,nx+ny+nx+3);
for(int i=0; i<nc; i++)
{
//--- Output centers (in the original variable scaling), weights and radii
for(int j=0; j<nx; j++)
xwr.Set(i,j,s.m_cw[i*cwwidth+j]*s.m_s[j]);
for(int j=0; j<ny; j++)
xwr.Set(i,nx+j,s.m_cw[i*cwwidth+nx+j]);
for(int j=0; j<nx; j++)
xwr.Set(i,nx+ny+j,s.m_s[j]);
//--- Recognize specific basis function used and perform post-processing
recognized=false;
if(s.m_bftype==1 && s.m_bfparam==0.0)
{
//--- Biharmonic kernel f=r
//--- Weights are multiplied by -1 because actually it is f=-r (the latter
//--- is conditionally positive definite basis function, and the former is
//--- how it is known to most users)
xwr.Set(i,nx+ny+nx,1);
xwr.Set(i,nx+ny+nx+1,0.0);
for(int j=0; j<ny; j++)
xwr.Mul(i,nx+j,(-1.0));
recognized=true;
}
if(s.m_bftype==1 && (double)(s.m_bfparam)>0.0)
{
//--- Multiquadric f=sqrt(r^2+alpha^2)
//--- Weights are multiplied by -1 because actually it is f=-sqrt(r^2+alpha^2)
//--- (the latter is conditionally positive definite basis function, and the
//--- former is how it is known to most users)
xwr.Set(i,nx+ny+nx,10);
xwr.Set(i,nx+ny+nx+1,s.m_bfparam);
for(int j=0; j<ny; j++)
xwr.Mul(i,nx+j,(-1.0));
recognized=true;
}
if(s.m_bftype==2)
{
//--- Thin plate spline f=r^2*ln(r)
xwr.Set(i,nx+ny+nx,2);
xwr.Set(i,nx+ny+nx+1,0);
recognized=true;
}
//--- check
if(!CAp::Assert(recognized,__FUNCTION__+": integrity check 5342 failed"))
return;
//--- Output indexes
xwr.Set(i,nx+ny+nx+2,s.m_pointindexes[i]);
}
}
}
//+------------------------------------------------------------------+
//| Creates fast evaluation structures after initialization of the |
//| model |
//+------------------------------------------------------------------+
void CRBFV3::CreateFastEvaluator(CRBFV3Model &model)
{
//--- create variables
int offs=0;
int ontheflystorage=0;
int nchunks=0;
int srcoffs=0;
int dstoffs=0;
int curlen=0;
CMatrixDouble xx;
//--- Setup model matrix structure
ontheflystorage=1;
CAblasF::RAllocM(model.m_nc,model.m_nx,xx);
offs=0;
for(int i=0; i<model.m_nc; i++)
{
for(int j=0; j<model.m_nx; j++)
xx.Set(i,j,model.m_cw[offs+j]);
offs+=model.m_nx+model.m_ny;
}
ModelMatrixInit(xx,model.m_nc,model.m_nx,model.m_bftype,model.m_bfparam,ontheflystorage,model.m_evaluator);
//--- Store model coefficients in the efficient chunked format (chunk size is aligned with that
//--- of the Model.Evaluator).
//--- check
if(!CAp::Assert(model.m_evaluator.m_chunksize>=1,__FUNCTION__+": integrity check 3535 failed"))
return;
nchunks=CApServ::IDivUp(model.m_nc,model.m_evaluator.m_chunksize);
CAblasF::RSetAllocM(nchunks*model.m_ny,model.m_evaluator.m_chunksize,0.0,model.m_wchunked);
srcoffs=0;
dstoffs=0;
while(srcoffs<model.m_nc)
{
curlen=MathMin(model.m_evaluator.m_chunksize,model.m_nc-srcoffs);
for(int i=0; i<curlen; i++)
for(int j=0; j<model.m_ny; j++)
model.m_wchunked.Set(dstoffs+j,i,model.m_cw[(srcoffs+i)*(model.m_nx+model.m_ny)+model.m_nx+j]);
srcoffs+=curlen;
dstoffs+=model.m_ny;
}
}
//+------------------------------------------------------------------+
//| Recursive worker function for gridded calculation |
//+------------------------------------------------------------------+
void CRBFV3::GridCalcRec(CRBFV3Model &s,int simdwidth,int tileidx0,
int tileidx1,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,
CRowDouble &x2,int n2,
CRowDouble &x3,int n3,
bool &flagy[],bool sparsey,
CRowDouble &y,CRBFV3CalcBuffer &calcpool[])
{
//--- create variables
int ny=s.m_ny;
int dstoffs=0;
double problemcost=0;
int tileidxm=0;
int k=0;
int k0=0;
int k1=0;
int k2=0;
int r0a=0;
int r0b=0;
int r1a=0;
int r1b=0;
int r2a=0;
int r2b=0;
CRBFV3CalcBuffer buf;
for(int idx=tileidx0; idx<tileidx1; idx++)
{
//--- Handle basecase
k=idx;
k0=k%CApServ::IDivUp(n0,simdwidth);
k=k/CApServ::IDivUp(n0,simdwidth);
k1=k%CApServ::IDivUp(n1,simdwidth);
k=k/CApServ::IDivUp(n1,simdwidth);
k2=k%CApServ::IDivUp(n2,simdwidth);
k=k/CApServ::IDivUp(n2,simdwidth);
k=k/CApServ::IDivUp(n3,simdwidth);
//--- check
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check 7350 failed"))
return;
r0a=k0*simdwidth;
r0b=MathMin(r0a+simdwidth,n0);
r1a=k1*simdwidth;
r1b=MathMin(r1a+simdwidth,n1);
r2a=k2*simdwidth;
r2b=MathMin(r2a+simdwidth,n2);
buf=calcpool[idx];
for(int i=r0a; i<r0b; i++)
{
for(int j=r1a; j<r1b; j++)
{
for(k=r2a; k<r2b; k++)
{
dstoffs=i+j*n0+k*n0*n1;
if(sparsey && !flagy[dstoffs])
{
for(int l=0; l<ny; l++)
y.Set(l+ny*dstoffs,0);
continue;
}
buf.m_xg.Set(0,x0[i]);
buf.m_xg.Set(1,x1[j]);
buf.m_xg.Set(2,x2[k]);
RBFV3TsCalcBuf(s,buf,buf.m_xg,buf.m_yg);
for(int l=0; l<ny; l++)
y.Set(l+ny*dstoffs,buf.m_yg[l]);
}
}
}
calcpool[idx]=buf;
}
}
//+------------------------------------------------------------------+
//| This function fills RBF model by zeros, also cleans up debug |
//| fields. |
//+------------------------------------------------------------------+
void CRBFV3::ZeroFill(CRBFV3Model &s,int nx,int ny)
{
s.m_bftype=0;
s.m_bfparam=0;
s.m_nc=0;
CAblasF::RSetAllocV(nx,1.0,s.m_s);
CAblasF::RSetAllocM(ny,nx+1,0.0,s.m_v);
}
//+------------------------------------------------------------------+
//| Reallocates calcBuf if necessary, reuses previously allocated |
//| space if possible. |
//+------------------------------------------------------------------+
void CRBFV3::AllocateCalcBuffer(CRBFV3Model &s,CRBFV3CalcBuffer &buf)
{
if(CAp::Len(buf.m_x)<s.m_nx)
buf.m_x.Resize(s.m_nx);
if(CAp::Len(buf.m_x123)<s.m_nx)
buf.m_x123.Resize(s.m_nx);
if(CAp::Len(buf.m_y123)<s.m_ny)
buf.m_y123.Resize(s.m_ny);
if(CAp::Len(buf.m_xg)<4)
buf.m_xg.Resize(4);
if(CAp::Len(buf.m_yg)<s.m_ny)
buf.m_yg.Resize(s.m_ny);
}
//+------------------------------------------------------------------+
//| Recursive function that merges points, used by |
//| PreprocessDataset() |
//+------------------------------------------------------------------+
void CRBFV3::PreprocessDatasetRec(CMatrixDouble &xbuf,
CMatrixDouble &ybuf,
CRowInt &initidx,
int wrk0,int wrk1,int nx,
int ny,double mergetol,
CRowDouble &tmpboxmin,
CRowDouble &tmpboxmax,
CMatrixDouble &xout,
CMatrixDouble &yout,
CRowInt &raw2wrkmap,
CRowInt &wrk2rawmap,
int &nout)
{
//--- create variables
int k0=0;
int k1=0;
int largestdim=0;
double splitval=0;
if(wrk1<=wrk0)
return;
//--- Analyze current working set
CAblasF::RAllocV(nx,tmpboxmin);
CAblasF::RAllocV(nx,tmpboxmax);
CAblasF::RCopyRV(nx,xbuf,wrk0,tmpboxmin);
CAblasF::RCopyRV(nx,xbuf,wrk0,tmpboxmax);
for(int i=wrk0+1; i<wrk1; i++)
{
for(int j=0; j<nx; j++)
{
tmpboxmin.Set(j,MathMin(tmpboxmin[j],xbuf.Get(i,j)));
tmpboxmax.Set(j,MathMax(tmpboxmax[j],xbuf.Get(i,j)));
}
}
for(int j=1; j<nx; j++)
{
if((tmpboxmax[j]-tmpboxmin[j])>(tmpboxmax[largestdim]-tmpboxmin[largestdim]))
largestdim=j;
}
//--- Handle basecase or perform recursive split
if(wrk1-wrk0==1 || (tmpboxmax[largestdim]-tmpboxmin[largestdim])<(mergetol*MathMax(CAblasF::RMaxAbsV(nx,tmpboxmax),MathMax(CAblasF::RMaxAbsV(nx,tmpboxmin),1))))
{
//--- Merge all points, output
CAblasF::RSetR(nx,0.0,xout,nout);
CAblasF::RSetR(ny,0.0,yout,nout);
for(int i=wrk0; i<wrk1; i++)
{
CAblasF::RAddRR(nx,1.0/(double)(wrk1-wrk0),xbuf,i,xout,nout);
CAblasF::RAddRR(ny,1.0/(double)(wrk1-wrk0),ybuf,i,yout,nout);
raw2wrkmap.Set(initidx[i],nout);
}
wrk2rawmap.Set(nout,initidx[wrk0]);
nout++;
}
else
{
//--- Perform recursive split along largest axis
splitval=0.5*(tmpboxmax[largestdim]+tmpboxmin[largestdim]);
k0=wrk0;
k1=wrk1-1;
while(k0<=k1)
{
if(xbuf.Get(k0,largestdim)<=splitval)
{
k0++;
continue;
}
if(xbuf.Get(k1,largestdim)>splitval)
{
k1--;
continue;
}
CApServ::SwapRows(xbuf,k0,k1,nx);
CApServ::SwapRows(ybuf,k0,k1,ny);
CApServ::SwapElementsI(initidx,k0,k1);
k0++;
k1--;
}
//--- check
if(!CAp::Assert(k0>wrk0 && k1<wrk1-1,__FUNCTION__+": integrity check 5843 in the recursive subdivision code failed"))
return;
if(!CAp::Assert(k0==k1+1,__FUNCTION__+": integrity check 5364 in the recursive subdivision code failed"))
return;
PreprocessDatasetRec(xbuf,ybuf,initidx,wrk0,k0,nx,ny,mergetol,tmpboxmin,tmpboxmax,xout,yout,raw2wrkmap,wrk2rawmap,nout);
PreprocessDatasetRec(xbuf,ybuf,initidx,k0,wrk1,nx,ny,mergetol,tmpboxmin,tmpboxmax,xout,yout,raw2wrkmap,wrk2rawmap,nout);
}
}
//+------------------------------------------------------------------+
//| This function preprocesses dataset by: |
//| * merging non - distinct points |
//| * centering points |
//| * applying user scale to X - values |
//| * performing additional scaling of X - values |
//| * normalizing Y - values |
//| INPUT PARAMETERS: |
//| XRaw - array[NRaw, NX], variable values |
//| YRaw - array[NRaw, NY], target values |
//| XScaleRaw - array[NX], user scales |
//| NRaw, NX, NY - metrics; N > 0, NX > 0, NY > 0 |
//| BFType - basis function type |
//| BFParamRaw - initial value for basis function paramerer (before|
//| applying additional rescaling AddXRescaleAplied) |
//| LambdaVRaw - smoothing coefficient, as specified by user |
//| OUTPUT PARAMETERS: |
//| XWrk - array[NWrk, NX], processed points, |
//| XWrk = (XRaw - XShift) / XScaleWrk |
//| YWrk - array[NWrk, NY], targets, scaled by dividing by |
//| YScale |
//| PointIndexes - array[NWrk], point indexes in the original |
//| dataset |
//| NWrk - number of points after preprocessing, |
//| 0 < NWrk <= NRaw |
//| XScaleWrk - array[NX], |
//| XScaleWrk[] = XScaleRaw[] * AddXRescaleAplied |
//| XShift - array[NX], centering coefficients |
//| YScale - common scaling for targets |
//| BFParamWrk - BFParamRaw / AddXRescaleAplied |
//| LambdaVWrk - LambdaV after dataset scaling, automatically |
//| adjusted for dataset spread |
//| AddXRescaleAplied - additional scaling applied after user |
//| scaling |
//+------------------------------------------------------------------+
void CRBFV3::PreprocessDataSet(CMatrixDouble &XRaw,double mergetol,
CMatrixDouble &YRaw,CRowDouble &XScaleRaw,
int nraw,int nx,int ny,int bftype,
double bfparamraw,double lambdavraw,
CMatrixDouble &xwrk,CMatrixDouble &ywrk,
CRowInt &raw2wrkmap,CRowInt &wrk2rawmap,
int &nwrk,CRowDouble &xscalewrk,
CRowDouble &xshift,double &bfparamwrk,
double &lambdavwrk,double &addxrescaleaplied)
{
//--- create variables
double diag2=0;
double v=0;
CMatrixDouble xbuf;
CMatrixDouble ybuf;
CRowDouble tmp0;
CRowDouble tmp1;
CRowDouble boxmin;
CRowDouble boxmax;
CRowInt initidx;
CMatrixDouble xraw=XRaw;
CMatrixDouble yraw=YRaw;
CRowDouble xscaleraw=XScaleRaw;
xwrk.Resize(0,0);
ywrk.Resize(0,0);
raw2wrkmap.Resize(0);
wrk2rawmap.Resize(0);
nwrk=0;
xscalewrk.Resize(0);
xshift.Resize(0);
bfparamwrk=0;
lambdavwrk=0;
addxrescaleaplied=0;
//--- check
if(!CAp::Assert(nraw>=1,__FUNCTION__+": integrity check 7295 failed"))
return;
//--- Scale dataset:
//--- * first, scale it according to user-supplied scale
//--- * second, analyze original dataset and rescale it one more time (same scaling across
//--- all dimensions) so it has zero mean and unit deviation
//--- As a result, user-supplied scaling handles dimensionality issues and our additional
//--- scaling normalizes data.
//--- After this block we have NRaw-sized dataset in XWrk/YWrk
CAblasF::RCopyAllocV(nx,xscaleraw,xscalewrk);
CAblasF::RSetAllocV(nx,0.0,xshift);
CAblasF::RAllocM(nraw,nx,xwrk);
for(int i=0; i<nraw; i++)
{
for(int j=0; j<nx; j++)
{
xwrk.Set(i,j,xraw.Get(i,j)/xscalewrk[j]);
xshift.Set(j,xshift[j]+xwrk.Get(i,j));
}
}
CAblasF::RMulV(nx,1.0/nraw,xshift);
v=0;
for(int i=0; i<=nraw-1; i++)
for(int j=0; j<nx; j++)
v+=CMath::Sqr(xwrk.Get(i,j)-xshift[j]);
addxrescaleaplied=MathSqrt((v+MathSqrt(CMath::m_machineepsilon))/(nraw*nx));
bfparamwrk=bfparamraw;
if(bftype==1)
{
//--- Basis function parameter needs rescaling
if(bfparamraw<0.0)
bfparamwrk=AutoDetectScaleParameter(xwrk,nraw,nx)*-bfparamraw/addxrescaleaplied;
else
bfparamwrk/=addxrescaleaplied;
}
else
{
if(bftype==2)
{
//--- Thin plate splines need special scaling; no params to rescale
addxrescaleaplied*=m_polyharmonic2scale;
}
else
{
CAp::Assert(false,__FUNCTION__+": integrity check 0632 failed");
return;
}
}
CAblasF::RMulV(nx,addxrescaleaplied,xscalewrk);
for(int i=0; i<nraw; i++)
for(int j=0; j<nx; j++)
xwrk.Set(i,j,(xraw.Get(i,j)-xshift[j])/xscalewrk[j]);
CAblasF::RCopyAllocM(nraw,ny,yraw,ywrk);
//--- Merge nondistinct points
CAblasF::IAllocV(nraw,initidx);
for(int i=0; i<nraw; i++)
initidx.Set(i,i);
CAblasF::RCopyAllocM(nraw,nx,xwrk,xbuf);
CAblasF::RCopyAllocM(nraw,ny,ywrk,ybuf);
CAblasF::IAllocV(nraw,raw2wrkmap);
CAblasF::IAllocV(nraw,wrk2rawmap);
nwrk=0;
PreprocessDatasetRec(xbuf,ybuf,initidx,0,nraw,nx,ny,mergetol,tmp0,tmp1,xwrk,ywrk,raw2wrkmap,wrk2rawmap,nwrk);
//--- Compute LambdaV:
//--- * compute bounding box
//--- * compute DIAG2 = squared diagonal of the box
//--- * set LambdaVWrk = LambdaVRaw times upper bound of the basis function value
CAblasF::RAllocV(nx,boxmin);
CAblasF::RAllocV(nx,boxmax);
CAblasF::RCopyRV(nx,xwrk,0,boxmin);
CAblasF::RCopyRV(nx,xwrk,0,boxmax);
for(int i=1; i<nwrk; i++)
{
CAblasF::RMergeMinRV(nx,xwrk,i,boxmin);
CAblasF::RMergeMaxRV(nx,xwrk,i,boxmax);
}
diag2=0;
for(int i=0; i<nx; i++)
diag2+=CMath::Sqr(boxmax[i]-boxmin[i]);
diag2=MathMax(diag2,1);
if(bftype==1)
lambdavwrk=lambdavraw*MathSqrt(diag2+bfparamwrk*bfparamwrk);
else
{
if(bftype==2)
lambdavwrk=lambdavraw*diag2*MathMax(MathAbs(0.5*MathLog(diag2)),1.0);
else
{
lambdavwrk=lambdavraw;
CAp::Assert(false,__FUNCTION__+": integrity check 7232 failed");
return;
}
}
lambdavwrk/=CMath::Sqr(addxrescaleaplied);
}
//+------------------------------------------------------------------+
//| This function selects NSpec global nodes for approximate cardinal|
//| basis functions. |
//| This function has O(N*NSpec) running time and O(N) memory |
//| requirements. |
//| Each approximate cardinal basis function is a combination of |
//| several local nodes (ones nearby to the center) and several |
//| global nodes(ones scattered over entire dataset span). |
//+------------------------------------------------------------------+
void CRBFV3::SelectGlobalNodes(CMatrixDouble &xx,int n,int nx,
CRowInt &existingnodes,int nexisting,
int nspec,CRowInt &nodes,
int &nchosen,double &maxdist)
{
//--- create variables
int k=0;
bool busy[];
double v=0;
double vv=0;
CRowDouble d2;
CRowDouble x;
nchosen=0;
maxdist=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 6429 failed"))
return;
if(!CAp::Assert(nexisting>=0,__FUNCTION__+": integrity check 6412 failed"))
return;
if(!CAp::Assert(nspec>=1,__FUNCTION__+": integrity check 6430 failed"))
return;
nspec=MathMin(nspec,n);
CAblasF::RSetAllocV(n,1.0E50,d2);
CAblasF::RSetAllocV(nx,0.0,x);
CAblasF::BSetAllocV(n,false,busy);
if(nexisting==0)
{
//--- No initial grid is provided, start distance evaluation from the data center
for(int i=0; i<n; i++)
CAblasF::RCopyRV(nx,xx,i,x);
CAblasF::RMulV(nx,1.0/(double)n,x);
}
else
{
CAp::Assert(false,__FUNCTION__+": NExisting<>0");
return;
}
CAblasF::IAllocV(nspec,nodes);
nchosen=0;
maxdist=CMath::m_maxrealnumber;
while(nchosen<nspec)
{
//--- Update distances using last added point stored in X.
for(int j=0; j<n; j++)
{
v=0;
for(k=0; k<nx; k++)
{
vv=x[k]-xx.Get(j,k);
v=v+vv*vv;
}
d2.Set(j,MathMin(d2[j],v));
}
//--- Select point with largest distance, add
k=0;
for(int j=0; j<n; j++)
{
if(d2[j]>d2[k] && !busy[j])
k=j;
}
if(busy[k])
break;
maxdist=MathMin(maxdist,d2[k]);
nodes.Set(nchosen,k);
busy[k]=true;
CAblasF::RCopyRV(nx,xx,k,x);
nchosen++;
}
maxdist=MathSqrt(maxdist);
//--- check
if(!CAp::Assert(nchosen>=1 || nexisting>0,__FUNCTION__+": integrity check 6431 failed"))
return;
}
//+------------------------------------------------------------------+
//| This function builds simplified tagged KD-tree: it assigns a tag |
//| (index in the dataset) to each point, then drops most points |
//| (leaving approximately 1 / ReduceFactor of the entire dataset) |
//| trying to spread residual points uniformly, and then constructs |
//| KD-tree. |
//| It ensures that at least min(N, MinSize) points is retained. |
//+------------------------------------------------------------------+
void CRBFV3::BuildSimplifiedKDTree(CMatrixDouble &xx,int n,int nx,
int reducefactor,int minsize,
CKDTree &kdt)
{
//--- create variables
CMatrixDouble xs;
CRowInt idx;
CHighQualityRandState rs;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
return;
if(!CAp::Assert(reducefactor>=1,__FUNCTION__+": ReduceFactor<1"))
return;
if(!CAp::Assert(minsize>=0,__FUNCTION__+": ReduceFactor<1"))
return;
CHighQualityRand::HQRndSeed(7674,45775,rs);
int ns=MathMax((int)MathRound((double)n/(double)reducefactor),MathMax(minsize,1));
ns=MathMin(ns,n);
CAblasF::IAllocV(n,idx);
CAblasF::RAllocM(ns,nx,xs);
for(int i=0; i<n; i++)
idx.Set(i,i);
for(int i=0; i<=ns-1; i++)
{
int j=i+CHighQualityRand::HQRndUniformI(rs,n-i);
int k=idx[i];
idx.Set(i,idx[j]);
idx.Set(j,k);
CAblasF::RCopyRR(nx,xx,idx[i],xs,i);
}
CNearestNeighbor::KDTreeBuildTagged(xs,idx,ns,nx,0,2,kdt);
}
//+------------------------------------------------------------------+
//| Compute design matrices for the target - scatter preconditioner |
//+------------------------------------------------------------------+
void CRBFV3::ComputeTargetScatterDesignMatrices(CMatrixDouble &xx,
int ntotal,
int nx,
int functype,
double funcparam,
CRowInt &workingnodes,
int nwrk,
CRowInt &scatternodes,
int nscatter,
CMatrixDouble &atwrk,
CMatrixDouble &atsctr)
{
//--- create variables
int i=0;
int j=0;
int k=0;
double v=0;
double vv=0;
int ni=0;
int nj=0;
double alpha2=0;
//--- Compute working set and scatter set design matrices ATWrk and ATSctr
//--- ATWrk is a (NWrk+NX+1)*NWrk matrix whose entries a[i,j] store:
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[wrj[j]])
//--- * for NWrk<=I<NWrk+NX X[wrk[j]], coordinate #(i-NWrk)
//--- * for I=NWrk+NX 1.0
//--- ATSctr is a (NWrk+NX+1)*NScatter matrix whose entries a[i,j] store:
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[scatter[j]])
//--- * for NWrk<=I<NWrk+NX X[scatter[j]], coordinate #(i-NWrk)
//--- * for I=NWrk+NX 1.0
//--- check
if(!CAp::Assert(functype==1 || functype==2 || functype==3,__FUNCTION__+": unexpected basis function type"))
return;
alpha2=funcparam*funcparam;
CAblasF::RAllocM(nwrk+nx+1,nwrk,atwrk);
for(i=0; i<nwrk; i++)
{
ni=workingnodes[i];
for(j=i; j<nwrk; j++)
{
nj=workingnodes[j];
v=0;
for(k=0; k<nx; k++)
{
vv=xx.Get(ni,k)-xx.Get(nj,k);
v=v+vv*vv;
}
switch(functype)
{
case 1:
v=-MathSqrt(v+alpha2);
break;
case 2:
if(v!=0)
v*=0.5*MathLog(v);
else
v=0;
break;
case 3:
v*=MathSqrt(v);
break;
}
atwrk.Set(i,j,v);
atwrk.Set(j,i,v);
}
}
for(j=0; j<nwrk; j++)
{
nj=workingnodes[j];
for(i=0; i<nx; i++)
atwrk.Set(nwrk+i,j,xx.Get(nj,i));
}
for(j=0; j<nwrk; j++)
atwrk.Set(nwrk+nx,j,1.0);
if(nscatter>0)
{
//--- We have scattered points too
CAblasF::RAllocM(nwrk+nx+1,nscatter,atsctr);
for(i=0; i<nwrk; i++)
{
ni=workingnodes[i];
for(j=0; j<nscatter; j++)
{
nj=scatternodes[j];
v=0;
for(k=0; k<nx; k++)
{
vv=xx.Get(ni,k)-xx.Get(nj,k);
v=v+vv*vv;
}
switch(functype)
{
case 1:
v=-MathSqrt(v+alpha2);
break;
case 2:
if(v!=0)
v*=0.5*MathLog(v);
else
v=0;
break;
case 3:
v*=MathSqrt(v);
break;
}
atsctr.Set(i,j,v);
}
}
for(j=0; j<nscatter; j++)
{
nj=scatternodes[j];
CAblasF::RCopyRR(nx,xx,nj,atsctr,nwrk+i);
}
for(j=0; j<nscatter; j++)
atsctr.Set(nwrk+nx,j,1.0);
}
}
//+------------------------------------------------------------------+
//| ACBF preconditioner generation basecase. |
//| PARAMETERS: |
//| Builder - ACBF builder object |
//| Wrk0, Wrk1 - elements [Wrk0...Wrk1 - 1] of Builder.WrkIdx[] |
//| array store row indexes of XX that are processed|
//| OUTPUT: |
//| Builder.OutputPool is updated with new chunks |
//+------------------------------------------------------------------+
void CRBFV3::ComputeACBFPreconditionerBasecase(CACBFBuilder &builder,
CACBFBuffer &buf,
int wrk0,
int wrk1)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int widx=0;
int targetidx=0;
int nx=0;
int nglobal=0;
int nlocal=0;
int ncorrection=0;
int ncenters=0;
int nchosen=0;
int ncoeff=0;
int batchsize=0;
int nk=0;
int nq=0;
CRowDouble x;
CRowDouble batchcenter;
CRowInt idummy;
double localrad=0;
double currentrad=0;
double reg=0;
double v=0;
double vv=0;
double mx=0;
double maxdist2=0;
int ortbasissize=0;
CRowInt ortbasismap;
CACBFChunk precchunk;
int expansionscount=0;
CMatrixDouble dbgb;
double dbgerrnodes=0;
double dbgerrort=0;
double dbgcondq=0;
double dbgmaxc=0;
if(wrk1<=wrk0)
return;
nx=builder.m_nx;
nglobal=builder.m_nglobal;
nlocal=builder.m_nlocal;
ncorrection=builder.m_ncorrection;
reg=MathSqrt(CMath::m_machineepsilon);
CAblasF::RAllocV(nx,x);
expansionscount=0;
//--- First, select a batch of central points and compute batch center
batchsize=wrk1-wrk0;
CAblasF::IAllocV(batchsize,buf.m_currentnodes);
CAblasF::RSetAllocV(nx,0.0,batchcenter);
for(i=0; i<batchsize; i++)
{
targetidx=builder.m_wrkidx[wrk0+i];
buf.m_currentnodes.Set(i,targetidx);
buf.m_bflags[targetidx]=true;
CAblasF::RAddRV(nx,1.0/(double)batchsize,builder.m_xx,builder.m_wrkidx[wrk0+i],batchcenter);
}
ncenters=batchsize;
//--- Then, add a hull of nearest neighbors and compute its radius
localrad=0;
for(widx=0; widx<batchsize; widx++)
{
//--- Select immediate neighbors
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0+widx],x);
nq=CNearestNeighbor::KDTreeTsQueryKNN(builder.m_kdt,buf.m_kdtbuf,x,nlocal,true);
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt,buf.m_kdtbuf,buf.m_neighbors);
CNearestNeighbor::KDTreeTsQueryResultsDistances(builder.m_kdt,buf.m_kdtbuf,buf.m_d);
for(k=0; k<nq; k++)
{
nk=buf.m_neighbors[k];
if(!buf.m_bflags[nk])
{
buf.m_bflags[nk]=true;
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
buf.m_currentnodes.Set(ncenters,nk);
ncenters++;
v=0;
for(j=0; j<nx; j++)
v+=(builder.m_xx.Get(nk,j)-batchcenter[j])*(builder.m_xx.Get(nk,j)-batchcenter[j]);
localrad=MathMax(localrad,v);
}
}
}
localrad=MathSqrt(localrad);
currentrad=localrad;
//--- Add global grid
if(nglobal>0)
{
for(k=0; k<nglobal; k++)
{
nk=builder.m_globalgrid[k];
if(!buf.m_bflags[nk])
{
buf.m_bflags[nk]=true;
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
buf.m_currentnodes.Set(ncenters,nk);
ncenters++;
}
}
}
//--- Add local correction grid: select more distant neighbors
while(currentrad>0.0 && currentrad<builder.m_roughdatasetdiameter)
{
//--- Select neighbors within CurrentRad*Builder.CorrectorGrowth
if(expansionscount==0)
{
//--- First expansion, use simplified kd-tree #1
nq=CNearestNeighbor::KDTreeTsQueryRNN(builder.m_kdt1,buf.m_kdt1buf,batchcenter,currentrad*builder.m_correctorgrowth,true);
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt1,buf.m_kdt1buf,buf.m_neighbors);
}
else
{
//--- Subsequent expansions, use simplified kd-tree #2
nq=CNearestNeighbor::KDTreeTsQueryRNN(builder.m_kdt2,buf.m_kdt2buf,batchcenter,currentrad*builder.m_correctorgrowth,true);
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt2,buf.m_kdt2buf,buf.m_neighbors);
}
//--- Compute a grid of well-separated nodes using neighbors
CAblasF::RAllocM(nq,nx,buf.m_xq);
for(k=0; k<nq; k++)
{
nk=buf.m_neighbors[k];
for(j=0; j<nx; j++)
buf.m_xq.Set(k,j,builder.m_xx.Get(nk,j));
}
SelectGlobalNodes(buf.m_xq,nq,nx,idummy,0,ncorrection,buf.m_chosenneighbors,nchosen,maxdist2);
//--- Select neighbrs that are NOT within CurrentRad from the batch center
//--- and that are NOT already chosen.
for(k=0; k<nchosen; k++)
{
nk=buf.m_neighbors[buf.m_chosenneighbors[k]];
v=CAblasF::RDotVR(nx,batchcenter,builder.m_xx,nk);
v=MathSqrt(v);
if(!buf.m_bflags[nk] && v>currentrad)
{
buf.m_bflags[nk]=true;
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
buf.m_currentnodes.Set(ncenters,nk);
ncenters++;
}
}
//--- Update radius and debug counters
currentrad*=builder.m_correctorgrowth;
expansionscount++;
}
//--- Clean up bFlags[]
for(k=0; k<ncenters; k++)
buf.m_bflags[buf.m_currentnodes[k]]=false;
//--- Compute working set and scatter set design matrices ATWrk and ATSctr
//--- ATWrk is a (NWrk+NX+1)*NWrk matrix whose entries a[i,j] store:
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[wrj[j]])
//--- * for NWrk<=I<NWrk+NX X[wrk[j]], coordinate #(i-NWrk)
//--- * for I=NWrk+NX 1.0
//--- ATSctr is a (NWrk+NX+1)*NScatter matrix whose entries a[i,j] store:
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[scatter[j]])
//--- * for NWrk<=I<NWrk+NX X[scatter[j]], coordinate #(i-NWrk)
//--- * for I=NWrk+NX 1.0
ComputeTargetScatterDesignMatrices(builder.m_xx,builder.m_ntotal,nx,builder.m_functype,builder.m_funcparam,buf.m_currentnodes,ncenters,buf.m_currentnodes,0,buf.m_atwrk,buf.m_atwrk);
//--- Prepare and solve linear system, coefficients are stored in rows of Buf.B
//--- Depending on whether the basis is conditionally positive definite (given current polynomial term type),
//--- we either:
//--- * use generic QR solver to solve the linear system (when the basis is not CPD)
//--- * use specialized CPD solver that is several times faster and is more accurate
if(builder.m_aterm!=1 || !IsCPDFunction(builder.m_functype,builder.m_aterm))
{
//--- check
if(!CAp::Assert(builder.m_lambdav>=0.0,__FUNCTION__+": integrity check 8363 failed"))
return;
if(!CAp::Assert(builder.m_aterm==1 || builder.m_aterm==2 || builder.m_aterm==3,__FUNCTION__+": integrity check 8364 failed"))
return;
//--- Basis function has no conditional positive definiteness guarantees (given the linear term type).
//--- Solve using QR decomposition.
ncoeff=ncenters+nx+1;
CAblasF::RSetAllocM(ncoeff,ncoeff+batchsize,0.0,buf.m_q);
for(i=0; i<ncenters; i++)
{
CAblasF::RCopyRR(ncenters,buf.m_atwrk,i,buf.m_q,i);
buf.m_q.Add(i,i,builder.m_lambdav);
}
switch(builder.m_aterm)
{
case 1:
//--- Linear term is used
for(i=0; i<=nx; i++)
for(j=0; j<ncenters; j++)
{
buf.m_q.Set(ncenters+i,j,buf.m_atwrk.Get(ncenters+i,j));
buf.m_q.Set(j,ncenters+i,buf.m_atwrk.Get(ncenters+i,j));
}
break;
case 2:
//--- Constant term is used
for(i=0; i<nx; i++)
buf.m_q.Set(ncenters+i,ncenters+i,1.0);
for(j=0; j<ncenters; j++)
{
buf.m_q.Set(ncenters+nx,j,1.0);
buf.m_q.Set(j,ncenters+nx,1.0);
}
break;
case 3:
//--- Zero term is used
for(i=0; i<=nx; i++)
buf.m_q.Set(ncenters+i,ncenters+i,1.0);
break;
}
for(i=0; i<batchsize; i++)
buf.m_q.Set(i,ncoeff+i,1.0);
mx=1.0;
for(i=0; i<ncoeff; i++)
{
for(j=i; j<ncoeff; j++)
mx=MathMax(mx,MathAbs(buf.m_q.Get(i,j)));
}
for(j=0; j<ncoeff; j++)
buf.m_q.Add(j,j,reg*mx*CApServ::PosSign(buf.m_q.Get(j,j)));
COrtFac::RMatrixQR(buf.m_q,ncoeff,ncoeff+batchsize,buf.m_tau);
CAblasF::RAllocM(batchsize,ncoeff,buf.m_b);
CAblas::RMatrixTranspose(ncoeff,batchsize,buf.m_q,0,ncoeff,buf.m_b,0,0);
CAblas::RMatrixRightTrsM(batchsize,ncoeff,buf.m_q,0,0,true,false,1,buf.m_b,0,0);
}
else
{
//--- check
if(!CAp::Assert(builder.m_lambdav>=0.0,__FUNCTION__+": integrity check 8368 failed"))
return;
if(!CAp::Assert(builder.m_aterm==1,__FUNCTION__+": integrity check 7365 failed"))
return;
ncoeff=ncenters+nx+1;
//--- First, compute orthogonal basis of space spanned by polynomials of degree 1
CAblasF::RAllocM(ncenters,ncenters,buf.m_r);
CAblasF::RAllocM(nx+1,ncenters,buf.m_q1);
CAblasF::IAllocV(nx+1,ortbasismap);
CAblasF::RSetR(ncenters,1/MathSqrt(ncenters),buf.m_q1,0);
buf.m_r.Set(0,0,MathSqrt(ncenters));
ortbasismap.Set(0,nx);
ortbasissize=1;
CAblasF::RAllocV(ncenters,buf.m_z);
for(k=0; k<nx; k++)
{
CAblasF::RCopyRV(ncenters,buf.m_atwrk,ncenters+k,buf.m_z);
v=MathSqrt(CAblasF::RDotV2(ncenters,buf.m_z));
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,true);
vv=MathSqrt(CAblasF::RDotV2(ncenters,buf.m_z));
if(vv>(MathSqrt(CMath::m_machineepsilon)*(v+1)))
{
CAblasF::RCopyMulVR(ncenters,1/vv,buf.m_z,buf.m_q1,ortbasissize);
CAblasF::RCopyVC(ortbasissize,buf.m_y,buf.m_r,ortbasissize);
buf.m_r.Set(ortbasissize,ortbasissize,vv);
ortbasismap.Set(ortbasissize,k);
ortbasissize++;
}
}
//--- Second, compute system matrix Q and target values for cardinal basis functions B.
//--- The Q is conditionally positive definite, i.e. x'*Q*x>0 for any x satisfying orthogonality conditions
//--- (orthogonal with respect to basis stored in Q1).
CAblasF::RSetAllocM(ncenters,ncenters,0.0,buf.m_q);
for(i=0; i<ncenters; i++)
CAblasF::RCopyRR(ncenters,buf.m_atwrk,i,buf.m_q,i);
CAblasF::RSetAllocM(batchsize,ncoeff,0.0,buf.m_b);
for(i=0; i<batchsize; i++)
buf.m_b.Set(i,i,1.0);
for(i=0; i<ncenters; i++)
buf.m_q.Add(i,i,builder.m_lambdav);
//--- Transform Q from conditionally positive definite to the (simply) positive definite one:
//--- multiply linear system Q*x=RHS from both sides by (I-Q1'*Q1), apply additional regularization.
//--- NOTE: RHS is also multiplied by (I-Q1'*Q1), but from the left only.
CAblasF::RAllocV(ncenters,buf.m_z);
for(i=0; i<ncenters; i++)
{
CAblasF::RCopyRV(ncenters,buf.m_q,i,buf.m_z);
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_q,i);
}
for(i=0; i<ncenters; i++)
{
CAblasF::RCopyCV(ncenters,buf.m_q,i,buf.m_z);
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
CAblasF::RCopyVC(ncenters,buf.m_z,buf.m_q,i);
}
for(i=0; i<batchsize; i++)
{
CAblasF::RCopyRV(ncenters,buf.m_b,i,buf.m_z);
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_b,i);
}
mx=1.0;
for(i=0; i<ncenters; i++)
mx=MathMax(mx,MathAbs(buf.m_q.Get(i,i)));
for(i=0; i<ncenters; i++)
{
CAblasF::RCopyRV(ncenters,buf.m_q,i,buf.m_z);
for(j=0; j<ortbasissize; j++)
CAblasF::RAddRV(ncenters,mx*buf.m_q1.Get(j,i),buf.m_q1,j,buf.m_z);
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_q,i);
}
if(builder.m_dodetailedtrace)
{
//--- Compute condition number for future reports
dbgcondq=1/(CRCond::SPDMatrixRCond(buf.m_q,ncenters,false)+CMath::m_machineepsilon);
}
else
{
dbgcondq=0;
}
for(i=0; i<ncenters; i++)
buf.m_q.Add(i,i,reg*mx);
//--- Perform Cholesky factorization, solve and obtain RBF coefficients (we still have
//--- to compute polynomial term - it will be done later)
if(!CTrFac::SPDMatrixCholeskyRec(buf.m_q,0,ncenters,false,buf.m_choltmp))
{
CAp::Assert(false,__FUNCTION__+": ACBF solver failed due to extreme degeneracy");
return;
}
CAblas::RMatrixRightTrsM(batchsize,ncenters,buf.m_q,0,0,false,false,1,buf.m_b,0,0);
CAblas::RMatrixRightTrsM(batchsize,ncenters,buf.m_q,0,0,false,false,0,buf.m_b,0,0);
//--- Now, having RBF coefficients we can compute residual from fitting ACBF targets
//--- with pure RBF term and fit polynomial term to this residual. In the ideal world
//--- it should result in the nice and precise polynomial coefficients.
CAblasF::RAllocV(ncenters,buf.m_z);
CAblasF::RSetAllocM(batchsize,ncenters,0.0,buf.m_c);
for(i=0; i<batchsize; i++)
buf.m_c.Set(i,i,1.0);
CAblas::RMatrixGemm(batchsize,ncenters,ncenters,-1.0,buf.m_b,0,0,0,buf.m_atwrk,0,0,1,1.0,buf.m_c,0,0);
for(i=0; i<batchsize; i++)
{
CAblasF::RCopyRV(ncenters,buf.m_c,i,buf.m_z);
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,true);
CAblas::RMatrixTrsVect(ortbasissize,buf.m_r,0,0,true,false,0,buf.m_y,0);
for(j=0; j<=nx; j++)
buf.m_b.Set(i,ncenters+j,0.0);
for(j=0; j<ortbasissize; j++)
buf.m_b.Set(i,ncenters+ortbasismap[j],buf.m_y[j]);
}
//--- Trace if needeed
if(builder.m_dodetailedtrace)
{
CAblasF::RAllocM(batchsize,ncenters,dbgb);
CAblas::RMatrixGemm(batchsize,ncenters,ncoeff,-1.0,buf.m_b,0,0,0,buf.m_atwrk,0,0,0,0.0,dbgb,0,0);
for(i=0; i<batchsize; i++)
dbgb.Add(i,i,1.0);
dbgmaxc=0;
for(i=0; i<batchsize; i++)
dbgmaxc=MathMax(dbgmaxc,CAblasF::RMaxAbsR(ncenters,buf.m_b,i));
dbgerrnodes=0;
for(i=0; i<batchsize; i++)
dbgerrnodes+=CAblasF::RDotRR(ncenters,dbgb,i,dbgb,i);
dbgerrnodes=MathSqrt(dbgerrnodes/(batchsize*ncenters));
dbgerrort=0;
for(i=0; i<batchsize; i++)
for(j=0; j<ortbasissize; j++)
dbgerrort=MathMax(dbgerrort,MathAbs(CAblasF::RDotRR(ncenters,buf.m_b,i,buf.m_q1,j)));
CAp::Trace(StringFormat("[ACBF_subprob] BatchSize=%3d NCenters=%4d RadiusExpansions=%d cond(Q)=%.2E max|C|=%.2E rmsErr=%.2E OrtErr=%.2E\n",batchsize,ncenters,expansionscount,dbgcondq,dbgmaxc,dbgerrnodes,dbgerrort));
}
}
//--- Solve and save solution to Builder.ChunksPool
precchunk=builder.m_chunksproducer;
//--- check
if(!CAp::Assert(precchunk.m_ntargetrows==-117,__FUNCTION__+": integrity check 9724 failed"))
return;
if(!CAp::Assert(precchunk.m_ntargetcols==-119,__FUNCTION__+": integrity check 9725 failed"))
return;
precchunk.m_ntargetrows=batchsize;
precchunk.m_ntargetcols=ncoeff;
CAblasF::IAllocV(precchunk.m_ntargetrows,precchunk.m_targetrows);
CAblasF::IAllocV(precchunk.m_ntargetcols,precchunk.m_targetcols);
CAblasF::RAllocM(batchsize,ncoeff,precchunk.m_s);
for(widx=0; widx<batchsize; widx++)
precchunk.m_targetrows.Set(widx,builder.m_wrkidx[wrk0+widx]);
CAblasF::IAllocV(ncoeff,buf.m_perm);
for(k=0; k<ncoeff; k++)
{
if(k<ncenters)
precchunk.m_targetcols.Set(k,buf.m_currentnodes[k]);
else
precchunk.m_targetcols.Set(k,builder.m_ntotal+(k-ncenters));
buf.m_perm.Set(k,k);
}
CTSort::TagSortMiddleII(precchunk.m_targetcols,buf.m_perm,0,ncoeff);
for(widx=0; widx<batchsize; widx++)
{
for(k=0; k<ncoeff; k++)
precchunk.m_s.Set(widx,k,buf.m_b.Get(widx,buf.m_perm[k]));
}
builder.m_chunkspool=precchunk;
}
//+------------------------------------------------------------------+
//| Recursive ACBF preconditioner generation subroutine. |
//| PARAMETERS: |
//| Builder - ACBF builder object |
//| Wrk0, Wrk1 - elements [Wrk0...Wrk1 - 1] of Builder.WrkIdx[] |
//| array store row indexes of XX that are processed|
//| OUTPUT: |
//| Builder.OutputPool is updated with new chunks |
//+------------------------------------------------------------------+
void CRBFV3::ComputeACBFPreconditionerRecV2(CACBFBuilder &builder,
int wrk0,int wrk1)
{
//--- create variables
int nx=builder.m_nx;
int k0=0;
int k1=0;
int largestdim=0;
double splitval=0;
double basecasecomplexity=0;
CACBFBuffer buf;
if(wrk1<=wrk0)
return;
//--- check
if(!CAp::Assert(0<=wrk0,__FUNCTION__+": wrk0 less zero"))
return;
basecasecomplexity=MathPow(builder.m_nglobal+builder.m_nlocal+2*builder.m_ncorrection,3);
//--- Retrieve temporary buffer
buf=builder.m_bufferpool;
//--- Analyze current working set
CAblasF::RAllocV(nx,buf.m_tmpboxmin);
CAblasF::RAllocV(nx,buf.m_tmpboxmax);
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0],buf.m_tmpboxmin);
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0],buf.m_tmpboxmax);
for(int i=wrk0+1; i<=wrk1-1; i++)
{
for(int j=0; j<nx; j++)
{
buf.m_tmpboxmin.Set(j,MathMin(buf.m_tmpboxmin[j],builder.m_xx.Get(builder.m_wrkidx[i],j)));
buf.m_tmpboxmax.Set(j,MathMax(buf.m_tmpboxmax[j],builder.m_xx.Get(builder.m_wrkidx[i],j)));
}
}
largestdim=0;
for(int j=1; j<nx; j++)
{
if((buf.m_tmpboxmax[j]-buf.m_tmpboxmin[j])>(buf.m_tmpboxmax[largestdim]-buf.m_tmpboxmin[largestdim]))
largestdim=j;
}
//--- Perform batch processing
ComputeACBFPreconditionerBasecase(builder,buf,wrk0,wrk1);
//--- Recycle temporary buffers
builder.m_bufferpool=buf;
}
//+------------------------------------------------------------------+
//| This function generates ACBF (approximate cardinal basis |
//| functions) preconditioner. |
//| PARAMETERS: |
//| XX - dataset(X - values), array[N, NX] |
//| N - points count, N >= 1 |
//| NX - dimensions count, NX >= 1 |
//| FuncType - basis function type |
//| OUTPUT: |
//| SP - preconditioner, sparse matrix in CRS format |
//+------------------------------------------------------------------+
void CRBFV3::ComputeACBFPreconditioner(CMatrixDouble &xx,int n,
int nx,int functype,
double funcparam,int aterm,
int batchsize,int nglobal,
int nlocal,int ncorrection,
int correctorgrowth,
int simplificationfactor,
double lambdav,
CSparseMatrix &sp)
{
//--- create variables
CACBFBuilder builder;
CACBFBuffer bufferseed;
CACBFChunk chunkseed;
CACBFChunk precchunk;
int offs=0;
CRowInt idummy;
CRowInt rowsizes;
CRowDouble boxmin;
CRowDouble boxmax;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 2524 failed"))
return;
//--- Prepare builder
builder.m_dodetailedtrace=CAp::IsTraceEnabled("RBF.DETAILED");
builder.m_functype=functype;
builder.m_funcparam=funcparam;
builder.m_ntotal=n;
builder.m_nx=nx;
builder.m_batchsize=batchsize;
if(nglobal>0)
SelectGlobalNodes(xx,n,nx,idummy,0,nglobal,builder.m_globalgrid,builder.m_nglobal,builder.m_globalgridseparation);
else
builder.m_nglobal=0;
builder.m_nlocal=nlocal;
builder.m_ncorrection=ncorrection;
builder.m_correctorgrowth=correctorgrowth;
builder.m_lambdav=lambdav;
builder.m_aterm=aterm;
CAblasF::RCopyAllocM(n,nx,xx,builder.m_xx);
CAblasF::RAllocV(nx,boxmin);
CAblasF::RAllocV(nx,boxmax);
CAblasF::RCopyRV(nx,xx,0,boxmin);
CAblasF::RCopyRV(nx,xx,0,boxmax);
for(int i=1; i<n; i++)
{
CAblasF::RMergeMinRV(nx,xx,i,boxmin);
CAblasF::RMergeMaxRV(nx,xx,i,boxmax);
}
builder.m_roughdatasetdiameter=0;
for(int i=0; i<nx; i++)
builder.m_roughdatasetdiameter+=CMath::Sqr(boxmax[i]-boxmin[i]);
builder.m_roughdatasetdiameter=MathSqrt(builder.m_roughdatasetdiameter);
CAblasF::IAllocV(n,builder.m_wrkidx);
for(int i=0; i<n; i++)
builder.m_wrkidx.Set(i,i);
CNearestNeighbor::KDTreeBuildTagged(xx,builder.m_wrkidx,n,nx,0,2,builder.m_kdt);
BuildSimplifiedKDTree(xx,n,nx,(int)MathRound(MathPow(simplificationfactor,nx)),(int)MathRound(MathPow(5,nx)),builder.m_kdt1);
BuildSimplifiedKDTree(xx,n,nx,(int)MathRound(MathPow(simplificationfactor,2*nx)),(int)MathRound(MathPow(5,nx)),builder.m_kdt2);
CAblasF::BSetAllocV(n,false,bufferseed.m_bflags);
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmin);
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmax);
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt,bufferseed.m_kdtbuf);
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt1,bufferseed.m_kdt1buf);
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt2,bufferseed.m_kdt2buf);
builder.m_bufferpool=bufferseed;
chunkseed.m_ntargetrows=-117;
chunkseed.m_ntargetcols=-119;
builder.m_chunksproducer=chunkseed;
builder.m_chunkspool=chunkseed;
//--- Prepare preconditioner matrix
ComputeACBFPreconditionerRecV2(builder,0,n);
CAblasF::ISetAllocV(n,-1,rowsizes);
precchunk=builder.m_chunkspool;
for(int i=0; i<precchunk.m_ntargetrows; i++)
{
//--- check
if(!CAp::Assert(rowsizes[precchunk.m_targetrows[i]]==-1,__FUNCTION__+": integrity check 2568 failed"))
return;
rowsizes.Set(precchunk.m_targetrows[i],precchunk.m_ntargetcols);
}
sp.m_MatrixType=1;
sp.m_M=n+nx+1;
sp.m_N=n+nx+1;
CAblasF::IAllocV(n+nx+2,sp.m_RIdx);
sp.m_RIdx.Set(0,0);
for(int i=0; i<n; i++)
{
//--- check
if(!CAp::Assert(rowsizes[i]>0,__FUNCTION__+": integrity check 2668 failed"))
return;
sp.m_RIdx.Set(i+1,sp.m_RIdx[i]+rowsizes[i]);
}
for(int i=n; i<=n+nx; i++)
sp.m_RIdx.Set(i+1,sp.m_RIdx[i]+1);
CAblasF::IAllocV(sp.m_RIdx[sp.m_M],sp.m_Idx);
CAblasF::RAllocV(sp.m_RIdx[sp.m_M],sp.m_Vals);
for(int i=n; i<=n+nx; i++)
{
sp.m_Idx.Set(sp.m_RIdx[i],i);
sp.m_Vals.Set(sp.m_RIdx[i],1.0);
}
precchunk=builder.m_chunkspool;
for(int i=0; i<=precchunk.m_ntargetrows-1; i++)
{
offs=sp.m_RIdx[precchunk.m_targetrows[i]];
for(int j=0; j<=precchunk.m_ntargetcols-1; j++)
{
sp.m_Idx.Set(offs+j,precchunk.m_targetcols[j]);
sp.m_Vals.Set(offs+j,precchunk.m_s.Get(i,j));
}
}
sp.m_NInitialized=sp.m_RIdx[sp.m_M];
CSparse::SparseInitDUIdx(sp);
}
//+------------------------------------------------------------------+
//| Basecase initialization routine for DDM solver. |
//| Appends an instance of RBF3DDMSubproblem to |
//| Solver.SubproblemsPool. |
//| INPUT PARAMETERS: |
//| Solver - solver object. This function may be called from |
//| the multiple threads, so it is important to work|
//| with Solver object using only thread-safe |
//| functions. |
//| X - array[N, NX], dataset points |
//| N, NX - dataset metrics, N > 0, NX > 0 |
//| BFMatrix - basis function matrix object |
//| LambdaV - smoothing parameter |
//| SP - sparse ACBF preconditioner, |
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
//| format |
//| Buf - an instance of RBF3DDMBuffer, reusable temporary|
//| buffers |
//| TgtIdx - array[], contains indexes of points in the |
//| current target set. Elements [Tgt0, Tgt1) are |
//| processed by this function. |
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
//| added to inner points of the chunk |
//| DoDetailedTrace - whether trace output is needed or not. When |
//| trace is activated, solver computes condition |
//| numbers. It results in the several - fold |
//| slowdown of the algorithm. |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverInitBasecase(CRBF3DDMSolver &solver,
CMatrixDouble &x,
int n,
int nx,
CRBF3Evaluator &bfmatrix,
double lambdav,
CSparseMatrix &sp,
CRBF3DDMBuffer &buf,
CRowInt &tgtidx,
int tgt0,
int tgt1,
int nneighbors,
bool dodetailedtrace)
{
//--- create variables
CRBF3DDMSubproblem subproblem;
int i=0;
int j=0;
int k=0;
int nc=0;
int nk=0;
double v=0;
double reg=0;
int nwrk=0;
int npreccol=0;
CRowInt neighbors;
CRowInt workingnodes;
CRowInt preccolumns;
CRowDouble tau;
CMatrixDouble q;
CRowDouble x0;
double lurcond=0;
bool lusuccess=false;
int ni=0;
int j0=0;
int j1=0;
int jj=0;
CMatrixDouble suba;
CMatrixDouble subsp;
CMatrixDouble dbga;
//--- check
if(!CAp::Assert(tgt1-tgt0>0,__FUNCTION__+": integrity check 7364 failed"))
return;
if(!CAp::Assert(nneighbors>=1,__FUNCTION__+": integrity check 7365 failed"))
return;
reg=(100+MathSqrt(tgt1-tgt0+nneighbors))*CMath::m_machineepsilon;
//--- Retrieve fresh subproblem. We expect that Solver.SubproblemsBuffer contains
//--- no recycled entries and that fresh subproblem with Subproblem.IsValid=False
//--- is returned.
//--- Start initialization
subproblem=solver.m_subproblemsbuffer;
//--- check
if(!CAp::Assert(!subproblem.m_isvalid,__FUNCTION__+": SubproblemsBuffer integrity check failed"))
return;
subproblem.m_isvalid=true;
subproblem.m_ntarget=tgt1-tgt0;
CAblasF::IAllocV(tgt1-tgt0,subproblem.m_targetnodes);
CAblasF::ICopyVX(tgt1-tgt0,tgtidx,tgt0,subproblem.m_targetnodes,0);
//--- Prepare working arrays
CAblasF::RAllocV(nx,x0);
//--- Determine working set: target nodes + neighbors of targets.
//--- Prepare mapping from node index to position in WorkingNodes[]
nwrk=0;
CAblasF::IAllocV(tgt1-tgt0,workingnodes);
for(i=tgt0; i<tgt1; i++)
{
nk=tgtidx[i];
buf.m_bflags[nk]=true;
workingnodes.Set(nwrk,nk);
nwrk++;
}
for(i=tgt0; i<tgt1; i++)
{
CAblasF::RCopyRV(nx,x,tgtidx[i],x0);
nc=CNearestNeighbor::KDTreeTsQueryKNN(solver.m_kdt,buf.m_kdtbuf,x0,nneighbors,true);
CNearestNeighbor::KDTreeTsQueryResultsTags(solver.m_kdt,buf.m_kdtbuf,neighbors);
for(k=0; k<nc; k++)
{
nk=neighbors[k];
if(!buf.m_bflags[nk])
{
buf.m_bflags[nk]=true;
CAblasF::IGrowV(nwrk+1,workingnodes);
workingnodes.Set(nwrk,nk);
nwrk++;
}
}
}
for(i=0; i<nwrk; i++)
buf.m_bflags[workingnodes[i]]=false;
//--- check
if(!CAp::Assert(nwrk>0,__FUNCTION__+": integrity check for NWrk failed"))
return;
subproblem.m_nwork=nwrk;
CAblasF::ICopyAllocV(nwrk,workingnodes,subproblem.m_workingnodes);
//--- Determine preconditioner columns that have nonzeros in rows corresponding
//--- to working nodes. Prepare mapping from [0,N+NX+1) column indexing to [0,NPrecCol)
//--- compressed one. Only these columns are extracted from the preconditioner
//--- during design system computation.
//--- NOTE: we ensure that preconditioner columns N...N+NX which correspond to linear
//--- terms are placed last. It greatly simplifies desi
npreccol=0;
for(i=0; i<nwrk; i++)
{
j0=sp.m_RIdx[workingnodes[i]];
j1=sp.m_RIdx[workingnodes[i]+1];
for(jj=j0; jj<j1; jj++)
{
j=sp.m_Idx[jj];
if(j<n && !buf.m_bflags[j])
{
buf.m_bflags[j]=true;
CAblasF::IGrowV(npreccol+1,preccolumns);
preccolumns.Set(npreccol,j);
npreccol++;
}
}
}
for(j=n; j<=n+nx; j++)
{
//--- check
if(!CAp::Assert(!buf.m_bflags[j],__FUNCTION__+": integrity check 9435 failed"))
return;
buf.m_bflags[j]=true;
CAblasF::IGrowV(npreccol+1,preccolumns);
preccolumns.Set(npreccol,j);
npreccol++;
}
for(i=0; i<=npreccol-1; i++)
{
buf.m_idx2preccol.Set(preccolumns[i],i);
buf.m_bflags[preccolumns[i]]=false;
}
//--- Generate working system, apply regularization
CAblasF::RSetAllocM(nwrk,npreccol,0.0,suba);
ModelMatrixComputePartial(bfmatrix,workingnodes,nwrk,preccolumns,npreccol-(nx+1),suba);
for(i=0; i<nwrk; i++)
{
ni=workingnodes[i];
for(j=0; j<nx; j++)
suba.Set(i,npreccol-(nx+1)+j,x.Get(ni,j));
suba.Set(i,npreccol-1,1.0);
}
for(i=0; i<nwrk; i++)
{
j=buf.m_idx2preccol[workingnodes[i]];
suba.Add(i,j,lambdav);
}
CAblasF::RSetAllocM(nwrk,npreccol,0.0,subsp);
for(i=0; i<nwrk; i++)
{
ni=workingnodes[i];
j0=sp.m_RIdx[ni];
j1=sp.m_RIdx[ni+1];
for(jj=j0; jj<j1; jj++)
subsp.Set(i,buf.m_idx2preccol[sp.m_Idx[jj]],sp.m_Vals[jj]);
}
CAblasF::RAllocM(nwrk,nwrk,subproblem.m_regsystem);
CAblas::RMatrixGemm(nwrk,nwrk,npreccol,1.0,suba,0,0,0,subsp,0,0,1,0.0,subproblem.m_regsystem,0,0);
//--- Try solving with LU decomposition
CAblasF::RCopyAllocM(nwrk,nwrk,subproblem.m_regsystem,subproblem.m_wrklu);
CTrFac::RMatrixLU(subproblem.m_wrklu,nwrk,nwrk,subproblem.m_wrkp);
lurcond=CRCond::RMatrixLURCondInf(subproblem.m_wrklu,nwrk);
if(lurcond>MathSqrt(CMath::m_machineepsilon))
{
//--- LU success
subproblem.m_decomposition=0;
lusuccess=true;
if(dodetailedtrace)
CAp::Trace(StringFormat(">> DDM subproblem: LU success,|target|=%4d,|wrk|=%4d,|preccol|=%4d,cond(LU)=%.2E\n",tgt1 - tgt0,nwrk,npreccol,1 / (lurcond + CMath::m_machineepsilon)));
}
else
lusuccess=false;
//--- Apply regularized QR if needed
if(!lusuccess)
{
CAblasF::RSetAllocM(2*nwrk,nwrk,0.0,subproblem.m_wrkr);
CAblasF::RCopyM(nwrk,nwrk,subproblem.m_regsystem,subproblem.m_wrkr);
v=MathSqrt(reg);
for(i=0; i<nwrk; i++)
subproblem.m_wrkr.Set(nwrk+i,i,v);
COrtFac::RMatrixQR(subproblem.m_wrkr,2*nwrk,nwrk,tau);
COrtFac::RMatrixQRUnpackQ(subproblem.m_wrkr,2*nwrk,nwrk,tau,nwrk,subproblem.m_wrkq);
subproblem.m_decomposition=1;
if(dodetailedtrace)
CAp::Trace(StringFormat(">> DDM subproblem: LU failure,using reg-QR,|target|=%4d,|wrk|=%4d,|preccol|=%4d,cond(R)=%.2E (cond(LU)=%.2E)\n",tgt1 - tgt0,nwrk,npreccol,1 / (CRCond::RMatrixTrRCondInf(subproblem.m_wrkr,nwrk,true,false) + CMath::m_machineepsilon),1 / (lurcond + CMath::m_machineepsilon)));
}
//--- Subproblem is ready.
//--- Move it to the SubproblemsPool
solver.m_subproblemspool=subproblem;
}
//+------------------------------------------------------------------+
//| Recursive initialization routine for DDM solver |
//| INPUT PARAMETERS: |
//| Solver - solver structure |
//| X - array[N, NX], dataset points |
//| N, NX - dataset metrics, N > 0, NX > 0 |
//| BFMatrix - basis function evaluator |
//| LambdaV - smoothing parameter |
//| SP - sparse ACBF preconditioner, |
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
//| format |
//| WrkIdx - array[], contains indexes of points in the |
//| current working set. Elements [Wrk0, Wrk1) are |
//| processed by this function. |
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
//| added to inner points of the chunk |
//| NBatch - batch size |
//| DoDetailedTrace - whether trace output is needed or not. When |
//| trace is activated, solver computes condition |
//| numbers. It results in the several - fold |
//| slowdown of the algorithm. |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverInitRec(CRBF3DDMSolver &solver,
CMatrixDouble &x,
int n,
int nx,
CRBF3Evaluator &bfmatrix,
double lambdav,
CSparseMatrix &sp,
CRowInt &wrkidx,
int wrk0,
int wrk1,
int nneighbors,
int nbatch,
bool dodetailedtrace)
{
//--- create variables
int largestdim=0;
double splitval=0;
double basecasecomplexity=0;
CRBF3DDMBuffer buf;
if(wrk1<=wrk0)
return;
basecasecomplexity=MathPow(nbatch+nneighbors+nx+1,3.0);
//--- Retrieve temporary buffer
buf=solver.m_bufferpool;
//--- Analyze current working set
CAblasF::RAllocV(nx,buf.m_tmpboxmin);
CAblasF::RAllocV(nx,buf.m_tmpboxmax);
CAblasF::RCopyRV(nx,x,wrkidx[wrk0],buf.m_tmpboxmin);
CAblasF::RCopyRV(nx,x,wrkidx[wrk0],buf.m_tmpboxmax);
for(int i=wrk0+1; i<wrk1; i++)
{
for(int j=0; j<nx; j++)
{
buf.m_tmpboxmin.Set(j,MathMin(buf.m_tmpboxmin[j],x.Get(wrkidx[i],j)));
buf.m_tmpboxmax.Set(j,MathMax(buf.m_tmpboxmax[j],x.Get(wrkidx[i],j)));
}
}
largestdim=0;
for(int j=1; j<nx; j++)
{
if((buf.m_tmpboxmax[j]-buf.m_tmpboxmin[j])>(buf.m_tmpboxmax[largestdim]-buf.m_tmpboxmin[largestdim]))
largestdim=j;
}
//--- Perform either batch processing or recursive split
//--- Either working set size is small enough or all points are non-distinct.
//--- Stop recursive subdivision.
DDMSolverInitBasecase(solver,x,n,nx,bfmatrix,lambdav,sp,buf,wrkidx,wrk0,wrk1,nneighbors,dodetailedtrace);
//--- Recycle temporary buffers
solver.m_bufferpool=buf;
}
//+------------------------------------------------------------------+
//| This function prepares domain decomposition method for RBF |
//| interpolation problem - it partitions problem into subproblems |
//| and precomputes factorizations, and prepares a smaller correction|
//| spline that is used to correct distortions introduced by domain |
//| decomposition and imperfections in approximate cardinal basis. |
//| INPUT PARAMETERS: |
//| X - array[N, NX], dataset points |
//| RescaledBy - additional scaling coefficient that was applied |
//| to the dataset by preprocessor. Used ONLY for |
//| logging purposes - without it all distances will|
//| be reported in [0, 1] scale, not one set by user|
//| N, NX - dataset metrics, N > 0, NX > 0 |
//| BFMatrix - RBF evaluator |
//| BFType - basis function type |
//| BFParam - basis function parameter |
//| LambdaV - regularization parameter, >= 0 |
//| ATerm - polynomial term type(1 for linear, 2 for |
//| constant, 3 for zero) |
//| SP - sparse ACBF preconditioner, |
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
//| format |
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
//| added to inner points of the batch |
//| NBatch - batch size |
//| NCorrector - nodes count for correction spline |
//| DoTrace - whether low overhead logging is needed or not |
//| DoDetailedTrace - whether detailed trace output is needed or |
//| not. When trace is activated, solver computes |
//| condition numbers. It results in the small |
//| slowdown of the algorithm. |
//| OUTPUT PARAMETERS: |
//| Solver - DDM solver |
//| timeDDMInit - time used by the DDM part initialization, ms |
//| timeCorrInit - time used by the corrector initialization, ms |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverInit(CMatrixDouble &x,
double rescaledby,
int n,
int nx,
CRBF3Evaluator &bfmatrix,
int bftype,
double bfparam,
double lambdav,
int aterm,
CSparseMatrix &sp,
int nneighbors,
int nbatch,
int ncorrector,
bool dotrace,
bool dodetailedtrace,
CRBF3DDMSolver &solver,
int &timeddminit,
int &timecorrinit)
{
//--- create variables
int i=0;
int j=0;
CRowInt idx;
CRBF3DDMBuffer bufferseed;
CRBF3DDMSubproblem subproblem;
CRBF3DDMSubproblem p;
double correctorgridseparation=0;
CMatrixDouble corrsys;
CRowDouble corrtau;
CRowInt idummy;
timeddminit=0;
timecorrinit=0;
//--- check
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": integrity check 3320 failed"))
return;
//--- Start DDM part
timeddminit=((int)(GetTickCount()/10000));
//--- Save problem info
solver.m_lambdav=lambdav;
//--- Prepare KD-tree
CAblasF::IAllocV(n,idx);
for(i=0; i<n; i++)
idx.Set(i,i);
CNearestNeighbor::KDTreeBuildTagged(x,idx,n,nx,0,2,solver.m_kdt);
//--- Prepare temporary buffer pool
CAblasF::BSetAllocV(n+nx+1,false,bufferseed.m_bflags);
CAblasF::IAllocV(n+nx+1,bufferseed.m_idx2preccol);
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmin);
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmax);
CNearestNeighbor::KDTreeCreateRequestBuffer(solver.m_kdt,bufferseed.m_kdtbuf);
solver.m_bufferpool=bufferseed;
//--- Prepare default subproblems buffer, run recursive procedure
//--- and count subproblems in the buffer
solver.m_subproblemspool=subproblem;
solver.m_subproblemsbuffer=subproblem;
DDMSolverInitRec(solver,x,n,nx,bfmatrix,solver.m_lambdav,sp,idx,0,n,nneighbors,nbatch,dodetailedtrace);
solver.m_subproblemscnt=0;
solver.m_cntlu=0;
solver.m_cntregqr=0;
p=solver.m_subproblemspool;
solver.m_subproblemscnt++;
if(p.m_decomposition==0)
solver.m_cntlu++;
if(p.m_decomposition==1)
solver.m_cntregqr++;
//--- check
if(!CAp::Assert(solver.m_cntlu+solver.m_cntregqr==solver.m_subproblemscnt,__FUNCTION__+": integrity check 5296 failed"))
return;
if(!CAp::Assert(solver.m_subproblemscnt>0,__FUNCTION__+": subproblems pool is empty,critical integrity check failed"))
return;
//--- DDM part is done
timeddminit=((int)(GetTickCount()/10000))-timeddminit;
if(dotrace)
CAp::Trace(StringFormat("> DDM part was prepared in %d ms,%d subproblems solved (%d well-conditioned,%d ill-conditioned)\n",timeddminit,solver.m_subproblemscnt,solver.m_cntlu,solver.m_cntregqr));
//--- Prepare correction spline
timecorrinit=((int)(GetTickCount()/10000));
SelectGlobalNodes(x,n,nx,idummy,0,ncorrector,solver.m_corrnodes,solver.m_ncorrector,correctorgridseparation);
ncorrector=solver.m_ncorrector;
//--- check
if(!CAp::Assert(ncorrector>0,__FUNCTION__+": NCorrector=0"))
return;
CAblasF::RSetAllocM(ncorrector+nx+1,ncorrector+nx+1,0.0,corrsys);
CAblasF::RAllocM(ncorrector,nx,solver.m_corrx);
for(i=0; i<=ncorrector-1; i++)
CAblasF::RCopyRR(nx,x,solver.m_corrnodes[i],solver.m_corrx,i);
ComputeBFMatrix(solver.m_corrx,ncorrector,nx,bftype,bfparam,corrsys);
if(aterm==1)
{
//--- Use linear term
for(i=0; i<nx; i++)
for(j=0; j<ncorrector; j++)
{
corrsys.Set(ncorrector+i,j,x.Get(solver.m_corrnodes[j],i));
corrsys.Set(j,ncorrector+i,x.Get(solver.m_corrnodes[j],i));
}
for(j=0; j<ncorrector; j++)
{
corrsys.Set(ncorrector+nx,j,1.0);
corrsys.Set(j,ncorrector+nx,1.0);
}
}
if(aterm==2)
{
//--- Use constant term
for(i=0; i<nx; i++)
corrsys.Set(ncorrector+i,ncorrector+i,1.0);
for(j=0; j<=ncorrector-1; j++)
{
corrsys.Set(ncorrector+nx,j,1.0);
corrsys.Set(j,ncorrector+nx,1.0);
}
}
if(aterm==3)
{
//--- Use zero term
for(i=0; i<=nx; i++)
corrsys.Set(ncorrector+i,ncorrector+i,1.0);
}
for(j=0; j<ncorrector; j++)
corrsys.Add(j,j,solver.m_lambdav);
COrtFac::RMatrixQR(corrsys,ncorrector+nx+1,ncorrector+nx+1,corrtau);
COrtFac::RMatrixQRUnpackQ(corrsys,ncorrector+nx+1,ncorrector+nx+1,corrtau,ncorrector+nx+1,solver.m_corrq);
COrtFac::RMatrixQRUnpackR(corrsys,ncorrector+nx+1,ncorrector+nx+1,solver.m_corrr);
timecorrinit=((int)(GetTickCount()/10000))-timecorrinit;
if(dotrace)
CAp::Trace(StringFormat("> Corrector spline was prepared in %d ms (%d nodes,max distance from dataset points to nearest grid node is %.2E)\n",timecorrinit,ncorrector,correctorgridseparation * rescaledby));
if(dodetailedtrace)
{
CAp::Trace("> printing condition numbers for correction spline:\n");
CAp::Trace(StringFormat("cond(A) = %.2E (Linf norm,leading NCoarsexNCoarse block)\n",1 / (CRCond::RMatrixTrRCondInf(solver.m_corrr,ncorrector,true,false) + CMath::m_machineepsilon)));
CAp::Trace(StringFormat("cond(A) = %.2E (Linf norm,full system)\n",1 / (CRCond::RMatrixTrRCondInf(solver.m_corrr,ncorrector + nx + 1,true,false) + CMath::m_machineepsilon)));
}
}
//+------------------------------------------------------------------+
//| Recursive subroutine for DDM method. Given initial subproblems |
//| count Cnt, it perform two recursive calls(spawns children in |
//| parallel when possible) with Cnt~Cnt / 2 until we end up with |
//| Cnt = 1. |
//| Case with Cnt = 1 is handled by retrieving subproblem from |
//| Solver.SubproblemsPool, solving it and pushing subproblem to |
//| Solver.SubproblemsBuffer. |
//| INPUT PARAMETERS: |
//| Solver - DDM solver object |
//| Res - array[N, NY], current residuals |
//| N, NX, NY - dataset metrics, N > 0, NX > 0, NY > 0 |
//| C - preallocated array[N + NX + 1, NY] |
//| Cnt - number of subproblems to process |
//| OUTPUT PARAMETERS: |
//| C - rows 0..N - 1 contain spline coefficients rows |
//| N..N + NX are filled by zeros |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverRunRec(CRBF3DDMSolver &solver,
CMatrixDouble &res,
int n,
int nx,
int ny,
CMatrixDouble &c,
int cnt)
{
//--- create variables
int nwrk=0;
int ntarget=0;
double v=0;
CRBF3DDMSubproblem subproblem;
//--- Retrieve subproblem from the source pool, solve it
subproblem=solver.m_subproblemspool;
//--- check
if(!CAp::Assert(subproblem.m_isvalid,__FUNCTION__+": integrity check 1742 failed"))
return;
nwrk=subproblem.m_nwork;
ntarget=subproblem.m_ntarget;
if(subproblem.m_decomposition==0)
{
//--- Solve using LU decomposition (the fastest option)
CAblasF::RAllocM(nwrk,ny,subproblem.m_rhs);
for(int i=0; i<nwrk; i++)
for(int j=0; j<ny; j++)
subproblem.m_rhs.Set(i,j,res.Get(subproblem.m_workingnodes[i],j));
for(int i=0; i<nwrk; i++)
{
if(subproblem.m_wrkp[i]!=i)
{
for(int j=0; j<ny; j++)
{
v=subproblem.m_rhs.Get(i,j);
subproblem.m_rhs.Set(i,j,subproblem.m_rhs.Get(subproblem.m_wrkp[i],j));
subproblem.m_rhs.Set(subproblem.m_wrkp[i],j,v);
}
}
}
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrklu,0,0,false,true,0,subproblem.m_rhs,0,0);
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrklu,0,0,true,false,0,subproblem.m_rhs,0,0);
CAblasF::RCopyAllocM(nwrk,ny,subproblem.m_rhs,subproblem.m_sol);
}
else
{
//--- Solve using regularized QR (well, we tried LU but it failed)
//--- check
if(!CAp::Assert(subproblem.m_decomposition==1,__FUNCTION__+": integrity check 1743 failed"))
return;
CAblasF::RAllocM(nwrk,ny,subproblem.m_rhs);
for(int i=0; i<nwrk; i++)
{
for(int j=0; j<ny; j++)
subproblem.m_rhs.Set(i,j,res.Get(subproblem.m_workingnodes[i],j));
}
CAblasF::RAllocM(nwrk,ny,subproblem.m_qtrhs);
CAblas::RMatrixGemm(nwrk,ny,nwrk,1.0,subproblem.m_wrkq,0,0,1,subproblem.m_rhs,0,0,0,0.0,subproblem.m_qtrhs,0,0);
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrkr,0,0,true,false,0,subproblem.m_qtrhs,0,0);
CAblasF::RCopyAllocM(nwrk,ny,subproblem.m_qtrhs,subproblem.m_sol);
}
for(int i=0; i<ntarget; i++)
{
for(int j=0; j<ny; j++)
c.Set(subproblem.m_targetnodes[i],j,subproblem.m_sol.Get(i,j));
}
//--- Push to the destination pool
solver.m_subproblemsbuffer=subproblem;
}
//+------------------------------------------------------------------+
//| This function APPROXIMATELY solves RBF interpolation problem |
//| using domain decomposition method. Given current residuals Res, |
//| it computes approximate basis function coefficients C (but does |
//| NOT compute linear coefficients - these are set to zero). |
//| This function is a linear operator with respect to its input RES,|
//| thus it can be used as a preconditioner for an iterative linear |
//| solver like GMRES. |
//| INPUT PARAMETERS: |
//| Solver - DDM solver object |
//| Res - array[N, NY], current residuals |
//| N, NX, NY - dataset metrics, N > 0, NX > 0, NY > 0 |
//| SP - preconditioner, (N + NX + 1)*(N + NX + 1) sparse|
//| matrix |
//| BFMatrix - basis functions evaluator |
//| C - preallocated array[N + NX + 1, NY] |
//| timeDDMSolve, |
//| timeCorrSolve - on input contain already accumulated timings |
//| for DDM and CORR parts |
//| OUTPUT PARAMETERS: |
//| C - rows 0..N - 1 contain spline coefficients rows |
//| N..N + NX are filled by zeros |
//| timeDDMSolve, |
//| timeCorrSolve - updated with new timings |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverRun(CRBF3DDMSolver &solver,
CMatrixDouble &res,
int n,
int nx,
int ny,
CSparseMatrix &sp,
CRBF3Evaluator &bfmatrix,
CMatrixDouble &upd,
int &timeddmsolve,
int &timecorrsolve)
{
//--- create variables
CRBF3DDMSubproblem subproblem;
CMatrixDouble c;
CRowDouble x0;
CRowDouble x1;
CRowDouble refrhs1;
CMatrixDouble updt;
CAblasF::RSetAllocM(ny,n+nx+1,0.0,updt);
CAblasF::RSetAllocM(n+nx+1,ny,0.0,c);
//--- Solve DDM part:
//--- * run recursive procedure that computes DDM part.
//--- * clean-up: move processed subproblems from Solver.SubproblemsBuffer back to Solver.SubproblemsPool
//--- * multiply solution by the preconditioner matrix
timeddmsolve-=((int)(GetTickCount()/10000));
DDMSolverRunRec(solver,res,n,nx,ny,c,solver.m_subproblemscnt);
for(int i=0; i<solver.m_subproblemscnt; i++)
{
subproblem=solver.m_subproblemsbuffer;
//--- check
if(!CAp::Assert(subproblem.m_isvalid,__FUNCTION__+": integrity check 5223 failed"))
return;
solver.m_subproblemspool=subproblem;
}
timeddmsolve+=((int)(GetTickCount()/10000));
CAblasF::RAllocV(n+nx+1,x0);
CAblasF::RAllocV(n+nx+1,x1);
for(int j=0; j<ny; j++)
{
CAblasF::RCopyCV(n+nx+1,c,j,x0);
CSparse::SparseGemV(sp,1.0,1,x0,0,0.0,x1,0);
CAblasF::RCopyVR(n+nx+1,x1,updt,j);
}
//--- Compute correction spline that fixes oscillations introduced by the DDM part
timecorrsolve-=((int)(GetTickCount()/10000));
CAblasF::RAllocV(solver.m_ncorrector+nx+1,x0);
CAblasF::RAllocV(n+nx+1,x1);
for(int j=0; j<ny; j++)
{
//--- Prepare right-hand side for the QR solver
CAblasF::RSetAllocV(solver.m_ncorrector+nx+1,0.0,refrhs1);
CAblasF::RCopyRV(n+nx+1,updt,j,x1);
ModelMatrixComputeProductAtNodes(bfmatrix,x1,solver.m_corrnodes,solver.m_ncorrector,refrhs1);
for(int i=0; i<solver.m_ncorrector; i++)
{
refrhs1.Set(i,res.Get(solver.m_corrnodes[i],j)-refrhs1[i]);
for(int k=0; k<nx; k++)
refrhs1.Add(i,- solver.m_corrx.Get(i,k)*x1[n+k]);
refrhs1.Add(i,- x1[n+nx]-solver.m_lambdav*x1[solver.m_corrnodes[i]]);
}
//--- Solve QR-factorized system
CAblasF::RGemV(solver.m_ncorrector+nx+1,solver.m_ncorrector+nx+1,1.0,solver.m_corrq,1,refrhs1,0.0,x0);
CAblas::RMatrixTrsVect(solver.m_ncorrector+nx+1,solver.m_corrr,0,0,true,false,0,x0,0);
for(int i=0; i<solver.m_ncorrector; i++)
updt.Add(j,solver.m_corrnodes[i],x0[i]);
for(int i=0; i<=nx; i++)
updt.Add(j,n+i,x0[solver.m_ncorrector+i]);
}
timecorrsolve+=((int)(GetTickCount()/10000));
CAblasF::RAllocM(n+nx+1,ny,upd);
CAblas::RMatrixTranspose(ny,n+nx+1,updt,0,0,upd,0,0);
}
//+------------------------------------------------------------------+
//| This function is a specialized version of DDMSolverRun() for |
//| NY = 1. |
//+------------------------------------------------------------------+
void CRBFV3::DDMSolverRun1(CRBF3DDMSolver &solver,
CRowDouble &res,
int n,
int nx,
CSparseMatrix &sp,
CRBF3Evaluator &bfmatrix,
CRowDouble &upd,
int &timeddmsolve,
int &timecorrsolve)
{
CAblasF::RAllocM(n,1,solver.m_tmpres1);
CAblasF::RCopyVC(n,res,solver.m_tmpres1,0);
DDMSolverRun(solver,solver.m_tmpres1,n,nx,1,sp,bfmatrix,solver.m_tmpres1,timeddmsolve,timecorrsolve);
CAblasF::RAllocV(n+nx+1,upd);
CAblasF::RCopyCV(n+nx+1,solver.m_tmpres1,0,upd);
}
//+------------------------------------------------------------------+
//| Automatically detect scale parameter as a mean distance towards |
//| nearest neighbor (not counting nearest neighbors that are too |
//| close) |
//| PARAMETERS: |
//| XX - dataset(X - values), array[N, NX] |
//| N - points count, N >= 1 |
//| NX - dimensions count, NX >= 1 |
//| RESULT: |
//| suggested scale |
//+------------------------------------------------------------------+
double CRBFV3::AutoDetectScaleParameter(CMatrixDouble &xx,int n,int nx)
{
//--- create variables
double result=0;
int nq=0;
int nlocal=0;
CKDTree kdt;
CRowDouble x;
CRowDouble d;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 7624 failed"))
return(0);
CAblasF::RAllocV(nx,x);
CNearestNeighbor::KDTreeBuild(xx,n,nx,0,2,kdt);
nlocal=(int)MathRound(MathPow(2,nx)+1);
result=0;
for(int i=0; i<n; i++)
{
//--- Query a batch of nearest neighbors
CAblasF::RCopyRV(nx,xx,i,x);
nq=CNearestNeighbor::KDTreeQueryKNN(kdt,x,nlocal,true);
//--- check
if(!CAp::Assert(nq>=1,__FUNCTION__+": integrity check 7625 failed"))
return(0);
CNearestNeighbor::KDTreeQueryResultsDistances(kdt,d);
//--- In order to filter out nearest neighbors that are too close,
//--- we use distance R toward most distant of NQ nearest neighbors as
//--- a reference and select nearest neighbor with distance >=0.5*R/NQ
for(int j=0; j<nq; j++)
{
if(d[j]>=(0.5*d[nq-1]/nq))
{
result+=d[j];
break;
}
}
}
result/=n;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Recursive functions matrix computation |
//+------------------------------------------------------------------+
void CRBFV3::ComputeBFMatrixRec(CMatrixDouble &xx,
int range0,
int range1,
int n,
int nx,
int functype,
double funcparam,
CMatrixDouble &f)
{
//--- create variables
double v=0;
double vv=0;
double elemcost=0;
double alpha2=0;
CRowDouble temp;
//--- check
if(!CAp::Assert(functype==1 || functype==2 || functype==3,__FUNCTION__+": unexpected FuncType"))
return;
//--- Serial processing
alpha2=funcparam*funcparam;
for(int i=range0; i<range1; i++)
{
for(int j=i; j<n; j++)
{
temp=xx[i]-xx[j];
v=CAblasF::RDotV2(nx,temp);
switch(functype)
{
case 1:
v=-MathSqrt(v+alpha2);
break;
case 2:
if(v!=0.0)
v=v*0.5*MathLog(v);
else
v=0.0;
break;
case 3:
v=v*MathSqrt(v);
break;
}
f.Set(i,j,v);
f.Set(j,i,v);
}
}
}
//+------------------------------------------------------------------+
//| This function computes basis functions matrix (both upper and |
//| lower triangles) |
//| [ f(dist(x0, x0)) ... f(dist(x0, x(n - 1))) ]|
//| [ f(dist(x1, x0)) ... f(dist(x1, x(n - 1))) ]|
//| [ ............................................................. ]|
//| [ f(dist(x(n - 1), x0)) ... f(dist(x(n - 1), x(n - 1))) ]|
//| NOTE: if F is large enough to store result, it is not reallocated|
//| Values outside of [0, N) x[0, N) range are not modified. |
//+------------------------------------------------------------------+
void CRBFV3::ComputeBFMatrix(CMatrixDouble &xx,int n,int nx,
int functype,double funcparam,
CMatrixDouble &f)
{
CAblasF::RAllocM(n,n,f);
ComputeBFMatrixRec(xx,0,n,n,nx,functype,funcparam,f);
}
//+------------------------------------------------------------------+
//| Initializes model matrix using specified matrix storage format: |
//| * StorageType = 0 a N * N matrix of basis function values is |
//| stored |
//| * StorageType = 1 basis function values are recomputed on |
//| demand |
//+------------------------------------------------------------------+
void CRBFV3::ModelMatrixInit(CMatrixDouble &xx,
int n,
int nx,
int functype,
double funcparam,
int storagetype,
CRBF3Evaluator &modelmatrix)
{
//--- create variables
int nchunks=0;
int srcoffs=0;
int dstoffs=0;
int curlen=0;
CRBF3EvaluatorBuffer bufseed;
//--- check
if(!CAp::Assert(storagetype==0 || storagetype==1,__FUNCTION__+": unexpected StorageType for ModelMatrixInit()"))
return;
modelmatrix.m_n=n;
modelmatrix.m_storagetype=storagetype;
switch(storagetype)
{
case 0:
ComputeBFMatrix(xx,n,nx,functype,funcparam,modelmatrix.m_f);
break;
case 1:
//--- Save model parameters
modelmatrix.m_nx=nx;
modelmatrix.m_functype=functype;
modelmatrix.m_funcparam=funcparam;
modelmatrix.m_chunksize=128;
//--- Prepare temporary buffers
bufseed=modelmatrix.m_bufferpool;
CAblasF::RSetAllocV(modelmatrix.m_chunksize,1.0,modelmatrix.m_chunk1);
//--- Store dataset in the chunked row storage format (rows with size at most ChunkSize, one row per dimension/chunk)
CAblasF::IAllocV(n,modelmatrix.m_entireset);
for(int i=0; i<n; i++)
modelmatrix.m_entireset.Set(i,i);
CAblasF::RCopyAllocM(n,nx,xx,modelmatrix.m_x);
nchunks=CApServ::IDivUp(n,modelmatrix.m_chunksize);
CAblasF::RSetAllocM(nchunks*nx,modelmatrix.m_chunksize,0.0,modelmatrix.m_xtchunked);
srcoffs=0;
dstoffs=0;
while(srcoffs<n)
{
curlen=MathMin(modelmatrix.m_chunksize,n-srcoffs);
for(int i=0; i<=curlen-1; i++)
{
for(int j=0; j<nx; j++)
modelmatrix.m_xtchunked.Set(dstoffs+j,i,xx.Get(srcoffs+i,j));
}
srcoffs+=curlen;
dstoffs+=nx;
}
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| Computes subset of the model matrix (subset of rows, subset of |
//| columns) and writes result to R. |
//| NOTE: If R is longer than M0xM1, it is not reallocated and |
//| additional elements are not modified. |
//+------------------------------------------------------------------+
void CRBFV3::ModelMatrixComputePartial(CRBF3Evaluator &modelmatrix,
CRowInt &ridx,
int m0,
CRowInt &cidx,
int m1,
CMatrixDouble &r)
{
//--- create variables
int ni=0;
int nj=0;
double v=0;
double vv=0;
//--- check
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
return;
CAblasF::RAllocM(m0,m1,r);
switch(modelmatrix.m_storagetype)
{
case 0:
for(int i=0; i<m0; i++)
{
ni=ridx[i];
for(int j=0; j<m1; j++)
r.Set(i,j,modelmatrix.m_f.Get(ni,cidx[j]));
}
break;
case 1:
//--- check
if(!CAp::Assert(modelmatrix.m_functype==1 || modelmatrix.m_functype==2,__FUNCTION__+": unexpected FuncType"))
return;
for(int i=0; i<m0; i++)
{
ni=ridx[i];
for(int j=0; j<m1; j++)
{
nj=cidx[j];
v=0;
switch(modelmatrix.m_functype)
{
case 1:
v=modelmatrix.m_funcparam*modelmatrix.m_funcparam;
break;
case 2:
v=1.0E-50;
break;
}
for(int k=0; k<modelmatrix.m_nx; k++)
{
vv=modelmatrix.m_x.Get(ni,k)-modelmatrix.m_x.Get(nj,k);
v=v+vv*vv;
}
switch(modelmatrix.m_functype)
{
case 1:
v=-MathSqrt(v);
break;
case 2:
v=v*0.5*MathLog(v);
break;
}
r.Set(i,j,v);
}
}
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//--- This function computes ChunkSize basis function values and stores |
//| them in the evaluator buffer. This function does not modify |
//| Evaluator object, thus it can be used in multiple threads with |
//| the same evaluator as long as different buffers are used. |
//| INPUT PARAMETERS: |
//| Evaluator - evaluator object |
//| X - origin point |
//| Buf - preallocated buffers. Following fields are used |
//| and must have at least ChunkSize elements: |
//| * Buf.FuncBuf |
//| * Buf.WrkBuf |
//| When NeedGradInfo >= 1, additionally we need the following fields|
//| to be preallocated: |
//| * Buf.MinDist2 - array[ChunkSize], filled by |
//| some positive values; on the |
//| very first call it is 1.0E50 |
//| or something comparably large |
//| * Buf.DeltaBuf - array[NX, ChunkSize] |
//| * Buf.DF1 - array[ChunkSize] |
//| When NeedGradInfo >= 2, additionally we need the following fields|
//| to be preallocated: |
//| * Buf.DF2 - array[ChunkSize] |
//| ChunkSize - amount of basis functions to compute, |
//| 0 < ChunkSize <= Evaluator.ChunkSize |
//| ChunkIdx - index of the chunk in Evaluator.XTChunked times |
//| NX |
//| Distance0 - strictly positive value that is added to the |
//| squared distance prior to passing it to the |
//| multiquadric kernel function. For other kernels-|
//| set it to small nonnegative value like 1.0E-50. |
//| NeedGradInfo - whether gradient - related information is needed|
//| or not: |
//| * if 0, only FuncBuf is set on exit |
//| * if 1, MinDist2, DeltaBuf and DF1 are also set |
//| on exit |
//| * if 2, additionally DF2 is set on exit |
//| OUTPUT PARAMETERS: |
//| Buf.FuncBuf - array[ChunkSize], basis function values |
//| Buf.MinDist2 - array[ChunkSize], if NeedGradInfo >= 1 then its |
//| I-th element is updated as |
//| MinDist2[I]:= min(MinDist2[I], DISTANCE_SQUARED(X, CENTER[I])) |
//| Buf.DeltaBuf - array[NX, ChunkSize], if NeedGradInfo >= 1 then |
//| J-th element of K-th row is set to |
//| X[K] - CENTER[J, K] |
//| Buf.DF1 - array[ChunkSize], if NeedGradInfo >= 1 then |
//| J-th element is derivative of the kernel |
//| function with respect to its input (squared |
//| distance) |
//| Buf.DF2 - array[ChunkSize], if NeedGradInfo >= 2 then |
//| J-th element is derivative of the kernel |
//| function with respect to its input (squared |
//| distance) |
//+------------------------------------------------------------------+
void CRBFV3::ComputeRowChunk(CRBF3Evaluator &evaluator,
CRowDouble &x,
CRBF3EvaluatorBuffer &buf,
int chunksize,
int chunkidx,
double distance0,
int needgradinfo)
{
//--- create variables
double r2=0;
double lnr=0;
//--- Compute squared distance in Buf.FuncBuf
CAblasF::RSetV(chunksize,distance0,buf.m_funcbuf);
for(int k=0; k<evaluator.m_nx; k++)
{
CAblasF::RSetV(chunksize,x[k],buf.m_wrkbuf);
CAblasF::RAddRV(chunksize,-1.0,evaluator.m_xtchunked,chunkidx+k,buf.m_wrkbuf);
CAblasF::RMulAddV(chunksize,buf.m_wrkbuf,buf.m_wrkbuf,buf.m_funcbuf);
if(needgradinfo>=1)
CAblasF::RCopyVR(chunksize,buf.m_wrkbuf,buf.m_deltabuf,k);
}
if(needgradinfo>=1)
CAblasF::RMergeMinV(chunksize,buf.m_funcbuf,buf.m_mindist2);
//--- Apply kernel function
switch(evaluator.m_functype)
{
case 1:
//--- f=-sqrt(r^2+alpha^2), including f=-r as a special case
switch(needgradinfo)
{
case 0:
//--- Only target f(r2)=-sqrt(r2) is needed
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
break;
case 1:
//--- First derivative is needed:
//--- f(r2) = -sqrt(r2)
//--- f'(r2) = -0.5/sqrt(r2)
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle zero value as a special case
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
CAblasF::RSetV(chunksize,0.5,buf.m_df1);
CAblasF::RMergeDivV(chunksize,buf.m_funcbuf,buf.m_df1);
break;
case 2:
//--- Second derivatives is needed:
//--- f(r2) = -sqrt(r2+alpha2)
//--- f'(r2) = -0.5/sqrt(r2+alpha2)
//--- f''(r2) = 0.25/((r2+alpha2)^(3/2))
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle zero value as a special case
CAblasF::RCopyMulV(chunksize,-2.0,buf.m_funcbuf,buf.m_wrkbuf);
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
CAblasF::RSetV(chunksize,0.5,buf.m_df1);
CAblasF::RMergeDivV(chunksize,buf.m_funcbuf,buf.m_df1);
CAblasF::RCopyV(chunksize,buf.m_df1,buf.m_df2);
CAblasF::RMergeDivV(chunksize,buf.m_wrkbuf,buf.m_df2);
break;
}
break;
case 2:
//--- f=r^2*ln(r)
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle ln(0) as a special case.
switch(needgradinfo)
{
case 0:
//--- No gradient info is required
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle zero value as a special case
for(int k=0; k<chunksize; k++)
buf.m_funcbuf.Mul(k,0.5*MathLog(buf.m_funcbuf[k]));
break;
case 1:
//--- First derivative is needed:
//--- f(r2) = 0.5*r2*ln(r2)
//--- f'(r2) = 0.5*ln(r2) + 0.5 = 0.5*(ln(r2)+1) =ln(r)+0.5
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle zero value as a special case
for(int k=0; k<chunksize; k++)
{
r2=buf.m_funcbuf[k];
lnr=0.5*MathLog(r2);
buf.m_funcbuf.Set(k,r2*lnr);
buf.m_df1.Set(k,lnr+0.5);
}
break;
case 2:
//--- Second derivative is needed:
//--- f(r2) = 0.5*r2*ln(r2)
//--- f'(r2) = 0.5*ln(r2) + 0.5 = 0.5*(ln(r2)+1) =ln(r)+0.5
//--- f''(r2)= 0.5/r2
//--- NOTE: FuncBuf[] is always positive due to small correction added,
//--- thus we have no need to handle zero value as a special case
for(int k=0; k<=chunksize-1; k++)
{
r2=buf.m_funcbuf[k];
lnr=0.5*MathLog(r2);
buf.m_funcbuf.Set(k,r2*lnr);
buf.m_df1.Set(k,lnr+0.5);
buf.m_df2.Set(k,0.5/r2);
}
break;
}
break;
default:
CAp::Assert(false,__FUNCTION__+": unexpected FuncType in ComputeRowChunk()");
}
}
//+------------------------------------------------------------------+
//| Recursive subroutine for parallel divide-and-conquer computation |
//| of matrix-vector product with coefficients vector. Works only for|
//| on-the-fly models with StorageType = 1 |
//+------------------------------------------------------------------+
void CRBFV3::ModelMatrixComputeProductRec(CRBF3Evaluator &modelmatrix,
CRowDouble &c,
CRowInt &rowidx,
CRowDouble &r,
int idx0,
int idx1,
bool toplevelcall)
{
//--- create variables
int s0=0;
int s1=0;
int colidx=0;
int curchunk=0;
int srcidx=0;
double distance0=0;
CRBF3EvaluatorBuffer buf;
//--- check
if(!CAp::Assert(modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
return;
//--- Now split column indexes
//--- check
if(!CAp::Assert(modelmatrix.m_functype==1 || modelmatrix.m_functype==2,__FUNCTION__+": unexpected FuncType"))
return;
buf=modelmatrix.m_bufferpool;
CAblasF::RSetAllocV(modelmatrix.m_nx,0.0,buf.m_x);
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_coeffbuf);
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_funcbuf);
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_wrkbuf);
colidx=0;
srcidx=0;
distance0=1.0E-50;
if(modelmatrix.m_functype==1)
{
//--- Kernels that add squared parameter to the squared distance
distance0=CMath::Sqr(modelmatrix.m_funcparam);
}
while(colidx<modelmatrix.m_n)
{
//--- Handle basecase with size at most ChunkSize*ChunkSize
curchunk=MathMin(modelmatrix.m_chunksize,modelmatrix.m_n-colidx);
CAblasF::RCopyVX(curchunk,c,colidx,buf.m_coeffbuf,0);
for(int i=idx0; i<idx1; i++)
{
CAblasF::RCopyRV(modelmatrix.m_nx,modelmatrix.m_x,rowidx[i],buf.m_x);
ComputeRowChunk(modelmatrix,buf.m_x,buf,curchunk,srcidx,distance0,0);
r.Add(i,CAblasF::RDotV(curchunk,buf.m_funcbuf,buf.m_coeffbuf));
}
colidx+=curchunk;
srcidx+=modelmatrix.m_nx;
}
modelmatrix.m_bufferpool=buf;
}
//+------------------------------------------------------------------+
//| Computes product of the model matrix with vector C, writes result|
//| to R. |
//| NOTE: this function is thread safe and can be used with the same |
//| model matrix from different threads |
//| NOTE: If R is longer than M, it is not reallocated and additional|
//| elements are not modified. |
//+------------------------------------------------------------------+
void CRBFV3::ModelMatrixComputeProduct(CRBF3Evaluator &modelmatrix,
CRowDouble &c,
CRowDouble &r)
{
//--- check
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
return;
CAblasF::RAllocV(modelmatrix.m_n,r);
switch(modelmatrix.m_storagetype)
{
case 0:
CAblas::RMatrixGemVect(modelmatrix.m_n,modelmatrix.m_n,1.0,modelmatrix.m_f,0,0,0,c,0,0.0,r,0);
break;
case 1:
CAblasF::RSetV(modelmatrix.m_n,0.0,r);
ModelMatrixComputeProductRec(modelmatrix,c,modelmatrix.m_entireset,r,0,modelmatrix.m_n,true);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
break;
}
}
//+------------------------------------------------------------------+
//| Computes product of the subset of the model matrix (only rows |
//| with indexes from Idx[]) with vector C, writes result to R. |
//| NOTE: this function is thread safe and can be used with the same |
//| model matrix from different threads |
//| NOTE: If R is longer than M, it is not reallocated and additional|
//| elements are not modified. |
//+------------------------------------------------------------------+
void CRBFV3::ModelMatrixComputeProductAtNodes(CRBF3Evaluator &modelmatrix,
CRowDouble &c,
CRowInt &idx,
int m,
CRowDouble &r)
{
//--- check
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
return;
CAblasF::RAllocV(m,r);
switch(modelmatrix.m_storagetype)
{
case 0:
for(int i=0; i<m; i++)
r.Set(i,CAblasF::RDotVR(modelmatrix.m_n,c,modelmatrix.m_f,idx[i]));
break;
case 1:
CAblasF::RSetV(m,0.0,r);
ModelMatrixComputeProductRec(modelmatrix,c,idx,r,0,m,true);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| Checks whether basis function is conditionally positive definite |
//| or not, given the polynomial term type (ATerm = 1 means linear, |
//| ATerm = 2 means constant, ATerm = 3 means no polynomial term). |
//+------------------------------------------------------------------+
bool CRBFV3::IsCPDFunction(int functype,
int aterm)
{
bool result=false;
//--- check
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": integrity check 3563 failed"))
return(false);
switch(functype)
{
case 1:
result=(aterm==2 || aterm==1);
break;
case 2:
result=(aterm==1);
break;
default:
CAp::Assert(false,"IsCPDFunction: unexpected FuncType");
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Buffer object which is used to perform RBF model calculation in |
//| the multithreaded mode (multiple threads working with same RBF |
//| object). |
//| This object should be created with RBFCreateCalcBuffer(). |
//+------------------------------------------------------------------+
struct CRBFCalcBuffer
{
int m_modelversion;
CRowDouble m_dy;
CRowDouble m_x;
CRowDouble m_y;
CRBFV3CalcBuffer m_bufv3;
CRBFV2CalcBuffer m_bufv2;
CRBFV1CalcBuffer m_bufv1;
//--- constructor / destructor
CRBFCalcBuffer(void) { m_modelversion=0; }
~CRBFCalcBuffer(void) {}
//---
void Copy(const CRBFCalcBuffer&obj);
//--- overloading
void operator=(const CRBFCalcBuffer&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFCalcBuffer::Copy(const CRBFCalcBuffer &obj)
{
m_modelversion=obj.m_modelversion;
m_dy=obj.m_dy;
m_x=obj.m_x;
m_y=obj.m_y;
m_bufv3=obj.m_bufv3;
m_bufv2=obj.m_bufv2;
m_bufv1=obj.m_bufv1;
}
//+------------------------------------------------------------------+
//| RBF model. |
//| Never try to directly work with fields of this object - always |
//| use ALGLIB functions to use this object. |
//+------------------------------------------------------------------+
struct CRBFModel
{
int m_algorithmtype;
int m_aterm;
int m_bftype;
int m_maxits;
int m_modelversion;
int m_n;
int m_nlayers;
int m_nnmaxits;
int m_nx;
int m_ny;
int m_progress10000;
double m_bfparam;
double m_epserr;
double m_epsort;
double m_lambdav;
double m_radvalue;
double m_radzvalue;
bool m_hasscale;
bool m_terminationrequest;
CRBFV1Model m_model1;
CRowDouble m_s;
CRBFV3Model m_model3;
CRBFV2Model m_model2;
CRBFCalcBuffer m_calcbuf;
CMatrixDouble m_x;
CMatrixDouble m_y;
//--- constructor / destructor
CRBFModel(void);
~CRBFModel(void) {}
//--- copy
void Copy(const CRBFModel&obj);
//--- overloading
void operator=(const CRBFModel&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CRBFModel::CRBFModel(void)
{
m_algorithmtype=0;
m_aterm=0;
m_bftype=0;
m_maxits=0;
m_modelversion=0;
m_n=0;
m_nlayers=0;
m_nnmaxits=0;
m_nx=0;
m_ny=0;
m_progress10000=0;
m_bfparam=0;
m_epserr=0;
m_epsort=0;
m_lambdav=0;
m_radvalue=0;
m_radzvalue=0;
m_hasscale=false;
m_terminationrequest=false;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFModel::Copy(const CRBFModel &obj)
{
m_algorithmtype=obj.m_algorithmtype;
m_aterm=obj.m_aterm;
m_bftype=obj.m_bftype;
m_maxits=obj.m_maxits;
m_modelversion=obj.m_modelversion;
m_n=obj.m_n;
m_nlayers=obj.m_nlayers;
m_nnmaxits=obj.m_nnmaxits;
m_nx=obj.m_nx;
m_ny=obj.m_ny;
m_progress10000=obj.m_progress10000;
m_bfparam=obj.m_bfparam;
m_epserr=obj.m_epserr;
m_epsort=obj.m_epsort;
m_lambdav=obj.m_lambdav;
m_radvalue=obj.m_radvalue;
m_radzvalue=obj.m_radzvalue;
m_hasscale=obj.m_hasscale;
m_terminationrequest=obj.m_terminationrequest;
m_model1=obj.m_model1;
m_s=obj.m_s;
m_model3=obj.m_model3;
m_model2=obj.m_model2;
m_calcbuf=obj.m_calcbuf;
m_x=obj.m_x;
m_y=obj.m_y;
}
//+------------------------------------------------------------------+
//| RBF solution report: |
//| * TerminationType - termination type, positive values - |
//| success, non-positive - failure. |
//| Fields which are set by modern RBF solvers (hierarchical): |
//| * RMSError - root-mean-square error; NAN for old solvers |
//| (ML, QNN) |
//| * MaxError - maximum error; NAN for old solvers (ML, QNN) |
//+------------------------------------------------------------------+
struct CRBFReport
{
int m_acols;
int m_annz;
int m_arows;
int m_iterationscount;
int m_nmv;
int m_terminationtype;
double m_maxerror;
double m_rmserror;
//--- constructor / destructor
CRBFReport(void) { ZeroMemory(this); }
~CRBFReport(void) {}
//--- copy
void Copy(const CRBFReport&obj);
//--- overloading
void operator=(const CRBFReport&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CRBFReport::Copy(const CRBFReport &obj)
{
m_acols=obj.m_acols;
m_annz=obj.m_annz;
m_arows=obj.m_arows;
m_iterationscount=obj.m_iterationscount;
m_nmv=obj.m_nmv;
m_terminationtype=obj.m_terminationtype;
m_maxerror=obj.m_maxerror;
m_rmserror=obj.m_rmserror;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CRBF
{
public:
//--- constants
static const double m_eps;
static const double m_rbffarradius;
static const int m_rbffirstversion;
static const int m_rbfversion2;
static const int m_rbfversion3;
static void RBFCreate(int nx,int ny,CRBFModel&s);
static void RBFCreateCalcBuffer(CRBFModel&s,CRBFCalcBuffer&buf);
static void RBFSetPoints(CRBFModel&s,CMatrixDouble&xy,int n);
static void RBFSetPointsAndScales(CRBFModel&r,CMatrixDouble&xy,int n,CRowDouble&s);
static void RBFSetAlgoQNN(CRBFModel&s,double q,double z);
static void RBFSetAlgoMultilayer(CRBFModel&s,double rbase,int nlayers,double lambdav);
static void RBFSetAlgoHierarchical(CRBFModel&s,double rbase,int nlayers,double lambdans);
static void RBFSetAlgoThinPlateSpline(CRBFModel&s,double lambdav);
static void RBFSetAlgoMultiQuadricManual(CRBFModel&s,double alpha,double lambdav);
static void RBFSetAlgoMultiQuadricAuto(CRBFModel&s,double lambdav);
static void RBFSetAlgoBiharmonic(CRBFModel&s,double lambdav);
static void RBFSetLinTerm(CRBFModel&s);
static void RBFSetConstTerm(CRBFModel&s);
static void RBFSetZeroTerm(CRBFModel&s);
static void RBFSetV2BF(CRBFModel&s,int bf);
static void RBFSetV2Its(CRBFModel&s,int maxits);
static void RBFSetV2SupportR(CRBFModel&s,double r);
static void RBFSetCond(CRBFModel&s,double epsort,double epserr,int maxits);
static void RBFBuildModel(CRBFModel&s,CRBFReport&rep);
static double RBFCalc1(CRBFModel&s,double x0);
static double RBFCalc2(CRBFModel&s,double x0,double x1);
static double RBFCalc3(CRBFModel&s,double x0,double x1,double x2);
static void RBFDiff1(CRBFModel&s,double x0,double&y,double&dy0);
static void RBFDiff2(CRBFModel&s,double x0,double x1,double&y,double&dy0,double&dy1);
static void RBFDiff3(CRBFModel&s,double x0,double x1,double x2,double&y,double&dy0,double&dy1,double&dy2);
static void RBFCalc(CRBFModel&s,CRowDouble&x,CRowDouble&y);
static void RBFDiff(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFHess(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFCalcBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y);
static void RBFDiffBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFHessBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFTSCalcBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y);
static void RBFTSDiffBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
static void RBFTSHessBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
static void RBFGridCalc2(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
static void RBFGridCalc2V(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&y);
static void RBFGridCalc2VSubset(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,bool &flagy[],CRowDouble&y);
static void RBFGridCalc3V(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&y);
static void RBFGridCalc3VSubset(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,bool &flagy[],CRowDouble&y);
static void RBFGridCalc2VX(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,bool &flagy[],bool sparsey,CRowDouble&y);
static void RBFGridCalc3VX(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,bool &flagy[],bool sparsey,CRowDouble&y);
static void RBFUnpack(CRBFModel&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v,int &modelversion);
static int RBFGetModelVersion(CRBFModel&s);
static double RBFPeekProgress(CRBFModel&s);
static void RBFRequestTermination(CRBFModel&s);
static void RBFAlloc(CSerializer&s,CRBFModel&model);
static void RBFSerialize(CSerializer&s,CRBFModel&model);
static void RBFUnserialize(CSerializer&s,CRBFModel&model);
private:
static void RBFPrepareNonSerializableFields(CRBFModel&s);
static void InitializeV1(int nx,int ny,CRBFV1Model&s);
static void InitializeV2(int nx,int ny,CRBFV2Model&s);
static void InitializeV3(int nx,int ny,CRBFV3Model&s);
static void ClearReportFields(CRBFReport&rep);
};
//+------------------------------------------------------------------+
//| Constants |
//+------------------------------------------------------------------+
const double CRBF::m_eps=1.0E-6;
const double CRBF::m_rbffarradius=6;
const int CRBF::m_rbffirstversion=0;
const int CRBF::m_rbfversion2=2;
const int CRBF::m_rbfversion3=3;
//+------------------------------------------------------------------+
//| This function creates RBF model for a scalar(NY = 1) or vector |
//| (NY > 1) function in a NX - dimensional space(NX >= 1). |
//| Newly created model is empty. It can be used for interpolation |
//| right after creation, but it just returns zeros. You have to add |
//| points to the model, tune interpolation settings, and then call |
//| model construction function RBFBuildModel() which will update |
//| model according to your specification. |
//| USAGE: |
//| 1. User creates model with RBFCreate() |
//| 2. User adds dataset with RBFSetPoints() or |
//| RBFSetPointsAndScales() |
//| 3. User selects RBF solver by calling: |
//| * RBFSetAlgoHierarchical() - for a HRBF solver, a |
//| hierarchical large - scale Gaussian RBFs (works well for |
//| uniformly distributed point clouds, but may fail when the |
//| data are non-uniform; use other solvers below in such |
//| cases) |
//| * RBFSetAlgoThinPlateSpline() - for a large - scale DDM-RBF |
//| solver with thin plate spline basis function being used |
//| * RBFSetAlgoBiharmonic() - for a large-scale DDM-RBF solver |
//| with biharmonic basis function being used |
//| * RBFSetAlgoMultiQuadricAuto() - for a large-scale DDM-RBF |
//| solver with multiquadric basis function being used |
//| (automatic selection of the scale parameter Alpha) |
//| * RBFSetAlgoMultiQuadricManual() - for a large-scale DDM-RBF|
//| solver with multiquadric basis function being used (manual|
//| selection of the scale parameter Alpha) |
//| 4.(OPTIONAL) User chooses polynomial term by calling: |
//| * RBFLinTerm() to set linear term (default) |
//| * RBFConstTerm() to set constant term |
//| * RBFZeroTerm() to set zero term |
//| 5. User calls RBFBuildModel() function which rebuilds model |
//| according to the specification |
//| INPUT PARAMETERS: |
//| NX - dimension of the space, NX >= 1 |
//| NY - function dimension, NY >= 1 |
//| OUTPUT PARAMETERS: |
//| S - RBF model(initially equals to zero) |
//| NOTE 1: memory requirements. RBF models require amount of memory |
//| which is proportional to the number of data points. Some |
//| additional memory is allocated during model construction,|
//| but most of this memory is freed after the model |
//| coefficients are calculated. Amount of this additional |
//| memory depends on model construction algorithm being used|
//+------------------------------------------------------------------+
void CRBF::RBFCreate(int nx,int ny,CRBFModel &s)
{
//--- check
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
return;
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
return;
s.m_nx=nx;
s.m_ny=ny;
RBFPrepareNonSerializableFields(s);
//--- Select default model version according to NX.
//--- The idea is that when we call this function with NX=2 or NX=3, backward
//--- compatible dummy (zero) V1 model is created, so serialization produces
//--- model which are compatible with pre-3.11 ALGLIB.
InitializeV1(nx,ny,s.m_model1);
InitializeV2(nx,ny,s.m_model2);
InitializeV3(nx,ny,s.m_model3);
if(nx==2 || nx==3)
s.m_modelversion=1;
else
s.m_modelversion=2;
//--- Report fields
s.m_progress10000=0;
s.m_terminationrequest=false;
//--- Prepare buffers
RBFCreateCalcBuffer(s,s.m_calcbuf);
}
//+------------------------------------------------------------------+
//| This function creates buffer structure which can be used to |
//| perform parallel RBF model evaluations (with one RBF model |
//| instance being used from multiple threads, as long as different |
//| threads use different instances of the buffer). |
//| This buffer object can be used with RBFTSCalcBuf() function (here|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
//| function which reuses previously allocated output space). |
//| A buffer creation function (this function) is also thread-safe. |
//| I.e. you may safely create multiple buffers for the same RBF |
//| model from multiple threads. |
//| NOTE: the buffer object is just a collection of several |
//| preallocated dynamic arrays and precomputed values. If you |
//| delete its "parent" RBF model when the buffer is still |
//| alive, nothing bad will happen (no dangling pointers or |
//| resource leaks). The buffer will simply become useless. |
//| How to use it: |
//| * create RBF model structure with RBFCreate() |
//| * load data, tune parameters |
//| * call RBFBuildModel() |
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
//| model (you should call this function only AFTER call to |
//| RBFBuildModel(), see below for more information) |
//| * call RBFTSCalcBuf() from different threads, with each thread |
//| working with its own copy of buffer object. |
//| * it is recommended to reuse buffer as much as possible because|
//| buffer creation involves allocation of several large dynamic |
//| arrays. It is a huge waste of resource to use it just once. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| Buf - external buffer. |
//| IMPORTANT: buffer object should be used only with RBF model |
//| object which was used to initialize buffer. Any |
//| attempt to use buffer with different object is |
//| dangerous - you may get memory violation error because|
//| sizes of internal arrays do not fit to dimensions of |
//| RBF structure. |
//| IMPORTANT: you should call this function only for model which was|
//| built with RBFBuildModel() function, after successful |
//| invocation of RBFBuildModel(). Sizes of some |
//| internal structures are determined only after model is|
//| built, so buffer object created before model |
//| construction stage will be useless (and any attempt to|
//| use it will result in exception). |
//+------------------------------------------------------------------+
void CRBF::RBFCreateCalcBuffer(CRBFModel &s,CRBFCalcBuffer &buf)
{
switch(s.m_modelversion)
{
case 1:
buf.m_modelversion=1;
CRBFV1::RBFV1CreateCalcBuffer(s.m_model1,buf.m_bufv1);
break;
case 2:
buf.m_modelversion=2;
CRBFV2::RBFV2CreateCalcBuffer(s.m_model2,buf.m_bufv2);
break;
case 3:
buf.m_modelversion=3;
CRBFV3::RBFV3CreateCalcBuffer(s.m_model3,buf.m_bufv3);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function adds dataset. |
//| This function overrides results of the previous calls, i.e. |
//| multiple calls of this function will result in only the last set |
//| being added. |
//| IMPORTANT: ALGLIB version 3.11 and later allows you to specify a |
//| set of per-dimension scales. Interpolation radii are |
//| multiplied by the scale vector. It may be useful if |
//| you have mixed spatio - temporal data (say, a set of |
//| 3D slices recorded at different times). You should |
//| call RBFSetPointsAndScales() function to use this |
//| feature. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call. |
//| XY - points, array[N, NX + NY]. One row corresponds to |
//| one point in the dataset. First NX elements are |
//| coordinates, next NY elements are function values. |
//| Array may be larger than specified, in this case |
//| only leading [N, NX+NY] elements will be used. |
//| N - number of points in the dataset |
//| After you've added dataset and (optionally) tuned algorithm |
//| settings you should call RBFBuildModel() in order to build a |
//| model for you. |
//| NOTE: dataset added by this function is not saved during model |
//| serialization. MODEL ITSELF is serialized, but data used |
//| to build it are not. |
//| So, if you 1) add dataset to empty RBF model, 2) serialize and |
//| unserialize it, then you will get an empty RBF model with no |
//| dataset being attached. |
//| From the other side, if you call RBFBuildModel() between(1) and |
//| (2), then after(2) you will get your fully constructed RBF model-|
//| but again with no dataset attached, so subsequent calls to |
//| RBFBuildModel() will produce empty model. |
//+------------------------------------------------------------------+
void CRBF::RBFSetPoints(CRBFModel &s,CMatrixDouble &xy,int n)
{
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
return;
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
return;
if(!CAp::Assert(CAp::Cols(xy)>=s.m_nx+s.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
return;
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,s.m_nx+s.m_ny),__FUNCTION__+": XY contains infinite or NaN values!"))
return;
s.m_n=n;
s.m_hasscale=false;
s.m_x.Resize(s.m_n,s.m_nx);
s.m_y.Resize(s.m_n,s.m_ny);
for(int i=0; i<s.m_n; i++)
{
for(int j=0; j<s.m_nx; j++)
s.m_x.Set(i,j,xy.Get(i,j));
for(int j=0; j<s.m_ny; j++)
s.m_y.Set(i,j,xy.Get(i,j+s.m_nx));
}
}
//+------------------------------------------------------------------+
//| This function adds dataset and a vector of per-dimension scales. |
//| It may be useful if you have mixed spatio - temporal data - say, |
//| a set of 3D slices recorded at different times. Such data |
//| typically require different RBF radii for spatial and temporal |
//| dimensions. ALGLIB solves this problem by specifying single RBF |
//| radius, which is (optionally) multiplied by the scale vector. |
//| This function overrides results of the previous calls, i.e. |
//| multiple calls of this function will result in only the last set |
//| being added. |
//| IMPORTANT: only modern RBF algorithms support variable scaling. |
//| Legacy algorithms like RBF-ML or QNN algorithms will |
//| result in - 3 completion code being returned(incorrect|
//| algorithm). |
//| INPUT PARAMETERS: |
//| R - RBF model, initialized by RBFCreate() call. |
//| XY - points, array[N, NX + NY]. One row corresponds to |
//| one point in the dataset. First NX elements are |
//| coordinates, next NY elements are function values. |
//| Array may be larger than specified, in this case |
//| only leading [N, NX+NY] elements will be used. |
//| N - number of points in the dataset |
//| S - array[NX], scale vector, S[i] > 0. |
//| After you've added dataset and (optionally) tuned algorithm |
//| settings you should call RBFBuildModel() in order to build a |
//| model for you. |
//| NOTE: dataset added by this function is not saved during model |
//| serialization. MODEL ITSELF is serialized, but data used |
//| to build it are not. |
//| So, if you 1) add dataset to empty RBF model, 2) serialize and |
//| unserialize it, then you will get an empty RBF model with no |
//| dataset being attached. |
//| From the other side, if you call RBFBuildModel() between(1) and |
//| (2), then after(2) you will get your fully constructed RBF model-|
//| but again with no dataset attached, so subsequent calls to |
//| RBFBuildModel() will produce empty model. |
//+------------------------------------------------------------------+
void CRBF::RBFSetPointsAndScales(CRBFModel &r,
CMatrixDouble &xy,
int n,
CRowDouble &s)
{
//--- check
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
return;
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
return;
if(!CAp::Assert(CAp::Cols(xy)>=r.m_nx+r.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
return;
if(!CAp::Assert(CAp::Len(s)>=r.m_nx,__FUNCTION__+": Length(S)<NX"))
return;
r.m_n=n;
r.m_hasscale=true;
r.m_x.Resize(r.m_n,r.m_nx);
r.m_y.Resize(r.m_n,r.m_ny);
for(int i=0; i<r.m_n; i++)
{
for(int j=0; j<=r.m_nx-1; j++)
r.m_x.Set(i,j,xy.Get(i,j));
for(int j=0; j<=r.m_ny-1; j++)
r.m_y.Set(i,j,xy.Get(i,j+r.m_nx));
}
r.m_s.Resize(r.m_nx);
for(int i=0; i<r.m_nx; i++)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S[i] is not finite number"))
return;
if(!CAp::Assert(s[i]>0.0,__FUNCTION__+": S[i]<=0"))
return;
r.m_s.Set(i,s[i]);
}
}
//+------------------------------------------------------------------+
//| DEPRECATED: this function is deprecated. ALGLIB includes new RBF |
//| model algorithms: |
//| DDM - RBF (since version 3.19) and HRBF (since version 3.11). |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoQNN(CRBFModel &s,double q,double z)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(q),__FUNCTION__+": Q is infinite or NAN"))
return;
if(!CAp::Assert(q>0.0,__FUNCTION__+": Q<=0"))
return;
if(!CAp::Assert(MathIsValidNumber(z),__FUNCTION__+": Z is infinite or NAN"))
return;
if(!CAp::Assert(z>0.0,__FUNCTION__+": Z<=0"))
return;
s.m_radvalue=q;
s.m_radzvalue=z;
s.m_algorithmtype=1;
}
//+------------------------------------------------------------------+
//| DEPRECATED: this function is deprecated. ALGLIB includes new RBF |
//| model algorithms: |
//| DDM - RBF(since version 3.19) and HRBF (since version 3.11). |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoMultilayer(CRBFModel &s,double rbase,
int nlayers,double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(rbase),__FUNCTION__+": RBase is infinite or NaN"))
return;
if(!CAp::Assert(rbase>0.0,__FUNCTION__+": RBase<=0"))
return;
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": NLayers<0"))
return;
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is infinite or NAN"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV<0"))
return;
s.m_radvalue=rbase;
s.m_nlayers=nlayers;
s.m_algorithmtype=2;
s.m_lambdav=lambdav;
}
//+------------------------------------------------------------------+
//| This function chooses HRBF solver, a 2nd version of ALGLIB RBFs. |
//| This algorithm is called Hierarchical RBF. It similar to its |
//| previous incarnation, RBF-ML, i.e. it also builds a sequence of |
//| models with decreasing radii. However, it uses more economical |
//| way of building upper layers (ones with large radii), which |
//| results in faster model construction and evaluation, as well as |
//| smaller memory footprint during construction. |
//| This algorithm has following important features: |
//| * ability to handle millions of points |
//| * controllable smoothing via nonlinearity penalization |
//| * support for specification of per - dimensional radii via |
//| scale vector, which is set by means of RBFSetPointsAndScales |
//| function. This feature is useful if you solve spatio - |
//| temporal interpolation problems, where different radii are |
//| required for spatial and temporal dimensions. |
//| Running times are roughly proportional to: |
//| * N*log(N) |
//| * NLayers - for the model construction |
//| * N*NLayers - for the model evaluation |
//| You may see that running time does not depend on search radius or|
//| points density, just on the number of layers in the hierarchy. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| RBase - RBase parameter, RBase > 0 |
//| NLayers - NLayers parameter, NLayers > 0, recommended value |
//| to start with - about 5. |
//| LambdaNS - >= 0, nonlinearity penalty coefficient, negative |
//| values are not allowed. This parameter adds |
//| controllable smoothing to the problem, which may |
//| reduce noise. Specification of non-zero lambda |
//| means that in addition to fitting error solver will|
//| also minimize LambdaNS* | S''(x) | 2 (appropriately|
//| generalized to multiple dimensions. |
//| Specification of exactly zero value means that no penalty is |
//| added (we do not even evaluate matrix of second derivatives which|
//| is necessary for smoothing). |
//| Calculation of nonlinearity penalty is costly - it results in |
//| several - fold increase of model construction time. Evaluation |
//| time remains the same. |
//| Optimal lambda is problem - dependent and requires trial and |
//| error. Good value to start from is 1e-5...1e-6, which corresponds|
//| to slightly noticeable smoothing of the function. Value 1e-2 |
//| usually means that quite heavy smoothing is applied. |
//| TUNING ALGORITHM |
//| In order to use this algorithm you have to choose three |
//| parameters: |
//| * initial radius RBase |
//| * number of layers in the model NLayers |
//| * penalty coefficient LambdaNS |
//| Initial radius is easy to choose - you can pick any number |
//| several times larger than the average distance between points. |
//| Algorithm won't break down if you choose radius which is too |
//| large (model construction time will increase, but model will be |
//| built correctly). |
//| Choose such number of layers that RLast = RBase / 2^(NLayers - 1)|
//| (radius used by the last layer) will be smaller than the typical |
//| distance between points. In case model error is too large, you |
//| can increase number of layers. Having more layers will make model|
//| construction and evaluation proportionally slower, but it will |
//| allow you to have model which precisely fits your data. From the |
//| other side, if you want to suppress noise, you can DECREASE |
//| number of layers to make your model less flexible (or specify |
//| non-zero LambdaNS). |
//| TYPICAL ERRORS: |
//| 1. Using too small number of layers - RBF models with large |
//| radius are not flexible enough to reproduce small variations|
//| in the target function. You need many layers with different |
//| radii, from large to small, in order to have good model. |
//| 2. Using initial radius which is too small. You will get model |
//| with "holes" in the areas which are too far away from |
//| interpolation centers. However, algorithm will work |
//| correctly (and quickly) in this case. |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoHierarchical(CRBFModel &s,double rbase,
int nlayers,double lambdans)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(rbase),__FUNCTION__+": RBase is infinite or NaN"))
return;
if(!CAp::Assert(rbase>0.0,__FUNCTION__+": RBase<=0"))
return;
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": NLayers<0"))
return;
if(!CAp::Assert(MathIsValidNumber(lambdans) && lambdans>=0.0,__FUNCTION__+": LambdaNS<0 or infinite"))
return;
s.m_radvalue=rbase;
s.m_nlayers=nlayers;
s.m_algorithmtype=3;
s.m_lambdav=lambdans;
}
//+------------------------------------------------------------------+
//| This function chooses a thin plate spline DDM-RBF solver, a fast |
//| RBF solver with f(r) = r ^ 2 * ln(r) basis function. |
//| This algorithm has following important features: |
//| * easy setup - no tunable parameters |
//| * C1 continuous RBF model (gradient is defined everywhere, but |
//| Hessian is undefined at nodes), high - quality interpolation |
//| * fast model construction algorithm with O(N) memory and O(N^2)|
//| running time requirements. Hundreds of thousands of points |
//| can be handled with this algorithm. |
//| * controllable smoothing via optional nonlinearity penalty |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
//| * LambdaV = 0 means that no smoothing is applied, |
//| i.e. the spline tries to pass through|
//| all dataset points exactly |
//| * LambdaV > 0 means that a smoothing thin plate |
//| spline is built, with larger LambdaV |
//| corresponding to models with less |
//| nonlinearities. Smoothing spline |
//| reproduces target values at nodes |
//| with small error; from the other |
//| side, it is much more stable. |
//| Recommended values: |
//| * 1.0E-6 for minimal stability improving smoothing |
//| * 1.0E-3 a good value to start experiments; first results are |
//| visible |
//| * 1.0 for strong smoothing |
//| IMPORTANT: this model construction algorithm was introduced in |
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
//| with previous versions of ALGLIB. You can not |
//| unserialize models produced with this function in |
//| ALGLIB 3.18 or earlier. |
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
//| somewhat slower than compactly supported RBFs built with |
//| HRBF algorithm due to the fact that non-compact basis |
//| function does not vanish far away from the nodes. From the |
//| other side, polyharmonic RBFs often produce much better |
//| results than HRBFs. |
//| NOTE: this algorithm supports specification of per-dimensional |
//| radii via scale vector, which is set by means of |
//| RBFSetPointsAndScales() function. This feature is useful if|
//| you solve spatio-temporal interpolation problems where |
//| different radii are required for spatial and temporal |
//| dimensions. |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoThinPlateSpline(CRBFModel &s,double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
return;
s.m_algorithmtype=4;
s.m_bftype=2;
s.m_bfparam=0;
s.m_lambdav=lambdav;
}
//+------------------------------------------------------------------+
//| This function chooses a multiquadric DDM - RBF solver, a fast RBF|
//| solver with f(r) = sqrt(r ^ 2 + Alpha ^ 2) as a basis function, |
//| with manual choice of the scale parameter Alpha. |
//| This algorithm has following important features: |
//| * C2 continuous RBF model(when Alpha > 0 is used; for Alpha = 0|
//| the model is merely C0 continuous) |
//| * fast model construction algorithm with O(N) memory and O(N^2)|
//| running time requirements. Hundreds of thousands of points |
//| can be handled with this algorithm. |
//| * controllable smoothing via optional nonlinearity penalty |
//| One important point is that this algorithm includes tunable |
//| parameter Alpha, which should be carefully chosen. Selecting too |
//| large value will result in extremely badly conditioned problems |
//| (interpolation accuracy may degrade up to complete breakdown) |
//| whilst selecting too small value may produce models that are |
//| precise but nearly nonsmooth at the nodes. |
//| Good value to start from is mean distance between nodes. |
//| Generally, choosing too small Alpha is better than choosing too |
//| large - in the former case you still have model that reproduces |
//| target values at the nodes. |
//| In most cases, better option is to choose good Alpha |
//| automatically - it is done by another version of the same |
//| algorithm that is activated by calling RBFSetAlgoMultiQuadricAuto|
//| method. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| Alpha - basis function parameter, Alpha >= 0: |
//| * Alpha > 0 means that multiquadric algorithm is |
//| used which produces C2-continuous RBF |
//| model |
//| * Alpha = 0 means that the multiquadric kernel |
//| effectively becomes a biharmonic one: |
//| f = r. As a result, the model becomes |
//| nonsmooth at nodes, and hence is C0 |
//| continuous |
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
//| * LambdaV = 0 means that no smoothing is applied, |
//| i.e. the spline tries to pass through|
//| all dataset points exactly |
//| * LambdaV > 0 means that a multiquadric spline is |
//| built with larger LambdaV |
//| corresponding to models with less |
//| nonlinearities. Smoothing spline |
//| reproduces target values at nodes |
//| with small error; from the other |
//| side, it is much more stable. |
//| Recommended values: |
//| * 1.0E-6 for minimal stability improving smoothing |
//| * 1.0E-3 a good value to start experiments; first results are |
//| visible |
//| * 1.0 for strong smoothing |
//| IMPORTANT: this model construction algorithm was introduced in |
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
//| with previous versions of ALGLIB. You can not |
//| unserialize models produced with this function in |
//| ALGLIB 3.18 or earlier. |
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
//| somewhat slower than compactly supported RBFs built with |
//| HRBF algorithm due to the fact that non-compact basis |
//| function does not vanish far away from the nodes. From the |
//| other side, polyharmonic RBFs often produce much better |
//| results than HRBFs. |
//| NOTE: this algorithm supports specification of per-dimensional |
//| radii via scale vector, which is set by means of |
//| RBFSetPointsAndScales() function. This feature is useful if|
//| you solve spatio-temporal interpolation problems where |
//| different radii are required for spatial and temporal |
//| dimensions. |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoMultiQuadricManual(CRBFModel &s,double alpha,
double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(alpha),__FUNCTION__+": Alpha is infinite or NAN"))
return;
if(!CAp::Assert(alpha>=0.0,__FUNCTION__+": Alpha<0"))
return;
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
return;
s.m_algorithmtype=4;
s.m_bftype=1;
s.m_bfparam=alpha;
s.m_lambdav=lambdav;
}
//+------------------------------------------------------------------+
//| This function chooses a multiquadric DDM-RBF solver, a fast RBF |
//| solver with f(r) = sqrt(r ^ 2 + Alpha ^ 2) as a basis function, |
//| with Alpha being automatically determined. |
//| This algorithm has following important features: |
//| * easy setup - no need to tune Alpha, good value is |
//| automatically assigned |
//| * C2 continuous RBF model |
//| * fast model construction algorithm with O(N) memory and O(N^2)|
//| running time requirements. Hundreds of thousands of points |
//| can be handled with this algorithm. |
//| * controllable smoothing via optional nonlinearity penalty |
//| This algorithm automatically selects Alpha as a mean distance to |
//| the nearest neighbor(ignoring neighbors that are too close). |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
//| * LambdaV = 0 means that no smoothing is applied, |
//| i.e. the spline tries to pass through|
//| all dataset points exactly |
//| * LambdaV > 0 means that a multiquadric spline is |
//| built with larger LambdaV |
//| corresponding to models with less |
//| nonlinearities. Smoothing spline |
//| reproduces target values at nodes |
//| with small error; from the other |
//| side, it is much more stable. |
//| Recommended values: |
//| * 1.0E-6 for minimal stability improving smoothing |
//| * 1.0E-3 a good value to start experiments; first results are |
//| visible |
//| * 1.0 for strong smoothing |
//| IMPORTANT: this model construction algorithm was introduced in |
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
//| with previous versions of ALGLIB. You can not |
//| unserialize models produced with this function in |
//| ALGLIB 3.18 or earlier. |
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
//| somewhat slower than compactly supported RBFs built with |
//| HRBF algorithm due to the fact that non-compact basis |
//| function does not vanish far away from the nodes. From the |
//| other side, polyharmonic RBFs often produce much better |
//| results than HRBFs. |
//| NOTE: this algorithm supports specification of per-dimensional |
//| radii via scale vector, which is set by means of |
//| RBFSetPointsAndScales() function. This feature is useful if|
//| you solve spatio - temporal interpolation problems where |
//| different radii are required for spatial and temporal |
//| dimensions. |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoMultiQuadricAuto(CRBFModel &s,double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
return;
s.m_algorithmtype=4;
s.m_bftype=1;
s.m_bfparam=-1.0;
s.m_lambdav=lambdav;
}
//+------------------------------------------------------------------+
//| This function chooses a biharmonic DDM-RBF solver, a fast RBF |
//| solver with f(r) = r as a basis function. |
//| This algorithm has following important features: |
//| * no tunable parameters |
//| * C0 continuous RBF model (the model has discontinuous |
//| derivatives at the interpolation nodes) |
//| * fast model construction algorithm with O(N) memory and O(N^2)|
//| running time requirements. Hundreds of thousands of points |
//| can be handled with this algorithm. |
//| * controllable smoothing via optional nonlinearity penalty |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
//| * LambdaV = 0 means that no smoothing is applied, |
//| i.e. the spline tries to pass through|
//| all dataset points exactly |
//| * LambdaV > 0 means that a multiquadric spline is |
//| built with larger LambdaV |
//| corresponding to models with less |
//| nonlinearities. Smoothing spline |
//| reproduces target values at nodes |
//| with small error; from the other |
//| side, it is much more stable. |
//| Recommended values: |
//| * 1.0E-6 for minimal stability improving smoothing |
//| * 1.0E-3 a good value to start experiments; first results are |
//| visible |
//| * 1.0 for strong smoothing |
//| IMPORTANT: this model construction algorithm was introduced in |
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
//| with previous versions of ALGLIB. You can not |
//| unserialize models produced with this function in |
//| ALGLIB 3.18 or earlier. |
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
//| somewhat slower than compactly supported RBFs built with |
//| HRBF algorithm due to the fact that non-compact basis |
//| function does not vanish far away from the nodes. From the |
//| other side, polyharmonic RBFs often produce much better |
//| results than HRBFs. |
//| NOTE: this algorithm supports specification of per-dimensional |
//| radii via scale vector, which is set by means of |
//| RBFSetPointsAndScales() function. This feature is useful if|
//| you solve spatio - temporal interpolation problems where |
//| different radii are required for spatial and temporal |
//| dimensions. |
//+------------------------------------------------------------------+
void CRBF::RBFSetAlgoBiharmonic(CRBFModel &s,double lambdav)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
return;
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
return;
s.m_algorithmtype=4;
s.m_bftype=1;
s.m_bfparam=0;
s.m_lambdav=lambdav;
}
//+------------------------------------------------------------------+
//| This function sets linear term (model is a sum of radial basis |
//| functions plus linear polynomial). This function won't have |
//| effect until next call to RBFBuildModel(). |
//| Using linear term is a default option and it is the best one-it |
//| provides best convergence guarantees for all RBF model types: |
//| legacy RBF-QNN and RBF-ML, Gaussian HRBFs and all types of |
//| DDM-RBF models. |
//| Other options, like constant or zero term, work for HRBFs, almost|
//| always work for DDM-RBFs but provide no stability guarantees in |
//| the latter case (e.g. the solver may fail on some carefully |
//| prepared problems). |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//+------------------------------------------------------------------+
void CRBF::RBFSetLinTerm(CRBFModel &s)
{
s.m_aterm=1;
}
//+------------------------------------------------------------------+
//| This function sets constant term (model is a sum of radial basis |
//| functions plus constant). This function won't have effect until |
//| next call to RBFBuildModel(). |
//| IMPORTANT: thin plate splines require polynomial term to be |
//| linear, not constant, in order to provide |
//| interpolation guarantees. Although failures are |
//| exceptionally rare, some small toy problems may result|
//| in degenerate linear systems. Thus, it is advised to |
//| use linear term when one fits data with TPS. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//+------------------------------------------------------------------+
void CRBF::RBFSetConstTerm(CRBFModel &s)
{
s.m_aterm=2;
}
//+------------------------------------------------------------------+
//| This function sets zero term (model is a sum of radial basis |
//| functions without polynomial term). This function won't have |
//| effect until next call to RBFBuildModel(). |
//| IMPORTANT: only Gaussian RBFs(HRBF algorithm) provide |
//| interpolation guarantees when no polynomial term is |
//| used. Most other RBFs, including biharmonic splines, |
//| thin plate splines and multiquadrics, require at least|
//| constant term(biharmonic and multiquadric) or linear |
//| one (thin plate splines) in order to guarantee |
//| non-degeneracy of linear systems being solved. |
//| Although failures are exceptionally rare, some small toy problems|
//| still may result in degenerate linear systems. Thus, it is |
//| advised to use constant / linear term, unless one is 100 % sure |
//| that he needs zero term. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//+------------------------------------------------------------------+
void CRBF::RBFSetZeroTerm(CRBFModel &s)
{
s.m_aterm=3;
}
//+------------------------------------------------------------------+
//| This function sets basis function type, which can be: |
//| * 0 for classic Gaussian |
//| * 1 for fast and compact bell - like basis function, which |
//| becomes exactly zero at distance equal to 3 * R (default |
//| option). |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| BF - basis function type: |
//| * 0 - classic Gaussian |
//| * 1 - fast and compact one |
//+------------------------------------------------------------------+
void CRBF::RBFSetV2BF(CRBFModel &s,int bf)
{
if(!CAp::Assert(bf==0 || bf==1,__FUNCTION__+": BF<>0 and BF<>1"))
return;
s.m_model2.m_basisfunction=bf;
}
//+------------------------------------------------------------------+
//| This function sets stopping criteria of the underlying linear |
//| solver for hierarchical (version 2) RBF constructor. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| MaxIts - this criterion will stop algorithm after MaxIts |
//| iterations. Typically a few hundreds iterations is |
//| required, with 400 being a good default value to |
//| start experimentation. Zero value means that |
//| default value will be selected. |
//+------------------------------------------------------------------+
void CRBF::RBFSetV2Its(CRBFModel &s,int maxits)
{
if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts is negative"))
return;
s.m_model2.m_maxits=maxits;
}
//+------------------------------------------------------------------+
//| This function sets support radius parameter of hierarchical |
//| (version 2) RBF constructor. |
//| Hierarchical RBF model achieves great speed-up by removing from |
//| the model excessive (too dense) nodes. Say, if you have RBF |
//| radius equal to 1 meter, and two nodes are just 1 millimeter |
//| apart, you may remove one of them without reducing model quality.|
//| Support radius parameter is used to justify which points need |
//| removal, and which do not. If two points are less than |
//| SUPPORT_R*CUR_RADIUS units of distance apart, one of them is |
//| removed from the model. The larger support radius is, the faster |
//| model construction AND evaluation are. However, too large values |
//| result in "bumpy" models. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| R - support radius coefficient, >= 0. |
//| Recommended values are [0.1, 0.4] range, with 0.1 being default |
//| value. |
//+------------------------------------------------------------------+
void CRBF::RBFSetV2SupportR(CRBFModel &s,double r)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(r),__FUNCTION__+": R is not finite"))
return;
if(!CAp::Assert(r>=0.0,__FUNCTION__+": R<0"))
return;
s.m_model2.m_supportr=r;
}
//+------------------------------------------------------------------+
//| This function sets stopping criteria of the underlying linear |
//| solver. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| EpsOrt - orthogonality stopping criterion, EpsOrt >= 0. |
//| Algorithm will stop when ||A'*r||<=EpsOrt where A' |
//| is a transpose of the system matrix, r is a |
//| residual vector. Recommended value of EpsOrt is |
//| equal to 1E-6. This criterion will stop algorithm |
//| when we have "bad fit" situation, i.e. when we |
//| should stop in a point with large, nonzero residual|
//| EpsErr - residual stopping criterion. Algorithm will stop |
//| when ||r|| <= EpsErr* ||b||, where r is a residual |
//| vector, b is a right part of the system (function |
//| values). Recommended value of EpsErr is equal to |
//| 1E-3 or 1E-6. This criterion will stop algorithm in|
//| a "good fit" situation when we have near-zero |
//| residual near the desired solution. |
//| MaxIts - this criterion will stop algorithm after MaxIts |
//| iterations. It should be used for debugging |
//| purposes only! Zero MaxIts means that no limit is |
//| placed on the number of iterations. |
//| We recommend to set moderate non-zero values EpsOrt and EpsErr |
//| simultaneously. Values equal to 10E-6 are good to start with. In |
//| case you need high performance and do not need high precision, |
//| you may decrease EpsErr down to 0.001. However, we do not |
//| recommend decreasing EpsOrt. |
//| As for MaxIts, we recommend to leave it zero unless you know what|
//| you do. |
//| NOTE: this function has some serialization - related subtleties. |
//| We recommend you to study serialization examples from |
//| ALGLIB Reference Manual if you want to perform |
//| serialization of your models. |
//+------------------------------------------------------------------+
void CRBF::RBFSetCond(CRBFModel &s,double epsort,double epserr,
int maxits)
{
//--- check
if(!CAp::Assert(MathIsValidNumber(epsort) && epsort>=0.0,__FUNCTION__+": EpsOrt is negative,INF or NAN"))
return;
if(!CAp::Assert(MathIsValidNumber(epserr) && epserr>=0.0,__FUNCTION__+": EpsB is negative,INF or NAN"))
return;
if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts is negative"))
return;
if(epsort==0.0 && epserr==0.0 && maxits==0)
{
s.m_epsort=m_eps;
s.m_epserr=m_eps;
s.m_maxits=0;
}
else
{
s.m_epsort=epsort;
s.m_epserr=epserr;
s.m_maxits=maxits;
}
}
//+------------------------------------------------------------------+
//| This function builds RBF model and returns report (contains some |
//| information which can be used for evaluation of the algorithm |
//| properties). |
//| Call to this function modifies RBF model by calculating its |
//| centers/radii/weights and saving them into RBFModel structure. |
//| Initially RBFModel contain zero coefficients, but after call to |
//| this function we will have coefficients which were calculated in |
//| order to fit our dataset. |
//| After you called this function you can call RBFCalc(), |
//| RBFGridCalc() and other model calculation functions. |
//| INPUT PARAMETERS: |
//| S - RBF model, initialized by RBFCreate() call |
//| Rep - report: |
//| * Rep.TerminationType: |
//| * -5 - non-distinct basis function centers were |
//| detected, interpolation aborted; only QNN|
//| returns this error code, other algorithms|
//| can handle non-distinct nodes. |
//| * -4 - nonconvergence of the internal SVD solver|
//| * -3 incorrect model construction algorithm |
//| was chosen: QNN or RBF-ML, combined with |
//| one of the incompatible features: |
//| * NX = 1 or NX > 3 |
//| * points with per - dimension scales. |
//| * 1 - successful termination |
//| * 8 - a termination request was submitted via |
//| RBFRequestTermination() function. |
//| Fields which are set only by modern RBF solvers (hierarchical or |
//| nonnegative; older solvers like QNN and ML initialize these |
//| fields by NANs): |
//| * rep.m_rmserror - root-mean-square error at nodes |
//| * rep.m_maxerror - maximum error at nodes |
//| Fields are used for debugging purposes: |
//| * Rep.IterationsCount - iterations count of the LSQR solver |
//| * Rep.NMV - number of matrix - vector products |
//| * Rep.ARows - rows count for the system matrix |
//| * Rep.ACols - columns count for the system matrix |
//| * Rep.ANNZ - number of significantly non - zero elements |
//| (elements above some algorithm - determined |
//| threshold) |
//| NOTE: failure to build model will leave current State of the |
//| structure unchanged. |
//+------------------------------------------------------------------+
void CRBF::RBFBuildModel(CRBFModel &s,CRBFReport &rep)
{
//--- create variables
CRBFV1Report rep1;
CRBFV2Report rep2;
CRBFV3Report rep3;
CMatrixDouble x3;
CRowDouble scalevec;
int i=0;
int v3bftype=0;
double v3bfparam=0;
int curalgorithmtype=0;
//--- Clean fields prior to processing
ClearReportFields(rep);
s.m_progress10000=0;
s.m_terminationrequest=false;
//--- Autoselect algorithm
v3bftype=-999;
v3bfparam=0.0;
if(s.m_algorithmtype==0)
{
curalgorithmtype=4;
v3bftype=2;
v3bfparam=0.0;
}
else
{
curalgorithmtype=s.m_algorithmtype;
if(s.m_algorithmtype==4)
{
v3bftype=s.m_bftype;
v3bfparam=s.m_bfparam;
}
}
//--- Algorithms which generate V1 models
if(curalgorithmtype==1 || curalgorithmtype==2)
{
//--- Perform compatibility checks
if(s.m_nx<2 || s.m_nx>3 || s.m_hasscale)
{
rep.m_terminationtype=-3;
return;
}
//--- Try to build model.
//--- NOTE: due to historical reasons RBFV1BuildModel() accepts points
//--- cast to 3-dimensional space, even if they are really 2-dimensional.
//--- So, for 2D data we have to explicitly convert them to 3D.
if(s.m_nx==2)
{
//--- Convert data to 3D
CApServ::RMatrixSetLengthAtLeast(x3,s.m_n,3);
for(i=0; i<s.m_n; i++)
{
x3.Set(i,0,s.m_x.Get(i,0));
x3.Set(i,1,s.m_x.Get(i,1));
x3.Set(i,2,0);
}
CRBFV1::RBFV1BuildModel(x3,s.m_y,s.m_n,s.m_aterm,curalgorithmtype,s.m_nlayers,s.m_radvalue,s.m_radzvalue,s.m_lambdav,s.m_epsort,s.m_epserr,s.m_maxits,s.m_model1,rep1);
}
else
{
//--- Work with raw data
CRBFV1::RBFV1BuildModel(s.m_x,s.m_y,s.m_n,s.m_aterm,curalgorithmtype,s.m_nlayers,s.m_radvalue,s.m_radzvalue,s.m_lambdav,s.m_epsort,s.m_epserr,s.m_maxits,s.m_model1,rep1);
}
s.m_modelversion=1;
RBFCreateCalcBuffer(s,s.m_calcbuf);
//--- Convert report fields
rep.m_arows=rep1.m_arows;
rep.m_acols=rep1.m_acols;
rep.m_annz=rep1.m_annz;
rep.m_iterationscount=rep1.m_iterationscount;
rep.m_nmv=rep1.m_nmv;
rep.m_terminationtype=rep1.m_terminationtype;
//--- Done
return;
}
//--- Algorithms which generate V2 models
if(curalgorithmtype==3)
{
//--- Prepare scale vector - use unit values or user supplied ones
scalevec.Resize(s.m_nx);
for(i=0; i<s.m_nx; i++)
{
if(s.m_hasscale)
scalevec.Set(i,s.m_s[i]);
else
scalevec.Set(i,1);
}
//--- Build model
CRBFV2::RBFV2BuildHierarchical(s.m_x,s.m_y,s.m_n,scalevec,s.m_aterm,s.m_nlayers,s.m_radvalue,s.m_lambdav,s.m_model2,s.m_progress10000,s.m_terminationrequest,rep2);
s.m_modelversion=2;
RBFCreateCalcBuffer(s,s.m_calcbuf);
//--- Convert report fields
rep.m_terminationtype=rep2.m_terminationtype;
rep.m_rmserror=rep2.m_rmserror;
rep.m_maxerror=rep2.m_maxerror;
//--- Done
return;
}
//--- Algorithms which generate DDM-RBF models
if(curalgorithmtype==4)
{
//--- Prepare scale vector - use unit values or user supplied ones
scalevec.Resize(s.m_nx);
for(i=0; i<s.m_nx; i++)
{
if(s.m_hasscale)
scalevec.Set(i,s.m_s[i]);
else
scalevec.Set(i,1);
}
//--- Build model
CRBFV3::RBFV3Build(s.m_x,s.m_y,s.m_n,scalevec,v3bftype,v3bfparam,s.m_lambdav,s.m_aterm,s.m_model3,s.m_progress10000,s.m_terminationrequest,rep3);
s.m_modelversion=3;
RBFCreateCalcBuffer(s,s.m_calcbuf);
//--- Convert report fields
rep.m_iterationscount=rep3.m_iterationscount;
rep.m_terminationtype=rep3.m_terminationtype;
rep.m_rmserror=rep3.m_rmserror;
rep.m_maxerror=rep3.m_maxerror;
//--- Done
return;
}
//--- Critical error
CAp::Assert(false,__FUNCTION__+": integrity check failure");
}
//+------------------------------------------------------------------+
//| This function calculates values of the 1-dimensional RBF model |
//| with scalar output (NY = 1) at the given point. |
//| IMPORTANT: this function works only with modern (hierarchical) |
//| RBFs. It can not be used with legacy (version 1) RBFs |
//| because older RBF code does not support 1-dimensional |
//| models. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 when: |
//| * the model is not initialized |
//| * NX<>1 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - X - coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBF::RBFCalc1(CRBFModel &s,double x0)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=1)
return(0);
switch(s.m_modelversion)
{
case 1:
result=0;
break;
case 2:
result=CRBFV2::RBFV2Calc1(s.m_model2,x0);
break;
case 3:
result=CRBFV3::RBFV3Calc1(s.m_model3,x0);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the 2-dimensional RBF model |
//| with scalar output (NY = 1) at the given point. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBF::RBFCalc2(CRBFModel &s,double x0,double x1)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=2)
return(0);
switch(s.m_modelversion)
{
case 1:
result=CRBFV1::RBFV1Calc2(s.m_model1,x0,x1);
break;
case 2:
result=CRBFV2::RBFV2Calc2(s.m_model2,x0,x1);
break;
case 3:
result=CRBFV3::RBFV3Calc2(s.m_model3,x0,x1);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates values of the 3-dimensional RBF model |
//| with scalar output (NY = 1) at the given point. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| * NY<>1 |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| X2 - third coordinate, finite number |
//| RESULT: |
//| value of the model or 0.0 (as defined above) |
//+------------------------------------------------------------------+
double CRBF::RBFCalc3(CRBFModel &s,double x0,double x1,double x2)
{
double result=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
return(0);
if(s.m_ny!=1 || s.m_nx!=3)
return(0);
switch(s.m_modelversion)
{
case 1:
result=CRBFV1::RBFV1Calc3(s.m_model1,x0,x1,x2);
break;
case 2:
result=CRBFV2::RBFV2Calc3(s.m_model2,x0,x1,x2);
break;
case 3:
result=CRBFV3::RBFV3Calc3(s.m_model3,x0,x1,x2);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function calculates value and derivatives of the |
//| 1-dimensional RBF model with scalar output (NY = 1) at the given |
//| point. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 in Y and/or DY in the following cases: |
//| * the model is not initialized (Y = 0, DY = 0) |
//| * NX<>1 or NY<>1 (Y = 0, DY = 0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only DY is set to zero (Y is |
//| still returned) |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| OUTPUT PARAMETERS: |
//| Y - value of the model or 0.0 (as defined above) |
//| DY0 - derivative with respect to X0 |
//+------------------------------------------------------------------+
void CRBF::RBFDiff1(CRBFModel &s,double x0,double &y,double &dy0)
{
//--- init variables
y=0;
dy0=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return;
y=0;
dy0=0;
if(s.m_ny!=1 || s.m_nx!=1)
return;
CAblasF::RAllocV(1,s.m_calcbuf.m_x);
s.m_calcbuf.m_x.Set(0,x0);
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
y=s.m_calcbuf.m_y[0];
dy0=s.m_calcbuf.m_dy[0];
}
//+------------------------------------------------------------------+
//| This function calculates value and derivatives of the |
//| 2-dimensional RBF model with scalar output (NY = 1) at the given |
//| point. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 in Y and/or DY in the following cases: |
//| * the model is not initialized(Y = 0, DY = 0) |
//| * NX<>2 or NY<>1 (Y=0, DY=0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient at |
//| the nodes In these cases only DY is set to zero (Y is still |
//| returned) |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| OUTPUT PARAMETERS: |
//| Y - value of the model or 0.0 (as defined above) |
//| DY0 - derivative with respect to X0 |
//| DY1 - derivative with respect to X1 |
//+------------------------------------------------------------------+
void CRBF::RBFDiff2(CRBFModel &s,double x0,double x1,double &y,
double &dy0,double &dy1)
{
y=0;
dy0=0;
dy1=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return;
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return;
if(s.m_ny!=1 || s.m_nx!=2)
return;
CAblasF::RAllocV(2,s.m_calcbuf.m_x);
s.m_calcbuf.m_x.Set(0,x0);
s.m_calcbuf.m_x.Set(1,x1);
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
y=s.m_calcbuf.m_y[0];
dy0=s.m_calcbuf.m_dy[0];
dy1=s.m_calcbuf.m_dy[1];
}
//+------------------------------------------------------------------+
//| This function calculates value and derivatives of the |
//| 3-dimensional RBF model with scalar output (NY = 1) at the given |
//| point. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 in Y and/or DY in the following cases: |
//| * the model is not initialized (Y = 0, DY = 0) |
//| * NX<>3 or NY<>1 (Y = 0, DY = 0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only DY is set to zero (Y is |
//| still returned) |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X0 - first coordinate, finite number |
//| X1 - second coordinate, finite number |
//| X2 - third coordinate, finite number |
//| OUTPUT PARAMETERS: |
//| Y - value of the model or 0.0 (as defined above) |
//| DY0 - derivative with respect to X0 |
//| DY1 - derivative with respect to X1 |
//| DY2 - derivative with respect to X2 |
//+------------------------------------------------------------------+
void CRBF::RBFDiff3(CRBFModel &s,double x0,double x1,double x2,
double &y,double &dy0,double &dy1,double &dy2)
{
y=0;
dy0=0;
dy1=0;
dy2=0;
//--- check
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
return;
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
return;
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
return;
if(s.m_ny!=1 || s.m_nx!=3)
return;
CAblasF::RAllocV(3,s.m_calcbuf.m_x);
s.m_calcbuf.m_x.Set(0,x0);
s.m_calcbuf.m_x.Set(1,x1);
s.m_calcbuf.m_x.Set(2,x2);
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
y=s.m_calcbuf.m_y[0];
dy0=s.m_calcbuf.m_dy[0];
dy1=s.m_calcbuf.m_dy[1];
dy2=s.m_calcbuf.m_dy[2];
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point. |
//| This is general function which can be used for arbitrary NX |
//| (dimension of the space of arguments) and NY (dimension of the |
//| function itself). However when you have NY = 1 you may find more |
//| convenient to use RBFCalc2() or RBFCalc3(). |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| This function returns 0.0 when model is not initialized. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is out - parameter and|
//| reallocated after call to this function. In case |
//| you want to reuse previously allocated Y, you may |
//| use RBFCalcBuf(), which reallocates Y only when it |
//| is too small. |
//+------------------------------------------------------------------+
void CRBF::RBFCalc(CRBFModel &s,CRowDouble &x,CRowDouble &y)
{
y.Resize(0);
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
RBFCalcBuf(s,x,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its |
//| derivatives at the given point. |
//| This is general function which can be used for arbitrary NX |
//| (dimension of the space of arguments) and NY(dimension of the |
//| function itself). However if you have NX = 3 and NY = 1, you may |
//| find more convenient to use RBFDiff3(). |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). |
//| If you want to perform parallel model evaluation from multiple |
//| threads, use RBFTSDiffBuf() with per-thread buffer object. |
//| This function returns 0.0 in Y and/or DY in the following cases: |
//| * the model is not initialized (Y = 0, DY = 0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only DY is set to zero (Y is |
//| still returned) |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is out-parameter and |
//| reallocated after call to this function. In case |
//| you want to reuse previously allocated Y, you may |
//| use RBFDiffBuf(), which reallocates Y only when it |
//| is too small. |
//| DY - derivatives, array[NX * NY]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX-dimensional gradient |
//| of the scalar NX-dimensional function DY is |
//| out-parameter and reallocated after call to this |
//| function. In case you want to reuse previously |
//| allocated DY, you may use RBFDiffBuf(), which |
//| reallocates DY only when it is too small to store|
//| the result. |
//+------------------------------------------------------------------+
void CRBF::RBFDiff(CRBFModel &s,CRowDouble &x,CRowDouble &y,CRowDouble &dy)
{
y.Resize(0);
dy.Resize(0);
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
RBFDiffBuf(s,x,y,dy);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its first |
//| and second derivatives (Hessian matrix) at the given point. |
//| This function supports both scalar (NY = 1) and vector - valued |
//| (NY > 1) RBFs. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). |
//| If you want to perform parallel model evaluation from multiple |
//| threads, use RBFTsHessBuf() with per - thread buffer object. |
//| This function returns 0 in Y and/or DY and/or D2Y in the |
//| following cases: |
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
//| * the gradient and/or Hessian is undefined at the trial point. |
//| Some basis functions have discontinuous derivatives at the |
//| interpolation nodes: |
//| * thin plate splines have no Hessian at the nodes |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only corresponding derivative |
//| is set to zero, and the rest of the derivatives is still |
//| returned. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is out-parameter and |
//| reallocated after call to this function. In case |
//| you want to reuse previously allocated Y, you may |
//| use RBFHessBuf(), which reallocates Y only when |
//| it is too small. |
//| DY - first derivatives, array[NY * NX]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX - dimensional gradient|
//| of the scalar NX-dimensional function DY is |
//| out-parameter and reallocated after call to this |
//| function. In case you want to reuse previously |
//| allocated DY, you may use RBFHessBuf(), which |
//| reallocates DY only when it is too small to store|
//| the result. |
//| D2Y - second derivatives, array[NY * NX * NX]: |
//| * for NY = 1 it is NX*NX array that stores Hessian |
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
//| * for a vector - valued RBF with NY > 1 it contains|
//| NY subsequently stored Hessians: an element |
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
//| 0 <= I < NX and 0 <= J < NX stores second |
//| derivative of the function #K with respect to |
//| inputs #I and #J. |
//| D2Y is out-parameter and reallocated after call to |
//| this function. In case you want to reuse previously|
//| allocated D2Y, you may use RBFHessBuf(), which |
//| reallocates D2Y only when it is too small to store |
//| the result. |
//+------------------------------------------------------------------+
void CRBF::RBFHess(CRBFModel &s,CRowDouble &x,CRowDouble &y,
CRowDouble &dy,CRowDouble &d2y)
{
y.Resize(0);
dy.Resize(0);
d2y.Resize(0);
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
RBFTSHessBuf(s,s.m_calcbuf,x,y,dy,d2y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point. |
//| Same as RBFCalc(), but does not reallocate Y when in is large |
//| enough to store function values. |
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
//| CRBFModel as temporary arrays, i.e. it is impossible |
//| to perform parallel evaluation on the same CRBFModel |
//| object (parallel calls of this function for |
//| independent CRBFModel objects are safe). If you want |
//| to perform parallel model evaluation from multiple |
//| threads, use RBFTSCalcBuf() with per-thread buffer |
//| object. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBF::RBFCalcBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y)
{
int i=0;
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
for(i=0; i<s.m_ny; i++)
y.Set(i,0);
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1CalcBuf(s.m_model1,x,y);
break;
case 2:
CRBFV2::RBFV2CalcBuf(s.m_model2,x,y);
break;
case 3:
CRBFV3::RBFV3CalcBuf(s.m_model3,x,y);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its |
//| derivatives at the given point. It is a buffered version of the |
//| RBFDiff() which tries to reuse possibly preallocated output |
//| arrays Y / DY as much as possible. |
//| This is general function which can be used for arbitrary NX |
//| (dimension of the space of arguments) and NY (dimension of the |
//| function itself). However if you have NX = 1, 2 or 3 and NY = 1, |
//| you may find more convenient to use RBFDiff1(), RBFDiff2() or |
//| RBFDiff3(). |
//| This function returns 0.0 in Y and/or DY in the following cases: |
//| * the model is not initialized (Y = 0, DY = 0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only DY is set to zero (Y is |
//| still returned) |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y, DY - possibly preallocated arrays; if array size is |
//| large enough to store results, this function does |
//| not reallocate array to fit output size exactly. |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. |
//| DY - derivatives, array[NX * NY]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX - dimensional gradient|
//| of the scalar NX - dimensional function |
//+------------------------------------------------------------------+
void CRBF::RBFDiffBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y,
CRowDouble &dy)
{
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(!CAp::Assert(s.m_modelversion==s.m_calcbuf.m_modelversion,__FUNCTION__+": integrity check 3945 failed"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
for(int i=0; i<s.m_ny; i++)
y.Set(i,0);
for(int i=0; i<s.m_ny*s.m_nx; i++)
dy.Set(i,0);
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1TSDiffBuf(s.m_model1,s.m_calcbuf.m_bufv1,x,y,dy);
break;
case 2:
CRBFV2::RBFV2TsDiffBuf(s.m_model2,s.m_calcbuf.m_bufv2,x,y,dy);
break;
case 3:
CRBFV3::RBFV3TsDiffBuf(s.m_model3,s.m_calcbuf.m_bufv3,x,y,dy);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its first |
//| and second derivatives (Hessian matrix) at the given point. It is|
//| a buffered version that reuses memory allocated in output buffers|
//| Y/DY/D2Y as much as possible. |
//| This function supports both scalar(NY = 1) and vector - valued |
//| (NY > 1) RBFs. |
//| This function returns 0 in Y and/or DY and/or D2Y in the |
//| following cases: |
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
//| * the gradient and/or Hessian is undefined at the trial point |
//| Some basis functions have discontinuous derivatives at the |
//| interpolation nodes: |
//| * thin plate splines have no Hessian at the nodes |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only corresponding derivative |
//| is set to zero, and the rest of the derivatives is still |
//| returned. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y, DY, D2Y - possible preallocated output arrays. If these |
//| arrays are smaller than required to store the |
//| result, they are automatically reallocated. If |
//| array is large enough, it is not resized. |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. |
//| DY - first derivatives, array[NY * NX]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX - dimensional gradient|
//| of the scalar NX - dimensional function |
//| D2Y - second derivatives, array[NY * NX * NX]: |
//| * for NY = 1 it is NX*NX array that stores Hessian |
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
//| * for a vector - valued RBF with NY > 1 it contains|
//| NY subsequently stored Hessians: an element |
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
//| 0 <= I < NX and 0 <= J < NX stores second |
//| derivative of the function #K with respect to |
//| inputs #I and #J. |
//+------------------------------------------------------------------+
void CRBF::RBFHessBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y,
CRowDouble &dy,CRowDouble &d2y)
{
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
RBFTSHessBuf(s,s.m_calcbuf,x,y,dy,d2y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the given |
//| point, using external buffer object (internal temporaries of RBF |
//| model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of buffer structure. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y - possibly preallocated array |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. Y is not reallocated |
//| when it is larger than NY. |
//+------------------------------------------------------------------+
void CRBF::RBFTSCalcBuf(CRBFModel &s,CRBFCalcBuffer &buf,CRowDouble &x,
CRowDouble &y)
{
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,__FUNCTION__+": buffer object is not compatible with RBF model"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
for(int i=0; i<s.m_ny; i++)
y.Set(i,0);
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1TSCalcBuf(s.m_model1,buf.m_bufv1,x,y);
break;
case 2:
CRBFV2::RBFV2TsCalcBuf(s.m_model2,buf.m_bufv2,x,y);
break;
case 3:
CRBFV3::RBFV3TsCalcBuf(s.m_model3,buf.m_bufv3,x,y);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its |
//| derivatives at the given point, using external buffer object |
//| (internal temporaries of the RBF model are not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances |
//| of the buffer structure. |
//| This function returns 0.0 in Y and/or DY in the following |
//| cases: |
//| * the model is not initialized(Y = 0, DY = 0) |
//| * the gradient is undefined at the trial point. Some basis |
//| functions have discontinuous derivatives at the interpolation|
//| nodes: |
//| * biharmonic splines f = r have no Hessian and no gradient |
//| at the nodes In these cases only DY is set to zero (Y is |
//| still returned) |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y, DY - possibly preallocated arrays; if array size is |
//| large enough to store results, this function does |
//| not reallocate array to fit output size exactly. |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. |
//| DY - derivatives, array[NX * NY]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX-dimensional gradient |
//| of the scalar NX-dimensional function |
//| Zero is returned when the first derivative is |
//| undefined. |
//+------------------------------------------------------------------+
void CRBF::RBFTSDiffBuf(CRBFModel &s,CRBFCalcBuffer &buf,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy)
{
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,"RBFTsDiffBuf: Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),"RBFTsDiffBuf: X contains infinite or NaN values"))
return;
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,"RBFTsDiffBuf: integrity check 3985 failed"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
for(int i=0; i<=s.m_ny-1; i++)
y.Set(i,0);
for(int i=0; i<=s.m_ny*s.m_nx-1; i++)
dy.Set(i,0);
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1TSDiffBuf(s.m_model1,buf.m_bufv1,x,y,dy);
break;
case 2:
CRBFV2::RBFV2TsDiffBuf(s.m_model2,buf.m_bufv2,x,y,dy);
break;
case 3:
CRBFV3::RBFV3TsDiffBuf(s.m_model3,buf.m_bufv3,x,y,dy);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model and its first |
//| and second derivatives (Hessian matrix) at the given point, using|
//| external buffer object (internal temporaries of the RBF model are|
//| not modified). |
//| This function allows to use same RBF model object in different |
//| threads, assuming that different threads use different instances|
//| of the buffer structure. |
//| This function returns 0 in Y and/or DY and/or D2Y in the |
//| following cases: |
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
//| * the gradient and/or Hessian is undefined at the trial point. |
//| Some basis functions have discontinuous derivatives at the |
//| interpolation nodes: |
//| * thin plate splines have no Hessian at the nodes |
//| * biharmonic splines f = r have no Hessian and no gradient at |
//| the nodes In these cases only corresponding derivative is set|
//| to zero, and the rest of the derivatives is still returned. |
//| INPUT PARAMETERS: |
//| S - RBF model, may be shared between different threads |
//| Buf - buffer object created for this particular instance |
//| of RBF model with RBFCreateCalcBuffer(). |
//| X - coordinates, array[NX]. X may have more than NX |
//| elements, in this case only leading NX will be used|
//| Y, DY, D2Y - possible preallocated output arrays. If these |
//| arrays are smaller than required to store the |
//| result, they are automatically reallocated. If |
//| array is large enough, it is not resized. |
//| OUTPUT PARAMETERS: |
//| Y - function value, array[NY]. |
//| DY - first derivatives, array[NY * NX]: |
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
//| stores derivative of function component I with |
//| respect to input J. |
//| * for NY = 1 it is simply NX - dimensional gradient|
//| of the scalar NX - dimensional function Zero is |
//| returned when the first derivative is undefined. |
//| D2Y - second derivatives, array[NY * NX * NX]: |
//| * for NY = 1 it is NX*NX array that stores Hessian |
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
//| * for a vector - valued RBF with NY > 1 it contains|
//| NY subsequently stored Hessians: an element |
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
//| 0 <= I < NX and 0 <= J < NX stores second |
//| derivative of the function #K with respect to |
//| inputs and #J. |
//| Zero is returned when the second derivative is |
//| undefined. |
//+------------------------------------------------------------------+
void CRBF::RBFTSHessBuf(CRBFModel &s,CRBFCalcBuffer &buf,
CRowDouble &x,CRowDouble &y,
CRowDouble &dy,CRowDouble &d2y)
{
//--- check
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
return;
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,__FUNCTION__+": integrity check 3953 failed"))
return;
if(CAp::Len(y)<s.m_ny)
y.Resize(s.m_ny);
if(CAp::Len(dy)<s.m_ny*s.m_nx)
dy.Resize(s.m_ny*s.m_nx);
if(CAp::Len(d2y)<s.m_ny*s.m_nx*s.m_nx)
d2y.Resize(s.m_ny*s.m_nx*s.m_nx);
for(int i=0; i<s.m_ny; i++)
y.Set(i,0);
for(int i=0; i<s.m_ny*s.m_nx; i++)
dy.Set(i,0);
for(int i=0; i<s.m_ny*s.m_nx*s.m_nx; i++)
d2y.Set(i,0);
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1TSHessBuf(s.m_model1,buf.m_bufv1,x,y,dy,d2y);
break;
case 2:
CRBFV2::RBFV2TSHessBuf(s.m_model2,buf.m_bufv2,x,y,dy,d2y);
break;
case 3:
CRBFV3::RBFV3TSHessBuf(s.m_model3,buf.m_bufv3,x,y,dy,d2y);
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This is legacy function for gridded calculation of RBF model. |
//| It is superseded by RBFGridCalc2V() and RBFGridCalc2VSubset() |
//| functions. |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc2(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CMatrixDouble &y)
{
//--- create variables
CRowDouble yy;
y.Resize(0,0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
switch(s.m_modelversion)
{
case 1:
CRBFV1::RBFV1GridCalc2(s.m_model1,x0,n0,x1,n1,y);
break;
case 2:
CRBFV2::RBFV2GridCalc2(s.m_model2,x0,n0,x1,n1,y);
break;
case 3:
CAblasF::RAllocM(n0,n1,y);
if(s.m_nx!=2 || s.m_ny!=1)
{
CAblasF::RSetM(n0,n1,0.0,y);
break;
}
RBFGridCalc2V(s,x0,n0,x1,n1,yy);
for(int i=0; i<n0; i++)
{
for(int j=0; j<n1; j++)
y.Set(i,j,yy[i+j*n0]);
}
break;
default:
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the regular |
//| grid, which has N0*N1 points, with Point[I, J] = (X0[I], X1[J]). |
//| Vector - valued RBF models are supported. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| INPUT PARAMETERS: |
//| S - RBF model, used in read - only mode, can be shared |
//| between multiple invocations of this function from|
//| multiple threads. |
//| X0 - array of grid nodes, first coordinates, array[N0]. |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N0 - grid size(number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N1 - grid size(number of nodes) in the second dimension |
//| OUTPUT PARAMETERS: |
//| Y - function values, array[NY * N0 * N1], where NY is a|
//| number of "output" vector values(this function |
//| supports vector - valued RBF models). Y is |
//| out-variable and is reallocated by this function. |
//| Y[K + NY * (I0 + I1 * N0)] = F_k(X0[I0], X1[I1]), |
//| for: |
//| * K = 0...NY - 1 |
//| * I0 = 0...N0 - 1 |
//| * I1 = 0...N1 - 1 |
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
//| may have X[i] = X[i + 1] for some i. It does not provide |
//| you any performance benefits due to duplication of points,|
//| just convenience and flexibility. |
//| NOTE: this function is re-entrant, i.e. you may use same |
//| CRBFModel structure in multiple threads calling this |
//| function for different grids. |
//| NOTE: if you need function values on some subset of regular grid,|
//| which may be described as "several compact and dense |
//| islands", you may use RBFGridCalc2VSubset(). |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc2V(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CRowDouble &y)
{
bool dummy[];
y.Resize(0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
for(int i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(int i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
//--- function call
RBFGridCalc2VX(s,x0,n0,x1,n1,dummy,false,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at some subset |
//| of regular grid: |
//| * grid has N0*N1 points, with Point[I, J] = (X0[I], X1[J]) |
//| * only values at some subset of this grid are required |
//| Vector - valued RBF models are supported. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>2 |
//| INPUT PARAMETERS: |
//| S - RBF model, used in read - only mode, can be shared |
//| between multiple invocations of this function from |
//| multiple threads. |
//| X0 - array of grid nodes, first coordinates, array[N0]. |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N0 - grid size (number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N1 - grid size(number of nodes) in the second dimension |
//| FlagY - array[N0 * N1]: |
//| * Y[I0 + I1 * N0] corresponds to node (X0[I0], |
//| X1[I1]) |
//| *it is a "bitmap" array which contains False for |
//| nodes which are NOT calculated, and True for |
//| nodes which are required. |
//| OUTPUT PARAMETERS: |
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
//| is a number of "output" vector values (this |
//| function supports vector - valued RBF models): |
//| * Y[K + NY * (I0 + I1 * N0)] = F_k(X0[I0], X1[I1]),|
//| for K = 0...NY-1, I0 = 0...N0-1, I1 = 0...N1-1. |
//| * elements of Y[] which correspond to FlagY[]=True |
//| are loaded by model values(which may be exactly |
//| zero for some nodes). |
//| * elements of Y[] which correspond to FlagY[]=False|
//| MAY be initialized by zeros OR may be calculated|
//| This function processes grid as a hierarchy of |
//| nested blocks and micro-rows. If just one |
//| element of micro-row is required, entire micro- |
//| row (up to 8 nodes in the current version, but |
//| no promises) is calculated. |
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
//| may have X[i] = X[i + 1] for some i. It does not provide |
//| you any performance benefits due to duplication of points, |
//| just convenience and flexibility. |
//| NOTE: this function is re - entrant, i.e. you may use same |
//| CRBFModel structure in multiple threads calling this |
//| function for different grids. |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc2VSubset(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,bool &flagy[],
CRowDouble &y)
{
y.Resize(0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CAp::Len(flagy)>=n0*n1,__FUNCTION__+": Length(FlagY)<N0*N1*N2"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
for(int i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(int i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
//--- function call
RBFGridCalc2VX(s,x0,n0,x1,n1,flagy,true,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at the regular |
//| grid, which has N0*N1*N2 points, with Point[I, J, K] = (X0[I], |
//| X1[J], X2[K]). Vector - valued RBF models are supported. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| INPUT PARAMETERS: |
//| S - RBF model, used in read-only mode, can be shared |
//| between multiple invocations of this function from|
//| multiple threads. |
//| X0 - array of grid nodes, first coordinates, array[N0]. |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N0 - grid size(number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N1 - grid size(number of nodes) in the second dimension |
//| X2 - array of grid nodes, third coordinates, array[N2] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N2 - grid size(number of nodes) in the third dimension |
//| OUTPUT PARAMETERS: |
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
//| is a number of "output" vector values (this |
//| function supports vector-valued RBF models). Y is |
//| out-variable and is reallocated by this function. |
//| Y[K+NY*(I0+I1*N0+I2*N0*N1)] = F_k(X0[I0],X1[I1],X2[I2]),|
//| for: |
//| * K = 0...NY - 1 |
//| * I0 = 0...N0 - 1 |
//| * I1 = 0...N1 - 1 |
//| * I2 = 0...N2 - 1 |
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
//| may have X[i] = X[i + 1] for some i. It does not provide |
//| you any performance benefits due to duplication of points,|
//| just convenience and flexibility. |
//| NOTE: this function is re-entrant, i.e. you may use same |
//| CRBFModel structure in multiple threads calling this |
//| function for different grids. |
//| NOTE: if you need function values on some subset of regular grid,|
//| which may be described as "several compact and dense |
//| islands", you may use RBFGridCalc3VSubset(). |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc3V(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CRowDouble &x2,int n2,
CRowDouble &y)
{
bool dummy[];
y.Resize(0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
return;
for(int i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(int i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
for(int i=0; i<n2-1; i++)
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
return;
//--- function call
RBFGridCalc3VX(s,x0,n0,x1,n1,x2,n2,dummy,false,y);
}
//+------------------------------------------------------------------+
//| This function calculates values of the RBF model at some subset |
//| of regular grid: |
//| * grid has N0*N1*N2 points, with Point[I, J, K] = (X0[I], |
//| X1[J], X2[K]) |
//| * only values at some subset of this grid are required |
//| Vector - valued RBF models are supported. |
//| This function returns 0.0 when: |
//| * model is not initialized |
//| * NX<>3 |
//| INPUT PARAMETERS: |
//| S - RBF model, used in read - only mode, can be shared |
//| between multiple invocations of this function from|
//| multiple threads. |
//| X0 - array of grid nodes, first coordinates, array[N0]. |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N0 - grid size(number of nodes) in the first dimension |
//| X1 - array of grid nodes, second coordinates, array[N1] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N1 - grid size(number of nodes) in the second dimension |
//| X2 - array of grid nodes, third coordinates, array[N2] |
//| Must be ordered by ascending. Exception is |
//| generated if the array is not correctly ordered. |
//| N2 - grid size(number of nodes) in the third dimension |
//| FlagY - array[N0 * N1 * N2]: |
//| * Y[I0 + I1 * N0 + I2 * N0 * N1] corresponds to |
//| node (X0[I0], X1[I1], X2[I2]) |
//| *it is a "bitmap" array which contains False for |
//| nodes which are NOT calculated, and True for |
//| nodes which are required. |
//| OUTPUT PARAMETERS: |
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
//| is a number of "output" vector values(this function|
//| supports vector- valued RBF models): |
//| * Y[K+NY*(I0+I1*N0+I2*N0*N1)] = F_k(X0[I0],X1[I1],X2[I2]),|
//| for K = 0...NY-1, I0 = 0...N0-1, I1 = 0...N1-1, |
//| I2 = 0...N2-1. |
//| * elements of Y[] which correspond to FlagY[]=True |
//| are loaded by model values(which may be exactly |
//| zero for some nodes). |
//| * elements of Y[] which correspond to FlagY[]=False|
//| MAY be initialized by zeros OR may be calculated.|
//| This function processes grid as a hierarchy of |
//| nested blocks and micro-rows. If just one element|
//| of micro-row is required, entire micro-row (up to|
//| 8 nodes in the current version, but no promises) |
//| is calculated. |
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
//| may have X[i] = X[i + 1] for some i. It does not provide |
//| you any performance benefits due to duplication of points,|
//| just convenience and flexibility. |
//| NOTE: this function is re-entrant, i.e. you may use same |
//| CRBFModel structure in multiple threads calling this |
//| function for different grids. |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc3VSubset(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CRowDouble &x2,
int n2,bool &flagy[],CRowDouble &y)
{
y.Resize(0);
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
return;
if(!CAp::Assert(CAp::Len(flagy)>=n0*n1*n2,__FUNCTION__+": Length(FlagY)<N0*N1*N2"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
return;
for(int i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(int i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
for(int i=0; i<n2-1; i++)
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
return;
//--- function call
RBFGridCalc3VX(s,x0,n0,x1,n1,x2,n2,flagy,true,y);
}
//+------------------------------------------------------------------+
//| This function, depending on SparseY, acts as RBFGridCalc2V |
//| (SparseY = False) or RBFGridCalc2VSubset (SparseY = True) |
//| function. See comments for these functions for more information |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc2VX(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,bool &flagy[],
bool sparsey,CRowDouble &y)
{
//--- create variables
int nx=0;
int ny=0;
int ylen=0;
int k=0;
int dstoffs=0;
CHighQualityRandState rs;
CRowDouble dummyx2;
CRowDouble dummyx3;
CRowDouble tx;
CRowDouble ty;
CRBFCalcBuffer calcbuf;
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
for(int i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(int i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
//--- Prepare local variables
nx=s.m_nx;
ny=s.m_ny;
CHighQualityRand::HQRndSeed(325,46345,rs);
//--- Prepare output array
ylen=ny*n0*n1;
y=vector<double>::Zeros(ylen);
if(s.m_nx!=2)
return;
//--- Reference code for V3 models
if(s.m_modelversion==3)
{
dummyx2=vector<double>::Zeros(1);
dummyx3=vector<double>::Zeros(1);
CRBFV3::RBFV3GridCalcVX(s.m_model3,x0,n0,x1,n1,dummyx2,1,dummyx3,1,flagy,sparsey,y);
return;
}
//--- Process V2 model
if(s.m_modelversion==2)
{
dummyx2=vector<double>
::Zeros(1);
dummyx3=vector<double>::Zeros(1);
CRBFV2::RBFV2GridCalcVX(s.m_model2,x0,n0,x1,n1,dummyx2,1,dummyx3,1,flagy,sparsey,y);
return;
}
//--- Reference code for V1 models
if(s.m_modelversion==1)
{
tx.Resize(nx);
RBFCreateCalcBuffer(s,calcbuf);
for(int i=0; i<n0; i++)
{
for(int j=0; j<n1; j++)
{
k=i+j*n0;
dstoffs=ny*k;
if(sparsey && !flagy[k])
{
for(int l=0; l<ny; l++)
y.Set(l+dstoffs,0);
continue;
}
tx.Set(0,x0[i]);
tx.Set(1,x1[j]);
RBFTSCalcBuf(s,calcbuf,tx,ty);
for(int l=0; l<ny; l++)
y.Set(l+dstoffs,ty[l]);
}
}
return;
}
//--- Unknown model
CAp::Assert(false,__FUNCTION__+": integrity check failed");
}
//+------------------------------------------------------------------+
//| This function, depending on SparseY, acts as RBFGridCalc3V |
//| (SparseY = False) or RBFGridCalc3VSubset (SparseY = True) |
//| function. See comments for these functions for more information |
//+------------------------------------------------------------------+
void CRBF::RBFGridCalc3VX(CRBFModel &s,CRowDouble &x0,int n0,
CRowDouble &x1,int n1,CRowDouble &x2,
int n2,bool &flagy[],bool sparsey,
CRowDouble &y)
{
//--- create variables
int i=0;
int ylen=0;
int nx=0;
int ny=0;
double rmax=0;
CRowInt blocks0;
CRowInt blocks1;
CRowInt blocks2;
int blockscnt0=0;
int blockscnt1=0;
int blockscnt2=0;
double blockwidth=0;
double searchradius=0;
double avgfuncpernode=0;
int ntrials=0;
int maxblocksize=0;
CGridCalc3v1Buf bufseedv1;
CGridCalc3v1Buf bufpool;
CHighQualityRandState rs;
CRowDouble dummyx3;
//--- check
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
return;
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
return;
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
return;
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
return;
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
return;
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
return;
for(i=0; i<n0-1; i++)
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
return;
for(i=0; i<n1-1; i++)
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
return;
for(i=0; i<n2-1; i++)
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
return;
//--- Prepare local variables
nx=s.m_nx;
ny=s.m_ny;
CHighQualityRand::HQRndSeed(325,46345,rs);
//--- Prepare output array
ylen=ny*n0*n1*n2;
y=vector<double>::Zeros(ylen);
if(s.m_nx!=3)
return;
//--- Process V1 model
if(s.m_modelversion==1)
{
//--- Fast exit for models without centers
if(s.m_model1.m_nc==0)
return;
//--- Prepare seed, create shared pool of temporary buffers
bufseedv1.m_cx=vector<double>::Zeros(nx);
bufseedv1.m_tx=vector<double>::Zeros(nx);
bufseedv1.m_ty=vector<double>::Zeros(ny);
bufseedv1.m_expbuf0=vector<double>::Zeros(n0);
bufseedv1.m_expbuf1=vector<double>::Zeros(n1);
bufseedv1.m_expbuf2=vector<double>::Zeros(n2);
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_model1.m_tree,bufseedv1.m_requestbuf);
bufpool=bufseedv1;
//--- Analyze input grid:
//--- * analyze average number of basis functions per grid node
//--- * partition grid in into blocks
rmax=s.m_model1.m_rmax;
blockwidth=2*rmax;
maxblocksize=8;
searchradius=rmax*m_rbffarradius+0.5*MathSqrt(s.m_nx)*blockwidth;
ntrials=100;
avgfuncpernode=0.0;
for(i=0; i<=ntrials-1; i++)
{
bufseedv1.m_tx.Set(0,x0[CHighQualityRand::HQRndUniformI(rs,n0)]);
bufseedv1.m_tx.Set(1,x1[CHighQualityRand::HQRndUniformI(rs,n1)]);
bufseedv1.m_tx.Set(2,x2[CHighQualityRand::HQRndUniformI(rs,n2)]);
avgfuncpernode+=(double)CNearestNeighbor::KDTreeTsQueryRNN(s.m_model1.m_tree,bufseedv1.m_requestbuf,bufseedv1.m_tx,searchradius,true)/(double)ntrials;
}
blocks0.Resize(n0+1);
blockscnt0=0;
blocks0.Set(0,0);
for(i=1; i<n0; i++)
{
if((double)(x0[i]-x0[blocks0[blockscnt0]])>(double)(blockwidth) || i-blocks0[blockscnt0]>=maxblocksize)
{
blockscnt0++;
blocks0.Set(blockscnt0,i);
}
}
blockscnt0++;
blocks0.Set(blockscnt0,n0);
blocks1.Resize(n1+1);
blocks1.Fill(0);
blockscnt1=0;
for(i=1; i<n1; i++)
{
if((double)(x1[i]-x1[blocks1[blockscnt1]])>(double)(blockwidth) || i-blocks1[blockscnt1]>=maxblocksize)
{
blockscnt1++;
blocks1.Set(blockscnt1,i);
}
}
blockscnt1++;
blocks1.Set(blockscnt1,n1);
blocks2.Resize(n2+1);
blockscnt2=0;
blocks2.Set(0,0);
for(i=1; i<n2; i++)
{
if((double)(x2[i]-x2[blocks2[blockscnt2]])>(double)(blockwidth) || i-blocks2[blockscnt2]>=maxblocksize)
{
blockscnt2++;
blocks2.Set(blockscnt2,i);
}
}
blockscnt2++;
blocks2.Set(blockscnt2,n2);
//--- Perform calculation in multithreaded mode
CRBFV1::RBFV1GridCalc3VRec(s.m_model1,x0,n0,x1,n1,x2,n2,blocks0,0,blockscnt0,blocks1,0,blockscnt1,blocks2,0,blockscnt2,flagy,sparsey,searchradius,avgfuncpernode,bufpool,y);
//--- Done
return;
}
//--- Process V2 model
if(s.m_modelversion==2)
{
dummyx3=vector<double>::Zeros(1);
CRBFV2::RBFV2GridCalcVX(s.m_model2,x0,n0,x1,n1,x2,n2,dummyx3,1,flagy,sparsey,y);
return;
}
//--- Process V3 model
if(s.m_modelversion==3)
{
dummyx3=vector<double>::Zeros(1);
CRBFV3::RBFV3GridCalcVX(s.m_model3,x0,n0,x1,n1,x2,n2,dummyx3,1,flagy,sparsey,y);
return;
}
//--- Unknown model
CAp::Assert(false,"RBFGridCalc3VX: integrity check failed");
}
//+------------------------------------------------------------------+
//| This function "unpacks" RBF model by extracting its coefficients.|
//| INPUT PARAMETERS: |
//| S - RBF model |
//| OUTPUT PARAMETERS: |
//| NX - dimensionality of argument |
//| NY - dimensionality of the target function |
//| XWR - model information, 2D array. One row of the array |
//| corresponds to one basis function. |
//| For ModelVersion = 1 we have NX + NY + 1 columns: |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of the function|
//| being modeled |
//| * last column - radius, same for all dimensions of the |
//| function being modeled |
//| For ModelVersion = 2 we have NX + NY + NX columns: |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of the function|
//| being modeled |
//| * last NX columns - radii, one per dimension |
//| For ModelVersion = 3 we have NX + NY + NX + 3 columns: |
//| * first NX columns - coordinates of the center |
//| * next NY columns - weights, one per dimension of the |
//| function being modeled |
//| * next NX columns - radii, one per dimension |
//| * next column - basis function type: |
//| * 1 for f = r |
//| * 2 for f = r ^ 2 * ln(r) |
//| * 10 for multiquadric f=sqrt(r^2+alpha^2) |
//| * next column - basis function parameter: |
//| * alpha, for basis function type 10 |
//| * ignored(zero) for other basis function |
//| types |
//| * next column - point index in the original dataset, or -1|
//| for an artificial node created by the |
//| solver. The algorithm may reorder the |
//| nodes, drop some nodes or add artificial |
//| nodes. Thus, one parsing this column |
//| should expect all these kinds of |
//| alterations in the dataset. |
//| NC - number of the centers |
//| V - polynomial term, array[NY, NX + 1]. One row per |
//| one dimension of the function being modelled. |
//| First NX elements are linear coefficients, V[NX]|
//| is equal to the constant part. |
//| ModelVersion - version of the RBF model: |
//| * 1 - for models created by QNN and RBF-ML |
//| algorithms, compatible with ALGLIB 3.10 or|
//| earlier. |
//| * 2 - for models created by HierarchicalRBF, |
//| requires ALGLIB 3.11 or later |
//| * 3 - for models created by DDM-RBF, requires |
//| ALGLIB 3.19 or later |
//+------------------------------------------------------------------+
void CRBF::RBFUnpack(CRBFModel &s,int &nx,int &ny,CMatrixDouble &xwr,
int &nc,CMatrixDouble &v,int &modelversion)
{
nx=0;
ny=0;
xwr.Resize(0,0);
nc=0;
v.Resize(0,0);
modelversion=0;
if(s.m_modelversion==1)
{
modelversion=1;
CRBFV1::RBFV1Unpack(s.m_model1,nx,ny,xwr,nc,v);
return;
}
if(s.m_modelversion==2)
{
modelversion=2;
CRBFV2::RBFV2Unpack(s.m_model2,nx,ny,xwr,nc,v);
return;
}
if(s.m_modelversion==3)
{
modelversion=3;
CRBFV3::RBFV3Unpack(s.m_model3,nx,ny,xwr,nc,v);
return;
}
CAp::Assert(false,__FUNCTION__+": integrity check failure");
}
//+------------------------------------------------------------------+
//|This function returns model version. |
//| INPUT PARAMETERS: |
//| S - RBF model |
//| RESULT: |
//| * 1 - for models created by QNN and RBF-ML algorithms, |
//| compatible with ALGLIB 3.10 or earlier. |
//| * 2 - for models created by HierarchicalRBF, requires |
//| ALGLIB 3.11 or later |
//+------------------------------------------------------------------+
int CRBF::RBFGetModelVersion(CRBFModel &s)
{
return(s.m_modelversion);
}
//+------------------------------------------------------------------+
//| This function is used to peek into hierarchical RBF construction |
//| process from some other thread and get current progress indicator|
//| It returns value in [0, 1]. |
//| IMPORTANT: only HRBFs (hierarchical RBFs) support peeking into |
//| progress indicator. Legacy RBF-ML and RBF-QNN do not |
//| support it. You will always get 0 value. |
//| INPUT PARAMETERS: |
//| S - RBF model object |
//| RESULT: |
//| progress value, in [0, 1] |
//+------------------------------------------------------------------+
double CRBF::RBFPeekProgress(CRBFModel &s)
{
double result=(double)s.m_progress10000/10000.0;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function is used to submit a request for termination of the |
//| hierarchical RBF construction process from some other thread. As |
//| result, RBF construction is terminated smoothly (with proper |
//| deallocation of all necessary resources) and resultant model is |
//| filled by zeros. |
//| A rep.m_terminationtype = 8 will be returned upon receiving such |
//| request. |
//| IMPORTANT: only HRBFs(hierarchical RBFs) support termination |
//| requests. Legacy RBF-ML and RBF-QNN do not support it.|
//| An attempt to terminate their construction will be |
//| ignored. |
//| IMPORTANT: termination request flag is cleared when the model |
//| construction starts. Thus, any pre-construction |
//| termination requests will be silently ignored - only |
//| ones submitted AFTER construction has actually began |
//| will be handled. |
//| INPUT PARAMETERS: |
//| S - RBF model object |
//+------------------------------------------------------------------+
void CRBF::RBFRequestTermination(CRBFModel &s)
{
s.m_terminationrequest=true;
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
void CRBF::RBFAlloc(CSerializer &s,CRBFModel &model)
{
//--- Header
s.Alloc_Entry();
//--- V1 model
if(model.m_modelversion==1)
{
//--- Header
s.Alloc_Entry();
CRBFV1::RBFV1Alloc(s,model.m_model1);
return;
}
//--- V2 model
if(model.m_modelversion==2)
{
//--- Header
s.Alloc_Entry();
CRBFV2::RBFV2Alloc(s,model.m_model2);
return;
}
//--- V3 model
if(model.m_modelversion==3)
{
//--- Header
s.Alloc_Entry();
CRBFV3::RBFV3Alloc(s,model.m_model3);
return;
}
CAp::Assert(false);
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
void CRBF::RBFSerialize(CSerializer &s,CRBFModel &model)
{
//--- Header
s.Serialize_Int(CSCodes::GetRBFSerializationCode());
//--- V1 model
if(model.m_modelversion==1)
{
s.Serialize_Int(m_rbffirstversion);
CRBFV1::RBFV1Serialize(s,model.m_model1);
return;
}
//--- V2 model
if(model.m_modelversion==2)
{
//--- Header
s.Serialize_Int(m_rbfversion2);
CRBFV2::RBFV2Serialize(s,model.m_model2);
return;
}
//--- V3 model
if(model.m_modelversion==3)
{
//--- Header
s.Serialize_Int(m_rbfversion3);
CRBFV3::RBFV3Serialize(s,model.m_model3);
return;
}
CAp::Assert(false);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
void CRBF::RBFUnserialize(CSerializer &s,CRBFModel &model)
{
//--- create variables
int i0=0;
int i1=0;
RBFPrepareNonSerializableFields(model);
//--- Header
i0=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i0==CSCodes::GetRBFSerializationCode(),__FUNCTION__+": stream header corrupted"))
return;
i1=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i1==m_rbffirstversion || i1==m_rbfversion2 || i1==m_rbfversion3,__FUNCTION__+": stream header corrupted"))
return;
//--- V1 model
if(i1==m_rbffirstversion)
{
CRBFV1::RBFV1Unserialize(s,model.m_model1);
model.m_modelversion=1;
model.m_ny=model.m_model1.m_ny;
model.m_nx=model.m_model1.m_nx;
InitializeV2(model.m_nx,model.m_ny,model.m_model2);
InitializeV3(model.m_nx,model.m_ny,model.m_model3);
RBFCreateCalcBuffer(model,model.m_calcbuf);
return;
}
//--- V2 model
if(i1==m_rbfversion2)
{
CRBFV2::RBFV2Unserialize(s,model.m_model2);
model.m_modelversion=2;
model.m_ny=model.m_model2.m_ny;
model.m_nx=model.m_model2.m_nx;
InitializeV1(model.m_nx,model.m_ny,model.m_model1);
InitializeV3(model.m_nx,model.m_ny,model.m_model3);
RBFCreateCalcBuffer(model,model.m_calcbuf);
return;
}
//--- V3 model
if(i1==m_rbfversion3)
{
CRBFV3::RBFV3Unserialize(s,model.m_model3);
model.m_modelversion=3;
model.m_ny=model.m_model3.m_ny;
model.m_nx=model.m_model3.m_nx;
InitializeV1(model.m_nx,model.m_ny,model.m_model1);
InitializeV2(model.m_nx,model.m_ny,model.m_model2);
RBFCreateCalcBuffer(model,model.m_calcbuf);
return;
}
CAp::Assert(false,__FUNCTION__+": unserialiation error (unexpected model type)");
}
//+------------------------------------------------------------------+
//| Initialize empty model |
//+------------------------------------------------------------------+
void CRBF::RBFPrepareNonSerializableFields(CRBFModel &s)
{
s.m_n=0;
s.m_hasscale=false;
s.m_radvalue=1;
s.m_radzvalue=5;
s.m_nlayers=0;
s.m_lambdav=0;
s.m_aterm=1;
s.m_algorithmtype=0;
s.m_epsort=m_eps;
s.m_epserr=m_eps;
s.m_maxits=0;
s.m_nnmaxits=100;
}
//+------------------------------------------------------------------+
//| Initialize V1 model (skip initialization for NX = 1 or NX > 3) |
//+------------------------------------------------------------------+
void CRBF::InitializeV1(int nx,int ny,CRBFV1Model &s)
{
if(nx==2 || nx==3)
CRBFV1::RBFV1Create(nx,ny,s);
}
//+------------------------------------------------------------------+
//| Initialize V2 model |
//+------------------------------------------------------------------+
void CRBF::InitializeV2(int nx,int ny,CRBFV2Model &s)
{
CRBFV2::RBFV2Create(nx,ny,s);
}
//+------------------------------------------------------------------+
//| Initialize V3 model |
//+------------------------------------------------------------------+
void CRBF::InitializeV3(int nx,int ny,CRBFV3Model &s)
{
CRBFV3::RBFV3Create(nx,ny,2,0,s);
}
//+------------------------------------------------------------------+
//| Cleans report fields |
//+------------------------------------------------------------------+
void CRBF::ClearReportFields(CRBFReport &rep)
{
rep.m_rmserror=AL_NaN;
rep.m_maxerror=AL_NaN;
rep.m_arows=0;
rep.m_acols=0;
rep.m_annz=0;
rep.m_iterationscount=0;
rep.m_nmv=0;
rep.m_terminationtype=0;
}
//+------------------------------------------------------------------+
//| 3-dimensional spline inteprolant |
//+------------------------------------------------------------------+
struct CSpline3DInterpolant
{
public:
int m_d;
int m_k;
int m_l;
int m_m;
int m_n;
int m_stype;
CRowDouble m_f;
CRowDouble m_x;
CRowDouble m_y;
CRowDouble m_z;
//--- constructor / destructor
CSpline3DInterpolant(void);
~CSpline3DInterpolant(void) {}
//--- copy
void Copy(const CSpline3DInterpolant&obj);
//--- overloading
void operator=(const CSpline3DInterpolant&obj) { Copy(obj); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CSpline3DInterpolant::CSpline3DInterpolant(void)
{
m_d=0;
m_k=0;
m_l=0;
m_m=0;
m_n=0;
m_stype=0;
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CSpline3DInterpolant::Copy(const CSpline3DInterpolant &obj)
{
m_d=obj.m_d;
m_k=obj.m_k;
m_l=obj.m_l;
m_m=obj.m_m;
m_n=obj.m_n;
m_stype=obj.m_stype;
m_f=obj.m_f;
m_x=obj.m_x;
m_y=obj.m_y;
m_z=obj.m_z;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CSpline3D
{
public:
static double Spline3DCalc(CSpline3DInterpolant&c,double x,double y,double z);
static void Spline3DLinTransXYZ(CSpline3DInterpolant&c,double ax,double bx,double ay,double by,double az,double bz);
static void Spline3DLinTransF(CSpline3DInterpolant&c,double a,double b);
static void Spline3DCopy(CSpline3DInterpolant&c,CSpline3DInterpolant&cc);
static void Spline3DResampleTrilinear(CRowDouble&a,int oldzcount,int oldycount,int oldxcount,int newzcount,int newycount,int newxcount,CRowDouble&b);
static void Spline3DBuildTrilinearV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&z,int l,CRowDouble&f,int d,CSpline3DInterpolant&c);
static void Spline3DCalcVBuf(CSpline3DInterpolant&c,double x,double y,double z,CRowDouble&f);
static void Spline3DCalcV(CSpline3DInterpolant&c,double x,double y,double z,CRowDouble&f);
static void Spline3DUnpackV(CSpline3DInterpolant&c,int &n,int &m,int &l,int &d,int &stype,CMatrixDouble&tbl);
private:
static void Spline3DDiff(CSpline3DInterpolant&c,double x,double y,double z,double&f,double&fx,double&fy,double&fxy);
};
//+------------------------------------------------------------------+
//| This subroutine calculates the value of the trilinear or tricubic|
//| spline at the given point (X,Y,Z). |
//| INPUT PARAMETERS: |
//| C - coefficients table. Built by BuildBilinearSpline or|
//| BuildBicubicSpline. |
//| X, Y, |
//| Z - point |
//| Result: |
//| S(x,y,z) |
//+------------------------------------------------------------------+
double CSpline3D::Spline3DCalc(CSpline3DInterpolant &c,
double x,
double y,
double z)
{
//--- create variables
double result=0;
double v=0;
double vx=0;
double vy=0;
double vxy=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return(0);
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X=NaN/Infinite,Y=NaN/Infinite or Z=NaN/Infinite"))
return(0);
if(c.m_d!=1)
{
result=0;
return(result);
}
Spline3DDiff(c,x,y,z,v,vx,vy,vxy);
result=v;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline |
//| argument. |
//| INPUT PARAMETERS: |
//| C - spline interpolant |
//| AX, BX - transformation coefficients: x = A*u + B |
//| AY, BY - transformation coefficients: y = A*v + B |
//| AZ, BZ - transformation coefficients: z = A*w + B |
//| OUTPUT PARAMETERS: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DLinTransXYZ(CSpline3DInterpolant &c,
double ax,
double bx,
double ay,
double by,
double az,
double bz)
{
//--- create variables
CRowDouble x;
CRowDouble y;
CRowDouble z;
CRowDouble f;
CRowDouble v;
int i=0;
int j=0;
int k=0;
int di=0;
int i_=0;
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
//--- prepare
x=c.m_x;
y=c.m_y;
z=c.m_z;
x.Resize(c.m_n);
y.Resize(c.m_m);
z.Resize(c.m_l);
f.Resize(c.m_m*c.m_n*c.m_l*c.m_d);
//---Handle different combinations of zero/nonzero AX/AY/AZ
if(ax!=0.0 && ay!=0.0 && az!=0.0)
f=c.m_f;
if(ax==0.0 && ay!=0.0 && az!=0.0)
{
for(i=0; i<c.m_m; i++)
for(j=0; j<c.m_l; j++)
{
Spline3DCalcV(c,bx,y[i],z[j],v);
for(k=0; k<c.m_n; k++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*j+i)+k)+di,v[di]);
}
ax=1;
bx=0;
}
if(ax!=0.0 && ay==0.0 && az!=0.0)
{
for(i=0; i<c.m_n; i++)
for(j=0; j<c.m_l; j++)
{
Spline3DCalcV(c,x[i],by,z[j],v);
for(k=0; k<c.m_m; k++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*j+k)+i)+di,v[di]);
}
ay=1;
by=0;
}
if(ax!=0.0 && ay!=0.0 && az==0.0)
{
for(i=0; i<c.m_n; i++)
for(j=0; j<c.m_m; j++)
{
Spline3DCalcV(c,x[i],y[j],bz,v);
for(k=0; k<c.m_l; k++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
}
az=1;
bz=0;
}
if(ax==0.0 && ay==0.0 && az!=0.0)
{
for(i=0; i<c.m_l; i++)
{
Spline3DCalcV(c,bx,by,z[i],v);
for(k=0; k<c.m_m; k++)
for(j=0; j<c.m_n; j++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*i+k)+j)+di,v[di]);
}
ax=1;
bx=0;
ay=1;
by=0;
}
if(ax==0.0 && ay!=0.0 && az==0.0)
{
for(i=0; i<c.m_m; i++)
{
Spline3DCalcV(c,bx,y[i],bz,v);
for(k=0; k<c.m_l; k++)
for(j=0; j<c.m_n; j++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*k+i)+j)+di,v[di]);
}
ax=1;
bx=0;
az=1;
bz=0;
}
if(ax!=0.0 && ay==0.0 && az==0.0)
{
for(i=0; i<c.m_n; i++)
{
Spline3DCalcV(c,x[i],by,bz,v);
for(k=0; k<c.m_l; k++)
for(j=0; j<c.m_m; j++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
}
ay=1;
by=0;
az=1;
bz=0;
}
if(ax==0.0 && ay==0.0 && az==0.0)
{
Spline3DCalcV(c,bx,by,bz,v);
for(k=0; k<c.m_l; k++)
for(j=0; j<c.m_m; j++)
for(i=0; i<c.m_n; i++)
for(di=0; di<c.m_d; di++)
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
ax=1;
bx=0;
ay=1;
by=0;
az=1;
bz=0;
}
//---General case: AX<>0, AY<>0, AZ<>0
//---Unpack, scale and pack again.
x=(x-bx)/ax;
y=(y-by)/ay;
z=(z-bz)/az;
if(c.m_stype==-1)
Spline3DBuildTrilinearV(x,c.m_n,y,c.m_m,z,c.m_l,f,c.m_d,c);
}
//+------------------------------------------------------------------+
//| This subroutine performs linear transformation of the spline. |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| A, B - transformation coefficients: S2(x,y)=A*S(x,y,z)+B |
//| OUTPUT PARAMETERS: |
//| C - transformed spline |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DLinTransF(CSpline3DInterpolant &c,
double a,
double b)
{
//--- create variables
CRowDouble x;
CRowDouble y;
CRowDouble z;
CRowDouble f;
int i=0;
int j=0;
//--- check
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
x=c.m_x;
y=c.m_y;
z=c.m_z;
f=c.m_f*a+b;
x.Resize(c.m_n);
y.Resize(c.m_m);
z.Resize(c.m_l);
f.Resize(c.m_m*c.m_n*c.m_l*c.m_d);
if(c.m_stype==-1)
Spline3DBuildTrilinearV(x,c.m_n,y,c.m_m,z,c.m_l,f,c.m_d,c);
}
//+------------------------------------------------------------------+
//| This subroutine makes the copy of the spline model. |
//| INPUT PARAMETERS: |
//| C - spline interpolant |
//| OUTPUT PARAMETERS: |
//| CC - spline copy |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DCopy(CSpline3DInterpolant &c,
CSpline3DInterpolant &cc)
{
//--- create variables
int tblsize=0;
int i_=0;
//--- check
if(!CAp::Assert(c.m_k==1 || c.m_k==3,__FUNCTION__+": incorrect C (incorrect parameter C.K)"))
return;
cc.m_k=c.m_k;
cc.m_n=c.m_n;
cc.m_m=c.m_m;
cc.m_l=c.m_l;
cc.m_d=c.m_d;
tblsize=c.m_n*c.m_m*c.m_l*c.m_d;
cc.m_stype=c.m_stype;
cc.m_x=c.m_x;
cc.m_y=c.m_y;
cc.m_z=c.m_z;
cc.m_f=c.m_f;
cc.m_x.Resize(cc.m_n);
cc.m_y.Resize(cc.m_m);
cc.m_z.Resize(cc.m_l);
cc.m_f.Resize(tblsize);
}
//+------------------------------------------------------------------+
//| Trilinear spline resampling |
//| INPUT PARAMETERS: |
//| A - array[0..OldXCount*OldYCount*OldZCount-1], function|
//| values at the old grid: |
//| A[0] x=0,y=0,z=0 |
//| A[1] x=1,y=0,z=0 |
//| A[..] ... |
//| A[..] x=oldxcount-1,y=0,z=0 |
//| A[..] x=0,y=1,z=0 |
//| A[..] ... |
//| ... |
//| OldZCount - old Z-count, OldZCount>1 |
//| OldYCount - old Y-count, OldYCount>1 |
//| OldXCount - old X-count, OldXCount>1 |
//| NewZCount - new Z-count, NewZCount>1 |
//| NewYCount - new Y-count, NewYCount>1 |
//| NewXCount - new X-count, NewXCount>1 |
//| OUTPUT PARAMETERS: |
//| B - array[0..NewXCount*NewYCount*NewZCount-1], function|
//| values at the new grid: |
//| B[0] x=0,y=0,z=0 |
//| B[1] x=1,y=0,z=0 |
//| B[..] ... |
//| B[..] x=newxcount-1,y=0,z=0 |
//| B[..] x=0,y=1,z=0 |
//| B[..] ... |
//| ... |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DResampleTrilinear(CRowDouble &a,
int oldzcount,
int oldycount,
int oldxcount,
int newzcount,
int newycount,
int newxcount,
CRowDouble &b)
{
//--- create variables
double xd=0;
double yd=0;
double zd=0;
double c0=0;
double c1=0;
double c2=0;
double c3=0;
int ix=0;
int iy=0;
int iz=0;
b.Resize(0);
//--- check
if(!CAp::Assert(oldycount>1 && oldzcount>1 && oldxcount>1,__FUNCTION__+": length/width/height less than 1"))
return;
if(!CAp::Assert(newycount>1 && newzcount>1 && newxcount>1,__FUNCTION__+": length/width/height less than 1"))
return;
if(!CAp::Assert(CAp::Len(a)>=oldycount*oldzcount*oldxcount,__FUNCTION__+": length/width/height less than 1"))
return;
b.Resize(newxcount*newycount*newzcount);
for(int i=0; i<newxcount; i++)
for(int j=0; j<newycount; j++)
for(int k=0; k<newzcount; k++)
{
ix=i*(oldxcount-1)/(newxcount-1);
if(ix==oldxcount-1)
ix=oldxcount-2;
xd=(double)(i*(oldxcount-1))/(double)(newxcount-1)-ix;
iy=j*(oldycount-1)/(newycount-1);
if(iy==oldycount-1)
iy=oldycount-2;
yd=(double)(j*(oldycount-1))/(double)(newycount-1)-iy;
iz=k*(oldzcount-1)/(newzcount-1);
if(iz==oldzcount-1)
iz=oldzcount-2;
zd=(double)(k*(oldzcount-1))/(double)(newzcount-1)-iz;
c0=a[oldxcount*(oldycount*iz+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*iz+iy)+(ix+1)]*xd;
c1=a[oldxcount*(oldycount*iz+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*iz+(iy+1))+(ix+1)]*xd;
c2=a[oldxcount*(oldycount*(iz+1)+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+iy)+(ix+1)]*xd;
c3=a[oldxcount*(oldycount*(iz+1)+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+(iy+1))+(ix+1)]*xd;
c0=c0*(1-yd)+c1*yd;
c1=c2*(1-yd)+c3*yd;
b.Set(newxcount*(newycount*k+j)+i,c0*(1-zd)+c1*zd);
}
}
//+------------------------------------------------------------------+
//| This subroutine builds trilinear vector-valued spline. |
//| INPUT PARAMETERS: |
//| X - spline abscissas, array[0..N-1] |
//| Y - spline ordinates, array[0..M-1] |
//| Z - spline applicates, array[0..L-1] |
//| F - function values, array[0..M*N*L*D-1]: |
//| * first D elements store D values at (X[0],Y[0],Z[0]) |
//| * next D elements store D values at (X[1],Y[0],Z[0]) |
//| * next D elements store D values at (X[2],Y[0],Z[0]) |
//| * ... |
//| * next D elements store D values at (X[0],Y[1],Z[0]) |
//| * next D elements store D values at (X[1],Y[1],Z[0]) |
//| * next D elements store D values at (X[2],Y[1],Z[0]) |
//| * ... |
//| * next D elements store D values at (X[0],Y[0],Z[1]) |
//| * next D elements store D values at (X[1],Y[0],Z[1]) |
//| * next D elements store D values at (X[2],Y[0],Z[1]) |
//| * ... |
//| * general form - D function values at (X[i],Y[j]) are |
//| stored at F[D*(N*(M*K+J)+I)...D*(N*(M*K+J)+I)+D-1]. |
//| M,N, |
//| L - grid size, M>=2, N>=2, L>=2 |
//| D - vector dimension, D>=1 |
//| OUTPUT PARAMETERS: |
//| C - spline interpolant |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DBuildTrilinearV(CRowDouble &x,int n,
CRowDouble &y,int m,
CRowDouble &z,int l,
CRowDouble &f,int d,
CSpline3DInterpolant &c)
{
//--- create variables
double t=0;
int tblsize=n*m*l*d;;
int i=0;
int j=0;
int k=0;
int i0=0;
int j0=0;
//--- check
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
return;
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
if(!CAp::Assert(l>=2,__FUNCTION__+": L<2"))
return;
if(!CAp::Assert(d>=1,__FUNCTION__+": D<1"))
return;
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m && CAp::Len(z)>=l,__FUNCTION__+": length of X,Y or Z is too short (Length(X/Y/Z)<N/M/L)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m) && CApServ::IsFiniteVector(z,l),__FUNCTION__+": X,Y or Z contains NaN or Infinite value"))
return;
if(!CAp::Assert(CAp::Len(f)>=tblsize,__FUNCTION__+": length of F is too short (Length(F)<N*M*L*D)"))
return;
if(!CAp::Assert(CApServ::IsFiniteVector(f,tblsize),__FUNCTION__+": F contains NaN or Infinite value"))
return;
//---Fill interpolant
c.m_k=1;
c.m_n=n;
c.m_m=m;
c.m_l=l;
c.m_d=d;
c.m_stype=-1;
c.m_x=x;
c.m_y=y;
c.m_z=z;
c.m_f=f;
c.m_x.Resize(c.m_n);
c.m_y.Resize(c.m_m);
c.m_z.Resize(c.m_l);
c.m_f.Resize(tblsize);
//---Sort points:
//--- * sort x;
//--- * sort y;
//--- * sort z.
for(j=0; j<c.m_n; j++)
{
k=j;
for(i=j+1; i<c.m_n; i++)
{
if(c.m_x[i]<c.m_x[k])
k=i;
}
if(k!=j)
{
for(i=0; i<c.m_m; i++)
{
for(j0=0; j0<c.m_l; j0++)
for(i0=0; i0<c.m_d; i0++)
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*j0+i)+j)+i0,c.m_d*(c.m_n*(c.m_m*j0+i)+k)+i0);
}
c.m_x.Swap(j,k);
}
}
for(i=0; i<c.m_m; i++)
{
k=i;
for(j=i+1; j<c.m_m; j++)
{
if(c.m_y[j]<c.m_y[k])
k=j;
}
if(k!=i)
{
for(j=0; j<c.m_n; j++)
{
for(j0=0; j0<c.m_l; j0++)
for(i0=0; i0<c.m_d; i0++)
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*j0+i)+j)+i0,c.m_d*(c.m_n*(c.m_m*j0+k)+j)+i0);
}
c.m_y.Swap(i,k);
}
}
for(k=0; k<c.m_l; k++)
{
i=k;
for(j=i+1; j<c.m_l; j++)
{
if(c.m_z[j]<c.m_z[i])
i=j;
}
if(i!=k)
{
for(j=0; j<c.m_m; j++)
{
for(j0=0; j0<c.m_n; j0++)
for(i0=0; i0<c.m_d; i0++)
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*k+j)+j0)+i0,c.m_d*(c.m_n*(c.m_m*i+j)+j0)+i0);
}
c.m_z.Swap(k,i);
}
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates bilinear or bicubic vector-valued |
//| spline at the given point (X,Y,Z). |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y, |
//| Z - point |
//| F - output buffer, possibly preallocated array. In case|
//| array size is large enough to store result, it is |
//| not reallocated. Array which is too short will be |
//| reallocated |
//| OUTPUT PARAMETERS: |
//| F - array[D] (or larger) which stores function values |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DCalcVBuf(CSpline3DInterpolant &c,
double x,
double y,
double z,
CRowDouble &f)
{
//--- create variables
double xd=0;
double yd=0;
double zd=0;
double c0=0;
double c1=0;
double c2=0;
double c3=0;
int ix=0;
int iy=0;
int iz=0;
int l=0;
int r=0;
int h=0;
int i=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X,Y or Z contains NaN/Infinite"))
return;
CApServ::RVectorSetLengthAtLeast(f,c.m_d);
//---Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
ix=l;
//---Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
iy=l;
//---Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
l=0;
r=c.m_l-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_z[h]>=z)
r=h;
else
l=h;
}
iz=l;
xd=(x-c.m_x[ix])/(c.m_x[ix+1]-c.m_x[ix]);
yd=(y-c.m_y[iy])/(c.m_y[iy+1]-c.m_y[iy]);
zd=(z-c.m_z[iz])/(c.m_z[iz+1]-c.m_z[iz]);
for(i=0; i<c.m_d; i++)
{
//---Trilinear interpolation
if(c.m_stype==-1)
{
c0=c.m_f[c.m_d*(c.m_n*(c.m_m*iz+iy)+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*iz+iy)+(ix+1))+i]*xd;
c1=c.m_f[c.m_d*(c.m_n*(c.m_m*iz+(iy+1))+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*iz+(iy+1))+(ix+1))+i]*xd;
c2=c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+iy)+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+iy)+(ix+1))+i]*xd;
c3=c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+(iy+1))+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+(iy+1))+(ix+1))+i]*xd;
c0=c0*(1-yd)+c1*yd;
c1=c2*(1-yd)+c3*yd;
f.Set(i,c0*(1-zd)+c1*zd);
}
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates trilinear or tricubic vector-valued |
//| spline at the given point (X,Y,Z). |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y, |
//| Z - point |
//| OUTPUT PARAMETERS: |
//| F - array[D] which stores function values. F is |
//| out-parameter and it is reallocated after call to |
//| this function. In case you want to reuse |
//| previously allocated F, you may use |
//| Spline2DCalcVBuf(), which reallocates F only when |
//| it is too small. |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DCalcV(CSpline3DInterpolant &c,
double x,
double y,
double z,
CRowDouble &f)
{
f.Resize(0);
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X=NaN/Infinite,Y=NaN/Infinite or Z=NaN/Infinite"))
return;
f.Resize(c.m_d);
Spline3DCalcVBuf(c,x,y,z,f);
}
//+------------------------------------------------------------------+
//| This subroutine unpacks tri-dimensional spline into the |
//| coefficients table |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| Result: |
//| N - grid size (X) |
//| M - grid size (Y) |
//| L - grid size (Z) |
//| D - number of components |
//| SType - spline type. Currently, only one spline type is |
//| supported: trilinear spline, as indicated |
//| by SType=1. |
//| Tbl - spline coefficients: |
//| [0..(N-1)*(M-1)*(L-1)*D-1, 0..13]. |
//| For T=0..D-1 (component index), I = 0...N-2 |
//| (x index), J=0..M-2 (y index), K=0..L-2 (z index): |
//| Q := T + I*D + J*D*(N-1) + K*D*(N-1)*(M-1), |
//| Q-th row stores decomposition for T-th component |
//| of the vector-valued function |
//| Tbl[Q,0] = X[i] |
//| Tbl[Q,1] = X[i+1] |
//| Tbl[Q,2] = Y[j] |
//| Tbl[Q,3] = Y[j+1] |
//| Tbl[Q,4] = Z[k] |
//| Tbl[Q,5] = Z[k+1] |
//| Tbl[Q,6] = C000 |
//| Tbl[Q,7] = C100 |
//| Tbl[Q,8] = C010 |
//| Tbl[Q,9] = C110 |
//| Tbl[Q,10]= C001 |
//| Tbl[Q,11]= C101 |
//| Tbl[Q,12]= C011 |
//| Tbl[Q,13]= C111 |
//| On each grid square spline is equals to: |
//| S(x) = SUM(c[i,j,k]*(x^i)*(y^j)*(z^k), i=0..1, j=0..1, k=0..1) |
//| t = x-x[j] |
//| u = y-y[i] |
//| v = z-z[k] |
//| NOTE: format of Tbl is given for SType=1. Future versions of |
//| ALGLIB can use different formats for different values of |
//| SType. |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DUnpackV(CSpline3DInterpolant &c,
int &n,
int &m,
int &l,
int &d,
int &stype,
CMatrixDouble &tbl)
{
//--- create variables
int p=0;
int ci=0;
int cj=0;
int ck=0;
double du=0;
double dv=0;
double dw=0;
int i0=0;
n=0;
m=0;
l=0;
d=0;
stype=0;
tbl.Resize(0,0);
//--- check
if(!CAp::Assert(c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
n=c.m_n;
m=c.m_m;
l=c.m_l;
d=c.m_d;
stype=MathAbs(c.m_stype);
tbl.Resize((n-1)*(m-1)*(l-1)*d,14);
//---Fill
for(int i=0; i<n-1; i++)
for(int j=0; j<m-1; j++)
for(int k=0; k<l-1; k++)
for(int di=0; di<d; di++)
{
p=d*((n-1)*((m-1)*k+j)+i)+di;
tbl.Set(p,0,c.m_x[i]);
tbl.Set(p,1,c.m_x[i+1]);
tbl.Set(p,2,c.m_y[j]);
tbl.Set(p,3,c.m_y[j+1]);
tbl.Set(p,4,c.m_z[k]);
tbl.Set(p,5,c.m_z[k+1]);
du=1.0/(tbl.Get(p,1)-tbl.Get(p,0));
dv=1.0/(tbl.Get(p,3)-tbl.Get(p,2));
dw=1.0/(tbl.Get(p,5)-tbl.Get(p,4));
//---Trilinear interpolation
if(c.m_stype==-1)
{
for(i0=6; i0<=13; i0++)
tbl.Set(p,i0,0);
tbl.Set(p,6,c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,7,c.m_f[d*(n*(m*k+j)+(i+1))+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,8,c.m_f[d*(n*(m*k+(j+1))+i)+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,9,c.m_f[d*(n*(m*k+(j+1))+(i+1))+di]-c.m_f[d*(n*(m*k+(j+1))+i)+di]-c.m_f[d*(n*(m*k+j)+(i+1))+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,10,c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,11,c.m_f[d*(n*(m*(k+1)+j)+(i+1))+di]-c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+j)+(i+1))+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,12,c.m_f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+(j+1))+i)+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
tbl.Set(p,13,c.m_f[d*(n*(m*(k+1)+(j+1))+(i+1))+di]-c.m_f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.m_f[d*(n*(m*(k+1)+j)+(i+1))+di]+c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+(j+1))+(i+1))+di]+c.m_f[d*(n*(m*k+(j+1))+i)+di]+c.m_f[d*(n*(m*k+j)+(i+1))+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
}
//---Rescale Cij
for(ci=0; ci<=1; ci++)
{
for(cj=0; cj<=1; cj++)
for(ck=0; ck<=1; ck++)
tbl.Mul(p,6+2*(2*ck+cj)+ci,MathPow(du,ci)*MathPow(dv,cj)*MathPow(dw,ck));
}
}
}
//+------------------------------------------------------------------+
//| This subroutine calculates the value of the trilinear (or |
//| tricubic;possible will be later) spline at the given point X (and|
//| its derivatives; possible will be later). |
//| INPUT PARAMETERS: |
//| C - spline interpolant. |
//| X, Y, Z - point |
//| OUTPUT PARAMETERS: |
//| F - S(x,y,z) |
//| FX - dS(x,y,z)/dX |
//| FY - dS(x,y,z)/dY |
//| FXY - d2S(x,y,z)/dXdY |
//+------------------------------------------------------------------+
void CSpline3D::Spline3DDiff(CSpline3DInterpolant &c,
double x,double y,
double z,double &f,
double &fx,double &fy,
double &fxy)
{
//--- create variables
double xd=0;
double yd=0;
double zd=0;
double c0=0;
double c1=0;
double c2=0;
double c3=0;
int ix=0;
int iy=0;
int iz=0;
int l=0;
int r=0;
int h=0;
//---Prepare F, dF/dX, dF/dY, d2F/dXdY
f=0;
fx=0;
fy=0;
fxy=0;
//--- check
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
return;
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
return;
if(c.m_d!=1)
return;
//---Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
l=0;
r=c.m_n-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_x[h]>=x)
r=h;
else
l=h;
}
ix=l;
//---Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
l=0;
r=c.m_m-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_y[h]>=y)
r=h;
else
l=h;
}
iy=l;
//---Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
l=0;
r=c.m_l-1;
while(l!=r-1)
{
h=(l+r)/2;
if(c.m_z[h]>=z)
r=h;
else
l=h;
}
iz=l;
xd=(x-c.m_x[ix])/(c.m_x[ix+1]-c.m_x[ix]);
yd=(y-c.m_y[iy])/(c.m_y[iy+1]-c.m_y[iy]);
zd=(z-c.m_z[iz])/(c.m_z[iz+1]-c.m_z[iz]);
//---Trilinear interpolation
if(c.m_stype==-1)
{
c0=c.m_f[c.m_n*(c.m_m*iz+iy)+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*iz+iy)+(ix+1)]*xd;
c1=c.m_f[c.m_n*(c.m_m*iz+(iy+1))+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*iz+(iy+1))+(ix+1)]*xd;
c2=c.m_f[c.m_n*(c.m_m*(iz+1)+iy)+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*(iz+1)+iy)+(ix+1)]*xd;
c3=c.m_f[c.m_n*(c.m_m*(iz+1)+(iy+1))+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*(iz+1)+(iy+1))+(ix+1)]*xd;
c0=c0*(1-yd)+c1*yd;
c1=c2*(1-yd)+c3*yd;
f=c0*(1-zd)+c1*zd;
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CIntComp
{
public:
static void NSFitSphereMCC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rhi);
static void NSFitSphereMIC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo);
static void NSFitSphereMZC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo,double&rhi);
static void NSFitSphereX(CMatrixDouble&xy,int npoints,int nx,int problemtype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi);
static void Spline1DFitPenalized(double &cx[],double &cy[],const int n,const int m,const double rho,int &info,CSpline1DInterpolant&s,CSpline1DFitReport&rep);
static void Spline1DFitPenalizedW(double &cx[],double &cy[],double &cw[],const int n,const int m,double rho,int &info,CSpline1DInterpolant&s,CSpline1DFitReport&rep);
};
//+------------------------------------------------------------------+
//| This function is left for backward compatibility. |
//| Use fitspheremc() instead. |
//+------------------------------------------------------------------+
void CIntComp::NSFitSphereMCC(CMatrixDouble &xy,int npoints,
int nx,CRowDouble &cx,double &rhi)
{
double dummy=0;
cx.Resize(0);
rhi=0;
//--- function call
NSFitSphereX(xy,npoints,nx,1,0.0,0,0.0,cx,dummy,rhi);
}
//+------------------------------------------------------------------+
//|This function is left for backward compatibility. |
//| Use fitspheremi() instead. |
//+------------------------------------------------------------------+
void CIntComp::NSFitSphereMIC(CMatrixDouble &xy,int npoints,
int nx,CRowDouble &cx,double &rlo)
{
double dummy=0;
cx.Resize(0);
rlo=0;
//--- function call
NSFitSphereX(xy,npoints,nx,2,0.0,0,0.0,cx,rlo,dummy);
}
//+------------------------------------------------------------------+
//| This function is left for backward compatibility. |
//| Use fitspheremz() instead. |
//+------------------------------------------------------------------+
void CIntComp::NSFitSphereMZC(CMatrixDouble &xy,int npoints,int nx,
CRowDouble &cx,double &rlo,double &rhi)
{
cx.Resize(0);
rlo=0;
rhi=0;
//--- function call
NSFitSphereX(xy,npoints,nx,3,0.0,0,0.0,cx,rlo,rhi);
}
//+------------------------------------------------------------------+
//| This function is left for backward compatibility. |
//| Use FitSphereX() instead. |
//+------------------------------------------------------------------+
void CIntComp::NSFitSphereX(CMatrixDouble &xy,int npoints,int nx,
int problemtype,double epsx,int aulits,
double penalty,CRowDouble &cx,double &rlo,
double &rhi)
{
cx.Resize(0);
rlo=0;
rhi=0;
//--- function call
CFitSphere::FitSphereX(xy,npoints,nx,problemtype,epsx,aulits,penalty,cx,rlo,rhi);
}
//+------------------------------------------------------------------+
//| This function is an obsolete and deprecated version of fitting by|
//| penalized cubic spline. |
//| It was superseded by Spline1DFit(), which is an orders of |
//| magnitude faster and more memory-efficient implementation. |
//| Do NOT use this function in the new code! |
//+------------------------------------------------------------------+
//| Rational least squares fitting using Floater-Hormann rational |
//| functions with optimal D chosen from [0,9]. |
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
//| basis functions. Different values of D are tried, optimal D |
//| (least root mean square error) is chosen. Task is linear, so |
//| linear least squares solver is used. Complexity of this |
//| computational scheme is O(N*M^2) (mostly dominated by the least |
//| squares solver). |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| N - number of points, N>0. |
//| M - number of basis functions ( = number_of_nodes), M>=2.|
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearWC() subroutine. |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| -3 means inconsistent constraints |
//| B - barycentric interpolant. |
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
//| Following fields are set: |
//| * DBest best value of the D parameter |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//+------------------------------------------------------------------+
void CIntComp::Spline1DFitPenalized(double &cx[],double &cy[],
const int n,const int m,
const double rho,int &info,
CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create arrays
double w[];
double x[];
double y[];
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(rho),__FUNCTION__+": Rho is infinite!"))
return;
//--- allocation
ArrayResize(w,n);
//--- initialization
for(int i=0; i<n; i++)
w[i]=1;
//--- function call
Spline1DFitPenalizedW(x,y,w,n,m,rho,info,s,rep);
}
//+------------------------------------------------------------------+
//| This function is an obsolete and deprecated version of fitting by|
//| penalized cubic spline. |
//| It was superseded by Spline1DFit(), which is an orders of |
//| magnitude faster and more memory-efficient implementation. |
//| Do NOT use this function in the new code! |
//+------------------------------------------------------------------+
//| Weighted fitting by penalized cubic spline. |
//| Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is used to|
//| build basis functions. Basis functions are cubic splines with |
//| natural boundary conditions. Problem is regularized by adding |
//| non-linearity penalty to the usual least squares penalty |
//| function: |
//| S(x) = arg min { LS + P }, where |
//| LS = SUM { w[i]^2*(y[i] - S(x[i]))^2 } - least squares |
//| penalty |
//| P = C*10^rho*integral{ S''(x)^2*dx } - non-linearity |
//| penalty |
//| rho - tunable constant given by user |
//| C - automatically determined scale parameter, |
//| makes penalty invariant with respect to scaling of X, |
//| Y, W. |
//| INPUT PARAMETERS: |
//| X - points, array[0..N-1]. |
//| Y - function values, array[0..N-1]. |
//| W - weights, array[0..N-1] |
//| Each summand in square sum of approximation |
//| deviations from given values is multiplied by the |
//| square of corresponding weight. Fill it by 1's if |
//| you don't want to solve weighted problem. |
//| N - number of points (optional): |
//| * N>0 |
//| * if given, only first N elements of X/Y/W are |
//| processed |
//| * if not given, automatically determined from X/Y/W |
//| sizes |
//| M - number of basis functions ( = number_of_nodes), M>=4.|
//| Rho - regularization constant passed by user. It penalizes |
//| nonlinearity in the regression spline. It is |
//| logarithmically scaled, i.e. actual value of |
//| regularization constant is calculated as 10^Rho. It |
//| is automatically scaled so that: |
//| * Rho=2.0 corresponds to moderate amount of |
//| nonlinearity |
//| * generally, it should be somewhere in the |
//| [-8.0,+8.0] |
//| If you do not want to penalize nonlineary, |
//| pass small Rho. Values as low as -15 should work. |
//| OUTPUT PARAMETERS: |
//| Info- same format as in LSFitLinearWC() subroutine. |
//| * Info>0 task is solved |
//| * Info<=0 an error occured: |
//| -4 means inconvergence of internal SVD |
//| or Cholesky decomposition; problem |
//| may be too ill-conditioned (very |
//| rare) |
//| S - spline interpolant. |
//| Rep - Following fields are set: |
//| * RMSError rms error on the (X,Y). |
//| * AvgError average error on the (X,Y). |
//| * AvgRelError average relative error on the |
//| non-zero Y |
//| * MaxError maximum error |
//| NON-WEIGHTED ERRORS ARE CALCULATED |
//| IMPORTANT: |
//| this subroitine doesn't calculate task's condition number |
//| for K<>0. |
//| NOTE 1: additional nodes are added to the spline outside of the |
//| fitting interval to force linearity when x<min(x,xc) or |
//| x>max(x,xc). It is done for consistency - we penalize |
//| non-linearity at [min(x,xc),max(x,xc)], so it is natural to |
//| force linearity outside of this interval. |
//| NOTE 2: function automatically sorts points, so caller may pass |
//| unsorted array. |
//+------------------------------------------------------------------+
void CIntComp::Spline1DFitPenalizedW(double &cx[],double &cy[],
double &cw[],const int n,
const int m,double rho,
int &info,CSpline1DInterpolant &s,
CSpline1DFitReport &rep)
{
//--- create variables
int i=0;
int j=0;
int b=0;
double v=0;
double relcnt=0;
double xa=0;
double xb=0;
double sa=0;
double sb=0;
double pdecay=0;
double tdecay=0;
double fdmax=0;
double admax=0;
double fa=0;
double ga=0;
double fb=0;
double gb=0;
double lambdav=0;
int i_=0;
int i1_=0;
//--- create arrays
double xoriginal[];
double yoriginal[];
double fcolumn[];
double y2[];
double w2[];
double xc[];
double yc[];
int dc[];
double bx[];
double by[];
double bd1[];
double bd2[];
double tx[];
double ty[];
double td[];
double rightpart[];
double c[];
double tmp0[];
double x[];
double y[];
double w[];
//--- create matrix
CMatrixDouble fmatrix;
CMatrixDouble amatrix;
CMatrixDouble d2matrix;
CMatrixDouble nmatrix;
//--- objects of classes
CSpline1DInterpolant bs;
CFblsLinCgState cgstate;
//--- copy arrays
ArrayCopy(x,cx);
ArrayCopy(y,cy);
ArrayCopy(w,cw);
//--- initialization
info=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
return;
//--- check
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
return;
//--- check
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(rho),__FUNCTION__+": Rho is infinite!"))
return;
//--- Prepare LambdaV
v=-(MathLog(CMath::m_machineepsilon)/MathLog(10));
//--- check
if(rho<-v)
rho=-v;
//--- check
if(rho>v)
rho=v;
lambdav=MathPow(10,rho);
//--- Sort X,Y,W
CSpline1D::HeapSortDPoints(x,y,w,n);
//--- Scale X,Y,XC,YC
CLSFit::LSFitScaleXY(x,y,w,n,xc,yc,dc,0,xa,xb,sa,sb,xoriginal,yoriginal);
//--- Allocate space
fmatrix.Resize(n,m);
amatrix.Resize(m,m);
d2matrix.Resize(m,m);
ArrayResize(bx,m);
ArrayResize(by,m);
ArrayResize(fcolumn,n);
nmatrix.Resize(m,m);
ArrayResize(rightpart,m);
ArrayResize(tmp0,MathMax(m,n));
ArrayResize(c,m);
//--- Fill:
//--- * FMatrix by values of basis functions
//--- * TmpAMatrix by second derivatives of I-th function at J-th point
//--- * CMatrix by constraints
fdmax=0;
for(b=0; b<=m-1; b++)
{
//--- Prepare I-th basis function
for(j=0; j<=m-1; j++)
{
bx[j]=(double)(2*j)/(double)(m-1)-1;
by[j]=0;
}
by[b]=1;
//--- function call
CSpline1D::Spline1DGridDiff2Cubic(bx,by,m,2,0.0,2,0.0,bd1,bd2);
//--- function call
CSpline1D::Spline1DBuildCubic(bx,by,m,2,0.0,2,0.0,bs);
//--- Calculate B-th column of FMatrix
//--- Update FDMax (maximum column norm)
CSpline1D::Spline1DConvCubic(bx,by,m,2,0.0,2,0.0,x,n,fcolumn);
for(i_=0; i_<n; i_++)
fmatrix.Set(i_,b,fcolumn[i_]);
v=0;
for(i=0; i<n; i++)
v=v+CMath::Sqr(w[i]*fcolumn[i]);
fdmax=MathMax(fdmax,v);
//--- Fill temporary with second derivatives of basis function
for(i_=0; i_<=m-1; i_++)
d2matrix.Set(b,i_,bd2[i_]);
}
//--- * calculate penalty matrix A
//--- * calculate max of diagonal elements of A
//--- * calculate PDecay - coefficient before penalty matrix
for(i=0; i<=m-1; i++)
{
for(j=i; j<=m-1; j++)
{
//--- calculate integral(B_i''*B_j'') where B_i and B_j are
//--- i-th and j-th basis splines.
//--- B_i and B_j are piecewise linear functions.
v=0;
for(b=0; b<=m-2; b++)
{
//--- change values
fa=d2matrix[i][b];
fb=d2matrix[i][b+1];
ga=d2matrix[j][b];
gb=d2matrix[j][b+1];
v=v+(bx[b+1]-bx[b])*(fa*ga+(fa*(gb-ga)+ga*(fb-fa))/2+(fb-fa)*(gb-ga)/3);
}
amatrix.Set(i,j,v);
amatrix.Set(j,i,v);
}
}
//--- change values
admax=0;
for(i=0; i<=m-1; i++)
admax=MathMax(admax,MathAbs(amatrix[i][i]));
pdecay=lambdav*fdmax/admax;
//--- Calculate TDecay for Tikhonov regularization
tdecay=fdmax*(1+pdecay)*10*CMath::m_machineepsilon;
//--- Prepare system
//--- NOTE: FMatrix is spoiled during this process
for(i=0; i<n; i++)
{
v=w[i];
for(i_=0; i_<=m-1; i_++)
fmatrix.Set(i,i_,v*fmatrix.Get(i,i_));
}
//--- function call
CAblas::RMatrixGemm(m,m,n,1.0,fmatrix,0,0,1,fmatrix,0,0,0,0.0,nmatrix,0,0);
for(i=0; i<=m-1; i++)
{
for(j=0; j<=m-1; j++)
nmatrix.Set(i,j,nmatrix[i][j]+pdecay*amatrix[i][j]);
}
//--- calculation
for(i=0; i<=m-1; i++)
nmatrix.Set(i,i,nmatrix[i][i]+tdecay);
for(i=0; i<=m-1; i++)
rightpart[i]=0;
//--- change values
for(i=0; i<n; i++)
{
v=y[i]*w[i];
for(i_=0; i_<=m-1; i_++)
rightpart[i_]=rightpart[i_]+v*fmatrix.Get(i,i_);
}
//--- Solve system
if(!CTrFac::SPDMatrixCholesky(nmatrix,m,true))
{
info=-4;
return;
}
//--- function call
CFbls::FblsCholeskySolve(nmatrix,1.0,m,true,rightpart,tmp0);
//--- copy
for(i_=0; i_<=m-1; i_++)
c[i_]=rightpart[i_];
//--- add nodes to force linearity outside of the fitting interval
CSpline1D::Spline1DGridDiffCubic(bx,c,m,2,0.0,2,0.0,bd1);
//--- allocation
ArrayResize(tx,m+2);
ArrayResize(ty,m+2);
ArrayResize(td,m+2);
//--- copy
i1_=-1;
for(i_=1; i_<=m; i_++)
tx[i_]=bx[i_+i1_];
i1_=-1;
for(i_=1; i_<=m; i_++)
ty[i_]=rightpart[i_+i1_];
i1_=-1;
for(i_=1; i_<=m; i_++)
td[i_]=bd1[i_+i1_];
//--- change values
tx[0]=tx[1]-(tx[2]-tx[1]);
ty[0]=ty[1]-td[1]*(tx[2]-tx[1]);
td[0]=td[1];
tx[m+1]=tx[m]+(tx[m]-tx[m-1]);
ty[m+1]=ty[m]+td[m]*(tx[m]-tx[m-1]);
td[m+1]=td[m];
//--- function call
CSpline1D::Spline1DBuildHermite(tx,ty,td,m+2,s);
//--- function call
CSpline1D::Spline1DLinTransX(s,2/(xb-xa),-((xa+xb)/(xb-xa)));
//--- function call
CSpline1D::Spline1DLinTransY(s,sb-sa,sa);
//--- change value
info=1;
//--- Fill report
rep.m_rmserror=0;
rep.m_avgerror=0;
rep.m_avgrelerror=0;
rep.m_maxerror=0;
relcnt=0;
//--- function call
CSpline1D::Spline1DConvCubic(bx,rightpart,m,2,0.0,2,0.0,x,n,fcolumn);
//--- calculation
for(i=0; i<n; i++)
{
//--- change values
v=(sb-sa)*fcolumn[i]+sa;
rep.m_rmserror+=CMath::Sqr(v-yoriginal[i]);
rep.m_avgerror=rep.m_avgerror+MathAbs(v-yoriginal[i]);
//--- check
if(yoriginal[i]!=0.0)
{
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(v-yoriginal[i])/MathAbs(yoriginal[i]);
relcnt=relcnt+1;
}
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v-yoriginal[i]));
}
//--- change values
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
rep.m_avgerror=rep.m_avgerror/n;
//--- check
if(relcnt!=0.0)
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
}
//+------------------------------------------------------------------+