//+------------------------------------------------------------------+ //| 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)=State.m_nx+State.m_ny,__FUNCTION__+": Cols(XY)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)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=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=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=1.0) break; } } else { //--- General case for(layeridx=0; layeridx=1.0) break; v=vv*vv; v=(1-v)*(1-v)/(v+lambdacur); for(j=0; j::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::Zeros(ny); for(i=0; i::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=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; i0) { 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; i0.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)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)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(i0.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_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; i0. | //| 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_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 | //| 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=1,__FUNCTION__+": N<1")) return; //--- check if(!CAp::Assert(CAp::Len(t)>=n,__FUNCTION__+": Length(T)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)=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,__FUNCTION__+": Length(Y)=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)=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)=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)=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)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; i0,__FUNCTION__+": N<=0!")) return(EMPTY_VALUE); //--- check if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)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; i0,__FUNCTION__+": N<=0!")) return(EMPTY_VALUE); //--- check if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)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=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=2,__FUNCTION__+": N2<2!")) return; //--- check if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)=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,__FUNCTION__+": Length(Y)=2,__FUNCTION__+"Spline1DConvDiffCubic: N2<2!")) return; //--- check if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)=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,__FUNCTION__+": Length(Y)=2,__FUNCTION__+": N2<2!")) return; //--- check if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)=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=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(D)=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,__FUNCTION__+": Length(Y)=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; ic.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; 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,__FUNCTION__+": Length(Y)=0.0,__FUNCTION__+": LambdaNS<0!")) return; bfrad=2; lsqrcnt=10; //--- Sort points. //--- Determine actual area size, make sure that XA=0.0) { xa=v/2-1; xb=v*2+1; } else { xa=v*2-1; xb=v/2+1; } } //--- check if(!CAp::Assert(xa::Zeros(arows); scaletargetsby=1/MathSqrt(n); scalepenaltyby=1/MathSqrt(m); for(i=0; i=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,__FUNCTION__+": Length(Y)n-2)); if(ca!=0.0) ca/=MathAbs(ca); i=0; while(i=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; j3.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) break; t=x2[pointindex]; //--- do we need to advance interval? havetoadvance=false; //--- check if(intervalindex==-1) havetoadvance=true; else { //--- check if(intervalindex=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=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)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,__FUNCTION__+": Length(Y)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=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!")) return; //--- check if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)=n,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(W)=k,__FUNCTION__+": Length(XC)=k,__FUNCTION__+": Length(YC)=k,__FUNCTION__+": Length(DC)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=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!")) return; //--- check if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)=n,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(W)=k,__FUNCTION__+": Length(XC)=k,__FUNCTION__+": Length(YC)=k,__FUNCTION__+": Length(DC)0,__FUNCTION__+": unexpected result from BarycentricFitWCFixedD!")) return; //--- check if(locinfo>0) { //--- Calculate weghted RMS wrmscur=0; for(int i=0; i0. | //| 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,__FUNCTION__+": Length(Y)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=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!")) return; //--- check if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)=n,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(W)=k,__FUNCTION__+": Length(XC)=k,__FUNCTION__+": Length(YC)=k,__FUNCTION__+": Length(DC)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=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!")) return; //--- check if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)=n,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(W)=k,__FUNCTION__+": Length(XC)=k,__FUNCTION__+": Length(YC)=k,__FUNCTION__+": Length(DC)=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": Length(Y)=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,__FUNCTION__+": length(W)=n,__FUNCTION__+": rows(FMatrix)=m,__FUNCTION__+": cols(FMatrix)=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,__FUNCTION__+": length(W)=n,__FUNCTION__+": rows(FMatrix)=m,__FUNCTION__+": cols(FMatrix)=k,__FUNCTION__+": rows(CMatrix)=m+1 || k==0,__FUNCTION__+": cols(CMatrix)=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; i0) { 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=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,__FUNCTION__+": rows(FMatrix)=m,__FUNCTION__+": cols(FMatrix)=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,__FUNCTION__+": rows(FMatrix)=m,__FUNCTION__+": cols(FMatrix)=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,__FUNCTION__+": rows(FMatrix)=m,__FUNCTION__+": cols(FMatrix)=k,__FUNCTION__+": rows(CMatrix)=m+1 || k==0,__FUNCTION__+": cols(CMatrix)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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": length(W)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)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::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)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::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": length(W)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": length(W)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)::Zeros(k,k); State.m_tasky.Resize(n); State.m_taskw.Resize(n); State.m_c=vector::Zeros(k); State.m_c0.Resize(k); State.m_c1.Resize(k); State.m_x=vector::Zeros(m); State.m_g=vector::Zeros(k); State.m_s=vector::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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)=n,__FUNCTION__+": length(Y)=n,__FUNCTION__+": rows(X)=m,__FUNCTION__+": cols(X)::Ones(k); State.m_bndl=vector::Full(k,AL_NEGINF); State.m_bndu=vector::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,__FUNCTION__+": Length(BndL)=k,__FUNCTION__+": Length(BndU)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; i0.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_=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_=m) { info=-1; return; } for(i=0; i1) 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=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; i0) { //--- 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=M. Generate design matrix and reduce to N=M using //--- QR decomposition. ft.Resize(n,m); ArrayResize(b,n); for(j=0; jthreshold) { //--- 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; i0.0) { rep.m_taskrcond=sv[m-1]/sv[0]; for(i=0; ithreshold*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_::Zeros(n); s=MathPow(fmatrix.ToMatrix()+0,2).Sum(0); for(i=0; i0. | //| 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=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::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; i0) { //--- 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; i1) 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; i0) { 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=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; i0) { //--- 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=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,"LogisticFitX: Length(Y)=0.0,"LogisticFitX: some X[] are negative")) return; nz=n; for(i=0; i0.0) { nz=i; break; } //--- For NZ=N (all X[] are zero) special code is used. //--- For NZ0.0,"LogisticFitX: internal error")) return; v=0.0; for(i=0; i::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::Zeros(5); bndl=vector::Zeros(5); bndu=vector::Zeros(5); bndl1=vector::Zeros(5); bndu1=vector::Zeros(5); bndl2=vector::Zeros(5); bndu2=vector::Zeros(5); CMinLM::MinLMCreateVJ(5,n+5,p0,state); CMinLM::MinLMSetScale(state,s); CMinLM::MinLMSetCond(state,epsx,maxits); CMinLM::MinLMSetXRep(state,true); p1=vector::Zeros(5); p2=vector::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; outerit0 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=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=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::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; jn-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; i0) { avg/=nzcnt; rss=0.0; tss=0.0; for(i=0; ik) { noisec=0.0; for(i=0; i0 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=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::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::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::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::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; i0) 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_=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_=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_=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_=2,__FUNCTION__+": internal error!")) return; //--- initialization n=p.m_n; //--- allocation ArrayResize(t,n); //--- copy for(int i_=0; i_=2,__FUNCTION__+": internal error!")) return; //--- initialization n=p.m_n; //--- allocation ArrayResize(t,n); //--- copy for(i_=0; i_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 | //| * B0,__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 | //| * B0,__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=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)=d,__FUNCTION__+": Cols(X)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=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=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; iworsterror) { 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; iworsterror) { 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)=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)=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)=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)=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=0 && 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=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; i1 | //| 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; i1 | //| 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=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)=k,__FUNCTION__+": length of F is too short (Length(F)=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)=k,__FUNCTION__+": length of F is too short (Length(F)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)=2+State.m_d,__FUNCTION__+": Cols(XY)=XB")) return; if(!CAp::Assert(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=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(xa0,__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::Zeros(4*s.m_n*s.m_m*d); for(i=0; i::Zeros(d); CApServ::RVectorSetLengthAtLeast(xywork,npoints*ew); acopied=0; eps=1.0E-6; for(i=0; i0,__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=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; i0.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::Zeros(3,3); d2y=matrix::Zeros(3,3); dxy=matrix::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=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=0; scaleidx--) { if((nlayers>0 && scaleidx=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=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=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::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; i10,__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=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::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::Zeros(kx*ky,kx*ky); for(i=0; iblockbandwidth || 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(idxi0) CLinLSQR::LinLSQRCreate(arows,kx*ky,solver); for(j=0; j0) { //---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=0 && i=0 && 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=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=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=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; blockidxchunksize) { 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=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=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=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=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; j10.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::Zeros(3,3); d2y=matrix::Zeros(3,3); dxy=matrix::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=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; bidx0) { batchsize=a.m_batches[bidx+1]-a.m_batches[bidx]; baseidx=a.m_batchbases[bidx]; for(k1=0; k1=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; bidx0) { batchsize=a.m_batches[bidx+1]-a.m_batches[bidx]; baseidx=a.m_batchbases[bidx]; for(int i=0; i0) { //---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 [-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 x=X.ToVector(); vector xc=XC.ToVector(); vector yc=YC.ToVector(); vector 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=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=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::Zeros(ny,nx+1); if(n==0) { switch(modeltype) { case 0: v.Col(nx,vector::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::Full(ny,priorval)); xy-=priorval; return; case 2: for(i=0; i::Zeros(nx+1,nx+1); braw.Resize(nx+1,ny); tmp0.Resize(nx+1); amod.Resize(nx+1,nx+1); for(i=0; i=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::Zeros(ny,nx+1); if(n==0) { switch(modeltype) { case 0: v.Col(nx,vector::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::Full(ny,priorval)); for(i=0; i::Zeros(nx+1,nx+1); braw.Resize(nx+1,ny); tmp0.Resize(nx+1); amod.Resize(nx+1,nx+1); for(i=0; i::Zeros(nx+1,ny); for(i=0; i0 | //| 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 vmin; vector vmax; vector 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 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::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; i0,__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::Zeros(nx+2)); nlcstate.m_j.Set(0,nx+0,-(1*vlo)); nlcstate.m_j.Set(0,nx+1,1*vhi); for(i=0; i0,__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; j0,__FUNCTION__+": unexpected failure of BLEIC solver")) return; rep.m_iterationscount+=blcrep.m_iterationscount; //--- Terminate iterations early if we converged v=0; for(j=0; j1) 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::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::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::Zeros(nc); ctags.Resize(nc); for(i=0; i1, calculate radii using distances to nearest neigbors for(i=0; i0) { 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::Zeros(s.m_nc,1+s.m_nl*s.m_ny); tags.Resize(s.m_nc); for(i=0; i2 | //| * 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::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; i3 | //| * 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::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=s.m_nx,__FUNCTION__+": Length(X)::Zeros(m_mxnx); for(int i=0; i=s.m_nx,__FUNCTION__+": Length(X)::Zeros(m_mxnx); for(int i=0; i=s.m_nx,__FUNCTION__+": Length(X)::Zeros(m_mxnx); for(int i=0; i=s.m_nx,__FUNCTION__+": Length(X)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)=n1,__FUNCTION__+": Length(X1)::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; i0) { xwr.Resize(s.m_nc*s.m_nl,s.m_nx+s.m_ny+1); for(int i=0; i=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::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; ivalue) mn=value; if(mx0) v.Mul(i,m_mxnx,1.0/n); for(j=0; j0 xcx=vector::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=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::Zeros(maxnearpointscnt+maxnearcenterscnt,maxnearcenterscnt); tmpy=vector::Zeros(maxnearpointscnt+maxnearcenterscnt); g=vector::Zeros(maxnearcenterscnt); c=vector::Zeros(maxnearcenterscnt); nearcenters=matrix::Zeros(maxnearcenterscnt,m_mxnx); nearpoints=matrix::Zeros(MathMax(maxnearpointscnt,1),m_mxnx); farpoints=matrix::Zeros(MathMax(maxfarpointscnt,1),m_mxnx); //--- fill matrix SpG CSparse::SparseCreate(n,nc,gnnz,spg); CSparse::SparseCreate(nc,nc,snnz,sps); for(i=0; i1 && 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::Zeros(nc,ny); CLinLSQR::LinLSQRCreate(n,nc,State); CLinLSQR::LinLSQRSetCond(State,epsort,epserr,maxits); for(i=0; i=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; i1) 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::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; j0.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::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; i0,__FUNCTION__+": integrity check failed")) return; ri.Resize(nh); for(levelidx=0; levelidx::Full(n,CMath::m_maxrealnumber); for(i=0; iri[0],__FUNCTION__+": integrity check failed")) return; } for(levelidx=0; levelidxcriticalr) { //--- 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; j0,__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; i0,__FUNCTION__+": integrity check failed")) return; for(i=0; i=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)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)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=s.m_nx,__FUNCTION__+": Length(X)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; i2 | //| * 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)=n1,__FUNCTION__+": Length(X1)::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::Zeros(1); dummyx3=vector::Zeros(1); vy.Resize(n0*n1); RBFV2GridCalcVX(s,cpx0,n0,cpx1,n1,dummyx2,1,dummyx3,1,dummyflag,false,vy); for(int i=0; i=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=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; iblockwidth0 || 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; iblockwidth1 || 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; iblockwidth2 || 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; iblockwidth3 || 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; blkidx1) 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; nodeidx0) 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; l0) { xwr.Resize(nc,s.m_nx+s.m_ny+s.m_nx); for(int i=0; i=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::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; ix.Get(j,i)) mn=x.Get(j,i); if(mx0) v.Mul(i,nx,1.0/(double)n); for(j=0; j=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=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=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=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_curdist20) { cwcnt=s.m_kdnodes[rootidx+0]; cwoffs=s.m_kdnodes[rootidx+1]; for(i0=0; i0=rfar2) continue; //--- Update Y val=RBFV2BasisFunc(s.m_bf,ptdist2*invr2); woffs=itemoffs+nx; for(j=0; j=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_curdist2buf.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=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_curdist20) { cwcnt=kdnodes[rootidx+0]; cwoffs=kdnodes[rootidx+1]; for(int i=0; i=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_curdist20) { cwcnt=kdnodes[rootidx+0]; cwoffs=kdnodes[rootidx+1]; for(int i=0; i0,__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::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::Ones(nx); s.m_v=matrix::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; j0.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; i0.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(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 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> 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 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; j0; //--- 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)=s.m_nx,__FUNCTION__+": Length(X)=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_nx,__FUNCTION__+": Length(X)=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=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; i0) { cwwidth=nx+ny; xwr.Resize(nc,nx+ny+nx+3); for(int i=0; i0.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=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(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; isplitval) { 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 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=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; i0"); return; } CAblasF::IAllocV(nspec,nodes); nchosen=0; maxdist=CMath::m_maxrealnumber; while(nchosend2[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; i0) { //--- We have scattered points too CAblasF::RAllocM(nwrk+nx+1,nscatter,atsctr); for(i=0; i0) { for(k=0; k0.0 && currentradcurrentrad) { 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=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=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(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(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; i0,__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; i0,__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; iMathSqrt(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> 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(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; i0,__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 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 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= 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=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=(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= 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=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 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)=s.m_nx+s.m_ny,__FUNCTION__+": Cols(XY) 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)=r.m_nx+r.m_ny,__FUNCTION__+": Cols(XY)=r.m_nx,__FUNCTION__+": Length(S)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; i1 | //| * 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)=s.m_nx,__FUNCTION__+": Length(X) 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)=s.m_nx,__FUNCTION__+": Length(X)=s.m_nx,__FUNCTION__+": Length(X) 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)=s.m_nx,__FUNCTION__+": Length(X)=s.m_nx,"RBFTsDiffBuf: Length(X) 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)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)=n1,__FUNCTION__+": Length(X1)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)=n1,__FUNCTION__+": Length(X1)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)=n1,__FUNCTION__+": Length(X1)=n0*n1,__FUNCTION__+": Length(FlagY)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)=n1,__FUNCTION__+": Length(X1)=n2,__FUNCTION__+": Length(X2)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)=n1,__FUNCTION__+": Length(X1)=n2,__FUNCTION__+": Length(X2)=n0*n1*n2,__FUNCTION__+": Length(FlagY)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)=n1,__FUNCTION__+": Length(X1)::Zeros(ylen); if(s.m_nx!=2) return; //--- Reference code for V3 models if(s.m_modelversion==3) { dummyx2=vector::Zeros(1); dummyx3=vector::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 ::Zeros(1); dummyx3=vector::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; i0,__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)=n1,__FUNCTION__+": Length(X1)=n2,__FUNCTION__+": Length(X2)::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::Zeros(nx); bufseedv1.m_tx=vector::Zeros(nx); bufseedv1.m_ty=vector::Zeros(ny); bufseedv1.m_expbuf0=vector::Zeros(n0); bufseedv1.m_expbuf1=vector::Zeros(n1); bufseedv1.m_expbuf2=vector::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(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(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(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::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::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; i0, 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=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)=tblsize,__FUNCTION__+": length of F is too short (Length(F)=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=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,__FUNCTION__+": Length(Y)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 xmax(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,__FUNCTION__+": Length(Y)=n,__FUNCTION__+": Length(W)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_