34262 lines
1.4 MiB
Plaintext
34262 lines
1.4 MiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| interpolation.mqh |
|
|
//| Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
|
|
//| Copyright 2012-2026, MetaQuotes Ltd. |
|
|
//| www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
//| Implementation of ALGLIB library in MetaQuotes Language 5 |
|
|
//| |
|
|
//| The features of the library include: |
|
|
//| - Linear algebra (direct algorithms, EVD, SVD) |
|
|
//| - Solving systems of linear and non-linear equations |
|
|
//| - Interpolation |
|
|
//| - Optimization |
|
|
//| - FFT (Fast Fourier Transform) |
|
|
//| - Numerical integration |
|
|
//| - Linear and nonlinear least-squares fitting |
|
|
//| - Ordinary differential equations |
|
|
//| - Computation of special functions |
|
|
//| - Descriptive statistics and hypothesis testing |
|
|
//| - Data analysis - classification, regression |
|
|
//| - Implementing linear algebra algorithms, interpolation, etc. |
|
|
//| in high-precision arithmetic (using MPFR) |
|
|
//| |
|
|
//| This file is free software; you can redistribute it and/or |
|
|
//| modify it under the terms of the GNU General Public License as |
|
|
//| published by the Free Software Foundation (www.fsf.org); either |
|
|
//| version 2 of the License, or (at your option) any later version. |
|
|
//| |
|
|
//| This program is distributed in the hope that it will be useful, |
|
|
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
//| GNU General Public License for more details. |
|
|
//+------------------------------------------------------------------+
|
|
#include "alglibmisc.mqh"
|
|
#include "optimization.mqh"
|
|
#include "solvers.mqh"
|
|
#include "integration.mqh"
|
|
//+------------------------------------------------------------------+
|
|
//| IDW Buffer |
|
|
//+------------------------------------------------------------------+
|
|
class CIDWCalcBuffer
|
|
{
|
|
public:
|
|
CRowDouble m_tsdist;
|
|
CRowDouble m_tsw;
|
|
CRowDouble m_tsyw;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CMatrixDouble m_tsxy;
|
|
CKDTreeRequestBuffer m_requestbuffer;
|
|
//--- constructor / destructor
|
|
CIDWCalcBuffer(void) {}
|
|
~CIDWCalcBuffer(void) {}
|
|
//--- copy
|
|
void Copy(const CIDWCalcBuffer &obj);
|
|
//--- overloading
|
|
void operator=(const CIDWCalcBuffer &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWCalcBuffer::Copy(const CIDWCalcBuffer &obj)
|
|
{
|
|
m_tsdist=obj.m_tsdist;
|
|
m_tsw=obj.m_tsw;
|
|
m_tsyw=obj.m_tsyw;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_tsxy=obj.m_tsxy;
|
|
m_requestbuffer=obj.m_requestbuffer;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW (Inverse Distance Weighting) model object. |
|
|
//+------------------------------------------------------------------+
|
|
class CIDWModel
|
|
{
|
|
public:
|
|
int m_algotype;
|
|
int m_nlayers;
|
|
int m_npoints;
|
|
int m_nx;
|
|
int m_ny;
|
|
double m_lambda0;
|
|
double m_lambdadecay;
|
|
double m_lambdalast;
|
|
double m_r0;
|
|
double m_rdecay;
|
|
double m_shepardp;
|
|
CKDTree m_tree;
|
|
CIDWCalcBuffer m_buffer;
|
|
//--- arrays
|
|
CRowDouble m_globalprior;
|
|
CRowDouble m_shepardxy;
|
|
//--- constructor / destructor
|
|
CIDWModel(void);
|
|
~CIDWModel(void) {}
|
|
//--- copy
|
|
void Copy(const CIDWModel &obj);
|
|
//--- overloading
|
|
void operator=(const CIDWModel &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CIDWModel::CIDWModel(void)
|
|
{
|
|
m_algotype=0;
|
|
m_nlayers=0;
|
|
m_npoints=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_lambda0=0;
|
|
m_lambdadecay=0;
|
|
m_lambdalast=0;
|
|
m_r0=0;
|
|
m_rdecay=0;
|
|
m_shepardp=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWModel::Copy(const CIDWModel &obj)
|
|
{
|
|
//--- copy variables
|
|
m_algotype=obj.m_algotype;
|
|
m_nlayers=obj.m_nlayers;
|
|
m_npoints=obj.m_npoints;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_lambda0=obj.m_lambda0;
|
|
m_lambdadecay=obj.m_lambdadecay;
|
|
m_lambdalast=obj.m_lambdalast;
|
|
m_r0=obj.m_r0;
|
|
m_rdecay=obj.m_rdecay;
|
|
m_shepardp=obj.m_shepardp;
|
|
m_globalprior=obj.m_globalprior;
|
|
m_shepardxy=obj.m_shepardxy;
|
|
m_tree=obj.m_tree;
|
|
m_buffer=obj.m_buffer;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW model. |
|
|
//+------------------------------------------------------------------+
|
|
class CIDWModelShell
|
|
{
|
|
private:
|
|
CIDWModel m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CIDWModelShell(void) {}
|
|
CIDWModelShell(CIDWModel &obj) { m_innerobj.Copy(obj); }
|
|
~CIDWModelShell(void) {}
|
|
//--- method
|
|
CIDWModel *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Builder object used to generate IDW (Inverse Distance Weighting) |
|
|
//| model. |
|
|
//+------------------------------------------------------------------+
|
|
struct CIDWBuilder
|
|
{
|
|
int m_algotype;
|
|
int m_nlayers;
|
|
int m_npoints;
|
|
int m_nx;
|
|
int m_ny;
|
|
int m_priortermtype;
|
|
double m_lambda0;
|
|
double m_lambdadecay;
|
|
double m_lambdalast;
|
|
double m_r0;
|
|
double m_rdecay;
|
|
double m_shepardp;
|
|
CRowInt m_tmptags;
|
|
CRowDouble m_priortermval;
|
|
CRowDouble m_tmpdist;
|
|
CRowDouble m_tmpmean;
|
|
CRowDouble m_tmpw;
|
|
CRowDouble m_tmpwy;
|
|
CRowDouble m_tmpx;
|
|
CRowDouble m_xy;
|
|
CMatrixDouble m_tmplayers;
|
|
CMatrixDouble m_tmpxy;
|
|
CKDTree m_tmptree;
|
|
//--- constructor / destructor
|
|
CIDWBuilder(void);
|
|
~CIDWBuilder(void) {}
|
|
//--- copy
|
|
void Copy(const CIDWBuilder &obj);
|
|
//--- overloading
|
|
void operator=(const CIDWBuilder &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CIDWBuilder::CIDWBuilder(void)
|
|
{
|
|
m_algotype=0;
|
|
m_nlayers=0;
|
|
m_npoints=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_priortermtype=0;
|
|
m_lambda0=0;
|
|
m_lambdadecay=0;
|
|
m_lambdalast=0;
|
|
m_r0=0;
|
|
m_rdecay=0;
|
|
m_shepardp=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWBuilder::Copy(const CIDWBuilder &obj)
|
|
{
|
|
m_algotype=obj.m_algotype;
|
|
m_nlayers=obj.m_nlayers;
|
|
m_npoints=obj.m_npoints;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_priortermtype=obj.m_priortermtype;
|
|
m_lambda0=obj.m_lambda0;
|
|
m_lambdadecay=obj.m_lambdadecay;
|
|
m_lambdalast=obj.m_lambdalast;
|
|
m_r0=obj.m_r0;
|
|
m_rdecay=obj.m_rdecay;
|
|
m_shepardp=obj.m_shepardp;
|
|
m_tmptags=obj.m_tmptags;
|
|
m_priortermval=obj.m_priortermval;
|
|
m_tmpdist=obj.m_tmpdist;
|
|
m_tmpmean=obj.m_tmpmean;
|
|
m_tmpw=obj.m_tmpw;
|
|
m_tmpwy=obj.m_tmpwy;
|
|
m_tmpx=obj.m_tmpx;
|
|
m_xy=obj.m_xy;
|
|
m_tmplayers=obj.m_tmplayers;
|
|
m_tmpxy=obj.m_tmpxy;
|
|
m_tmptree=obj.m_tmptree;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW fitting report: |
|
|
//| rmserror RMS error |
|
|
//| avgerror average error |
|
|
//| maxerror maximum error |
|
|
//| r2 coefficient of determination, |
|
|
//| R-squared, 1-RSS/TSS |
|
|
//+------------------------------------------------------------------+
|
|
struct CIDWReport
|
|
{
|
|
public:
|
|
double m_avgerror;
|
|
double m_maxerror;
|
|
double m_r2;
|
|
double m_rmserror;
|
|
//--- constructor / destructor
|
|
CIDWReport(void) { ZeroMemory(this); }
|
|
~CIDWReport(void) {}
|
|
//--- copy
|
|
void Copy(const CIDWReport &obj);
|
|
//--- overloading
|
|
void operator=(const CIDWReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWReport::Copy(const CIDWReport &obj)
|
|
{
|
|
m_avgerror=obj.m_avgerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_r2=obj.m_r2;
|
|
m_rmserror=obj.m_rmserror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Inverse distance weighting interpolation |
|
|
//+------------------------------------------------------------------+
|
|
class CIDWInt
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const double m_w0;
|
|
static const double m_meps;
|
|
static const int m_defaultnlayers;
|
|
static const double m_defaultlambda0;
|
|
//--- public methods
|
|
static void IDWCreateCalcBuffer(CIDWModel &s,CIDWCalcBuffer &buf);
|
|
static void IDWBuilderCreate(int nx,int ny,CIDWBuilder &State);
|
|
static void IDWBuilderSetNLayers(CIDWBuilder &State,int nlayers);
|
|
static void IDWBuilderSetPoints(CIDWBuilder &State,CMatrixDouble &xy,int n);
|
|
static void IDWBuilderSetAlgoMSTAB(CIDWBuilder &State,double srad);
|
|
static void IDWBuilderSetAlgoTextBookShepard(CIDWBuilder &State,double p);
|
|
static void IDWBuilderSetAlgoTextBookModShepard(CIDWBuilder &State,double r);
|
|
static void IDWBuilderSetUserTerm(CIDWBuilder &State,double v);
|
|
static void IDWBuilderSetConstTerm(CIDWBuilder &State);
|
|
static void IDWBuilderSetZeroTerm(CIDWBuilder &State);
|
|
static double IDWCalc1(CIDWModel &s,double x0);
|
|
static double IDWCalc2(CIDWModel &s,double x0,double x1);
|
|
static double IDWCalc3(CIDWModel &s,double x0,double x1,double x2);
|
|
static void IDWCalc(CIDWModel &s,CRowDouble &x,CRowDouble &y);
|
|
static void IDWCalcBuf(CIDWModel &s,CRowDouble &x,CRowDouble &y);
|
|
static void IDWTsCalcBuf(CIDWModel &s,CIDWCalcBuffer &buf,CRowDouble &x,CRowDouble &y);
|
|
static void IDWFit(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep);
|
|
static void IDWAlloc(CSerializer &s,CIDWModel &model);
|
|
static void IDWSerialize(CSerializer &s,CIDWModel &model);
|
|
static void CIDWInt::IDWUnserialize(CSerializer &s,CIDWModel &model);
|
|
|
|
private:
|
|
static void CIDWInt::ErrorMetricsViaCalc(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CIDWInt::m_w0=1.0;
|
|
const double CIDWInt::m_meps=1.0E-50;
|
|
const int CIDWInt::m_defaultnlayers=16;
|
|
const double CIDWInt::m_defaultlambda0=0.3333;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel IDW model evaluations (with one IDW model |
|
|
//| instance being used from multiple threads, as long as different |
|
|
//| threads use different instances of buffer). |
|
|
//| This buffer object can be used with IDWTsCalcBuf() function (here|
|
|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
|
|
//| function which reuses previously allocated output space). |
|
|
//| How to use it: |
|
|
//| * create IDW model structure or load it from file |
|
|
//| * call IDWCreateCalcBuffer(), once per thread working with IDW |
|
|
//| model (you should call this function only AFTER model |
|
|
//| initialization, see below for more information) |
|
|
//| * call IDWTsCalcBuf() from different threads, with each thread |
|
|
//| working with its own copy of buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with IDW model |
|
|
//| object which was used to initialize buffer. Any |
|
|
//| attempt to use buffer with different object is |
|
|
//| dangerous - you may get memory violation error because|
|
|
//| sizes of internal arrays do not fit to dimensions of |
|
|
//| the IDW structure. |
|
|
//| IMPORTANT: you should call this function only for model which was|
|
|
//| built with model builder (or unserialized from file). |
|
|
//| Sizes of some internal structures are determined only |
|
|
//| after model is built, so buffer object created before |
|
|
//| model construction stage will be useless (and any |
|
|
//| attempt to use it will result in exception). |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWCreateCalcBuffer(CIDWModel &s,CIDWCalcBuffer &buf)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_ny>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nlayers>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_algotype>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(s.m_nlayers>=1 && s.m_algotype!=0)
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_tree,buf.m_requestbuffer);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_x,s.m_nx);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_y,s.m_ny);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_tsyw,s.m_ny*MathMax(s.m_nlayers,1));
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_tsw,MathMax(s.m_nlayers,1));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine creates builder object used to generate IDW model|
|
|
//| from irregularly sampled (scattered) dataset. Multidimensional |
|
|
//| scalar/vector-valued are supported. |
|
|
//| Builder object is used to fit model to data as follows: |
|
|
//| * builder object is created with idwbuildercreate() function |
|
|
//| * dataset is added with IDWBuilderSetPoints() function |
|
|
//| * one of the modern IDW algorithms is chosen with either: |
|
|
//| * IDWBuilderSetAlgoMSTAB() - Multilayer STABilized algorithm|
|
|
//| (interpolation). |
|
|
//| Alternatively, one of the textbook algorithms can be chosen (not |
|
|
//| recommended): |
|
|
//| * IDWBuilderSetAlgoTextBookShepard() - textbook Shepard |
|
|
//| algorithm |
|
|
//| * IDWBuilderSetAlgoTextBookModShepard()- textbook modified |
|
|
//| Shepard algorithm |
|
|
//| * finally, model construction is performed with IDWFit() |
|
|
//| function. |
|
|
//| INPUT PARAMETERS: |
|
|
//| NX - dimensionality of the argument, NX>=1 |
|
|
//| NY - dimensionality of the function being modeled, |
|
|
//| NY>=1; NY=1 corresponds to classic scalar function,|
|
|
//| NY>=1 corresponds to vector-valued function. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderCreate(int nx,int ny,CIDWBuilder &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<=0"))
|
|
return;
|
|
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<=0"))
|
|
return;
|
|
//--- We choose reasonable defaults for the algorithm:
|
|
//--- * MSTAB algorithm
|
|
//--- * 12 layers
|
|
//--- * default radius
|
|
//--- * default Lambda0
|
|
State.m_algotype=2;
|
|
State.m_priortermtype=2;
|
|
CApServ::RVectorSetLengthAtLeast(State.m_priortermval,ny);
|
|
State.m_nlayers=m_defaultnlayers;
|
|
State.m_r0=0;
|
|
State.m_rdecay=0.5;
|
|
State.m_lambda0=m_defaultlambda0;
|
|
State.m_lambdalast=0;
|
|
State.m_lambdadecay=1.0;
|
|
//--- Other parameters, not used but initialized
|
|
State.m_shepardp=0;
|
|
//--- Initial dataset is empty
|
|
State.m_npoints=0;
|
|
State.m_nx=nx;
|
|
State.m_ny=ny;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function changes number of layers used by IDW-MSTAB |
|
|
//| algorithm. |
|
|
//| The more layers you have, the finer details can be reproduced |
|
|
//| with IDW model. The less layers you have, the less memory and CPU|
|
|
//| time is consumed by the model. |
|
|
//| Memory consumption grows linearly with layers count, running time|
|
|
//| grows sub-linearly. |
|
|
//| The default number of layers is 16, which allows you to reproduce|
|
|
//| details at distance down to SRad/65536. You will rarely need to |
|
|
//| change it. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| NLayers - NLayers>=1, the number of layers used by the model.|
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetNLayers(CIDWBuilder &State,int nlayers)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nlayers>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
|
|
State.m_nlayers=nlayers;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset to the builder object. |
|
|
//| This function overrides results of the previous calls, i.e. |
|
|
//| multiple calls of this function will result in only the last set |
|
|
//| being added. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| XY - points, array[N,NX+NY]. One row corresponds to one |
|
|
//| point in the dataset. First NX elements are |
|
|
//| coordinates, next NY elements are function values. |
|
|
//| Array may be larger than specified, in this case |
|
|
//| only leading [N,NX+NY] elements will be used. |
|
|
//| N - number of points in the dataset, N>=0. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetPoints(CIDWBuilder &State,
|
|
CMatrixDouble &xy,int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
|
|
return;
|
|
if(!CAp::Assert(n==0 || CAp::Cols(xy)>=State.m_nx+State.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,State.m_nx+State.m_ny),__FUNCTION__+": XY contains infinite or NaN values!"))
|
|
return;
|
|
|
|
State.m_npoints=n;
|
|
int ew=State.m_nx+State.m_ny;
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xy,n*ew);
|
|
for(int i=0; i<n; i++)
|
|
for(int j=0; j<ew; j++)
|
|
State.m_xy.Set(i*ew+j,xy.Get(i,j));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets IDW model construction algorithm to the |
|
|
//| Multilayer Stabilized IDW method (IDW-MSTAB), a latest |
|
|
//| incarnation of the inverse distance weighting interpolation which|
|
|
//| fixes shortcomings of the original and modified Shepard's |
|
|
//| variants. |
|
|
//| The distinctive features of IDW-MSTAB are: |
|
|
//| 1) exact interpolation is pursued (as opposed to fitting and |
|
|
//| noise suppression) |
|
|
//| 2) improved robustness when compared with that of other |
|
|
//| algorithms: |
|
|
//| * MSTAB shows almost no strange fitting artifacts like |
|
|
//| ripples and sharp spikes (unlike N-dimensional splines |
|
|
//| and HRBFs) |
|
|
//| * MSTAB does not return function values far from the |
|
|
//| interval spanned by the dataset; say, if all your points |
|
|
//| have |f|<=1, you can be sure that model value won't |
|
|
//| deviate too much from [-1,+1] |
|
|
//| 3) good model construction time competing with that of HRBFs |
|
|
//| and bicubic splines |
|
|
//| 4) ability to work with any number of dimensions, starting |
|
|
//| from NX=1 |
|
|
//| The drawbacks of IDW-MSTAB (and all IDW algorithms in general) |
|
|
//| are: |
|
|
//| 1) dependence of the model evaluation time on the search radius|
|
|
//| 2) bad extrapolation properties, models built by this method |
|
|
//| are usually conservative in their predictions |
|
|
//| Thus, IDW-MSTAB is a good "default" option if you want to perform|
|
|
//| scattered multidimensional interpolation. Although it has its |
|
|
//| drawbacks, it is easy to use and robust, which makes it a good |
|
|
//| first step. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| SRad - initial search radius, SRad>0 is required. A model |
|
|
//| value is obtained by "smart" averaging of the |
|
|
//| dataset points within search radius. |
|
|
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
|
|
//| including datasets with non-distinct points. In case |
|
|
//| non-distinct points are found, an average value for this |
|
|
//| point will be calculated. |
|
|
//| NOTE 2: the memory requirements for model storage are |
|
|
//| O(NPoints*NLayers). The model construction needs twice |
|
|
//| as much memory as model storage. |
|
|
//| NOTE 3: by default 16 IDW layers are built which is enough for |
|
|
//| most cases. You can change this parameter with |
|
|
//| IDWBuilderSetNLayers() method. Larger values may be |
|
|
//| necessary if you need to reproduce extrafine details at |
|
|
//| distances smaller than SRad/65536. Smaller value may |
|
|
//| be necessary if you have to save memory and computing |
|
|
//| time, and ready to sacrifice some model quality. |
|
|
//| ALGORITHM DESCRIPTION: |
|
|
//| ALGLIB implementation of IDW is somewhat similar to the |
|
|
//| modified Shepard's method (one with search radius R) but |
|
|
//| overcomes several of its drawbacks, namely: |
|
|
//| 1) a tendency to show stepwise behavior for uniform datasets|
|
|
//| 2) a tendency to show terrible interpolation properties for |
|
|
//| highly nonuniform datasets which often arise in |
|
|
//| geospatial tasks (function values are densely sampled |
|
|
//| across multiple separated "tracks") |
|
|
//| IDW-MSTAB method performs several passes over dataset and builds |
|
|
//| a sequence of progressively refined IDW models (layers), which |
|
|
//| starts from one with largest search radius SRad and continues |
|
|
//| to smaller search radii until required number of layers is built.|
|
|
//| Highest layers reproduce global behavior of the target function |
|
|
//| at larger distances whilst lower layers reproduce fine details at|
|
|
//| smaller distances. |
|
|
//| Each layer is an IDW model built with following modifications: |
|
|
//| * weights go to zero when distance approach to the current |
|
|
//| search radius |
|
|
//| * an additional regularizing term is added to the distance: |
|
|
//| w=1/(d^2+lambda) |
|
|
//| * an additional fictional term with unit weight and zero |
|
|
//| function value is added in order to promote continuity |
|
|
//| properties at the isolated and boundary points |
|
|
//| By default, 16 layers is built, which is enough for most cases. |
|
|
//| You can change this parameter with IDWBuilderSetNLayers() method.|
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetAlgoMSTAB(CIDWBuilder &State,double srad)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(srad),__FUNCTION__+": SRad is not finite"))
|
|
return;
|
|
if(!CAp::Assert(srad>0.0,__FUNCTION__+": SRad<=0"))
|
|
return;
|
|
//--- Set algorithm
|
|
State.m_algotype=2;
|
|
//--- Set options
|
|
State.m_r0=srad;
|
|
State.m_rdecay=0.5;
|
|
State.m_lambda0=m_defaultlambda0;
|
|
State.m_lambdalast=0;
|
|
State.m_lambdadecay=1.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets IDW model construction algorithm to the |
|
|
//| textbook Shepard's algorithm with custom (user-specified) power |
|
|
//| parameter. |
|
|
//| IMPORTANT: we do NOT recommend using textbook IDW algorithms |
|
|
//| because they have terrible interpolation properties. |
|
|
//| Use MSTAB in all cases. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| P - power parameter, P>0; good value to start with is |
|
|
//| 2.0 |
|
|
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
|
|
//| including datasets with non-distinct points. In case |
|
|
//| non-distinct points are found, an average value for this |
|
|
//| point will be calculated. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetAlgoTextBookShepard(CIDWBuilder &State,double p)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(p),__FUNCTION__+": P is not finite"))
|
|
return;
|
|
if(!CAp::Assert(p>0.0,__FUNCTION__+": P<=0"))
|
|
return;
|
|
//--- Set algorithm and options
|
|
State.m_algotype=0;
|
|
State.m_shepardp=p;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets IDW model construction algorithm to the |
|
|
//| 'textbook' modified Shepard's algorithm with user-specified |
|
|
//| search radius. |
|
|
//| IMPORTANT: we do NOT recommend using textbook IDW algorithms |
|
|
//| because they have terrible interpolation properties. |
|
|
//| Use MSTAB in all cases. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| R - search radius |
|
|
//| NOTE 1: IDW interpolation can correctly handle ANY dataset, |
|
|
//| including datasets with non-distinct points. In case |
|
|
//| non-distinct points are found, an average value for this |
|
|
//| point will be calculated. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetAlgoTextBookModShepard(CIDWBuilder &State,double r)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(r),__FUNCTION__+": R is not finite"))
|
|
return;
|
|
if(!CAp::Assert(r>0.0,__FUNCTION__+": R<=0"))
|
|
return;
|
|
//--- Set algorithm and options
|
|
State.m_algotype=1;
|
|
State.m_r0=r;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets prior term (model value at infinity) as |
|
|
//| user-specified value. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//| V - value for user-defined prior |
|
|
//| NOTE: for vector-valued models all components of the prior are |
|
|
//| set to same user-specified value |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetUserTerm(CIDWBuilder &State,double v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": infinite/NAN value passed"))
|
|
return;
|
|
|
|
State.m_priortermtype=0;
|
|
State.m_priortermval.Fill(v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets constant prior term (model value at infinity).|
|
|
//| Constant prior term is determined as mean value over dataset. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetConstTerm(CIDWBuilder &State)
|
|
{
|
|
State.m_priortermtype=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets zero prior term (model value at infinity). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWBuilderSetZeroTerm(CIDWBuilder &State)
|
|
{
|
|
State.m_priortermtype=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW interpolation: scalar target, 1-dimensional argument |
|
|
//| NOTE: this function modifies internal temporaries of the IDW |
|
|
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
|
|
//| parallel model evaluation from the multiple threads, use |
|
|
//| IDWTsCalcBuf() with per-thread buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW interpolant built with IDW builder |
|
|
//| X0 - argument value |
|
|
//| Result: |
|
|
//| IDW interpolant S(X0) |
|
|
//+------------------------------------------------------------------+
|
|
double CIDWInt::IDWCalc1(CIDWModel &s,double x0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx==1,__FUNCTION__+": S.NX<>1"))
|
|
return(0);
|
|
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
|
|
return(0);
|
|
|
|
s.m_buffer.m_x.Set(0,x0);
|
|
//--- function call
|
|
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
|
|
//--- return result
|
|
return(s.m_buffer.m_y[0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW interpolation: scalar target, 2-dimensional argument |
|
|
//| NOTE: this function modifies internal temporaries of the IDW |
|
|
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
|
|
//| parallel model evaluation from the multiple threads, use |
|
|
//| IDWTsCalcBuf() with per- thread buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW interpolant built with IDW builder |
|
|
//| X0, X1 - argument value |
|
|
//| Result: |
|
|
//| IDW interpolant S(X0,X1) |
|
|
//+------------------------------------------------------------------+
|
|
double CIDWInt::IDWCalc2(CIDWModel &s,double x0,double x1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx==2,__FUNCTION__+": S.NX<>2"))
|
|
return(0);
|
|
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": X1 is INF or NAN"))
|
|
return(0);
|
|
|
|
s.m_buffer.m_x.Set(0,x0);
|
|
s.m_buffer.m_x.Set(1,x1);
|
|
//--- function call
|
|
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
|
|
//--- return result
|
|
return(s.m_buffer.m_y[0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IDW interpolation: scalar target, 3-dimensional argument |
|
|
//| NOTE: this function modifies internal temporaries of the IDW |
|
|
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
|
|
//| parallel model evaluation from the multiple threads, use |
|
|
//| IDWTsCalcBuf() with per- thread buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW interpolant built with IDW builder |
|
|
//| X0,X1,X2 - argument value |
|
|
//| Result: |
|
|
//| IDW interpolant S(X0,X1,X2) |
|
|
//+------------------------------------------------------------------+
|
|
double CIDWInt::IDWCalc3(CIDWModel &s,double x0,double x1,double x2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx==3,__FUNCTION__+": S.NX<>3"))
|
|
return(0);
|
|
if(!CAp::Assert(s.m_ny==1,__FUNCTION__+": S.NY<>1"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": X0 is INF or NAN"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": X1 is INF or NAN"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": X2 is INF or NAN"))
|
|
return(0);
|
|
|
|
s.m_buffer.m_x.Set(0,x0);
|
|
s.m_buffer.m_x.Set(1,x1);
|
|
s.m_buffer.m_x.Set(2,x2);
|
|
//--- function call
|
|
IDWTsCalcBuf(s,s.m_buffer,s.m_buffer.m_x,s.m_buffer.m_y);
|
|
//--- return result
|
|
return(s.m_buffer.m_y[0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the IDW model at the given |
|
|
//| point. |
|
|
//| This is general function which can be used for arbitrary NX |
|
|
//| (dimension of the space of arguments) and NY (dimension of the |
|
|
//| function itself). However when you have NY=1 you may find more |
|
|
//| convenient to use IDWCalc1(), IDWCalc2() or IDWCalc3(). |
|
|
//| NOTE: this function modifies internal temporaries of the IDW |
|
|
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
|
|
//| parallel model evaluation from the multiple threads, use |
|
|
//| IDWTsCalcBuf() with per-thread buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is out-parameter and |
|
|
//| will be reallocated after call to this function. In|
|
|
//| case you want to reuse previously allocated Y, you |
|
|
//| may use IDWCalcBuf(), which reallocates Y only when|
|
|
//| it is too small. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWCalc(CIDWModel &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
y.Resize(0);
|
|
//--- function call
|
|
IDWTsCalcBuf(s,s.m_buffer,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the IDW model at the given |
|
|
//| point. |
|
|
//| Same as IDWCalc(), but does not reallocate Y when in is large |
|
|
//| enough to store function values. |
|
|
//| NOTE: this function modifies internal temporaries of the IDW |
|
|
//| model, thus IT IS NOT THREAD-SAFE! If you want to perform |
|
|
//| parallel model evaluation from the multiple threads, use |
|
|
//| IDWTsCalcBuf() with per-thread buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWCalcBuf(CIDWModel &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
IDWTsCalcBuf(s,s.m_buffer,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the IDW model at the given |
|
|
//| point, using external buffer object (internal temporaries of IDW |
|
|
//| model are not modified). |
|
|
//| This function allows to use same IDW model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of the buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - IDW model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of IDW model with IDWCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWTsCalcBuf(CIDWModel &s,CIDWCalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
int i=0;
|
|
int j=0;
|
|
int ew=0;
|
|
int k=0;
|
|
int layeridx=0;
|
|
int npoints=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double f=0;
|
|
double p=0;
|
|
double r=0;
|
|
double eps=0;
|
|
double lambdacur=0;
|
|
double lambdadecay=0;
|
|
double invrdecay=0;
|
|
double invr=0;
|
|
bool fastcalcpossible=false;
|
|
double wf0=0;
|
|
double ws0=0;
|
|
double wf1=0;
|
|
double ws1=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
//--- Avoid spurious compiler warnings
|
|
wf0=0;
|
|
ws0=0;
|
|
wf1=0;
|
|
ws1=0;
|
|
//--- Allocate output
|
|
if(CAp::Len(y)<ny)
|
|
y.Resize(ny);
|
|
//--- Quick exit for NLayers=0 (no dataset)
|
|
if(s.m_nlayers==0)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j,s.m_globalprior[j]);
|
|
return;
|
|
}
|
|
//--- Textbook Shepard's method
|
|
if(s.m_algotype==0)
|
|
{
|
|
npoints=s.m_npoints;
|
|
//--- check
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
eps=1.0E-50;
|
|
ew=nx+ny;
|
|
p=s.m_shepardp;
|
|
y.Fill(0);
|
|
buf.m_tsyw.Fill(eps);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
//--- Compute squared distance
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
vv=s.m_shepardxy[i*ew+j]-x[j];
|
|
v=v+vv*vv;
|
|
}
|
|
//--- Compute weight (with small regularizing addition)
|
|
v=MathPow(v,p*0.5);
|
|
v=1/(eps+v);
|
|
//--- Accumulate
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
y.Add(j,v*s.m_shepardxy[i*ew+nx+j]);
|
|
buf.m_tsyw.Add(j,v);
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j,y[j]/buf.m_tsyw[j]+s.m_globalprior[j]);
|
|
return;
|
|
}
|
|
//--- Textbook modified Shepard's method
|
|
if(s.m_algotype==1)
|
|
{
|
|
eps=1.0E-50;
|
|
r=s.m_r0;
|
|
y.Fill(0);
|
|
buf.m_tsyw.Fill(eps);
|
|
k=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,x,r,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsXY(s.m_tree,buf.m_requestbuffer,buf.m_tsxy);
|
|
CNearestNeighbor::KDTreeTsQueryResultsDistances(s.m_tree,buf.m_requestbuffer,buf.m_tsdist);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
v=buf.m_tsdist[i];
|
|
v=(r-v)/(r*v+eps);
|
|
v=v*v;
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
y.Add(j,v*buf.m_tsxy.Get(i,nx+j));
|
|
buf.m_tsyw.Add(j,v);
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j,y[j]/buf.m_tsyw[j]+s.m_globalprior[j]);
|
|
return;
|
|
}
|
|
//--- MSTAB
|
|
if(s.m_algotype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(m_w0==1.0,__FUNCTION__+": unexpected W0,integrity check failed"))
|
|
return;
|
|
invrdecay=1.0/s.m_rdecay;
|
|
invr=1.0/s.m_r0;
|
|
lambdadecay=s.m_lambdadecay;
|
|
fastcalcpossible=(ny==1 && s.m_nlayers>=3) && lambdadecay==1.0;
|
|
if(fastcalcpossible)
|
|
{
|
|
//--- Important special case, NY=1, no lambda-decay,
|
|
//--- we can perform optimized fast evaluation
|
|
wf0=0;
|
|
ws0=m_w0;
|
|
wf1=0;
|
|
ws1=m_w0;
|
|
for(j=0; j<s.m_nlayers; j++)
|
|
{
|
|
buf.m_tsyw.Set(j,0);
|
|
buf.m_tsw.Set(j,m_w0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Setup variables for generic evaluation path
|
|
for(j=0; j<ny*s.m_nlayers; j++)
|
|
buf.m_tsyw.Set(j,0);
|
|
for(j=0; j<s.m_nlayers; j++)
|
|
buf.m_tsw.Set(j,m_w0);
|
|
}
|
|
k=CNearestNeighbor::KDTreeTsQueryRNNU(s.m_tree,buf.m_requestbuffer,x,s.m_r0,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsXY(s.m_tree,buf.m_requestbuffer,buf.m_tsxy);
|
|
CNearestNeighbor::KDTreeTsQueryResultsDistances(s.m_tree,buf.m_requestbuffer,buf.m_tsdist);
|
|
for(i=0; i<k; i++)
|
|
{
|
|
lambdacur=s.m_lambda0;
|
|
vv=buf.m_tsdist[i]*invr;
|
|
if(fastcalcpossible)
|
|
{
|
|
//--- Important special case, fast evaluation possible
|
|
v=vv*vv;
|
|
v=(1-v)*(1-v)/(v+lambdacur);
|
|
f=buf.m_tsxy.Get(i,nx);
|
|
wf0=wf0+v*f;
|
|
ws0=ws0+v;
|
|
vv=vv*invrdecay;
|
|
if(vv>=1.0)
|
|
continue;
|
|
v=vv*vv;
|
|
v=(1-v)*(1-v)/(v+lambdacur);
|
|
f=buf.m_tsxy.Get(i,nx+1);
|
|
wf1=wf1+v*f;
|
|
ws1=ws1+v;
|
|
vv=vv*invrdecay;
|
|
if(vv>=1.0)
|
|
continue;
|
|
for(layeridx=2; layeridx<s.m_nlayers; layeridx++)
|
|
{
|
|
if(layeridx==s.m_nlayers-1)
|
|
lambdacur=s.m_lambdalast;
|
|
v=vv*vv;
|
|
v=(1-v)*(1-v)/(v+lambdacur);
|
|
f=buf.m_tsxy.Get(i,nx+layeridx);
|
|
buf.m_tsyw.Add(layeridx,v*f);
|
|
buf.m_tsw.Add(layeridx,v);
|
|
vv=vv*invrdecay;
|
|
if(vv>=1.0)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- General case
|
|
for(layeridx=0; layeridx<s.m_nlayers; layeridx++)
|
|
{
|
|
if(layeridx==s.m_nlayers-1)
|
|
lambdacur=s.m_lambdalast;
|
|
if(vv>=1.0)
|
|
break;
|
|
v=vv*vv;
|
|
v=(1-v)*(1-v)/(v+lambdacur);
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
f=buf.m_tsxy.Get(i,nx+layeridx*ny+j);
|
|
buf.m_tsyw.Add(layeridx*ny+j,v*f);
|
|
}
|
|
buf.m_tsw.Add(layeridx,v);
|
|
lambdacur*=lambdadecay;
|
|
vv*=invrdecay;
|
|
}
|
|
}
|
|
}
|
|
if(fastcalcpossible)
|
|
{
|
|
//--- Important special case, finalize evaluations
|
|
buf.m_tsyw.Set(0,wf0);
|
|
buf.m_tsw.Set(0,ws0);
|
|
buf.m_tsyw.Set(1,wf1);
|
|
buf.m_tsw.Set(1,ws1);
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j,s.m_globalprior[j]);
|
|
for(layeridx=0; layeridx<s.m_nlayers; layeridx++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
y.Add(j,buf.m_tsyw[layeridx*ny+j]/buf.m_tsw[layeridx]);
|
|
}
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": unexpected AlgoType");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits IDW model to the dataset using current IDW |
|
|
//| construction algorithm. A model being built and fitting report |
|
|
//| are returned. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Model - an IDW model built with current algorithm |
|
|
//| Rep - model fitting report, fields of this structure |
|
|
//| contain information about average fitting errors. |
|
|
//| NOTE: although IDW-MSTAB algorithm is an interpolation method, |
|
|
//| i.e. it tries to fit the model exactly, it can handle |
|
|
//| datasets with non-distinct points which can not be fit |
|
|
//| exactly; in such cases least-squares fitting is performed. |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWFit(CIDWBuilder &State,CIDWModel &model,CIDWReport &rep)
|
|
{
|
|
//--- create variables
|
|
int nx=State.m_nx;
|
|
int ny=State.m_ny;
|
|
int npoints=State.m_npoints;
|
|
int i=0;
|
|
int i0=0;
|
|
int j=0;
|
|
int k=0;
|
|
int layeridx=0;
|
|
int srcidx=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double rcur=0;
|
|
double lambdacur=0;
|
|
double rss=0;
|
|
double tss=0;
|
|
//--- Clear report fields
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_r2=1.0;
|
|
//--- Quick exit for empty dataset
|
|
if(State.m_npoints==0)
|
|
{
|
|
model.m_nx=nx;
|
|
model.m_ny=ny;
|
|
model.m_globalprior=vector<double>::Zeros(ny);
|
|
model.m_algotype=0;
|
|
model.m_nlayers=0;
|
|
model.m_r0=1;
|
|
model.m_rdecay=0.5;
|
|
model.m_lambda0=0;
|
|
model.m_lambdalast=0;
|
|
model.m_lambdadecay=1;
|
|
model.m_shepardp=2;
|
|
model.m_npoints=0;
|
|
IDWCreateCalcBuffer(model,model.m_buffer);
|
|
return;
|
|
}
|
|
//--- Compute temporaries which will be required later:
|
|
//--- * global mean
|
|
//--- check
|
|
if(!CAp::Assert(State.m_npoints>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
State.m_tmpmean=vector<double>::Zeros(ny);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
State.m_tmpmean.Add(j,State.m_xy[i*(nx+ny)+nx+j]);
|
|
}
|
|
State.m_tmpmean/=npoints;
|
|
//--- Compute global prior
|
|
//--- NOTE: for original Shepard's method it is always mean value
|
|
model.m_globalprior=State.m_tmpmean;
|
|
if(State.m_algotype!=0)
|
|
{
|
|
//--- Algorithm is set to one of the "advanced" versions with search
|
|
//--- radius which can handle non-mean prior term
|
|
if(State.m_priortermtype==0)
|
|
{
|
|
//--- User-specified prior
|
|
model.m_globalprior=State.m_priortermval;
|
|
}
|
|
if(State.m_priortermtype==3)
|
|
{
|
|
//--- Zero prior
|
|
model.m_globalprior=vector<double>::Zeros(ny);
|
|
}
|
|
}
|
|
//--- Textbook Shepard
|
|
if(State.m_algotype==0)
|
|
{
|
|
//--- Initialize model
|
|
model.m_algotype=0;
|
|
model.m_nx=nx;
|
|
model.m_ny=ny;
|
|
model.m_nlayers=1;
|
|
model.m_r0=1;
|
|
model.m_rdecay=0.5;
|
|
model.m_lambda0=0;
|
|
model.m_lambdalast=0;
|
|
model.m_lambdadecay=1;
|
|
model.m_shepardp=State.m_shepardp;
|
|
//--- Copy dataset
|
|
CApServ::RVectorSetLengthAtLeast(model.m_shepardxy,npoints*(nx+ny));
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
model.m_shepardxy.Set(i*(nx+ny)+j,State.m_xy[i*(nx+ny)+j]);
|
|
for(j=0; j<ny; j++)
|
|
model.m_shepardxy.Set(i*(nx+ny)+nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
|
|
}
|
|
model.m_npoints=npoints;
|
|
//--- Prepare internal buffer
|
|
//--- Evaluate report fields
|
|
IDWCreateCalcBuffer(model,model.m_buffer);
|
|
ErrorMetricsViaCalc(State,model,rep);
|
|
return;
|
|
}
|
|
//--- Textbook modified Shepard's method
|
|
if(State.m_algotype==1)
|
|
{
|
|
//--- Initialize model
|
|
model.m_algotype=1;
|
|
model.m_nx=nx;
|
|
model.m_ny=ny;
|
|
model.m_nlayers=1;
|
|
model.m_r0=State.m_r0;
|
|
model.m_rdecay=1;
|
|
model.m_lambda0=0;
|
|
model.m_lambdalast=0;
|
|
model.m_lambdadecay=1;
|
|
model.m_shepardp=0;
|
|
//--- Build kd-tree search structure
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmpxy,npoints,nx+ny);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
State.m_tmpxy.Set(i,j,State.m_xy[i*(nx+ny)+j]);
|
|
for(j=0; j<ny; j++)
|
|
State.m_tmpxy.Set(i,nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
|
|
}
|
|
CNearestNeighbor::KDTreeBuild(State.m_tmpxy,npoints,nx,ny,2,model.m_tree);
|
|
//--- Prepare internal buffer
|
|
//--- Evaluate report fields
|
|
IDWCreateCalcBuffer(model,model.m_buffer);
|
|
ErrorMetricsViaCalc(State,model,rep);
|
|
return;
|
|
}
|
|
//--- MSTAB algorithm
|
|
if(State.m_algotype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_nlayers>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Initialize model
|
|
model.m_algotype=2;
|
|
model.m_nx=nx;
|
|
model.m_ny=ny;
|
|
model.m_nlayers=State.m_nlayers;
|
|
model.m_r0=State.m_r0;
|
|
model.m_rdecay=0.5;
|
|
model.m_lambda0=State.m_lambda0;
|
|
model.m_lambdadecay=1.0;
|
|
model.m_lambdalast=m_meps;
|
|
model.m_shepardp=0;
|
|
//--- Build kd-tree search structure,
|
|
//--- prepare input residuals for the first layer of the model
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmpxy,npoints,nx);
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmplayers,npoints,nx+ny*(State.m_nlayers+1));
|
|
CApServ::IVectorSetLengthAtLeast(State.m_tmptags,npoints);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
v=State.m_xy[i*(nx+ny)+j];
|
|
State.m_tmpxy.Set(i,j,v);
|
|
State.m_tmplayers.Set(i,j,v);
|
|
}
|
|
State.m_tmptags.Set(i,i);
|
|
for(j=0; j<ny; j++)
|
|
State.m_tmplayers.Set(i,nx+j,State.m_xy[i*(nx+ny)+nx+j]-model.m_globalprior[j]);
|
|
}
|
|
CNearestNeighbor::KDTreeBuildTagged(State.m_tmpxy,State.m_tmptags,npoints,nx,0,2,State.m_tmptree);
|
|
//--- Iteratively build layer by layer
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmpx,nx);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmpwy,ny);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmpw,ny);
|
|
for(layeridx=0; layeridx<State.m_nlayers; layeridx++)
|
|
{
|
|
//--- Determine layer metrics
|
|
rcur=model.m_r0*MathPow(model.m_rdecay,layeridx);
|
|
lambdacur=model.m_lambda0*MathPow(model.m_lambdadecay,layeridx);
|
|
if(layeridx==State.m_nlayers-1)
|
|
lambdacur=model.m_lambdalast;
|
|
//--- For each point compute residual from fitting with current layer
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
State.m_tmpx.Set(j,State.m_tmplayers.Get(i,j));
|
|
k=CNearestNeighbor::KDTreeQueryRNN(State.m_tmptree,State.m_tmpx,rcur,true);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(State.m_tmptree,State.m_tmptags);
|
|
CNearestNeighbor::KDTreeQueryResultsDistances(State.m_tmptree,State.m_tmpdist);
|
|
State.m_tmpwy.Fill(0);
|
|
State.m_tmpw.Fill(m_w0);
|
|
for(i0=0; i0<k; i0++)
|
|
{
|
|
vv=State.m_tmpdist[i0]/rcur;
|
|
vv=vv*vv;
|
|
v=(1-vv)*(1-vv)/(vv+lambdacur);
|
|
srcidx=State.m_tmptags[i0];
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
State.m_tmpwy.Add(j,v*State.m_tmplayers.Get(srcidx,nx+layeridx*ny+j));
|
|
State.m_tmpw.Add(j,v);
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
v=State.m_tmplayers.Get(i,nx+layeridx*ny+j);
|
|
State.m_tmplayers.Set(i,nx+(layeridx+1)*ny+j,v-State.m_tmpwy[j]/State.m_tmpw[j]);
|
|
}
|
|
}
|
|
}
|
|
CNearestNeighbor::KDTreeBuild(State.m_tmplayers,npoints,nx,ny*State.m_nlayers,2,model.m_tree);
|
|
//--- Evaluate report fields
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rss=0;
|
|
tss=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
v=MathAbs(State.m_tmplayers.Get(i,nx+State.m_nlayers*ny+j));
|
|
rep.m_rmserror=rep.m_rmserror+v*v;
|
|
rep.m_avgerror=rep.m_avgerror+v;
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
rss=rss+v*v;
|
|
tss=tss+CMath::Sqr(State.m_xy[i*(nx+ny)+nx+j]-State.m_tmpmean[j]);
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/(npoints*ny));
|
|
rep.m_avgerror=rep.m_avgerror/(npoints*ny);
|
|
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
|
|
//--- Prepare internal buffer
|
|
IDWCreateCalcBuffer(model,model.m_buffer);
|
|
return;
|
|
}
|
|
//--- Unknown algorithm
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed,unexpected algorithm");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWAlloc(CSerializer &s,CIDWModel &model)
|
|
{
|
|
//--- Header
|
|
s.Alloc_Entry();
|
|
//--- Algorithm type and fields which are set for all algorithms
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,model.m_globalprior,-1);
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
//--- Algorithm-specific fields
|
|
bool processed=false;
|
|
if(model.m_algotype==0)
|
|
{
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,model.m_shepardxy,-1);
|
|
processed=true;
|
|
}
|
|
if(model.m_algotype>0)
|
|
{
|
|
CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
|
|
processed=true;
|
|
}
|
|
CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWSerialize(CSerializer &s,CIDWModel &model)
|
|
{
|
|
//--- Header
|
|
s.Serialize_Int(CSCodes::GetIDWSerializationCode());
|
|
//--- Algorithm type and fields which are set for all algorithms
|
|
s.Serialize_Int(model.m_algotype);
|
|
s.Serialize_Int(model.m_nx);
|
|
s.Serialize_Int(model.m_ny);
|
|
CApServ::SerializeRealArray(s,model.m_globalprior,-1);
|
|
s.Serialize_Int(model.m_nlayers);
|
|
s.Serialize_Double(model.m_r0);
|
|
s.Serialize_Double(model.m_rdecay);
|
|
s.Serialize_Double(model.m_lambda0);
|
|
s.Serialize_Double(model.m_lambdalast);
|
|
s.Serialize_Double(model.m_lambdadecay);
|
|
s.Serialize_Double(model.m_shepardp);
|
|
//--- Algorithm-specific fields
|
|
bool processed=false;
|
|
if(model.m_algotype==0)
|
|
{
|
|
s.Serialize_Int(model.m_npoints);
|
|
CApServ::SerializeRealArray(s,model.m_shepardxy,-1);
|
|
processed=true;
|
|
}
|
|
if(model.m_algotype>0)
|
|
{
|
|
CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
|
|
processed=true;
|
|
}
|
|
CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::IDWUnserialize(CSerializer &s,CIDWModel &model)
|
|
{
|
|
//--- Header
|
|
int scode=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(scode==CSCodes::GetIDWSerializationCode(),__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
//--- Algorithm type and fields which are set for all algorithms
|
|
model.m_algotype=s.Unserialize_Int();
|
|
model.m_nx=s.Unserialize_Int();
|
|
model.m_ny=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,model.m_globalprior);
|
|
model.m_nlayers=s.Unserialize_Int();
|
|
model.m_r0=s.Unserialize_Double();
|
|
model.m_rdecay=s.Unserialize_Double();
|
|
model.m_lambda0=s.Unserialize_Double();
|
|
model.m_lambdalast=s.Unserialize_Double();
|
|
model.m_lambdadecay=s.Unserialize_Double();
|
|
model.m_shepardp=s.Unserialize_Double();
|
|
//
|
|
//--- Algorithm-specific fields
|
|
//
|
|
bool processed=false;
|
|
if(model.m_algotype==0)
|
|
{
|
|
model.m_npoints=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,model.m_shepardxy);
|
|
processed=true;
|
|
}
|
|
if(model.m_algotype>0)
|
|
{
|
|
CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
|
|
processed=true;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(processed,__FUNCTION__+": integrity check failed during serialization"))
|
|
return;
|
|
//--- Temporary buffers
|
|
IDWCreateCalcBuffer(model,model.m_buffer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function evaluates error metrics for the model using |
|
|
//| IDWTsCalcBuf() to calculate model at each point. |
|
|
//| NOTE: modern IDW algorithms (MSTAB, MSMOOTH) can generate |
|
|
//| residuals during model construction, so they do not need this |
|
|
//| function in order to evaluate error metrics. |
|
|
//| Following fields of Rep are filled: |
|
|
//| * rep.m_rmserror |
|
|
//| * rep.m_avgerror |
|
|
//| * rep.m_maxerror |
|
|
//| * rep.m_r2 |
|
|
//+------------------------------------------------------------------+
|
|
void CIDWInt::ErrorMetricsViaCalc(CIDWBuilder &State,
|
|
CIDWModel &model,
|
|
CIDWReport &rep)
|
|
{
|
|
//--- create variables
|
|
int npoints=State.m_npoints;
|
|
int nx=State.m_nx;
|
|
int ny=State.m_ny;
|
|
double v=0;
|
|
double vv=0;
|
|
double rss=0;
|
|
double tss=0;
|
|
|
|
//--- quick exit
|
|
if(npoints==0)
|
|
{
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_r2=1;
|
|
return;
|
|
}
|
|
//--- initialization
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rss=0;
|
|
tss=0;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
model.m_buffer.m_x.Set(j,State.m_xy[i*(nx+ny)+j]);
|
|
//--- function call
|
|
IDWTsCalcBuf(model,model.m_buffer,model.m_buffer.m_x,model.m_buffer.m_y);
|
|
for(int j=0; j<ny; j++)
|
|
{
|
|
vv=State.m_xy[i*(nx+ny)+nx+j];
|
|
v=MathAbs(vv-model.m_buffer.m_y[j]);
|
|
rep.m_rmserror=rep.m_rmserror+v*v;
|
|
rep.m_avgerror=rep.m_avgerror+v;
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,v);
|
|
rss=rss+v*v;
|
|
tss=tss+CMath::Sqr(vv-State.m_tmpmean[j]);
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/(npoints*ny));
|
|
rep.m_avgerror=rep.m_avgerror/(npoints*ny);
|
|
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Barycentric interpolant. |
|
|
//+------------------------------------------------------------------+
|
|
class CBarycentricInterpolant
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_n;
|
|
double m_sy;
|
|
//--- arrays
|
|
double m_x[];
|
|
double m_y[];
|
|
double m_w[];
|
|
//--- constructor, destructor
|
|
CBarycentricInterpolant(void) { ZeroMemory(this); }
|
|
~CBarycentricInterpolant(void) {}
|
|
//--- copy
|
|
void Copy(CBarycentricInterpolant &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricInterpolant::Copy(CBarycentricInterpolant &obj)
|
|
{
|
|
//--- copy variables
|
|
m_n=obj.m_n;
|
|
m_sy=obj.m_sy;
|
|
//--- copy arrays
|
|
ArrayCopy(m_x,obj.m_x);
|
|
ArrayCopy(m_y,obj.m_y);
|
|
ArrayCopy(m_w,obj.m_w);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Barycentric interpolant. |
|
|
//+------------------------------------------------------------------+
|
|
class CBarycentricInterpolantShell
|
|
{
|
|
private:
|
|
CBarycentricInterpolant m_innerobj;
|
|
|
|
public:
|
|
//--- constructor, destructor
|
|
CBarycentricInterpolantShell(void) {}
|
|
CBarycentricInterpolantShell(CBarycentricInterpolant &obj) { m_innerobj.Copy(obj); }
|
|
~CBarycentricInterpolantShell(void) {}
|
|
//--- method
|
|
CBarycentricInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Rational interpolation |
|
|
//+------------------------------------------------------------------+
|
|
class CRatInt
|
|
{
|
|
public:
|
|
static double BarycentricCalc(CBarycentricInterpolant &b,const double t);
|
|
static void BarycentricDiff1(CBarycentricInterpolant &b,double t,double &f,double &df);
|
|
static void BarycentricDiff2(CBarycentricInterpolant &b,const double t,double &f,double &df,double &d2f);
|
|
static void BarycentricLinTransX(CBarycentricInterpolant &b,const double ca,const double cb);
|
|
static void BarycentricLinTransY(CBarycentricInterpolant &b,const double ca,const double cb);
|
|
static void BarycentricUnpack(CBarycentricInterpolant &b,int &n,double &x[],double &y[],double &w[]);
|
|
static void BarycentricBuildXYW(double &x[],double &y[],double &w[],const int n,CBarycentricInterpolant &b);
|
|
static void BarycentricBuildFloaterHormann(double &x[],double &y[],const int n,int d,CBarycentricInterpolant &b);
|
|
static void BarycentricCopy(CBarycentricInterpolant &b,CBarycentricInterpolant &b2);
|
|
|
|
private:
|
|
static void BarycentricNormalize(CBarycentricInterpolant &b);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Rational interpolation using barycentric formula |
|
|
//| F(t)=SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))|
|
|
//| Input parameters: |
|
|
//| B - barycentric interpolant built with one of model |
|
|
//| building subroutines. |
|
|
//| T - interpolation point |
|
|
//| Result: |
|
|
//| barycentric interpolant F(t) |
|
|
//+------------------------------------------------------------------+
|
|
double CRatInt::BarycentricCalc(CBarycentricInterpolant &b,const double t)
|
|
{
|
|
//--- create variables
|
|
double s1=0;
|
|
double s2=0;
|
|
double s=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
|
|
return(EMPTY_VALUE);
|
|
//--- special case: NaN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
return(CInfOrNaN::NaN());
|
|
//--- special case: N=1
|
|
if(b.m_n==1)
|
|
return(b.m_sy*b.m_y[0]);
|
|
//--- Here we assume that task is normalized,i.m_e.:
|
|
//--- 1. abs(Y[i])<=1
|
|
//--- 2. abs(W[i])<=1
|
|
//--- 3. X[] is ordered
|
|
s=MathAbs(t-b.m_x[0]);
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
v=b.m_x[i];
|
|
//--- check
|
|
if(v==(double)(t))
|
|
return(b.m_sy*b.m_y[i]);
|
|
v=MathAbs(t-v);
|
|
//--- check
|
|
if(v<s)
|
|
s=v;
|
|
}
|
|
//--- change values
|
|
s1=0;
|
|
s2=0;
|
|
//--- calculation
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
v=s/(t-b.m_x[i]);
|
|
v=v*b.m_w[i];
|
|
s1=s1+v*b.m_y[i];
|
|
s2=s2+v;
|
|
}
|
|
//--- return result
|
|
return(b.m_sy*s1/s2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Differentiation of barycentric interpolant: first derivative. |
|
|
//| Algorithm used in this subroutine is very robust and should not |
|
|
//| fail until provided with values too close to MaxRealNumber |
|
|
//| (usually MaxRealNumber/N or greater will overflow). |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - barycentric interpolant built with one of model |
|
|
//| building subroutines. |
|
|
//| T - interpolation point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - barycentric interpolant at T |
|
|
//| DF - first derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricDiff1(CBarycentricInterpolant &b,double t,
|
|
double &f,double &df)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double vv=0;
|
|
int i=0;
|
|
int k=0;
|
|
double n0=0;
|
|
double n1=0;
|
|
double d0=0;
|
|
double d1=0;
|
|
double s0=0;
|
|
double s1=0;
|
|
double xk=0;
|
|
double xi=0;
|
|
double xmin=0;
|
|
double xmax=0;
|
|
double xscale1=0;
|
|
double xoffs1=0;
|
|
double xscale2=0;
|
|
double xoffs2=0;
|
|
double xprev=0;
|
|
//--- initialization
|
|
f=0;
|
|
df=0;
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
|
|
return;
|
|
//--- special case: NaN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
{
|
|
//--- change values
|
|
f=CInfOrNaN::NaN();
|
|
df=CInfOrNaN::NaN();
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- special case: N=1
|
|
if(b.m_n==1)
|
|
{
|
|
//--- change values
|
|
f=b.m_sy*b.m_y[0];
|
|
df=0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if(b.m_sy==0.0)
|
|
{
|
|
//--- change values
|
|
f=0;
|
|
df=0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(b.m_sy>0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- We assume than N>1 and B.SY>0. Find:
|
|
//--- 1. pivot point (X[i] closest to T)
|
|
//--- 2. width of interval containing X[i]
|
|
v=MathAbs(b.m_x[0]-t);
|
|
k=0;
|
|
xmin=b.m_x[0];
|
|
xmax=b.m_x[0];
|
|
//--- calculation
|
|
for(i=1; i<=b.m_n-1; i++)
|
|
{
|
|
vv=b.m_x[i];
|
|
//--- check
|
|
if(MathAbs(vv-t)<v)
|
|
{
|
|
v=MathAbs(vv-t);
|
|
k=i;
|
|
}
|
|
//--- change values
|
|
xmin=MathMin(xmin,vv);
|
|
xmax=MathMax(xmax,vv);
|
|
}
|
|
//--- pivot point found,calculate dNumerator and dDenominator
|
|
xscale1=1/(xmax-xmin);
|
|
xoffs1=-(xmin/(xmax-xmin))+1;
|
|
xscale2=2;
|
|
xoffs2=-3;
|
|
t=t*xscale1+xoffs1;
|
|
t=t*xscale2+xoffs2;
|
|
xk=b.m_x[k];
|
|
xk=xk*xscale1+xoffs1;
|
|
xk=xk*xscale2+xoffs2;
|
|
v=t-xk;
|
|
n0=0;
|
|
n1=0;
|
|
d0=0;
|
|
d1=0;
|
|
xprev=-2;
|
|
//--- calculation
|
|
for(i=0; i<b.m_n; i++)
|
|
{
|
|
//--- change values
|
|
xi=b.m_x[i];
|
|
xi=xi*xscale1+xoffs1;
|
|
xi=xi*xscale2+xoffs2;
|
|
//--- check
|
|
if(!CAp::Assert(xi>xprev,__FUNCTION__+": points are too close!"))
|
|
return;
|
|
xprev=xi;
|
|
//--- check
|
|
if(i!=k)
|
|
{
|
|
vv=CMath::Sqr(t-xi);
|
|
s0=(t-xk)/(t-xi);
|
|
s1=(xk-xi)/vv;
|
|
}
|
|
else
|
|
{
|
|
s0=1;
|
|
s1=0;
|
|
}
|
|
//--- change values
|
|
vv=b.m_w[i]*b.m_y[i];
|
|
n0=n0+s0*vv;
|
|
n1=n1+s1*vv;
|
|
vv=b.m_w[i];
|
|
d0=d0+s0*vv;
|
|
d1=d1+s1*vv;
|
|
}
|
|
//--- change values
|
|
f=b.m_sy*n0/d0;
|
|
df=(n1*d0-n0*d1)/CMath::Sqr(d0);
|
|
//--- check
|
|
if(df!=0.0)
|
|
df=MathSign(df)*MathExp(MathLog(MathAbs(df))+MathLog(b.m_sy)+MathLog(xscale1)+MathLog(xscale2));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Differentiation of barycentric interpolant: first/second |
|
|
//| derivatives. |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - barycentric interpolant built with one of model |
|
|
//| building subroutines. |
|
|
//| T - interpolation point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - barycentric interpolant at T |
|
|
//| DF - first derivative |
|
|
//| D2F - second derivative |
|
|
//| NOTE: this algorithm may fail due to overflow/underflor if used |
|
|
//| on data whose values are close to MaxRealNumber or MinRealNumber.|
|
|
//| Use more robust BarycentricDiff1() subroutine in such cases. |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricDiff2(CBarycentricInterpolant &b,const double t,
|
|
double &f,double &df,double &d2f)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double vv=0;
|
|
int k=0;
|
|
double n0=0;
|
|
double n1=0;
|
|
double n2=0;
|
|
double d0=0;
|
|
double d1=0;
|
|
double d2=0;
|
|
double s0=0;
|
|
double s1=0;
|
|
double s2=0;
|
|
double xk=0;
|
|
double xi=0;
|
|
//--- initialization
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": infinite T!"))
|
|
return;
|
|
//--- special case: NaN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
{
|
|
//--- change values
|
|
f=CInfOrNaN::NaN();
|
|
df=CInfOrNaN::NaN();
|
|
d2f=CInfOrNaN::NaN();
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- special case: N=1
|
|
if(b.m_n==1)
|
|
{
|
|
//--- change values
|
|
f=b.m_sy*b.m_y[0];
|
|
df=0;
|
|
d2f=0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if(b.m_sy==0.0)
|
|
{
|
|
//--- change values
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- We assume than N>1 and B.SY>0. Find:
|
|
//--- 1. pivot point (X[i] closest to T)
|
|
//--- 2. width of interval containing X[i]
|
|
if(!CAp::Assert(b.m_sy>0.0,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- change values
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
v=MathAbs(b.m_x[0]-t);
|
|
k=0;
|
|
for(int i=1; i<=b.m_n-1; i++)
|
|
{
|
|
vv=b.m_x[i];
|
|
//--- check
|
|
if(MathAbs(vv-t)<v)
|
|
{
|
|
v=MathAbs(vv-t);
|
|
k=i;
|
|
}
|
|
}
|
|
//--- pivot point found, calculate dNumerator and dDenominator
|
|
xk=b.m_x[k];
|
|
v=t-xk;
|
|
n0=0;
|
|
n1=0;
|
|
n2=0;
|
|
d0=0;
|
|
d1=0;
|
|
d2=0;
|
|
//--- calculation
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
//--- check
|
|
if(i!=k)
|
|
{
|
|
xi=b.m_x[i];
|
|
vv=CMath::Sqr(t-xi);
|
|
s0=(t-xk)/(t-xi);
|
|
s1=(xk-xi)/vv;
|
|
s2=-(2*(xk-xi)/(vv*(t-xi)));
|
|
}
|
|
else
|
|
{
|
|
s0=1;
|
|
s1=0;
|
|
s2=0;
|
|
}
|
|
//--- change values
|
|
vv=b.m_w[i]*b.m_y[i];
|
|
n0=n0+s0*vv;
|
|
n1=n1+s1*vv;
|
|
n2=n2+s2*vv;
|
|
vv=b.m_w[i];
|
|
d0=d0+s0*vv;
|
|
d1=d1+s1*vv;
|
|
d2=d2+s2*vv;
|
|
}
|
|
//--- change values
|
|
f=b.m_sy*n0/d0;
|
|
df=b.m_sy*(n1*d0-n0*d1)/CMath::Sqr(d0);
|
|
d2f=b.m_sy*((n2*d0-n0*d2)*CMath::Sqr(d0)-(n1*d0-n0*d1)*2*d0*d1)/CMath::Sqr(CMath::Sqr(d0));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the argument. |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - rational interpolant in barycentric form |
|
|
//| CA, CB - transformation coefficients: x = CA*t + CB |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B - transformed interpolant with X replaced by T |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricLinTransX(CBarycentricInterpolant &b,
|
|
const double ca,const double cb)
|
|
{
|
|
double v=0;
|
|
//--- special case,replace by constant F(CB)
|
|
if(ca==0.0)
|
|
{
|
|
b.m_sy=BarycentricCalc(b,cb);
|
|
v=1;
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
b.m_y[i]=1;
|
|
b.m_w[i]=v;
|
|
v=-v;
|
|
}
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- general case: CA<>0
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
b.m_x[i]=(b.m_x[i]-cb)/ca;
|
|
//--- check
|
|
if(ca<0.0)
|
|
{
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
//--- check
|
|
if(i<b.m_n-1-i)
|
|
{
|
|
//--- change values
|
|
int j=b.m_n-1-i;
|
|
v=b.m_x[i];
|
|
b.m_x[i]=b.m_x[j];
|
|
b.m_x[j]=v;
|
|
v=b.m_y[i];
|
|
b.m_y[i]=b.m_y[j];
|
|
b.m_y[j]=v;
|
|
v=b.m_w[i];
|
|
b.m_w[i]=b.m_w[j];
|
|
b.m_w[j]=v;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the barycentric|
|
|
//| interpolant. |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - rational interpolant in barycentric form |
|
|
//| CA, CB - transformation coefficients: B2(x) = CA*B(x) + CB|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B - transformed interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricLinTransY(CBarycentricInterpolant &b,
|
|
const double ca,const double cb)
|
|
{
|
|
double v=0;
|
|
//--- calculation
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
b.m_y[i]=ca*b.m_sy*b.m_y[i]+cb;
|
|
//--- change value
|
|
b.m_sy=0;
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
b.m_sy=MathMax(b.m_sy,MathAbs(b.m_y[i]));
|
|
//--- check
|
|
if(b.m_sy>0.0)
|
|
{
|
|
v=1/b.m_sy;
|
|
//--- calculation
|
|
for(int i_=0; i_<=b.m_n-1; i_++)
|
|
b.m_y[i_]=v*b.m_y[i_];
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Extracts X/Y/W arrays from rational interpolant |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - barycentric interpolant |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| N - nodes count, N>0 |
|
|
//| X - interpolation nodes, array[0..N-1] |
|
|
//| F - function values, array[0..N-1] |
|
|
//| W - barycentric weights, array[0..N-1] |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricUnpack(CBarycentricInterpolant &b,int &n,
|
|
double &x[],double &y[],double &w[])
|
|
{
|
|
double v=0;
|
|
//--- initialization
|
|
n=b.m_n;
|
|
//--- allocation
|
|
ArrayResize(x,n);
|
|
ArrayResize(y,n);
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
v=b.m_sy;
|
|
//--- copy
|
|
for(int i_=0; i_<n; i_++)
|
|
x[i_]=b.m_x[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
y[i_]=v*b.m_y[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
w[i_]=b.m_w[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Rational interpolant from X/Y/W arrays |
|
|
//| F(t)=SUM(i=0,n-1,w[i]*f[i]/(t-x[i])) / SUM(i=0,n-1,w[i]/(t-x[i]))|
|
|
//| INPUT PARAMETERS: |
|
|
//| X - interpolation nodes, array[0..N-1] |
|
|
//| F - function values, array[0..N-1] |
|
|
//| W - barycentric weights, array[0..N-1] |
|
|
//| N - nodes count, N>0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B - barycentric interpolant built from (X, Y, W) |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricBuildXYW(double &x[],double &y[],double &w[],
|
|
const int n,CBarycentricInterpolant &b)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": incorrect N!"))
|
|
return;
|
|
//--- fill X/Y/W
|
|
ArrayResize(b.m_x,n);
|
|
ArrayResize(b.m_y,n);
|
|
ArrayResize(b.m_w,n);
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
b.m_x[i]=x[i];
|
|
b.m_y[i]=y[i];
|
|
b.m_w[i]=w[i];
|
|
}
|
|
b.m_n=n;
|
|
//--- Normalize
|
|
BarycentricNormalize(b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Rational interpolant without poles |
|
|
//| The subroutine constructs the rational interpolating function |
|
|
//| without real poles (see 'Barycentric rational interpolation with |
|
|
//| no poles and high rates of approximation', Michael S. Floater. |
|
|
//| and Kai Hormann, for more information on this subject). |
|
|
//| Input parameters: |
|
|
//| X - interpolation nodes, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| N - number of nodes, N>0. |
|
|
//| D - order of the interpolation scheme, 0 <= D <= N-1. |
|
|
//| D<0 will cause an error. |
|
|
//| D>=N it will be replaced with D=N-1. |
|
|
//| if you don't know what D to choose, use small value |
|
|
//| about 3-5. |
|
|
//| Output parameters: |
|
|
//| B - barycentric interpolant. |
|
|
//| Note: |
|
|
//| this algorithm always succeeds and calculates the weights |
|
|
//| with close to machine precision. |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricBuildFloaterHormann(double &x[],double &y[],
|
|
const int n,int d,
|
|
CBarycentricInterpolant &b)
|
|
{
|
|
//--- create variables
|
|
double s0=0;
|
|
double s=0;
|
|
double v=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
int perm[];
|
|
double wtemp[];
|
|
double sortrbuf[];
|
|
double sortrbuf2[];
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(d>=0,__FUNCTION__+": incorrect D!"))
|
|
return;
|
|
//--- Prepare
|
|
if(d>n-1)
|
|
d=n-1;
|
|
b.m_n=n;
|
|
//--- special case: N=1
|
|
if(n==1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(b.m_x,n);
|
|
ArrayResize(b.m_y,n);
|
|
ArrayResize(b.m_w,n);
|
|
//--- change values
|
|
b.m_x[0]=x[0];
|
|
b.m_y[0]=y[0];
|
|
b.m_w[0]=1;
|
|
//--- function call
|
|
BarycentricNormalize(b);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- Fill X/Y
|
|
ArrayResize(b.m_x,n);
|
|
ArrayResize(b.m_y,n);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
b.m_x[i_]=x[i_];
|
|
for(i_=0; i_<n; i_++)
|
|
b.m_y[i_]=y[i_];
|
|
//--- function call
|
|
CTSort::TagSortFastR(b.m_x,b.m_y,sortrbuf,sortrbuf2,n);
|
|
//--- Calculate Wk
|
|
ArrayResize(b.m_w,n);
|
|
s0=1;
|
|
for(k=1; k<=d; k++)
|
|
s0=-s0;
|
|
//--- calculation
|
|
for(k=0; k<n; k++)
|
|
{
|
|
//--- Wk
|
|
s=0;
|
|
for(i=(int)(MathMax(k-d,0)); i<=MathMin(k,n-1-d); i++)
|
|
{
|
|
v=1;
|
|
for(j=i; j<=i+d; j++)
|
|
{
|
|
//--- check
|
|
if(j!=k)
|
|
v=v/MathAbs(b.m_x[k]-b.m_x[j]);
|
|
}
|
|
s=s+v;
|
|
}
|
|
b.m_w[k]=s0*s;
|
|
//--- Next S0
|
|
s0=-s0;
|
|
}
|
|
//--- Normalize
|
|
BarycentricNormalize(b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copying of the barycentric interpolant (for internal use only) |
|
|
//| INPUT PARAMETERS: |
|
|
//| B - barycentric interpolant |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B2 - copy(B1) |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricCopy(CBarycentricInterpolant &b,
|
|
CBarycentricInterpolant &b2)
|
|
{
|
|
b2.Copy(b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Normalization of barycentric interpolant: |
|
|
//| * B.N, B.X, B.Y and B.W are initialized |
|
|
//| * B.SY is NOT initialized |
|
|
//| * Y[] is normalized, scaling coefficient is stored in B.SY |
|
|
//| * W[] is normalized, no scaling coefficient is stored |
|
|
//| * X[] is sorted |
|
|
//| Internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
void CRatInt::BarycentricNormalize(CBarycentricInterpolant &b)
|
|
{
|
|
//--- create variables
|
|
int j2=0;
|
|
double v=0;
|
|
//--- create arrays
|
|
int p1[];
|
|
int p2[];
|
|
//--- Normalize task: |Y|<=1,|W|<=1,sort X[]
|
|
b.m_sy=0;
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
b.m_sy=MathMax(b.m_sy,MathAbs(b.m_y[i]));
|
|
//--- check
|
|
if(b.m_sy>0.0 && MathAbs(b.m_sy-1)>10*CMath::m_machineepsilon)
|
|
{
|
|
v=1/b.m_sy;
|
|
for(int i_=0; i_<=b.m_n-1; i_++)
|
|
b.m_y[i_]=v*b.m_y[i_];
|
|
}
|
|
//--- change value
|
|
v=0;
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
v=MathMax(v,MathAbs(b.m_w[i]));
|
|
//--- check
|
|
if(v>0.0 && MathAbs(v-1)>10*CMath::m_machineepsilon)
|
|
{
|
|
v=1/v;
|
|
for(int i_=0; i_<=b.m_n-1; i_++)
|
|
b.m_w[i_]=v*b.m_w[i_];
|
|
}
|
|
for(int i=0; i<=b.m_n-2; i++)
|
|
{
|
|
//--- check
|
|
if(b.m_x[i+1]<b.m_x[i])
|
|
{
|
|
//--- function call
|
|
CTSort::TagSort(b.m_x,b.m_n,p1,p2);
|
|
//--- calculation
|
|
for(int j=0; j<=b.m_n-1; j++)
|
|
{
|
|
j2=p2[j];
|
|
v=b.m_y[j];
|
|
b.m_y[j]=b.m_y[j2];
|
|
b.m_y[j2]=v;
|
|
v=b.m_w[j];
|
|
b.m_w[j]=b.m_w[j2];
|
|
b.m_w[j2]=v;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Polynomial interpolant |
|
|
//+------------------------------------------------------------------+
|
|
class CPolInt
|
|
{
|
|
public:
|
|
static void PolynomialBar2Cheb(CBarycentricInterpolant &p,const double a,const double b,double &t[]);
|
|
static void PolynomialCheb2Bar(double &t[],const int n,const double a,const double b,CBarycentricInterpolant &p);
|
|
static void PolynomialBar2Pow(CBarycentricInterpolant &p,const double c,const double s,double &a[]);
|
|
static void PolynomialPow2Bar(double &a[],const int n,const double c,const double s,CBarycentricInterpolant &p);
|
|
static void PolynomialBuild(double &cx[],double &cy[],const int n,CBarycentricInterpolant &p);
|
|
static void PolynomialBuildEqDist(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
|
|
static void PolynomialBuildCheb1(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
|
|
static void PolynomialBuildCheb2(const double a,const double b,double &y[],const int n,CBarycentricInterpolant &p);
|
|
static double PolynomialCalcEqDist(const double a,const double b,double &f[],const int n,const double t);
|
|
static double PolynomialCalcCheb1(const double a,const double b,double &f[],const int n,double t);
|
|
static double PolynomialCalcCheb2(const double a,const double b,double &f[],const int n,double t);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Conversion from barycentric representation to Chebyshev basis. |
|
|
//| This function has O(N^2) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - polynomial in barycentric form |
|
|
//| A,B - base interval for Chebyshev polynomials (see below) |
|
|
//| A<>B |
|
|
//| OUTPUT PARAMETERS |
|
|
//| T - coefficients of Chebyshev representation; |
|
|
//| P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N-1 }, |
|
|
//| where Ti - I-th Chebyshev polynomial. |
|
|
//| NOTES: |
|
|
//| barycentric interpolant passed as P may be either polynomial |
|
|
//| obtained from polynomial interpolation/ fitting or rational |
|
|
//| function which is NOT polynomial. We can't distinguish |
|
|
//| between these two cases, and this algorithm just tries to |
|
|
//| work assuming that P IS a polynomial. If not, algorithm will |
|
|
//| return results, but they won't have any meaning. |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBar2Cheb(CBarycentricInterpolant &p,
|
|
const double a,const double b,
|
|
double &t[])
|
|
{
|
|
double v=0;
|
|
//--- create arrays
|
|
double vp[];
|
|
double vx[];
|
|
double tk[];
|
|
double tk1[];
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(a!=b,__FUNCTION__+": A=B!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(p.m_n>0,__FUNCTION__+": P is not correctly initialized barycentric interpolant!"))
|
|
return;
|
|
//--- Calculate function values on a Chebyshev grid
|
|
ArrayResize(vp,p.m_n);
|
|
ArrayResize(vx,p.m_n);
|
|
for(int i=0; i<=p.m_n-1; i++)
|
|
{
|
|
vx[i]=MathCos(M_PI*(i+0.5)/p.m_n);
|
|
vp[i]=CRatInt::BarycentricCalc(p,0.5*(vx[i]+1)*(b-a)+a);
|
|
}
|
|
//--- T[0]
|
|
ArrayResize(t,p.m_n);
|
|
v=0;
|
|
for(int i=0; i<=p.m_n-1; i++)
|
|
v=v+vp[i];
|
|
t[0]=v/p.m_n;
|
|
//--- other T's.
|
|
//--- NOTES:
|
|
//--- 1. TK stores T{k} on VX,TK1 stores T{k-1} on VX
|
|
//--- 2. we can do same calculations with fast DCT,but it
|
|
//--- * adds dependencies
|
|
//--- * still leaves us with O(N^2) algorithm because
|
|
//--- preparation of function values is O(N^2) process
|
|
if(p.m_n>1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(tk,p.m_n);
|
|
ArrayResize(tk1,p.m_n);
|
|
for(int i=0; i<=p.m_n-1; i++)
|
|
{
|
|
tk[i]=vx[i];
|
|
tk1[i]=1;
|
|
}
|
|
//--- calculation
|
|
for(int k=1; k<=p.m_n-1; k++)
|
|
{
|
|
//--- calculate discrete product of function vector and TK
|
|
v=0.0;
|
|
for(int i_=0; i_<=p.m_n-1; i_++)
|
|
v+=tk[i_]*vp[i_];
|
|
t[k]=v/(0.5*p.m_n);
|
|
//--- Update TK and TK1
|
|
for(int i=0; i<=p.m_n-1; i++)
|
|
{
|
|
v=2*vx[i]*tk[i]-tk1[i];
|
|
tk1[i]=tk[i];
|
|
tk[i]=v;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conversion from Chebyshev basis to barycentric representation. |
|
|
//| This function has O(N^2) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| T - coefficients of Chebyshev representation; |
|
|
//| P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N }, |
|
|
//| where Ti - I-th Chebyshev polynomial. |
|
|
//| N - number of coefficients: |
|
|
//| * if given, only leading N elements of T are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of T |
|
|
//| A,B - base interval for Chebyshev polynomials (see above) |
|
|
//| A<B |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - polynomial in barycentric form |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialCheb2Bar(double &t[],const int n,const double a,
|
|
const double b,CBarycentricInterpolant &p)
|
|
{
|
|
//--- create variables
|
|
double tk=0;
|
|
double tk1=0;
|
|
double vx=0;
|
|
double vy=0;
|
|
double v=0;
|
|
//--- create array
|
|
double y[];
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(a!=b,__FUNCTION__+": A=B!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(t)>=n,__FUNCTION__+": Length(T)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(t,n),__FUNCTION__+": T[] contains INF or NAN"))
|
|
return;
|
|
//--- Calculate function values on a Chebyshev grid spanning [-1,+1]
|
|
ArrayResize(y,n);
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Calculate value on a grid spanning [-1,+1]
|
|
vx=MathCos(M_PI*(i+0.5)/n);
|
|
vy=t[0];
|
|
tk1=1;
|
|
tk=vx;
|
|
//--- change values
|
|
for(int k=1; k<n; k++)
|
|
{
|
|
vy=vy+t[k]*tk;
|
|
v=2*vx*tk-tk1;
|
|
tk1=tk;
|
|
tk=v;
|
|
}
|
|
y[i]=vy;
|
|
}
|
|
//--- Build barycentric interpolant,map grid from [-1,+1] to [A,B]
|
|
PolynomialBuildCheb1(a,b,y,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conversion from barycentric representation to power basis. |
|
|
//| This function has O(N^2) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - polynomial in barycentric form |
|
|
//| C - offset (see below); 0.0 is used as default value. |
|
|
//| S - scale (see below); 1.0 is used as default value. |
|
|
//| S<>0. |
|
|
//| OUTPUT PARAMETERS |
|
|
//| A - coefficients, |
|
|
//| P(x) = sum { A[i]*((X-C)/S)^i, i=0..N-1 } |
|
|
//| N - number of coefficients (polynomial degree plus 1) |
|
|
//| NOTES: |
|
|
//| 1. this function accepts offset and scale, which can be set to |
|
|
//| improve numerical properties of polynomial. For example, if |
|
|
//| P was obtained as result of interpolation on [-1,+1], you can|
|
|
//| set C=0 and S=1 and represent P as sum of 1, x, x^2, x^3 and |
|
|
//| so on. In most cases you it is exactly what you need. |
|
|
//| However, if your interpolation model was built on [999,1001],|
|
|
//| you will see significant growth of numerical errors when |
|
|
//| using {1, x, x^2, x^3} as basis. Representing P as sum of 1, |
|
|
//| (x-1000), (x-1000)^2, (x-1000)^3 will be better option. Such |
|
|
//| representation can be obtained by using 1000.0 as offset |
|
|
//| C and 1.0 as scale S. |
|
|
//| 2. power basis is ill-conditioned and tricks described above |
|
|
//| can't solve this problem completely. This function will |
|
|
//| return coefficients in any case, but for N>8 they will become|
|
|
//| unreliable. However, N's less than 5 are pretty safe. |
|
|
//| 3. barycentric interpolant passed as P may be either polynomial |
|
|
//| obtained from polynomial interpolation/ fitting or rational |
|
|
//| function which is NOT polynomial. We can't distinguish |
|
|
//| between these two cases, and this algorithm just tries to |
|
|
//| work assuming that P IS a polynomial. If not, algorithm will |
|
|
//| return results, but they won't have any meaning. |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBar2Pow(CBarycentricInterpolant &p,
|
|
const double c,const double s,
|
|
double &a[])
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int k=0;
|
|
double e=0;
|
|
double d=0;
|
|
double v=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double vp[];
|
|
double vx[];
|
|
double tk[];
|
|
double tk1[];
|
|
double t[];
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(c),__FUNCTION__+": C is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s),__FUNCTION__+": S is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s!=0.0,__FUNCTION__+": S=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(p.m_n>0,__FUNCTION__+": P is not correctly initialized barycentric interpolant!"))
|
|
return;
|
|
//--- Calculate function values on a Chebyshev grid
|
|
ArrayResize(vp,p.m_n);
|
|
ArrayResize(vx,p.m_n);
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
{
|
|
vx[i]=MathCos(M_PI*(i+0.5)/p.m_n);
|
|
vp[i]=CRatInt::BarycentricCalc(p,s*vx[i]+c);
|
|
}
|
|
//--- T[0]
|
|
ArrayResize(t,p.m_n);
|
|
v=0;
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
v=v+vp[i];
|
|
t[0]=v/p.m_n;
|
|
//--- other T's.
|
|
//--- NOTES:
|
|
//--- 1. TK stores T{k} on VX,TK1 stores T{k-1} on VX
|
|
//--- 2. we can do same calculations with fast DCT,but it
|
|
//--- * adds dependencies
|
|
//--- * still leaves us with O(N^2) algorithm because
|
|
//--- preparation of function values is O(N^2) process
|
|
if(p.m_n>1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(tk,p.m_n);
|
|
ArrayResize(tk1,p.m_n);
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
{
|
|
tk[i]=vx[i];
|
|
tk1[i]=1;
|
|
}
|
|
//--- calculation
|
|
for(k=1; k<=p.m_n-1; k++)
|
|
{
|
|
//--- calculate discrete product of function vector and TK
|
|
v=0.0;
|
|
for(i_=0; i_<=p.m_n-1; i_++)
|
|
v+=tk[i_]*vp[i_];
|
|
t[k]=v/(0.5*p.m_n);
|
|
//--- Update TK and TK1
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
{
|
|
v=2*vx[i]*tk[i]-tk1[i];
|
|
tk1[i]=tk[i];
|
|
tk[i]=v;
|
|
}
|
|
}
|
|
}
|
|
//--- Convert from Chebyshev basis to power basis
|
|
ArrayResize(a,p.m_n);
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
a[i]=0;
|
|
d=0;
|
|
//--- calculation
|
|
for(i=0; i<=p.m_n-1; i++)
|
|
{
|
|
for(k=i; k<=p.m_n-1; k++)
|
|
{
|
|
e=a[k];
|
|
a[k]=0;
|
|
//--- check
|
|
if(i<=1 && k==i)
|
|
a[k]=1;
|
|
else
|
|
{
|
|
//--- check
|
|
if(i!=0)
|
|
a[k]=2*d;
|
|
//--- check
|
|
if(k>i+1)
|
|
a[k]=a[k]-a[k-2];
|
|
}
|
|
d=e;
|
|
}
|
|
//--- change values
|
|
d=a[i];
|
|
e=0;
|
|
k=i;
|
|
//--- cycle
|
|
while(k<=p.m_n-1)
|
|
{
|
|
e=e+a[k]*t[k];
|
|
k=k+2;
|
|
}
|
|
a[i]=e;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Conversion from power basis to barycentric representation. |
|
|
//| This function has O(N^2) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - coefficients, P(x)=sum { A[i]*((X-C)/S)^i, i=0..N-1 }|
|
|
//| N - number of coefficients (polynomial degree plus 1) |
|
|
//| * if given, only leading N elements of A are used |
|
|
//| * if not given, automatically determined from size |
|
|
//| of A |
|
|
//| C - offset (see below); 0.0 is used as default value. |
|
|
//| S - scale (see below); 1.0 is used as default value. |
|
|
//| S<>0. |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - polynomial in barycentric form |
|
|
//| NOTES: |
|
|
//| 1. this function accepts offset and scale, which can be set to |
|
|
//| improve numerical properties of polynomial. For example, if |
|
|
//| you interpolate on [-1,+1], you can set C=0 and S=1 and |
|
|
//| convert from sum of 1, x, x^2, x^3 and so on. In most cases |
|
|
//| you it is exactly what you need. |
|
|
//| However, if your interpolation model was built on [999,1001],|
|
|
//| you will see significant growth of numerical errors when |
|
|
//| using {1, x, x^2, x^3} as input basis. Converting from sum |
|
|
//| of 1, (x-1000), (x-1000)^2, (x-1000)^3 will be better option |
|
|
//| (you have to specify 1000.0 as offset C and 1.0 as scale S). |
|
|
//| 2. power basis is ill-conditioned and tricks described above |
|
|
//| can't solve this problem completely. This function will |
|
|
//| return barycentric model in any case, but for N>8 accuracy |
|
|
//| well degrade. However, N's less than 5 are pretty safe. |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialPow2Bar(double &a[],const int n,const double c,
|
|
const double s,CBarycentricInterpolant &p)
|
|
{
|
|
//--- create variables
|
|
double vx=0;
|
|
double vy=0;
|
|
double px=0;
|
|
//--- create array
|
|
double y[];
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(c),__FUNCTION__+": C is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s),__FUNCTION__+": S is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s!=0.0,__FUNCTION__+": S is zero!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(a)>=n,__FUNCTION__+": Length(A)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(a,n),__FUNCTION__+": A[] contains INF or NAN"))
|
|
return;
|
|
//--- Calculate function values on a Chebyshev grid spanning [-1,+1]
|
|
ArrayResize(y,n);
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Calculate value on a grid spanning [-1,+1]
|
|
vx=MathCos(M_PI*(i+0.5)/n);
|
|
vy=a[0];
|
|
px=vx;
|
|
//--- calculation
|
|
for(int k=1; k<n; k++)
|
|
{
|
|
vy=vy+px*a[k];
|
|
px=px*vx;
|
|
}
|
|
y[i]=vy;
|
|
}
|
|
//--- Build barycentric interpolant,map grid from [-1,+1] to [A,B]
|
|
PolynomialBuildCheb1(c-s,c+s,y,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Lagrange intepolant: generation of the model on the general grid.|
|
|
//| This function has O(N^2) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - abscissas, array[0..N-1] |
|
|
//| Y - function values, array[0..N-1] |
|
|
//| N - number of points, N>=1 |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - barycentric model which represents Lagrange |
|
|
//| interpolant (see ratint unit info and |
|
|
//| BarycentricCalc() description for more information). |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBuild(double &cx[],double &cy[],const int n,
|
|
CBarycentricInterpolant &p)
|
|
{
|
|
//--- create variables
|
|
double b=0;
|
|
double a=0;
|
|
double v=0;
|
|
double mx=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double w[];
|
|
double sortrbuf[];
|
|
double sortrbuf2[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
CTSort::TagSortFastR(x,y,sortrbuf,sortrbuf2,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- calculate W[j]
|
|
//--- multi-pass algorithm is used to avoid overflow
|
|
ArrayResize(w,n);
|
|
a=x[0];
|
|
b=x[0];
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
w[j]=1;
|
|
a=MathMin(a,x[j]);
|
|
b=MathMax(b,x[j]);
|
|
}
|
|
//--- calculation
|
|
for(int k=0; k<n; k++)
|
|
{
|
|
//--- W[K] is used instead of 0.0 because
|
|
//--- cycle on J does not touch K-th element
|
|
//--- and we MUST get maximum from ALL elements
|
|
mx=MathAbs(w[k]);
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- check
|
|
if(j!=k)
|
|
{
|
|
v=(b-a)/(x[j]-x[k]);
|
|
w[j]=w[j]*v;
|
|
mx=MathMax(mx,MathAbs(w[j]));
|
|
}
|
|
}
|
|
//--- check
|
|
if(k%5==0)
|
|
{
|
|
//--- every 5-th run we renormalize W[]
|
|
v=1/mx;
|
|
for(i_=0; i_<n; i_++)
|
|
w[i_]=v*w[i_];
|
|
}
|
|
}
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Lagrange intepolant: generation of the model on equidistant grid.|
|
|
//| This function has O(N) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| Y - function values at the nodes, array[0..N-1] |
|
|
//| N - number of points, N>=1 |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - barycentric model which represents Lagrange |
|
|
//| interpolant (see ratint unit info and |
|
|
//| BarycentricCalc() description for more information). |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBuildEqDist(const double a,const double b,
|
|
double &y[],const int n,
|
|
CBarycentricInterpolant &p)
|
|
{
|
|
double v=0;
|
|
//--- create arrays
|
|
double w[];
|
|
double x[];
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(a+(b-a)/n)!=a,__FUNCTION__+": B is too close to A!"))
|
|
return;
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(x,1);
|
|
ArrayResize(w,1);
|
|
x[0]=0.5*(b+a);
|
|
w[0]=1;
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- general case
|
|
ArrayResize(x,n);
|
|
ArrayResize(w,n);
|
|
v=1;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
w[i]=v;
|
|
x[i]=a+(b-a)*i/(n-1);
|
|
v=-(v*(n-1-i));
|
|
v=v/(i+1);
|
|
}
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Lagrange intepolant on Chebyshev grid (first kind). |
|
|
//| This function has O(N) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| Y - function values at the nodes, array[0..N-1], |
|
|
//| Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)))|
|
|
//| N - number of points, N>=1 |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - barycentric model which represents Lagrange |
|
|
//| interpolant (see ratint unit info and |
|
|
//| BarycentricCalc() description for more information). |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBuildCheb1(const double a,const double b,
|
|
double &y[],const int n,
|
|
CBarycentricInterpolant &p)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double t=0;
|
|
//--- create arrays
|
|
double w[];
|
|
double x[];
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return;
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(x,1);
|
|
ArrayResize(w,1);
|
|
x[0]=0.5*(b+a);
|
|
w[0]=1;
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- general case
|
|
ArrayResize(x,n);
|
|
ArrayResize(w,n);
|
|
v=1;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
t=MathTan(0.5*M_PI*(2*i+1)/(2*n));
|
|
w[i]=2*v*t/(1+CMath::Sqr(t));
|
|
x[i]=0.5*(b+a)+0.5*(b-a)*(1-CMath::Sqr(t))/(1+CMath::Sqr(t));
|
|
v=-v;
|
|
}
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Lagrange intepolant on Chebyshev grid (second kind). |
|
|
//| This function has O(N) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| Y - function values at the nodes, array[0..N-1], |
|
|
//| Y[I] = Y(0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1))) |
|
|
//| N - number of points, N>=1 |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| OUTPUT PARAMETERS |
|
|
//| P - barycentric model which represents Lagrange |
|
|
//| interpolant (see ratint unit info and |
|
|
//| BarycentricCalc() description for more information). |
|
|
//+------------------------------------------------------------------+
|
|
void CPolInt::PolynomialBuildCheb2(const double a,const double b,
|
|
double &y[],const int n,
|
|
CBarycentricInterpolant &p)
|
|
{
|
|
double v=0;
|
|
//--- create arrays
|
|
double w[];
|
|
double x[];
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(x,1);
|
|
ArrayResize(w,1);
|
|
x[0]=0.5*(b+a);
|
|
w[0]=1;
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,1,p);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- general case
|
|
ArrayResize(x,n);
|
|
ArrayResize(w,n);
|
|
v=1;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(i==0 || i==n-1)
|
|
w[i]=v*0.5;
|
|
else
|
|
w[i]=v;
|
|
x[i]=0.5*(b+a)+0.5*(b-a)*MathCos(M_PI*i/(n-1));
|
|
v=-v;
|
|
}
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(x,y,w,n,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast equidistant polynomial interpolation function with O(N) |
|
|
//| complexity |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| F - function values, array[0..N-1] |
|
|
//| N - number of points on equidistant grid, N>=1 |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| T - position where P(x) is calculated |
|
|
//| RESULT |
|
|
//| value of the Lagrange interpolant at T |
|
|
//| IMPORTANT |
|
|
//| this function provides fast interface which is not |
|
|
//| overflow-safe nor it is very precise. |
|
|
//| the best option is to use PolynomialBuildEqDist() or |
|
|
//| BarycentricCalc() subroutines unless you are pretty sure that|
|
|
//| your data will not result in overflow. |
|
|
//+------------------------------------------------------------------+
|
|
double CPolInt::PolynomialCalcEqDist(const double a,const double b,
|
|
double &f[],const int n,const double t)
|
|
{
|
|
//--- create variables
|
|
double s1=0;
|
|
double s2=0;
|
|
double v=0;
|
|
double threshold=0;
|
|
double s=0;
|
|
double h=0;
|
|
int j=0;
|
|
double w=0;
|
|
double x=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
|
|
return(EMPTY_VALUE);
|
|
//--- Special case: T is NAN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
return(CInfOrNaN::NaN());
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
return(f[0]);
|
|
//--- First,decide: should we use "safe" formula (guarded
|
|
//--- against overflow) or fast one?
|
|
threshold=MathSqrt(CMath::m_minrealnumber);
|
|
j=0;
|
|
s=t-a;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
x=a+(double)i/(double)(n-1)*(b-a);
|
|
//--- check
|
|
if(MathAbs(t-x)<MathAbs(s))
|
|
{
|
|
s=t-x;
|
|
j=i;
|
|
}
|
|
}
|
|
//--- check
|
|
if(s==0.0)
|
|
return(f[j]);
|
|
//--- check
|
|
if(MathAbs(s)>threshold)
|
|
{
|
|
//--- use fast formula
|
|
j=-1;
|
|
s=1.0;
|
|
}
|
|
//--- Calculate using safe or fast barycentric formula
|
|
s1=0;
|
|
s2=0;
|
|
w=1.0;
|
|
h=(b-a)/(n-1);
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(i!=j)
|
|
{
|
|
v=s*w/(t-(a+i*h));
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
else
|
|
{
|
|
v=w;
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
//--- change values
|
|
w=-(w*(n-1-i));
|
|
w=w/(i+1);
|
|
}
|
|
//--- return result
|
|
return(s1/s2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast polynomial interpolation function on Chebyshev points (first|
|
|
//| kind) with O(N) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| F - function values, array[0..N-1] |
|
|
//| N - number of points on Chebyshev grid (first kind), |
|
|
//| X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*(2*i+1)/(2*n)) |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| T - position where P(x) is calculated |
|
|
//| RESULT |
|
|
//| value of the Lagrange interpolant at T |
|
|
//| IMPORTANT |
|
|
//| this function provides fast interface which is not |
|
|
//| overflow-safe nor it is very precise |
|
|
//| the best option is to use PolIntBuildCheb1() or |
|
|
//| BarycentricCalc() subroutines unless you are pretty sure that|
|
|
//| your data will not result in overflow. |
|
|
//+------------------------------------------------------------------+
|
|
double CPolInt::PolynomialCalcCheb1(const double a,const double b,
|
|
double &f[],const int n,double t)
|
|
{
|
|
//--- create variables
|
|
double s1=0;
|
|
double s2=0;
|
|
double v=0;
|
|
double threshold=0;
|
|
double s=0;
|
|
int j=0;
|
|
double a0=0;
|
|
double delta=0;
|
|
double alpha=0;
|
|
double beta=0;
|
|
double ca=0;
|
|
double sa=0;
|
|
double tempc=0;
|
|
double temps=0;
|
|
double x=0;
|
|
double w=0;
|
|
double p1=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
|
|
return(EMPTY_VALUE);
|
|
//--- Special case: T is NAN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
return(CInfOrNaN::NaN());
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
return(f[0]);
|
|
//--- Prepare information for the recurrence formula
|
|
//--- used to calculate sin(pi*(2j+1)/(2n+2)) and
|
|
//--- cos(pi*(2j+1)/(2n+2)):
|
|
//--- A0=pi/(2n+2)
|
|
//--- Delta=pi/(n+1)
|
|
//--- Alpha=2 sin^2 (Delta/2)
|
|
//--- Beta=sin(Delta)
|
|
//--- so that sin(..)=sin(A0+j*delta) and cos(..)=cos(A0+j*delta).
|
|
//--- Then we use
|
|
//--- sin(x+delta)=sin(x) - (alpha*sin(x) - beta*cos(x))
|
|
//--- cos(x+delta)=cos(x) - (alpha*cos(x) - beta*sin(x))
|
|
//--- to repeatedly calculate sin(..) and cos(..).
|
|
threshold=MathSqrt(CMath::m_minrealnumber);
|
|
t=(t-0.5*(a+b))/(0.5*(b-a));
|
|
a0=M_PI/(2*(n-1)+2);
|
|
delta=2*M_PI/(2*(n-1)+2);
|
|
alpha=2*CMath::Sqr(MathSin(delta/2));
|
|
beta=MathSin(delta);
|
|
//--- First, decide: should we use "safe" formula (guarded
|
|
//--- against overflow) or fast one?
|
|
ca=MathCos(a0);
|
|
sa=MathSin(a0);
|
|
j=0;
|
|
x=ca;
|
|
s=t-x;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
//--- Next X[i]
|
|
temps=sa-(alpha*sa-beta*ca);
|
|
tempc=ca-(alpha*ca+beta*sa);
|
|
sa=temps;
|
|
ca=tempc;
|
|
x=ca;
|
|
//--- Use X[i]
|
|
if(MathAbs(t-x)<MathAbs(s))
|
|
{
|
|
s=t-x;
|
|
j=i;
|
|
}
|
|
}
|
|
//--- check
|
|
if(s==0.0)
|
|
return(f[j]);
|
|
//--- check
|
|
if(MathAbs(s)>threshold)
|
|
{
|
|
//--- use fast formula
|
|
j=-1;
|
|
s=1.0;
|
|
}
|
|
//--- Calculate using safe or fast barycentric formula
|
|
s1=0;
|
|
s2=0;
|
|
ca=MathCos(a0);
|
|
sa=MathSin(a0);
|
|
p1=1.0;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Calculate X[i],W[i]
|
|
x=ca;
|
|
w=p1*sa;
|
|
//--- Proceed
|
|
if(i!=j)
|
|
{
|
|
v=s*w/(t-x);
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
else
|
|
{
|
|
v=w;
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
//--- Next CA,SA,P1
|
|
temps=sa-(alpha*sa-beta*ca);
|
|
tempc=ca-(alpha*ca+beta*sa);
|
|
sa=temps;
|
|
ca=tempc;
|
|
p1=-p1;
|
|
}
|
|
//--- return result
|
|
return(s1/s2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fast polynomial interpolation function on Chebyshev points |
|
|
//| (second kind) with O(N) complexity. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - left boundary of [A,B] |
|
|
//| B - right boundary of [A,B] |
|
|
//| F - function values, array[0..N-1] |
|
|
//| N - number of points on Chebyshev grid (second kind), |
|
|
//| X[i] = 0.5*(B+A) + 0.5*(B-A)*Cos(PI*i/(n-1)) |
|
|
//| for N=1 a constant model is constructed. |
|
|
//| T - position where P(x) is calculated |
|
|
//| RESULT |
|
|
//| value of the Lagrange interpolant at T |
|
|
//| IMPORTANT |
|
|
//| this function provides fast interface which is not |
|
|
//| overflow-safe nor it is very precise. |
|
|
//| the best option is to use PolIntBuildCheb2() or |
|
|
//| BarycentricCalc() subroutines unless you are pretty sure that|
|
|
//| your data will not result in overflow. |
|
|
//+------------------------------------------------------------------+
|
|
double CPolInt::PolynomialCalcCheb2(const double a,const double b,
|
|
double &f[],const int n,double t)
|
|
{
|
|
//--- create variables
|
|
double s1=0;
|
|
double s2=0;
|
|
double v=0;
|
|
double threshold=0;
|
|
double s=0;
|
|
int j=0;
|
|
double a0=0;
|
|
double delta=0;
|
|
double alpha=0;
|
|
double beta=0;
|
|
double ca=0;
|
|
double sa=0;
|
|
double tempc=0;
|
|
double temps=0;
|
|
double x=0;
|
|
double w=0;
|
|
double p1=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(f)>=n,__FUNCTION__+": Length(F)<N!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(a),__FUNCTION__+": A is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(b),__FUNCTION__+": B is infinite or NaN!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(b!=a,__FUNCTION__+": B=A!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,n),__FUNCTION__+": F contains infinite or NaN values!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(t),__FUNCTION__+": T is infinite!"))
|
|
return(EMPTY_VALUE);
|
|
//--- Special case: T is NAN
|
|
if(CInfOrNaN::IsNaN(t))
|
|
return(CInfOrNaN::NaN());
|
|
//--- Special case: N=1
|
|
if(n==1)
|
|
return(f[0]);
|
|
//--- Prepare information for the recurrence formula
|
|
//--- used to calculate sin(pi*i/n) and
|
|
//--- cos(pi*i/n):
|
|
//--- A0=0
|
|
//--- Delta=pi/n
|
|
//--- Alpha=2 sin^2 (Delta/2)
|
|
//--- Beta=sin(Delta)
|
|
//--- so that sin(..)=sin(A0+j*delta) and cos(..)=cos(A0+j*delta).
|
|
//--- Then we use
|
|
//--- sin(x+delta)=sin(x) - (alpha*sin(x) - beta*cos(x))
|
|
//--- cos(x+delta)=cos(x) - (alpha*cos(x) - beta*sin(x))
|
|
//--- to repeatedly calculate sin(..) and cos(..).
|
|
threshold=MathSqrt(CMath::m_minrealnumber);
|
|
t=(t-0.5*(a+b))/(0.5*(b-a));
|
|
a0=0.0;
|
|
delta=M_PI/(n-1);
|
|
alpha=2*CMath::Sqr(MathSin(delta/2));
|
|
beta=MathSin(delta);
|
|
//--- First,decide: should we use "safe" formula (guarded
|
|
//--- against overflow) or fast one?
|
|
ca=MathCos(a0);
|
|
sa=MathSin(a0);
|
|
j=0;
|
|
x=ca;
|
|
s=t-x;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
//--- Next X[i]
|
|
temps=sa-(alpha*sa-beta*ca);
|
|
tempc=ca-(alpha*ca+beta*sa);
|
|
sa=temps;
|
|
ca=tempc;
|
|
x=ca;
|
|
//--- Use X[i]
|
|
if(MathAbs(t-x)<MathAbs(s))
|
|
{
|
|
s=t-x;
|
|
j=i;
|
|
}
|
|
}
|
|
//--- check
|
|
if(s==0.0)
|
|
return(f[j]);
|
|
//--- check
|
|
if(MathAbs(s)>threshold)
|
|
{
|
|
//--- use fast formula
|
|
j=-1;
|
|
s=1.0;
|
|
}
|
|
//--- Calculate using safe or fast barycentric formula
|
|
s1=0;
|
|
s2=0;
|
|
ca=MathCos(a0);
|
|
sa=MathSin(a0);
|
|
p1=1.0;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Calculate X[i],W[i]
|
|
x=ca;
|
|
//--- check
|
|
if(i==0 || i==n-1)
|
|
w=0.5*p1;
|
|
else
|
|
w=1.0*p1;
|
|
//--- Proceed
|
|
if(i!=j)
|
|
{
|
|
v=s*w/(t-x);
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
else
|
|
{
|
|
v=w;
|
|
s1=s1+v*f[i];
|
|
s2=s2+v;
|
|
}
|
|
//--- Next CA,SA,P1
|
|
temps=sa-(alpha*sa-beta*ca);
|
|
tempc=ca-(alpha*ca+beta*sa);
|
|
sa=temps;
|
|
ca=tempc;
|
|
p1=-p1;
|
|
}
|
|
//--- return result
|
|
return(s1/s2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 1-dimensional spline inteprolant |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline1DInterpolant
|
|
{
|
|
public:
|
|
//--- variables
|
|
bool m_periodic;
|
|
int m_n;
|
|
int m_k;
|
|
int m_continuity;
|
|
//--- arrays
|
|
double m_x[];
|
|
double m_c[];
|
|
//--- constructor, destructor
|
|
CSpline1DInterpolant(void) { ZeroMemory(this); }
|
|
~CSpline1DInterpolant(void) {}
|
|
//--- copy
|
|
void Copy(CSpline1DInterpolant &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DInterpolant::Copy(CSpline1DInterpolant &obj)
|
|
{
|
|
//--- copy variables
|
|
m_periodic=obj.m_periodic;
|
|
m_n=obj.m_n;
|
|
m_k=obj.m_k;
|
|
m_continuity=obj.m_continuity;
|
|
//--- copy arrays
|
|
ArrayCopy(m_x,obj.m_x);
|
|
ArrayCopy(m_c,obj.m_c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 1-dimensional spline inteprolant |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline1DInterpolantShell
|
|
{
|
|
private:
|
|
CSpline1DInterpolant m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CSpline1DInterpolantShell(void) {}
|
|
CSpline1DInterpolantShell(CSpline1DInterpolant &obj) { m_innerobj.Copy(obj); }
|
|
~CSpline1DInterpolantShell(void) {}
|
|
//--- method
|
|
CSpline1DInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Spline fitting report: |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| Fields below are filled by obsolete functions (Spline1DFitCubic, |
|
|
//| Spline1DFitHermite). Modern fitting functions do NOT fill these |
|
|
//| fields: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline1DFitReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
double m_taskrcond;
|
|
double m_rmserror;
|
|
double m_avgerror;
|
|
double m_avgrelerror;
|
|
double m_maxerror;
|
|
//--- constructor, destructor
|
|
CSpline1DFitReport(void) { ZeroMemory(this); }
|
|
~CSpline1DFitReport(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline1DFitReport &obj);
|
|
//--- overloading
|
|
void operator=(const CSpline1DFitReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReport::Copy(const CSpline1DFitReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_taskrcond=obj.m_taskrcond;
|
|
m_rmserror=obj.m_rmserror;
|
|
m_avgerror=obj.m_avgerror;
|
|
m_avgrelerror=obj.m_avgrelerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Spline fitting report: |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| Fields below are filled by obsolete functions (Spline1DFitCubic, |
|
|
//| Spline1DFitHermite). Modern fitting functions do NOT fill these |
|
|
//| fields: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline1DFitReportShell
|
|
{
|
|
private:
|
|
CSpline1DFitReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CSpline1DFitReportShell(void) {}
|
|
CSpline1DFitReportShell(CSpline1DFitReport &obj) { m_innerobj.Copy(obj); }
|
|
~CSpline1DFitReportShell(void) {}
|
|
//--- methods
|
|
double GetTaskRCond(void);
|
|
void SetTaskRCond(const double d);
|
|
double GetRMSError(void);
|
|
void SetRMSError(const double d);
|
|
double GetAvgError(void);
|
|
void SetAvgError(const double d);
|
|
double GetAvgRelError(void);
|
|
void SetAvgRelError(const double d);
|
|
double GetMaxError(void);
|
|
void SetMaxError(const double d);
|
|
CSpline1DFitReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1DFitReportShell::GetTaskRCond(void)
|
|
{
|
|
return(m_innerobj.m_taskrcond);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReportShell::SetTaskRCond(const double d)
|
|
{
|
|
m_innerobj.m_taskrcond=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1DFitReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_rmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_rmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1DFitReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_avgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_avgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1DFitReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_avgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_avgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1DFitReportShell::GetMaxError(void)
|
|
{
|
|
return(m_innerobj.m_maxerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1DFitReportShell::SetMaxError(const double d)
|
|
{
|
|
m_innerobj.m_maxerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CSpline1DFitReport *CSpline1DFitReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 1-dimensional spline interpolation |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline1D
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_lambdareg;
|
|
static const double m_cholreg;
|
|
//--- public methods
|
|
static void Spline1DBuildLinear(double &cx[],double &cy[],const int n,CSpline1DInterpolant &c);
|
|
static void Spline1DBuildCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,CSpline1DInterpolant &c);
|
|
static void Spline1DGridDiffCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d[]);
|
|
static void Spline1DGridDiff2Cubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d1[],double &d2[]);
|
|
static void Spline1DConvCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[]);
|
|
static void Spline1DConvDiffCubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[],double &d2[]);
|
|
static void Spline1DConvDiff2Cubic(double &cx[],double &cy[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &cx2[],const int n2,double &y2[],double &d2[],double &dd2[]);
|
|
static void Spline1DBuildCatmullRom(double &cx[],double &cy[],const int n,const int boundtype,const double tension,CSpline1DInterpolant &c);
|
|
static void Spline1DBuildHermite(double &cx[],double &cy[],double &cd[],const int n,CSpline1DInterpolant &c);
|
|
static void Spline1DBuildAkima(double &cx[],double &cy[],const int n,CSpline1DInterpolant &c);
|
|
static double Spline1DCalc(CSpline1DInterpolant &c,double x);
|
|
static void Spline1DDiff(CSpline1DInterpolant &c,double x,double &s,double &ds,double &d2s);
|
|
static void Spline1DCopy(CSpline1DInterpolant &c,CSpline1DInterpolant &cc);
|
|
static void Spline1DUnpack(CSpline1DInterpolant &c,int &n,CMatrixDouble &tbl);
|
|
static void Spline1DLinTransX(CSpline1DInterpolant &c,const double a,const double b);
|
|
static void Spline1DLinTransY(CSpline1DInterpolant &c,const double a,const double b);
|
|
static double Spline1DIntegrate(CSpline1DInterpolant &c,double x);
|
|
static void Spline1DFit(double &x[],double &y[],int n,int m,double lambdans,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void Spline1DBuildMonotone(double &X[],double &Y[],int n,CSpline1DInterpolant &c);
|
|
static void Spline1DConvDiffInternal(double &xold[],double &yold[],double &dold[],const int n,double &x2[],const int n2,double &y[],const bool needy,double &d1[],const bool needd1,double &d2[],const bool needd2);
|
|
static void HeapSortDPoints(double &x[],double &y[],double &d[],const int n);
|
|
static void HeapSortDPoints(CRowDouble &x,CRowDouble &y,CRowDouble &d,const int n);
|
|
|
|
private:
|
|
static void Spline1DGridDiffCubicInternal(double &x[],double &y[],const int n,const int boundltype,const double boundl,const int boundrtype,const double boundr,double &d[],double &a1[],double &a2[],double &a3[],double &b[],double &dt[]);
|
|
static void HeapSortPoints(double &x[],double &y[],const int n);
|
|
static void HeapSortPPoints(double &x[],double &y[],int &p[],const int n);
|
|
static void SolveTridiagonal(double &a[],double &cb[],double &c[],double &cd[],const int n,double &x[]);
|
|
static void SolveCyclicTridiagonal(double &a[],double &cb[],double &c[],double &d[],const int n,double &x[]);
|
|
static double DiffThreePoint(double t,const double x0,const double f0,double x1,const double f1,double x2,const double f2);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const double CSpline1D::m_lambdareg=1.0e-9;
|
|
const double CSpline1D::m_cholreg=1.0e-12;
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds linear spline interpolant |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1] |
|
|
//| Y - function values, array[0..N-1] |
|
|
//| N - points count (optional): |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used to build |
|
|
//| spline |
|
|
//| * if not given, automatically detected from X/Y |
|
|
//| sizes (len(X) must be equal to len(Y)) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildLinear(double &cx[],double &cy[],
|
|
const int n,CSpline1DInterpolant &c)
|
|
{
|
|
//--- create arrays
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check
|
|
if(!CAp::Assert(n>1,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPoints(x,y,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Build
|
|
c.m_periodic=false;
|
|
c.m_n=n;
|
|
c.m_k=3;
|
|
//--- allocation
|
|
ArrayResize(c.m_x,n);
|
|
ArrayResize(c.m_c,4*(n-1));
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
c.m_x[i]=x[i];
|
|
//--- calculation
|
|
for(int i=0; i<=n-2; i++)
|
|
{
|
|
c.m_c[4*i+0]=y[i];
|
|
c.m_c[4*i+1]=(y[i+1]-y[i])/(x[i+1]-x[i]);
|
|
c.m_c[4*i+2]=0;
|
|
c.m_c[4*i+3]=0;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds cubic spline interpolant. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used to |
|
|
//| build spline |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildCubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,const int boundrtype,
|
|
const double boundr,CSpline1DInterpolant &c)
|
|
{
|
|
//--- create a variable
|
|
int ylen=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double dt[];
|
|
double d[];
|
|
int p[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Now we've checked and preordered everything,
|
|
//--- so we can call internal function to calculate derivatives,
|
|
//--- and then build Hermite spline using these derivatives
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
|
|
Spline1DBuildHermite(x,y,d,n,c);
|
|
//--- check
|
|
if(boundltype==-1 || boundrtype==-1)
|
|
c.m_periodic=1;
|
|
else
|
|
c.m_periodic=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves following problem: given table y[] of |
|
|
//| function values at nodes x[], it calculates and returns table of |
|
|
//| function derivatives d[] (calculated at the same nodes x[]). |
|
|
//| This function yields same result as Spline1DBuildCubic() call |
|
|
//| followed by sequence of Spline1DDiff() calls, but it can be |
|
|
//| several times faster when called for ordered X[] and X2[]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes |
|
|
//| Y - function values |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - derivative values at X[] |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. Derivative values are correctly reordered on |
|
|
//| return, so D[I] is always equal to S'(X[I]) independently of |
|
|
//| points order. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DGridDiffCubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,const int boundrtype,
|
|
const double boundr,double &d[])
|
|
{
|
|
int ylen=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double dt[];
|
|
int p[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Now we've checked and preordered everything,
|
|
//--- so we can call internal function.
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
|
|
//--- Remember that HeapSortPPoints() call?
|
|
//--- Now we have to reorder them back.
|
|
if(CAp::Len(dt)<n)
|
|
ArrayResize(dt,n);
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
dt[p[i]]=d[i];
|
|
for(int i_=0; i_<n; i_++)
|
|
d[i_]=dt[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves following problem: given table y[] of |
|
|
//| function values at nodes x[], it calculates and returns tables of|
|
|
//| first and second function derivatives d1[] and d2[] (calculated |
|
|
//| at the same nodes x[]). |
|
|
//| This function yields same result as Spline1DBuildCubic() call |
|
|
//| followed by sequence of Spline1DDiff() calls, but it can be |
|
|
//| several times faster when called for ordered X[] and X2[]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes |
|
|
//| Y - function values |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D1 - S' values at X[] |
|
|
//| D2 - S'' values at X[] |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. Derivative values are correctly reordered on |
|
|
//| return, so D[I] is always equal to S'(X[I]) independently of |
|
|
//| points order. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. |
|
|
//| However, this subroutine doesn't require you to specify equal |
|
|
//| values for the first and last points - it automatically forces |
|
|
//| them to be equal by copying Y[first_point] (corresponds to the |
|
|
//| leftmost, minimal X[]) to Y[last_point]. However it is |
|
|
//| recommended to pass consistent values of Y[], i.e. to make |
|
|
//| Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DGridDiff2Cubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,const int boundrtype,
|
|
const double boundr,double &d1[],double &d2[])
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ylen=0;
|
|
double delta=0;
|
|
double delta2=0;
|
|
double delta3=0;
|
|
double s0=0;
|
|
double s1=0;
|
|
double s2=0;
|
|
double s3=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double dt[];
|
|
int p[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Now we've checked and preordered everything,
|
|
//--- so we can call internal function.
|
|
//--- After this call we will calculate second derivatives
|
|
//--- (manually,by converting to the power basis)
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d1,a1,a2,a3,b,dt);
|
|
//--- allocation
|
|
ArrayResize(d2,n);
|
|
delta=0;
|
|
s2=0;
|
|
s3=0;
|
|
//--- calculation
|
|
for(i=0; i<=n-2; i++)
|
|
{
|
|
//--- We convert from Hermite basis to the power basis.
|
|
//--- Si is coefficient before x^i.
|
|
//--- Inside this cycle we need just S2,
|
|
//--- because we calculate S'' exactly at spline node,
|
|
//--- (only x^2 matters at x=0),but after iterations
|
|
//--- will be over,we will need other coefficients
|
|
//--- to calculate spline value at the last node.
|
|
delta=x[i+1]-x[i];
|
|
delta2=CMath::Sqr(delta);
|
|
delta3=delta*delta2;
|
|
s0=y[i];
|
|
s1=d1[i];
|
|
s2=(3*(y[i+1]-y[i])-2*d1[i]*delta-d1[i+1]*delta)/delta2;
|
|
s3=(2*(y[i]-y[i+1])+d1[i]*delta+d1[i+1]*delta)/delta3;
|
|
d2[i]=2*s2;
|
|
}
|
|
d2[n-1]=2*s2+6*s3*delta;
|
|
//--- Remember that HeapSortPPoints() call?
|
|
//--- Now we have to reorder them back.
|
|
if(CAp::Len(dt)<n)
|
|
ArrayResize(dt,n);
|
|
//--- copy
|
|
for(i=0; i<n; i++)
|
|
dt[p[i]]=d1[i];
|
|
for(i_=0; i_<n; i_++)
|
|
d1[i_]=dt[i_];
|
|
for(i=0; i<n; i++)
|
|
dt[p[i]]=d2[i];
|
|
for(i_=0; i_<n; i_++)
|
|
d2[i_]=dt[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves following problem: given table y[] of |
|
|
//| function values at old nodes x[] and new nodes x2[], it |
|
|
//| calculates and returns table of function values y2[] (calculated |
|
|
//| at x2[]). |
|
|
//| This function yields same result as Spline1DBuildCubic() call |
|
|
//| followed by sequence of Spline1DDiff() calls, but it can be |
|
|
//| several times faster when called for ordered X[] and X2[]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - old spline nodes |
|
|
//| Y - function values |
|
|
//| X2 - new spline nodes |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points from X/Y are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| N2 - new points count: |
|
|
//| * N2>=2 |
|
|
//| * if given, only first N2 points from X2 are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X2 size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F2 - function values at X2[] |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. Function values are correctly reordered on |
|
|
//| return, so F2[I] is always equal to S(X2[I]) independently of |
|
|
//| points order. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DConvCubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,const int boundrtype,
|
|
const double boundr,double &cx2[],
|
|
const int n2,double &y2[])
|
|
{
|
|
//--- create variables
|
|
int ylen=0;
|
|
double t=0;
|
|
double t2=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double d[];
|
|
double dt[];
|
|
double d1[];
|
|
double d2[];
|
|
int p[];
|
|
int p2[];
|
|
double x[];
|
|
double y[];
|
|
double x2[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(x2,cx2);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n2>=2,__FUNCTION__+": N2<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
|
|
return;
|
|
//--- check and sort X/Y
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- set up DT (we will need it below)
|
|
ArrayResize(dt,MathMax(n,n2));
|
|
//--- sort X2:
|
|
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
|
|
//--- * if we have periodic problem,wrap points
|
|
//--- * sort them,store permutation at P2
|
|
if(boundrtype==-1 && boundltype==-1)
|
|
{
|
|
for(int i=0; i<=n2-1; i++)
|
|
{
|
|
t=x2[i];
|
|
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
|
|
x2[i]=t;
|
|
}
|
|
}
|
|
//--- function call
|
|
HeapSortPPoints(x2,dt,p2,n2);
|
|
//--- Now we've checked and preordered everything,so we:
|
|
//--- * call internal GridDiff() function to get Hermite form of spline
|
|
//--- * convert using internal Conv() function
|
|
//--- * convert Y2 back to original order
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
|
|
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d1,false,d2,false);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- copy
|
|
for(int i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=y2[i];
|
|
for(int i_=0; i_<=n2-1; i_++)
|
|
y2[i_]=dt[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves following problem: given table y[] of |
|
|
//| function values at old nodes x[] and new nodes x2[], it |
|
|
//| calculates and returns table of function values y2[] and |
|
|
//| derivatives d2[] (calculated at x2[]). |
|
|
//| This function yields same result as Spline1DBuildCubic() call |
|
|
//| followed by sequence of Spline1DDiff() calls, but it can be |
|
|
//| several times faster when called for ordered X[] and X2[]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - old spline nodes |
|
|
//| Y - function values |
|
|
//| X2 - new spline nodes |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points from X/Y are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| N2 - new points count: |
|
|
//| * N2>=2 |
|
|
//| * if given, only first N2 points from X2 are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X2 size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F2 - function values at X2[] |
|
|
//| D2 - first derivatives at X2[] |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. Function values are correctly reordered on |
|
|
//| return, so F2[I] is always equal to S(X2[I]) independently of |
|
|
//| points order. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DConvDiffCubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,const int boundrtype,
|
|
const double boundr,double &cx2[],
|
|
const int n2,double &y2[],double &d2[])
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ylen=0;
|
|
double t=0;
|
|
double t2=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double d[];
|
|
double dt[];
|
|
double rt1[];
|
|
int p[];
|
|
int p2[];
|
|
double x[];
|
|
double y[];
|
|
double x2[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(x2,cx2);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n2>=2,__FUNCTION__+"Spline1DConvDiffCubic: N2<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
|
|
return;
|
|
//--- check and sort X/Y
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- set up DT (we will need it below)
|
|
ArrayResize(dt,MathMax(n,n2));
|
|
//--- sort X2:
|
|
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
|
|
//--- * if we have periodic problem,wrap points
|
|
//--- * sort them,store permutation at P2
|
|
if(boundrtype==-1 && boundltype==-1)
|
|
{
|
|
for(i=0; i<=n2-1; i++)
|
|
{
|
|
t=x2[i];
|
|
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
|
|
x2[i]=t;
|
|
}
|
|
}
|
|
//--- function call
|
|
HeapSortPPoints(x2,dt,p2,n2);
|
|
//--- Now we've checked and preordered everything,so we:
|
|
//--- * call internal GridDiff() function to get Hermite form of spline
|
|
//--- * convert using internal Conv() function
|
|
//--- * convert Y2 back to original order
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
|
|
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d2,true,rt1,false);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- copy
|
|
for(i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=y2[i];
|
|
for(i_=0; i_<=n2-1; i_++)
|
|
y2[i_]=dt[i_];
|
|
for(i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=d2[i];
|
|
for(i_=0; i_<=n2-1; i_++)
|
|
d2[i_]=dt[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function solves following problem: given table y[] of |
|
|
//| function values at old nodes x[] and new nodes x2[], it |
|
|
//| calculates and returns table of function values y2[], first and |
|
|
//| second derivatives d2[] and dd2[] (calculated at x2[]). |
|
|
//| This function yields same result as Spline1DBuildCubic() call |
|
|
//| followed by sequence of Spline1DDiff() calls, but it can be |
|
|
//| several times faster when called for ordered X[] and X2[]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - old spline nodes |
|
|
//| Y - function values |
|
|
//| X2 - new spline nodes |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points from X/Y are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundLType - boundary condition type for the left boundary|
|
|
//| BoundL - left boundary condition (first or second |
|
|
//| derivative, depending on the BoundLType) |
|
|
//| BoundRType - boundary condition type for the right |
|
|
//| boundary |
|
|
//| BoundR - right boundary condition (first or second |
|
|
//| derivative, depending on the BoundRType) |
|
|
//| N2 - new points count: |
|
|
//| * N2>=2 |
|
|
//| * if given, only first N2 points from X2 are |
|
|
//| used |
|
|
//| * if not given, automatically detected from |
|
|
//| X2 size |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F2 - function values at X2[] |
|
|
//| D2 - first derivatives at X2[] |
|
|
//| DD2 - second derivatives at X2[] |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. Function values are correctly reordered on |
|
|
//| return, so F2[I] is always equal to S(X2[I]) independently of |
|
|
//| points order. |
|
|
//| SETTING BOUNDARY VALUES: |
|
|
//| The BoundLType/BoundRType parameters can have the following |
|
|
//| values: |
|
|
//| * -1, which corresonds to the periodic (cyclic) boundary |
|
|
//| conditions. In this case: |
|
|
//| * both BoundLType and BoundRType must be equal to -1. |
|
|
//| * BoundL/BoundR are ignored |
|
|
//| * Y[last] is ignored (it is assumed to be equal to |
|
|
//| Y[first]). |
|
|
//| * 0, which corresponds to the parabolically terminated |
|
|
//| spline (BoundL and/or BoundR are ignored). |
|
|
//| * 1, which corresponds to the first derivative boundary |
|
|
//| condition |
|
|
//| * 2, which corresponds to the second derivative boundary |
|
|
//| condition |
|
|
//| * by default, BoundType=0 is used |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DConvDiff2Cubic(double &cx[],double &cy[],
|
|
const int n,const int boundltype,
|
|
const double boundl,
|
|
const int boundrtype,
|
|
const double boundr,double &cx2[],
|
|
const int n2,double &y2[],
|
|
double &d2[],double &dd2[])
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ylen=0;
|
|
double t=0;
|
|
double t2=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double a1[];
|
|
double a2[];
|
|
double a3[];
|
|
double b[];
|
|
double d[];
|
|
double dt[];
|
|
int p[];
|
|
int p2[];
|
|
double x[];
|
|
double y[];
|
|
double x2[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(x2,cx2);
|
|
//--- check correctness of boundary conditions
|
|
if(!CAp::Assert(((boundltype==-1 || boundltype==0) || boundltype==1) || boundltype==2,__FUNCTION__+": incorrect BoundLType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(((boundrtype==-1 || boundrtype==0) || boundrtype==1) || boundrtype==2,__FUNCTION__+": incorrect BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((boundrtype==-1 && boundltype==-1) || (boundrtype!=-1 && boundltype!=-1),__FUNCTION__+": incorrect BoundLType/BoundRType!"))
|
|
return;
|
|
//--- check
|
|
if(boundltype==1 || boundltype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundl),__FUNCTION__+": BoundL is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check
|
|
if(boundrtype==1 || boundrtype==2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(boundr),__FUNCTION__+": BoundR is infinite or NAN!"))
|
|
return;
|
|
}
|
|
//--- check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n2>=2,__FUNCTION__+": N2<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2!"))
|
|
return;
|
|
//--- check and sort X/Y
|
|
ylen=n;
|
|
//--- check
|
|
if(boundltype==-1)
|
|
ylen=n-1;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,ylen),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPPoints(x,y,p,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- set up DT (we will need it below)
|
|
ArrayResize(dt,MathMax(n,n2));
|
|
//--- sort X2:
|
|
//--- * use fake array DT because HeapSortPPoints() needs both integer AND real arrays
|
|
//--- * if we have periodic problem,wrap points
|
|
//--- * sort them,store permutation at P2
|
|
if(boundrtype==-1 && boundltype==-1)
|
|
{
|
|
for(i=0; i<=n2-1; i++)
|
|
{
|
|
t=x2[i];
|
|
CApServ::ApPeriodicMap(t,x[0],x[n-1],t2);
|
|
x2[i]=t;
|
|
}
|
|
}
|
|
//--- function call
|
|
HeapSortPPoints(x2,dt,p2,n2);
|
|
//--- Now we've checked and preordered everything,so we:
|
|
//--- * call internal GridDiff() function to get Hermite form of spline
|
|
//--- * convert using internal Conv() function
|
|
//--- * convert Y2 back to original order
|
|
Spline1DGridDiffCubicInternal(x,y,n,boundltype,boundl,boundrtype,boundr,d,a1,a2,a3,b,dt);
|
|
Spline1DConvDiffInternal(x,y,d,n,x2,n2,y2,true,d2,true,dd2,true);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dt)>=n2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- copy
|
|
for(i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=y2[i];
|
|
for(i_=0; i_<=n2-1; i_++)
|
|
y2[i_]=dt[i_];
|
|
for(i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=d2[i];
|
|
for(i_=0; i_<=n2-1; i_++)
|
|
d2[i_]=dt[i_];
|
|
for(i=0; i<=n2-1; i++)
|
|
dt[p2[i]]=dd2[i];
|
|
for(i_=0; i_<=n2-1; i_++)
|
|
dd2[i_]=dt[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds Catmull-Rom spline interpolant. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| OPTIONAL PARAMETERS: |
|
|
//| N - points count: |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used to |
|
|
//| build spline |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| BoundType - boundary condition type: |
|
|
//| * -1 for periodic boundary condition |
|
|
//| * 0 for parabolically terminated spline |
|
|
//| (default) |
|
|
//| Tension - tension parameter: |
|
|
//| * tension=0 corresponds to classic |
|
|
//| Catmull-Rom spline (default) |
|
|
//| * 0<tension<1 corresponds to more general |
|
|
//| form - cardinal spline |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//| PROBLEMS WITH PERIODIC BOUNDARY CONDITIONS: |
|
|
//| Problems with periodic boundary conditions have |
|
|
//| Y[first_point]=Y[last_point]. However, this subroutine doesn't |
|
|
//| require you to specify equal values for the first and last |
|
|
//| points - it automatically forces them to be equal by copying |
|
|
//| Y[first_point] (corresponds to the leftmost, minimal X[]) to |
|
|
//| Y[last_point]. However it is recommended to pass consistent |
|
|
//| values of Y[], i.e. to make Y[first_point]=Y[last_point]. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildCatmullRom(double &cx[],double &cy[],
|
|
const int n,const int boundtype,
|
|
const double tension,
|
|
CSpline1DInterpolant &c)
|
|
{
|
|
//--- create arrays
|
|
double d[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(boundtype==-1 || boundtype==0,__FUNCTION__+": incorrect BoundType!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(tension)>=0.0,__FUNCTION__+": Tension<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(tension)<=1.0,__FUNCTION__+": Tension>1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPoints(x,y,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Special cases:
|
|
//--- * N=2,parabolic terminated boundary condition on both ends
|
|
//--- * N=2,periodic boundary condition
|
|
if(n==2 && boundtype==0)
|
|
{
|
|
//--- Just linear spline
|
|
Spline1DBuildLinear(x,y,n,c);
|
|
return;
|
|
}
|
|
if(n==2 && boundtype==-1)
|
|
{
|
|
//--- Same as cubic spline with periodic conditions
|
|
Spline1DBuildCubic(x,y,n,-1,0.0,-1,0.0,c);
|
|
return;
|
|
}
|
|
//--- Periodic or non-periodic boundary conditions
|
|
if(boundtype==-1)
|
|
{
|
|
//--- Periodic boundary conditions
|
|
y[n-1]=y[0];
|
|
//--- allocation
|
|
ArrayResize(d,n);
|
|
d[0]=(y[1]-y[n-2])/(2*(x[1]-x[0]+x[n-1]-x[n-2]));
|
|
for(int i=1; i<=n-2; i++)
|
|
d[i]=(1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
|
|
d[n-1]=d[0];
|
|
//--- Now problem is reduced to the cubic Hermite spline
|
|
Spline1DBuildHermite(x,y,d,n,c);
|
|
c.m_periodic=true;
|
|
}
|
|
else
|
|
{
|
|
//--- Non-periodic boundary conditions
|
|
ArrayResize(d,n);
|
|
for(int i=1; i<=n-2; i++)
|
|
d[i]=(1-tension)*(y[i+1]-y[i-1])/(x[i+1]-x[i-1]);
|
|
d[0]=2*(y[1]-y[0])/(x[1]-x[0])-d[1];
|
|
d[n-1]=2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])-d[n-2];
|
|
//--- Now problem is reduced to the cubic Hermite spline
|
|
Spline1DBuildHermite(x,y,d,n,c);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds Hermite spline interpolant. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1] |
|
|
//| Y - function values, array[0..N-1] |
|
|
//| D - derivatives, array[0..N-1] |
|
|
//| N - points count (optional): |
|
|
//| * N>=2 |
|
|
//| * if given, only first N points are used to |
|
|
//| build spline |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildHermite(double &cx[],double &cy[],
|
|
double &cd[],const int n,
|
|
CSpline1DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
double delta=0;
|
|
double delta2=0;
|
|
double delta3=0;
|
|
//--- create arrays
|
|
double x[];
|
|
double y[];
|
|
double d[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(d,cd);
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(d)>=n,__FUNCTION__+": Length(D)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(d,n),__FUNCTION__+": D contains infinite or NAN values!"))
|
|
return;
|
|
HeapSortDPoints(x,y,d,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Build
|
|
ArrayResize(c.m_x,n);
|
|
ArrayResize(c.m_c,4*(n-1));
|
|
//--- change values
|
|
c.m_periodic=false;
|
|
c.m_k=3;
|
|
c.m_n=n;
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
c.m_x[i]=x[i];
|
|
//--- calculation
|
|
for(int i=0; i<=n-2; i++)
|
|
{
|
|
delta=x[i+1]-x[i];
|
|
delta2=CMath::Sqr(delta);
|
|
delta3=delta*delta2;
|
|
c.m_c[4*i+0]=y[i];
|
|
c.m_c[4*i+1]=d[i];
|
|
c.m_c[4*i+2]=(3*(y[i+1]-y[i])-2*d[i]*delta-d[i+1]*delta)/delta2;
|
|
c.m_c[4*i+3]=(2*(y[i]-y[i+1])+d[i]*delta+d[i+1]*delta)/delta3;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds Akima spline interpolant |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1] |
|
|
//| Y - function values, array[0..N-1] |
|
|
//| N - points count (optional): |
|
|
//| * N>=5 |
|
|
//| * if given, only first N points are used to |
|
|
//| build spline |
|
|
//| * if not given, automatically detected from |
|
|
//| X/Y sizes (len(X) must be equal to len(Y)) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildAkima(double &cx[],double &cy[],
|
|
const int n,CSpline1DInterpolant &c)
|
|
{
|
|
//--- create arrays
|
|
double d[];
|
|
double w[];
|
|
double diff[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- check
|
|
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check and sort points
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- function call
|
|
HeapSortPoints(x,y,n);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close!"))
|
|
return;
|
|
//--- Prepare W (weights),Diff (divided differences)
|
|
ArrayResize(w,n-1);
|
|
ArrayResize(diff,n-1);
|
|
for(int i=0; i<=n-2; i++)
|
|
diff[i]=(y[i+1]-y[i])/(x[i+1]-x[i]);
|
|
for(int i=1; i<=n-2; i++)
|
|
w[i]=MathAbs(diff[i]-diff[i-1]);
|
|
//--- Prepare Hermite interpolation scheme
|
|
ArrayResize(d,n);
|
|
for(int i=2; i<=n-3; i++)
|
|
{
|
|
//--- check
|
|
if(MathAbs(w[i-1])+MathAbs(w[i+1])!=0.0)
|
|
d[i]=(w[i+1]*diff[i-1]+w[i-1]*diff[i])/(w[i+1]+w[i-1]);
|
|
else
|
|
d[i]=((x[i+1]-x[i])*diff[i-1]+(x[i]-x[i-1])*diff[i])/(x[i+1]-x[i-1]);
|
|
}
|
|
//--- change values
|
|
d[0]=DiffThreePoint(x[0],x[0],y[0],x[1],y[1],x[2],y[2]);
|
|
d[1]=DiffThreePoint(x[1],x[0],y[0],x[1],y[1],x[2],y[2]);
|
|
d[n-2]=DiffThreePoint(x[n-2],x[n-3],y[n-3],x[n-2],y[n-2],x[n-1],y[n-1]);
|
|
d[n-1]=DiffThreePoint(x[n-1],x[n-3],y[n-3],x[n-2],y[n-2],x[n-1],y[n-1]);
|
|
//--- Build Akima spline using Hermite interpolation scheme
|
|
Spline1DBuildHermite(x,y,d,n,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates the value of the spline at the given |
|
|
//| point X. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| X - point |
|
|
//| Result: |
|
|
//| S(x) |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1D::Spline1DCalc(CSpline1DInterpolant &c,double x)
|
|
{
|
|
//--- create variables
|
|
int l=0;
|
|
int r=0;
|
|
int m=0;
|
|
double t=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
|
|
return(EMPTY_VALUE);
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
|
|
return(EMPTY_VALUE);
|
|
//--- special case: NaN
|
|
if(CInfOrNaN::IsNaN(x))
|
|
return(CInfOrNaN::NaN());
|
|
//--- correct if periodic
|
|
if(c.m_periodic)
|
|
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
|
|
//--- Binary search in the [ x[0],...,x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=c.m_n-2+1;
|
|
while(l!=r-1)
|
|
{
|
|
m=(l+r)/2;
|
|
//--- check
|
|
if(c.m_x[m]>=x)
|
|
r=m;
|
|
else
|
|
l=m;
|
|
}
|
|
//--- Interpolation
|
|
x=x-c.m_x[l];
|
|
m=4*l;
|
|
//--- return result
|
|
return(c.m_c[m]+x*(c.m_c[m+1]+x*(c.m_c[m+2]+x*c.m_c[m+3])));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine differentiates the spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X - point |
|
|
//| Result: |
|
|
//| S - S(x) |
|
|
//| DS - S'(x) |
|
|
//| D2S - S''(x) |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DDiff(CSpline1DInterpolant &c,double x,
|
|
double &s,double &ds,double &d2s)
|
|
{
|
|
//--- create variables
|
|
int l=0;
|
|
int r=0;
|
|
int m=0;
|
|
double t=0;
|
|
//--- initialization
|
|
s=0;
|
|
ds=0;
|
|
d2s=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(!CInfOrNaN::IsInfinity(x),__FUNCTION__+": infinite X!"))
|
|
return;
|
|
//--- special case: NaN
|
|
if(CInfOrNaN::IsNaN(x))
|
|
{
|
|
//--- change values
|
|
s=CInfOrNaN::NaN();
|
|
ds=CInfOrNaN::NaN();
|
|
d2s=CInfOrNaN::NaN();
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- correct if periodic
|
|
if(c.m_periodic)
|
|
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
|
|
//--- Binary search
|
|
l=0;
|
|
r=c.m_n-2+1;
|
|
while(l!=r-1)
|
|
{
|
|
m=(l+r)/2;
|
|
//--- check
|
|
if(c.m_x[m]>=x)
|
|
r=m;
|
|
else
|
|
l=m;
|
|
}
|
|
//--- Differentiation
|
|
x=x-c.m_x[l];
|
|
m=4*l;
|
|
s=c.m_c[m]+x*(c.m_c[m+1]+x*(c.m_c[m+2]+x*c.m_c[m+3]));
|
|
ds=c.m_c[m+1]+2*x*c.m_c[m+2]+3*CMath::Sqr(x)*c.m_c[m+3];
|
|
d2s=2*c.m_c[m+2]+6*x*c.m_c[m+3];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine makes the copy of the spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| Result: |
|
|
//| CC - spline copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DCopy(CSpline1DInterpolant &c,CSpline1DInterpolant &cc)
|
|
{
|
|
//--- change values
|
|
cc.m_periodic=c.m_periodic;
|
|
cc.m_n=c.m_n;
|
|
cc.m_k=c.m_k;
|
|
cc.m_continuity=c.m_continuity;
|
|
//--- allocation
|
|
ArrayResize(cc.m_x,cc.m_n);
|
|
//--- copy
|
|
for(int i_=0; i_<=cc.m_n-1; i_++)
|
|
cc.m_x[i_]=c.m_x[i_];
|
|
//--- allocation
|
|
ArrayResize(cc.m_c,(cc.m_k+1)*(cc.m_n-1));
|
|
//--- copy
|
|
for(int i_=0; i_<=(cc.m_k+1)*(cc.m_n-1)-1; i_++)
|
|
cc.m_c[i_]=c.m_c[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine unpacks the spline into the coefficients table. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X - point |
|
|
//| Result: |
|
|
//| Tbl - coefficients table, unpacked format, array[0..N-2, |
|
|
//| 0..5]. |
|
|
//| For I = 0...N-2: |
|
|
//| Tbl[I,0] = X[i] |
|
|
//| Tbl[I,1] = X[i+1] |
|
|
//| Tbl[I,2] = C0 |
|
|
//| Tbl[I,3] = C1 |
|
|
//| Tbl[I,4] = C2 |
|
|
//| Tbl[I,5] = C3 |
|
|
//| On [x[i], x[i+1]] spline is equals to: |
|
|
//| S(x) = C0 + C1*t + C2*t^2 + C3*t^3 |
|
|
//| t = x-x[i] |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DUnpack(CSpline1DInterpolant &c,int &n,
|
|
CMatrixDouble &tbl)
|
|
{
|
|
//--- allocation
|
|
tbl.Resize(c.m_n-2+1,2+c.m_k+1);
|
|
//--- initialization
|
|
n=c.m_n;
|
|
//--- Fill
|
|
for(int i=0; i<=n-2; i++)
|
|
{
|
|
tbl.Set(i,0,c.m_x[i]);
|
|
tbl.Set(i,1,c.m_x[i+1]);
|
|
for(int j=0; j<=c.m_k; j++)
|
|
tbl.Set(i,2+j,c.m_c[(c.m_k+1)*i+j]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline |
|
|
//| argument. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| A, B- transformation coefficients: x = A*t + B |
|
|
//| Result: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DLinTransX(CSpline1DInterpolant &c,const double a,
|
|
const double b)
|
|
{
|
|
//--- create variables
|
|
int n=c.m_n;
|
|
double v=0;
|
|
double dv=0;
|
|
double d2v=0;
|
|
//--- create arrays
|
|
double x[];
|
|
double y[];
|
|
double d[];
|
|
//--- Special case: A=0
|
|
if(a==0.0)
|
|
{
|
|
v=Spline1DCalc(c,b);
|
|
for(int i=0; i<=n-2; i++)
|
|
{
|
|
c.m_c[(c.m_k+1)*i]=v;
|
|
for(int j=1; j<=c.m_k; j++)
|
|
c.m_c[(c.m_k+1)*i+j]=0;
|
|
}
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- General case: A<>0.
|
|
//--- Unpack,X,Y,dY/dX.
|
|
//--- Scale and pack again.
|
|
if(!CAp::Assert(c.m_k==3,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(x,n);
|
|
ArrayResize(y,n);
|
|
ArrayResize(d,n);
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
x[i]=c.m_x[i];
|
|
Spline1DDiff(c,x[i],v,dv,d2v);
|
|
x[i]=(x[i]-b)/a;
|
|
y[i]=v;
|
|
d[i]=a*dv;
|
|
}
|
|
//--- function call
|
|
Spline1DBuildHermite(x,y,d,n,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| A,B- transformation coefficients: S2(x)=A*S(x) + B |
|
|
//| Result: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DLinTransY(CSpline1DInterpolant &c,const double a,
|
|
const double b)
|
|
{
|
|
int n=c.m_n;
|
|
//--- calculation
|
|
for(int i=0; i<=n-2; i++)
|
|
{
|
|
c.m_c[(c.m_k+1)*i]=a*c.m_c[(c.m_k+1)*i]+b;
|
|
for(int j=1; j<=c.m_k; j++)
|
|
c.m_c[(c.m_k+1)*i+j]=a*c.m_c[(c.m_k+1)*i+j];
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine integrates the spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X - right bound of the integration interval [a, x], |
|
|
//| here 'a' denotes min(x[]) |
|
|
//| Result: |
|
|
//| integral(S(t)dt,a,x) |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1D::Spline1DIntegrate(CSpline1DInterpolant &c,double x)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int n=c.m_n;
|
|
int l=0;
|
|
int r=0;
|
|
int m=0;
|
|
double w=0;
|
|
double v=0;
|
|
double t=0;
|
|
double intab=0;
|
|
double additionalterm=0;
|
|
//--- Periodic splines require special treatment. We make
|
|
//--- following transformation:
|
|
//--- integral(S(t)dt,A,X)=integral(S(t)dt,A,Z)+AdditionalTerm
|
|
//--- here X may lie outside of [A,B],Z lies strictly in [A,B],
|
|
//--- AdditionalTerm is equals to integral(S(t)dt,A,B) times some
|
|
//--- integer number (may be zero).
|
|
if(c.m_periodic && (x<c.m_x[0] || x>c.m_x[c.m_n-1]))
|
|
{
|
|
//--- compute integral(S(x)dx,A,B)
|
|
intab=0;
|
|
for(int i=0; i<=c.m_n-2; i++)
|
|
{
|
|
w=c.m_x[i+1]-c.m_x[i];
|
|
m=(c.m_k+1)*i;
|
|
intab=intab+c.m_c[m]*w;
|
|
v=w;
|
|
for(int j=1; j<=c.m_k; j++)
|
|
{
|
|
v=v*w;
|
|
intab=intab+c.m_c[m+j]*v/(j+1);
|
|
}
|
|
}
|
|
//--- map X into [A,B]
|
|
CApServ::ApPeriodicMap(x,c.m_x[0],c.m_x[c.m_n-1],t);
|
|
additionalterm=t*intab;
|
|
}
|
|
else
|
|
additionalterm=0;
|
|
//--- Binary search in the [ x[0],...,x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=n-2+1;
|
|
while(l!=r-1)
|
|
{
|
|
m=(l+r)/2;
|
|
//--- check
|
|
if(c.m_x[m]>=x)
|
|
r=m;
|
|
else
|
|
l=m;
|
|
}
|
|
//--- Integration
|
|
result=0;
|
|
for(int i=0; i<=l-1; i++)
|
|
{
|
|
w=c.m_x[i+1]-c.m_x[i];
|
|
m=(c.m_k+1)*i;
|
|
result=result+c.m_c[m]*w;
|
|
v=w;
|
|
//--- calculation
|
|
for(int j=1; j<=c.m_k; j++)
|
|
{
|
|
v=v*w;
|
|
result=result+c.m_c[m+j]*v/(j+1);
|
|
}
|
|
}
|
|
//--- change values
|
|
w=x-c.m_x[l];
|
|
m=(c.m_k+1)*l;
|
|
v=w;
|
|
result=result+c.m_c[m]*w;
|
|
//--- calculation
|
|
for(int j=1; j<=c.m_k; j++)
|
|
{
|
|
v=v*w;
|
|
result=result+c.m_c[m+j]*v/(j+1);
|
|
}
|
|
//--- return result
|
|
return(result+additionalterm);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fitting by smoothing (penalized) cubic spline. |
|
|
//| This function approximates N scattered points (some of X[] may |
|
|
//| be equal to each other) by cubic spline with M nodes at |
|
|
//| equidistant grid spanning interval [min(x,xc),max(x,xc)]. |
|
|
//| The problem is regularized by adding nonlinearity penalty to |
|
|
//| usual least squares penalty function: |
|
|
//| MERIT_FUNC = F_LS + F_NL |
|
|
//| where F_LS is a least squares error term, and F_NL is a |
|
|
//| nonlinearity penalty which is roughly proportional to |
|
|
//| LambdaNS*integral{ S''(x)^2*dx }. Algorithm applies automatic |
|
|
//| renormalization of F_NL which makes penalty term roughly |
|
|
//| invariant to scaling of X[] and changes in M. |
|
|
//| This function is a new edition of penalized regression spline|
|
|
//| fitting, a fast and compact one which needs much less resources |
|
|
//| that its previous version: just O(maxMN) memory and |
|
|
//| O(maxMN*log(maxMN)) time. |
|
|
//| NOTE: it is OK to run this function with both M<<N and M>>N; say,|
|
|
//| it is possible to process 100 points with 1000-node spline.|
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| N - number of points (optional): |
|
|
//| * N>0 |
|
|
//| * if given, only first N elements of X/Y are |
|
|
//| processed |
|
|
//| * if not given, automatically determined from |
|
|
//| lengths |
|
|
//| M - number of basis functions ( = number_of_nodes), |
|
|
//| M>=4. |
|
|
//| LambdaNS - LambdaNS>=0, regularization constant passed by |
|
|
//| user. It penalizes nonlinearity in the regression |
|
|
//| spline. Possible values to start from are 0.00001, |
|
|
//| 0.1, 1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - spline interpolant. |
|
|
//| Rep - Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DFit(double &X[],double &Y[],int n,int m,
|
|
double lambdans,CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
int bfrad=0;
|
|
double xa=0;
|
|
double xb=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double v=0;
|
|
double dv=0;
|
|
double d2v=0;
|
|
int gridexpansion=0;
|
|
double xywork[];
|
|
CMatrixDouble vterm;
|
|
double sx[];
|
|
double sy[];
|
|
double sdy[];
|
|
double tmpx[];
|
|
double tmpy[];
|
|
CSpline1DInterpolant basis1;
|
|
CSparseMatrix av;
|
|
CSparseMatrix ah;
|
|
CSparseMatrix ata;
|
|
CRowDouble targets;
|
|
double meany=0;
|
|
int lsqrcnt=0;
|
|
int nrel=0;
|
|
double rss=0;
|
|
double tss=0;
|
|
int arows=0;
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
CLinLSQRState solver;
|
|
CLinLSQRReport srep;
|
|
double creg=0;
|
|
double mxata=0;
|
|
int bw=0;
|
|
CRowInt nzidx;
|
|
CRowDouble nzval;
|
|
int nzcnt=0;
|
|
double scaletargetsby=0;
|
|
double scalepenaltyby=0;
|
|
double x[];
|
|
double y[];
|
|
ArrayCopy(x,X);
|
|
ArrayCopy(y,Y);
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is infinite!"))
|
|
return;
|
|
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0!"))
|
|
return;
|
|
bfrad=2;
|
|
lsqrcnt=10;
|
|
//--- Sort points.
|
|
//--- Determine actual area size, make sure that XA<XB
|
|
CTSort::TagSortFastR(x,y,tmpx,tmpy,n);
|
|
xa=x[0];
|
|
xb=x[n-1];
|
|
if(xa==xb)
|
|
{
|
|
v=xa;
|
|
if(v>=0.0)
|
|
{
|
|
xa=v/2-1;
|
|
xb=v*2+1;
|
|
}
|
|
else
|
|
{
|
|
xa=v*2-1;
|
|
xb=v/2+1;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(xa<xb,__FUNCTION__+": integrity error"))
|
|
return;
|
|
//--- Perform a grid correction according to current grid expansion size.
|
|
m=MathMax(m,4);
|
|
gridexpansion=1;
|
|
v=(xb-xa)/m;
|
|
xa=xa-v*gridexpansion;
|
|
xb=xb+v*gridexpansion;
|
|
m=m+2*gridexpansion;
|
|
//--- Convert X/Y to work representation, remove linear trend (in
|
|
//--- order to improve condition number).
|
|
//--- Compute total-sum-of-squares (needed later for R2 coefficient).
|
|
ArrayResize(xywork,2*n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
xywork[2*i+0]=(x[i]-xa)/(xb-xa);
|
|
xywork[2*i+1]=y[i];
|
|
}
|
|
CIntFitServ::BuildPriorTerm1(xywork,n,1,1,1,0.0,vterm);
|
|
meany=0;
|
|
for(i=0; i<n; i++)
|
|
meany+=y[i];
|
|
meany=meany/n;
|
|
tss=0;
|
|
for(i=0; i<n; i++)
|
|
tss+=CMath::Sqr(y[i]-meany);
|
|
//--- Build 1D compact basis function
|
|
//--- Generate design matrix AV ("vertical") and its transpose AH ("horizontal").
|
|
ArrayResize(tmpx,7);
|
|
ArrayResize(tmpy,7);
|
|
tmpx[0]=-(3.0/(double)(m-1));
|
|
tmpx[1]=-(2.0/(double)(m-1));
|
|
tmpx[2]=-(1.0/(double)(m-1));
|
|
tmpx[3]=0.0;
|
|
tmpx[4]=1.0/(double)(m-1);
|
|
tmpx[5]=2.0/(double)(m-1);
|
|
tmpx[6]=3.0/(double)(m-1);
|
|
tmpy[0]=0.0;
|
|
tmpy[1]=0.0;
|
|
tmpy[2]=1.0/12.0;
|
|
tmpy[3]=2.0/6.0;
|
|
tmpy[4]=1.0/12.0;
|
|
tmpy[5]=0.0;
|
|
tmpy[6]=0.0;
|
|
Spline1DBuildCubic(tmpx,tmpy,CAp::Len(tmpx),2,0.0,2,0.0,basis1);
|
|
arows=n+2*m;
|
|
CSparse::SparseCreate(arows,m,0,av);
|
|
targets=vector<double>::Zeros(arows);
|
|
scaletargetsby=1/MathSqrt(n);
|
|
scalepenaltyby=1/MathSqrt(m);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Generate design matrix row #I which corresponds to I-th dataset point
|
|
k=(int)MathFloor(CApServ::BoundVal(xywork[2*i+0]*(m-1),0.0,m-1.0));
|
|
k0=MathMax(k-(bfrad-1),0);
|
|
k1=MathMin(k+bfrad,m-1);
|
|
for(j=k0; j<=k1; j++)
|
|
CSparse::SparseSet(av,i,j,Spline1DCalc(basis1,xywork[2*i+0]-(double)j/(double)(m-1))*scaletargetsby);
|
|
targets.Set(i,xywork[2*i+1]*scaletargetsby);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- Generate design matrix row #(I+N) which corresponds to nonlinearity penalty at I-th node
|
|
k0=MathMax(i-(bfrad-1),0);
|
|
k1=MathMin(i+(bfrad-1),m-1);
|
|
for(j=k0; j<=k1; j++)
|
|
{
|
|
Spline1DDiff(basis1,(double)i/(double)(m-1)-(double)j/(double)(m-1),v,dv,d2v);
|
|
CSparse::SparseSet(av,n+i,j,lambdans*d2v*scalepenaltyby);
|
|
}
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- Generate design matrix row #(I+N+M) which corresponds to regularization for I-th coefficient
|
|
CSparse::SparseSet(av,n+m+i,i,m_lambdareg);
|
|
}
|
|
CSparse::SparseConvertToCRS(av);
|
|
CSparse::SparseCopyTransposeCRS(av,ah);
|
|
//--- Build 7-diagonal (bandwidth=3) normal equations matrix and perform Cholesky
|
|
//--- decomposition (to be used later as preconditioner for LSQR iterations).
|
|
bw=3;
|
|
CSparse::SparseCreateSKSBand(m,m,bw,ata);
|
|
mxata=0;
|
|
for(i=0; i<m; i++)
|
|
{
|
|
for(j=i; j<=MathMin(i+bw,m-1); j++)
|
|
{
|
|
//--- Get pattern of nonzeros in one of the rows (let it be I-th one)
|
|
//--- and compute dot product only for nonzero entries.
|
|
CSparse::SparseGetCompressedRow(ah,i,nzidx,nzval,nzcnt);
|
|
v=0;
|
|
for(k=0; k<nzcnt; k++)
|
|
v+=CSparse::SparseGet(ah,i,nzidx[k])*CSparse::SparseGet(ah,j,nzidx[k]);
|
|
//--- Update ATA and max(ATA)
|
|
CSparse::SparseSet(ata,i,j,v);
|
|
if(i==j)
|
|
mxata=MathMax(mxata,MathAbs(v));
|
|
}
|
|
}
|
|
mxata=CApServ::Coalesce(mxata,1.0);
|
|
creg=m_cholreg;
|
|
while(true)
|
|
{
|
|
//--- Regularization
|
|
for(i=0; i<=m-1; i++)
|
|
CSparse::SparseSet(ata,i,i,CSparse::SparseGet(ata,i,i)+mxata*creg);
|
|
//--- Try Cholesky factorization.
|
|
if(!CTrFac::SparseCholeskySkyLine(ata,m,true))
|
|
{
|
|
//--- Factorization failed, increase regularizer and repeat
|
|
creg=CApServ::Coalesce(10*creg,1.0E-12);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
//--- Solve with preconditioned LSQR:
|
|
//--- use Cholesky factor U of squared design matrix A'*A to
|
|
//--- transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
|
|
//--- Preconditioned problem is solved with LSQR solver, which
|
|
//--- gives superior results to normal equations approach. Due
|
|
//--- to Cholesky preconditioner being utilized we can solve
|
|
//--- problem in just a few iterations.
|
|
CApServ::RVectorSetLengthAtLeast(tmp0,arows);
|
|
CApServ::RVectorSetLengthAtLeast(tmp1,m);
|
|
CLinLSQR::LinLSQRCreateBuf(arows,m,solver);
|
|
CLinLSQR::LinLSQRSetB(solver,targets);
|
|
CLinLSQR::LinLSQRSetCond(solver,1.0E-14,1.0E-14,lsqrcnt);
|
|
while(CLinLSQR::LinLSQRIteration(solver))
|
|
{
|
|
if(solver.m_needmv)
|
|
{
|
|
tmp1=solver.m_x;
|
|
//--- Use Cholesky factorization of the system matrix
|
|
//--- as preconditioner: solve TRSV(U,Solver.X)
|
|
CSparse::SparseTRSV(ata,true,false,0,tmp1);
|
|
//--- After preconditioning is done, multiply by A
|
|
CSparse::SparseMV(av,tmp1,solver.m_mv);
|
|
}
|
|
if(solver.m_needmtv)
|
|
{
|
|
//--- Multiply by design matrix A
|
|
CSparse::SparseMTV(av,solver.m_x,solver.m_mtv);
|
|
//--- Multiply by preconditioner: solve TRSV(U',A*Solver.X)
|
|
CSparse::SparseTRSV(ata,true,false,1,solver.m_mtv);
|
|
}
|
|
}
|
|
CLinLSQR::LinLSQRResults(solver,tmp1,srep);
|
|
CSparse::SparseTRSV(ata,true,false,0,tmp1);
|
|
//--- Generate output spline as a table of spline valued and first
|
|
//--- derivatives at nodes (used to build Hermite spline)
|
|
ArrayResize(sx,m);
|
|
ArrayResize(sy,m);
|
|
ArrayResize(sdy,m);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
sx[i]=(double)i/(double)(m-1);
|
|
sy[i]=0;
|
|
sdy[i]=0;
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
k0=MathMax(i-(bfrad-1),0);
|
|
k1=MathMin(i+bfrad,m-1);
|
|
for(j=k0; j<=k1; j++)
|
|
{
|
|
Spline1DDiff(basis1,(double)j/(double)(m-1)-(double)i/(double)(m-1),v,dv,d2v);
|
|
sy[j]=sy[j]+tmp1[i]*v;
|
|
sdy[j]=sdy[j]+tmp1[i]*dv;
|
|
}
|
|
}
|
|
//--- Calculate model values
|
|
CSparse::SparseMV(av,tmp1,tmp0);
|
|
tmp0/=scaletargetsby;
|
|
rss=0.0;
|
|
nrel=0;
|
|
rep.m_rmserror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_avgrelerror=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=xywork[2*i+1]-tmp0[i];
|
|
rss+=v*v;
|
|
rep.m_rmserror+=CMath::Sqr(v);
|
|
rep.m_avgerror+=MathAbs(v);
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
if(y[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror+=MathAbs(v/y[i]);
|
|
nrel+=1;
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
|
|
rep.m_avgerror/=n;
|
|
rep.m_avgrelerror/=CApServ::Coalesce(nrel,1.0);
|
|
//--- Append prior term.
|
|
//--- Transform spline to original coordinates.
|
|
//--- Output.
|
|
for(i=0; i<m; i++)
|
|
{
|
|
sy[i]+=vterm.Get(0,0)*sx[i]+vterm.Get(0,1);
|
|
sdy[i]+=vterm.Get(0,0);
|
|
}
|
|
for(i=0; i<m; i++)
|
|
{
|
|
sx[i]=sx[i]*(xb-xa)+xa;
|
|
sdy[i]/=(xb-xa);
|
|
}
|
|
Spline1DBuildHermite(sx,sy,sdy,m,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds monotone cubic Hermite interpolant. This |
|
|
//| interpolant is monotonic in [x(0),x(n-1)] and is constant outside|
|
|
//| of this interval. |
|
|
//| In case y[] form non-monotonic sequence, interpolant is |
|
|
//| piecewise monotonic. Say, for x=(0,1,2,3,4) and y=(0,1,2,1,0) |
|
|
//| interpolant will monotonically grow at [0..2] and monotonically |
|
|
//| decrease at [2..4]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline nodes, array[0..N-1]. Subroutine |
|
|
//| automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//| Y - function values, array[0..N-1] |
|
|
//| N - the number of points(N>=2). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DBuildMonotone(double &X[],double &Y[],int n,
|
|
CSpline1DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
double d[];
|
|
double ex[];
|
|
double ey[];
|
|
int p[];
|
|
double delta=0;
|
|
double alpha=0;
|
|
double beta=0;
|
|
int tmpn=0;
|
|
int sn=0;
|
|
double ca=0;
|
|
double cb=0;
|
|
double epsilon=0;
|
|
int i=0;
|
|
int j=0;
|
|
double x[];
|
|
double y[];
|
|
ArrayCopy(x,X);
|
|
ArrayCopy(y,Y);
|
|
//--- Check lengths of arguments
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N"))
|
|
return;
|
|
//--- Check and sort points
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values"))
|
|
return;
|
|
HeapSortPPoints(x,y,p,n);
|
|
if(!CAp::Assert(CApServ::AreDistinct(x,n),__FUNCTION__+": at least two consequent points are too close"))
|
|
return;
|
|
|
|
epsilon=CMath::m_machineepsilon;
|
|
n+=2;
|
|
ArrayResize(d,n);
|
|
ArrayResize(ex,n);
|
|
ArrayResize(ey,n);
|
|
ex[0]=x[0]-MathAbs(x[1]-x[0]);
|
|
ex[n-1]=x[n-3]+MathAbs(x[n-3]-x[n-4]);
|
|
ey[0]=y[0];
|
|
ey[n-1]=y[n-3];
|
|
for(i=1; i<=n-2; i++)
|
|
{
|
|
ex[i]=x[i-1];
|
|
ey[i]=y[i-1];
|
|
}
|
|
//--- Init sign of the function for first segment
|
|
i=0;
|
|
ca=0;
|
|
do
|
|
{
|
|
ca=ey[i+1]-ey[i];
|
|
i++;
|
|
}
|
|
while(!(ca!=0.0 || i>n-2));
|
|
if(ca!=0.0)
|
|
ca/=MathAbs(ca);
|
|
i=0;
|
|
while(i<n-1)
|
|
{
|
|
//--- Partition of the segment [X0;Xn]
|
|
tmpn=1;
|
|
for(j=i; j<n-1; j++)
|
|
{
|
|
cb=ey[j+1]-ey[j];
|
|
if((ca*cb)>=0.0)
|
|
tmpn++;
|
|
else
|
|
{
|
|
ca=cb/MathAbs(cb);
|
|
break;
|
|
}
|
|
}
|
|
sn=i+tmpn;
|
|
//--- check
|
|
if(!CAp::Assert(tmpn>=2,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- Calculate derivatives for current segment
|
|
d[i]=0;
|
|
d[sn-1]=0;
|
|
for(j=i+1; j<sn-1; j++)
|
|
d[j]=((ey[j]-ey[j-1])/(ex[j]-ex[j-1])+(ey[j+1]-ey[j])/(ex[j+1]-ex[j]))/2;
|
|
for(j=i; j<sn-1; j++)
|
|
{
|
|
delta=(ey[j+1]-ey[j])/(ex[j+1]-ex[j]);
|
|
if(MathAbs(delta)<=epsilon)
|
|
{
|
|
d[j]=0;
|
|
d[j+1]=0;
|
|
}
|
|
else
|
|
{
|
|
alpha=d[j]/delta;
|
|
beta=d[j+1]/delta;
|
|
if(alpha!=0.0)
|
|
cb=alpha*MathSqrt(1+CMath::Sqr(beta/alpha));
|
|
else
|
|
{
|
|
if(beta!=0.0)
|
|
cb=beta;
|
|
else
|
|
continue;
|
|
}
|
|
if(cb>3.0)
|
|
{
|
|
d[j]=3*alpha*delta/cb;
|
|
d[j+1]=3*beta*delta/cb;
|
|
}
|
|
}
|
|
}
|
|
//--- Transition to next segment
|
|
i=sn-1;
|
|
}
|
|
Spline1DBuildHermite(ex,ey,d,n,c);
|
|
c.m_continuity=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal version of Spline1DConvDiff |
|
|
//| Converts from Hermite spline given by grid XOld to new grid X2 |
|
|
//| INPUT PARAMETERS: |
|
|
//| XOld - old grid |
|
|
//| YOld - values at old grid |
|
|
//| DOld - first derivative at old grid |
|
|
//| N - grid size |
|
|
//| X2 - new grid |
|
|
//| N2 - new grid size |
|
|
//| Y - possibly preallocated output array |
|
|
//| (reallocate if too small) |
|
|
//| NeedY - do we need Y? |
|
|
//| D1 - possibly preallocated output array |
|
|
//| (reallocate if too small) |
|
|
//| NeedD1 - do we need D1? |
|
|
//| D2 - possibly preallocated output array |
|
|
//| (reallocate if too small) |
|
|
//| NeedD2 - do we need D1? |
|
|
//| OUTPUT ARRAYS: |
|
|
//| Y - values, if needed |
|
|
//| D1 - first derivative, if needed |
|
|
//| D2 - second derivative, if needed |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DConvDiffInternal(double &xold[],double &yold[],
|
|
double &dold[],const int n,
|
|
double &x2[],const int n2,
|
|
double &y[],const bool needy,
|
|
double &d1[],const bool needd1,
|
|
double &d2[],const bool needd2)
|
|
{
|
|
//--- create variables
|
|
int intervalindex=0;
|
|
int pointindex=0;
|
|
bool havetoadvance;
|
|
double c0=0;
|
|
double c1=0;
|
|
double c2=0;
|
|
double c3=0;
|
|
double a=0;
|
|
double b=0;
|
|
double w=0;
|
|
double w2=0;
|
|
double w3=0;
|
|
double fa=0;
|
|
double fb=0;
|
|
double da=0;
|
|
double db=0;
|
|
double t=0;
|
|
//--- Prepare space
|
|
if(needy && CAp::Len(y)<n2)
|
|
ArrayResize(y,n2);
|
|
//--- check
|
|
if(needd1 && CAp::Len(d1)<n2)
|
|
ArrayResize(d1,n2);
|
|
//--- check
|
|
if(needd2 && CAp::Len(d2)<n2)
|
|
ArrayResize(d2,n2);
|
|
//--- These assignments aren't actually needed
|
|
//--- (variables are initialized in the loop below),
|
|
//--- but without them compiler will complain about uninitialized locals
|
|
c0=0;
|
|
c1=0;
|
|
c2=0;
|
|
c3=0;
|
|
a=0;
|
|
b=0;
|
|
//--- Cycle
|
|
intervalindex=-1;
|
|
pointindex=0;
|
|
//--- calculation
|
|
while(true)
|
|
{
|
|
//--- are we ready to exit?
|
|
if(pointindex>=n2)
|
|
break;
|
|
t=x2[pointindex];
|
|
//--- do we need to advance interval?
|
|
havetoadvance=false;
|
|
//--- check
|
|
if(intervalindex==-1)
|
|
havetoadvance=true;
|
|
else
|
|
{
|
|
//--- check
|
|
if(intervalindex<n-2)
|
|
havetoadvance=t>=b;
|
|
}
|
|
//--- check
|
|
if(havetoadvance)
|
|
{
|
|
//--- change values
|
|
intervalindex=intervalindex+1;
|
|
a=xold[intervalindex];
|
|
b=xold[intervalindex+1];
|
|
w=b-a;
|
|
w2=w*w;
|
|
w3=w*w2;
|
|
fa=yold[intervalindex];
|
|
fb=yold[intervalindex+1];
|
|
da=dold[intervalindex];
|
|
db=dold[intervalindex+1];
|
|
c0=fa;
|
|
c1=da;
|
|
c2=(3*(fb-fa)-2*da*w-db*w)/w2;
|
|
c3=(2*(fa-fb)+da*w+db*w)/w3;
|
|
continue;
|
|
}
|
|
//--- Calculate spline and its derivatives using power basis
|
|
t=t-a;
|
|
if(needy)
|
|
y[pointindex]=c0+t*(c1+t*(c2+t*c3));
|
|
//--- check
|
|
if(needd1)
|
|
d1[pointindex]=c1+2*t*c2+3*t*t*c3;
|
|
//--- check
|
|
if(needd2)
|
|
d2[pointindex]=2*c2+6*t*c3;
|
|
//--- change value
|
|
pointindex=pointindex+1;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Heap sort. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::HeapSortDPoints(double &x[],double &y[],double &d[],
|
|
const int n)
|
|
{
|
|
//--- create arrays
|
|
double rbuf[];
|
|
int ibuf[];
|
|
double rbuf2[];
|
|
int ibuf2[];
|
|
//--- allocation
|
|
ArrayResize(ibuf,n);
|
|
ArrayResize(rbuf,n);
|
|
for(int i=0; i<n; i++)
|
|
ibuf[i]=i;
|
|
//--- function call
|
|
CTSort::TagSortFastI(x,ibuf,rbuf2,ibuf2,n);
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
rbuf[i]=y[ibuf[i]];
|
|
ArrayCopy(y,rbuf);
|
|
for(int i=0; i<n; i++)
|
|
rbuf[i]=d[ibuf[i]];
|
|
ArrayCopy(d,rbuf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Heap sort. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::HeapSortDPoints(CRowDouble &x,CRowDouble &y,CRowDouble &d,
|
|
const int n)
|
|
{
|
|
//--- create arrays
|
|
CRowDouble rbuf;
|
|
CRowInt ibuf;
|
|
CRowDouble rbuf2;
|
|
CRowInt ibuf2;
|
|
//--- allocation
|
|
ibuf.Resize(n);
|
|
rbuf.Resize(n);
|
|
for(int i=0; i<n; i++)
|
|
ibuf.Set(i,i);
|
|
//--- function call
|
|
CTSort::TagSortFastI(x,ibuf,rbuf2,ibuf2,n);
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
rbuf.Set(i,y[ibuf[i]]);
|
|
for(int i_=0; i_<n; i_++)
|
|
y.Set(i_,rbuf[i_]);
|
|
for(int i=0; i<n; i++)
|
|
rbuf.Set(i,d[ibuf[i]]);
|
|
for(int i_=0; i_<n; i_++)
|
|
d.Set(i_,rbuf[i_]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal version of Spline1DGridDiffCubic. |
|
|
//| Accepts pre-ordered X/Y, temporary arrays (which may be |
|
|
//| preallocated, if you want to save time, or not) and output array |
|
|
//| (which may be preallocated too). |
|
|
//| Y is passed as var-parameter because we may need to force last |
|
|
//| element to be equal to the first one (if periodic boundary |
|
|
//| conditions are specified). |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::Spline1DGridDiffCubicInternal(double &x[],double &y[],
|
|
const int n,const int boundltype,
|
|
const double boundl,
|
|
const int boundrtype,
|
|
const double boundr,
|
|
double &d[],double &a1[],
|
|
double &a2[],double &a3[],
|
|
double &b[],double &dt[])
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int i_=0;
|
|
//--- allocate arrays
|
|
if(CAp::Len(d)<n)
|
|
ArrayResize(d,n);
|
|
//--- check
|
|
if(CAp::Len(a1)<n)
|
|
ArrayResize(a1,n);
|
|
//--- check
|
|
if(CAp::Len(a2)<n)
|
|
ArrayResize(a2,n);
|
|
//--- check
|
|
if(CAp::Len(a3)<n)
|
|
ArrayResize(a3,n);
|
|
//--- check
|
|
if(CAp::Len(b)<n)
|
|
ArrayResize(b,n);
|
|
//--- check
|
|
if(CAp::Len(dt)<n)
|
|
ArrayResize(dt,n);
|
|
//--- Special cases:
|
|
//--- * N=2,parabolic terminated boundary condition on both ends
|
|
//--- * N=2,periodic boundary condition
|
|
if((n==2 && boundltype==0) && boundrtype==0)
|
|
{
|
|
//--- change values
|
|
d[0]=(y[1]-y[0])/(x[1]-x[0]);
|
|
d[1]=d[0];
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- check
|
|
if((n==2 && boundltype==-1) && boundrtype==-1)
|
|
{
|
|
//--- change values
|
|
d[0]=0;
|
|
d[1]=0;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- Periodic and non-periodic boundary conditions are
|
|
//--- two separate classes
|
|
if(boundrtype==-1 && boundltype==-1)
|
|
{
|
|
//--- Periodic boundary conditions
|
|
y[n-1]=y[0];
|
|
//--- Boundary conditions at N-1 points
|
|
//--- (one point less because last point is the same as first point).
|
|
a1[0]=x[1]-x[0];
|
|
a2[0]=2*(x[1]-x[0]+x[n-1]-x[n-2]);
|
|
a3[0]=x[n-1]-x[n-2];
|
|
b[0]=3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])*(x[1]-x[0])+3*(y[1]-y[0])/(x[1]-x[0])*(x[n-1]-x[n-2]);
|
|
//--- calculation
|
|
for(i=1; i<=n-2; i++)
|
|
{
|
|
//--- Altough last point is [N-2],we use X[N-1] and Y[N-1]
|
|
//--- (because of periodicity)
|
|
a1[i]=x[i+1]-x[i];
|
|
a2[i]=2*(x[i+1]-x[i-1]);
|
|
a3[i]=x[i]-x[i-1];
|
|
b[i]=3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
|
|
}
|
|
//--- Solve,add last point (with index N-1)
|
|
SolveCyclicTridiagonal(a1,a2,a3,b,n-1,dt);
|
|
for(i_=0; i_<=n-2; i_++)
|
|
d[i_]=dt[i_];
|
|
d[n-1]=d[0];
|
|
}
|
|
else
|
|
{
|
|
//--- Non-periodic boundary condition.
|
|
//--- Left boundary conditions.
|
|
if(boundltype==0)
|
|
{
|
|
//--- change values
|
|
a1[0]=0;
|
|
a2[0]=1;
|
|
a3[0]=1;
|
|
b[0]=2*(y[1]-y[0])/(x[1]-x[0]);
|
|
}
|
|
//--- check
|
|
if(boundltype==1)
|
|
{
|
|
//--- change values
|
|
a1[0]=0;
|
|
a2[0]=1;
|
|
a3[0]=0;
|
|
b[0]=boundl;
|
|
}
|
|
//--- check
|
|
if(boundltype==2)
|
|
{
|
|
//--- change values
|
|
a1[0]=0;
|
|
a2[0]=2;
|
|
a3[0]=1;
|
|
b[0]=3*(y[1]-y[0])/(x[1]-x[0])-0.5*boundl*(x[1]-x[0]);
|
|
}
|
|
//--- Central conditions
|
|
for(i=1; i<=n-2; i++)
|
|
{
|
|
a1[i]=x[i+1]-x[i];
|
|
a2[i]=2*(x[i+1]-x[i-1]);
|
|
a3[i]=x[i]-x[i-1];
|
|
b[i]=3*(y[i]-y[i-1])/(x[i]-x[i-1])*(x[i+1]-x[i])+3*(y[i+1]-y[i])/(x[i+1]-x[i])*(x[i]-x[i-1]);
|
|
}
|
|
//--- Right boundary conditions
|
|
if(boundrtype==0)
|
|
{
|
|
//--- change values
|
|
a1[n-1]=1;
|
|
a2[n-1]=1;
|
|
a3[n-1]=0;
|
|
b[n-1]=2*(y[n-1]-y[n-2])/(x[n-1]-x[n-2]);
|
|
}
|
|
//--- check
|
|
if(boundrtype==1)
|
|
{
|
|
//--- change values
|
|
a1[n-1]=0;
|
|
a2[n-1]=1;
|
|
a3[n-1]=0;
|
|
b[n-1]=boundr;
|
|
}
|
|
//--- check
|
|
if(boundrtype==2)
|
|
{
|
|
//--- change values
|
|
a1[n-1]=1;
|
|
a2[n-1]=2;
|
|
a3[n-1]=0;
|
|
b[n-1]=3*(y[n-1]-y[n-2])/(x[n-1]-x[n-2])+0.5*boundr*(x[n-1]-x[n-2]);
|
|
}
|
|
//--- Solve
|
|
SolveTridiagonal(a1,a2,a3,b,n,d);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Heap sort. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::HeapSortPoints(double &x[],double &y[],const int n)
|
|
{
|
|
//--- create arrays
|
|
double bufx[];
|
|
double bufy[];
|
|
//--- function call
|
|
CTSort::TagSortFastR(x,y,bufx,bufy,n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Heap sort. |
|
|
//| Accepts: |
|
|
//| X, Y - points |
|
|
//| P - empty or preallocated array |
|
|
//| Returns: |
|
|
//| X, Y - sorted by X |
|
|
//| P - array of permutations; I-th position of output |
|
|
//| arrays X/Y contains(X[P[I]],Y[P[I]]) |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::HeapSortPPoints(double &x[],double &y[],int &p[],
|
|
const int n)
|
|
{
|
|
//--- create arrays
|
|
double rbuf[];
|
|
int ibuf[];
|
|
//--- check
|
|
if(CAp::Len(p)<n)
|
|
ArrayResizeAL(p,n);
|
|
//--- allocation
|
|
ArrayResize(rbuf,n);
|
|
//--- initialization
|
|
for(int i=0; i<n; i++)
|
|
p[i]=i;
|
|
//--- function call
|
|
CTSort::TagSortFastI(x,p,rbuf,ibuf,n);
|
|
//--- copy
|
|
for(int i=0; i<n; i++)
|
|
rbuf[i]=y[p[i]];
|
|
for(int i_=0; i_<n; i_++)
|
|
y[i_]=rbuf[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Tridiagonal solver. Solves |
|
|
//| ( B[0] C[0] ) |
|
|
//| ( A[1] B[1] C[1] ) |
|
|
//| ( A[2] B[2] C[2] ) |
|
|
//| ( .......... ) * X=D |
|
|
//| ( .......... ) |
|
|
//| ( A[N-2] B[N-2] C[N-2] ) |
|
|
//| ( A[N-1] B[N-1] ) |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::SolveTridiagonal(double &a[],double &cb[],double &c[],
|
|
double &cd[],const int n,double &x[])
|
|
{
|
|
double t=0;
|
|
//--- create arrays
|
|
double d[];
|
|
double b[];
|
|
//--- copy arrays
|
|
ArrayCopy(d,cd);
|
|
ArrayCopy(b,cb);
|
|
//--- check
|
|
if(CAp::Len(x)<n)
|
|
ArrayResize(x,n);
|
|
//--- calculation
|
|
for(int k=1; k<n; k++)
|
|
{
|
|
t=a[k]/b[k-1];
|
|
b[k]=b[k]-t*c[k-1];
|
|
d[k]=d[k]-t*d[k-1];
|
|
}
|
|
x[n-1]=d[n-1]/b[n-1];
|
|
for(int k=n-2; k>=0; k--)
|
|
x[k]=(d[k]-c[k]*x[k+1])/b[k];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Cyclic tridiagonal solver. Solves |
|
|
//| ( B[0] C[0] A[0] ) |
|
|
//| ( A[1] B[1] C[1] ) |
|
|
//| ( A[2] B[2] C[2] ) |
|
|
//| ( .......... ) * X=D |
|
|
//| ( .......... ) |
|
|
//| ( A[N-2] B[N-2] C[N-2] ) |
|
|
//| ( C[N-1] A[N-1] B[N-1] ) |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline1D::SolveCyclicTridiagonal(double &a[],double &cb[],
|
|
double &c[],double &d[],
|
|
const int n,double &x[])
|
|
{
|
|
//--- create variables
|
|
double alpha=0;
|
|
double beta=0;
|
|
double gamma=0;
|
|
//--- create arrays
|
|
double y[];
|
|
double z[];
|
|
double u[];
|
|
double b[];
|
|
//--- copy array
|
|
ArrayCopy(b,cb);
|
|
//--- check
|
|
if(CAp::Len(x)<n)
|
|
ArrayResize(x,n);
|
|
//--- change values
|
|
beta=a[0];
|
|
alpha=c[n-1];
|
|
gamma=-b[0];
|
|
b[0]=2*b[0];
|
|
b[n-1]=b[n-1]-alpha*beta/gamma;
|
|
//--- allocation
|
|
ArrayResize(u,n);
|
|
//--- initialization
|
|
for(int k=0; k<n; k++)
|
|
u[k]=0;
|
|
u[0]=gamma;
|
|
u[n-1]=alpha;
|
|
//--- function call
|
|
SolveTridiagonal(a,b,c,d,n,y);
|
|
//--- function call
|
|
SolveTridiagonal(a,b,c,u,n,z);
|
|
//--- calculation
|
|
for(int k=0; k<n; k++)
|
|
x[k]=y[k]-(y[0]+beta/gamma*y[n-1])/(1+z[0]+beta/gamma*z[n-1])*z[k];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Three-point differentiation |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline1D::DiffThreePoint(double t,const double x0,const double f0,
|
|
double x1,const double f1,double x2,
|
|
const double f2)
|
|
{
|
|
//--- create variables
|
|
double a=0;
|
|
double b=0;
|
|
//--- change values
|
|
t=t-x0;
|
|
x1=x1-x0;
|
|
x2=x2-x0;
|
|
a=(f2-f0-x2/x1*(f1-f0))/(CMath::Sqr(x2)-x1*x2);
|
|
b=(f1-f0-a*CMath::Sqr(x1))/x1;
|
|
//--- return result
|
|
return(2*a*t+b);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Polynomial fitting report: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//+------------------------------------------------------------------+
|
|
class CPolynomialFitReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
double m_taskrcond;
|
|
double m_rmserror;
|
|
double m_avgerror;
|
|
double m_avgrelerror;
|
|
double m_maxerror;
|
|
//--- constructor, destructor
|
|
CPolynomialFitReport(void) { ZeroMemory(this); }
|
|
~CPolynomialFitReport(void) {}
|
|
//--- copy
|
|
void Copy(CPolynomialFitReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReport::Copy(CPolynomialFitReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_taskrcond=obj.m_taskrcond;
|
|
m_rmserror=obj.m_rmserror;
|
|
m_avgerror=obj.m_avgerror;
|
|
m_avgrelerror=obj.m_avgrelerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Polynomial fitting report: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//+------------------------------------------------------------------+
|
|
class CPolynomialFitReportShell
|
|
{
|
|
private:
|
|
CPolynomialFitReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CPolynomialFitReportShell(void) {}
|
|
CPolynomialFitReportShell(CPolynomialFitReport &obj) { m_innerobj.Copy(obj); }
|
|
~CPolynomialFitReportShell(void) {}
|
|
//--- methods
|
|
double GetTaskRCond(void);
|
|
void SetTaskRCond(const double d);
|
|
double GetRMSError(void);
|
|
void SetRMSError(const double d);
|
|
double GetAvgError(void);
|
|
void SetAvgError(const double d);
|
|
double GetAvgRelError(void);
|
|
void SetAvgRelError(const double d);
|
|
double GetMaxError(void);
|
|
void SetMaxError(const double d);
|
|
CPolynomialFitReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
double CPolynomialFitReportShell::GetTaskRCond(void)
|
|
{
|
|
return(m_innerobj.m_taskrcond);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReportShell::SetTaskRCond(const double d)
|
|
{
|
|
m_innerobj.m_taskrcond=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CPolynomialFitReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_rmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_rmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CPolynomialFitReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_avgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_avgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CPolynomialFitReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_avgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_avgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
double CPolynomialFitReportShell::GetMaxError(void)
|
|
{
|
|
return(m_innerobj.m_maxerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
void CPolynomialFitReportShell::SetMaxError(const double d)
|
|
{
|
|
m_innerobj.m_maxerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CPolynomialFitReport *CPolynomialFitReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Barycentric fitting report: |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//+------------------------------------------------------------------+
|
|
class CBarycentricFitReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
double m_taskrcond;
|
|
int m_dbest;
|
|
double m_rmserror;
|
|
double m_avgerror;
|
|
double m_avgrelerror;
|
|
double m_maxerror;
|
|
//--- constructor, destructor
|
|
CBarycentricFitReport(void) { ZeroMemory(this); }
|
|
~CBarycentricFitReport(void) {}
|
|
//--- copy
|
|
void Copy(CBarycentricFitReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReport::Copy(CBarycentricFitReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_taskrcond=obj.m_taskrcond;
|
|
m_dbest=obj.m_dbest;
|
|
m_rmserror=obj.m_rmserror;
|
|
m_avgerror=obj.m_avgerror;
|
|
m_avgrelerror=obj.m_avgrelerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Barycentric fitting report: |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//+------------------------------------------------------------------+
|
|
class CBarycentricFitReportShell
|
|
{
|
|
private:
|
|
CBarycentricFitReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CBarycentricFitReportShell(void) {}
|
|
CBarycentricFitReportShell(CBarycentricFitReport &obj) { m_innerobj.Copy(obj); }
|
|
~CBarycentricFitReportShell(void) {}
|
|
//--- methods
|
|
double GetTaskRCond(void);
|
|
void SetTaskRCond(const double d);
|
|
int GetDBest(void);
|
|
void SetDBest(const int i);
|
|
double GetRMSError(void);
|
|
void SetRMSError(const double d);
|
|
double GetAvgError(void);
|
|
void SetAvgError(const double d);
|
|
double GetAvgRelError(void);
|
|
void SetAvgRelError(const double d);
|
|
double GetMaxError(void);
|
|
void SetMaxError(const double d);
|
|
CBarycentricFitReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
double CBarycentricFitReportShell::GetTaskRCond(void)
|
|
{
|
|
return(m_innerobj.m_taskrcond);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetTaskRCond(const double d)
|
|
{
|
|
m_innerobj.m_taskrcond=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable dbest |
|
|
//+------------------------------------------------------------------+
|
|
int CBarycentricFitReportShell::GetDBest(void)
|
|
{
|
|
return(m_innerobj.m_dbest);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable dbest |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetDBest(const int i)
|
|
{
|
|
m_innerobj.m_dbest=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CBarycentricFitReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_rmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_rmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CBarycentricFitReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_avgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_avgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CBarycentricFitReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_avgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_avgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
double CBarycentricFitReportShell::GetMaxError(void)
|
|
{
|
|
return(m_innerobj.m_maxerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
void CBarycentricFitReportShell::SetMaxError(const double d)
|
|
{
|
|
m_innerobj.m_maxerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CBarycentricFitReport *CBarycentricFitReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Least squares fitting report: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//| IterationsCount number of internal iterations |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| WRMSError weighted RMS error |
|
|
//+------------------------------------------------------------------+
|
|
class CLSFitReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_iterationscount;
|
|
int m_varidx;
|
|
double m_avgerror;
|
|
double m_avgrelerror;
|
|
double m_maxerror;
|
|
double m_r2;
|
|
double m_rmserror;
|
|
double m_taskrcond;
|
|
double m_wrmserror;
|
|
CRowDouble m_errcurve;
|
|
CRowDouble m_errpar;
|
|
CRowDouble m_noise;
|
|
CMatrixDouble m_covpar;
|
|
//--- constructor, destructor
|
|
CLSFitReport(void);
|
|
~CLSFitReport(void) {}
|
|
//--- copy
|
|
void Copy(CLSFitReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLSFitReport::CLSFitReport(void)
|
|
{
|
|
m_iterationscount=0;
|
|
m_varidx=0;
|
|
m_avgerror=0;
|
|
m_avgrelerror=0;
|
|
m_maxerror=0;
|
|
m_r2=0;
|
|
m_rmserror=0;
|
|
m_taskrcond=0;
|
|
m_wrmserror=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReport::Copy(CLSFitReport &obj)
|
|
{
|
|
//--- copy variables
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_varidx=obj.m_varidx;
|
|
m_avgerror=obj.m_avgerror;
|
|
m_avgrelerror=obj.m_avgrelerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_r2=obj.m_r2;
|
|
m_rmserror=obj.m_rmserror;
|
|
m_taskrcond=obj.m_taskrcond;
|
|
m_wrmserror=obj.m_wrmserror;
|
|
m_errcurve=obj.m_errcurve;
|
|
m_errpar=obj.m_errpar;
|
|
m_noise=obj.m_noise;
|
|
m_covpar=obj.m_covpar;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Least squares fitting report: |
|
|
//| TaskRCond reciprocal of task's condition number |
|
|
//| IterationsCount number of internal iterations |
|
|
//| RMSError RMS error |
|
|
//| AvgError average error |
|
|
//| AvgRelError average relative error (for non-zero Y[I]) |
|
|
//| MaxError maximum error |
|
|
//| WRMSError weighted RMS error |
|
|
//+------------------------------------------------------------------+
|
|
class CLSFitReportShell
|
|
{
|
|
private:
|
|
CLSFitReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CLSFitReportShell(void) {}
|
|
CLSFitReportShell(CLSFitReport &obj) { m_innerobj.Copy(obj); }
|
|
~CLSFitReportShell(void) {}
|
|
//--- methods
|
|
double GetTaskRCond(void);
|
|
void SetTaskRCond(const double d);
|
|
int GetIterationsCount(void);
|
|
void SetIterationsCount(const int i);
|
|
double GetRMSError(void);
|
|
void SetRMSError(const double d);
|
|
double GetAvgError(void);
|
|
void SetAvgError(const double d);
|
|
double GetAvgRelError(void);
|
|
void SetAvgRelError(const double d);
|
|
double GetMaxError(void);
|
|
void SetMaxError(const double d);
|
|
double GetWRMSError(void);
|
|
void SetWRMSError(const double d);
|
|
CLSFitReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetTaskRCond(void)
|
|
{
|
|
return(m_innerobj.m_taskrcond);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable taskrcond |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetTaskRCond(const double d)
|
|
{
|
|
m_innerobj.m_taskrcond=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CLSFitReportShell::GetIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_iterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable iterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_iterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_rmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_rmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_avgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_avgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_avgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_avgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetMaxError(void)
|
|
{
|
|
return(m_innerobj.m_maxerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable maxerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetMaxError(const double d)
|
|
{
|
|
m_innerobj.m_maxerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable wrmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitReportShell::GetWRMSError(void)
|
|
{
|
|
return(m_innerobj.m_wrmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable wrmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitReportShell::SetWRMSError(const double d)
|
|
{
|
|
m_innerobj.m_wrmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CLSFitReport *CLSFitReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear fitter. |
|
|
//| You should use ALGLIB functions to work with fitter. |
|
|
//| Never try to access its fields directly! |
|
|
//+------------------------------------------------------------------+
|
|
class CLSFitState
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_k;
|
|
int m_m;
|
|
int m_maxits;
|
|
int m_nec;
|
|
int m_nic;
|
|
int m_npoints;
|
|
int m_nweights;
|
|
int m_optalgo;
|
|
int m_pointindex;
|
|
int m_prevalgo;
|
|
int m_prevnpt;
|
|
int m_repiterationscount;
|
|
int m_repterminationtype;
|
|
int m_repvaridx;
|
|
int m_wits;
|
|
int m_wkind;
|
|
double m_diffstep;
|
|
double m_epsx;
|
|
double m_f;
|
|
double m_repavgerror;
|
|
double m_repavgrelerror;
|
|
double m_repmaxerror;
|
|
double m_reprmserror;
|
|
double m_repwrmserror;
|
|
double m_stpmax;
|
|
double m_teststep;
|
|
double m_tmpnoise;
|
|
bool m_needf;
|
|
bool m_needfg;
|
|
bool m_needfgh;
|
|
bool m_xrep;
|
|
bool m_xupdated;
|
|
RCommState m_rstate;
|
|
CRowInt m_tmpct;
|
|
CRowDouble m_bndl;
|
|
CRowDouble m_bndu;
|
|
CRowDouble m_c0;
|
|
CRowDouble m_c1;
|
|
CRowDouble m_c;
|
|
CRowDouble m_g;
|
|
CRowDouble m_s;
|
|
CRowDouble m_taskw;
|
|
CRowDouble m_tasky;
|
|
CRowDouble m_tmp;
|
|
CRowDouble m_tmpf;
|
|
CRowDouble m_wcur;
|
|
CRowDouble m_x;
|
|
CMinLMState m_optstate;
|
|
CMinLMReport m_optrep;
|
|
CMatrixDouble m_cleic;
|
|
CMatrixDouble m_h;
|
|
CMatrixDouble m_taskx;
|
|
CMatrixDouble m_tmpjac;
|
|
CMatrixDouble m_tmpjacw;
|
|
CMatInvReport m_invrep;
|
|
CLSFitReport m_rep;
|
|
//--- constructor, destructor
|
|
CLSFitState(void);
|
|
~CLSFitState(void) {}
|
|
//--- copy
|
|
void Copy(const CLSFitState &obj);
|
|
//--- overloading
|
|
void operator=(const CLSFitState &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLSFitState::CLSFitState(void)
|
|
{
|
|
m_k=0;
|
|
m_m=0;
|
|
m_maxits=0;
|
|
m_nec=0;
|
|
m_nic=0;
|
|
m_npoints=0;
|
|
m_nweights=0;
|
|
m_optalgo=0;
|
|
m_pointindex=0;
|
|
m_prevalgo=0;
|
|
m_prevnpt=0;
|
|
m_repiterationscount=0;
|
|
m_repterminationtype=0;
|
|
m_repvaridx=0;
|
|
m_wits=0;
|
|
m_wkind=0;
|
|
m_diffstep=0;
|
|
m_epsx=0;
|
|
m_f=0;
|
|
m_repavgerror=0;
|
|
m_repavgrelerror=0;
|
|
m_repmaxerror=0;
|
|
m_reprmserror=0;
|
|
m_repwrmserror=0;
|
|
m_stpmax=0;
|
|
m_teststep=0;
|
|
m_tmpnoise=0;
|
|
m_needf=false;
|
|
m_needfg=false;
|
|
m_needfgh=false;
|
|
m_xrep=false;
|
|
m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitState::Copy(const CLSFitState &obj)
|
|
{
|
|
//--- copy variables
|
|
m_k=obj.m_k;
|
|
m_m=obj.m_m;
|
|
m_maxits=obj.m_maxits;
|
|
m_nec=obj.m_nec;
|
|
m_nic=obj.m_nic;
|
|
m_npoints=obj.m_npoints;
|
|
m_nweights=obj.m_nweights;
|
|
m_optalgo=obj.m_optalgo;
|
|
m_pointindex=obj.m_pointindex;
|
|
m_prevalgo=obj.m_prevalgo;
|
|
m_prevnpt=obj.m_prevnpt;
|
|
m_repiterationscount=obj.m_repiterationscount;
|
|
m_repterminationtype=obj.m_repterminationtype;
|
|
m_repvaridx=obj.m_repvaridx;
|
|
m_wits=obj.m_wits;
|
|
m_wkind=obj.m_wkind;
|
|
m_diffstep=obj.m_diffstep;
|
|
m_epsx=obj.m_epsx;
|
|
m_f=obj.m_f;
|
|
m_repavgerror=obj.m_repavgerror;
|
|
m_repavgrelerror=obj.m_repavgrelerror;
|
|
m_repmaxerror=obj.m_repmaxerror;
|
|
m_reprmserror=obj.m_reprmserror;
|
|
m_repwrmserror=obj.m_repwrmserror;
|
|
m_stpmax=obj.m_stpmax;
|
|
m_teststep=obj.m_teststep;
|
|
m_tmpnoise=obj.m_tmpnoise;
|
|
m_needf=obj.m_needf;
|
|
m_needfg=obj.m_needfg;
|
|
m_needfgh=obj.m_needfgh;
|
|
m_xrep=obj.m_xrep;
|
|
m_xupdated=obj.m_xupdated;
|
|
m_rstate=obj.m_rstate;
|
|
m_tmpct=obj.m_tmpct;
|
|
m_bndl=obj.m_bndl;
|
|
m_bndu=obj.m_bndu;
|
|
m_c0=obj.m_c0;
|
|
m_c1=obj.m_c1;
|
|
m_c=obj.m_c;
|
|
m_g=obj.m_g;
|
|
m_s=obj.m_s;
|
|
m_taskw=obj.m_taskw;
|
|
m_tasky=obj.m_tasky;
|
|
m_tmp=obj.m_tmp;
|
|
m_tmpf=obj.m_tmpf;
|
|
m_wcur=obj.m_wcur;
|
|
m_x=obj.m_x;
|
|
m_optstate=obj.m_optstate;
|
|
m_optrep=obj.m_optrep;
|
|
m_cleic=obj.m_cleic;
|
|
m_h=obj.m_h;
|
|
m_taskx=obj.m_taskx;
|
|
m_tmpjac=obj.m_tmpjac;
|
|
m_tmpjacw=obj.m_tmpjacw;
|
|
m_invrep=obj.m_invrep;
|
|
m_rep=obj.m_rep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear fitter. |
|
|
//| You should use ALGLIB functions to work with fitter. |
|
|
//| Never try to access its fields directly! |
|
|
//+------------------------------------------------------------------+
|
|
class CLSFitStateShell
|
|
{
|
|
private:
|
|
CLSFitState m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CLSFitStateShell(void) {}
|
|
CLSFitStateShell(CLSFitState &obj) { m_innerobj.Copy(obj); }
|
|
~CLSFitStateShell(void) {}
|
|
//--- methods
|
|
bool GetNeedF(void);
|
|
void SetNeedF(const bool b);
|
|
bool GetNeedFG(void);
|
|
void SetNeedFG(const bool b);
|
|
bool GetNeedFGH(void);
|
|
void SetNeedFGH(const bool b);
|
|
bool GetXUpdated(void);
|
|
void SetXUpdated(const bool b);
|
|
double GetF(void);
|
|
void SetF(const double d);
|
|
CLSFitState *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
bool CLSFitStateShell::GetNeedF(void)
|
|
{
|
|
return(m_innerobj.m_needf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needf |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitStateShell::SetNeedF(const bool b)
|
|
{
|
|
m_innerobj.m_needf=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
bool CLSFitStateShell::GetNeedFG(void)
|
|
{
|
|
return(m_innerobj.m_needfg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfg |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitStateShell::SetNeedFG(const bool b)
|
|
{
|
|
m_innerobj.m_needfg=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable needfgh |
|
|
//+------------------------------------------------------------------+
|
|
bool CLSFitStateShell::GetNeedFGH(void)
|
|
{
|
|
return(m_innerobj.m_needfgh);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable needfgh |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitStateShell::SetNeedFGH(const bool b)
|
|
{
|
|
m_innerobj.m_needfgh=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
bool CLSFitStateShell::GetXUpdated(void)
|
|
{
|
|
return(m_innerobj.m_xupdated);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable xupdated |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitStateShell::SetXUpdated(const bool b)
|
|
{
|
|
m_innerobj.m_xupdated=b;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFitStateShell::GetF(void)
|
|
{
|
|
return(m_innerobj.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable f |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFitStateShell::SetF(const double d)
|
|
{
|
|
m_innerobj.m_f=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CLSFitState *CLSFitStateShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Least squares fitting |
|
|
//+------------------------------------------------------------------+
|
|
class CLSFit
|
|
{
|
|
public:
|
|
//--- class constant
|
|
static const int m_rfsmax;
|
|
//--- public methods
|
|
static void PolynomialFit(double &x[],double &y[],const int n,const int m,int &info,CBarycentricInterpolant &p,CPolynomialFitReport &rep);
|
|
static void PolynomialFitWC(double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,CBarycentricInterpolant &p,CPolynomialFitReport &rep);
|
|
static void BarycentricFitFloaterHormannWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
|
|
static void BarycentricFitFloaterHormann(double &x[],double &y[],const int n,const int m,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
|
|
static void Spline1DFitCubicWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void Spline1DFitHermiteWC(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void Spline1DFitCubic(double &x[],double &y[],const int n,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void Spline1DFitHermite(double &x[],double &y[],const int n,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void LSFitLinearW(double &y[],double &w[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitLinearWC(double &cy[],double &w[],CMatrixDouble &fmatrix,CMatrixDouble &ccmatrix,const int n,const int m,const int k,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitLinear(double &y[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitLinear(CRowDouble &y,CMatrixDouble &fmatrix,const int n,const int m,int &info,CRowDouble &c,CLSFitReport &rep);
|
|
static void LSFitLinearC(double &cy[],CMatrixDouble &fmatrix,CMatrixDouble &cmatrix,const int n,const int m,const int k,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitCreateWF(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,const double diffstep,CLSFitState &State);
|
|
static void LSFitCreateF(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,const double diffstep,CLSFitState &State);
|
|
static void LSFitCreateWFG(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,bool cheapfg,CLSFitState &State);
|
|
static void LSFitCreateFG(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,const bool cheapfg,CLSFitState &State);
|
|
static void LSFitCreateWFGH(CMatrixDouble &x,double &y[],double &w[],double &c[],const int n,const int m,const int k,CLSFitState &State);
|
|
static void LSFitCreateFGH(CMatrixDouble &x,double &y[],double &c[],const int n,const int m,const int k,CLSFitState &State);
|
|
static void LSFitSetCond(CLSFitState &State,const double epsx,const int maxits);
|
|
static void LSFitSetStpMax(CLSFitState &State,const double stpmax);
|
|
static void LSFitSetXRep(CLSFitState &State,const bool needxrep);
|
|
static void LSFitSetScale(CLSFitState &State,double &s[]);
|
|
static void LSFitSetBC(CLSFitState &State,double &bndl[],double &bndu[]);
|
|
static void LSFitResults(CLSFitState &State,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitScaleXY(double &x[],double &y[],double &w[],const int n,double &xc[],double &yc[],int &dc[],const int k,double &xa,double &xb,double &sa,double &sb,double &xoriginal[],double &yoriginal[]);
|
|
static bool LSFitIteration(CLSFitState &State);
|
|
static double LogisticCalc4(double x,double a,double b,double c,double d);
|
|
static double LogisticCalc5(double x,double a,double b,double c,double d,double g);
|
|
static void LogisticFit4(CRowDouble &X,CRowDouble &Y,int n,double &a,double &b,double &c,double &d,CLSFitReport &rep);
|
|
static void LogisticFit4ec(CRowDouble &X,CRowDouble &Y,int n,double cnstrleft,double cnstrright,double &a,double &b,double &c,double &d,CLSFitReport &rep);
|
|
static void LogisticFit5(CRowDouble &x,CRowDouble &Y,int n,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
|
|
static void LogisticFit5ec(CRowDouble &X,CRowDouble &Y,int n,double cnstrleft,double cnstrright,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
|
|
static void LogisticFit45x(CRowDouble &x,CRowDouble &y,int n,double cnstrleft,double cnstrright,bool is4pl,double lambdav,double epsx,int rscnt,double &a,double &b,double &c,double &d,double &g,CLSFitReport &rep);
|
|
|
|
private:
|
|
static void Spline1DFitInternal(const int st,double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,CSpline1DInterpolant &s,CSpline1DFitReport &rep);
|
|
static void LSFitLinearInternal(double &y[],double &w[],CMatrixDouble &fmatrix,const int n,const int m,int &info,double &c[],CLSFitReport &rep);
|
|
static void LSFitClearRequestFields(CLSFitState &State);
|
|
static void BarycentricCalcBasis(CBarycentricInterpolant &b,const double t,double &y[]);
|
|
static void InternalChebyshevFit(double &x[],double &y[],double &w[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,int &info,double &c[],CLSFitReport &rep);
|
|
static void BarycentricFitWCFixedD(double &cx[],double &cy[],double &cw[],const int n,double &cxc[],double &cyc[],int &dc[],const int k,const int m,const int d,int &info,CBarycentricInterpolant &b,CBarycentricFitReport &rep);
|
|
static void EstimateErrors(CMatrixDouble &f1,CRowDouble &f0,CRowDouble &y,CRowDouble &w,CRowDouble &x,CRowDouble &s,int n,int k,CLSFitReport &rep,CMatrixDouble &z,int zkind);
|
|
static void ClearReport(CLSFitReport &rep);
|
|
static void LogisticFit45Errors(CRowDouble &x,CRowDouble &y,int n,double a,double b,double c,double d,double g,CLSFitReport &rep);
|
|
static void LogisticFitInternal(CRowDouble &x,CRowDouble &y,int n,bool is4pl,double lambdav,CMinLMState &state,CMinLMReport &replm,CRowDouble &p1,double &flast);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constant |
|
|
//+------------------------------------------------------------------+
|
|
const int CLSFit::m_rfsmax=10;
|
|
//+------------------------------------------------------------------+
|
|
//| Fitting by polynomials in barycentric form. This function |
|
|
//| provides simple unterface for unconstrained unweighted fitting. |
|
|
//| See PolynomialFitWC() if you need constrained fitting. |
|
|
//| Task is linear, so linear least squares solver is used. |
|
|
//| Complexity of this computational scheme is O(N*M^2), mostly |
|
|
//| dominated by least squares solver |
|
|
//| SEE ALSO: |
|
|
//| PolynomialFitWC() |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| N - number of points, N>0 |
|
|
//| * if given, only leading N elements of X/Y are used |
|
|
//| * if not given, automatically determined from sizes |
|
|
//| of X/Y |
|
|
//| M - number of basis functions (= polynomial_degree + 1), |
|
|
//| M>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearW() subroutine: |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| P - interpolant in barycentric form. |
|
|
//| Rep - report, same format as in LSFitLinearW() subroutine. |
|
|
//| Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| NOTES: |
|
|
//| you can convert P from barycentric form to the power or |
|
|
//| Chebyshev basis with PolynomialBar2Pow() or |
|
|
//| PolynomialBar2Cheb() functions from POLINT subpackage. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::PolynomialFit(double &x[],double &y[],const int n,
|
|
const int m,int &info,
|
|
CBarycentricInterpolant &p,
|
|
CPolynomialFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
int dc[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1);
|
|
//--- function call
|
|
PolynomialFitWC(x,y,w,n,xc,yc,dc,0,m,info,p,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted fitting by polynomials in barycentric form, with |
|
|
//| constraints on function values or first derivatives. |
|
|
//| Small regularizing term is used when solving constrained tasks |
|
|
//| (to improve stability). |
|
|
//| Task is linear, so linear least squares solver is used. |
|
|
//| Complexity of this computational scheme is O(N*M^2), mostly |
|
|
//| dominated by least squares solver |
|
|
//| SEE ALSO: |
|
|
//| PolynomialFit() |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| Each summand in square sum of approximation |
|
|
//| deviations from given values is multiplied by the |
|
|
//| square of corresponding weight. Fill it by 1's if you|
|
|
//| don't want to solve weighted task. |
|
|
//| N - number of points, N>0. |
|
|
//| * if given, only leading N elements of X/Y/W are used|
|
|
//| * if not given, automatically determined from sizes |
|
|
//| of X/Y/W |
|
|
//| XC - points where polynomial values/derivatives are |
|
|
//| constrained, array[0..K-1]. |
|
|
//| YC - values of constraints, array[0..K-1] |
|
|
//| DC - array[0..K-1], types of constraints: |
|
|
//| * DC[i]=0 means that P(XC[i])=YC[i] |
|
|
//| * DC[i]=1 means that P'(XC[i])=YC[i] |
|
|
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
|
|
//| K - number of constraints, 0<=K<M. |
|
|
//| K=0 means no constraints (XC/YC/DC are not used in |
|
|
//| such cases) |
|
|
//| M - number of basis functions (= polynomial_degree + 1), |
|
|
//| M>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearW() subroutine: |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| P - interpolant in barycentric form. |
|
|
//| Rep - report, same format as in LSFitLinearW() subroutine. |
|
|
//| Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//| NOTES: |
|
|
//| you can convert P from barycentric form to the power or |
|
|
//| Chebyshev basis with PolynomialBar2Pow() or |
|
|
//| PolynomialBar2Cheb() functions from POLINT subpackage. |
|
|
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
|
|
//| Setting constraints can lead to undesired results, like |
|
|
//| ill-conditioned behavior, or inconsistency being detected. |
|
|
//| From the other side, it allows us to improve quality of the fit. |
|
|
//| Here we summarize our experience with constrained regression |
|
|
//| splines: |
|
|
//| * even simple constraints can be inconsistent, see Wikipedia |
|
|
//| article on this subject: |
|
|
//| http://en.wikipedia.org/wiki/Birkhoff_interpolation |
|
|
//| * the greater is M (given fixed constraints), the more chances |
|
|
//| that constraints will be consistent |
|
|
//| * in the general case, consistency of constraints is NOT |
|
|
//| GUARANTEED. |
|
|
//| * in the one special cases, however, we can guarantee |
|
|
//| consistency. This case is: M>1 and constraints on the |
|
|
//| function values (NOT DERIVATIVES) |
|
|
//| Our final recommendation is to use constraints WHEN AND ONLY when|
|
|
//| you can't solve your task without them. Anything beyond special |
|
|
//| cases given above is not guaranteed and may result in |
|
|
//| inconsistency. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::PolynomialFitWC(double &cx[],double &cy[],double &cw[],
|
|
const int n,double &cxc[],double &cyc[],
|
|
int &dc[],const int k,const int m,
|
|
int &info,CBarycentricInterpolant &p,
|
|
CPolynomialFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double xa=0;
|
|
double xb=0;
|
|
double sa=0;
|
|
double sb=0;
|
|
double u=0;
|
|
double v=0;
|
|
double s=0;
|
|
int relcnt=0;
|
|
//--- create arrays
|
|
double xoriginal[];
|
|
double yoriginal[];
|
|
double y2[];
|
|
double w2[];
|
|
double tmp[];
|
|
double tmp2[];
|
|
double bx[];
|
|
double by[];
|
|
double bw[];
|
|
double x[];
|
|
double y[];
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
//--- object of class
|
|
CLSFitReport lrep;
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(w,cw);
|
|
ArrayCopy(xc,cxc);
|
|
ArrayCopy(yc,cyc);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": XC contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": YC contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": one of DC[] is not 0 or 1!"))
|
|
return;
|
|
}
|
|
//--- Scale X,Y,XC,YC.
|
|
//--- Solve scaled problem using internal Chebyshev fitting function.
|
|
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
|
|
InternalChebyshevFit(x,y,w,n,xc,yc,dc,k,m,info,tmp,lrep);
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
//--- Generate barycentric model and scale it
|
|
//--- * BX,BY store barycentric model nodes
|
|
//--- * FMatrix is reused (remember - it is at least MxM,what we need)
|
|
//--- Model intialization is done in O(M^2). In principle,it can be
|
|
//--- done in O(M*log(M)),but before it we solved task with O(N*M^2)
|
|
//--- complexity,so it is only a small amount of total time spent.
|
|
ArrayResize(bx,m);
|
|
ArrayResize(by,m);
|
|
ArrayResize(bw,m);
|
|
ArrayResize(tmp2,m);
|
|
s=1;
|
|
//--- calculation
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
//--- check
|
|
if(m!=1)
|
|
u=MathCos(M_PI*i/(m-1));
|
|
else
|
|
u=0;
|
|
v=0;
|
|
for(int j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(j==0)
|
|
tmp2[j]=1;
|
|
else
|
|
{
|
|
//--- check
|
|
if(j==1)
|
|
tmp2[j]=u;
|
|
else
|
|
tmp2[j]=2*u*tmp2[j-1]-tmp2[j-2];
|
|
}
|
|
v=v+tmp[j]*tmp2[j];
|
|
}
|
|
//--- change values
|
|
bx[i]=u;
|
|
by[i]=v;
|
|
bw[i]=s;
|
|
//--- check
|
|
if(i==0 || i==m-1)
|
|
bw[i]=0.5*bw[i];
|
|
s=-s;
|
|
}
|
|
//--- function call
|
|
CRatInt::BarycentricBuildXYW(bx,by,bw,m,p);
|
|
//--- function call
|
|
CRatInt::BarycentricLinTransX(p,2/(xb-xa),-((xa+xb)/(xb-xa)));
|
|
//--- function call
|
|
CRatInt::BarycentricLinTransY(p,sb-sa,sa);
|
|
//--- Scale absolute errors obtained from LSFitLinearW.
|
|
//--- Relative error should be calculated separately
|
|
//--- (because of shifting/scaling of the task)
|
|
rep.m_taskrcond=lrep.m_taskrcond;
|
|
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
|
|
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
|
|
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
|
|
rep.m_avgrelerror=0;
|
|
relcnt=0;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(yoriginal[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CRatInt::BarycentricCalc(p,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
|
|
relcnt=relcnt+1;
|
|
}
|
|
}
|
|
//--- check
|
|
if(relcnt!=0)
|
|
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weghted rational least squares fitting using Floater-Hormann |
|
|
//| rational functions with optimal D chosen from [0,9], with |
|
|
//| constraints and individual weights. |
|
|
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
|
|
//| basis functions. Different values of D are tried, optimal D |
|
|
//| (least WEIGHTED root mean square error) is chosen. Task is |
|
|
//| linear, so linear least squares solver is used. Complexity of |
|
|
//| this computational scheme is O(N*M^2) (mostly dominated by the |
|
|
//| least squares solver). |
|
|
//| SEE ALSO |
|
|
//|*BarycentricFitFloaterHormann(), "lightweight" fitting without |
|
|
//| invididual weights and constraints. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| Each summand in square sum of approximation |
|
|
//| deviations from given values is multiplied by the |
|
|
//| square of corresponding weight. Fill it by 1's if |
|
|
//| you don't want to solve weighted task. |
|
|
//| N - number of points, N>0. |
|
|
//| XC - points where function values/derivatives are |
|
|
//| constrained, array[0..K-1]. |
|
|
//| YC - values of constraints, array[0..K-1] |
|
|
//| DC - array[0..K-1], types of constraints: |
|
|
//| * DC[i]=0 means that S(XC[i])=YC[i] |
|
|
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
|
|
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
|
|
//| K - number of constraints, 0<=K<M. |
|
|
//| K=0 means no constraints (XC/YC/DC are not used in |
|
|
//| such cases) |
|
|
//| M - number of basis functions ( = number_of_nodes), |
|
|
//| M>=2. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearWC() subroutine. |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| -1 means another errors in parameters |
|
|
//| passed (N<=0, for example) |
|
|
//| B - barycentric interpolant. |
|
|
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
|
|
//| Following fields are set: |
|
|
//| * DBest best value of the D parameter |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroutine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
|
|
//| Setting constraints can lead to undesired results, like |
|
|
//| ill-conditioned behavior, or inconsistency being detected. From |
|
|
//| the other side, it allows us to improve quality of the fit. Here |
|
|
//| we summarize our experience with constrained barycentric |
|
|
//| interpolants: |
|
|
//| * excessive constraints can be inconsistent. Floater-Hormann |
|
|
//| basis functions aren't as flexible as splines (although they |
|
|
//| are very smooth). |
|
|
//| * the more evenly constraints are spread across [min(x),max(x)], |
|
|
//| the more chances that they will be consistent |
|
|
//| * the greater is M (given fixed constraints), the more chances |
|
|
//| that constraints will be consistent |
|
|
//| * in the general case, consistency of constraints IS NOT |
|
|
//| GUARANTEED. |
|
|
//| * in the several special cases, however, we CAN guarantee |
|
|
//| consistency. |
|
|
//| * one of this cases is constraints on the function VALUES at the |
|
|
//| interval boundaries. Note that consustency of the constraints |
|
|
//| on the function DERIVATIVES is NOT guaranteed (you can use in |
|
|
//| such cases cubic splines which are more flexible). |
|
|
//| * another special case is ONE constraint on the function value |
|
|
//| (OR, but not AND, derivative) anywhere in the interval |
|
|
//| Our final recommendation is to use constraints WHEN AND ONLY |
|
|
//| WHEN you can't solve your task without them. Anything beyond |
|
|
//| special cases given above is not guaranteed and may result in |
|
|
//| inconsistency. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::BarycentricFitFloaterHormannWC(double &x[],double &y[],
|
|
double &w[],const int n,
|
|
double &xc[],double &yc[],
|
|
int &dc[],const int k,
|
|
const int m,int &info,
|
|
CBarycentricInterpolant &b,
|
|
CBarycentricFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double wrmscur=0;
|
|
double wrmsbest=0;
|
|
int locinfo=0;
|
|
//--- objects of classes
|
|
CBarycentricInterpolant locb;
|
|
CBarycentricFitReport locrep;
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": XC contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": YC contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": one of DC[] is not 0 or 1!"))
|
|
return;
|
|
}
|
|
//--- Find optimal D
|
|
//--- Info is -3 by default (degenerate constraints).
|
|
//--- If LocInfo will always be equal to -3,Info will remain equal to -3.
|
|
//--- If at least once LocInfo will be -4,Info will be -4.
|
|
wrmsbest=CMath::m_maxrealnumber;
|
|
rep.m_dbest=-1;
|
|
info=-3;
|
|
//--- calculation
|
|
for(int d=0; d<=MathMin(9,n-1); d++)
|
|
{
|
|
//--- function call
|
|
BarycentricFitWCFixedD(x,y,w,n,xc,yc,dc,k,m,d,locinfo,locb,locrep);
|
|
//--- check
|
|
if(!CAp::Assert((locinfo==-4 || locinfo==-3) || locinfo>0,__FUNCTION__+": unexpected result from BarycentricFitWCFixedD!"))
|
|
return;
|
|
//--- check
|
|
if(locinfo>0)
|
|
{
|
|
//--- Calculate weghted RMS
|
|
wrmscur=0;
|
|
for(int i=0; i<n; i++)
|
|
wrmscur=wrmscur+CMath::Sqr(w[i]*(y[i]-CRatInt::BarycentricCalc(locb,x[i])));
|
|
wrmscur=MathSqrt(wrmscur/n);
|
|
//--- check
|
|
if(wrmscur<wrmsbest || rep.m_dbest<0)
|
|
{
|
|
//--- function call
|
|
CRatInt::BarycentricCopy(locb,b);
|
|
//--- change values
|
|
rep.m_dbest=d;
|
|
info=1;
|
|
rep.m_rmserror=locrep.m_rmserror;
|
|
rep.m_avgerror=locrep.m_avgerror;
|
|
rep.m_avgrelerror=locrep.m_avgrelerror;
|
|
rep.m_maxerror=locrep.m_maxerror;
|
|
rep.m_taskrcond=locrep.m_taskrcond;
|
|
wrmsbest=wrmscur;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(locinfo!=-3 && info<0)
|
|
info=locinfo;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Rational least squares fitting using Floater-Hormann rational |
|
|
//| functions with optimal D chosen from [0,9]. |
|
|
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
|
|
//| basis functions. Different values of D are tried, optimal D |
|
|
//| (least root mean square error) is chosen. Task is linear, so |
|
|
//| linear least squares solver is used. Complexity of this |
|
|
//| computational scheme is O(N*M^2) (mostly dominated by the least |
|
|
//| squares solver). |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| N - number of points, N>0. |
|
|
//| M - number of basis functions ( = number_of_nodes), M>=2.|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearWC() subroutine. |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| B - barycentric interpolant. |
|
|
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
|
|
//| Following fields are set: |
|
|
//| * DBest best value of the D parameter |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::BarycentricFitFloaterHormann(double &x[],double &y[],
|
|
const int n,const int m,
|
|
int &info,CBarycentricInterpolant &b,
|
|
CBarycentricFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
int dc[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>0,__FUNCTION__+": M<=0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
for(int i=0; i<n; i++)
|
|
w[i]=1;
|
|
//--- function call
|
|
BarycentricFitFloaterHormannWC(x,y,w,n,xc,yc,dc,0,m,info,b,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted fitting by cubic spline, with constraints on function |
|
|
//| values or derivatives. |
|
|
//| Equidistant grid with M-2 nodes on [min(x,xc),max(x,xc)] is used |
|
|
//| to build basis functions. Basis functions are cubic splines with |
|
|
//| continuous second derivatives and non-fixed first derivatives at |
|
|
//| interval ends. Small regularizing term is used when solving |
|
|
//| constrained tasks (to improve stability). |
|
|
//| Task is linear, so linear least squares solver is used. |
|
|
//| Complexity of this computational scheme is O(N*M^2), mostly |
|
|
//| dominated by least squares solver |
|
|
//| SEE ALSO |
|
|
//| Spline1DFitHermiteWC() - fitting by Hermite splines (more |
|
|
//| flexible, less smooth) |
|
|
//| Spline1DFitCubic() - "lightweight" fitting by cubic |
|
|
//| splines, without invididual |
|
|
//| weights and constraints |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| Each summand in square sum of approximation |
|
|
//| deviations from given values is multiplied by the |
|
|
//| square of corresponding weight. Fill it by 1's if you|
|
|
//| don't want to solve weighted task. |
|
|
//| N - number of points (optional): |
|
|
//| * N>0 |
|
|
//| * if given, only first N elements of X/Y/W are |
|
|
//| processed |
|
|
//| * if not given, automatically determined from X/Y/W |
|
|
//| sizes |
|
|
//| XC - points where spline values/derivatives are |
|
|
//| constrained, array[0..K-1]. |
|
|
//| YC - values of constraints, array[0..K-1] |
|
|
//| DC - array[0..K-1], types of constraints: |
|
|
//| * DC[i]=0 means that S(XC[i])=YC[i] |
|
|
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
|
|
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
|
|
//| K - number of constraints (optional): |
|
|
//| * 0<=K<M. |
|
|
//| * K=0 means no constraints (XC/YC/DC are not used) |
|
|
//| * if given, only first K elements of XC/YC/DC are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| XC/YC/DC |
|
|
//| M - number of basis functions ( = number_of_nodes+2), |
|
|
//| M>=4. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearWC() subroutine. |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| S - spline interpolant. |
|
|
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
|
|
//| Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//| ORDER OF POINTS |
|
|
//| Subroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
|
|
//| Setting constraints can lead to undesired results, like |
|
|
//| ill-conditioned behavior, or inconsistency being detected. From |
|
|
//| the other side, it allows us to improve quality of the fit. |
|
|
//| Here we summarize our experience with constrained regression |
|
|
//| splines: |
|
|
//| * excessive constraints can be inconsistent. Splines are |
|
|
//| piecewise cubic functions, and it is easy to create an |
|
|
//| example, where large number of constraints concentrated in |
|
|
//| small area will result in inconsistency. Just because spline |
|
|
//| is not flexible enough to satisfy all of them. And same |
|
|
//| constraints spread across the [min(x),max(x)] will be |
|
|
//| perfectly consistent. |
|
|
//| * the more evenly constraints are spread across [min(x),max(x)], |
|
|
//| the more chances that they will be consistent |
|
|
//| * the greater is M (given fixed constraints), the more chances |
|
|
//| that constraints will be consistent |
|
|
//| * in the general case, consistency of constraints IS NOT |
|
|
//| GUARANTEED. |
|
|
//| * in the several special cases, however, we CAN guarantee |
|
|
//| consistency. |
|
|
//| * one of this cases is constraints on the function values |
|
|
//| AND/OR its derivatives at the interval boundaries. |
|
|
//| * another special case is ONE constraint on the function value |
|
|
//| (OR, but not AND, derivative) anywhere in the interval |
|
|
//| Our final recommendation is to use constraints WHEN AND ONLY WHEN|
|
|
//| you can't solve your task without them. Anything beyond special |
|
|
//| cases given above is not guaranteed and may result in |
|
|
//| inconsistency. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::Spline1DFitCubicWC(double &x[],double &y[],double &w[],
|
|
const int n,double &xc[],double &yc[],
|
|
int &dc[],const int k,const int m,
|
|
int &info,CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": DC[i] is neither 0 or 1!"))
|
|
return;
|
|
}
|
|
//--- function call
|
|
Spline1DFitInternal(0,x,y,w,n,xc,yc,dc,k,m,info,s,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted fitting by Hermite spline, with constraints on function |
|
|
//| values or first derivatives. |
|
|
//| Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is used to|
|
|
//| build basis functions. Basis functions are Hermite splines. Small|
|
|
//| regularizing term is used when solving constrained tasks (to |
|
|
//| improve stability). |
|
|
//| Task is linear, so linear least squares solver is used. |
|
|
//| Complexity of this computational scheme is O(N*M^2), mostly |
|
|
//| dominated by least squares solver |
|
|
//| SEE ALSO |
|
|
//| Spline1DFitCubicWC() - fitting by Cubic splines (less |
|
|
//| flexible, more smooth) |
|
|
//| Spline1DFitHermite() - "lightweight" Hermite fitting, |
|
|
//| without invididual weights and |
|
|
//| constraints |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| Each summand in square sum of approximation |
|
|
//| deviations from given values is multiplied by the |
|
|
//| square of corresponding weight. Fill it by 1's if |
|
|
//| you don't want to solve weighted task. |
|
|
//| N - number of points (optional): |
|
|
//| * N>0 |
|
|
//| * if given, only first N elements of X/Y/W are |
|
|
//| processed |
|
|
//| * if not given, automatically determined from X/Y/W |
|
|
//| sizes |
|
|
//| XC - points where spline values/derivatives are |
|
|
//| constrained, array[0..K-1]. |
|
|
//| YC - values of constraints, array[0..K-1] |
|
|
//| DC - array[0..K-1], types of constraints: |
|
|
//| * DC[i]=0 means that S(XC[i])=YC[i] |
|
|
//| * DC[i]=1 means that S'(XC[i])=YC[i] |
|
|
//| SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS |
|
|
//| K - number of constraints (optional): |
|
|
//| * 0<=K<M. |
|
|
//| * K=0 means no constraints (XC/YC/DC are not used) |
|
|
//| * if given, only first K elements of XC/YC/DC are |
|
|
//| used |
|
|
//| * if not given, automatically determined from |
|
|
//| XC/YC/DC |
|
|
//| M - number of basis functions (= 2 * number of nodes), |
|
|
//| M>=4, |
|
|
//| M IS EVEN! |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearW() subroutine: |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| -2 means odd M was passed (which is not |
|
|
//| supported) |
|
|
//| -1 means another errors in parameters |
|
|
//| passed (N<=0, for example) |
|
|
//| S - spline interpolant. |
|
|
//| Rep - report, same format as in LSFitLinearW() subroutine. |
|
|
//| Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//| IMPORTANT: |
|
|
//| this subroitine supports only even M's |
|
|
//| ORDER OF POINTS |
|
|
//| ubroutine automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//| SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: |
|
|
//| Setting constraints can lead to undesired results, like |
|
|
//| ill-conditioned behavior, or inconsistency being detected. From |
|
|
//| the other side, it allows us to improve quality of the fit. Here |
|
|
//| we summarize our experience with constrained regression splines:|
|
|
//| * excessive constraints can be inconsistent. Splines are |
|
|
//| piecewise cubic functions, and it is easy to create an example,|
|
|
//| where large number of constraints concentrated in small area |
|
|
//| will result in inconsistency. Just because spline is not |
|
|
//| flexible enough to satisfy all of them. And same constraints |
|
|
//| spread across the [min(x),max(x)] will be perfectly consistent.|
|
|
//| * the more evenly constraints are spread across [min(x),max(x)], |
|
|
//| the more chances that they will be consistent |
|
|
//| * the greater is M (given fixed constraints), the more chances |
|
|
//| that constraints will be consistent |
|
|
//| * in the general case, consistency of constraints is NOT |
|
|
//| GUARANTEED. |
|
|
//| * in the several special cases, however, we can guarantee |
|
|
//| consistency. |
|
|
//| * one of this cases is M>=4 and constraints on the function |
|
|
//| value (AND/OR its derivative) at the interval boundaries. |
|
|
//| * another special case is M>=4 and ONE constraint on the |
|
|
//| function value (OR, BUT NOT AND, derivative) anywhere in |
|
|
//| [min(x),max(x)] |
|
|
//| Our final recommendation is to use constraints WHEN AND ONLY when|
|
|
//| you can't solve your task without them. Anything beyond special |
|
|
//| cases given above is not guaranteed and may result in |
|
|
//| inconsistency. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::Spline1DFitHermiteWC(double &x[],double &y[],double &w[],
|
|
const int n,double &xc[],double &yc[],
|
|
int &dc[],const int k,const int m,
|
|
int &info,CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m%2==0,__FUNCTION__+": M is odd!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k<m,__FUNCTION__+": K>=M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(xc)>=k,__FUNCTION__+": Length(XC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(yc)>=k,__FUNCTION__+": Length(YC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(dc)>=k,__FUNCTION__+": Length(DC)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(xc,k),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(yc,k),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]==0 || dc[i]==1,__FUNCTION__+": DC[i] is neither 0 or 1!"))
|
|
return;
|
|
}
|
|
//--- function call
|
|
Spline1DFitInternal(1,x,y,w,n,xc,yc,dc,k,m,info,s,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Least squares fitting by cubic spline. |
|
|
//| This subroutine is "lightweight" alternative for more complex |
|
|
//| and feature - rich Spline1DFitCubicWC(). See Spline1DFitCubicWC()|
|
|
//| for more information about subroutine parameters (we don't |
|
|
//| duplicate it here because of length) |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::Spline1DFitCubic(double &x[],double &y[],const int n,
|
|
const int m,int &info,
|
|
CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
int dc[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1.0);
|
|
//--- function call
|
|
Spline1DFitCubicWC(x,y,w,n,xc,yc,dc,0,m,info,s,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Least squares fitting by Hermite spline. |
|
|
//| This subroutine is "lightweight" alternative for more complex |
|
|
//| and feature - rich Spline1DFitHermiteWC(). See |
|
|
//| Spline1DFitHermiteWC() description for more information about |
|
|
//| subroutine parameters (we don't duplicate it here because of |
|
|
//| length). |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::Spline1DFitHermite(double &x[],double &y[],const int n,
|
|
const int m,int &info,
|
|
CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
int dc[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m%2==0,__FUNCTION__+": M is odd!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1.0);
|
|
//--- function call
|
|
Spline1DFitHermiteWC(x,y,w,n,xc,yc,dc,0,m,info,s,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted linear least squares fitting. |
|
|
//| QR decomposition is used to reduce task to MxM, then triangular |
|
|
//| solver or SVD-based solver is used depending on condition number |
|
|
//| of the system. It allows to maximize speed and retain decent |
|
|
//| accuracy. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Y - array[0..N-1] Function values in N points. |
|
|
//| W - array[0..N-1] Weights corresponding to function |
|
|
//| values. Each summand in square sum of |
|
|
//| approximation deviations from given values is |
|
|
//| multiplied by the square of corresponding weight.|
|
|
//| FMatrix - a table of basis functions values, |
|
|
//| array[0..N-1, 0..M-1]. FMatrix[I, J] - value of |
|
|
//| J-th basis function in I-th point. |
|
|
//| N - number of points used. N>=1. |
|
|
//| M - number of basis functions, M>=1. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - error code: |
|
|
//| * -4 internal SVD decomposition subroutine |
|
|
//| failed (very rare and for degenerate |
|
|
//| systems only) |
|
|
//| * -1 incorrect N/M were specified |
|
|
//| * 1 task is solved |
|
|
//| C - decomposition coefficients, array[0..M-1] |
|
|
//| Rep - fitting report. Following fields are set: |
|
|
//| * Rep.TaskRCond reciprocal of condition |
|
|
//| number |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the|
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE |
|
|
//| CALCULATED |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinearW(double &y[],double &w[],CMatrixDouble &fmatrix,
|
|
const int n,const int m,int &info,
|
|
double &c[],CLSFitReport &rep)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- function call
|
|
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted constained linear least squares fitting. |
|
|
//| This is variation of LSFitLinearW(), which searchs for |
|
|
//| min|A*x=b| given that K additional constaints C*x=bc are |
|
|
//| satisfied. It reduces original task to modified one: min|B*y-d| |
|
|
//| WITHOUT constraints, then LSFitLinearW() is called. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Y - array[0..N-1] Function values in N points. |
|
|
//| W - array[0..N-1] Weights corresponding to function |
|
|
//| values. Each summand in square sum of |
|
|
//| approximation deviations from given values is |
|
|
//| multiplied by the square of corresponding |
|
|
//| weight. |
|
|
//| FMatrix - a table of basis functions values, |
|
|
//| array[0..N-1, 0..M-1]. FMatrix[I,J] - value of |
|
|
//| J-th basis function in I-th point. |
|
|
//| CMatrix - a table of constaints, array[0..K-1,0..M]. |
|
|
//| I-th row of CMatrix corresponds to I-th linear |
|
|
//| constraint: CMatrix[I,0]*C[0] + ... + |
|
|
//| + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M] |
|
|
//| N - number of points used. N>=1. |
|
|
//| M - number of basis functions, M>=1. |
|
|
//| K - number of constraints, 0 <= K < M |
|
|
//| K=0 corresponds to absence of constraints. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - error code: |
|
|
//| * -4 internal SVD decomposition subroutine |
|
|
//| failed (very rare and for degenerate |
|
|
//| systems only) |
|
|
//| * -3 either too many constraints (M or more), |
|
|
//| degenerate constraints (some constraints |
|
|
//| are repetead twice) or inconsistent |
|
|
//| constraints were specified. |
|
|
//| * 1 task is solved |
|
|
//| C - decomposition coefficients, array[0..M-1] |
|
|
//| Rep - fitting report. Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the|
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE |
|
|
//| CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinearWC(double &cy[],double &w[],CMatrixDouble &fmatrix,
|
|
CMatrixDouble &ccmatrix,const int n,
|
|
const int m,const int k,int &info,
|
|
double &c[],CLSFitReport &rep)
|
|
{
|
|
double v=0;
|
|
//--- create arrays
|
|
double tau[];
|
|
double tmp[];
|
|
double c0[];
|
|
double y[];
|
|
//--- create matrix
|
|
CMatrixDouble q;
|
|
CMatrixDouble f2;
|
|
CMatrixDouble cmatrix;
|
|
//--- copy array
|
|
ArrayCopy(y,cy);
|
|
//--- copy matrix
|
|
cmatrix=ccmatrix;
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(cmatrix)>=k,__FUNCTION__+": rows(CMatrix)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(cmatrix)>=m+1 || k==0,__FUNCTION__+": cols(CMatrix)<M+1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(cmatrix,k,m+1),__FUNCTION__+": CMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(k>=m)
|
|
{
|
|
info=-3;
|
|
return;
|
|
}
|
|
//--- Solve
|
|
if(k==0)
|
|
{
|
|
//--- no constraints
|
|
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
|
|
}
|
|
else
|
|
{
|
|
//--- First,find general form solution of constraints system:
|
|
//--- * factorize C=L*Q
|
|
//--- * unpack Q
|
|
//--- * fill upper part of C with zeros (for RCond)
|
|
//--- We got C=C0+Q2'*y where Q2 is lower M-K rows of Q.
|
|
COrtFac::RMatrixLQ(cmatrix,k,m,tau);
|
|
COrtFac::RMatrixLQUnpackQ(cmatrix,k,m,tau,m,q);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
for(int j=i+1; j<=m-1; j++)
|
|
cmatrix.Set(i,j,0.0);
|
|
}
|
|
//--- check
|
|
if(CRCond::RMatrixLURCondInf(cmatrix,k)<1000*CMath::m_machineepsilon)
|
|
{
|
|
info=-3;
|
|
return;
|
|
}
|
|
//--- allocation
|
|
ArrayResize(tmp,k);
|
|
//--- calculation
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(i>0)
|
|
{
|
|
v=0.0;
|
|
for(int i_=0; i_<=i-1; i_++)
|
|
v+=cmatrix.Get(i,i_)*tmp[i_];
|
|
}
|
|
else
|
|
v=0;
|
|
//--- change values
|
|
tmp[i]=(cmatrix[i][m]-v)/cmatrix[i][i];
|
|
}
|
|
//--- allocation
|
|
ArrayResize(c0,m);
|
|
//--- calculation
|
|
for(int i=0; i<=m-1; i++)
|
|
c0[i]=0;
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
v=tmp[i];
|
|
for(int i_=0; i_<=m-1; i_++)
|
|
c0[i_]=c0[i_]+v*q.Get(i,i_);
|
|
}
|
|
//--- Second,prepare modified matrix F2=F*Q2' and solve modified task
|
|
ArrayResize(tmp,MathMax(n,m)+1);
|
|
f2.Resize(n,m-k);
|
|
//--- function call
|
|
CBlas::MatrixVectorMultiply(fmatrix,0,n-1,0,m-1,false,c0,0,m-1,-1.0,y,0,n-1,1.0);
|
|
//--- function call
|
|
CBlas::MatrixMatrixMultiply(fmatrix,0,n-1,0,m-1,false,q,k,m-1,0,m-1,true,1.0,f2,0,n-1,0,m-k-1,0.0,tmp);
|
|
//--- function call
|
|
LSFitLinearInternal(y,w,f2,n,m-k,info,tmp,rep);
|
|
rep.m_taskrcond=-1;
|
|
//--- check
|
|
if(info<=0)
|
|
return;
|
|
//--- then,convert back to original answer: C=C0 + Q2'*Y0
|
|
ArrayResize(c,m);
|
|
for(int i_=0; i_<=m-1; i_++)
|
|
c[i_]=c0[i_];
|
|
//--- function call
|
|
CBlas::MatrixVectorMultiply(q,k,m-1,0,m-1,true,tmp,0,m-k-1,1.0,c,0,m-1,1.0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Linear least squares fitting. |
|
|
//| QR decomposition is used to reduce task to MxM, then triangular |
|
|
//| solver or SVD-based solver is used depending on condition number |
|
|
//| of the system. It allows to maximize speed and retain decent |
|
|
//| accuracy. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Y - array[0..N-1] Function values in N points. |
|
|
//| FMatrix - a table of basis functions values, |
|
|
//| array[0..N-1, 0..M-1]. |
|
|
//| FMatrix[I, J] - value of J-th basis function in |
|
|
//| I-th point. |
|
|
//| N - number of points used. N>=1. |
|
|
//| M - number of basis functions, M>=1. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - error code: |
|
|
//| * -4 internal SVD decomposition subroutine |
|
|
//| failed (very rare and for degenerate |
|
|
//| systems only) |
|
|
//| * 1 task is solved |
|
|
//| C - decomposition coefficients, array[0..M-1] |
|
|
//| Rep - fitting report. Following fields are set: |
|
|
//| * Rep.TaskRCond reciprocal of condition |
|
|
//| number |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the|
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE |
|
|
//| CALCULATED |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinear(double &y[],CMatrixDouble &fmatrix,
|
|
const int n,const int m,int &info,
|
|
double &c[],CLSFitReport &rep)
|
|
{
|
|
//--- create array
|
|
double w[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1.0);
|
|
//--- function call
|
|
LSFitLinearInternal(y,w,fmatrix,n,m,info,c,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinear(CRowDouble &y,CMatrixDouble &fmatrix,
|
|
const int n,const int m,int &info,
|
|
CRowDouble &c,CLSFitReport &rep)
|
|
{
|
|
//--- create array
|
|
double w[];
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1.0);
|
|
double Y[];
|
|
double C[];
|
|
y.ToArray(Y);
|
|
c.ToArray(C);
|
|
//--- function call
|
|
LSFitLinearInternal(Y,w,fmatrix,n,m,info,C,rep);
|
|
c=C;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Constained linear least squares fitting. |
|
|
//| This is variation of LSFitLinear(), which searchs for min|A*x=b| |
|
|
//| given that K additional constaints C*x=bc are satisfied. It |
|
|
//| reduces original task to modified one: min|B*y-d| WITHOUT |
|
|
//| constraints, then LSFitLinear() is called. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Y - array[0..N-1] Function values in N points. |
|
|
//| FMatrix - a table of basis functions values, |
|
|
//| array[0..N-1, 0..M-1]. FMatrix[I,J] - value of |
|
|
//| J-th basis function in I-th point. |
|
|
//| CMatrix - a table of constaints, array[0..K-1,0..M]. |
|
|
//| I-th row of CMatrix corresponds to I-th linear |
|
|
//| constraint: CMatrix[I,0]*C[0] + ... + |
|
|
//| + CMatrix[I,M-1]*C[M-1] = CMatrix[I,M] |
|
|
//| N - number of points used. N>=1. |
|
|
//| M - number of basis functions, M>=1. |
|
|
//| K - number of constraints, 0 <= K < M |
|
|
//| K=0 corresponds to absence of constraints. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - error code: |
|
|
//| * -4 internal SVD decomposition subroutine |
|
|
//| failed (very rare and for degenerate |
|
|
//| systems only) |
|
|
//| * -3 either too many constraints (M or more), |
|
|
//| degenerate constraints (some constraints |
|
|
//| are repetead twice) or inconsistent |
|
|
//| constraints were specified. |
|
|
//| * 1 task is solved |
|
|
//| C - decomposition coefficients, array[0..M-1] |
|
|
//| Rep - fitting report. Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the|
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE |
|
|
//| CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinearC(double &cy[],CMatrixDouble &fmatrix,
|
|
CMatrixDouble &cmatrix,const int n,
|
|
const int m,const int k,int &info,
|
|
double &c[],CLSFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double y[];
|
|
//--- copy array
|
|
ArrayCopy(y,cy);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(fmatrix)>=n,__FUNCTION__+": rows(FMatrix)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(fmatrix)>=m,__FUNCTION__+": cols(FMatrix)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(fmatrix,n,m),__FUNCTION__+": FMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(cmatrix)>=k,__FUNCTION__+": rows(CMatrix)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(cmatrix)>=m+1 || k==0,__FUNCTION__+": cols(CMatrix)<M+1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(cmatrix,k,m+1),__FUNCTION__+": CMatrix contains infinite or NaN values!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
ArrayInitialize(w,1.0);
|
|
//--- function call
|
|
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,c,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted nonlinear least squares fitting using function values |
|
|
//| only. |
|
|
//| Combination of numerical differentiation and secant updates is |
|
|
//| used to obtain function Jacobian. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
|
|
//| + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * w is an N-dimensional vector of weight coefficients, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses only f(c,x[i]). |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| DiffStep- numerical differentiation step; |
|
|
//| should not be very small or large; |
|
|
//| large = loss of accuracy |
|
|
//| small = growth of round-off errors |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateWF(CMatrixDouble &x,double &y[],double &w[],
|
|
double &c[],const int n,const int m,
|
|
const int k,const double diffstep,
|
|
CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is not finite!"))
|
|
return;
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
|
|
return;
|
|
|
|
State.m_teststep=0;
|
|
State.m_diffstep=diffstep;
|
|
State.m_npoints=n;
|
|
State.m_nweights=n;
|
|
State.m_wkind=1;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
LSFitSetCond(State,0.0,0);
|
|
LSFitSetStpMax(State,0.0);
|
|
LSFitSetXRep(State,false);
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_tasky.Resize(n);
|
|
State.m_taskw.Resize(n);
|
|
State.m_c.Resize(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_x.Resize(m);
|
|
State.m_taskw=w;
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
State.m_optalgo=0;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
CMinLM::MinLMCreateV(k,n,State.m_c0,diffstep,State.m_optstate);
|
|
LSFitClearRequestFields(State);
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear least squares fitting using function values only. |
|
|
//| Combination of numerical differentiation and secant updates is |
|
|
//| used to obtain function Jacobian. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = (f(c,x[0])-y[0])^2 + ... + (f(c,x[n-1])-y[n-1])^2, |
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * w is an N-dimensional vector of weight coefficients, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses only f(c,x[i]). |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| DiffStep- numerical differentiation step; |
|
|
//| should not be very small or large; |
|
|
//| large = loss of accuracy |
|
|
//| small = growth of round-off errors |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateF(CMatrixDouble &x,double &y[],double &c[],
|
|
const int n,const int m,const int k,
|
|
const double diffstep,CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(diffstep),__FUNCTION__+": DiffStep is not finite!"))
|
|
return;
|
|
if(!CAp::Assert(diffstep>0.0,__FUNCTION__+": DiffStep<=0!"))
|
|
return;
|
|
|
|
State.m_teststep=0;
|
|
State.m_diffstep=diffstep;
|
|
State.m_npoints=n;
|
|
State.m_wkind=0;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
LSFitSetCond(State,0.0,0);
|
|
LSFitSetStpMax(State,0.0);
|
|
LSFitSetXRep(State,false);
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_x.Resize(m);
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_tasky.Resize(n);
|
|
State.m_c.Resize(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
State.m_optalgo=0;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
CMinLM::MinLMCreateV(k,n,State.m_c0,diffstep,State.m_optstate);
|
|
LSFitClearRequestFields(State);
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted nonlinear least squares fitting using gradient only. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
|
|
//| + (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * w is an N-dimensional vector of weight coefficients, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses only f(c,x[i]) and its gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| CheapFG - boolean flag, which is: |
|
|
//| * True if both function and gradient calculation |
|
|
//| complexity are less than O(M^2). An |
|
|
//| improved algorithm can be used which |
|
|
//| corresponds to FGJ scheme from MINLM unit.|
|
|
//| * False otherwise. |
|
|
//| Standard Jacibian-bases |
|
|
//| Levenberg-Marquardt algo will be used (FJ |
|
|
//| scheme). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| See also: |
|
|
//| LSFitResults |
|
|
//| LSFitCreateFG (fitting without weights) |
|
|
//| LSFitCreateWFGH (fitting using Hessian) |
|
|
//| LSFitCreateFGH (fitting using Hessian, without weights) |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateWFG(CMatrixDouble &x,double &y[],double &w[],
|
|
double &c[],const int n,const int m,
|
|
const int k,bool cheapfg,CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
|
|
State.m_teststep=0;
|
|
State.m_diffstep=0;
|
|
State.m_npoints=n;
|
|
State.m_nweights=n;
|
|
State.m_wkind=1;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
LSFitSetCond(State,0.0,0);
|
|
LSFitSetStpMax(State,0.0);
|
|
LSFitSetXRep(State,false);
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_taskw=w;
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_tasky.Resize(n);
|
|
State.m_taskw.Resize(n);
|
|
State.m_c.Resize(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_x.Resize(m);
|
|
State.m_g.Resize(k);
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
State.m_optalgo=1;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
if(cheapfg)
|
|
CMinLM::MinLMCreateVGJ(k,n,State.m_c0,State.m_optstate);
|
|
else
|
|
CMinLM::MinLMCreateVJ(k,n,State.m_c0,State.m_optstate);
|
|
//--- function call
|
|
LSFitClearRequestFields(State);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear least squares fitting using gradient only, without |
|
|
//| individual weights. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = ((f(c,x[0])-y[0]))^2 + ... + ((f(c,x[n-1])-y[n-1]))^2,|
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses only f(c,x[i]) and its gradient. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| CheapFG - boolean flag, which is: |
|
|
//| * True if both function and gradient calculation|
|
|
//| complexity are less than O(M^2). An |
|
|
//| improved algorithm can be used which |
|
|
//| corresponds to FGJ scheme from MINLM |
|
|
//| unit. |
|
|
//| * False otherwise. |
|
|
//| Standard Jacibian-bases |
|
|
//| Levenberg-Marquardt algo will be used |
|
|
//| (FJ scheme). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateFG(CMatrixDouble &x,double &y[],double &c[],
|
|
const int n,const int m,const int k,
|
|
const bool cheapfg,CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialization
|
|
State.m_teststep=0;
|
|
State.m_diffstep=0;
|
|
State.m_npoints=n;
|
|
State.m_wkind=0;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
LSFitSetCond(State,0.0,0);
|
|
LSFitSetStpMax(State,0.0);
|
|
LSFitSetXRep(State,false);
|
|
//--- copy
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
//--- allocation
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_tasky.Resize(n);
|
|
State.m_c.Resize(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_x.Resize(m);
|
|
State.m_g.Resize(k);
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
State.m_optalgo=1;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
//--- check
|
|
if(cheapfg)
|
|
CMinLM::MinLMCreateVGJ(k,n,State.m_c0,State.m_optstate);
|
|
else
|
|
CMinLM::MinLMCreateVJ(k,n,State.m_c0,State.m_optstate);
|
|
//--- function call
|
|
LSFitClearRequestFields(State);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted nonlinear least squares fitting using gradient/Hessian. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = (w[0]*(f(c,x[0])-y[0]))^2 + ... + |
|
|
//| (w[n-1]*(f(c,x[n-1])-y[n-1]))^2, |
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * w is an N-dimensional vector of weight coefficients, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses f(c,x[i]), its gradient and its Hessian. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateWFGH(CMatrixDouble &x,double &y[],double &w[],
|
|
double &c[],const int n,const int m,
|
|
const int k,CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": W contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialization
|
|
State.m_teststep=0;
|
|
State.m_diffstep=0;
|
|
State.m_npoints=n;
|
|
State.m_nweights=n;
|
|
State.m_wkind=1;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
//--- function call
|
|
LSFitSetCond(State,0.0,0);
|
|
//--- function call
|
|
LSFitSetStpMax(State,0.0);
|
|
//--- function call
|
|
LSFitSetXRep(State,false);
|
|
//--- copy
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
State.m_taskw=w;
|
|
//--- allocation
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_h=matrix<double>::Zeros(k,k);
|
|
State.m_tasky.Resize(n);
|
|
State.m_taskw.Resize(n);
|
|
State.m_c=vector<double>::Zeros(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_x=vector<double>::Zeros(m);
|
|
State.m_g=vector<double>::Zeros(k);
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
//--- change values
|
|
State.m_optalgo=2;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
//--- function call
|
|
CMinLM::MinLMCreateFGH(k,State.m_c0,State.m_optstate);
|
|
//--- function call
|
|
LSFitClearRequestFields(State);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear least squares fitting using gradient/Hessian, without |
|
|
//| individial weights. |
|
|
//| Nonlinear task min(F(c)) is solved, where |
|
|
//| F(c) = ((f(c,x[0])-y[0]))^2 + ... + |
|
|
//| ((f(c,x[n-1])-y[n-1]))^2, |
|
|
//| * N is a number of points, |
|
|
//| * M is a dimension of a space points belong to, |
|
|
//| * K is a dimension of a space of parameters being fitted, |
|
|
//| * x is a set of N points, each of them is an M-dimensional |
|
|
//| vector, |
|
|
//| * c is a K-dimensional vector of parameters being fitted |
|
|
//| This subroutine uses f(c,x[i]), its gradient and its Hessian. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[0..N-1,0..M-1], points (one row = one |
|
|
//| point) |
|
|
//| Y - array[0..N-1], function values. |
|
|
//| C - array[0..K-1], initial approximation to the |
|
|
//| solution, |
|
|
//| N - number of points, N>1 |
|
|
//| M - dimension of space |
|
|
//| K - number of parameters being fitted |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitCreateFGH(CMatrixDouble &x,double &y[],double &c[],
|
|
const int n,const int m,const int k,
|
|
CLSFitState &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(c)>=k,__FUNCTION__+": length(C)<K!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(c,k),__FUNCTION__+": C contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NaN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(x)>=n,__FUNCTION__+": rows(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(x)>=m,__FUNCTION__+": cols(X)<M!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,m),__FUNCTION__+": X contains infinite or NaN values!"))
|
|
return;
|
|
//--- initialization
|
|
State.m_teststep=0;
|
|
State.m_diffstep=0;
|
|
State.m_npoints=n;
|
|
State.m_wkind=0;
|
|
State.m_m=m;
|
|
State.m_k=k;
|
|
//--- function call
|
|
LSFitSetCond(State,0.0,0);
|
|
//--- function call
|
|
LSFitSetStpMax(State,0.0);
|
|
//--- function call
|
|
LSFitSetXRep(State,false);
|
|
//--- copy
|
|
State.m_c0=c;
|
|
State.m_c1=c;
|
|
State.m_taskx=x;
|
|
State.m_tasky=y;
|
|
//--- allocation
|
|
State.m_taskx.Resize(n,m);
|
|
State.m_tasky.Resize(n);
|
|
State.m_c.Resize(k);
|
|
State.m_c0.Resize(k);
|
|
State.m_c1.Resize(k);
|
|
State.m_h.Resize(k,k);
|
|
State.m_x.Resize(m);
|
|
State.m_g.Resize(k);
|
|
State.m_s=vector<double>::Ones(k);
|
|
State.m_bndl=vector<double>::Full(k,AL_NEGINF);
|
|
State.m_bndu=vector<double>::Full(k,AL_POSINF);
|
|
//--- change values
|
|
State.m_optalgo=2;
|
|
State.m_prevnpt=-1;
|
|
State.m_prevalgo=-1;
|
|
State.m_nec=0;
|
|
State.m_nic=0;
|
|
//--- function call
|
|
CMinLM::MinLMCreateFGH(k,State.m_c0,State.m_optstate);
|
|
//--- function call
|
|
LSFitClearRequestFields(State);
|
|
//--- allocation
|
|
State.m_rstate.ia.Resize(7);
|
|
State.m_rstate.ra.Resize(9);
|
|
State.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Stopping conditions for nonlinear least squares fitting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| EpsF - stopping criterion. Algorithm stops if |
|
|
//| |F(k+1)-F(k)| <= EpsF*max{|F(k)|, |F(k+1)|, 1} |
|
|
//| EpsX - >=0 |
|
|
//| The subroutine finishes its work if on k+1-th |
|
|
//| iteration the condition |v|<=EpsX is fulfilled, |
|
|
//| where: |
|
|
//| * |.| means Euclidian norm |
|
|
//| * v - scaled step vector, v[i]=dx[i]/s[i] |
|
|
//| * dx - ste pvector, dx=X(k+1)-X(k) |
|
|
//| * s - scaling coefficients set by LSFitSetScale()|
|
|
//| MaxIts - maximum number of iterations. If MaxIts=0, the |
|
|
//| number of iterations is unlimited. Only |
|
|
//| Levenberg-Marquardt iterations are counted |
|
|
//| (L-BFGS/CG iterations are NOT counted because |
|
|
//| their cost is very low compared to that of LM). |
|
|
//| NOTE |
|
|
//| Passing EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to|
|
|
//| automatic stopping criterion selection (according to the scheme |
|
|
//| used by MINLM unit). |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitSetCond(CLSFitState &State,const double epsx,const int maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(epsx),__FUNCTION__+": EpsX is not finite!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(epsx)>=0.0,__FUNCTION__+": negative EpsX!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": negative MaxIts!"))
|
|
return;
|
|
//--- change values
|
|
State.m_epsx=epsx;
|
|
State.m_maxits=maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets maximum step length |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| StpMax - maximum step length, >=0. Set StpMax to 0.0, if |
|
|
//| you don't want to limit step length. |
|
|
//| Use this subroutine when you optimize target function which |
|
|
//| contains exp() or other fast growing functions, and optimization |
|
|
//| algorithm makes too large steps which leads to overflow. This |
|
|
//| function allows us to reject steps that are too large (and |
|
|
//| therefore expose us to the possible overflow) without actually |
|
|
//| calculating function value at the x+stp*d. |
|
|
//| NOTE: non-zero StpMax leads to moderate performance degradation |
|
|
//| because intermediate step of preconditioned L-BFGS optimization |
|
|
//| is incompatible with limits on step size. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitSetStpMax(CLSFitState &State,const double stpmax)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(stpmax>=0.0,__FUNCTION__+": StpMax<0!"))
|
|
return;
|
|
//--- change value
|
|
State.m_stpmax=stpmax;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function turns on/off reporting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure which stores algorithm State |
|
|
//| NeedXRep- whether iteration reports are needed or not |
|
|
//| When reports are needed, State.C (current parameters) and State. |
|
|
//| F (current value of fitting function) are reported. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitSetXRep(CLSFitState &State,const bool needxrep)
|
|
{
|
|
State.m_xrep=needxrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets scaling coefficients for underlying optimizer.|
|
|
//| ALGLIB optimizers use scaling matrices to test stopping |
|
|
//| conditions (step size and gradient are scaled before comparison |
|
|
//| with tolerances). Scale of the I-th variable is a translation |
|
|
//| invariant measure of: |
|
|
//| a) "how large" the variable is |
|
|
//| b) how large the step should be to make significant changes in |
|
|
//| the function |
|
|
//| Generally, scale is NOT considered to be a form of |
|
|
//| preconditioner. But LM optimizer is unique in that it uses |
|
|
//| scaling matrix both in the stopping condition tests and as |
|
|
//| Marquardt damping factor. |
|
|
//| Proper scaling is very important for the algorithm performance. |
|
|
//| It is less important for the quality of results, but still has |
|
|
//| some influence (it is easier to converge when variables are |
|
|
//| properly scaled, so premature stopping is possible when very |
|
|
//| badly scalled variables are combined with relaxed stopping |
|
|
//| conditions). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| S - array[N], non-zero scaling coefficients |
|
|
//| S[i] may be negative, sign doesn't matter. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitSetScale(CLSFitState &State,double &s[])
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(s)>=State.m_k,__FUNCTION__+": Length(S)<K"))
|
|
return;
|
|
|
|
for(int i=0; i<State.m_k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(s[i]),__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((double)(s[i])!=0.0,__FUNCTION__+": S contains infinite or NAN elements"))
|
|
return;
|
|
//--- change values
|
|
State.m_s.Set(i,MathAbs(s[i]));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets boundary constraints for underlying optimizer |
|
|
//| Boundary constraints are inactive by default (after initial |
|
|
//| creation). They are preserved until explicitly turned off with |
|
|
//| another SetBC() call. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - structure stores algorithm State |
|
|
//| BndL - lower bounds, array[K]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very small number or -INF (latter is |
|
|
//| recommended because it will allow solver to use |
|
|
//| better algorithm). |
|
|
//| BndU - upper bounds, array[K]. |
|
|
//| If some (all) variables are unbounded, you may |
|
|
//| specify very large number or +INF (latter is |
|
|
//| recommended because it will allow solver to use |
|
|
//| better algorithm). |
|
|
//| NOTE 1: it is possible to specify BndL[i]=BndU[i]. In this case |
|
|
//| I-th variable will be "frozen" at X[i]=BndL[i]=BndU[i]. |
|
|
//| NOTE 2: unlike other constrained optimization algorithms, this |
|
|
//| solver has following useful properties: |
|
|
//| * bound constraints are always satisfied exactly |
|
|
//| * function is evaluated only INSIDE area specified by bound |
|
|
//| constraints |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitSetBC(CLSFitState &State,double &bndl[],
|
|
double &bndu[])
|
|
{
|
|
int k=State.m_k;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndl)>=k,__FUNCTION__+": Length(BndL)<K"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(bndu)>=k,__FUNCTION__+": Length(BndU)<K"))
|
|
return;
|
|
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndl[i]) || CInfOrNaN::IsNegativeInfinity(bndl[i]),__FUNCTION__+": BndL contains NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(bndu[i]) || CInfOrNaN::IsPositiveInfinity(bndu[i]),__FUNCTION__+": BndU contains NAN or -INF"))
|
|
return;
|
|
//--- check
|
|
if(CMath::IsFinite(bndl[i]) && CMath::IsFinite(bndu[i]))
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(bndl[i]<=bndu[i],__FUNCTION__+": BndL[i]>BndU[i]"))
|
|
return;
|
|
}
|
|
//--- change values
|
|
State.m_bndl.Set(i,bndl[i]);
|
|
State.m_bndu.Set(i,bndu[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear least squares fitting results. |
|
|
//| Called after return from LSFitFit(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - algorithm State |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info - completetion code: |
|
|
//| * 1 relative function improvement is no |
|
|
//| more than EpsF. |
|
|
//| * 2 relative step is no more than EpsX. |
|
|
//| * 4 gradient norm is no more than EpsG |
|
|
//| * 5 MaxIts steps was taken |
|
|
//| * 7 stopping conditions are too |
|
|
//| stringent, further improvement is |
|
|
//| impossible |
|
|
//| C - array[0..K-1], solution |
|
|
//| Rep - optimization report. Following fields are set: |
|
|
//| * Rep.TerminationType completetion code: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the|
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE |
|
|
//| CALCULATED |
|
|
//| * WRMSError weighted rms error on the |
|
|
//| (X,Y). |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitResults(CLSFitState &State,int &info,double &c[],
|
|
CLSFitReport &rep)
|
|
{
|
|
//--- initialization
|
|
info=State.m_repterminationtype;
|
|
//--- check
|
|
if(info>0)
|
|
{
|
|
//--- allocation
|
|
ArrayResize(c,State.m_k);
|
|
for(int i_=0; i_<=State.m_k-1; i_++)
|
|
c[i_]=State.m_c[i_];
|
|
//--- change values
|
|
rep.m_rmserror=State.m_reprmserror;
|
|
rep.m_wrmserror=State.m_repwrmserror;
|
|
rep.m_avgerror=State.m_repavgerror;
|
|
rep.m_avgrelerror=State.m_repavgrelerror;
|
|
rep.m_maxerror=State.m_repmaxerror;
|
|
rep.m_iterationscount=State.m_repiterationscount;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: automatic scaling for LLS tasks. |
|
|
//| NEVER CALL IT DIRECTLY! |
|
|
//| Maps abscissas to [-1,1], standartizes ordinates and |
|
|
//| correspondingly scales constraints. It also scales weights so |
|
|
//| that max(W[i])=1 |
|
|
//| Transformations performed: |
|
|
//| * X, XC [XA,XB] => [-1,+1] |
|
|
//| transformation makes min(X)=-1, max(X)=+1 |
|
|
//| * Y [SA,SB] => [0,1] |
|
|
//| transformation makes mean(Y)=0, stddev(Y)=1 |
|
|
//| * YC transformed accordingly to SA, SB, DC[I] |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitScaleXY(double &x[],double &y[],double &w[],
|
|
const int n,double &xc[],double &yc[],
|
|
int &dc[],const int k,double &xa,
|
|
double &xb,double &sa,double &sb,
|
|
double &xoriginal[],double &yoriginal[])
|
|
{
|
|
//--- create variables
|
|
double xmin=0;
|
|
double xmax=0;
|
|
double mx=0;
|
|
//--- initialization
|
|
xa=0;
|
|
xb=0;
|
|
sa=0;
|
|
sb=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": incorrect N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": incorrect K"))
|
|
return;
|
|
//--- Calculate xmin/xmax.
|
|
//--- Force xmin<>xmax.
|
|
xmin=x[0];
|
|
xmax=x[0];
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
xmin=MathMin(xmin,x[i]);
|
|
xmax=MathMax(xmax,x[i]);
|
|
}
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
xmin=MathMin(xmin,xc[i]);
|
|
xmax=MathMax(xmax,xc[i]);
|
|
}
|
|
//--- check
|
|
if(xmin==xmax)
|
|
{
|
|
//--- check
|
|
if(xmin==0.0)
|
|
{
|
|
xmin=-1;
|
|
xmax=1;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(xmin>0.0)
|
|
xmin=0.5*xmin;
|
|
else
|
|
xmax=0.5*xmax;
|
|
}
|
|
}
|
|
//--- Transform abscissas: map [XA,XB] to [0,1]
|
|
//--- Store old X[] in XOriginal[] (it will be used
|
|
//--- to calculate relative error).
|
|
ArrayResize(xoriginal,n);
|
|
for(int i_=0; i_<n; i_++)
|
|
xoriginal[i_]=x[i_];
|
|
//--- change values
|
|
xa=xmin;
|
|
xb=xmax;
|
|
for(int i=0; i<n; i++)
|
|
x[i]=2*(x[i]-0.5*(xa+xb))/(xb-xa);
|
|
//--- calculation
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]>=0,__FUNCTION__+": internal error!"))
|
|
return;
|
|
xc[i]=2*(xc[i]-0.5*(xa+xb))/(xb-xa);
|
|
yc[i]=yc[i]*MathPow(0.5*(xb-xa),dc[i]);
|
|
}
|
|
//--- Transform function values: map [SA,SB] to [0,1]
|
|
//--- SA=mean(Y),
|
|
//--- SB=SA+stddev(Y).
|
|
//--- Store old Y[] in YOriginal[] (it will be used
|
|
//--- to calculate relative error).
|
|
ArrayResize(yoriginal,n);
|
|
for(int i_=0; i_<n; i_++)
|
|
yoriginal[i_]=y[i_];
|
|
sa=0;
|
|
for(int i=0; i<n; i++)
|
|
sa=sa+y[i];
|
|
sa=sa/n;
|
|
//--- change value
|
|
sb=0;
|
|
for(int i=0; i<n; i++)
|
|
sb=sb+CMath::Sqr(y[i]-sa);
|
|
sb=MathSqrt(sb/n)+sa;
|
|
//--- check
|
|
if(sb==sa)
|
|
sb=2*sa;
|
|
//--- check
|
|
if(sb==sa)
|
|
sb=sa+1;
|
|
for(int i=0; i<n; i++)
|
|
y[i]=(y[i]-sa)/(sb-sa);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(dc[i]==0)
|
|
yc[i]=(yc[i]-sa)/(sb-sa);
|
|
else
|
|
yc[i]=yc[i]/(sb-sa);
|
|
}
|
|
//--- Scale weights
|
|
mx=0;
|
|
for(int i=0; i<n; i++)
|
|
mx=MathMax(mx,MathAbs(w[i]));
|
|
//--- check
|
|
if(mx!=0.0)
|
|
{
|
|
for(int i=0; i<n; i++)
|
|
w[i]=w[i]/mx;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal spline fitting subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::Spline1DFitInternal(const int st,double &cx[],double &cy[],
|
|
double &cw[],const int n,double &cxc[],
|
|
double &cyc[],int &dc[],const int k,
|
|
const int m,int &info,
|
|
CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double mx=0;
|
|
int i=0;
|
|
int j=0;
|
|
int relcnt=0;
|
|
double xa=0;
|
|
double xb=0;
|
|
double sa=0;
|
|
double sb=0;
|
|
double bl=0;
|
|
double br=0;
|
|
double decay=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double y2[];
|
|
double w2[];
|
|
double sx[];
|
|
double sy[];
|
|
double sd[];
|
|
double tmp[];
|
|
double xoriginal[];
|
|
double yoriginal[];
|
|
double x[];
|
|
double y[];
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
//--- create matrix
|
|
CMatrixDouble fmatrix;
|
|
CMatrixDouble cmatrix;
|
|
//--- objects of classes
|
|
CLSFitReport lrep;
|
|
CSpline1DInterpolant s2;
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(w,cw);
|
|
ArrayCopy(xc,cxc);
|
|
ArrayCopy(yc,cyc);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(st==0 || st==1,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- check
|
|
if(st==0 && m<4)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- check
|
|
if(st==1 && m<4)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- check
|
|
if((n<1 || k<0) || k>=m)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
info=0;
|
|
//--- check
|
|
if(dc[i]<0)
|
|
info=-1;
|
|
//--- check
|
|
if(dc[i]>1)
|
|
info=-1;
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
}
|
|
//--- check
|
|
if(st==1 && m%2!=0)
|
|
{
|
|
//--- Hermite fitter must have even number of basis functions
|
|
info=-2;
|
|
return;
|
|
}
|
|
//--- weight decay for correct handling of task which becomes
|
|
//--- degenerate after constraints are applied
|
|
decay=10000*CMath::m_machineepsilon;
|
|
//--- Scale X,Y,XC,YC
|
|
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
|
|
//--- allocate space,initialize:
|
|
//--- * SX - grid for basis functions
|
|
//--- * SY - values of basis functions at grid points
|
|
//--- * FMatrix- values of basis functions at X[]
|
|
//--- * CMatrix- values (derivatives) of basis functions at XC[]
|
|
ArrayResize(y2,n+m);
|
|
ArrayResize(w2,n+m);
|
|
fmatrix.Resize(n+m,m);
|
|
//--- check
|
|
if(k>0)
|
|
cmatrix.Resize(k,m+1);
|
|
//--- check
|
|
if(st==0)
|
|
{
|
|
//--- allocate space for cubic spline
|
|
ArrayResize(sx,m-2);
|
|
ArrayResize(sy,m-2);
|
|
for(j=0; j<=m-2-1; j++)
|
|
sx[j]=(double)(2*j)/(double)(m-2-1)-1;
|
|
}
|
|
//--- check
|
|
if(st==1)
|
|
{
|
|
//--- allocate space for Hermite spline
|
|
ArrayResize(sx,m/2);
|
|
ArrayResize(sy,m/2);
|
|
ArrayResize(sd,m/2);
|
|
for(j=0; j<=m/2-1; j++)
|
|
sx[j]=(double)(2*j)/(double)(m/2-1)-1;
|
|
}
|
|
//--- Prepare design and constraints matrices:
|
|
//--- * fill constraints matrix
|
|
//--- * fill first N rows of design matrix with values
|
|
//--- * fill next M rows of design matrix with regularizing term
|
|
//--- * append M zeros to Y
|
|
//--- * append M elements,mean(abs(W)) each,to W
|
|
for(j=0; j<=m-1; j++)
|
|
{
|
|
//--- prepare Jth basis function
|
|
if(st==0)
|
|
{
|
|
//--- cubic spline basis
|
|
for(i=0; i<=m-2-1; i++)
|
|
sy[i]=0;
|
|
bl=0;
|
|
br=0;
|
|
//--- check
|
|
if(j<m-2)
|
|
sy[j]=1;
|
|
//--- check
|
|
if(j==m-2)
|
|
bl=1;
|
|
//--- check
|
|
if(j==m-1)
|
|
br=1;
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(sx,sy,m-2,1,bl,1,br,s2);
|
|
}
|
|
//--- check
|
|
if(st==1)
|
|
{
|
|
//--- Hermite basis
|
|
for(i=0; i<=m/2-1; i++)
|
|
{
|
|
sy[i]=0;
|
|
sd[i]=0;
|
|
}
|
|
//--- check
|
|
if(j%2==0)
|
|
sy[j/2]=1;
|
|
else
|
|
sd[j/2]=1;
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildHermite(sx,sy,sd,m/2,s2);
|
|
}
|
|
//--- values at X[],XC[]
|
|
for(i=0; i<n; i++)
|
|
fmatrix.Set(i,j,CSpline1D::Spline1DCalc(s2,x[i]));
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]>=0 && dc[i]<=2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(s2,xc[i],v0,v1,v2);
|
|
//--- check
|
|
if(dc[i]==0)
|
|
cmatrix.Set(i,j,v0);
|
|
//--- check
|
|
if(dc[i]==1)
|
|
cmatrix.Set(i,j,v1);
|
|
//--- check
|
|
if(dc[i]==2)
|
|
cmatrix.Set(i,j,v2);
|
|
}
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<k; i++)
|
|
cmatrix.Set(i,m,yc[i]);
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
for(j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(i==j)
|
|
fmatrix.Set(n+i,j,decay);
|
|
else
|
|
fmatrix.Set(n+i,j,0);
|
|
}
|
|
}
|
|
//--- allocation
|
|
ArrayResize(y2,n+m);
|
|
ArrayResize(w2,n+m);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
y2[i_]=y[i_];
|
|
for(i_=0; i_<n; i_++)
|
|
w2[i_]=w[i_];
|
|
//--- change value
|
|
mx=0;
|
|
for(i=0; i<n; i++)
|
|
mx=mx+MathAbs(w[i]);
|
|
mx=mx/n;
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
y2[n+i]=0;
|
|
w2[n+i]=mx;
|
|
}
|
|
//--- Solve constrained task
|
|
if(k>0)
|
|
{
|
|
//--- solve using regularization
|
|
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,tmp,lrep);
|
|
}
|
|
else
|
|
{
|
|
//--- no constraints,no regularization needed
|
|
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,tmp,lrep);
|
|
}
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
//--- Generate spline and scale it
|
|
if(st==0)
|
|
{
|
|
//--- cubic spline basis
|
|
for(i_=0; i_<=m-2-1; i_++)
|
|
sy[i_]=tmp[i_];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(sx,sy,m-2,1,tmp[m-2],1,tmp[m-1],s);
|
|
}
|
|
//--- check
|
|
if(st==1)
|
|
{
|
|
//--- Hermite basis
|
|
for(i=0; i<=m/2-1; i++)
|
|
{
|
|
sy[i]=tmp[2*i];
|
|
sd[i]=tmp[2*i+1];
|
|
}
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildHermite(sx,sy,sd,m/2,s);
|
|
}
|
|
//--- function call
|
|
CSpline1D::Spline1DLinTransX(s,2/(xb-xa),-((xa+xb)/(xb-xa)));
|
|
//--- function call
|
|
CSpline1D::Spline1DLinTransY(s,sb-sa,sa);
|
|
//--- Scale absolute errors obtained from LSFitLinearW.
|
|
//--- Relative error should be calculated separately
|
|
//--- (because of shifting/scaling of the task)
|
|
rep.m_taskrcond=lrep.m_taskrcond;
|
|
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
|
|
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
|
|
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
|
|
rep.m_avgrelerror=0;
|
|
relcnt=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(yoriginal[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CSpline1D::Spline1DCalc(s,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
|
|
relcnt=relcnt+1;
|
|
}
|
|
}
|
|
//--- check
|
|
if(relcnt!=0)
|
|
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal fitting subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitLinearInternal(double &y[],double &w[],
|
|
CMatrixDouble &fmatrix,const int n,
|
|
const int m,int &info,double &c[],
|
|
CLSFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double threshold=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
int relcnt=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double b[];
|
|
double wmod[];
|
|
double tau[];
|
|
double sv[];
|
|
double tmp[];
|
|
double utb[];
|
|
double sutb[];
|
|
//--- create matrix
|
|
CMatrixDouble ft;
|
|
CMatrixDouble q;
|
|
CMatrixDouble l;
|
|
CMatrixDouble r;
|
|
CMatrixDouble u;
|
|
CMatrixDouble vt;
|
|
//--- initialization
|
|
info=0;
|
|
ClearReport(rep);
|
|
//--- check
|
|
if(n<1 || m<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- initialization
|
|
info=1;
|
|
threshold=MathSqrt(CMath::m_machineepsilon);
|
|
//--- Degenerate case,needs special handling
|
|
if(n<m)
|
|
{
|
|
//--- Create design matrix.
|
|
ft.Resize(n,m);
|
|
ArrayResize(b,n);
|
|
ArrayResize(wmod,n);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
v=w[j];
|
|
ft.Row(j,fmatrix[j]* v);
|
|
//--- change values
|
|
b[j]=w[j]*y[j];
|
|
wmod[j]=1;
|
|
}
|
|
//--- LQ decomposition and reduction to M=N
|
|
ArrayResize(c,m);
|
|
ArrayInitialize(c,0);
|
|
rep.m_taskrcond=0;
|
|
//--- function call
|
|
COrtFac::RMatrixLQ(ft,n,m,tau);
|
|
//--- function call
|
|
COrtFac::RMatrixLQUnpackQ(ft,n,m,tau,n,q);
|
|
//--- function call
|
|
COrtFac::RMatrixLQUnpackL(ft,n,m,l);
|
|
//--- function call
|
|
LSFitLinearInternal(b,wmod,l,n,n,info,tmp,rep);
|
|
//--- check
|
|
if(info<=0)
|
|
return;
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=tmp[i];
|
|
for(i_=0; i_<m; i_++)
|
|
c[i_]+=v*q.Get(i,i_);
|
|
}
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- N>=M. Generate design matrix and reduce to N=M using
|
|
//--- QR decomposition.
|
|
ft.Resize(n,m);
|
|
ArrayResize(b,n);
|
|
for(j=0; j<n; j++)
|
|
{
|
|
ft.Row(j,fmatrix[j]*w[j]);
|
|
b[j]=w[j]*y[j];
|
|
}
|
|
//--- function call
|
|
COrtFac::RMatrixQR(ft,n,m,tau);
|
|
//--- function call
|
|
COrtFac::RMatrixQRUnpackQ(ft,n,m,tau,m,q);
|
|
//--- function call
|
|
COrtFac::RMatrixQRUnpackR(ft,n,m,r);
|
|
//--- allocation
|
|
ArrayResize(tmp,m);
|
|
ArrayInitialize(tmp,0);
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=b[i];
|
|
for(i_=0; i_<m; i_++)
|
|
tmp[i_]+=v*q.Get(i,i_);
|
|
}
|
|
//--- allocation
|
|
//--- copy
|
|
ArrayCopy(b,tmp);
|
|
//--- R contains reduced MxM design upper triangular matrix,
|
|
//--- B contains reduced Mx1 right part.
|
|
//--- Determine system condition number and decide
|
|
//--- should we use triangular solver (faster) or
|
|
//--- SVD-based solver (more stable).
|
|
//--- We can use LU-based RCond estimator for this task.
|
|
rep.m_taskrcond=CRCond::RMatrixLURCondInf(r,m);
|
|
//--- check
|
|
if(rep.m_taskrcond>threshold)
|
|
{
|
|
//--- use QR-based solver
|
|
ArrayResize(c,m);
|
|
c[m-1]=b[m-1]/r.Get(m-1,m-1);
|
|
//--- calculation
|
|
for(i=m-2; i>=0; i--)
|
|
{
|
|
v=0.0;
|
|
for(i_=i+1; i_<=m-1; i_++)
|
|
v+=r.Get(i,i_)*c[i_];
|
|
c[i]=(b[i]-v)/r.Get(i,i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- use SVD-based solver
|
|
if(!CSingValueDecompose::RMatrixSVD(r,m,m,1,1,2,sv,u,vt))
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
//--- allocation
|
|
ArrayResize(utb,m);
|
|
ArrayResize(sutb,m);
|
|
ArrayInitialize(utb,0);
|
|
//--- calculation
|
|
for(i=0; i<m; i++)
|
|
{
|
|
v=b[i];
|
|
for(i_=0; i_<m; i_++)
|
|
utb[i_]+=v*u.Get(i,i_);
|
|
}
|
|
//--- check
|
|
if(sv[0]>0.0)
|
|
{
|
|
rep.m_taskrcond=sv[m-1]/sv[0];
|
|
for(i=0; i<m; i++)
|
|
{
|
|
//--- check
|
|
if(sv[i]>threshold*sv[0])
|
|
sutb[i]=utb[i]/sv[i];
|
|
else
|
|
sutb[i]=0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- change values
|
|
rep.m_taskrcond=0;
|
|
ArrayInitialize(sutb,0);
|
|
}
|
|
//--- allocation
|
|
ArrayResize(c,m);
|
|
ArrayInitialize(c,0);
|
|
//--- calculation
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
v=sutb[i];
|
|
for(i_=0; i_<m; i_++)
|
|
c[i_]+=v*vt.Get(i,i_);
|
|
}
|
|
}
|
|
//--- calculate errors
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_avgrelerror=0;
|
|
rep.m_maxerror=0;
|
|
relcnt=0;
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=0.0;
|
|
for(i_=0; i_<m; i_++)
|
|
v+=fmatrix.Get(i,i_)*c[i_];
|
|
//--- change values
|
|
rep.m_rmserror+=CMath::Sqr(v-y[i]);
|
|
rep.m_avgerror+=MathAbs(v-y[i]);
|
|
//--- check
|
|
if(y[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror+=MathAbs(v-y[i])/MathAbs(y[i]);
|
|
relcnt++;
|
|
}
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v-y[i]));
|
|
}
|
|
//--- change values
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
|
|
rep.m_avgerror/=n;
|
|
//--- check
|
|
if(relcnt!=0)
|
|
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
|
|
|
|
CRowDouble nzeros,s,Y,W,C;
|
|
nzeros=vector<double>::Zeros(n);
|
|
s=MathPow(fmatrix.ToMatrix()+0,2).Sum(0);
|
|
for(i=0; i<m; i++)
|
|
{
|
|
if(s[i]!=0)
|
|
s.Set(i,MathSqrt(1/s[i]));
|
|
else
|
|
s.Set(i,1);
|
|
}
|
|
Y=y;
|
|
W=w;
|
|
C=c;
|
|
EstimateErrors(fmatrix,nzeros,Y,W,C,s,n,m,rep,r,1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LSFitClearRequestFields(CLSFitState &State)
|
|
{
|
|
//--- change values
|
|
State.m_needf=false;
|
|
State.m_needfg=false;
|
|
State.m_needfgh=false;
|
|
State.m_xupdated=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine, calculates barycentric basis functions. |
|
|
//| Used for efficient simultaneous calculation of N basis functions.|
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::BarycentricCalcBasis(CBarycentricInterpolant &b,
|
|
const double t,double &y[])
|
|
{
|
|
//--- create variables
|
|
double s2=0;
|
|
double s=0;
|
|
double v=0;
|
|
//--- special case: N=1
|
|
if(b.m_n==1)
|
|
{
|
|
y[0]=1;
|
|
return;
|
|
}
|
|
//--- Here we assume that task is normalized,i.m_e.:
|
|
//--- 1. abs(Y[i])<=1
|
|
//--- 2. abs(W[i])<=1
|
|
//--- 3. X[] is ordered
|
|
//--- First,we decide: should we use "safe" formula (guarded
|
|
//--- against overflow) or fast one?
|
|
s=MathAbs(t-b.m_x[0]);
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
v=b.m_x[i];
|
|
//--- check
|
|
if(v==t)
|
|
{
|
|
for(int j=0; j<=b.m_n-1; j++)
|
|
y[j]=0;
|
|
y[i]=1;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- change value
|
|
v=MathAbs(t-v);
|
|
//--- check
|
|
if(v<s)
|
|
s=v;
|
|
}
|
|
s2=0;
|
|
//--- calculation
|
|
for(int i=0; i<=b.m_n-1; i++)
|
|
{
|
|
v=s/(t-b.m_x[i]);
|
|
v=v*b.m_w[i];
|
|
y[i]=v;
|
|
s2=s2+v;
|
|
}
|
|
//--- change values
|
|
v=1/s2;
|
|
for(int i_=0; i_<=b.m_n-1; i_++)
|
|
y[i_]=v*y[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is internal function for Chebyshev fitting. |
|
|
//| It assumes that input data are normalized: |
|
|
//| * X/XC belong to [-1,+1], |
|
|
//| * mean(Y)=0, stddev(Y)=1. |
|
|
//| It does not checks inputs for errors. |
|
|
//| This function is used to fit general (shifted) Chebyshev models, |
|
|
//| power basis models or barycentric models. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| N - number of points, N>0. |
|
|
//| XC - points where polynomial values/derivatives are |
|
|
//| constrained, array[0..K-1]. |
|
|
//| YC - values of constraints, array[0..K-1] |
|
|
//| DC - array[0..K-1], types of constraints: |
|
|
//| * DC[i]=0 means that P(XC[i])=YC[i] |
|
|
//| * DC[i]=1 means that P'(XC[i])=YC[i] |
|
|
//| K - number of constraints, 0<=K<M. |
|
|
//| K=0 means no constraints (XC/YC/DC are not used in |
|
|
//| such cases) |
|
|
//| M - number of basis functions (= polynomial_degree + 1), |
|
|
//| M>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearW() subroutine: |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| C - interpolant in Chebyshev form; [-1,+1] is used as |
|
|
//| base interval |
|
|
//| Rep - report, same format as in LSFitLinearW() subroutine. |
|
|
//| Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number for|
|
|
//| K<>0. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::InternalChebyshevFit(double &x[],double &y[],double &w[],
|
|
const int n,double &cxc[],double &cyc[],
|
|
int &dc[],const int k,const int m,
|
|
int &info,double &c[],CLSFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double mx=0;
|
|
double decay=0;
|
|
//--- create arrays
|
|
double y2[];
|
|
double w2[];
|
|
double tmp[];
|
|
double tmp2[];
|
|
double tmpdiff[];
|
|
double bx[];
|
|
double by[];
|
|
double bw[];
|
|
double xc[];
|
|
double yc[];
|
|
//--- create matrix
|
|
CMatrixDouble fmatrix;
|
|
CMatrixDouble cmatrix;
|
|
//--- copy arrays
|
|
ArrayCopy(xc,cxc);
|
|
ArrayCopy(yc,cyc);
|
|
//--- initialization
|
|
info=0;
|
|
//--- weight decay for correct handling of task which becomes
|
|
//--- degenerate after constraints are applied
|
|
decay=10000*CMath::m_machineepsilon;
|
|
//--- allocate space,initialize/fill:
|
|
//--- * FMatrix- values of basis functions at X[]
|
|
//--- * CMatrix- values (derivatives) of basis functions at XC[]
|
|
//--- * fill constraints matrix
|
|
//--- * fill first N rows of design matrix with values
|
|
//--- * fill next M rows of design matrix with regularizing term
|
|
//--- * append M zeros to Y
|
|
//--- * append M elements,mean(abs(W)) each,to W
|
|
ArrayResize(y2,n+m);
|
|
ArrayResize(w2,n+m);
|
|
ArrayResize(tmp,m);
|
|
ArrayResize(tmpdiff,m);
|
|
fmatrix=matrix<double>::Zeros(n+m,m);
|
|
//--- check
|
|
if(k>0)
|
|
cmatrix.Resize(k,m+1);
|
|
//--- Fill design matrix,Y2,W2:
|
|
//--- * first N rows with basis functions for original points
|
|
//--- * next M rows with decay terms
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- prepare Ith row
|
|
//--- use Tmp for calculations to avoid multidimensional arrays overhead
|
|
for(int j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(j==0)
|
|
tmp[j]=1;
|
|
else
|
|
{
|
|
//--- check
|
|
if(j==1)
|
|
tmp[j]=x[i];
|
|
else
|
|
tmp[j]=2*x[i]*tmp[j-1]-tmp[j-2];
|
|
}
|
|
}
|
|
//--- copy
|
|
for(int i_=0; i_<=m-1; i_++)
|
|
fmatrix.Set(i,i_,tmp[i_]);
|
|
}
|
|
//--- calculation
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
for(int j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(i==j)
|
|
fmatrix.Set(n+i,j,decay);
|
|
else
|
|
fmatrix.Set(n+i,j,0);
|
|
}
|
|
}
|
|
//--- copy
|
|
for(int i_=0; i_<n; i_++)
|
|
y2[i_]=y[i_];
|
|
for(int i_=0; i_<n; i_++)
|
|
w2[i_]=w[i_];
|
|
//--- change value
|
|
mx=0;
|
|
for(int i=0; i<n; i++)
|
|
mx=mx+MathAbs(w[i]);
|
|
mx=mx/n;
|
|
//--- change values
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
y2[n+i]=0;
|
|
w2[n+i]=mx;
|
|
}
|
|
//--- fill constraints matrix
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- prepare Ith row
|
|
//--- use Tmp for basis function values,
|
|
//--- TmpDiff for basos function derivatives
|
|
for(int j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(j==0)
|
|
{
|
|
tmp[j]=1;
|
|
tmpdiff[j]=0;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(j==1)
|
|
{
|
|
tmp[j]=xc[i];
|
|
tmpdiff[j]=1;
|
|
}
|
|
else
|
|
{
|
|
tmp[j]=2*xc[i]*tmp[j-1]-tmp[j-2];
|
|
tmpdiff[j]=2*(tmp[j-1]+xc[i]*tmpdiff[j-1])-tmpdiff[j-2];
|
|
}
|
|
}
|
|
}
|
|
//--- check
|
|
if(dc[i]==0)
|
|
{
|
|
for(int i_=0; i_<=m-1; i_++)
|
|
cmatrix.Set(i,i_,tmp[i_]);
|
|
}
|
|
//--- check
|
|
if(dc[i]==1)
|
|
{
|
|
for(int i_=0; i_<=m-1; i_++)
|
|
cmatrix.Set(i,i_,tmpdiff[i_]);
|
|
}
|
|
cmatrix.Set(i,m,yc[i]);
|
|
}
|
|
//--- Solve constrained task
|
|
if(k>0)
|
|
{
|
|
//--- solve using regularization
|
|
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,c,rep);
|
|
}
|
|
else
|
|
{
|
|
//--- no constraints,no regularization needed
|
|
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,0,info,c,rep);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal Floater-Hormann fitting subroutine for fixed D |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::BarycentricFitWCFixedD(double &cx[],double &cy[],
|
|
double &cw[],const int n,
|
|
double &cxc[],double &cyc[],
|
|
int &dc[],const int k,const int m,
|
|
const int d,int &info,
|
|
CBarycentricInterpolant &b,
|
|
CBarycentricFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double v0=0;
|
|
double v1=0;
|
|
double mx=0;
|
|
int i=0;
|
|
int j=0;
|
|
int relcnt=0;
|
|
double xa=0;
|
|
double xb=0;
|
|
double sa=0;
|
|
double sb=0;
|
|
double decay=0;
|
|
int i_=0;
|
|
//--- create arrays
|
|
double y2[];
|
|
double w2[];
|
|
double sx[];
|
|
double sy[];
|
|
double sbf[];
|
|
double xoriginal[];
|
|
double yoriginal[];
|
|
double tmp[];
|
|
double x[];
|
|
double y[];
|
|
double w[];
|
|
double xc[];
|
|
double yc[];
|
|
//--- create matrix
|
|
CMatrixDouble fmatrix;
|
|
CMatrixDouble cmatrix;
|
|
//--- objects of classes
|
|
CLSFitReport lrep;
|
|
CBarycentricInterpolant b2;
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(w,cw);
|
|
ArrayCopy(xc,cxc);
|
|
ArrayCopy(yc,cyc);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(((n<1 || m<2) || k<0) || k>=m)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
for(i=0; i<k; i++)
|
|
{
|
|
info=0;
|
|
//--- check
|
|
if(dc[i]<0)
|
|
info=-1;
|
|
//--- check
|
|
if(dc[i]>1)
|
|
info=-1;
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
}
|
|
//--- weight decay for correct handling of task which becomes
|
|
//--- degenerate after constraints are applied
|
|
decay=10000*CMath::m_machineepsilon;
|
|
//--- Scale X,Y,XC,YC
|
|
LSFitScaleXY(x,y,w,n,xc,yc,dc,k,xa,xb,sa,sb,xoriginal,yoriginal);
|
|
//--- allocate space,initialize:
|
|
//--- * FMatrix- values of basis functions at X[]
|
|
//--- * CMatrix- values (derivatives) of basis functions at XC[]
|
|
ArrayResize(y2,n+m);
|
|
ArrayResize(w2,n+m);
|
|
fmatrix.Resize(n+m,m);
|
|
//--- check
|
|
if(k>0)
|
|
cmatrix.Resize(k,m+1);
|
|
//--- allocation
|
|
ArrayResize(y2,n+m);
|
|
ArrayResize(w2,n+m);
|
|
//--- Prepare design and constraints matrices:
|
|
//--- * fill constraints matrix
|
|
//--- * fill first N rows of design matrix with values
|
|
//--- * fill next M rows of design matrix with regularizing term
|
|
//--- * append M zeros to Y
|
|
//--- * append M elements,mean(abs(W)) each,to W
|
|
ArrayResize(sx,m);
|
|
ArrayResize(sy,m);
|
|
ArrayResize(sbf,m);
|
|
for(j=0; j<=m-1; j++)
|
|
sx[j]=(double)(2*j)/(double)(m-1)-1;
|
|
for(i=0; i<=m-1; i++)
|
|
sy[i]=1;
|
|
//--- function call
|
|
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b2);
|
|
//--- change value
|
|
mx=0;
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- function call
|
|
BarycentricCalcBasis(b2,x[i],sbf);
|
|
for(i_=0; i_<=m-1; i_++)
|
|
fmatrix.Set(i,i_,sbf[i_]);
|
|
//--- change values
|
|
y2[i]=y[i];
|
|
w2[i]=w[i];
|
|
mx=mx+MathAbs(w[i])/n;
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
for(j=0; j<=m-1; j++)
|
|
{
|
|
//--- check
|
|
if(i==j)
|
|
fmatrix.Set(n+i,j,decay);
|
|
else
|
|
fmatrix.Set(n+i,j,0);
|
|
}
|
|
//--- change values
|
|
y2[n+i]=0;
|
|
w2[n+i]=mx;
|
|
}
|
|
//--- check
|
|
if(k>0)
|
|
{
|
|
for(j=0; j<=m-1; j++)
|
|
{
|
|
for(i=0; i<=m-1; i++)
|
|
sy[i]=0;
|
|
sy[j]=1;
|
|
//--- function call
|
|
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b2);
|
|
//--- calculation
|
|
for(i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]>=0 && dc[i]<=1,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- function call
|
|
CRatInt::BarycentricDiff1(b2,xc[i],v0,v1);
|
|
//--- check
|
|
if(dc[i]==0)
|
|
cmatrix.Set(i,j,v0);
|
|
//--- check
|
|
if(dc[i]==1)
|
|
cmatrix.Set(i,j,v1);
|
|
}
|
|
}
|
|
for(i=0; i<k; i++)
|
|
cmatrix.Set(i,m,yc[i]);
|
|
}
|
|
//--- Solve constrained task
|
|
if(k>0)
|
|
{
|
|
//--- solve using regularization
|
|
LSFitLinearWC(y2,w2,fmatrix,cmatrix,n+m,m,k,info,tmp,lrep);
|
|
}
|
|
else
|
|
{
|
|
//--- no constraints,no regularization needed
|
|
LSFitLinearWC(y,w,fmatrix,cmatrix,n,m,k,info,tmp,lrep);
|
|
}
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
//--- Generate interpolant and scale it
|
|
for(i_=0; i_<=m-1; i_++)
|
|
sy[i_]=tmp[i_];
|
|
//--- function call
|
|
CRatInt::BarycentricBuildFloaterHormann(sx,sy,m,d,b);
|
|
//--- function call
|
|
CRatInt::BarycentricLinTransX(b,2/(xb-xa),-((xa+xb)/(xb-xa)));
|
|
//--- function call
|
|
CRatInt::BarycentricLinTransY(b,sb-sa,sa);
|
|
//--- Scale absolute errors obtained from LSFitLinearW.
|
|
//--- Relative error should be calculated separately
|
|
//--- (because of shifting/scaling of the task)
|
|
rep.m_taskrcond=lrep.m_taskrcond;
|
|
rep.m_rmserror=lrep.m_rmserror*(sb-sa);
|
|
rep.m_avgerror=lrep.m_avgerror*(sb-sa);
|
|
rep.m_maxerror=lrep.m_maxerror*(sb-sa);
|
|
rep.m_avgrelerror=0;
|
|
relcnt=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(yoriginal[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(CRatInt::BarycentricCalc(b,xoriginal[i])-yoriginal[i])/MathAbs(yoriginal[i]);
|
|
relcnt=relcnt+1;
|
|
}
|
|
}
|
|
//--- check
|
|
if(relcnt!=0)
|
|
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates value of four-parameter logistic (4PL) |
|
|
//| model at specified point X. 4PL model has following form: |
|
|
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - current point, X>=0: |
|
|
//| * zero X is correctly handled even for B<=0 |
|
|
//| * negative X results in exception. |
|
|
//| A, B, C, D - parameters of 4PL model: |
|
|
//| * A is unconstrained |
|
|
//| * B is unconstrained; zero or negative values |
|
|
//| are handled correctly. |
|
|
//| * C>0, non-positive value results in exception |
|
|
//| * D is unconstrained |
|
|
//| RESULT: |
|
|
//| model value at X |
|
|
//| NOTE: if B=0, denominator is assumed to be equal to 2.0 even for|
|
|
//| zero X (strictly speaking, 0^0 is undefined). |
|
|
//| NOTE: this function also throws exception if all input parameters|
|
|
//| are correct, but overflow was detected during calculations.|
|
|
//| NOTE: this function performs a lot of checks; if you need really |
|
|
//| high performance, consider evaluating model yourself, |
|
|
//| without checking for degenerate cases. |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFit::LogisticCalc4(double x,double a,double b,double c,
|
|
double d)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x),"LogisticCalc4: X is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(a),"LogisticCalc4: A is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(b),"LogisticCalc4: B is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(c),"LogisticCalc4: C is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(d),"LogisticCalc4: D is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(x>=0.0,"LogisticCalc4: X is negative"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(c>0.0,"LogisticCalc4: C is non-positive"))
|
|
return(EMPTY_VALUE);
|
|
//--- Check for degenerate cases
|
|
if(b==0.0)
|
|
{
|
|
result=0.5*(a+d);
|
|
return(result);
|
|
}
|
|
if(x==0.0)
|
|
{
|
|
if(b>0.0)
|
|
result=a;
|
|
else
|
|
result=d;
|
|
return(result);
|
|
}
|
|
//--- General case
|
|
result=d+(a-d)/(1.0+MathPow(x/c,b));
|
|
if(!CAp::Assert(MathIsValidNumber(result),"LogisticCalc4: overflow during calculations"))
|
|
return(EMPTY_VALUE);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates value of five-parameter logistic (5PL) |
|
|
//| model at specified point X. 5PL model has following form: |
|
|
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - current point, X>=0: |
|
|
//| * zero X is correctly handled even for B<=0 |
|
|
//| * negative X results in exception. |
|
|
//| A, B, C, D, G- parameters of 5PL model: |
|
|
//| * A is unconstrained |
|
|
//| * B is unconstrained; zero or negative values |
|
|
//| are handled correctly. |
|
|
//| * C>0, non-positive value results in exception |
|
|
//| * D is unconstrained |
|
|
//| * G>0, non-positive value results in exception |
|
|
//| RESULT: |
|
|
//| model value at X |
|
|
//| NOTE: if B=0, denominator is assumed to be equal to Power(2.0,G) |
|
|
//| even for zero X (strictly speaking, 0^0 is undefined). |
|
|
//| NOTE: this function also throws exception if all input parameters|
|
|
//| are correct, but overflow was detected during calculations.|
|
|
//| NOTE: this function performs a lot of checks; if you need really |
|
|
//| high performance, consider evaluating model yourself, |
|
|
//| without checking for degenerate cases. |
|
|
//+------------------------------------------------------------------+
|
|
double CLSFit::LogisticCalc5(double x,double a,double b,double c,
|
|
double d,double g)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x),"LogisticCalc5: X is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(a),"LogisticCalc5: A is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(b),"LogisticCalc5: B is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(c),"LogisticCalc5: C is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(d),"LogisticCalc5: D is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(MathIsValidNumber(g),"LogisticCalc5: G is not finite"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(x>=0.0,"LogisticCalc5: X is negative"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(c>0.0,"LogisticCalc5: C is non-positive"))
|
|
return(EMPTY_VALUE);
|
|
if(!CAp::Assert(g>0.0,"LogisticCalc5: G is non-positive"))
|
|
return(EMPTY_VALUE);
|
|
//--- Check for degenerate cases
|
|
if(b==0.0)
|
|
{
|
|
result=d+(a-d)/MathPow(2.0,g);
|
|
return(result);
|
|
}
|
|
if(x==0.0)
|
|
{
|
|
if(b>0.0)
|
|
result=a;
|
|
else
|
|
result=d;
|
|
return(result);
|
|
}
|
|
//--- General case
|
|
result=d+(a-d)/MathPow(1.0+MathPow(x/c,b),g);
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(result),"LogisticCalc5: overflow during calculations"))
|
|
return(EMPTY_VALUE);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits four-parameter logistic (4PL) model to data |
|
|
//| provided by user. 4PL model has following form: |
|
|
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
|
|
//| Here: |
|
|
//| * A, D - unconstrained (see LogisticFit4EC() for |
|
|
//| constrained 4PL) |
|
|
//| * B>=0 |
|
|
//| * C>0 |
|
|
//| IMPORTANT: output of this function is constrained in such way |
|
|
//| that B>0. Because 4PL model is symmetric with respect|
|
|
//| to B, there is no need to explore B<0. Constraining |
|
|
//| B makes algorithm easier to stabilize and debug. |
|
|
//| Users who for some reason prefer to work with |
|
|
//| negative B's should transform output themselves (swap |
|
|
//| A and D, replace B by -B). |
|
|
//| 4PL fitting is implemented as follows: |
|
|
//| * we perform small number of restarts from random locations |
|
|
//| which helps to solve problem of bad local extrema. Locations |
|
|
//| are only partially random - we use input data to determine |
|
|
//| good initial guess, but we include controlled amount of |
|
|
//| randomness. |
|
|
//| * we perform Levenberg-Marquardt fitting with very tight |
|
|
//| constraints on parameters B and C - it allows us to find good|
|
|
//| initial guess for the second stage without risk of running|
|
|
//| into "flat spot". |
|
|
//| * second Levenberg-Marquardt round is performed without |
|
|
//| excessive constraints. Results from the previous round are |
|
|
//| used as initial guess. |
|
|
//| * after fitting is done, we compare results with best values |
|
|
//| found so far, rewrite "best solution" if needed, and move to |
|
|
//| next random location. |
|
|
//| Overall algorithm is very stable and is not prone to bad local |
|
|
//| extrema. Furthermore, it automatically scales when input data |
|
|
//| have very large or very small range. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], stores X-values. |
|
|
//| MUST include only non-negative numbers (but may |
|
|
//| include zero values). Can be unsorted. |
|
|
//| Y - array[N], values to fit. |
|
|
//| N - number of points. If N is less than length of |
|
|
//| X/Y, only leading N elements are used. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B, C, D- parameters of 4PL model |
|
|
//| Rep - fitting report. This structure has many fields, |
|
|
//| but ONLY ONES LISTED BELOW ARE SET: |
|
|
//| * Rep.IterationsCount - number of iterations |
|
|
//| performed |
|
|
//| * Rep.RMSError - root-mean-square error |
|
|
//| * Rep.AvgError - average absolute error |
|
|
//| * Rep.AvgRelError - average relative error |
|
|
//| (calculated for non-zero |
|
|
//| Y-values) |
|
|
//| * Rep.MaxError - maximum absolute error |
|
|
//| * Rep.R2 - coefficient of determination,|
|
|
//| R-squared. This coefficient|
|
|
//| is calculated as |
|
|
//| R2=1-RSS/TSS (in case of |
|
|
//| nonlinear regression there|
|
|
//| are multiple ways to |
|
|
//| define R2, each of them |
|
|
//| giving different results). |
|
|
//| NOTE: for stability reasons the B parameter is restricted by |
|
|
//| [1/1000,1000] range. It prevents algorithm from making |
|
|
//| trial steps deep into the area of bad parameters. |
|
|
//| NOTE: after you obtained coefficients, you can evaluate model |
|
|
//| with LogisticCalc4() function. |
|
|
//| NOTE: if you need better control over fitting process than |
|
|
//| provided by this function, you may use LogisticFit45X(). |
|
|
//| NOTE: step is automatically scaled according to scale of |
|
|
//| parameters being fitted before we compare its length with |
|
|
//| EpsX. Thus, this function can be used to fit data with |
|
|
//| very small or very large values without changing EpsX. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit4(CRowDouble &x,CRowDouble &y,int n,
|
|
double &a,double &b,double &c,
|
|
double &d,CLSFitReport &rep)
|
|
{
|
|
double g=0;
|
|
//--- function call
|
|
LogisticFit45x(x,y,n,AL_NaN,AL_NaN,true,0.0,0.0,0,a,b,c,d,g,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits four-parameter logistic (4PL) model to data |
|
|
//| provided by user. 4PL model has following form: |
|
|
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) |
|
|
//| Here: |
|
|
//| * A, D - unconstrained (see LogisticFit4EC() for |
|
|
//| constrained 4PL) |
|
|
//| * B>=0 |
|
|
//| * C>0 |
|
|
//| IMPORTANT: output of this function is constrained in such way |
|
|
//| that B>0. Because 4PL model is symmetric with respect|
|
|
//| to B, there is no need to explore B<0. Constraining |
|
|
//| B makes algorithm easier to stabilize and debug. |
|
|
//| Users who for some reason prefer to work with |
|
|
//| negative B's should transform output themselves (swap |
|
|
//| A and D, replace B by -B). |
|
|
//| 4PL fitting is implemented as follows: |
|
|
//| * we perform small number of restarts from random locations |
|
|
//| which helps to solve problem of bad local extrema. Locations |
|
|
//| are only partially random - we use input data to determine |
|
|
//| good initial guess, but we include controlled amount of |
|
|
//| randomness. |
|
|
//| * we perform Levenberg-Marquardt fitting with very tight |
|
|
//| constraints on parameters B and C - it allows us to find good|
|
|
//| initial guess for the second stage without risk of running|
|
|
//| into "flat spot". |
|
|
//| * second Levenberg-Marquardt round is performed without |
|
|
//| excessive constraints. Results from the previous round are |
|
|
//| used as initial guess. |
|
|
//| * after fitting is done, we compare results with best values |
|
|
//| found so far, rewrite "best solution" if needed, and move to |
|
|
//| next random location. |
|
|
//| Overall algorithm is very stable and is not prone to bad local |
|
|
//| extrema. Furthermore, it automatically scales when input data |
|
|
//| have very large or very small range. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], stores X-values. |
|
|
//| MUST include only non-negative numbers (but may |
|
|
//| include zero values). Can be unsorted. |
|
|
//| Y - array[N], values to fit. |
|
|
//| N - number of points. If N is less than length of |
|
|
//| X/Y, only leading N elements are used. |
|
|
//| CnstrLeft- optional equality constraint for model value at the|
|
|
//| left boundary (at X=0). Specify NAN (Not-a-Number) |
|
|
//| if you do not need constraint on the model value |
|
|
//| at X=0. See below, section "EQUALITY CONSTRAINTS" |
|
|
//| for more information about constraints. |
|
|
//| CnstrRight- optional equality constraint for model value at |
|
|
//| X=infinity. Specify NAN (Not-a-Number) if you do |
|
|
//| not need constraint on the model value. See below,|
|
|
//| section "EQUALITY CONSTRAINTS" for more |
|
|
//| information about constraints. |
|
|
//| OUTPUT PARAMETERS:
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B, C, D- parameters of 4PL model |
|
|
//| Rep - fitting report. This structure has many fields, |
|
|
//| but ONLY ONES LISTED BELOW ARE SET: |
|
|
//| * Rep.IterationsCount - number of iterations |
|
|
//| performed |
|
|
//| * Rep.RMSError - root-mean-square error |
|
|
//| * Rep.AvgError - average absolute error |
|
|
//| * Rep.AvgRelError - average relative error |
|
|
//| (calculated for non-zero |
|
|
//| Y-values) |
|
|
//| * Rep.MaxError - maximum absolute error |
|
|
//| * Rep.R2 - coefficient of determination,|
|
|
//| R-squared. This coefficient|
|
|
//| is calculated as |
|
|
//| R2=1-RSS/TSS (in case of |
|
|
//| nonlinear regression there|
|
|
//| are multiple ways to |
|
|
//| define R2, each of them |
|
|
//| giving different results). |
|
|
//| NOTE: for stability reasons the B parameter is restricted by |
|
|
//| [1/1000,1000] range. It prevents algorithm from making |
|
|
//| trial steps deep into the area of bad parameters. |
|
|
//| NOTE: after you obtained coefficients, you can evaluate model |
|
|
//| with LogisticCalc4() function. |
|
|
//| NOTE: if you need better control over fitting process than |
|
|
//| provided by this function, you may use LogisticFit45X(). |
|
|
//| NOTE: step is automatically scaled according to scale of |
|
|
//| parameters being fitted before we compare its length with |
|
|
//| EpsX. Thus, this function can be used to fit data with |
|
|
//| very small or very large values without changing EpsX. |
|
|
//| EQUALITY CONSTRAINTS ON PARAMETERS |
|
|
//| 4PL/5PL solver supports equality constraints on model values at |
|
|
//| the left boundary (X=0) and right boundary (X=infinity). These|
|
|
//| constraints are completely optional and you can specify both of |
|
|
//| them, only one - or no constraints at all. |
|
|
//| Parameter CnstrLeft contains left constraint (or NAN for |
|
|
//| unconstrained fitting), and CnstrRight contains right one. For |
|
|
//| 4PL, left constraint ALWAYS corresponds to parameter A, and |
|
|
//| right one is ALWAYS constraint on D. That's because 4PL model |
|
|
//| is normalized in such way that B>=0. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit4ec(CRowDouble &x,CRowDouble &y,int n,
|
|
double cnstrleft,double cnstrright,
|
|
double &a,double &b,double &c,
|
|
double &d,CLSFitReport &rep)
|
|
{
|
|
double g=0;
|
|
//--- function call
|
|
LogisticFit45x(x,y,n,cnstrleft,cnstrright,true,0.0,0.0,0,a,b,c,d,g,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits five-parameter logistic (5PL) model to data |
|
|
//| provided by user. 5PL model has following form: |
|
|
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
|
|
//| Here: |
|
|
//| * A, D - unconstrained |
|
|
//| * B - unconstrained |
|
|
//| * C>0 |
|
|
//| * G>0 |
|
|
//| IMPORTANT: unlike in 4PL fitting, output of this function |
|
|
//| is NOT constrained in such way that B is guaranteed |
|
|
//| to be positive. Furthermore, unlike 4PL, 5PL model |
|
|
//| is NOT symmetric with respect to B, so you can NOT |
|
|
//| transform model to equivalent one, with B having |
|
|
//| desired sign (>0 or <0). |
|
|
//| 5PL fitting is implemented as follows: |
|
|
//| * we perform small number of restarts from random locations |
|
|
//| which helps to solve problem of bad local extrema. Locations |
|
|
//| are only partially random - we use input data to determine |
|
|
//| good initial guess, but we include controlled amount of |
|
|
//| randomness. |
|
|
//| * we perform Levenberg - Marquardt fitting with very tight |
|
|
//| constraints on parameters B and C - it allows us to find good|
|
|
//| initial guess for the second stage without risk of running|
|
|
//| into "flat spot". Parameter G is fixed at G = 1. |
|
|
//| * second Levenberg - Marquardt round is performed without |
|
|
//| excessive constraints on B and C, but with G still equal to 1|
|
|
//| Results from the previous round are used as initial guess. |
|
|
//| * third Levenberg - Marquardt round relaxes constraints on G |
|
|
//| and tries two different models - one with B > 0 and one |
|
|
//| with B < 0. |
|
|
//| * after fitting is done, we compare results with best values |
|
|
//| found so far, rewrite "best solution" if needed, and move to |
|
|
//| next random location. |
|
|
//| Overall algorithm is very stable and is not prone to bad local |
|
|
//| extrema. Furthermore, it automatically scales when input data |
|
|
//| have very large or very small range. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], stores X - values. |
|
|
//| MUST include only non - negative numbers(but may|
|
|
//| include zero values). Can be unsorted. |
|
|
//| Y - array[N], values to fit. |
|
|
//| N - number of points. If N is less than length of |
|
|
//| X / Y, only leading N elements are used. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B, C, D, G - parameters of 5PL model |
|
|
//| Rep - fitting report. This structure has many fields, |
|
|
//| but ONLY ONES LISTED BELOW ARE SET: |
|
|
//| * Rep.IterationsCount - number of iterations performed|
|
|
//| * Rep.RMSError - root - mean - square error |
|
|
//| * Rep.AvgError - average absolute error |
|
|
//| * Rep.AvgRelError - average relative error |
|
|
//| (calculated for non - zero |
|
|
//| Y - values) |
|
|
//| * Rep.MaxError - maximum absolute error |
|
|
//| * Rep.R2 - coefficient of determination, |
|
|
//| R - squared. This coefficient|
|
|
//| is calculated as |
|
|
//| R2 = 1 - RSS / TSS (in case |
|
|
//| of nonlinear regression there|
|
|
//| are multiple ways to define|
|
|
//| R2, each of them giving |
|
|
//| different results). |
|
|
//| NOTE: for better stability B parameter is restricted by |
|
|
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
|
|
//| range. It prevents algorithm from making trial steps deep |
|
|
//| into the area of bad parameters. |
|
|
//| NOTE: after you obtained coefficients, you can evaluate |
|
|
//| model with LogisticCalc5() function. |
|
|
//| NOTE: if you need better control over fitting process than |
|
|
//| provided by this function, you may use LogisticFit45X(). |
|
|
//| NOTE: step is automatically scaled according to scale of |
|
|
//| parameters being fitted before we compare its length with |
|
|
//| EpsX. Thus, this function can be used to fit data with |
|
|
//| very small or very large values without changing EpsX. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit5(CRowDouble &x,CRowDouble &y,int n,
|
|
double &a,double &b,double &c,
|
|
double &d,double &g,CLSFitReport &rep)
|
|
{
|
|
LogisticFit45x(x,y,n,AL_NaN,AL_NaN,false,0.0,0.0,0,a,b,c,d,g,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits five-parameter logistic (5PL) model to data |
|
|
//| provided by user. 5PL model has following form: |
|
|
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G) |
|
|
//| Here: |
|
|
//| * A, D - unconstrained |
|
|
//| * B - unconstrained |
|
|
//| * C>0 |
|
|
//| * G>0 |
|
|
//| IMPORTANT: unlike in 4PL fitting, output of this function |
|
|
//| is NOT constrained in such way that B is guaranteed |
|
|
//| to be positive. Furthermore, unlike 4PL, 5PL model |
|
|
//| is NOT symmetric with respect to B, so you can NOT |
|
|
//| transform model to equivalent one, with B having |
|
|
//| desired sign (>0 or <0). |
|
|
//| 5PL fitting is implemented as follows: |
|
|
//| * we perform small number of restarts from random locations |
|
|
//| which helps to solve problem of bad local extrema. Locations |
|
|
//| are only partially random - we use input data to determine |
|
|
//| good initial guess, but we include controlled amount of |
|
|
//| randomness. |
|
|
//| * we perform Levenberg - Marquardt fitting with very tight |
|
|
//| constraints on parameters B and C - it allows us to find good|
|
|
//| initial guess for the second stage without risk of running|
|
|
//| into "flat spot". Parameter G is fixed at G = 1. |
|
|
//| * second Levenberg - Marquardt round is performed without |
|
|
//| excessive constraints on B and C, but with G still equal to 1|
|
|
//| Results from the previous round are used as initial guess. |
|
|
//| * third Levenberg - Marquardt round relaxes constraints on G |
|
|
//| and tries two different models - one with B > 0 and one |
|
|
//| with B < 0. |
|
|
//| * after fitting is done, we compare results with best values |
|
|
//| found so far, rewrite "best solution" if needed, and move to |
|
|
//| next random location. |
|
|
//| Overall algorithm is very stable and is not prone to bad local |
|
|
//| extrema. Furthermore, it automatically scales when input data |
|
|
//| have very large or very small range. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], stores X - values. |
|
|
//| MUST include only non - negative numbers(but may|
|
|
//| include zero values). Can be unsorted. |
|
|
//| Y - array[N], values to fit. |
|
|
//| N - number of points. If N is less than length of |
|
|
//| X / Y, only leading N elements are used. |
|
|
//| CnstrLeft - optional equality constraint for model value at |
|
|
//| the left boundary(at X = 0). |
|
|
//| Specify NAN(Not - a - Number) if you do not |
|
|
//| need constraint on the model value at X = 0. |
|
|
//| See below, section "EQUALITY CONSTRAINTS" |
|
|
//| for more information about constraints. |
|
|
//| CnstrRight - optional equality constraint for model value at |
|
|
//| X = infinity. |
|
|
//| Specify NAN(Not - a - Number) if you do not |
|
|
//| need constraint on the model value at X = 0. |
|
|
//| See below, section "EQUALITY CONSTRAINTS" |
|
|
//| for more information about constraints. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B, C, D, G - parameters of 5PL model |
|
|
//| Rep - fitting report. This structure has many fields, |
|
|
//| but ONLY ONES LISTED BELOW ARE SET: |
|
|
//| * Rep.IterationsCount - number of iterations performed|
|
|
//| * Rep.RMSError - root - mean - square error |
|
|
//| * Rep.AvgError - average absolute error |
|
|
//| * Rep.AvgRelError - average relative error |
|
|
//| (calculated for non - zero |
|
|
//| Y - values) |
|
|
//| * Rep.MaxError - maximum absolute error |
|
|
//| * Rep.R2 - coefficient of determination, |
|
|
//| R - squared. This coefficient|
|
|
//| is calculated as |
|
|
//| R2 = 1 - RSS / TSS (in case |
|
|
//| of nonlinear regression there|
|
|
//| are multiple ways to define|
|
|
//| R2, each of them giving |
|
|
//| different results). |
|
|
//| NOTE: for better stability B parameter is restricted by |
|
|
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
|
|
//| range. It prevents algorithm from making trial steps deep |
|
|
//| into the area of bad parameters. |
|
|
//| NOTE: after you obtained coefficients, you can evaluate |
|
|
//| model with LogisticCalc5() function. |
|
|
//| NOTE: if you need better control over fitting process than |
|
|
//| provided by this function, you may use LogisticFit45X(). |
|
|
//| NOTE: step is automatically scaled according to scale of |
|
|
//| parameters being fitted before we compare its length with |
|
|
//| EpsX. Thus, this function can be used to fit data with |
|
|
//| very small or very large values without changing EpsX. |
|
|
//| EQUALITY CONSTRAINTS ON PARAMETERS |
|
|
//| 5PL solver supports equality constraints on model values at the|
|
|
//| left boundary(X = 0) and right boundary(X = infinity). These |
|
|
//| constraints are completely optional and you can specify both of |
|
|
//| them, only one - or no constraints at all. |
|
|
//| Parameter CnstrLeft contains left constraint (or NAN for |
|
|
//| unconstrained fitting), and CnstrRight contains right one. |
|
|
//| Unlike 4PL one, 5PL model is NOT symmetric with respect to change|
|
|
//| in sign of B. Thus, negative B's are possible, and left |
|
|
//| constraint may constrain parameter A(for positive B's) - or |
|
|
//| parameter D (for negative B's). Similarly changes meaning of |
|
|
//| right constraint. |
|
|
//| You do not have to decide what parameter to constrain - algorithm|
|
|
//| will automatically determine correct parameters as fitting |
|
|
//| progresses. However, question highlighted above is important when|
|
|
//| you interpret fitting results. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit5ec(CRowDouble &x,CRowDouble &y,int n,
|
|
double cnstrleft,double cnstrright,
|
|
double &a,double &b,double &c,double &d,
|
|
double &g,CLSFitReport &rep)
|
|
{
|
|
LogisticFit45x(x,y,n,cnstrleft,cnstrright,false,0.0,0.0,0,a,b,c,d,g,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is "expert" 4PL / 5PL fitting function, which can be used if|
|
|
//| you need better control over fitting process than provided by |
|
|
//| LogisticFit4() or LogisticFit5(). |
|
|
//| This function fits model of the form |
|
|
//| F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B)) (4PL model) |
|
|
//| or |
|
|
//| F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)(5PL model) |
|
|
//| Here: |
|
|
//| * A, D - unconstrained |
|
|
//| * B >= 0 for 4PL, unconstrained for 5PL |
|
|
//| * C > 0 |
|
|
//| * G > 0(if present) |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], stores X - values. |
|
|
//| MUST include only non-negative numbers(but may |
|
|
//| include zero values). Can be unsorted. |
|
|
//| Y - array[N], values to fit. |
|
|
//| N - number of points. If N is less than length of |
|
|
//| X / Y, only leading N elements are used. |
|
|
//| CnstrLeft - optional equality constraint for model value at |
|
|
//| the left boundary(at X = 0). |
|
|
//| Specify NAN (Not-a-Number) if you do not need |
|
|
//| constraint on the model value at X = 0. |
|
|
//| See below, section "EQUALITY CONSTRAINTS" |
|
|
//| for more information about constraints. |
|
|
//| CnstrRight - optional equality constraint for model value at |
|
|
//| X = infinity. |
|
|
//| Specify NAN (Not-a-Number) if you do not need |
|
|
//| constraint on the model value at X = 0. |
|
|
//| See below, section "EQUALITY CONSTRAINTS" |
|
|
//| for more information about constraints. |
|
|
//| Is4PL - whether 4PL or 5PL models are fitted |
|
|
//| LambdaV - regularization coefficient, LambdaV >= 0. Set it|
|
|
//| to zero unless you know what you are doing. |
|
|
//| EpsX - stopping condition(step size), EpsX >= 0. Zero |
|
|
//| value means that small step is automatically |
|
|
//| chosen. See notes below for more information. |
|
|
//| RsCnt - number of repeated restarts from random points.|
|
|
//| 4PL/5PL models are prone to problem of bad local|
|
|
//| extrema. Utilizing multiple random restarts |
|
|
//| allows us to improve algorithm convergence. |
|
|
//| RsCnt >= 0. Zero value means that function |
|
|
//| automatically choose small amount of restarts |
|
|
//| (recommended). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A, B, C, D - parameters of 4PL model |
|
|
//| G - parameter of 5PL model; for Is4PL = True, G = 1 |
|
|
//| is returned. |
|
|
//| Rep - fitting report. This structure has many fields, |
|
|
//| but ONLY ONES LISTED BELOW ARE SET: |
|
|
//| * Rep.IterationsCount - number of iterations performed|
|
|
//| * Rep.RMSError - root - mean - square error |
|
|
//| * Rep.AvgError - average absolute error |
|
|
//| * Rep.AvgRelError - average relative error |
|
|
//| (calculated for non - zero |
|
|
//| Y - values) |
|
|
//| * Rep.MaxError - maximum absolute error |
|
|
//| * Rep.R2 - coefficient of determination, |
|
|
//| R - squared. This coefficient|
|
|
//| is calculated as |
|
|
//| R2 = 1 - RSS / TSS (in case |
|
|
//| of nonlinear regression there|
|
|
//| are multiple ways to define|
|
|
//| R2, each of them giving |
|
|
//| different results). |
|
|
//| NOTE: for better stability B parameter is restricted by |
|
|
//| [+-1/1000, +-1000] range, and G is restricted by [1/10, 10]|
|
|
//| range. It prevents algorithm from making trial steps deep |
|
|
//| into the area of bad parameters. |
|
|
//| NOTE: after you obtained coefficients, you can evaluate |
|
|
//| model with LogisticCalc5() function. |
|
|
//| NOTE: if you need better control over fitting process than |
|
|
//| provided by this function, you may use LogisticFit45X(). |
|
|
//| NOTE: step is automatically scaled according to scale of |
|
|
//| parameters being fitted before we compare its length with |
|
|
//| EpsX. Thus, this function can be used to fit data with |
|
|
//| very small or very large values without changing EpsX. |
|
|
//| EQUALITY CONSTRAINTS ON PARAMETERS |
|
|
//| 4PL/5PL solver supports equality constraints on model values at |
|
|
//| the left boundary(X = 0) and right boundary(X = infinity). These |
|
|
//| constraints are completely optional and you can specify both of |
|
|
//| them, only one - or no constraints at all. |
|
|
//| Parameter CnstrLeft contains left constraint (or NAN for |
|
|
//| unconstrained fitting), and CnstrRight contains right one. For |
|
|
//| 4PL, left constraint ALWAYS corresponds to parameter A, and right|
|
|
//| one is ALWAYS constraint on D. That's because 4PL model is |
|
|
//| normalized in such way that B>=0. |
|
|
//| For 5PL model things are different. Unlike 4PL one, 5PL model is |
|
|
//| NOT symmetric with respect to change in sign of B. Thus, |
|
|
//| negative B's are possible, and left constraint may constrain |
|
|
//| parameter A(for positive B's) - or parameter D(for negative B's).|
|
|
//| Similarly changes meaning of right constraint. |
|
|
//| You do not have to decide what parameter to constrain - algorithm|
|
|
//| will automatically determine correct parameters as fitting |
|
|
//| progresses. However, question highlighted above is important when|
|
|
//| you interpret fitting results. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit45x(CRowDouble &X,CRowDouble &Y,int n,
|
|
double cnstrleft,double cnstrright,
|
|
bool is4pl,double lambdav,double epsx,
|
|
int rscnt,double &a,double &b,double &c,
|
|
double &d,double &g,CLSFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int outerit=0;
|
|
int nz=0;
|
|
double v=0;
|
|
CRowDouble p0;
|
|
CRowDouble p1;
|
|
CRowDouble p2;
|
|
CRowDouble bndl;
|
|
CRowDouble bndu;
|
|
CRowDouble s;
|
|
CRowDouble bndl1;
|
|
CRowDouble bndu1;
|
|
CRowDouble bndl2;
|
|
CRowDouble bndu2;
|
|
CMatrixDouble z;
|
|
CHighQualityRandState rs;
|
|
CMinLMState state;
|
|
CMinLMReport replm;
|
|
int maxits=0;
|
|
double fbest=0;
|
|
double flast=0;
|
|
double scalex=0;
|
|
double scaley=0;
|
|
CRowDouble bufx;
|
|
CRowDouble bufy;
|
|
double fposb=0;
|
|
double fnegb=0;
|
|
CRowDouble x=X;
|
|
CRowDouble y=Y;
|
|
|
|
a=0;
|
|
b=0;
|
|
c=0;
|
|
d=0;
|
|
g=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsx),"LogisticFitX: EpsX is infinite/NAN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),"LogisticFitX: LambdaV is infinite/NAN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(cnstrleft) || CInfOrNaN::IsNaN(cnstrleft),"LogisticFitX: CnstrLeft is NOT finite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(cnstrright) || CInfOrNaN::IsNaN(cnstrright),"LogisticFitX: CnstrRight is NOT finite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,"LogisticFitX: negative LambdaV"))
|
|
return;
|
|
if(!CAp::Assert(n>0,"LogisticFitX: N<=0"))
|
|
return;
|
|
if(!CAp::Assert(rscnt>=0,"LogisticFitX: RsCnt<0"))
|
|
return;
|
|
if(!CAp::Assert(epsx>=0.0,"LogisticFitX: EpsX<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n,"LogisticFitX: Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(y)>=n,"LogisticFitX: Length(Y)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),"LogisticFitX: X contains infinite/NAN values"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),"LogisticFitX: X contains infinite/NAN values"))
|
|
return;
|
|
|
|
CHighQualityRand::HQRndSeed(2211,1033044,rs);
|
|
ClearReport(rep);
|
|
if(epsx==0.0)
|
|
epsx=1.0E-10;
|
|
if(rscnt==0)
|
|
rscnt=4;
|
|
maxits=1000;
|
|
//--- Sort points by X.
|
|
//--- Determine number of zero and non-zero values.
|
|
CTSort::TagSortFastR(x,y,bufx,bufy,n);
|
|
//--- check
|
|
if(!CAp::Assert(x[0]>=0.0,"LogisticFitX: some X[] are negative"))
|
|
return;
|
|
nz=n;
|
|
for(i=0; i<n; i++)
|
|
if(x[i]>0.0)
|
|
{
|
|
nz=i;
|
|
break;
|
|
}
|
|
//--- For NZ=N (all X[] are zero) special code is used.
|
|
//--- For NZ<N we use general-purpose code.
|
|
rep.m_iterationscount=0;
|
|
if(nz==n)
|
|
{
|
|
//--- NZ=N, degenerate problem.
|
|
//--- No need to run optimizer.
|
|
v=0.0;
|
|
for(i=0; i<n; i++)
|
|
v+=y[i];
|
|
v/=n;
|
|
if(MathIsValidNumber(cnstrleft))
|
|
a=cnstrleft;
|
|
else
|
|
a=v;
|
|
b=1;
|
|
c=1;
|
|
if(MathIsValidNumber(cnstrright))
|
|
d=cnstrright;
|
|
else
|
|
d=a;
|
|
g=1;
|
|
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
|
|
return;
|
|
}
|
|
//--- Non-degenerate problem.
|
|
//--- Determine scale of data.
|
|
scalex=x[nz+(n-nz)/2];
|
|
//--- check
|
|
if(!CAp::Assert(scalex>0.0,"LogisticFitX: internal error"))
|
|
return;
|
|
v=0.0;
|
|
for(i=0; i<n; i++)
|
|
v+=y[i];
|
|
v/=n;
|
|
scaley=0.0;
|
|
for(i=0; i<n; i++)
|
|
scaley+=CMath::Sqr(y[i]-v);
|
|
scaley=MathSqrt(scaley/n);
|
|
if(scaley==0.0)
|
|
scaley=1.0;
|
|
s=vector<double>::Zeros(5);
|
|
s.Set(0,scaley);
|
|
s.Set(1,0.1);
|
|
s.Set(2,scalex);
|
|
s.Set(3,scaley);
|
|
s.Set(4,0.1);
|
|
p0=vector<double>::Zeros(5);
|
|
bndl=vector<double>::Zeros(5);
|
|
bndu=vector<double>::Zeros(5);
|
|
bndl1=vector<double>::Zeros(5);
|
|
bndu1=vector<double>::Zeros(5);
|
|
bndl2=vector<double>::Zeros(5);
|
|
bndu2=vector<double>::Zeros(5);
|
|
CMinLM::MinLMCreateVJ(5,n+5,p0,state);
|
|
CMinLM::MinLMSetScale(state,s);
|
|
CMinLM::MinLMSetCond(state,epsx,maxits);
|
|
CMinLM::MinLMSetXRep(state,true);
|
|
p1=vector<double>::Zeros(5);
|
|
p2=vector<double>::Zeros(5);
|
|
//--- Is it 4PL problem?
|
|
if(is4pl)
|
|
{
|
|
//--- Run outer iterations
|
|
a=0;
|
|
b=1;
|
|
c=1;
|
|
d=1;
|
|
g=1;
|
|
fbest=CMath::m_maxrealnumber;
|
|
for(outerit=0; outerit<rscnt; outerit++)
|
|
{
|
|
//--- Prepare initial point; use B>0
|
|
if(MathIsValidNumber(cnstrleft))
|
|
p1.Set(0,cnstrleft);
|
|
else
|
|
p1.Set(0,y[0]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p1.Set(1,0.5+CHighQualityRand::HQRndUniformR(rs));
|
|
p1.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
|
|
if(MathIsValidNumber(cnstrright))
|
|
p1.Set(3,cnstrright);
|
|
else
|
|
p1.Set(3,y[n-1]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p1.Set(4,1.0);
|
|
//--- Run optimization with tight constraints and increased regularization
|
|
if(MathIsValidNumber(cnstrleft))
|
|
{
|
|
bndl.Set(0,cnstrleft);
|
|
bndu.Set(0,cnstrleft);
|
|
}
|
|
else
|
|
{
|
|
bndl.Set(0,AL_NEGINF);
|
|
bndu.Set(0,AL_POSINF);
|
|
}
|
|
bndl.Set(1,0.5);
|
|
bndu.Set(1,2.0);
|
|
bndl.Set(2,0.5*scalex);
|
|
bndu.Set(2,2.0*scalex);
|
|
if(MathIsValidNumber(cnstrright))
|
|
{
|
|
bndl.Set(3,cnstrright);
|
|
bndu.Set(3,cnstrright);
|
|
}
|
|
else
|
|
{
|
|
bndl.Set(3,AL_NEGINF);
|
|
bndu.Set(3,AL_POSINF);
|
|
}
|
|
bndl.Set(4,1.0);
|
|
bndu.Set(4,1.0);
|
|
CMinLM::MinLMSetBC(state,bndl,bndu);
|
|
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Relax constraints, run optimization one more time
|
|
bndl.Set(1,0.1);
|
|
bndu.Set(1,10.0);
|
|
bndl.Set(2,CMath::m_machineepsilon*scalex);
|
|
bndu.Set(2,scalex/CMath::m_machineepsilon);
|
|
CMinLM::MinLMSetBC(state,bndl,bndu);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Relax constraints more, run optimization one more time
|
|
bndl.Set(1,0.01);
|
|
bndu.Set(1,100.0);
|
|
CMinLM::MinLMSetBC(state,bndl,bndu);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Relax constraints ever more, run optimization one more time
|
|
bndl.Set(1,0.001);
|
|
bndu.Set(1,1000.0);
|
|
CMinLM::MinLMSetBC(state,bndl,bndu);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Compare results with best value found so far.
|
|
if((double)(flast)<(double)(fbest))
|
|
{
|
|
a=p1[0];
|
|
b=p1[1];
|
|
c=p1[2];
|
|
d=p1[3];
|
|
g=p1[4];
|
|
fbest=flast;
|
|
}
|
|
}
|
|
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
|
|
return;
|
|
}
|
|
//--- Well.... we have 5PL fit, and we have to test two separate branches:
|
|
//--- B>0 and B<0, because of asymmetry in the curve. First, we run optimization
|
|
//--- with tight constraints two times, in order to determine better sign for B.
|
|
//--- Run outer iterations
|
|
a=0;
|
|
b=1;
|
|
c=1;
|
|
d=1;
|
|
g=1;
|
|
fbest=CMath::m_maxrealnumber;
|
|
for(outerit=0; outerit<rscnt; outerit++)
|
|
{
|
|
//--- First, we try positive B.
|
|
p1.Set(0,y[0]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p1.Set(1,0.5+CHighQualityRand::HQRndUniformR(rs));
|
|
p1.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
|
|
p1.Set(3,y[n-1]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p1.Set(4,1.0);
|
|
bndl1.Set(0,AL_NEGINF);
|
|
bndu1.Set(0,AL_POSINF);
|
|
bndl1.Set(1,0.5);
|
|
bndu1.Set(1,2.0);
|
|
bndl1.Set(2,0.5*scalex);
|
|
bndu1.Set(2,2.0*scalex);
|
|
bndl1.Set(3,AL_NEGINF);
|
|
bndu1.Set(3,AL_POSINF);
|
|
bndl1.Set(4,0.5);
|
|
bndu1.Set(4,2.0);
|
|
if(MathIsValidNumber(cnstrleft))
|
|
{
|
|
p1.Set(0,cnstrleft);
|
|
bndl1.Set(0,cnstrleft);
|
|
bndu1.Set(0,cnstrleft);
|
|
}
|
|
if(MathIsValidNumber(cnstrright))
|
|
{
|
|
p1.Set(3,cnstrright);
|
|
bndl1.Set(3,cnstrright);
|
|
bndu1.Set(3,cnstrright);
|
|
}
|
|
CMinLM::MinLMSetBC(state,bndl1,bndu1);
|
|
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p1,fposb);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Second attempt - with negative B (constraints are still tight).
|
|
p2.Set(0,y[n-1]+0.15*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p2.Set(1,-(0.5+CHighQualityRand::HQRndUniformR(rs)));
|
|
p2.Set(2,x[nz+CHighQualityRand::HQRndUniformI(rs,n-nz)]);
|
|
p2.Set(3,y[0]+0.25*scaley*(CHighQualityRand::HQRndUniformR(rs)-0.5));
|
|
p2.Set(4,1.0);
|
|
bndl2.Set(0,AL_NEGINF);
|
|
bndu2.Set(0,AL_POSINF);
|
|
bndl2.Set(1,-2.0);
|
|
bndu2.Set(1,-0.5);
|
|
bndl2.Set(2,0.5*scalex);
|
|
bndu2.Set(2,2.0*scalex);
|
|
bndl2.Set(3,AL_NEGINF);
|
|
bndu2.Set(3,AL_POSINF);
|
|
bndl2.Set(4,0.5);
|
|
bndu2.Set(4,2.0);
|
|
if(MathIsValidNumber(cnstrleft))
|
|
{
|
|
p2.Set(3,cnstrleft);
|
|
bndl2.Set(3,cnstrleft);
|
|
bndu2.Set(3,cnstrleft);
|
|
}
|
|
if(MathIsValidNumber(cnstrright))
|
|
{
|
|
p2.Set(0,cnstrright);
|
|
bndl2.Set(0,cnstrright);
|
|
bndu2.Set(0,cnstrright);
|
|
}
|
|
CMinLM::MinLMSetBC(state,bndl2,bndu2);
|
|
LogisticFitInternal(x,y,n,is4pl,100*lambdav,state,replm,p2,fnegb);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Select best version of B sign
|
|
if((double)(fposb)<(double)(fnegb))
|
|
{
|
|
//--- Prepare relaxed constraints assuming that B is positive
|
|
bndl1.Set(1,0.1);
|
|
bndu1.Set(1,10.0);
|
|
bndl1.Set(2,CMath::m_machineepsilon*scalex);
|
|
bndu1.Set(2,scalex/CMath::m_machineepsilon);
|
|
bndl1.Set(4,0.1);
|
|
bndu1.Set(4,10.0);
|
|
CMinLM::MinLMSetBC(state,bndl1,bndu1);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Prepare stronger relaxation of constraints
|
|
bndl1.Set(1,0.01);
|
|
bndu1.Set(1,100.0);
|
|
CMinLM::MinLMSetBC(state,bndl1,bndu1);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Prepare stronger relaxation of constraints
|
|
bndl1.Set(1,0.001);
|
|
bndu1.Set(1,1000.0);
|
|
CMinLM::MinLMSetBC(state,bndl1,bndu1);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p1,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Compare results with best value found so far.
|
|
if((double)(flast)<(double)(fbest))
|
|
{
|
|
a=p1[0];
|
|
b=p1[1];
|
|
c=p1[2];
|
|
d=p1[3];
|
|
g=p1[4];
|
|
fbest=flast;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Prepare relaxed constraints assuming that B is negative
|
|
bndl2.Set(1,-10.0);
|
|
bndu2.Set(1,-0.1);
|
|
bndl2.Set(2,CMath::m_machineepsilon*scalex);
|
|
bndu2.Set(2,scalex/CMath::m_machineepsilon);
|
|
bndl2.Set(4,0.1);
|
|
bndu2.Set(4,10.0);
|
|
CMinLM::MinLMSetBC(state,bndl2,bndu2);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Prepare stronger relaxation
|
|
bndl2.Set(1,-100.0);
|
|
bndu2.Set(1,-0.01);
|
|
CMinLM::MinLMSetBC(state,bndl2,bndu2);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Prepare stronger relaxation
|
|
bndl2.Set(1,-1000.0);
|
|
bndu2.Set(1,-0.001);
|
|
CMinLM::MinLMSetBC(state,bndl2,bndu2);
|
|
LogisticFitInternal(x,y,n,is4pl,lambdav,state,replm,p2,flast);
|
|
rep.m_iterationscount+=replm.m_iterationscount;
|
|
//--- Compare results with best value found so far.
|
|
if((double)(flast)<(double)(fbest))
|
|
{
|
|
a=p2[0];
|
|
b=p2[1];
|
|
c=p2[2];
|
|
d=p2[3];
|
|
g=p2[4];
|
|
fbest=flast;
|
|
}
|
|
}
|
|
}
|
|
LogisticFit45Errors(x,y,n,a,b,c,d,g,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| NOTES: |
|
|
//| 1. this algorithm is somewhat unusual because it works with |
|
|
//| parameterized function f(C,X), where X is a function argument |
|
|
//| (we have many points which are characterized by different |
|
|
//| argument values), and C is a parameter to fit. |
|
|
//| For example, if we want to do linear fit by f(c0,c1,x) = |
|
|
//| = c0*x+c1, then x will be argument, and {c0,c1} will be |
|
|
//| parameters. |
|
|
//| It is important to understand that this algorithm finds |
|
|
//| minimum in the space of function PARAMETERS (not arguments), |
|
|
//| so it needs derivatives of f() with respect to C, not X. |
|
|
//| In the example above it will need f=c0*x+c1 and |
|
|
//| {df/dc0,df/dc1} = {x,1} instead of {df/dx} = {c0}. |
|
|
//| 2. Callback functions accept C as the first parameter, and X as |
|
|
//| the second |
|
|
//| 3. If State was created with LSFitCreateFG(), algorithm needs |
|
|
//| just function and its gradient, but if State was created with |
|
|
//| LSFitCreateFGH(), algorithm will need function, gradient and |
|
|
//| Hessian. |
|
|
//| According to the said above, there ase several versions of |
|
|
//| this function, which accept different sets of callbacks. |
|
|
//| This flexibility opens way to subtle errors - you may create |
|
|
//| State with LSFitCreateFGH() (optimization using Hessian), but |
|
|
//| call function which does not accept Hessian. So when algorithm|
|
|
//| will request Hessian, there will be no callback to call. In |
|
|
//| this case exception will be thrown. |
|
|
//| Be careful to avoid such errors because there is no way to |
|
|
//| find them at compile time - you can see them at runtime only. |
|
|
//+------------------------------------------------------------------+
|
|
bool CLSFit::LSFitIteration(CLSFitState &State)
|
|
{
|
|
//--- create variables
|
|
double lx=0;
|
|
double lf=0;
|
|
double ld=0;
|
|
double rx=0;
|
|
double rf=0;
|
|
double rd=0;
|
|
int n=0;
|
|
int m=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double relcnt=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j1=0;
|
|
int info=0;
|
|
int i_=0;
|
|
int label=-1;
|
|
//--- Reverse communication preparations
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(State.m_rstate.stage>=0)
|
|
{
|
|
n=State.m_rstate.ia[0];
|
|
m=State.m_rstate.ia[1];
|
|
k=State.m_rstate.ia[2];
|
|
i=State.m_rstate.ia[3];
|
|
j=State.m_rstate.ia[4];
|
|
j1=State.m_rstate.ia[5];
|
|
info=State.m_rstate.ia[6];
|
|
lx=State.m_rstate.ra[0];
|
|
lf=State.m_rstate.ra[1];
|
|
ld=State.m_rstate.ra[2];
|
|
rx=State.m_rstate.ra[3];
|
|
rf=State.m_rstate.ra[4];
|
|
rd=State.m_rstate.ra[5];
|
|
v=State.m_rstate.ra[6];
|
|
vv=State.m_rstate.ra[7];
|
|
relcnt=State.m_rstate.ra[8];
|
|
}
|
|
else
|
|
{
|
|
n=359;
|
|
m=-58;
|
|
k=-919;
|
|
i=-909;
|
|
j=81;
|
|
j1=255;
|
|
info=74;
|
|
lx=-788;
|
|
lf=809;
|
|
ld=205;
|
|
rx=-838;
|
|
rf=939;
|
|
rd=-526;
|
|
v=763;
|
|
vv=-541;
|
|
relcnt=-698;
|
|
}
|
|
//--- select stage
|
|
switch(State.m_rstate.stage)
|
|
{
|
|
case 0:
|
|
label=0;
|
|
break;
|
|
case 1:
|
|
label=1;
|
|
break;
|
|
case 2:
|
|
label=2;
|
|
break;
|
|
case 3:
|
|
label=3;
|
|
break;
|
|
case 4:
|
|
label=4;
|
|
break;
|
|
case 5:
|
|
label=5;
|
|
break;
|
|
case 6:
|
|
label=6;
|
|
break;
|
|
case 7:
|
|
label=7;
|
|
break;
|
|
case 8:
|
|
label=8;
|
|
break;
|
|
case 9:
|
|
label=9;
|
|
break;
|
|
case 10:
|
|
label=10;
|
|
break;
|
|
case 11:
|
|
label=11;
|
|
break;
|
|
case 12:
|
|
label=12;
|
|
break;
|
|
case 13:
|
|
label=13;
|
|
break;
|
|
default:
|
|
//--- Routine body
|
|
//--- Init
|
|
if(State.m_wkind==1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(State.m_npoints==State.m_nweights,__FUNCTION__+": number of points is not equal to the number of weights"))
|
|
return(false);
|
|
}
|
|
State.m_repvaridx=-1;
|
|
n=State.m_npoints;
|
|
m=State.m_m;
|
|
k=State.m_k;
|
|
State.m_tmpct.Resize(State.m_nec+State.m_nic);
|
|
State.m_tmpct.Fill(0,0,State.m_nec);
|
|
State.m_tmpct.Fill(-1,State.m_nec,State.m_nic);
|
|
CMinLM::MinLMSetCond(State.m_optstate,State.m_epsx,State.m_maxits);
|
|
CMinLM::MinLMSetStpMax(State.m_optstate,State.m_stpmax);
|
|
CMinLM::MinLMSetXRep(State.m_optstate,State.m_xrep);
|
|
CMinLM::MinLMSetScale(State.m_optstate,State.m_s);
|
|
CMinLM::MinLMSetBC(State.m_optstate,State.m_bndl,State.m_bndu);
|
|
CMinLM::MinLMSetLC(State.m_optstate,State.m_cleic,State.m_tmpct,State.m_nec+State.m_nic);
|
|
//--- Check that user-supplied gradient is correct
|
|
LSFitClearRequestFields(State);
|
|
if(!(State.m_teststep>0.0 && State.m_optalgo==1))
|
|
{
|
|
label=14;
|
|
break;
|
|
}
|
|
State.m_c=State.m_c0;
|
|
for(i=0; i<k; i++)
|
|
{
|
|
if(MathIsValidNumber(State.m_bndl[i]))
|
|
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
|
|
if(MathIsValidNumber(State.m_bndu[i]))
|
|
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
|
|
}
|
|
State.m_needfg=true;
|
|
i=0;
|
|
label=16;
|
|
break;
|
|
}
|
|
//--- main loop
|
|
while(label>=0)
|
|
switch(label)
|
|
{
|
|
case 16:
|
|
if(i>k-1)
|
|
{
|
|
label=18;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(State.m_bndl[i]<=State.m_c[i] && State.m_c[i]<=State.m_bndu[i],__FUNCTION__+": internal error(State.C is out of bounds)"))
|
|
return(false);
|
|
v=State.m_c[i];
|
|
j=0;
|
|
case 19:
|
|
if(j>n-1)
|
|
{
|
|
label=21;
|
|
break;
|
|
}
|
|
State.m_x=State.m_taskx[j]+0;
|
|
State.m_c.Set(i,v-State.m_teststep*State.m_s[i]);
|
|
if(MathIsValidNumber(State.m_bndl[i]))
|
|
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
|
|
lx=State.m_c[i];
|
|
State.m_rstate.stage=0;
|
|
label=-1;
|
|
break;
|
|
case 0:
|
|
lf=State.m_f;
|
|
ld=State.m_g[i];
|
|
State.m_c.Set(i,v+State.m_teststep*State.m_s[i]);
|
|
if(MathIsValidNumber(State.m_bndu[i]))
|
|
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
|
|
rx=State.m_c[i];
|
|
State.m_rstate.stage=1;
|
|
label=-1;
|
|
break;
|
|
case 1:
|
|
rf=State.m_f;
|
|
rd=State.m_g[i];
|
|
State.m_c.Set(i,(lx+rx)/2.0);
|
|
if(MathIsValidNumber(State.m_bndl[i]))
|
|
State.m_c.Set(i,MathMax(State.m_c[i],State.m_bndl[i]));
|
|
if(MathIsValidNumber(State.m_bndu[i]))
|
|
State.m_c.Set(i,MathMin(State.m_c[i],State.m_bndu[i]));
|
|
State.m_rstate.stage=2;
|
|
label=-1;
|
|
break;
|
|
case 2:
|
|
State.m_c.Set(i,v);
|
|
if(!COptServ::DerivativeCheck(lf,ld,rf,rd,State.m_f,State.m_g[i],rx-lx))
|
|
{
|
|
State.m_repvaridx=i;
|
|
State.m_repterminationtype=-7;
|
|
return(false);
|
|
}
|
|
j++;
|
|
label=19;
|
|
break;
|
|
case 21:
|
|
i++;
|
|
label=16;
|
|
break;
|
|
case 18:
|
|
State.m_needfg=false;
|
|
case 14:
|
|
//--- Fill WCur by weights:
|
|
//--- * for WKind=0 unit weights are chosen
|
|
//--- * for WKind=1 we use user-supplied weights stored in State.TaskW
|
|
if(State.m_wkind==1)
|
|
State.m_wcur=State.m_taskw;
|
|
else
|
|
State.m_wcur=vector<double>::Ones(n);
|
|
//--- Optimize
|
|
case 22:
|
|
if(!CMinLM::MinLMIteration(State.m_optstate))
|
|
{
|
|
label=23;
|
|
break;
|
|
}
|
|
if(!State.m_optstate.m_needfi)
|
|
{
|
|
label=24;
|
|
break;
|
|
}
|
|
//--- calculate f[] = wi*(f(xi,c)-yi)
|
|
i=0;
|
|
case 26:
|
|
if(i>n-1)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=3;
|
|
label=-1;
|
|
break;
|
|
case 3:
|
|
State.m_needf=false;
|
|
vv=State.m_wcur[i];
|
|
State.m_optstate.m_fi.Set(i,vv*(State.m_f-State.m_tasky[i]));
|
|
i++;
|
|
label=26;
|
|
break;
|
|
case 28:
|
|
label=22;
|
|
break;
|
|
case 24:
|
|
if(!State.m_optstate.m_needf)
|
|
{
|
|
label=29;
|
|
break;
|
|
}
|
|
//--- calculate F = sum (wi*(f(xi,c)-yi))^2
|
|
State.m_optstate.m_f=0;
|
|
i=0;
|
|
case 31:
|
|
if(i>n-1)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=4;
|
|
label=-1;
|
|
break;
|
|
case 4:
|
|
State.m_needf=false;
|
|
vv=State.m_wcur[i];
|
|
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
|
|
i++;
|
|
label=31;
|
|
break;
|
|
case 33:
|
|
label=22;
|
|
break;
|
|
case 29:
|
|
if(!State.m_optstate.m_needfg)
|
|
{
|
|
label=34;
|
|
break;
|
|
}
|
|
//--- calculate F/gradF
|
|
State.m_optstate.m_f=0;
|
|
State.m_optstate.m_g.Fill(0);
|
|
i=0;
|
|
case 36:
|
|
if(i>n-1)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=5;
|
|
label=-1;
|
|
break;
|
|
case 5:
|
|
State.m_needfg=false;
|
|
vv=State.m_wcur[i];
|
|
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
|
|
v=CMath::Sqr(vv)*2*(State.m_f-State.m_tasky[i]);
|
|
State.m_optstate.m_g+=State.m_g.ToVector()*v;
|
|
i++;
|
|
label=36;
|
|
break;
|
|
case 38:
|
|
label=22;
|
|
break;
|
|
case 34:
|
|
if(!State.m_optstate.m_needfij)
|
|
{
|
|
label=39;
|
|
break;
|
|
}
|
|
//--- calculate Fi/jac(Fi)
|
|
i=0;
|
|
case 41:
|
|
if(i>n-1)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needfg=true;
|
|
State.m_rstate.stage=6;
|
|
label=-1;
|
|
break;
|
|
case 6:
|
|
State.m_needfg=false;
|
|
vv=State.m_wcur[i];
|
|
State.m_optstate.m_fi.Set(i,vv*(State.m_f-State.m_tasky[i]));
|
|
State.m_optstate.m_j.Row(i,State.m_g.ToVector()*vv);
|
|
i++;
|
|
label=41;
|
|
break;
|
|
case 43:
|
|
label=22;
|
|
break;
|
|
case 39:
|
|
if(!State.m_optstate.m_needfgh)
|
|
{
|
|
label=44;
|
|
break;
|
|
}
|
|
//--- calculate F/grad(F)/hess(F)
|
|
State.m_optstate.m_f=0;
|
|
State.m_optstate.m_g.Fill(0);
|
|
State.m_optstate.m_h.Fill(0,k,k);
|
|
i=0;
|
|
case 46:
|
|
if(i>n-1)
|
|
{
|
|
label=22;
|
|
break;
|
|
}
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needfgh=true;
|
|
State.m_rstate.stage=7;
|
|
label=-1;
|
|
break;
|
|
case 7:
|
|
State.m_needfgh=false;
|
|
vv=State.m_wcur[i];
|
|
State.m_optstate.m_f+=CMath::Sqr(vv*(State.m_f-State.m_tasky[i]));
|
|
v=CMath::Sqr(vv)*2*(State.m_f-State.m_tasky[i]);
|
|
State.m_optstate.m_g+=State.m_g.ToVector()*v;
|
|
for(j=0; j<k; j++)
|
|
{
|
|
v=2*CMath::Sqr(vv)*State.m_g[j];
|
|
State.m_optstate.m_h.Row(j,State.m_optstate.m_h[j]+State.m_g*v);
|
|
v=2*CMath::Sqr(vv)*(State.m_f-State.m_tasky[i]);
|
|
State.m_optstate.m_h.Row(j,State.m_optstate.m_h[j]+State.m_h[j]*v);
|
|
}
|
|
i++;
|
|
label=46;
|
|
break;
|
|
case 48:
|
|
label=22;
|
|
break;
|
|
case 44:
|
|
if(!State.m_optstate.m_xupdated)
|
|
{
|
|
label=49;
|
|
break;
|
|
}
|
|
//--- Report new iteration
|
|
State.m_c=State.m_optstate.m_x;
|
|
State.m_f=State.m_optstate.m_f;
|
|
LSFitClearRequestFields(State);
|
|
State.m_xupdated=true;
|
|
State.m_rstate.stage=8;
|
|
label=-1;
|
|
break;
|
|
case 8:
|
|
State.m_xupdated=false;
|
|
label=22;
|
|
break;
|
|
case 49:
|
|
label=22;
|
|
break;
|
|
case 23:
|
|
//--- Extract results
|
|
//--- NOTE: reverse communication protocol used by this unit does NOT
|
|
//--- allow us to reallocate State.C[] array. Thus, we extract
|
|
//--- results to the temporary variable in order to avoid possible
|
|
//--- reallocation.
|
|
CMinLM::MinLMResults(State.m_optstate,State.m_c1,State.m_optrep);
|
|
State.m_repterminationtype=State.m_optrep.m_terminationtype;
|
|
State.m_repiterationscount=State.m_optrep.m_iterationscount;
|
|
//--- calculate errors
|
|
if(State.m_repterminationtype<=0)
|
|
{
|
|
label=51;
|
|
break;
|
|
}
|
|
//--- Calculate RMS/Avg/Max/... errors
|
|
State.m_reprmserror=0;
|
|
State.m_repwrmserror=0;
|
|
State.m_repavgerror=0;
|
|
State.m_repavgrelerror=0;
|
|
State.m_repmaxerror=0;
|
|
relcnt=0;
|
|
i=0;
|
|
case 53:
|
|
if(i>n-1)
|
|
{
|
|
label=55;
|
|
break;
|
|
}
|
|
State.m_c=State.m_c1;
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
LSFitClearRequestFields(State);
|
|
State.m_needf=true;
|
|
State.m_rstate.stage=9;
|
|
label=-1;
|
|
break;
|
|
case 9:
|
|
State.m_needf=false;
|
|
v=State.m_f;
|
|
vv=State.m_wcur[i];
|
|
State.m_reprmserror+=CMath::Sqr(v-State.m_tasky[i]);
|
|
State.m_repwrmserror+=CMath::Sqr(vv*(v-State.m_tasky[i]));
|
|
State.m_repavgerror+=MathAbs(v-State.m_tasky[i]);
|
|
if(State.m_tasky[i]!=0.0)
|
|
{
|
|
State.m_repavgrelerror+=MathAbs(v-State.m_tasky[i])/MathAbs(State.m_tasky[i]);
|
|
relcnt++;
|
|
}
|
|
State.m_repmaxerror=MathMax(State.m_repmaxerror,MathAbs(v-State.m_tasky[i]));
|
|
i++;
|
|
label=53;
|
|
break;
|
|
case 55:
|
|
State.m_reprmserror=MathSqrt(State.m_reprmserror/n);
|
|
State.m_repwrmserror=MathSqrt(State.m_repwrmserror/n);
|
|
State.m_repavgerror=State.m_repavgerror/n;
|
|
if(relcnt!=0.0)
|
|
State.m_repavgrelerror=State.m_repavgrelerror/relcnt;
|
|
//--- Calculate covariance matrix
|
|
CApServ::RMatrixSetLengthAtLeast(State.m_tmpjac,n,k);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmpf,n);
|
|
CApServ::RVectorSetLengthAtLeast(State.m_tmp,k);
|
|
if(State.m_diffstep<=0.0)
|
|
{
|
|
label=56;
|
|
break;
|
|
}
|
|
//--- Compute Jacobian by means of numerical differentiation
|
|
LSFitClearRequestFields(State);
|
|
State.m_needf=true;
|
|
i=0;
|
|
case 58:
|
|
if(i>n-1)
|
|
{
|
|
label=60;
|
|
break;
|
|
}
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
State.m_rstate.stage=10;
|
|
label=-1;
|
|
break;
|
|
case 10:
|
|
State.m_tmpf.Set(i,State.m_f);
|
|
j=0;
|
|
case 61:
|
|
if(j>k-1)
|
|
{
|
|
label=63;
|
|
break;
|
|
}
|
|
v=State.m_c[j];
|
|
lx=v-State.m_diffstep*State.m_s[j];
|
|
State.m_c.Set(j,lx);
|
|
if(MathIsValidNumber(State.m_bndl[j]))
|
|
State.m_c.Set(j,MathMax(State.m_c[j],State.m_bndl[j]));
|
|
State.m_rstate.stage=11;
|
|
label=-1;
|
|
break;
|
|
case 11:
|
|
lf=State.m_f;
|
|
rx=v+State.m_diffstep*State.m_s[j];
|
|
State.m_c.Set(j,rx);
|
|
if(MathIsValidNumber(State.m_bndu[j]))
|
|
State.m_c.Set(j,MathMin(State.m_c[j],State.m_bndu[j]));
|
|
State.m_rstate.stage=12;
|
|
label=-1;
|
|
break;
|
|
case 12:
|
|
rf=State.m_f;
|
|
State.m_c.Set(j,v);
|
|
if(rx!=lx)
|
|
State.m_tmpjac.Set(i,j,(rf-lf)/(rx-lx));
|
|
else
|
|
State.m_tmpjac.Set(i,j,0);
|
|
j++;
|
|
label=61;
|
|
break;
|
|
case 63:
|
|
i++;
|
|
label=58;
|
|
break;
|
|
case 60:
|
|
State.m_needf=false;
|
|
label=57;
|
|
break;
|
|
case 56:
|
|
//--- Jacobian is calculated with user-provided analytic gradient
|
|
LSFitClearRequestFields(State);
|
|
State.m_needfg=true;
|
|
i=0;
|
|
case 64:
|
|
if(i>n-1)
|
|
{
|
|
label=66;
|
|
break;
|
|
}
|
|
State.m_x=State.m_taskx[i]+0;
|
|
State.m_pointindex=i;
|
|
State.m_rstate.stage=13;
|
|
label=-1;
|
|
break;
|
|
case 13:
|
|
State.m_tmpf.Set(i,State.m_f);
|
|
State.m_tmpjac.Row(i,State.m_g);
|
|
i++;
|
|
label=64;
|
|
break;
|
|
case 66:
|
|
State.m_needfg=false;
|
|
case 57:
|
|
State.m_tmp.Fill(0.0);
|
|
EstimateErrors(State.m_tmpjac,State.m_tmpf,State.m_tasky,State.m_wcur,State.m_tmp,State.m_s,n,k,State.m_rep,State.m_tmpjacw,0);
|
|
case 51:
|
|
return(false);
|
|
}
|
|
//--- Saving State
|
|
State.m_rstate.ia.Set(0,n);
|
|
State.m_rstate.ia.Set(1,m);
|
|
State.m_rstate.ia.Set(2,k);
|
|
State.m_rstate.ia.Set(3,i);
|
|
State.m_rstate.ia.Set(4,j);
|
|
State.m_rstate.ia.Set(5,j1);
|
|
State.m_rstate.ia.Set(6,info);
|
|
State.m_rstate.ra.Set(0,lx);
|
|
State.m_rstate.ra.Set(1,lf);
|
|
State.m_rstate.ra.Set(2,ld);
|
|
State.m_rstate.ra.Set(3,rx);
|
|
State.m_rstate.ra.Set(4,rf);
|
|
State.m_rstate.ra.Set(5,rd);
|
|
State.m_rstate.ra.Set(6,v);
|
|
State.m_rstate.ra.Set(7,vv);
|
|
State.m_rstate.ra.Set(8,relcnt);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This internal function estimates covariance matrix and other |
|
|
//| error-related information for linear/nonlinear least squares |
|
|
//| model. |
|
|
//| It has a bit awkward interface, but it can be used for both |
|
|
//| linear and nonlinear problems. |
|
|
//| INPUT PARAMETERS: |
|
|
//| F1 - array[0..N-1,0..K-1]: |
|
|
//| * for linear problems - matrix of function values |
|
|
//| * for nonlinear problems - Jacobian matrix |
|
|
//| F0 - array[0..N-1]: |
|
|
//| * for linear problems - must be filled with zeros |
|
|
//| * for nonlinear problems - must store values of |
|
|
//| function being fitted |
|
|
//| Y - array[0..N-1]: |
|
|
//| * for linear and nonlinear problems - must store |
|
|
//| target values |
|
|
//| W - weights, array[0..N-1]: |
|
|
//| * for linear and nonlinear problems - weights |
|
|
//| X - array[0..K-1]: |
|
|
//| * for linear and nonlinear problems - current |
|
|
//| solution |
|
|
//| S - array[0..K-1]: |
|
|
//| * its components should be strictly positive |
|
|
//| * squared inverse of this diagonal matrix is used |
|
|
//| as damping factor for covariance matrix (linear |
|
|
//| and nonlinear problems) |
|
|
//| * for nonlinear problems, when scale of the |
|
|
//| variables is usually explicitly given by user, |
|
|
//| you may use scale vector for this parameter |
|
|
//| * for linear problems you may set this parameter to|
|
|
//| S=sqrt(1/diag(F'*F)) |
|
|
//| * this parameter is automatically rescaled by this |
|
|
//| function, only relative magnitudes of its |
|
|
//| components (with respect to each other) matter. |
|
|
//| N - number of points, N>0. |
|
|
//| K - number of dimensions |
|
|
//| Rep - structure which is used to store results |
|
|
//| Z - additional matrix which, depending on ZKind, may |
|
|
//| contain some information used to accelerate |
|
|
//| calculations - or just can be temporary buffer: |
|
|
//| * for ZKind=0 Z contains no information, just |
|
|
//| temporary buffer which can be |
|
|
//| resized and used as needed |
|
|
//| * for ZKind=1 Z contains triangular matrix from|
|
|
//| QR decomposition of W*F1. This |
|
|
//| matrix can be used to speedup |
|
|
//| calculation of covariance matrix.|
|
|
//| It should not be changed by |
|
|
//| algorithm. |
|
|
//| ZKind - contents of Z |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| * Rep.CovPar covariance matrix for parameters, array[K,K].|
|
|
//| * Rep.ErrPar errors in parameters, array[K], |
|
|
//| errpar = sqrt(diag(CovPar)) |
|
|
//| * Rep.ErrCurve vector of fit errors - standard deviations of|
|
|
//| empirical best-fit curve from "ideal" |
|
|
//| best-fit curve built with infinite number |
|
|
//| of samples, array[N]. |
|
|
//| errcurve = sqrt(diag(J*CovPar*J')), |
|
|
//| where J is Jacobian matrix. |
|
|
//| * Rep.Noise vector of per-point estimates of noise, |
|
|
//| array[N] |
|
|
//| * Rep.R2 coefficient of determination (non-weighted) |
|
|
//| Other fields of Rep are not changed. |
|
|
//| IMPORTANT: errors in parameters are calculated without taking |
|
|
//| into account boundary/linear constraints! Presence of |
|
|
//| constraints changes distribution of errors, but there |
|
|
//| is no easy way to account for constraints when you |
|
|
//| calculate covariance matrix. |
|
|
//| NOTE: noise in the data is estimated as follows: |
|
|
//| * for fitting without user-supplied weights all points |
|
|
//| are assumed to have same level of noise, which is |
|
|
//| estimated from the data |
|
|
//| * for fitting with user-supplied weights we assume that |
|
|
//| noise level in I-th point is inversely proportional to |
|
|
//| Ith weight. Coefficient of proportionality is estimated|
|
|
//| from the data. |
|
|
//| NOTE: we apply small amount of regularization when we invert |
|
|
//| squared Jacobian and calculate covariance matrix. It |
|
|
//| guarantees that algorithm won't divide by zero during |
|
|
//| inversion, but skews error estimates a bit (fractional |
|
|
//| error is about 10^-9). |
|
|
//| However, we believe that this difference is insignificant for all|
|
|
//| practical purposes except for the situation when you want to |
|
|
//| compare ALGLIB results with "reference" implementation up to the|
|
|
//| last significant digit. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::EstimateErrors(CMatrixDouble &f1,CRowDouble &f0,CRowDouble &y,
|
|
CRowDouble &w,CRowDouble &x,CRowDouble &S,
|
|
int n,int k,CLSFitReport &rep,
|
|
CMatrixDouble &z,int zkind)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j1=0;
|
|
double v=0;
|
|
double noisec=0;
|
|
int info=0;
|
|
CMatInvReport invrep;
|
|
int nzcnt=0;
|
|
double avg=0;
|
|
double rss=0;
|
|
double tss=0;
|
|
double sz=0;
|
|
double ss=0;
|
|
int i_=0;
|
|
CRowDouble s=S;
|
|
//--- Compute NZCnt - count of non-zero weights
|
|
//--- Compute R2
|
|
nzcnt=0;
|
|
avg=0.0;
|
|
for(i=0; i<n; i++)
|
|
if(w[i]!=0.0)
|
|
{
|
|
avg+=y[i];
|
|
nzcnt++;
|
|
}
|
|
//--- Compute R2
|
|
if(nzcnt>0)
|
|
{
|
|
avg/=nzcnt;
|
|
rss=0.0;
|
|
tss=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(w[i]!=0.0)
|
|
{
|
|
v=CAblasF::RDotVR(k,x,f1,i);
|
|
v+=f0[i];
|
|
rss+=CMath::Sqr(v-y[i]);
|
|
tss+=CMath::Sqr(y[i]-avg);
|
|
}
|
|
}
|
|
if(tss!=0.0)
|
|
rep.m_r2=MathMax(1.0-rss/tss,0.0);
|
|
else
|
|
rep.m_r2=1.0;
|
|
}
|
|
else
|
|
rep.m_r2=0;
|
|
//--- Compute estimate of proportionality between noise in the data and weights:
|
|
//--- NoiseC = mean(per-point-noise*per-point-weight)
|
|
//--- Noise level (standard deviation) at each point is equal to NoiseC/W[I].
|
|
//
|
|
if(nzcnt>k)
|
|
{
|
|
noisec=0.0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(w[i]!=0.0)
|
|
{
|
|
v=CAblasF::RDotVR(k,x,f1,i)+f0[i];
|
|
noisec+=CMath::Sqr((v-y[i])*w[i]);
|
|
}
|
|
}
|
|
noisec=MathSqrt(noisec/(nzcnt-k));
|
|
}
|
|
else
|
|
noisec=0.0;
|
|
//--- Two branches on noise level:
|
|
//--- * NoiseC>0 normal situation
|
|
//--- * NoiseC=0 degenerate case CovPar is filled by zeros
|
|
CApServ::RMatrixSetLengthAtLeast(rep.m_covpar,k,k);
|
|
if(noisec>0.0)
|
|
{
|
|
//--- Normal situation: non-zero noise level
|
|
if(!CAp::Assert(zkind==0 || zkind==1,__FUNCTION__+": internal error in EstimateErrors() function"))
|
|
return;
|
|
if(zkind==0)
|
|
{
|
|
//--- Z contains no additional information which can be used to speed up
|
|
//--- calculations. We have to calculate covariance matrix on our own:
|
|
//--- * Compute scaled Jacobian N*J, where N[i,i]=WCur[I]/NoiseC, store in Z
|
|
//--- * Compute Z'*Z, store in CovPar
|
|
//--- * Apply moderate regularization to CovPar and compute matrix inverse.
|
|
//--- In case inverse failed, increase regularization parameter and try
|
|
//--- again.
|
|
CApServ::RMatrixSetLengthAtLeast(z,n,k);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=w[i]/noisec;
|
|
z.Row(i,f1[i]*v);
|
|
}
|
|
//--- Convert S to automatically scaled damped matrix:
|
|
//--- * calculate SZ - sum of diagonal elements of Z'*Z
|
|
//--- * calculate SS - sum of diagonal elements of S^(-2)
|
|
//--- * overwrite S by (SZ/SS)*S^(-2)
|
|
//--- * now S has approximately same magnitude as giagonal of Z'*Z
|
|
sz=0;
|
|
for(i=0; i<n; i++)
|
|
sz+=CAblasF::RDotRR(k,z,i,z,i);
|
|
if(sz==0.0)
|
|
sz=1;
|
|
ss=0;
|
|
for(j=0; j<k; j++)
|
|
ss+=1/CMath::Sqr(s[j]);
|
|
for(j=0; j<k; j++)
|
|
s.Set(j,sz/ss/CMath::Sqr(s[j]));
|
|
//--- Calculate damped inverse inv(Z'*Z+S).
|
|
//--- We increase damping factor V until Z'*Z become well-conditioned.
|
|
v=1.0E3*CMath::m_machineepsilon;
|
|
do
|
|
{
|
|
CAblas::RMatrixSyrk(k,n,1.0,z,0,0,2,0.0,rep.m_covpar,0,0,true);
|
|
for(i=0; i<k; i++)
|
|
rep.m_covpar.Add(i,i,v*s[i]);
|
|
CMatInv::SPDMatrixInverse(rep.m_covpar,k,true,info,invrep);
|
|
v=10*v;
|
|
}
|
|
while(info<=0);
|
|
rep.m_covpar+=(rep.m_covpar.TriU(1)+0).Transpose();
|
|
}
|
|
if(zkind==1)
|
|
{
|
|
//--- We can reuse additional information:
|
|
//--- * Z contains R matrix from QR decomposition of W*F1
|
|
//--- * After multiplication by 1/NoiseC we get Z_mod = N*F1, where diag(N)=w[i]/NoiseC
|
|
//--- * Such triangular Z_mod is a Cholesky factor from decomposition of J'*N'*N*J.
|
|
//--- Thus, we can calculate covariance matrix as inverse of the matrix given by
|
|
//--- its Cholesky decomposition. It allow us to avoid time-consuming calculation
|
|
//--- of J'*N'*N*J in CovPar - complexity is reduced from O(N*K^2) to O(K^3), which
|
|
//--- is quite good because K is usually orders of magnitude smaller than N.
|
|
//--- First, convert S to automatically scaled damped matrix:
|
|
//--- * calculate SZ - sum of magnitudes of diagonal elements of Z/NoiseC
|
|
//--- * calculate SS - sum of diagonal elements of S^(-1)
|
|
//--- * overwrite S by (SZ/SS)*S^(-1)
|
|
//--- * now S has approximately same magnitude as giagonal of Z'*Z
|
|
sz=MathAbs(z.Diag()/noisec).Sum();
|
|
if(sz==0.0)
|
|
sz=1;
|
|
ss=0;
|
|
for(j=0; j<k; j++)
|
|
ss+=1/s[j];
|
|
for(j=0; j<k; j++)
|
|
s.Set(j,sz/ss/s[j]);
|
|
//--- Calculate damped inverse of inv((Z+v*S)'*(Z+v*S))
|
|
//--- We increase damping factor V until matrix become well-conditioned.
|
|
v=1.0E3*CMath::m_machineepsilon;
|
|
do
|
|
{
|
|
for(i=0; i<k; i++)
|
|
{
|
|
rep.m_covpar.Row(i,z[i]/noisec);
|
|
rep.m_covpar.Add(i,i,v*s[i]);
|
|
}
|
|
CMatInv::SPDMatrixCholeskyInverse(rep.m_covpar,k,true,info,invrep);
|
|
v=10*v;
|
|
}
|
|
while(info<=0);
|
|
rep.m_covpar+=(rep.m_covpar.TriU(1)+0).Transpose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Degenerate situation: zero noise level, covariance matrix is zero.
|
|
rep.m_covpar.Fill(0);
|
|
}
|
|
//--- Estimate erorrs in parameters, curve and per-point noise
|
|
CApServ::RVectorSetLengthAtLeast(rep.m_errpar,k);
|
|
CApServ::RVectorSetLengthAtLeast(rep.m_errcurve,n);
|
|
CApServ::RVectorSetLengthAtLeast(rep.m_noise,n);
|
|
rep.m_errpar=MathSqrt(rep.m_covpar.Diag()+0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- ErrCurve[I] is sqrt(P[i,i]) where P=J*CovPar*J'
|
|
v=0.0;
|
|
for(j=0; j<k; j++)
|
|
for(j1=0; j1<k; j1++)
|
|
v+=f1.Get(i,j)*rep.m_covpar.Get(j,j1)*f1.Get(i,j1);
|
|
rep.m_errcurve.Set(i,MathSqrt(v));
|
|
//--- Noise[i] is filled using weights and current estimate of noise level
|
|
if(w[i]!=0.0)
|
|
rep.m_noise.Set(i,noisec/w[i]);
|
|
else
|
|
rep.m_noise.Set(i,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal 4PL/5PL fitting function. |
|
|
//| Accepts X, Y and already initialized and prepared MinLMState |
|
|
//| structure. On input P1 contains initial guess, on output it |
|
|
//| contains solution. FLast stores function value at P1. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFitInternal(CRowDouble &x,CRowDouble &y,int n,
|
|
bool is4pl,double lambdav,
|
|
CMinLMState &state,
|
|
CMinLMReport &replm,
|
|
CRowDouble &p1,double &flast)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double ta=0;
|
|
double tb=0;
|
|
double tc=0;
|
|
double td=0;
|
|
double tg=0;
|
|
double vp0=0;
|
|
double vp1=0;
|
|
//--- initialization
|
|
flast=0;
|
|
//--- function call
|
|
CMinLM::MinLMRestartFrom(state,p1);
|
|
while(CMinLM::MinLMIteration(state))
|
|
{
|
|
ta=state.m_x[0];
|
|
tb=state.m_x[1];
|
|
tc=state.m_x[2];
|
|
td=state.m_x[3];
|
|
tg=state.m_x[4];
|
|
if(state.m_xupdated)
|
|
{
|
|
//--- Save best function value obtained so far.
|
|
flast=state.m_f;
|
|
continue;
|
|
}
|
|
if(state.m_needfi || state.m_needfij)
|
|
{
|
|
//--- Function vector and Jacobian
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(!CAp::Assert(x[i]>=0.0,"LogisticFitInternal: integrity error"))
|
|
return;
|
|
//--- Handle zero X
|
|
if(x[i]==0.0)
|
|
{
|
|
if(tb>=0.0)
|
|
{
|
|
//--- Positive or zero TB, limit X^TB subject to X->+0 is equal to zero.
|
|
state.m_fi.Set(i,ta-y[i]);
|
|
if(state.m_needfij)
|
|
{
|
|
state.m_j.Row(i,vector<double>::Zeros(5));
|
|
state.m_j.Set(i,0,1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Negative TB, limit X^TB subject to X->+0 is equal to +INF.
|
|
state.m_fi.Set(i,td-y[i]);
|
|
if(state.m_needfij)
|
|
state.m_j.Row(i,vector<double>::Zeros(5));
|
|
}
|
|
continue;
|
|
}
|
|
//--- Positive X.
|
|
//--- Prepare VP0/VP1, it may become infinite or nearly overflow in some rare cases,
|
|
//--- handle these cases
|
|
vp0=MathPow(x[i]/tc,tb);
|
|
if(is4pl)
|
|
vp1=1+vp0;
|
|
else
|
|
vp1=MathPow(1+vp0,tg);
|
|
if((!MathIsValidNumber(vp1) || vp0>1.0E50) || vp1>1.0E50)
|
|
{
|
|
//--- VP0/VP1 are not finite, assume that it is +INF or -INF
|
|
state.m_fi.Set(i,td-y[i]);
|
|
if(state.m_needfij)
|
|
{
|
|
state.m_j.Row(i,vector<double>::Zeros(5));
|
|
state.m_j.Set(i,3,1.0);
|
|
}
|
|
continue;
|
|
}
|
|
//--- VP0/VP1 are finite, normal processing
|
|
if(is4pl)
|
|
{
|
|
state.m_fi.Set(i,td+(ta-td)/vp1-y[i]);
|
|
if(state.m_needfij)
|
|
{
|
|
state.m_j.Set(i,0,1/vp1);
|
|
state.m_j.Set(i,1,-((ta-td)*vp0*MathLog(x[i]/tc)/CMath::Sqr(vp1)));
|
|
state.m_j.Set(i,2,(ta-td)*(tb/tc)*vp0/CMath::Sqr(vp1));
|
|
state.m_j.Set(i,3,1-1/vp1);
|
|
state.m_j.Set(i,4,0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
state.m_fi.Set(i,td+(ta-td)/vp1-y[i]);
|
|
if(state.m_needfij)
|
|
{
|
|
state.m_j.Set(i,0,1/vp1);
|
|
state.m_j.Set(i,1,(ta-td)*-tg*MathPow(1+vp0,-tg-1)*vp0*MathLog(x[i]/tc));
|
|
state.m_j.Set(i,2,(ta-td)*-tg*MathPow(1+vp0,-tg-1)*vp0*-(tb/tc));
|
|
state.m_j.Set(i,3,1-1/vp1);
|
|
state.m_j.Set(i,4,-((ta-td)/vp1*MathLog(1+vp0)));
|
|
}
|
|
}
|
|
}
|
|
//--- Add regularizer
|
|
for(i=0; i<=4; i++)
|
|
{
|
|
state.m_fi.Set(n+i,lambdav*state.m_x[i]);
|
|
if(state.m_needfij)
|
|
{
|
|
state.m_j.Row(n+i,vector<double>::Zeros(5));
|
|
state.m_j.Set(n+i,i,lambdav);
|
|
}
|
|
}
|
|
//--- Done
|
|
continue;
|
|
}
|
|
CAp::Assert(false,"LogisticFitX: internal error");
|
|
return;
|
|
}
|
|
CMinLM::MinLMResultsBuf(state,p1,replm);
|
|
CAp::Assert(replm.m_terminationtype>0,"LogisticFitX: internal error");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate errors for 4PL/5PL fit. |
|
|
//| Leaves other fields of Rep unchanged, so caller should properly |
|
|
//| initialize it with ClearRep() call. |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::LogisticFit45Errors(CRowDouble &x,CRowDouble &y,int n,
|
|
double a,double b,double c,
|
|
double d,double g,CLSFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
int k=0;
|
|
double v=0;
|
|
double rss=0;
|
|
double tss=0;
|
|
double meany=0;
|
|
//--- Calculate errors
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_avgrelerror=0;
|
|
rep.m_maxerror=0;
|
|
k=0;
|
|
rss=0.0;
|
|
tss=0.0;
|
|
meany=0.0;
|
|
for(int i=0; i<n; i++)
|
|
meany+=y[i];
|
|
meany/=n;
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Calculate residual from regression
|
|
if(x[i]>0)
|
|
v=d+(a-d)/MathPow(1.0+MathPow(x[i]/c,b),g)-y[i];
|
|
else
|
|
if(b>=0.0)
|
|
v=a-y[i];
|
|
else
|
|
v=d-y[i];
|
|
//--- Update RSS (residual sum of squares) and TSS (total sum of squares)
|
|
//--- which are used to calculate coefficient of determination.
|
|
//--- NOTE: we use formula R2 = 1-RSS/TSS because it has nice property of
|
|
//--- being equal to 0.0 if and only if model perfectly fits data.
|
|
//--- When we fit nonlinear models, there are exist multiple ways of
|
|
//--- determining R2, each of them giving different results. Formula
|
|
//--- above is the most intuitive one.
|
|
rss+=v*v;
|
|
tss+=CMath::Sqr(y[i]-meany);
|
|
//--- Update errors
|
|
rep.m_rmserror+=CMath::Sqr(v);
|
|
rep.m_avgerror+=MathAbs(v);
|
|
if(y[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror+=MathAbs(v/y[i]);
|
|
k++;
|
|
}
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
|
|
rep.m_avgerror/=n;
|
|
if(k>0)
|
|
rep.m_avgrelerror/=k;
|
|
rep.m_r2=1.0-rss/tss;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLSFit::ClearReport(CLSFitReport &rep)
|
|
{
|
|
rep.m_taskrcond=0;
|
|
rep.m_iterationscount=0;
|
|
rep.m_varidx=-1;
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_avgrelerror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_wrmserror=0;
|
|
rep.m_r2=0;
|
|
rep.m_covpar.Resize(0,0);
|
|
rep.m_errpar.Resize(0);
|
|
rep.m_errcurve.Resize(0);
|
|
rep.m_noise.Resize(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Parametric spline inteprolant: 2-dimensional curve. |
|
|
//| You should not try to access its members directly - use |
|
|
//| PSpline2XXXXXXXX() functions instead. |
|
|
//+------------------------------------------------------------------+
|
|
class CPSpline2Interpolant
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_n;
|
|
bool m_periodic;
|
|
CSpline1DInterpolant m_x;
|
|
CSpline1DInterpolant m_y;
|
|
//--- array
|
|
double m_p[];
|
|
//--- constructor, destructor
|
|
CPSpline2Interpolant(void) { m_n=0; m_periodic=false; }
|
|
~CPSpline2Interpolant(void) {}
|
|
//--- copy
|
|
void Copy(CPSpline2Interpolant&obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline2Interpolant::Copy(CPSpline2Interpolant &obj)
|
|
{
|
|
//--- copy variables
|
|
m_n=obj.m_n;
|
|
m_periodic=obj.m_periodic;
|
|
m_x.Copy(obj.m_x);
|
|
m_y.Copy(obj.m_y);
|
|
//--- copy array
|
|
ArrayCopy(m_p,obj.m_p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Parametric spline inteprolant: 2-dimensional curve. |
|
|
//| You should not try to access its members directly - use |
|
|
//| PSpline2XXXXXXXX() functions instead. |
|
|
//+------------------------------------------------------------------+
|
|
class CPSpline2InterpolantShell
|
|
{
|
|
private:
|
|
CPSpline2Interpolant m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CPSpline2InterpolantShell(void) {}
|
|
CPSpline2InterpolantShell(CPSpline2Interpolant&obj) { m_innerobj.Copy(obj); }
|
|
~CPSpline2InterpolantShell(void) {}
|
|
//--- method
|
|
CPSpline2Interpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Parametric spline inteprolant: 3-dimensional curve. |
|
|
//| You should not try to access its members directly - use |
|
|
//| PSpline3XXXXXXXX() functions instead. |
|
|
//+------------------------------------------------------------------+
|
|
class CPSpline3Interpolant
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_n;
|
|
bool m_periodic;
|
|
CSpline1DInterpolant m_x;
|
|
CSpline1DInterpolant m_y;
|
|
CSpline1DInterpolant m_z;
|
|
//--- array
|
|
double m_p[];
|
|
//--- constructor, destructor
|
|
CPSpline3Interpolant(void) { m_n=0; m_periodic=false; }
|
|
~CPSpline3Interpolant(void) {}
|
|
//--- copy
|
|
void Copy(CPSpline3Interpolant&obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline3Interpolant::Copy(CPSpline3Interpolant &obj)
|
|
{
|
|
//--- copy variables
|
|
m_n=obj.m_n;
|
|
m_periodic=obj.m_periodic;
|
|
m_x.Copy(obj.m_x);
|
|
m_y.Copy(obj.m_y);
|
|
m_z.Copy(obj.m_z);
|
|
//--- copy array
|
|
ArrayCopy(m_p,obj.m_p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Parametric spline inteprolant: 3-dimensional curve. |
|
|
//| You should not try to access its members directly - use |
|
|
//| PSpline3XXXXXXXX() functions instead. |
|
|
//+------------------------------------------------------------------+
|
|
class CPSpline3InterpolantShell
|
|
{
|
|
private:
|
|
CPSpline3Interpolant m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CPSpline3InterpolantShell(void) {}
|
|
CPSpline3InterpolantShell(CPSpline3Interpolant&obj) { m_innerobj.Copy(obj); }
|
|
~CPSpline3InterpolantShell(void) {}
|
|
//--- method
|
|
CPSpline3Interpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Parametric spline |
|
|
//+------------------------------------------------------------------+
|
|
class CPSpline
|
|
{
|
|
public:
|
|
static void PSpline2Build(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline2Interpolant&p);
|
|
static void PSpline3Build(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline3Interpolant&p);
|
|
static void PSpline2BuildPeriodic(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline2Interpolant&p);
|
|
static void PSpline3BuildPeriodic(CMatrixDouble&cxy,const int n,const int st,const int pt,CPSpline3Interpolant&p);
|
|
static void PSpline2ParameterValues(CPSpline2Interpolant&p,int &n,double &t[]);
|
|
static void PSpline3ParameterValues(CPSpline3Interpolant&p,int &n,double &t[]);
|
|
static void PSpline2Calc(CPSpline2Interpolant&p,double t,double&x,double&y);
|
|
static void PSpline3Calc(CPSpline3Interpolant&p,double t,double&x,double&y,double&z);
|
|
static void PSpline2Tangent(CPSpline2Interpolant&p,double t,double&x,double&y);
|
|
static void PSpline3Tangent(CPSpline3Interpolant&p,double t,double&x,double&y,double&z);
|
|
static void PSpline2Diff(CPSpline2Interpolant&p,double t,double&x,double&dx,double&y,double&dy);
|
|
static void PSpline3Diff(CPSpline3Interpolant&p,double t,double&x,double&dx,double&y,double&dy,double&z,double&dz);
|
|
static void PSpline2Diff2(CPSpline2Interpolant&p,double t,double&x,double&dx,double&d2x,double&y,double&dy,double&d2y);
|
|
static void PSpline3Diff2(CPSpline3Interpolant&p,double t,double&x,double&dx,double&d2x,double&y,double&dy,double&d2y,double&z,double&dz,double&d2z);
|
|
static double PSpline2ArcLength(CPSpline2Interpolant&p,const double a,const double b);
|
|
static double PSpline3ArcLength(CPSpline3Interpolant&p,const double a,const double b);
|
|
static void ParametricRDPFixed(CMatrixDouble&x,int n,int d,int stopm,double stopeps,CMatrixDouble&x2,int &idx2[],int &nsections);
|
|
|
|
private:
|
|
static void PSpline2Par(CMatrixDouble&xy,const int n,const int pt,double &p[]);
|
|
static void PSpline3Par(CMatrixDouble&xy,const int n,const int pt,double &p[]);
|
|
static void RDPAnalyzeSectionPar(CMatrixDouble&xy,int i0,int i1,int d,int &worstidx,double&worsterror);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds non-periodic 2-dimensional parametric |
|
|
//| spline which starts at (X[0],Y[0]) and ends at (X[N-1],Y[N-1]). |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - points, array[0..N-1,0..1]. |
|
|
//| XY[I,0:1] corresponds to the Ith point. |
|
|
//| Order of points is important! |
|
|
//| N - points count, N>=5 for Akima splines, N>=2 for other |
|
|
//| types of splines. |
|
|
//| ST - spline type: |
|
|
//| * 0 Akima spline |
|
|
//| * 1 parabolically terminated Catmull-Rom spline |
|
|
//| (Tension=0) |
|
|
//| * 2 parabolically terminated cubic spline |
|
|
//| PT - parameterization type: |
|
|
//| * 0 uniform |
|
|
//| * 1 chord length |
|
|
//| * 2 centripetal |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| NOTES: |
|
|
//| * this function assumes that there all consequent points are |
|
|
//| distinct. I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2), |
|
|
//| (x2,y2)<>(x3,y3) and so on. However, non-consequent points may |
|
|
//| coincide, i.e. we can have (x0,y0) = (x2,y2). |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Build(CMatrixDouble &cxy,const int n,
|
|
const int st,const int pt,
|
|
CPSpline2Interpolant &p)
|
|
{
|
|
int i_=0;
|
|
//--- create array
|
|
double tmp[];
|
|
//--- copy matrix
|
|
CMatrixDouble xy;
|
|
xy=cxy;
|
|
//--- check
|
|
if(!CAp::Assert(st>=0 && st<=2,__FUNCTION__+": incorrect spline type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
|
|
return;
|
|
//--- check
|
|
if(st==0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5 (minimum value for Akima splines)!"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2!"))
|
|
return;
|
|
}
|
|
//--- Prepare
|
|
p.m_n=n;
|
|
p.m_periodic=false;
|
|
//--- allocation
|
|
ArrayResize(tmp,n);
|
|
//--- Build parameterization,check that all parameters are distinct
|
|
PSpline2Par(xy,n,pt,p.m_p);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n),__FUNCTION__+": consequent points are too close!"))
|
|
return;
|
|
//--- Build splines
|
|
if(st==0)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_y);
|
|
}
|
|
//--- check
|
|
if(st==1)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_y);
|
|
}
|
|
//--- check
|
|
if(st==2)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_y);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds non-periodic 3-dimensional parametric spline|
|
|
//| which starts at (X[0],Y[0],Z[0]) and ends at |
|
|
//| (X[N-1],Y[N-1],Z[N-1]). |
|
|
//| Same as PSpline2Build() function, but for 3D, so we won't |
|
|
//| duplicate its description here. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Build(CMatrixDouble &cxy,const int n,
|
|
const int st,const int pt,
|
|
CPSpline3Interpolant &p)
|
|
{
|
|
int i_=0;
|
|
//--- create array
|
|
double tmp[];
|
|
//--- copy matrix
|
|
CMatrixDouble xy;
|
|
xy=cxy;
|
|
//--- check
|
|
if(!CAp::Assert(st>=0 && st<=2,__FUNCTION__+": incorrect spline type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
|
|
return;
|
|
//--- check
|
|
if(st==0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=5,__FUNCTION__+": N<5 (minimum value for Akima splines)!"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+"PSpline3Build: N<2!"))
|
|
return;
|
|
}
|
|
//--- Prepare
|
|
p.m_n=n;
|
|
p.m_periodic=false;
|
|
//--- allocation
|
|
ArrayResize(tmp,n);
|
|
//--- Build parameterization,check that all parameters are distinct
|
|
PSpline3Par(xy,n,pt,p.m_p);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n),__FUNCTION__+": consequent points are too close!"))
|
|
return;
|
|
//--- Build splines
|
|
if(st==0)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_y);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][2];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildAkima(p.m_p,tmp,n,p.m_z);
|
|
}
|
|
//--- check
|
|
if(st==1)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_y);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][2];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n,0,0.0,p.m_z);
|
|
}
|
|
//--- check
|
|
if(st==2)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_y);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
tmp[i_]=xy[i_][2];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n,0,0.0,0,0.0,p.m_z);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds periodic 2-dimensional parametric spline |
|
|
//| which starts at (X[0],Y[0]), goes through all points to |
|
|
//| (X[N-1],Y[N-1]) and then back to (X[0],Y[0]). |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - points, array[0..N-1,0..1]. |
|
|
//| XY[I,0:1] corresponds to the Ith point. |
|
|
//| XY[N-1,0:1] must be different from XY[0,0:1]. |
|
|
//| Order of points is important! |
|
|
//| N - points count, N>=3 for other types of splines. |
|
|
//| ST - spline type: |
|
|
//| * 1 Catmull-Rom spline (Tension=0) with cyclic |
|
|
//| boundary conditions |
|
|
//| * 2 cubic spline with cyclic boundary conditions |
|
|
//| PT - parameterization type: |
|
|
//| * 0 uniform |
|
|
//| * 1 chord length |
|
|
//| * 2 centripetal |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| NOTES: |
|
|
//| * this function assumes that there all consequent points are |
|
|
//| distinct. I.e. (x0,y0)<>(x1,y1), (x1,y1)<>(x2,y2), |
|
|
//| (x2,y2)<>(x3,y3) and so on. However, non-consequent points may |
|
|
//| coincide, i.e. we can have (x0,y0) = (x2,y2). |
|
|
//| * last point of sequence is NOT equal to the first point. You |
|
|
//| shouldn't make curve "explicitly periodic" by making them |
|
|
//| equal. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2BuildPeriodic(CMatrixDouble &cxy,const int n,
|
|
const int st,const int pt,
|
|
CPSpline2Interpolant &p)
|
|
{
|
|
int i_=0;
|
|
//--- create array
|
|
double tmp[];
|
|
//--- create matrix
|
|
CMatrixDouble xyp;
|
|
CMatrixDouble xy;
|
|
//--- copy matrix
|
|
xy=cxy;
|
|
//--- check
|
|
if(!CAp::Assert(st>=1 && st<=2,__FUNCTION__+": incorrect spline type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=3,__FUNCTION__+": N<3!"))
|
|
return;
|
|
//--- Prepare
|
|
p.m_n=n;
|
|
p.m_periodic=true;
|
|
//--- allocation
|
|
ArrayResize(tmp,n+1);
|
|
xyp.Resize(n+1,2);
|
|
//--- change values
|
|
for(i_=0; i_<n; i_++)
|
|
xyp.Set(i_,0,xy[i_][0]);
|
|
for(i_=0; i_<n; i_++)
|
|
xyp.Set(i_,1,xy[i_][1]);
|
|
for(i_=0; i_<=1; i_++)
|
|
xyp.Set(n,i_,xy[0][i_]);
|
|
//--- Build parameterization,check that all parameters are distinct
|
|
PSpline2Par(xyp,n+1,pt,p.m_p);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n+1),__FUNCTION__+": consequent (or first and last) points are too close!"))
|
|
return;
|
|
//--- Build splines
|
|
if(st==1)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_y);
|
|
}
|
|
//--- check
|
|
if(st==2)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_y);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds periodic 3-dimensional parametric spline |
|
|
//| which starts at (X[0],Y[0],Z[0]), goes through all points to |
|
|
//| (X[N-1],Y[N-1],Z[N-1]) and then back to (X[0],Y[0],Z[0]). |
|
|
//| Same as PSpline2Build() function, but for 3D, so we won't |
|
|
//| duplicate its description here. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3BuildPeriodic(CMatrixDouble &cxy,const int n,
|
|
const int st,const int pt,
|
|
CPSpline3Interpolant &p)
|
|
{
|
|
int i_=0;
|
|
//--- create array
|
|
double tmp[];
|
|
//--- create matrix
|
|
CMatrixDouble xyp;
|
|
CMatrixDouble xy;
|
|
//--- copy matrix
|
|
xy=cxy;
|
|
//--- check
|
|
if(!CAp::Assert(st>=1 && st<=2,__FUNCTION__+": incorrect spline type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": incorrect parameterization type!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=3,__FUNCTION__+": N<3!"))
|
|
return;
|
|
//--- Prepare
|
|
p.m_n=n;
|
|
p.m_periodic=true;
|
|
//--- allocation
|
|
ArrayResize(tmp,n+1);
|
|
xyp.Resize(n+1,3);
|
|
//--- change values
|
|
for(i_=0; i_<n; i_++)
|
|
xyp.Set(i_,0,xy[i_][0]);
|
|
for(i_=0; i_<n; i_++)
|
|
xyp.Set(i_,1,xy[i_][1]);
|
|
for(i_=0; i_<n; i_++)
|
|
xyp.Set(i_,2,xy[i_][2]);
|
|
for(i_=0; i_<=2; i_++)
|
|
xyp.Set(n,i_,xy[0][i_]);
|
|
//--- Build parameterization,check that all parameters are distinct
|
|
PSpline3Par(xyp,n+1,pt,p.m_p);
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::AreDistinct(p.m_p,n+1),__FUNCTION__+": consequent (or first and last) points are too close!"))
|
|
return;
|
|
//--- Build splines
|
|
if(st==1)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_y);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][2];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCatmullRom(p.m_p,tmp,n+1,-1,0.0,p.m_z);
|
|
}
|
|
//--- check
|
|
if(st==2)
|
|
{
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][0];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_x);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][1];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_y);
|
|
//--- copy
|
|
for(i_=0; i_<=n; i_++)
|
|
tmp[i_]=xyp[i_][2];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(p.m_p,tmp,n+1,-1,0.0,-1,0.0,p.m_z);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns vector of parameter values correspoding to |
|
|
//| points. |
|
|
//| I.e. for P created from (X[0],Y[0])...(X[N-1],Y[N-1]) and |
|
|
//| U=TValues(P) we have |
|
|
//| (X[0],Y[0]) = PSpline2Calc(P,U[0]), |
|
|
//| (X[1],Y[1]) = PSpline2Calc(P,U[1]), |
|
|
//| (X[2],Y[2]) = PSpline2Calc(P,U[2]), |
|
|
//| ... |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| N - array size |
|
|
//| T - array[0..N-1] |
|
|
//| NOTES: |
|
|
//| * for non-periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]=1|
|
|
//| * for periodic splines U[0]=0, U[0]<U[1]<...<U[N-1], U[N-1]<1|
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2ParameterValues(CPSpline2Interpolant &p,
|
|
int &n,double &t[])
|
|
{
|
|
//--- initialization
|
|
n=0;
|
|
//--- check
|
|
if(!CAp::Assert(p.m_n>=2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- initialization
|
|
n=p.m_n;
|
|
//--- allocation
|
|
ArrayResize(t,n);
|
|
//--- copy
|
|
for(int i_=0; i_<n; i_++)
|
|
t[i_]=p.m_p[i_];
|
|
t[0]=0;
|
|
//--- check
|
|
if(!p.m_periodic)
|
|
t[n-1]=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns vector of parameter values correspoding to |
|
|
//| points. |
|
|
//| Same as PSpline2ParameterValues(), but for 3D. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3ParameterValues(CPSpline3Interpolant &p,
|
|
int &n,double &t[])
|
|
{
|
|
int i_=0;
|
|
//--- initialization
|
|
n=0;
|
|
//--- check
|
|
if(!CAp::Assert(p.m_n>=2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- initialization
|
|
n=p.m_n;
|
|
//--- allocation
|
|
ArrayResize(t,n);
|
|
//--- copy
|
|
for(i_=0; i_<n; i_++)
|
|
t[i_]=p.m_p[i_];
|
|
t[0]=0;
|
|
//--- check
|
|
if(!p.m_periodic)
|
|
t[n-1]=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates the value of the parametric spline for a|
|
|
//| given value of parameter T |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-position |
|
|
//| Y - Y-position |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Calc(CPSpline2Interpolant &p,double t,
|
|
double &x,double &y)
|
|
{
|
|
//--- initialization
|
|
x=0;
|
|
y=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
x=CSpline1D::Spline1DCalc(p.m_x,t);
|
|
//--- function call
|
|
y=CSpline1D::Spline1DCalc(p.m_y,t);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates the value of the parametric spline for a|
|
|
//| given value of parameter T. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond |
|
|
//| to parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-position |
|
|
//| Y - Y-position |
|
|
//| Z - Z-position |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Calc(CPSpline3Interpolant &p,double t,
|
|
double &x,double &y,double &z)
|
|
{
|
|
//--- initialization
|
|
x=0;
|
|
y=0;
|
|
z=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
x=CSpline1D::Spline1DCalc(p.m_x,t);
|
|
//--- function call
|
|
y=CSpline1D::Spline1DCalc(p.m_y,t);
|
|
//--- function call
|
|
z=CSpline1D::Spline1DCalc(p.m_z,t);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates tangent vector for a given value of |
|
|
//| parameter T |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-component of tangent vector (normalized) |
|
|
//| Y - Y-component of tangent vector (normalized) |
|
|
//| NOTE: |
|
|
//| X^2+Y^2 is either 1 (for non-zero tangent vector) or 0. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Tangent(CPSpline2Interpolant &p,double t,
|
|
double &x,double &y)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
//--- initialization
|
|
x=0;
|
|
y=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
PSpline2Diff(p,t,v0,x,v1,y);
|
|
//--- check
|
|
if(x!=0.0 || y!=0.0)
|
|
{
|
|
//--- this code is a bit more complex than X^2+Y^2 to avoid
|
|
//--- overflow for large values of X and Y.
|
|
v=CApServ::SafePythag2(x,y);
|
|
x=x/v;
|
|
y=y/v;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates tangent vector for a given value of |
|
|
//| parameter T |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-component of tangent vector (normalized) |
|
|
//| Y - Y-component of tangent vector (normalized) |
|
|
//| Z - Z-component of tangent vector (normalized) |
|
|
//| NOTE: |
|
|
//| X^2+Y^2+Z^2 is either 1 (for non-zero tangent vector) or 0. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Tangent(CPSpline3Interpolant &p,double t,
|
|
double &x,double &y,double &z)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
//--- initialization
|
|
x=0;
|
|
y=0;
|
|
z=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
PSpline3Diff(p,t,v0,x,v1,y,v2,z);
|
|
//--- check
|
|
if((x!=0.0 || y!=0.0) || z!=0.0)
|
|
{
|
|
//--- function call
|
|
v=CApServ::SafePythag3(x,y,z);
|
|
//--- change values
|
|
x=x/v;
|
|
y=y/v;
|
|
z=z/v;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates derivative, i.e. it returns |
|
|
//| (dX/dT,dY/dT). |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-value |
|
|
//| DX - X-derivative |
|
|
//| Y - Y-value |
|
|
//| DY - Y-derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Diff(CPSpline2Interpolant &p,double t,
|
|
double &x,double &dx,double &y,
|
|
double &dy)
|
|
{
|
|
double d2s=0;
|
|
//--- change values
|
|
x=0;
|
|
dx=0;
|
|
y=0;
|
|
dy=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2s);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates derivative, i.e. it returns |
|
|
//| (dX/dT,dY/dT,dZ/dT). |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-value |
|
|
//| DX - X-derivative |
|
|
//| Y - Y-value |
|
|
//| DY - Y-derivative |
|
|
//| Z - Z-value |
|
|
//| DZ - Z-derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Diff(CPSpline3Interpolant &p,double t,
|
|
double &x,double &dx,double &y,
|
|
double &dy,double &z,double &dz)
|
|
{
|
|
double d2s=0;
|
|
//--- initialization
|
|
x=0;
|
|
dx=0;
|
|
y=0;
|
|
dy=0;
|
|
z=0;
|
|
dz=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2s);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2s);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_z,t,z,dz,d2s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates first and second derivative with respect|
|
|
//| to T. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-value |
|
|
//| DX - derivative |
|
|
//| D2X - second derivative |
|
|
//| Y - Y-value |
|
|
//| DY - derivative |
|
|
//| D2Y - second derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Diff2(CPSpline2Interpolant &p,double t,
|
|
double &x,double &dx,double &d2x,
|
|
double &y,double &dy,double &d2y)
|
|
{
|
|
//--- initialization
|
|
x=0;
|
|
dx=0;
|
|
d2x=0;
|
|
y=0;
|
|
dy=0;
|
|
d2y=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2x);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates first and second derivative with respect|
|
|
//| to T. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| T - point: |
|
|
//| * T in [0,1] corresponds to interval spanned by |
|
|
//| points |
|
|
//| * for non-periodic splines T<0 (or T>1) correspond to|
|
|
//| parts of the curve before the first (after the |
|
|
//| last) point |
|
|
//| * for periodic splines T<0 (or T>1) are projected |
|
|
//| into [0,1] by making T=T-floor(T). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - X-value |
|
|
//| DX - derivative |
|
|
//| D2X - second derivative |
|
|
//| Y - Y-value |
|
|
//| DY - derivative |
|
|
//| D2Y - second derivative |
|
|
//| Z - Z-value |
|
|
//| DZ - derivative |
|
|
//| D2Z - second derivative |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Diff2(CPSpline3Interpolant &p,double t,
|
|
double &x,double &dx,double &d2x,
|
|
double &y,double &dy,double &d2y,
|
|
double &z,double &dz,double &d2z)
|
|
{
|
|
//--- initialization
|
|
x=0;
|
|
dx=0;
|
|
d2x=0;
|
|
y=0;
|
|
dy=0;
|
|
d2y=0;
|
|
z=0;
|
|
dz=0;
|
|
d2z=0;
|
|
//--- check
|
|
if(p.m_periodic)
|
|
t=t-(int)MathFloor(t);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_x,t,x,dx,d2x);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_y,t,y,dy,d2y);
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(p.m_z,t,z,dz,d2z);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates arc length, i.e. length of curve between|
|
|
//| t=a and t=b. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| A,B - parameter values corresponding to arc ends: |
|
|
//| * B>A will result in positive length returned |
|
|
//| * B<A will result in negative length returned |
|
|
//| RESULT: |
|
|
//| length of arc starting at T=A and ending at T=B. |
|
|
//+------------------------------------------------------------------+
|
|
double CPSpline::PSpline2ArcLength(CPSpline2Interpolant &p,const double a,
|
|
const double b)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double sx=0;
|
|
double dsx=0;
|
|
double d2sx=0;
|
|
double sy=0;
|
|
double dsy=0;
|
|
double d2sy=0;
|
|
//--- objects of classes
|
|
CAutoGKState State;
|
|
CAutoGKReport rep;
|
|
//--- function call
|
|
CAutoGK::AutoGKSmooth(a,b,State);
|
|
//--- cycle
|
|
while(CAutoGK::AutoGKIteration(State))
|
|
{
|
|
CSpline1D::Spline1DDiff(p.m_x,State.m_x,sx,dsx,d2sx);
|
|
CSpline1D::Spline1DDiff(p.m_y,State.m_x,sy,dsy,d2sy);
|
|
State.m_f=CApServ::SafePythag2(dsx,dsy);
|
|
}
|
|
//--- function call
|
|
CAutoGK::AutoGKResults(State,result,rep);
|
|
//--- check
|
|
if(!CAp::Assert(rep.m_terminationtype>0,__FUNCTION__+": internal error!"))
|
|
return(EMPTY_VALUE);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates arc length, i.e. length of curve between|
|
|
//| t=a and t=b. |
|
|
//| INPUT PARAMETERS: |
|
|
//| P - parametric spline interpolant |
|
|
//| A,B - parameter values corresponding to arc ends: |
|
|
//| * B>A will result in positive length returned |
|
|
//| * B<A will result in negative length returned |
|
|
//| RESULT: |
|
|
//| length of arc starting at T=A and ending at T=B. |
|
|
//+------------------------------------------------------------------+
|
|
double CPSpline::PSpline3ArcLength(CPSpline3Interpolant &p,const double a,
|
|
const double b)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double sx=0;
|
|
double dsx=0;
|
|
double d2sx=0;
|
|
double sy=0;
|
|
double dsy=0;
|
|
double d2sy=0;
|
|
double sz=0;
|
|
double dsz=0;
|
|
double d2sz=0;
|
|
//--- objects of classes
|
|
CAutoGKState State;
|
|
CAutoGKReport rep;
|
|
//--- function call
|
|
CAutoGK::AutoGKSmooth(a,b,State);
|
|
//--- cycle
|
|
while(CAutoGK::AutoGKIteration(State))
|
|
{
|
|
CSpline1D::Spline1DDiff(p.m_x,State.m_x,sx,dsx,d2sx);
|
|
CSpline1D::Spline1DDiff(p.m_y,State.m_x,sy,dsy,d2sy);
|
|
CSpline1D::Spline1DDiff(p.m_z,State.m_x,sz,dsz,d2sz);
|
|
State.m_f=CApServ::SafePythag3(dsx,dsy,dsz);
|
|
}
|
|
//--- function call
|
|
CAutoGK::AutoGKResults(State,result,rep);
|
|
//--- check
|
|
if(!CAp::Assert(rep.m_terminationtype>0,__FUNCTION__+": internal error!"))
|
|
return(EMPTY_VALUE);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine fits piecewise linear curve to points with |
|
|
//| Ramer-Douglas-Peucker algorithm. This function performs |
|
|
//| PARAMETRIC fit, i.e. it can be used to fit curves like circles. |
|
|
//| On input it accepts dataset which describes parametric |
|
|
//| multidimensional curve X(t), with X being vector, and t taking |
|
|
//| values in [0,N), where N is a number of points in dataset. As |
|
|
//| result, it returns reduced dataset X2, which can be used to |
|
|
//| build parametric curve X2(t), which approximates X(t) with |
|
|
//| desired precision (or has specified number of sections). |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array of multidimensional points: |
|
|
//| * at least N elements, leading N elements are used |
|
|
//| if more than N elements were specified |
|
|
//| * order of points is IMPORTANT because it is |
|
|
//| parametric fit |
|
|
//| * each row of array is one point which has D |
|
|
//| coordinates |
|
|
//| N - number of elements in X |
|
|
//| D - number of dimensions (elements per row of X) |
|
|
//| StopM - stopping condition - desired number of sections: |
|
|
//| * at most M sections are generated by this function|
|
|
//| * less than M sections can be generated if we have |
|
|
//| N<M (or some X are non-distinct). |
|
|
//| * zero StopM means that algorithm does not stop |
|
|
//| after achieving some pre-specified section count |
|
|
//| StopEps - stopping condition - desired precision: |
|
|
//| * algorithm stops after error in each section is at|
|
|
//| most Eps |
|
|
//| * zero Eps means that algorithm does not stop after|
|
|
//| achieving some pre-specified precision |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X2 - array of corner points for piecewise approximation,|
|
|
//| has length NSections+1 or zero (for NSections=0). |
|
|
//| Idx2 - array of indexes (parameter values): |
|
|
//| * has length NSections+1 or zero (for NSections=0).|
|
|
//| * each element of Idx2 corresponds to same-numbered|
|
|
//| element of X2 |
|
|
//| * each element of Idx2 is index of corresponding |
|
|
//| element of X2 at original array X, i.e. I-th row |
|
|
//| of X2 is Idx2[I]-th row of X. |
|
|
//| * elements of Idx2 can be treated as parameter |
|
|
//| values which should be used when building new |
|
|
//| parametric curve |
|
|
//| * Idx2[0]=0, Idx2[NSections]=N-1 |
|
|
//| NSections - number of sections found by algorithm,NSections<=M,|
|
|
//| NSections can be zero for degenerate datasets (N<=1|
|
|
//| or all X[] are non-distinct). |
|
|
//| NOTE: algorithm stops after: |
|
|
//| a) dividing curve into StopM sections |
|
|
//| b) achieving required precision StopEps |
|
|
//| c) dividing curve into N-1 sections |
|
|
//| If both StopM and StopEps are non-zero, algorithm is stopped by |
|
|
//| the FIRST criterion which is satisfied. In case both StopM and |
|
|
//| StopEps are zero, algorithm stops because of (c). |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::ParametricRDPFixed(CMatrixDouble &x,
|
|
int n,
|
|
int d,
|
|
int stopm,
|
|
double stopeps,
|
|
CMatrixDouble &x2,
|
|
int &idx2[],
|
|
int &nsections)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
bool allsame=false;
|
|
int k0=0;
|
|
int k1=0;
|
|
int k2=0;
|
|
double e0=0;
|
|
double e1=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int worstidx=0;
|
|
double worsterror=0;
|
|
CMatrixDouble sections;
|
|
double heaperrors[];
|
|
int heaptags[];
|
|
double buf0[];
|
|
double buf1[];
|
|
|
|
x2.Resize(0,0);
|
|
ArrayFree(idx2);
|
|
nsections=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(d>=1,__FUNCTION__+": D<=0"))
|
|
return;
|
|
if(!CAp::Assert(stopm>=0,__FUNCTION__+": StopM<1"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(stopeps) && stopeps>=0.0,__FUNCTION__+": StopEps<0 or is infinite"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(x)>=n,__FUNCTION__+": Rows(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(x)>=d,__FUNCTION__+": Cols(X)<D"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(x,n,d),__FUNCTION__+": X contains infinite/NAN values"))
|
|
return;
|
|
//--- Handle degenerate cases
|
|
if(n<=1)
|
|
{
|
|
nsections=0;
|
|
return;
|
|
}
|
|
allsame=true;
|
|
for(i=1; i<n; i++)
|
|
{
|
|
for(j=0; j<d; j++)
|
|
allsame=allsame && x.Get(i,j)==x.Get(0,j);
|
|
}
|
|
if(allsame)
|
|
{
|
|
nsections=0;
|
|
return;
|
|
}
|
|
//--- Prepare first section
|
|
RDPAnalyzeSectionPar(x,0,n-1,d,worstidx,worsterror);
|
|
sections.Resize(n,4);
|
|
ArrayResize(heaperrors,n);
|
|
ArrayResize(heaptags,n);
|
|
nsections=1;
|
|
sections.Set(0,0,0);
|
|
sections.Set(0,1,n-1);
|
|
sections.Set(0,2,worstidx);
|
|
sections.Set(0,3,worsterror);
|
|
heaperrors[0]=worsterror;
|
|
heaptags[0]=0;
|
|
//--- check
|
|
if(!CAp::Assert(sections.Get(0,1)==(n-1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Main loop.
|
|
//--- Repeatedly find section with worst error and divide it.
|
|
//--- Terminate after M-th section, or because of other reasons (see loop internals).
|
|
while(true)
|
|
{
|
|
//--- Break loop if one of the stopping conditions was met.
|
|
//--- Store index of worst section to K.
|
|
if(heaperrors[0]==0.0)
|
|
break;
|
|
if(stopeps>0.0 && heaperrors[0]<=stopeps)
|
|
break;
|
|
if(stopm>0 && nsections>=stopm)
|
|
break;
|
|
k=heaptags[0];
|
|
//--- K-th section is divided in two:
|
|
//--- * first one spans interval from X[Sections[K,0]] to X[Sections[K,2]]
|
|
//--- * second one spans interval from X[Sections[K,2]] to X[Sections[K,1]]
|
|
//--- First section is stored at K-th position, second one is appended to the table.
|
|
//--- Then we update heap which stores pairs of (error,section_index)
|
|
k0=(int)MathRound(sections.Get(k,0));
|
|
k1=(int)MathRound(sections.Get(k,1));
|
|
k2=(int)MathRound(sections.Get(k,2));
|
|
RDPAnalyzeSectionPar(x,k0,k2,d,idx0,e0);
|
|
RDPAnalyzeSectionPar(x,k2,k1,d,idx1,e1);
|
|
sections.Set(k,0,k0);
|
|
sections.Set(k,1,k2);
|
|
sections.Set(k,2,idx0);
|
|
sections.Set(k,3,e0);
|
|
CTSort::TagHeapReplaceTopI(heaperrors,heaptags,nsections,e0,k);
|
|
sections.Set(nsections,0,k2);
|
|
sections.Set(nsections,1,k1);
|
|
sections.Set(nsections,2,idx1);
|
|
sections.Set(nsections,3,e1);
|
|
CTSort::TagHeapPushI(heaperrors,heaptags,nsections,e1,nsections);
|
|
}
|
|
//--- Convert from sections to indexes
|
|
ArrayResize(buf0,nsections+1);
|
|
for(i=0; i<nsections; i++)
|
|
buf0[i]=(int)MathRound(sections.Get(i,0));
|
|
buf0[nsections]=n-1;
|
|
CTSort::TagSortFast(buf0,buf1,nsections+1);
|
|
ArrayResize(idx2,nsections+1);
|
|
for(i=0; i<=nsections; i++)
|
|
idx2[i]=(int)MathRound(buf0[i]);
|
|
//--- check
|
|
if(!CAp::Assert(idx2[0]==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(idx2[nsections]==n-1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Output sections:
|
|
//--- * first NSection elements of X2/Y2 are filled by x/y at left boundaries of sections
|
|
//--- * last element of X2/Y2 is filled by right boundary of rightmost section
|
|
//--- * X2/Y2 is sorted by ascending of X2
|
|
x2.Resize(nsections+1,d);
|
|
for(i=0; i<=nsections; i++)
|
|
for(j=0; j<d; j++)
|
|
x2.Set(i,j,x.Get(idx2[i],j));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Builds non-periodic parameterization for 2-dimensional spline |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline2Par(CMatrixDouble &xy,const int n,const int pt,
|
|
double &p[])
|
|
{
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- Build parameterization:
|
|
//--- * fill by non-normalized values
|
|
//--- * normalize them so we have P[0]=0,P[N-1]=1.
|
|
ArrayResize(p,n);
|
|
//--- check
|
|
if(pt==0)
|
|
{
|
|
for(int i=0; i<n; i++)
|
|
p[i]=i;
|
|
}
|
|
//--- check
|
|
if(pt==1)
|
|
{
|
|
p[0]=0;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
p[i]=p[i-1]+CApServ::SafePythag2(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
|
|
}
|
|
//--- check
|
|
if(pt==2)
|
|
{
|
|
p[0]=0;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
p[i]=p[i-1]+MathSqrt(CApServ::SafePythag2(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]));
|
|
}
|
|
//--- change value
|
|
v=1/p[n-1];
|
|
//--- calculation
|
|
for(int i_=0; i_<n; i_++)
|
|
p[i_]=v*p[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Builds non-periodic parameterization for 3-dimensional spline |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::PSpline3Par(CMatrixDouble &xy,const int n,const int pt,
|
|
double &p[])
|
|
{
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(pt>=0 && pt<=2,__FUNCTION__+": internal error!"))
|
|
return;
|
|
//--- Build parameterization:
|
|
//--- * fill by non-normalized values
|
|
//--- * normalize them so we have P[0]=0,P[N-1]=1.
|
|
ArrayResize(p,n);
|
|
//--- check
|
|
if(pt==0)
|
|
{
|
|
for(int i=0; i<n; i++)
|
|
p[i]=i;
|
|
}
|
|
//--- check
|
|
if(pt==1)
|
|
{
|
|
p[0]=0;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
p[i]=p[i-1]+CApServ::SafePythag3(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1],xy[i][2]-xy[i-1][2]);
|
|
}
|
|
//--- check
|
|
if(pt==2)
|
|
{
|
|
p[0]=0;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
p[i]=p[i-1]+MathSqrt(CApServ::SafePythag3(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1],xy[i][2]-xy[i-1][2]));
|
|
}
|
|
//--- change value
|
|
v=1/p[n-1];
|
|
//--- calculation
|
|
for(int i_=0; i_<n; i_++)
|
|
p[i_]=v*p[i_];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function analyzes section of curve for processing by RDP |
|
|
//| algorithm: given set of points X,Y with indexes [I0,I1] it |
|
|
//| returns point with worst deviation from linear model (PARAMETRIC |
|
|
//| version which sees curve as X(t) with vector X). |
|
|
//| Input parameters: |
|
|
//| XY - array |
|
|
//| I0,I1 - interval (boundaries included) to process |
|
|
//| D - number of dimensions |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| WorstIdx - index of worst point |
|
|
//| WorstError - error at worst point |
|
|
//| NOTE: this function guarantees that it returns exactly zero for |
|
|
//| a section with less than 3 points. |
|
|
//+------------------------------------------------------------------+
|
|
void CPSpline::RDPAnalyzeSectionPar(CMatrixDouble &xy,
|
|
int i0,
|
|
int i1,
|
|
int d,
|
|
int &worstidx,
|
|
double &worsterror)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double d2=0;
|
|
double ts=0;
|
|
double vv=0;
|
|
|
|
worstidx=0;
|
|
worsterror=0;
|
|
//--- Quick exit for 0, 1, 2 points
|
|
if(i1-i0+1<3)
|
|
{
|
|
worstidx=i0;
|
|
worsterror=0.0;
|
|
return;
|
|
}
|
|
//--- Estimate D2 - squared distance between XY[I1] and XY[I0].
|
|
//--- In case D2=0 handle it as special case.
|
|
d2=0.0;
|
|
for(int j=0; j<d; j++)
|
|
d2+=CMath::Sqr(xy.Get(i1,j)-xy.Get(i0,j));
|
|
if(d2==0.0)
|
|
{
|
|
//--- First and last points are equal, interval evaluation is
|
|
//--- trivial - we just calculate distance from all points to
|
|
//--- the first/last one.
|
|
worstidx=i0;
|
|
worsterror=0.0;
|
|
for(int i=i0+1; i<i1; i++)
|
|
{
|
|
vv=0.0;
|
|
for(int j=0; j<d ; j++)
|
|
{
|
|
v=xy.Get(i,j)-xy.Get(i0,j);
|
|
vv+=v*v;
|
|
}
|
|
vv=MathSqrt(vv);
|
|
if(vv>worsterror)
|
|
{
|
|
worsterror=vv;
|
|
worstidx=i;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- General case
|
|
//--- Current section of curve is modeled as x(t) = d*t+c, where
|
|
//--- d = XY[I1]-XY[I0]
|
|
//--- c = XY[I0]
|
|
//--- t is in [0,1]
|
|
worstidx=i0;
|
|
worsterror=0.0;
|
|
for(int i=i0+1; i<i1; i++)
|
|
{
|
|
//--- Determine t_s - parameter value for projected point.
|
|
ts=(double)(i-i0)/(double)(i1-i0);
|
|
//--- Estimate error norm
|
|
vv=0.0;
|
|
for(int j=0; j<d; j++)
|
|
{
|
|
v=(xy.Get(i1,j)-xy.Get(i0,j))*ts-(xy.Get(i,j)-xy.Get(i0,j));
|
|
vv=vv+CMath::Sqr(v);
|
|
}
|
|
vv=MathSqrt(vv);
|
|
if(vv>worsterror)
|
|
{
|
|
worsterror=vv;
|
|
worstidx=i;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 2-dimensional spline inteprolant |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline2DInterpolant
|
|
{
|
|
public:
|
|
//--- variable
|
|
int m_d;
|
|
int m_m;
|
|
int m_n;
|
|
int m_stype;
|
|
//--- array
|
|
CRowDouble m_f;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
//--- constructor, destructor
|
|
CSpline2DInterpolant(void);
|
|
~CSpline2DInterpolant(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline2DInterpolant&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DInterpolant&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSpline2DInterpolant::CSpline2DInterpolant(void)
|
|
{
|
|
m_d=0;
|
|
m_m=0;
|
|
m_n=0;
|
|
m_stype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DInterpolant::Copy(const CSpline2DInterpolant &obj)
|
|
{
|
|
//--- copy variable
|
|
m_d=obj.m_d;
|
|
m_m=obj.m_m;
|
|
m_n=obj.m_n;
|
|
m_stype=obj.m_stype;
|
|
//--- copy array
|
|
m_f=obj.m_f;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 2-dimensional spline inteprolant |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline2DInterpolantShell
|
|
{
|
|
private:
|
|
CSpline2DInterpolant m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CSpline2DInterpolantShell(void) {}
|
|
CSpline2DInterpolantShell(CSpline2DInterpolant&obj) { m_innerobj.Copy(obj); }
|
|
~CSpline2DInterpolantShell(void) {}
|
|
//--- method
|
|
CSpline2DInterpolant *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Nonlinear least squares solver used to fit 2D splines to data |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline2DBuilder
|
|
{
|
|
public:
|
|
int m_areatype;
|
|
int m_d;
|
|
int m_gridtype;
|
|
int m_interfacesize;
|
|
int m_kx;
|
|
int m_ky;
|
|
int m_lsqrcnt;
|
|
int m_maxcoresize;
|
|
int m_nlayers;
|
|
int m_npoints;
|
|
int m_priorterm;
|
|
int m_solvertype;
|
|
double m_lambdabase;
|
|
double m_priortermval;
|
|
double m_smoothing;
|
|
double m_sx;
|
|
double m_sy;
|
|
double m_xa;
|
|
double m_xb;
|
|
double m_ya;
|
|
double m_yb;
|
|
bool m_adddegreeoffreedom;
|
|
CRowDouble m_xy;
|
|
//--- constructor / destructor
|
|
CSpline2DBuilder(void);
|
|
~CSpline2DBuilder(void) {}
|
|
//---
|
|
void Copy(const CSpline2DBuilder&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DBuilder&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSpline2DBuilder::CSpline2DBuilder(void)
|
|
{
|
|
m_areatype=0;
|
|
m_d=0;
|
|
m_gridtype=0;
|
|
m_interfacesize=0;
|
|
m_kx=0;
|
|
m_ky=0;
|
|
m_lsqrcnt=0;
|
|
m_maxcoresize=0;
|
|
m_nlayers=0;
|
|
m_npoints=0;
|
|
m_priorterm=0;
|
|
m_solvertype=0;
|
|
m_lambdabase=0;
|
|
m_priortermval=0;
|
|
m_smoothing=0;
|
|
m_sx=0;
|
|
m_sy=0;
|
|
m_xa=0;
|
|
m_xb=0;
|
|
m_ya=0;
|
|
m_yb=0;
|
|
m_adddegreeoffreedom=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DBuilder::Copy(const CSpline2DBuilder &obj)
|
|
{
|
|
m_areatype=obj.m_areatype;
|
|
m_d=obj.m_d;
|
|
m_gridtype=obj.m_gridtype;
|
|
m_interfacesize=obj.m_interfacesize;
|
|
m_kx=obj.m_kx;
|
|
m_ky=obj.m_ky;
|
|
m_lsqrcnt=obj.m_lsqrcnt;
|
|
m_maxcoresize=obj.m_maxcoresize;
|
|
m_nlayers=obj.m_nlayers;
|
|
m_npoints=obj.m_npoints;
|
|
m_priorterm=obj.m_priorterm;
|
|
m_solvertype=obj.m_solvertype;
|
|
m_lambdabase=obj.m_lambdabase;
|
|
m_priortermval=obj.m_priortermval;
|
|
m_smoothing=obj.m_smoothing;
|
|
m_sx=obj.m_sx;
|
|
m_sy=obj.m_sy;
|
|
m_xa=obj.m_xa;
|
|
m_xb=obj.m_xb;
|
|
m_ya=obj.m_ya;
|
|
m_yb=obj.m_yb;
|
|
m_adddegreeoffreedom=obj.m_adddegreeoffreedom;
|
|
m_xy=obj.m_xy;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Spline 2D fitting report: |
|
|
//| rmserror RMS error |
|
|
//| avgerror average error |
|
|
//| maxerror maximum error |
|
|
//| r2 coefficient of determination, R-squared, 1-RSS/TSS/
|
|
//+------------------------------------------------------------------+
|
|
struct CSpline2DFitReport
|
|
{
|
|
double m_avgerror;
|
|
double m_maxerror;
|
|
double m_r2;
|
|
double m_rmserror;
|
|
//--- constructor / destructor
|
|
CSpline2DFitReport(void) { ZeroMemory(this); }
|
|
~CSpline2DFitReport(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline2DFitReport&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DFitReport&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DFitReport::Copy(const CSpline2DFitReport &obj)
|
|
{
|
|
m_avgerror=obj.m_avgerror;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_r2=obj.m_r2;
|
|
m_rmserror=obj.m_rmserror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Design matrix stored in batch/block sparse format. |
|
|
//| The idea is that design matrix for bicubic spline fitting has |
|
|
//| very regular structure: |
|
|
//| 1. I-th row has non-zero entries in elements with indexes |
|
|
//| starting from some IDX, and including: IDX, IDX+1, IDX+2, |
|
|
//| IDX+3, IDX+KX+0, IDX+KX+1, and so on, up to 16 elements in |
|
|
//| total. |
|
|
//| Rows corresponding to dataset points have 16 non-zero |
|
|
//| elements, rows corresponding to nonlinearity penalty have 9 |
|
|
//| non-zero elements, and rows of regularizer have 1 element. |
|
|
//| For the sake of simplicity, we can use 16 elements for dataset|
|
|
//| rows and penalty rows, and process regularizer explicitly. |
|
|
//| 2. points located in the same cell of the grid have same pattern |
|
|
//| of non-zeros, so we can use dense Level 2 and Level 3 linear |
|
|
//| algebra to work with such matrices. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSpline2DXDesignMatrix
|
|
{
|
|
int m_blockwidth;
|
|
int m_d;
|
|
int m_kx;
|
|
int m_ky;
|
|
int m_maxbatch;
|
|
int m_ndensebatches;
|
|
int m_ndenserows;
|
|
int m_npoints;
|
|
int m_nrows;
|
|
double m_lambdareg;
|
|
CRowInt m_batchbases;
|
|
CRowInt m_batches;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CMatrixDouble m_tmp2;
|
|
CMatrixDouble m_vals;
|
|
//--- constructor / destructor
|
|
CSpline2DXDesignMatrix(void);
|
|
~CSpline2DXDesignMatrix(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline2DXDesignMatrix&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DXDesignMatrix&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSpline2DXDesignMatrix::CSpline2DXDesignMatrix(void)
|
|
{
|
|
m_blockwidth=0;
|
|
m_d=0;
|
|
m_kx=0;
|
|
m_ky=0;
|
|
m_maxbatch=0;
|
|
m_ndensebatches=0;
|
|
m_ndenserows=0;
|
|
m_npoints=0;
|
|
m_nrows=0;
|
|
m_lambdareg=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DXDesignMatrix::Copy(const CSpline2DXDesignMatrix &obj)
|
|
{
|
|
m_blockwidth=obj.m_blockwidth;
|
|
m_d=obj.m_d;
|
|
m_kx=obj.m_kx;
|
|
m_ky=obj.m_ky;
|
|
m_maxbatch=obj.m_maxbatch;
|
|
m_ndensebatches=obj.m_ndensebatches;
|
|
m_ndenserows=obj.m_ndenserows;
|
|
m_npoints=obj.m_npoints;
|
|
m_nrows=obj.m_nrows;
|
|
m_lambdareg=obj.m_lambdareg;
|
|
m_batchbases=obj.m_batchbases;
|
|
m_batches=obj.m_batches;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_tmp2=obj.m_tmp2;
|
|
m_vals=obj.m_vals;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Temporaries for BlockLLS solver |
|
|
//+------------------------------------------------------------------+
|
|
struct CSpline2DBlockLLSBuf
|
|
{
|
|
CRowDouble m_cholbuf1;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmp1;
|
|
CMatrixDouble m_blockata;
|
|
CMatrixDouble m_cholbuf2;
|
|
CMatrixDouble m_trsmbuf2;
|
|
CLinLSQRState m_solver;
|
|
CLinLSQRReport m_solverrep;
|
|
//--- constructor / destructor
|
|
CSpline2DBlockLLSBuf(void) {}
|
|
~CSpline2DBlockLLSBuf(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline2DBlockLLSBuf&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DBlockLLSBuf&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DBlockLLSBuf::Copy(const CSpline2DBlockLLSBuf &obj)
|
|
{
|
|
m_cholbuf1=obj.m_cholbuf1;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmp1=obj.m_tmp1;
|
|
m_blockata=obj.m_blockata;
|
|
m_cholbuf2=obj.m_cholbuf2;
|
|
m_trsmbuf2=obj.m_trsmbuf2;
|
|
m_solver=obj.m_solver;
|
|
m_solverrep=obj.m_solverrep;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Temporaries for FastDDM solver |
|
|
//+------------------------------------------------------------------+
|
|
struct CSpline2DFastDDMBuf
|
|
{
|
|
CSpline2DXDesignMatrix m_xdesignmatrix;
|
|
CSpline2DInterpolant m_localmodel;
|
|
CSpline2DFitReport m_dummyrep;
|
|
CSpline2DBlockLLSBuf m_blockllsbuf;
|
|
CRowDouble m_tmp0;
|
|
CRowDouble m_tmpz;
|
|
//--- constructor / destructor
|
|
CSpline2DFastDDMBuf(void) {}
|
|
~CSpline2DFastDDMBuf(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline2DFastDDMBuf&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline2DFastDDMBuf&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2DFastDDMBuf::Copy(const CSpline2DFastDDMBuf &obj)
|
|
{
|
|
m_xdesignmatrix=obj.m_xdesignmatrix;
|
|
m_localmodel=obj.m_localmodel;
|
|
m_dummyrep=obj.m_dummyrep;
|
|
m_blockllsbuf=obj.m_blockllsbuf;
|
|
m_tmp0=obj.m_tmp0;
|
|
m_tmpz=obj.m_tmpz;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 2-dimensional spline interpolation |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline2D
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_cholreg;
|
|
static const double m_lambdaregblocklls;
|
|
static const double m_lambdaregfastddm;
|
|
static const double m_lambdadecay;
|
|
//--- public methods
|
|
static void Spline2DBuildBilinear(double &cx[],double &cy[],CMatrixDouble&cf,const int m,const int n,CSpline2DInterpolant&c);
|
|
static void Spline2DBuildBicubic(double &cx[],double &cy[],CMatrixDouble&cf,const int m,const int n,CSpline2DInterpolant&c);
|
|
static double Spline2DCalc(CSpline2DInterpolant&c,const double x,const double y);
|
|
static void Spline2DDiff(CSpline2DInterpolant&c,const double x,const double y,double&f,double&fx,double&fy,double&fxy);
|
|
static void Spline2DCalcVBuf(CSpline2DInterpolant&c,double x,double y,CRowDouble&f);
|
|
static double Spline2DCalcVi(CSpline2DInterpolant&c,double x,double y,int i);
|
|
static void Spline2DCalcV(CSpline2DInterpolant&c,double x,double y,CRowDouble&f);
|
|
static void Spline2DDiffVi(CSpline2DInterpolant&c,double x,double y,int i,double&f,double&fx,double&fy,double&fxy);
|
|
static void Spline2DUnpack(CSpline2DInterpolant&c,int &m,int &n,CMatrixDouble&tbl);
|
|
static void Spline2DLinTransXY(CSpline2DInterpolant&c,double ax,double bx,double ay,double by);
|
|
static void Spline2DLinTransF(CSpline2DInterpolant&c,const double a,const double b);
|
|
static void Spline2DCopy(CSpline2DInterpolant&c,CSpline2DInterpolant&cc);
|
|
static void Spline2DResampleBicubic(CMatrixDouble&a,const int oldheight,const int oldwidth,CMatrixDouble&b,const int newheight,const int newwidth);
|
|
static void Spline2DResampleBilinear(CMatrixDouble&a,const int oldheight,const int oldwidth,CMatrixDouble&b,const int newheight,const int newwidth);
|
|
static void Spline2DBuildBilinearV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&f,int d,CSpline2DInterpolant&c);
|
|
static void Spline2DBuildBicubicV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&f,int d,CSpline2DInterpolant&c);
|
|
static void Spline2DUnpackV(CSpline2DInterpolant&c,int &m,int &n,int &d,CMatrixDouble&tbl);
|
|
static void Spline2DBuilderCreate(int d,CSpline2DBuilder&State);
|
|
static void Spline2DBuilderSetUserTerm(CSpline2DBuilder&State,double v);
|
|
static void Spline2DBuilderSetLinTerm(CSpline2DBuilder&State);
|
|
static void Spline2DBuilderSetConstTerm(CSpline2DBuilder&State);
|
|
static void Spline2DBuilderSetZeroTerm(CSpline2DBuilder&State);
|
|
static void Spline2DBuilderSetPoints(CSpline2DBuilder&State,CMatrixDouble&xy,int n);
|
|
static void Spline2DBuilderSetAreaAuto(CSpline2DBuilder&State);
|
|
static void Spline2DBuilderSetArea(CSpline2DBuilder&State,double xa,double xb,double ya,double yb);
|
|
static void Spline2DBuilderSetGrid(CSpline2DBuilder&State,int kx,int ky);
|
|
static void Spline2DBuilderSetAlgoFastDDM(CSpline2DBuilder&State,int nlayers,double lambdav);
|
|
static void Spline2DBuilderSetAlgoBlockLLS(CSpline2DBuilder&State,double lambdans);
|
|
static void Spline2DBuilderSetAlgoNaiveLLS(CSpline2DBuilder&State,double lambdans);
|
|
static void Spline2DFit(CSpline2DBuilder&State,CSpline2DInterpolant&s,CSpline2DFitReport&rep);
|
|
static void Spline2DAlloc(CSerializer&s,CSpline2DInterpolant&spline);
|
|
static void Spline2DSerialize(CSerializer&s,CSpline2DInterpolant&spline);
|
|
static void Spline2DUnserialize(CSerializer&s,CSpline2DInterpolant&spline);
|
|
|
|
private:
|
|
static void BicubicCalcDerivatives(CMatrixDouble&a,CRowDouble&x,CRowDouble&y,const int m,const int n,CMatrixDouble&dx,CMatrixDouble&dy,CMatrixDouble&dxy);
|
|
static void GenerateDesignMatrix(CRowDouble&xy,int npoints,int d,int kx,int ky,double smoothing,double lambdareg,CSpline1DInterpolant&basis1,CSparseMatrix&av,CSparseMatrix&ah,int &arows);
|
|
static void UpdateSplineTable(CRowDouble&z,int kx,int ky,int d,CSpline1DInterpolant&basis1,int bfrad,CRowDouble&ftbl,int m,int n,int scalexy);
|
|
static void FastDDMFit(CRowDouble&xy,int npoints,int d,int kx,int ky,int basecasex,int basecasey,int maxcoresize,int interfacesize,int nlayers,double smoothing,int lsqrcnt,CSpline1DInterpolant&basis1,CSpline2DInterpolant&spline,CSpline2DFitReport&rep,double tss);
|
|
static void FastDDMFitLayer(CRowDouble&xy,int d,int scalexy,CRowInt&xyindex,int basecasex,int tilex0,int tilex1,int tilescountx,int basecasey,int tiley0,int tiley1,int tilescounty,int maxcoresize,int interfacesize,int lsqrcnt,double lambdareg,CSpline1DInterpolant&basis1,CSpline2DFastDDMBuf&pool,CSpline2DInterpolant&spline);
|
|
static void BlockLLSFit(CSpline2DXDesignMatrix&xdesign,int lsqrcnt,CRowDouble&z,CSpline2DFitReport&rep,double tss,CSpline2DBlockLLSBuf&buf);
|
|
static void NaiveLLSFit(CSparseMatrix&av,CSparseMatrix&ah,int arows,CRowDouble&xy,int kx,int ky,int npoints,int d,int lsqrcnt,CRowDouble&z,CSpline2DFitReport&rep,double tss);
|
|
static int GetCellOffset(int kx,int ky,int blockbandwidth,int i,int j);
|
|
static void CopyCellTo(int kx,int ky,int blockbandwidth,CMatrixDouble&blockata,int i,int j,CMatrixDouble&dst,int dst0,int dst1);
|
|
static void FlushToZeroCell(int kx,int ky,int blockbandwidth,CMatrixDouble&blockata,int i,int j,double eps);
|
|
static void BlockLLSGenerateATA(CSparseMatrix&ah,int ky0,int ky1,int kx,int ky,CMatrixDouble&blockata,double mxata);
|
|
static bool BlockLLSCholesky(CMatrixDouble&blockata,int kx,int ky,CMatrixDouble&trsmbuf2,CMatrixDouble&cholbuf2,CRowDouble&cholbuf1);
|
|
static void BlockLLSTrsV(CMatrixDouble&blockata,int kx,int ky,bool transu,CRowDouble&b);
|
|
static void ComputeResidualsFromScratch(CRowDouble&xy,CRowDouble&yraw,int npoints,int d,int scalexy,CSpline2DInterpolant&spline);
|
|
static void ComputeResidualsFromScratchRec(CRowDouble&xy,CRowDouble&yraw,int pt0,int pt1,int chunksize,int d,int scalexy,CSpline2DInterpolant&spline,CRowDouble&pool);
|
|
static void ReorderDatasetAndBuildIndex(CRowDouble&xy,int npoints,int d,CRowDouble&shadow,int ns,int kx,int ky,CRowInt&xyindex,CRowInt&bufi);
|
|
static void RescaleDatasetAndRefineIndex(CRowDouble&xy,int npoints,int d,CRowDouble&shadow,int ns,int kx,int ky,CRowInt&xyindex,CRowInt&bufi);
|
|
static void ExpandIndexRows(CRowDouble&xy,int d,CRowDouble&shadow,int ns,CRowInt&cidx,int pt0,int pt1,CRowInt&xyindexprev,int row0,int row1,CRowInt&xyindexnew,int kxnew,int kynew,bool rootcall);
|
|
static void ReorderDatasetAndBuildIndexRec(CRowDouble&xy,int d,CRowDouble&shadow,int ns,CRowInt&cidx,int pt0,int pt1,CRowInt&xyindex,int idx0,int idx1,bool rootcall);
|
|
static void XDesignGenerate(CRowDouble&xy,CRowInt&xyindex,int kx0,int kx1,int kxtotal,int ky0,int ky1,int kytotal,int d,double lambdareg,double lambdans,CSpline1DInterpolant&basis1,CSpline2DXDesignMatrix&a);
|
|
static void XDesignMV(CSpline2DXDesignMatrix&a,CRowDouble&x,CRowDouble&y);
|
|
static void XDesignMTV(CSpline2DXDesignMatrix&a,CRowDouble&x,CRowDouble&y);
|
|
static void XDesignBlockATA(CSpline2DXDesignMatrix&a,CMatrixDouble&blockata,double&mxata);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CSpline2D::m_cholreg=1.0E-12;
|
|
const double CSpline2D::m_lambdaregblocklls=1.0E-6;
|
|
const double CSpline2D::m_lambdaregfastddm=1.0E-4;
|
|
const double CSpline2D::m_lambdadecay=0.5;
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds bilinear spline coefficients table. |
|
|
//| Input parameters: |
|
|
//| X - spline abscissas, array[0..N-1] |
|
|
//| Y - spline ordinates, array[0..M-1] |
|
|
//| F - function values, array[0..M-1,0..N-1] |
|
|
//| M,N - grid size, M>=2, N>=2 |
|
|
//| Output parameters: |
|
|
//| C - spline interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuildBilinear(double &x[],double &y[],
|
|
CMatrixDouble &f,const int m,
|
|
const int n,CSpline2DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
double t=0;
|
|
int k=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
|
|
return;
|
|
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(f)>=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)<M or cols(F)<N)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(f,m,n),__FUNCTION__+": F contains NaN or Infinite value"))
|
|
return;
|
|
//---Fill interpolant
|
|
c.m_n=n;
|
|
c.m_m=m;
|
|
c.m_d=1;
|
|
c.m_stype=-1;
|
|
c.m_x=x;
|
|
c.m_y=y;
|
|
c.m_x.Resize(c.m_n);
|
|
c.m_y.Resize(c.m_m);
|
|
c.m_f.Resize(c.m_n*c.m_m);
|
|
for(int i=0; i<c.m_m; i++)
|
|
for(int j=0; j<c.m_n; j++)
|
|
c.m_f.Set(i*c.m_n+j,f.Get(i,j));
|
|
//---Sort points
|
|
for(int j=0; j<c.m_n; j++)
|
|
{
|
|
k=j;
|
|
for(int i=j+1; i<c.m_n; i++)
|
|
{
|
|
if(c.m_x[i]<c.m_x[k])
|
|
k=i;
|
|
}
|
|
if(k!=j)
|
|
{
|
|
for(int i=0; i<c.m_m; i++)
|
|
c.m_f.Swap(i*c.m_n+j,i*c.m_n+k);
|
|
c.m_x.Swap(j,k);
|
|
}
|
|
}
|
|
for(int i=0; i<c.m_m; i++)
|
|
{
|
|
k=i;
|
|
for(int j=i+1; j<c.m_m; j++)
|
|
{
|
|
if(c.m_y[j]<c.m_y[k])
|
|
k=j;
|
|
}
|
|
if(k!=i)
|
|
{
|
|
for(int j=0; j<=c.m_n-1; j++)
|
|
c.m_f.Swap(i*c.m_n+j,k*c.m_n+j);
|
|
c.m_y.Swap(i,k);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds bicubic spline coefficients table. |
|
|
//| Input parameters: |
|
|
//| X - spline abscissas, array[0..N-1] |
|
|
//| Y - spline ordinates, array[0..M-1] |
|
|
//| F - function values, array[0..M-1,0..N-1] |
|
|
//| M,N - grid size, M>=2, N>=2 |
|
|
//| Output parameters: |
|
|
//| C - spline interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuildBicubic(double &cx[],double &cy[],
|
|
CMatrixDouble &cf,const int m,
|
|
const int n,CSpline2DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
CMatrixDouble dx;
|
|
CMatrixDouble dy;
|
|
CMatrixDouble dxy;
|
|
double t=0;
|
|
int k=0;
|
|
//--- copy
|
|
CMatrixDouble f=cf;
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
|
|
return;
|
|
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(cx)>=n && CAp::Len(cy)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(cx,n) && CApServ::IsFiniteVector(cy,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(f)>=m && CAp::Cols(f)>=n,__FUNCTION__+": size of F is too small (rows(F)<M or cols(F)<N)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(f,m,n),__FUNCTION__+": F contains NaN or Infinite value"))
|
|
return;
|
|
//---Fill interpolant:
|
|
//--- F[0]...F[N*M-1]:
|
|
//--- f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
|
|
//--- F[N*M]...F[2*N*M-1]:
|
|
//--- df(i,j)/dx table.
|
|
//--- F[2*N*M]...F[3*N*M-1]:
|
|
//--- df(i,j)/dy table.
|
|
//--- F[3*N*M]...F[4*N*M-1]:
|
|
//--- d2f(i,j)/dxdy table.
|
|
c.m_d=1;
|
|
c.m_n=n;
|
|
c.m_m=m;
|
|
c.m_stype=-3;
|
|
sfx=c.m_n*c.m_m;
|
|
sfy=2*c.m_n*c.m_m;
|
|
sfxy=3*c.m_n*c.m_m;
|
|
c.m_x=cx;
|
|
c.m_y=cy;
|
|
c.m_x.Resize(c.m_n);
|
|
c.m_y.Resize(c.m_m);
|
|
c.m_f.Resize(4*c.m_n*c.m_m);
|
|
//---Sort points
|
|
for(int j=0; j<c.m_n; j++)
|
|
{
|
|
k=j;
|
|
for(int i=j+1; i<c.m_n; i++)
|
|
if(c.m_x[i]<c.m_x[k])
|
|
k=i;
|
|
if(k!=j)
|
|
{
|
|
f.SwapCols(j,k);
|
|
c.m_x.Swap(j,k);
|
|
}
|
|
}
|
|
for(int i=0; i<c.m_m; i++)
|
|
{
|
|
k=i;
|
|
for(int j=i+1; j<c.m_m; j++)
|
|
if(c.m_y[j]<c.m_y[k])
|
|
k=j;
|
|
if(k!=i)
|
|
{
|
|
f.SwapRows(i,k);
|
|
c.m_y.Swap(i,k);
|
|
}
|
|
}
|
|
BicubicCalcDerivatives(f,c.m_x,c.m_y,c.m_m,c.m_n,dx,dy,dxy);
|
|
for(int i=0; i<c.m_m; i++)
|
|
for(int j=0; j<c.m_n; j++)
|
|
{
|
|
k=i*c.m_n+j;
|
|
c.m_f.Set(k,f.Get(i,j));
|
|
c.m_f.Set(sfx+k,dx.Get(i,j));
|
|
c.m_f.Set(sfy+k,dy.Get(i,j));
|
|
c.m_f.Set(sfxy+k,dxy.Get(i,j));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates the value of the bilinear or bicubic |
|
|
//| spline at the given point X. |
|
|
//| Input parameters: |
|
|
//| C - coefficients table. |
|
|
//| Built by BuildBilinearSpline or BuildBicubicSpline. |
|
|
//| X, Y- point |
|
|
//| Result: |
|
|
//| S(x,y) |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline2D::Spline2DCalc(CSpline2DInterpolant &c,const double x,
|
|
const double y)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
double t=0;
|
|
double dt=0;
|
|
double u=0;
|
|
double du=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double t2=0;
|
|
double t3=0;
|
|
double u2=0;
|
|
double u3=0;
|
|
double ht00=0;
|
|
double ht01=0;
|
|
double ht10=0;
|
|
double ht11=0;
|
|
double hu00=0;
|
|
double hu01=0;
|
|
double hu10=0;
|
|
double hu11=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return(0);
|
|
if(c.m_d!=1)
|
|
{
|
|
result=0;
|
|
return(result);
|
|
}
|
|
//--- Determine evaluation interval
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
|
|
t=(x-c.m_x[l])*dt;
|
|
ix=l;
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
du=1.0/(c.m_y[l+1]-c.m_y[l]);
|
|
u=(y-c.m_y[l])*du;
|
|
iy=l;
|
|
//--- Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
y1=c.m_f[c.m_n*iy+ix];
|
|
y2=c.m_f[c.m_n*iy+(ix+1)];
|
|
y3=c.m_f[c.m_n*(iy+1)+(ix+1)];
|
|
y4=c.m_f[c.m_n*(iy+1)+ix];
|
|
result=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- Bicubic interpolation:
|
|
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
|
|
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
|
|
//--- Same for HUij.
|
|
//--- * after initial calculation, apply scaling by DT/DU to the basis
|
|
//--- * calculate using stored table of second derivatives
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
sfx=c.m_n*c.m_m;
|
|
sfy=2*c.m_n*c.m_m;
|
|
sfxy=3*c.m_n*c.m_m;
|
|
s1=c.m_n*iy+ix;
|
|
s2=c.m_n*iy+(ix+1);
|
|
s3=c.m_n*(iy+1)+ix;
|
|
s4=c.m_n*(iy+1)+(ix+1);
|
|
t2=t*t;
|
|
t3=t*t2;
|
|
u2=u*u;
|
|
u3=u*u2;
|
|
ht00=2*t3-3*t2+1;
|
|
ht10=t3-2*t2+t;
|
|
ht01=-(2*t3)+3*t2;
|
|
ht11=t3-t2;
|
|
hu00=2*u3-3*u2+1;
|
|
hu10=u3-2*u2+u;
|
|
hu01=-(2*u3)+3*u2;
|
|
hu11=u3-u2;
|
|
ht10=ht10/dt;
|
|
ht11=ht11/dt;
|
|
hu10=hu10/du;
|
|
hu11=hu11/du;
|
|
result=c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01;
|
|
result+=c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01;
|
|
result+=c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11;
|
|
result+=c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates the value of the bilinear or bicubic |
|
|
//| spline at the given point X and its derivatives. |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y- point |
|
|
//| Output parameters: |
|
|
//| F - S(x,y) |
|
|
//| FX - dS(x,y)/dX |
|
|
//| FY - dS(x,y)/dY |
|
|
//| FXY - d2S(x,y)/dXdY |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DDiff(CSpline2DInterpolant &c,const double x,
|
|
const double y,double &f,double &fx,
|
|
double &fy,double &fxy)
|
|
{
|
|
//--- create variables
|
|
double t=0;
|
|
double dt=0;
|
|
double u=0;
|
|
double du=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double v3=0;
|
|
double t2=0;
|
|
double t3=0;
|
|
double u2=0;
|
|
double u3=0;
|
|
double ht00=0;
|
|
double ht01=0;
|
|
double ht10=0;
|
|
double ht11=0;
|
|
double hu00=0;
|
|
double hu01=0;
|
|
double hu10=0;
|
|
double hu11=0;
|
|
double dht00=0;
|
|
double dht01=0;
|
|
double dht10=0;
|
|
double dht11=0;
|
|
double dhu00=0;
|
|
double dhu01=0;
|
|
double dhu10=0;
|
|
double dhu11=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
//--- Prepare F, dF/dX, dF/dY, d2F/dXdY
|
|
f=0;
|
|
fx=0;
|
|
fy=0;
|
|
fxy=0;
|
|
if(c.m_d!=1)
|
|
return;
|
|
//--- Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
t=(x-c.m_x[l])/(c.m_x[l+1]-c.m_x[l]);
|
|
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
|
|
ix=l;
|
|
//--- Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
u=(y-c.m_y[l])/(c.m_y[l+1]-c.m_y[l]);
|
|
du=1.0/(c.m_y[l+1]-c.m_y[l]);
|
|
iy=l;
|
|
//--- Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
y1=c.m_f[c.m_n*iy+ix];
|
|
y2=c.m_f[c.m_n*iy+(ix+1)];
|
|
y3=c.m_f[c.m_n*(iy+1)+(ix+1)];
|
|
y4=c.m_f[c.m_n*(iy+1)+ix];
|
|
f=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
|
|
fx=(-((1-u)*y1)+(1-u)*y2+u*y3-u*y4)*dt;
|
|
fy=(-((1-t)*y1)-t*y2+t*y3+(1-t)*y4)*du;
|
|
fxy=(y1-y2+y3-y4)*du*dt;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- Bicubic interpolation
|
|
if(c.m_stype==-3)
|
|
{
|
|
sfx=c.m_n*c.m_m;
|
|
sfy=2*c.m_n*c.m_m;
|
|
sfxy=3*c.m_n*c.m_m;
|
|
s1=c.m_n*iy+ix;
|
|
s2=c.m_n*iy+(ix+1);
|
|
s3=c.m_n*(iy+1)+ix;
|
|
s4=c.m_n*(iy+1)+(ix+1);
|
|
t2=t*t;
|
|
t3=t*t2;
|
|
u2=u*u;
|
|
u3=u*u2;
|
|
ht00=2*t3-3*t2+1;
|
|
ht10=t3-2*t2+t;
|
|
ht01=-(2*t3)+3*t2;
|
|
ht11=t3-t2;
|
|
hu00=2*u3-3*u2+1;
|
|
hu10=u3-2*u2+u;
|
|
hu01=-(2*u3)+3*u2;
|
|
hu11=u3-u2;
|
|
ht10=ht10/dt;
|
|
ht11=ht11/dt;
|
|
hu10=hu10/du;
|
|
hu11=hu11/du;
|
|
dht00=6*t2-6*t;
|
|
dht10=3*t2-4*t+1;
|
|
dht01=-(6*t2)+6*t;
|
|
dht11=3*t2-2*t;
|
|
dhu00=6*u2-6*u;
|
|
dhu10=3*u2-4*u+1;
|
|
dhu01=-(6*u2)+6*u;
|
|
dhu11=3*u2-2*u;
|
|
dht00=dht00*dt;
|
|
dht01=dht01*dt;
|
|
dhu00=dhu00*du;
|
|
dhu01=dhu01*du;
|
|
v0=c.m_f[s1];
|
|
v1=c.m_f[s2];
|
|
v2=c.m_f[s3];
|
|
v3=c.m_f[s4];
|
|
f=v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
|
|
fx=v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
|
|
fy=v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
|
|
fxy=v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
|
|
v0=c.m_f[sfx+s1];
|
|
v1=c.m_f[sfx+s2];
|
|
v2=c.m_f[sfx+s3];
|
|
v3=c.m_f[sfx+s4];
|
|
f+=v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
|
|
fx+=v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
|
|
fy+=v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
|
|
fxy+=v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
|
|
v0=c.m_f[sfy+s1];
|
|
v1=c.m_f[sfy+s2];
|
|
v2=c.m_f[sfy+s3];
|
|
v3=c.m_f[sfy+s4];
|
|
f+=v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
|
|
fx+=v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
|
|
fy+=v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
|
|
fxy+=v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
|
|
v0=c.m_f[sfxy+s1];
|
|
v1=c.m_f[sfxy+s2];
|
|
v2=c.m_f[sfxy+s3];
|
|
v3=c.m_f[sfxy+s4];
|
|
f+=v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
|
|
fx+=v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
|
|
fy+=v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
|
|
fxy+=v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates bilinear or bicubic vector-valued |
|
|
//| spline at the given point (X,Y). |
|
|
//| If you need just some specific component of vector-valued spline,|
|
|
//| you can use Spline2DCalcVi() function. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y - point |
|
|
//| F - output buffer, possibly preallocated array. In case|
|
|
//| array size is large enough to store result, it is |
|
|
//| not reallocated. Array which is too short will be |
|
|
//| reallocated |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - array[D] (or larger) which stores function values |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DCalcVBuf(CSpline2DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
CRowDouble &f)
|
|
{
|
|
//--- create variables
|
|
int ix=0;
|
|
int iy=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
double t=0;
|
|
double dt=0;
|
|
double u=0;
|
|
double du=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double t2=0;
|
|
double t3=0;
|
|
double u2=0;
|
|
double u3=0;
|
|
double ht00=0;
|
|
double ht01=0;
|
|
double ht10=0;
|
|
double ht11=0;
|
|
double hu00=0;
|
|
double hu01=0;
|
|
double hu10=0;
|
|
double hu11=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
//--- Allocate place for output
|
|
CApServ::RVectorSetLengthAtLeast(f,c.m_d);
|
|
//--- Determine evaluation interval
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
|
|
t=(x-c.m_x[l])*dt;
|
|
ix=l;
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
du=1.0/(c.m_y[l+1]-c.m_y[l]);
|
|
u=(y-c.m_y[l])*du;
|
|
iy=l;
|
|
//--- Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
for(int i=0; i<c.m_d; i++)
|
|
{
|
|
y1=c.m_f[c.m_d*(c.m_n*iy+ix)+i];
|
|
y2=c.m_f[c.m_d*(c.m_n*iy+(ix+1))+i];
|
|
y3=c.m_f[c.m_d*(c.m_n*(iy+1)+(ix+1))+i];
|
|
y4=c.m_f[c.m_d*(c.m_n*(iy+1)+ix)+i];
|
|
f.Set(i,(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4);
|
|
}
|
|
return;
|
|
}
|
|
//--- Bicubic interpolation:
|
|
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
|
|
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
|
|
//--- Same for HUij.
|
|
//--- * after initial calculation, apply scaling by DT/DU to the basis
|
|
//--- * calculate using stored table of second derivatives
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
sfx=c.m_n*c.m_m*c.m_d;
|
|
sfy=2*c.m_n*c.m_m*c.m_d;
|
|
sfxy=3*c.m_n*c.m_m*c.m_d;
|
|
s1=(c.m_n*iy+ix)*c.m_d;
|
|
s2=(c.m_n*iy+(ix+1))*c.m_d;
|
|
s3=(c.m_n*(iy+1)+ix)*c.m_d;
|
|
s4=(c.m_n*(iy+1)+(ix+1))*c.m_d;
|
|
t2=t*t;
|
|
t3=t*t2;
|
|
u2=u*u;
|
|
u3=u*u2;
|
|
ht00=2*t3-3*t2+1;
|
|
ht10=t3-2*t2+t;
|
|
ht01=-(2*t3)+3*t2;
|
|
ht11=t3-t2;
|
|
hu00=2*u3-3*u2+1;
|
|
hu10=u3-2*u2+u;
|
|
hu01=-(2*u3)+3*u2;
|
|
hu11=u3-u2;
|
|
ht10=ht10/dt;
|
|
ht11=ht11/dt;
|
|
hu10=hu10/du;
|
|
hu11=hu11/du;
|
|
for(int i=0; i<c.m_d; i++)
|
|
{
|
|
//--- Calculate I-th component
|
|
f.Set(i,c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01);
|
|
f.Add(i,c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01);
|
|
f.Add(i,c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11);
|
|
f.Add(i,c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11);
|
|
//--- Advance source indexes
|
|
s1=s1+1;
|
|
s2=s2+1;
|
|
s3=s3+1;
|
|
s4=s4+1;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates specific component of vector-valued |
|
|
//| bilinear or bicubic spline at the given point (X,Y). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y - point |
|
|
//| I - component index, in [0,D). An exception is |
|
|
//| generated for out of range values. |
|
|
//| RESULT: |
|
|
//| value of I-th component |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline2D::Spline2DCalcVi(CSpline2DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
int i)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
double t=0;
|
|
double dt=0;
|
|
double u=0;
|
|
double du=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double t2=0;
|
|
double t3=0;
|
|
double u2=0;
|
|
double u3=0;
|
|
double ht00=0;
|
|
double ht01=0;
|
|
double ht10=0;
|
|
double ht11=0;
|
|
double hu00=0;
|
|
double hu01=0;
|
|
double hu10=0;
|
|
double hu11=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return(0);
|
|
if(!CAp::Assert(i>=0 && i<c.m_d,__FUNCTION__+": incorrect I (I<0 or I>=D)"))
|
|
return(0);
|
|
//--- Determine evaluation interval
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
|
|
t=(x-c.m_x[l])*dt;
|
|
ix=l;
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
du=1.0/(c.m_y[l+1]-c.m_y[l]);
|
|
u=(y-c.m_y[l])*du;
|
|
iy=l;
|
|
//--- Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
y1=c.m_f[c.m_d*(c.m_n*iy+ix)+i];
|
|
y2=c.m_f[c.m_d*(c.m_n*iy+(ix+1))+i];
|
|
y3=c.m_f[c.m_d*(c.m_n*(iy+1)+(ix+1))+i];
|
|
y4=c.m_f[c.m_d*(c.m_n*(iy+1)+ix)+i];
|
|
result=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//--- Bicubic interpolation:
|
|
//--- * calculate Hermite basis for dimensions X and Y (variables T and U),
|
|
//--- here HTij means basis function whose I-th derivative has value 1 at T=J.
|
|
//--- Same for HUij.
|
|
//--- * after initial calculation, apply scaling by DT/DU to the basis
|
|
//--- * calculate using stored table of second derivatives
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3,__FUNCTION__+": integrity check failed"))
|
|
return(0);
|
|
sfx=c.m_n*c.m_m*c.m_d;
|
|
sfy=2*c.m_n*c.m_m*c.m_d;
|
|
sfxy=3*c.m_n*c.m_m*c.m_d;
|
|
s1=(c.m_n*iy+ix)*c.m_d;
|
|
s2=(c.m_n*iy+(ix+1))*c.m_d;
|
|
s3=(c.m_n*(iy+1)+ix)*c.m_d;
|
|
s4=(c.m_n*(iy+1)+(ix+1))*c.m_d;
|
|
t2=t*t;
|
|
t3=t*t2;
|
|
u2=u*u;
|
|
u3=u*u2;
|
|
ht00=2*t3-3*t2+1;
|
|
ht10=t3-2*t2+t;
|
|
ht01=-(2*t3)+3*t2;
|
|
ht11=t3-t2;
|
|
hu00=2*u3-3*u2+1;
|
|
hu10=u3-2*u2+u;
|
|
hu01=-(2*u3)+3*u2;
|
|
hu11=u3-u2;
|
|
ht10=ht10/dt;
|
|
ht11=ht11/dt;
|
|
hu10=hu10/du;
|
|
hu11=hu11/du;
|
|
//--- Advance source indexes to I-th position
|
|
s1=s1+i;
|
|
s2=s2+i;
|
|
s3=s3+i;
|
|
s4=s4+i;
|
|
//--- Calculate I-th component
|
|
result=c.m_f[s1]*ht00*hu00+c.m_f[s2]*ht01*hu00+c.m_f[s3]*ht00*hu01+c.m_f[s4]*ht01*hu01;
|
|
result+=c.m_f[sfx+s1]*ht10*hu00+c.m_f[sfx+s2]*ht11*hu00+c.m_f[sfx+s3]*ht10*hu01+c.m_f[sfx+s4]*ht11*hu01;
|
|
result+=c.m_f[sfy+s1]*ht00*hu10+c.m_f[sfy+s2]*ht01*hu10+c.m_f[sfy+s3]*ht00*hu11+c.m_f[sfy+s4]*ht01*hu11;
|
|
result+=c.m_f[sfxy+s1]*ht10*hu10+c.m_f[sfxy+s2]*ht11*hu10+c.m_f[sfxy+s3]*ht10*hu11+c.m_f[sfxy+s4]*ht11*hu11;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates bilinear or bicubic vector-valued |
|
|
//| spline at the given point (X,Y). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y - point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - array[D] which stores function values. F is |
|
|
//| out-parameter and it is reallocated after call to |
|
|
//| this function. In case you want to reuse previously|
|
|
//| allocated F, you may use Spline2DCalcVBuf(), which |
|
|
//| reallocates F only when it is too small. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DCalcV(CSpline2DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
CRowDouble &f)
|
|
{
|
|
f.Resize(0);
|
|
Spline2DCalcVBuf(c,x,y,f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates value of specific component of |
|
|
//| bilinear or bicubic vector-valued spline and its derivatives. |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y - point |
|
|
//| I - component index, in [0,D) |
|
|
//| Output parameters: |
|
|
//| F - S(x,y) |
|
|
//| FX - dS(x,y)/dX |
|
|
//| FY - dS(x,y)/dY |
|
|
//| FXY - d2S(x,y)/dXdY |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DDiffVi(CSpline2DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
int i,
|
|
double &f,
|
|
double &fx,
|
|
double &fy,
|
|
double &fxy)
|
|
{
|
|
//--- create variables
|
|
int d=0;
|
|
double t=0;
|
|
double dt=0;
|
|
double u=0;
|
|
double du=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double v3=0;
|
|
double t2=0;
|
|
double t3=0;
|
|
double u2=0;
|
|
double u3=0;
|
|
double ht00=0;
|
|
double ht01=0;
|
|
double ht10=0;
|
|
double ht11=0;
|
|
double hu00=0;
|
|
double hu01=0;
|
|
double hu10=0;
|
|
double hu11=0;
|
|
double dht00=0;
|
|
double dht01=0;
|
|
double dht10=0;
|
|
double dht11=0;
|
|
double dhu00=0;
|
|
double dhu01=0;
|
|
double dhu10=0;
|
|
double dhu11=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
if(!CAp::Assert(i>=0 && i<c.m_d,__FUNCTION__+": I<0 or I>=D"))
|
|
return;
|
|
//--- Prepare F, dF/dX, dF/dY, d2F/dXdY
|
|
f=0;
|
|
fx=0;
|
|
fy=0;
|
|
fxy=0;
|
|
d=c.m_d;
|
|
//--- Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
t=(x-c.m_x[l])/(c.m_x[l+1]-c.m_x[l]);
|
|
dt=1.0/(c.m_x[l+1]-c.m_x[l]);
|
|
ix=l;
|
|
//--- Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
u=(y-c.m_y[l])/(c.m_y[l+1]-c.m_y[l]);
|
|
du=1.0/(c.m_y[l+1]-c.m_y[l]);
|
|
iy=l;
|
|
//--- Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
y1=c.m_f[d*(c.m_n*iy+ix)+i];
|
|
y2=c.m_f[d*(c.m_n*iy+(ix+1))+i];
|
|
y3=c.m_f[d*(c.m_n*(iy+1)+(ix+1))+i];
|
|
y4=c.m_f[d*(c.m_n*(iy+1)+ix)+i];
|
|
f=(1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
|
|
fx=(-((1-u)*y1)+(1-u)*y2+u*y3-u*y4)*dt;
|
|
fy=(-((1-t)*y1)-t*y2+t*y3+(1-t)*y4)*du;
|
|
fxy=(y1-y2+y3-y4)*du*dt;
|
|
//--- exit
|
|
return;
|
|
}
|
|
//--- Bicubic interpolation
|
|
if(c.m_stype==-3)
|
|
{
|
|
sfx=c.m_n*c.m_m*d;
|
|
sfy=2*c.m_n*c.m_m*d;
|
|
sfxy=3*c.m_n*c.m_m*d;
|
|
s1=d*(c.m_n*iy+ix)+i;
|
|
s2=d*(c.m_n*iy+(ix+1))+i;
|
|
s3=d*(c.m_n*(iy+1)+ix)+i;
|
|
s4=d*(c.m_n*(iy+1)+(ix+1))+i;
|
|
t2=t*t;
|
|
t3=t*t2;
|
|
u2=u*u;
|
|
u3=u*u2;
|
|
ht00=2*t3-3*t2+1;
|
|
ht10=t3-2*t2+t;
|
|
ht01=-(2*t3)+3*t2;
|
|
ht11=t3-t2;
|
|
hu00=2*u3-3*u2+1;
|
|
hu10=u3-2*u2+u;
|
|
hu01=-(2*u3)+3*u2;
|
|
hu11=u3-u2;
|
|
ht10=ht10/dt;
|
|
ht11=ht11/dt;
|
|
hu10=hu10/du;
|
|
hu11=hu11/du;
|
|
dht00=6*t2-6*t;
|
|
dht10=3*t2-4*t+1;
|
|
dht01=-(6*t2)+6*t;
|
|
dht11=3*t2-2*t;
|
|
dhu00=6*u2-6*u;
|
|
dhu10=3*u2-4*u+1;
|
|
dhu01=-(6*u2)+6*u;
|
|
dhu11=3*u2-2*u;
|
|
dht00=dht00*dt;
|
|
dht01=dht01*dt;
|
|
dhu00=dhu00*du;
|
|
dhu01=dhu01*du;
|
|
v0=c.m_f[s1];
|
|
v1=c.m_f[s2];
|
|
v2=c.m_f[s3];
|
|
v3=c.m_f[s4];
|
|
f=v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
|
|
fx=v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
|
|
fy=v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
|
|
fxy=v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
|
|
v0=c.m_f[sfx+s1];
|
|
v1=c.m_f[sfx+s2];
|
|
v2=c.m_f[sfx+s3];
|
|
v3=c.m_f[sfx+s4];
|
|
f+=v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
|
|
fx+=v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
|
|
fy+=v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
|
|
fxy+=v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
|
|
v0=c.m_f[sfy+s1];
|
|
v1=c.m_f[sfy+s2];
|
|
v2=c.m_f[sfy+s3];
|
|
v3=c.m_f[sfy+s4];
|
|
f+=v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
|
|
fx+=v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
|
|
fy+=v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
|
|
fxy+=v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
|
|
v0=c.m_f[sfxy+s1];
|
|
v1=c.m_f[sfxy+s2];
|
|
v2=c.m_f[sfxy+s3];
|
|
v3=c.m_f[sfxy+s4];
|
|
f+=v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
|
|
fx+=v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
|
|
fy+=v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
|
|
fxy+=v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
|
|
//--- exit
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine unpacks two-dimensional spline into the |
|
|
//| coefficients table |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant. |
|
|
//| Result: |
|
|
//| M, N- grid size (x-axis and y-axis) |
|
|
//| Tbl - coefficients table, unpacked format, |
|
|
//| [0..(N-1)*(M-1)-1, 0..19]. |
|
|
//| For I = 0...M-2, J=0..N-2: |
|
|
//| K = I*(N-1)+J |
|
|
//| Tbl[K,0] = X[j] |
|
|
//| Tbl[K,1] = X[j+1] |
|
|
//| Tbl[K,2] = Y[i] |
|
|
//| Tbl[K,3] = Y[i+1] |
|
|
//| Tbl[K,4] = C00 |
|
|
//| Tbl[K,5] = C01 |
|
|
//| Tbl[K,6] = C02 |
|
|
//| Tbl[K,7] = C03 |
|
|
//| Tbl[K,8] = C10 |
|
|
//| Tbl[K,9] = C11 |
|
|
//| ... |
|
|
//| Tbl[K,19] = C33 |
|
|
//| On each grid square spline is equals to: |
|
|
//| S(x) = SUM(c[i,j]*(x^i)*(y^j), i=0..3, j=0..3) |
|
|
//| t = x-x[j] |
|
|
//| u = y-y[i] |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DUnpack(CSpline2DInterpolant &c,int &m,
|
|
int &n,CMatrixDouble &tbl)
|
|
{
|
|
//--- create variables
|
|
int k=0;
|
|
int p=0;
|
|
int ci=0;
|
|
int cj=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
double dt=0;
|
|
double du=0;
|
|
int i=0;
|
|
int j=0;
|
|
|
|
m=0;
|
|
n=0;
|
|
tbl.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(c.m_d!=1)
|
|
return;
|
|
|
|
n=c.m_n;
|
|
m=c.m_m;
|
|
tbl.Resize((n-1)*(m-1),20);
|
|
sfx=n*m;
|
|
sfy=2*n*m;
|
|
sfxy=3*n*m;
|
|
//---Fill
|
|
for(i=0; i<m-1; i++)
|
|
{
|
|
for(j=0; j<n-1; j++)
|
|
{
|
|
p=i*(n-1)+j;
|
|
tbl.Set(p,0,c.m_x[j]);
|
|
tbl.Set(p,1,c.m_x[j+1]);
|
|
tbl.Set(p,2,c.m_y[i]);
|
|
tbl.Set(p,3,c.m_y[i+1]);
|
|
dt=1/(tbl.Get(p,1)-tbl.Get(p,0));
|
|
du=1/(tbl.Get(p,3)-tbl.Get(p,2));
|
|
//---Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
for(k=4; k<=19; k++)
|
|
tbl.Set(p,k,0);
|
|
y1=c.m_f[n*i+j];
|
|
y2=c.m_f[n*i+(j+1)];
|
|
y3=c.m_f[n*(i+1)+(j+1)];
|
|
y4=c.m_f[n*(i+1)+j];
|
|
tbl.Set(p,4,y1);
|
|
tbl.Set(p,4+1*4+0,y2-y1);
|
|
tbl.Set(p,4+0*4+1,y4-y1);
|
|
tbl.Set(p,4+1*4+1,y3-y2-y4+y1);
|
|
}
|
|
//---Bicubic interpolation
|
|
if(c.m_stype==-3)
|
|
{
|
|
s1=n*i+j;
|
|
s2=n*i+(j+1);
|
|
s3=n*(i+1)+(j+1);
|
|
s4=n*(i+1)+j;
|
|
tbl.Set(p,4+0*4+0,c.m_f[s1]);
|
|
tbl.Set(p,4+0*4+1,c.m_f[sfy+s1]/du);
|
|
tbl.Set(p,4+0*4+2,-(3*c.m_f[s1])+3*c.m_f[s4]-2*c.m_f[sfy+s1]/du-c.m_f[sfy+s4]/du);
|
|
tbl.Set(p,4+0*4+3,2*c.m_f[s1]-2*c.m_f[s4]+c.m_f[sfy+s1]/du+c.m_f[sfy+s4]/du);
|
|
tbl.Set(p,4+1*4+0,c.m_f[sfx+s1]/dt);
|
|
tbl.Set(p,4+1*4+1,c.m_f[sfxy+s1]/(dt*du));
|
|
tbl.Set(p,4+1*4+2,-(3*c.m_f[sfx+s1]/dt)+3*c.m_f[sfx+s4]/dt-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+1*4+3,2*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s4]/dt+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+2*4+0,-(3*c.m_f[s1])+3*c.m_f[s2]-2*c.m_f[sfx+s1]/dt-c.m_f[sfx+s2]/dt);
|
|
tbl.Set(p,4+2*4+1,-(3*c.m_f[sfy+s1]/du)+3*c.m_f[sfy+s2]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du));
|
|
tbl.Set(p,4+2*4+2,9*c.m_f[s1]-9*c.m_f[s2]+9*c.m_f[s3]-9*c.m_f[s4]+6*c.m_f[sfx+s1]/dt+3*c.m_f[sfx+s2]/dt-3*c.m_f[sfx+s3]/dt-6*c.m_f[sfx+s4]/dt+6*c.m_f[sfy+s1]/du-6*c.m_f[sfy+s2]/du-3*c.m_f[sfy+s3]/du+3*c.m_f[sfy+s4]/du+4*c.m_f[sfxy+s1]/(dt*du)+2*c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+2*c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+2*4+3,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-4*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s2]/dt+2*c.m_f[sfx+s3]/dt+4*c.m_f[sfx+s4]/dt-3*c.m_f[sfy+s1]/du+3*c.m_f[sfy+s2]/du+3*c.m_f[sfy+s3]/du-3*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-2*c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+3*4+0,2*c.m_f[s1]-2*c.m_f[s2]+c.m_f[sfx+s1]/dt+c.m_f[sfx+s2]/dt);
|
|
tbl.Set(p,4+3*4+1,2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du));
|
|
tbl.Set(p,4+3*4+2,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-3*c.m_f[sfx+s1]/dt-3*c.m_f[sfx+s2]/dt+3*c.m_f[sfx+s3]/dt+3*c.m_f[sfx+s4]/dt-4*c.m_f[sfy+s1]/du+4*c.m_f[sfy+s2]/du+2*c.m_f[sfy+s3]/du-2*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-2*c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+3*4+3,4*c.m_f[s1]-4*c.m_f[s2]+4*c.m_f[s3]-4*c.m_f[s4]+2*c.m_f[sfx+s1]/dt+2*c.m_f[sfx+s2]/dt-2*c.m_f[sfx+s3]/dt-2*c.m_f[sfx+s4]/dt+2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du-2*c.m_f[sfy+s3]/du+2*c.m_f[sfy+s4]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
|
|
}
|
|
//---Rescale Cij
|
|
for(ci=0; ci<=3; ci++)
|
|
for(cj=0; cj<=3; cj++)
|
|
tbl.Mul(p,4+ci*4+cj,MathPow(dt,ci)*MathPow(du,cj));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline |
|
|
//| argument. |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant |
|
|
//| AX, BX - transformation coefficients: x = A*t + B |
|
|
//| AY, BY - transformation coefficients: y = A*u + B |
|
|
//| Result: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DLinTransXY(CSpline2DInterpolant &c,double ax,
|
|
double bx,double ay,double by)
|
|
{
|
|
//--- create variables
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
CRowDouble f;
|
|
CRowDouble v;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(ax),__FUNCTION__+": AX is infinite or NaN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bx),__FUNCTION__+": BX is infinite or NaN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(ay),__FUNCTION__+": AY is infinite or NaN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(by),__FUNCTION__+": BY is infinite or NaN"))
|
|
return;
|
|
|
|
x=c.m_x;
|
|
y=c.m_y;
|
|
f=c.m_f;
|
|
x.Resize(c.m_n);
|
|
y.Resize(c.m_m);
|
|
f.Resize(c.m_m*c.m_n*c.m_d);
|
|
//--- Handle different combinations of AX/AY
|
|
if(ax==0.0 && ay!=0.0)
|
|
{
|
|
for(int i=0; i<c.m_m; i++)
|
|
{
|
|
Spline2DCalcVBuf(c,bx,y[i],v);
|
|
y.Set(i,(y[i]-by)/ay);
|
|
for(int j=0; j<c.m_n; j++)
|
|
for(int k=0; k<c.m_d; k++)
|
|
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
|
|
}
|
|
}
|
|
if(ax!=0.0 && ay==0.0)
|
|
{
|
|
for(int j=0; j<c.m_n; j++)
|
|
{
|
|
Spline2DCalcVBuf(c,x[j],by,v);
|
|
x.Set(j,(x[j]-bx)/ax);
|
|
for(int i=0; i<c.m_m; i++)
|
|
for(int k=0; k<c.m_d; k++)
|
|
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
|
|
}
|
|
}
|
|
if(ax!=0.0 && ay!=0.0)
|
|
{
|
|
for(int j=0; j<c.m_n; j++)
|
|
x.Set(j,(x[j]-bx)/ax);
|
|
for(int i=0; i<c.m_m; i++)
|
|
y.Set(i,(y[i]-by)/ay);
|
|
}
|
|
if(ax==0.0 && ay==0.0)
|
|
{
|
|
Spline2DCalcVBuf(c,bx,by,v);
|
|
for(int i=0; i<c.m_m; i++)
|
|
for(int j=0; j<c.m_n; j++)
|
|
for(int k=0; k<c.m_d; k++)
|
|
f.Set(c.m_d*(i*c.m_n+j)+k,v[k]);
|
|
}
|
|
//--- Rebuild spline
|
|
if(c.m_stype==-3)
|
|
Spline2DBuildBicubicV(x,c.m_n,y,c.m_m,f,c.m_d,c);
|
|
if(c.m_stype==-1)
|
|
Spline2DBuildBilinearV(x,c.m_n,y,c.m_m,f,c.m_d,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline. |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant. |
|
|
//| A, B- transformation coefficients: S2(x,y) = A*S(x,y) + B |
|
|
//| Output parameters: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DLinTransF(CSpline2DInterpolant &c,const double a,
|
|
const double b)
|
|
{
|
|
//--- create variables
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
CRowDouble f;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
|
|
x=c.m_x;
|
|
y=c.m_y;
|
|
f=c.m_f*a+b;
|
|
x.Resize(c.m_n);
|
|
y.Resize(c.m_m);
|
|
f.Resize(c.m_m*c.m_n*c.m_d);
|
|
if(c.m_stype==-3)
|
|
Spline2DBuildBicubicV(x,c.m_n,y,c.m_m,f,c.m_d,c);
|
|
if(c.m_stype==-1)
|
|
Spline2DBuildBilinearV(x,c.m_n,y,c.m_m,f,c.m_d,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine makes the copy of the spline model. |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant |
|
|
//| Output parameters: |
|
|
//| CC - spline copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DCopy(CSpline2DInterpolant &c,CSpline2DInterpolant &cc)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
|
|
cc.m_n=c.m_n;
|
|
cc.m_m=c.m_m;
|
|
cc.m_d=c.m_d;
|
|
cc.m_stype=c.m_stype;
|
|
cc.m_x=c.m_x;
|
|
cc.m_y=c.m_y;
|
|
cc.m_f=c.m_f;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Bicubic spline resampling |
|
|
//| Input parameters: |
|
|
//| A - function values at the old grid, |
|
|
//| array[0..OldHeight-1, 0..OldWidth-1] |
|
|
//| OldHeight - old grid height, OldHeight>1 |
|
|
//| OldWidth - old grid width, OldWidth>1 |
|
|
//| NewHeight - new grid height, NewHeight>1 |
|
|
//| NewWidth - new grid width, NewWidth>1 |
|
|
//| Output parameters: |
|
|
//| B - function values at the new grid, |
|
|
//| array[0..NewHeight-1, 0..NewWidth-1] |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DResampleBicubic(CMatrixDouble &a,const int oldheight,
|
|
const int oldwidth,CMatrixDouble &b,
|
|
const int newheight,const int newwidth)
|
|
{
|
|
//--- create variables
|
|
int mw=0;
|
|
int mh=0;
|
|
//--- create arrays
|
|
double x[];
|
|
double y[];
|
|
//--- create matrix
|
|
CMatrixDouble buf;
|
|
//--- object of class
|
|
CSpline1DInterpolant c;
|
|
//--- check
|
|
if(!CAp::Assert(oldwidth>1 && oldheight>1,__FUNCTION__+": width/height less than 1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(newwidth>1 && newheight>1,__FUNCTION__+": width/height less than 1"))
|
|
return;
|
|
//--- Prepare
|
|
mw=MathMax(oldwidth,newwidth);
|
|
mh=MathMax(oldheight,newheight);
|
|
//--- allocation
|
|
b.Resize(newheight,newwidth);
|
|
buf.Resize(oldheight,newwidth);
|
|
ArrayResize(x,MathMax(mw,mh));
|
|
ArrayResize(y,MathMax(mw,mh));
|
|
//--- Horizontal interpolation
|
|
for(int i=0; i<oldheight; i++)
|
|
{
|
|
//--- Fill X,Y
|
|
for(int j=0; j<oldwidth; j++)
|
|
{
|
|
x[j]=(double)j/(double)(oldwidth-1);
|
|
y[j]=a.Get(i,j);
|
|
}
|
|
//--- Interpolate and place result into temporary matrix
|
|
CSpline1D::Spline1DBuildCubic(x,y,oldwidth,0,0.0,0,0.0,c);
|
|
for(int j=0; j<newwidth; j++)
|
|
buf.Set(i,j,CSpline1D::Spline1DCalc(c,(double)j/(double)(newwidth-1)));
|
|
}
|
|
//--- Vertical interpolation
|
|
for(int j=0; j<newwidth; j++)
|
|
{
|
|
//--- Fill X,Y
|
|
for(int i=0; i<oldheight; i++)
|
|
{
|
|
x[i]=(double)i/(double)(oldheight-1);
|
|
y[i]=buf.Get(i,j);
|
|
}
|
|
//--- Interpolate and place result into B
|
|
CSpline1D::Spline1DBuildCubic(x,y,oldheight,0,0.0,0,0.0,c);
|
|
for(int i=0; i<newheight; i++)
|
|
b.Set(i,j,CSpline1D::Spline1DCalc(c,(double)i/(double)(newheight-1)));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Bilinear spline resampling |
|
|
//| Input parameters: |
|
|
//| A - function values at the old grid, |
|
|
//| array[0..OldHeight-1, 0..OldWidth-1] |
|
|
//| OldHeight - old grid height, OldHeight>1 |
|
|
//| OldWidth - old grid width, OldWidth>1 |
|
|
//| NewHeight - new grid height, NewHeight>1 |
|
|
//| NewWidth - new grid width, NewWidth>1 |
|
|
//| Output parameters: |
|
|
//| B - function values at the new grid, |
|
|
//| array[0..NewHeight-1, 0..NewWidth-1] |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DResampleBilinear(CMatrixDouble &a,const int oldheight,
|
|
const int oldwidth,CMatrixDouble &b,
|
|
const int newheight,const int newwidth)
|
|
{
|
|
//--- create variables
|
|
int l=0;
|
|
int c=0;
|
|
double t=0;
|
|
double u=0;
|
|
//--- check
|
|
if(!CAp::Assert(oldwidth>1 && oldheight>1,__FUNCTION__+": width/height less than 1"))
|
|
return;
|
|
if(!CAp::Assert(newwidth>1 && newheight>1,__FUNCTION__+": width/height less than 1"))
|
|
return;
|
|
//--- allocation
|
|
b.Resize(newheight,newwidth);
|
|
for(int i=0; i<newheight; i++)
|
|
{
|
|
for(int j=0; j<newwidth; j++)
|
|
{
|
|
//--- calculation
|
|
l=i*(oldheight-1)/(newheight-1);
|
|
//--- check
|
|
if(l==oldheight-1)
|
|
l=oldheight-2;
|
|
//--- calculation
|
|
u=(double)i/(double)(newheight-1)*(oldheight-1)-l;
|
|
c=j*(oldwidth-1)/(newwidth-1);
|
|
//--- check
|
|
if(c==oldwidth-1)
|
|
c=oldwidth-2;
|
|
//--- calculation
|
|
t=(double)(j*(oldwidth-1))/(double)(newwidth-1)-c;
|
|
b.Set(i,j,(1-t)*(1-u)*a.Get(l,c)+t*(1-u)*a.Get(l,c+1)+t*u*a.Get(l+1,c+1)+(1-t)*u*a.Get(l+1,c));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds bilinear vector-valued spline. |
|
|
//| Input parameters: |
|
|
//| X - spline abscissas, array[0..N-1] |
|
|
//| Y - spline ordinates, array[0..M-1] |
|
|
//| F - function values, array[0..M*N*D-1]: |
|
|
//| * first D elements store D values at (X[0],Y[0]) |
|
|
//| * next D elements store D values at (X[1],Y[0]) |
|
|
//| * general form - D function values at (X[i],Y[j]) |
|
|
//| are stored at F[D*(J*N+I)...D*(J*N+I)+D-1]. |
|
|
//| M,N - grid size, M>=2, N>=2 |
|
|
//| D - vector dimension, D>=1 |
|
|
//| Output parameters: |
|
|
//| C - spline interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuildBilinearV(CRowDouble &x,int n,
|
|
CRowDouble &y,
|
|
int m,
|
|
CRowDouble &f,
|
|
int d,
|
|
CSpline2DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
double t=0;
|
|
int k=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N is less then 2"))
|
|
return;
|
|
if(!CAp::Assert(m>=2,__FUNCTION__+": M is less then 2"))
|
|
return;
|
|
if(!CAp::Assert(d>=1,__FUNCTION__+": invalid argument D (D<1)"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
k=n*m*d;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(f)>=k,__FUNCTION__+": length of F is too short (Length(F)<N*M*D)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,k),__FUNCTION__+": F contains NaN or Infinite value"))
|
|
return;
|
|
//--- Fill interpolant
|
|
c.m_n=n;
|
|
c.m_m=m;
|
|
c.m_d=d;
|
|
c.m_stype=-1;
|
|
c.m_x=x;
|
|
c.m_y=y;
|
|
c.m_f=f;
|
|
c.m_x.Resize(c.m_n);
|
|
c.m_y.Resize(c.m_m);
|
|
c.m_f.Resize(k);
|
|
//--- Sort points
|
|
for(int j=0; j<c.m_n; j++)
|
|
{
|
|
k=j;
|
|
for(int i=j+1; i<c.m_n; i++)
|
|
if(c.m_x[i]<c.m_x[k])
|
|
k=i;
|
|
if(k!=j)
|
|
{
|
|
for(int i=0; i<c.m_m; i++)
|
|
for(int i0=0; i0<c.m_d; i0++)
|
|
c.m_f.Swap(c.m_d*(i*c.m_n+j)+i0,c.m_d*(i*c.m_n+k)+i0);
|
|
c.m_x.Swap(j,k);
|
|
}
|
|
}
|
|
for(int i=0; i<c.m_m; i++)
|
|
{
|
|
k=i;
|
|
for(int j=i+1; j<c.m_m; j++)
|
|
if(c.m_y[j]<c.m_y[k])
|
|
k=j;
|
|
if(k!=i)
|
|
{
|
|
for(int j=0; j<c.m_n; j++)
|
|
for(int i0=0; i0<=c.m_d-1; i0++)
|
|
c.m_f.Swap(c.m_d*(i*c.m_n+j)+i0,c.m_d*(k*c.m_n+j)+i0);
|
|
c.m_y.Swap(i,k);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds bicubic vector-valued spline. |
|
|
//| Input parameters: |
|
|
//| X - spline abscissas, array[0..N-1] |
|
|
//| Y - spline ordinates, array[0..M-1] |
|
|
//| F - function values, array[0..M*N*D-1]: |
|
|
//| * first D elements store D values at (X[0],Y[0]) |
|
|
//| * next D elements store D values at (X[1],Y[0]) |
|
|
//| * general form - D function values at (X[i],Y[j]) |
|
|
//| are stored at F[D*(J*N+I)...D*(J*N+I)+D-1]. |
|
|
//| M,N - grid size, M>=2, N>=2 |
|
|
//| D - vector dimension, D>=1 |
|
|
//| Output parameters: |
|
|
//| C - spline interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuildBicubicV(CRowDouble &x,
|
|
int n,
|
|
CRowDouble &y,
|
|
int m,
|
|
CRowDouble &F,
|
|
int d,
|
|
CSpline2DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble tf;
|
|
CMatrixDouble dx;
|
|
CMatrixDouble dy;
|
|
CMatrixDouble dxy;
|
|
double t=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int di=0;
|
|
//--- copy
|
|
CRowDouble f=F;
|
|
//--- check
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N is less than 2"))
|
|
return;
|
|
if(!CAp::Assert(m>=2,__FUNCTION__+": M is less than 2"))
|
|
return;
|
|
if(!CAp::Assert(d>=1,__FUNCTION__+": invalid argument D (D<1)"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m,__FUNCTION__+": length of X or Y is too short (Length(X/Y)<N/M)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
|
|
k=n*m*d;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(f)>=k,__FUNCTION__+": length of F is too short (Length(F)<N*M*D)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,k),__FUNCTION__+": F contains NaN or Infinite value"))
|
|
return;
|
|
//--- Fill interpolant:
|
|
//--- F[0]...F[N*M*D-1]:
|
|
//--- f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
|
|
//--- F[N*M*D]...F[2*N*M*D-1]:
|
|
//--- df(i,j)/dx table.
|
|
//--- F[2*N*M*D]...F[3*N*M*D-1]:
|
|
//--- df(i,j)/dy table.
|
|
//--- F[3*N*M*D]...F[4*N*M*D-1]:
|
|
//--- d2f(i,j)/dxdy table.
|
|
c.m_d=d;
|
|
c.m_n=n;
|
|
c.m_m=m;
|
|
c.m_stype=-3;
|
|
k=4*k;
|
|
c.m_x=x;
|
|
c.m_y=y;
|
|
c.m_x.Resize(c.m_n);
|
|
c.m_y.Resize(c.m_m);
|
|
c.m_f.Resize(k);
|
|
tf.Resize(c.m_m,c.m_n);
|
|
//--- Sort points
|
|
for(j=0; j<c.m_n; j++)
|
|
{
|
|
k=j;
|
|
for(i=j+1; i<c.m_n; i++)
|
|
if(c.m_x[i]<c.m_x[k])
|
|
k=i;
|
|
if(k!=j)
|
|
{
|
|
for(i=0; i<c.m_m; i++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Swap(c.m_d*(i*c.m_n+j)+di,c.m_d*(i*c.m_n+k)+di);
|
|
c.m_x.Swap(j,k);
|
|
}
|
|
}
|
|
for(i=0; i<c.m_m; i++)
|
|
{
|
|
k=i;
|
|
for(j=i+1; j<c.m_m; j++)
|
|
if(c.m_y[j]<c.m_y[k])
|
|
k=j;
|
|
if(k!=i)
|
|
{
|
|
for(j=0; j<c.m_n; j++)
|
|
for(di=0; di<=c.m_d-1; di++)
|
|
f.Swap(c.m_d*(i*c.m_n+j)+di,c.m_d*(k*c.m_n+j)+di);
|
|
c.m_y.Swap(i,k);
|
|
}
|
|
}
|
|
for(di=0; di<c.m_d; di++)
|
|
{
|
|
for(i=0; i<c.m_m; i++)
|
|
for(j=0; j<c.m_n; j++)
|
|
tf.Set(i,j,f[c.m_d*(i*c.m_n+j)+di]);
|
|
BicubicCalcDerivatives(tf,c.m_x,c.m_y,c.m_m,c.m_n,dx,dy,dxy);
|
|
for(i=0; i<c.m_m; i++)
|
|
{
|
|
for(j=0; j<c.m_n; j++)
|
|
{
|
|
k=c.m_d*(i*c.m_n+j)+di;
|
|
c.m_f.Set(k,tf.Get(i,j));
|
|
c.m_f.Set(c.m_n*c.m_m*c.m_d+k,dx.Get(i,j));
|
|
c.m_f.Set(2*c.m_n*c.m_m*c.m_d+k,dy.Get(i,j));
|
|
c.m_f.Set(3*c.m_n*c.m_m*c.m_d+k,dxy.Get(i,j));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine unpacks two-dimensional spline into the |
|
|
//| coefficients table |
|
|
//| Input parameters: |
|
|
//| C - spline interpolant. |
|
|
//| Result: |
|
|
//| M, N - grid size (x-axis and y-axis) |
|
|
//| D - number of components |
|
|
//| Tbl - coefficients table, unpacked format, |
|
|
//| D - components: [0..(N-1)*(M-1)*D-1, 0..19]. |
|
|
//| For T=0..D-1 (component index), I = 0...N-2 (x index), J=0..M-2 |
|
|
//| (y index): |
|
|
//| K := T + I*D + J*D*(N-1) |
|
|
//| K-th row stores decomposition for T-th component of the |
|
|
//| vector-valued function |
|
|
//| Tbl[K,0] = X[i] |
|
|
//| Tbl[K,1] = X[i+1] |
|
|
//| Tbl[K,2] = Y[j] |
|
|
//| Tbl[K,3] = Y[j+1] |
|
|
//| Tbl[K,4] = C00 |
|
|
//| Tbl[K,5] = C01 |
|
|
//| Tbl[K,6] = C02 |
|
|
//| Tbl[K,7] = C03 |
|
|
//| Tbl[K,8] = C10 |
|
|
//| Tbl[K,9] = C11 |
|
|
//| ... |
|
|
//| Tbl[K,19] = C33 |
|
|
//| On each grid square spline is equals to: |
|
|
//| S(x) = SUM(c[i,j]*(t^i)*(u^j), i=0..3, j=0..3) |
|
|
//| t = x-x[j] |
|
|
//| u = y-y[i] |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DUnpackV(CSpline2DInterpolant &c,
|
|
int &m,
|
|
int &n,
|
|
int &d,
|
|
CMatrixDouble &tbl)
|
|
{
|
|
//--- create variables
|
|
int p=0;
|
|
int s1=0;
|
|
int s2=0;
|
|
int s3=0;
|
|
int s4=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double y1=0;
|
|
double y2=0;
|
|
double y3=0;
|
|
double y4=0;
|
|
double dt=0;
|
|
double du=0;
|
|
int k0=0;
|
|
|
|
m=0;
|
|
n=0;
|
|
d=0;
|
|
tbl.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
|
|
n=c.m_n;
|
|
m=c.m_m;
|
|
d=c.m_d;
|
|
tbl.Resize((n-1)*(m-1)*d,20);
|
|
sfx=n*m*d;
|
|
sfy=2*n*m*d;
|
|
sfxy=3*n*m*d;
|
|
for(int i=0; i<m-1; i++)
|
|
{
|
|
for(int j=0; j<n-1; j++)
|
|
{
|
|
for(int k=0; k<d; k++)
|
|
{
|
|
p=d*(i*(n-1)+j)+k;
|
|
tbl.Set(p,0,c.m_x[j]);
|
|
tbl.Set(p,1,c.m_x[j+1]);
|
|
tbl.Set(p,2,c.m_y[i]);
|
|
tbl.Set(p,3,c.m_y[i+1]);
|
|
dt=1/(tbl.Get(p,1)-tbl.Get(p,0));
|
|
du=1/(tbl.Get(p,3)-tbl.Get(p,2));
|
|
//---Bilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
for(k0=4; k0<=19; k0++)
|
|
tbl.Set(p,k0,0);
|
|
y1=c.m_f[d*(n*i+j)+k];
|
|
y2=c.m_f[d*(n*i+(j+1))+k];
|
|
y3=c.m_f[d*(n*(i+1)+(j+1))+k];
|
|
y4=c.m_f[d*(n*(i+1)+j)+k];
|
|
tbl.Set(p,4,y1);
|
|
tbl.Set(p,4+1*4+0,y2-y1);
|
|
tbl.Set(p,4+0*4+1,y4-y1);
|
|
tbl.Set(p,4+1*4+1,y3-y2-y4+y1);
|
|
}
|
|
//---Bicubic interpolation
|
|
if(c.m_stype==-3)
|
|
{
|
|
s1=d*(n*i+j)+k;
|
|
s2=d*(n*i+(j+1))+k;
|
|
s3=d*(n*(i+1)+(j+1))+k;
|
|
s4=d*(n*(i+1)+j)+k;
|
|
tbl.Set(p,4+0*4+0,c.m_f[s1]);
|
|
tbl.Set(p,4+0*4+1,c.m_f[sfy+s1]/du);
|
|
tbl.Set(p,4+0*4+2,-(3*c.m_f[s1])+3*c.m_f[s4]-2*c.m_f[sfy+s1]/du-c.m_f[sfy+s4]/du);
|
|
tbl.Set(p,4+0*4+3,2*c.m_f[s1]-2*c.m_f[s4]+c.m_f[sfy+s1]/du+c.m_f[sfy+s4]/du);
|
|
tbl.Set(p,4+1*4+0,c.m_f[sfx+s1]/dt);
|
|
tbl.Set(p,4+1*4+1,c.m_f[sfxy+s1]/(dt*du));
|
|
tbl.Set(p,4+1*4+2,-(3*c.m_f[sfx+s1]/dt)+3*c.m_f[sfx+s4]/dt-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+1*4+3,2*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s4]/dt+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+2*4+0,-(3*c.m_f[s1])+3*c.m_f[s2]-2*c.m_f[sfx+s1]/dt-c.m_f[sfx+s2]/dt);
|
|
tbl.Set(p,4+2*4+1,-(3*c.m_f[sfy+s1]/du)+3*c.m_f[sfy+s2]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du));
|
|
tbl.Set(p,4+2*4+2,9*c.m_f[s1]-9*c.m_f[s2]+9*c.m_f[s3]-9*c.m_f[s4]+6*c.m_f[sfx+s1]/dt+3*c.m_f[sfx+s2]/dt-3*c.m_f[sfx+s3]/dt-6*c.m_f[sfx+s4]/dt+6*c.m_f[sfy+s1]/du-6*c.m_f[sfy+s2]/du-3*c.m_f[sfy+s3]/du+3*c.m_f[sfy+s4]/du+4*c.m_f[sfxy+s1]/(dt*du)+2*c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+2*c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+2*4+3,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-4*c.m_f[sfx+s1]/dt-2*c.m_f[sfx+s2]/dt+2*c.m_f[sfx+s3]/dt+4*c.m_f[sfx+s4]/dt-3*c.m_f[sfy+s1]/du+3*c.m_f[sfy+s2]/du+3*c.m_f[sfy+s3]/du-3*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-2*c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+3*4+0,2*c.m_f[s1]-2*c.m_f[s2]+c.m_f[sfx+s1]/dt+c.m_f[sfx+s2]/dt);
|
|
tbl.Set(p,4+3*4+1,2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du));
|
|
tbl.Set(p,4+3*4+2,-(6*c.m_f[s1])+6*c.m_f[s2]-6*c.m_f[s3]+6*c.m_f[s4]-3*c.m_f[sfx+s1]/dt-3*c.m_f[sfx+s2]/dt+3*c.m_f[sfx+s3]/dt+3*c.m_f[sfx+s4]/dt-4*c.m_f[sfy+s1]/du+4*c.m_f[sfy+s2]/du+2*c.m_f[sfy+s3]/du-2*c.m_f[sfy+s4]/du-2*c.m_f[sfxy+s1]/(dt*du)-2*c.m_f[sfxy+s2]/(dt*du)-c.m_f[sfxy+s3]/(dt*du)-c.m_f[sfxy+s4]/(dt*du));
|
|
tbl.Set(p,4+3*4+3,4*c.m_f[s1]-4*c.m_f[s2]+4*c.m_f[s3]-4*c.m_f[s4]+2*c.m_f[sfx+s1]/dt+2*c.m_f[sfx+s2]/dt-2*c.m_f[sfx+s3]/dt-2*c.m_f[sfx+s4]/dt+2*c.m_f[sfy+s1]/du-2*c.m_f[sfy+s2]/du-2*c.m_f[sfy+s3]/du+2*c.m_f[sfy+s4]/du+c.m_f[sfxy+s1]/(dt*du)+c.m_f[sfxy+s2]/(dt*du)+c.m_f[sfxy+s3]/(dt*du)+c.m_f[sfxy+s4]/(dt*du));
|
|
}
|
|
//---Rescale Cij
|
|
for(int ci=0; ci<=3; ci++)
|
|
for(int cj=0; cj<=3; cj++)
|
|
tbl.Mul(p,4+ci*4+cj,MathPow(dt,ci)*MathPow(du,cj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine creates least squares solver used to fit 2D |
|
|
//| splines to irregularly sampled (scattered) data. |
|
|
//| Solver object is used to perform spline fits as follows: |
|
|
//| * solver object is created with Spline2DBuilderCreate() |
|
|
//| function |
|
|
//| * dataset is added with Spline2DBuilderSetPoints() function |
|
|
//| * fit area is chosen: |
|
|
//| * Spline2DBuilderSetArea() - for user-defined area |
|
|
//| * Spline2DBuilderSetAreaAuto()- for automatically chosen |
|
|
//| area |
|
|
//| * number of grid nodes is chosen with Spline2DBuilderSetGrid() |
|
|
//| * prior term is chosen with one of the following functions: |
|
|
//| * Spline2DBuilderSetLinTerm() to set linear prior |
|
|
//| * Spline2DBuilderSetConstTerm() to set constant prior |
|
|
//| * Spline2DBuilderSetZeroTerm() to set zero prior |
|
|
//| * Spline2DBuilderSetUserTerm() to set user-defined constant |
|
|
//| prior |
|
|
//| * solver algorithm is chosen with either: |
|
|
//| * Spline2DBuilderSetAlgoBlockLLS() - BlockLLS algorithm, |
|
|
//| medium-scale problems|
|
|
//| * Spline2DBuilderSetAlgoFastDDM() - FastDDM algorithm, |
|
|
//| large-scale problems |
|
|
//| * finally, fitting itself is performed with Spline2DFit() |
|
|
//| function. |
|
|
//| Most of the steps above can be omitted, solver is configured with|
|
|
//| good defaults. The minimum is to call: |
|
|
//| * Spline2DBuilderCreate() to create solver object |
|
|
//| * Spline2DBuilderSetPoints() to specify dataset |
|
|
//| * Spline2DBuilderSetGrid() to tell how many nodes you need |
|
|
//| * Spline2DFit() to perform fit |
|
|
//| INPUT PARAMETERS: |
|
|
//| D - positive number, number of Y-components: D=1 for |
|
|
//| simple scalar fit, D>1 for vector-valued spline |
|
|
//| fitting. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - solver object |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderCreate(int d,CSpline2DBuilder &State)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(d>=1,__FUNCTION__+": D<=0"))
|
|
return;
|
|
//---NOTES:
|
|
//---1. Prior term is set to linear one (good default option)
|
|
//---2. Solver is set to BlockLLS - good enough for small-scale problems.
|
|
//---3. Refinement rounds: 5; enough to get good convergence.
|
|
State.m_priorterm=1;
|
|
State.m_priortermval=0;
|
|
State.m_areatype=0;
|
|
State.m_gridtype=0;
|
|
State.m_smoothing=0.0;
|
|
State.m_nlayers=0;
|
|
State.m_solvertype=1;
|
|
State.m_npoints=0;
|
|
State.m_d=d;
|
|
State.m_sx=1.0;
|
|
State.m_sy=1.0;
|
|
State.m_lsqrcnt=5;
|
|
//---Algorithm settings
|
|
State.m_adddegreeoffreedom=true;
|
|
State.m_maxcoresize=16;
|
|
State.m_interfacesize=5;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets constant prior term (model is a sum of bicubic|
|
|
//| spline and global prior, which can be linear, constant, |
|
|
//| user-defined constant or zero). |
|
|
//| Constant prior term is determined by least squares fitting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//| V - value for user-defined prior |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetUserTerm(CSpline2DBuilder &State,
|
|
double v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": infinite/NAN value passed"))
|
|
return;
|
|
|
|
State.m_priorterm=0;
|
|
State.m_priortermval=v;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear prior term (model is a sum of bicubic |
|
|
//| spline and global prior, which can be linear, constant, |
|
|
//| user-defined constant or zero). |
|
|
//| Linear prior term is determined by least squares fitting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetLinTerm(CSpline2DBuilder &State)
|
|
{
|
|
State.m_priorterm=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets constant prior term (model is a sum of bicubic|
|
|
//| spline and global prior, which can be linear, constant, |
|
|
//| user-defined constant or zero). |
|
|
//| Constant prior term is determined by least squares fitting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetConstTerm(CSpline2DBuilder &State)
|
|
{
|
|
State.m_priorterm=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets zero prior term (model is a sum of bicubic |
|
|
//| spline and global prior, which can be linear, constant, |
|
|
//| user-defined constant or zero). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline builder |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetZeroTerm(CSpline2DBuilder &State)
|
|
{
|
|
State.m_priorterm=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset to the builder object. |
|
|
//| This function overrides results of the previous calls, i.e. |
|
|
//| multiple calls of this function will result in only the last |
|
|
//| set being added. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| XY - points, array[N,2+D]. One row corresponds to one |
|
|
//| point in the dataset. First 2 elements are |
|
|
//| coordinates, next D elements are function values. |
|
|
//| Array may be larger than specified, in this case |
|
|
//| only leading [N,NX+NY] elements will be used. |
|
|
//| N - number of points in the dataset |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetPoints(CSpline2DBuilder &State,
|
|
CMatrixDouble &xy,
|
|
int n)
|
|
{
|
|
int ew=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(xy)>=2+State.m_d,__FUNCTION__+": Cols(XY)<NX+NY"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,2+State.m_d),__FUNCTION__+": XY contains infinite or NaN values!"))
|
|
return;
|
|
|
|
State.m_npoints=n;
|
|
ew=2+State.m_d;
|
|
CApServ::RVectorSetLengthAtLeast(State.m_xy,n*ew);
|
|
for(int i=0; i<n; i++)
|
|
for(int j=0; j<ew; j++)
|
|
State.m_xy.Set(i*ew+j,xy.Get(i,j));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets area where 2D spline interpolant is built. |
|
|
//| "Auto" means that area extent is determined automatically from |
|
|
//| dataset extent. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetAreaAuto(CSpline2DBuilder &State)
|
|
{
|
|
State.m_areatype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets area where 2D spline interpolant is built to |
|
|
//| user-defined one: [XA,XB]*[YA,YB] |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| XA,XB - spatial extent in the first (X) dimension, XA<XB |
|
|
//| YA,YB - spatial extent in the second (Y) dimension, YA<YB |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetArea(CSpline2DBuilder &State,
|
|
double xa,
|
|
double xb,
|
|
double ya,
|
|
double yb)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(xa),__FUNCTION__+": XA is not finite"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(xb),__FUNCTION__+": XB is not finite"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(ya),__FUNCTION__+": YA is not finite"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(yb),__FUNCTION__+": YB is not finite"))
|
|
return;
|
|
if(!CAp::Assert(xa<xb,__FUNCTION__+": XA>=XB"))
|
|
return;
|
|
if(!CAp::Assert(ya<yb,__FUNCTION__+": YA>=YB"))
|
|
return;
|
|
|
|
State.m_areatype=1;
|
|
State.m_xa=xa;
|
|
State.m_xb=xb;
|
|
State.m_ya=ya;
|
|
State.m_yb=yb;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets nodes count for 2D spline interpolant. Fitting|
|
|
//| is performed on area defined with one of the "setarea" functions;|
|
|
//| this one sets number of nodes placed upon the fitting area. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| KX - nodes count for the first (X) dimension; fitting |
|
|
//| interval [XA,XB] is separated into KX-1 |
|
|
//| subintervals, with KX nodes created at the |
|
|
//| boundaries. |
|
|
//| KY - nodes count for the first (Y) dimension; fitting |
|
|
//| interval [YA,YB] is separated into KY-1 |
|
|
//| subintervals, with KY nodes created at the |
|
|
//| boundaries. |
|
|
//| NOTE: at least 4 nodes is created in each dimension, so KX and KY|
|
|
//| are silently increased if needed. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetGrid(CSpline2DBuilder &State,
|
|
int kx,
|
|
int ky)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(kx>0,__FUNCTION__+":KX<=0"))
|
|
return;
|
|
if(!CAp::Assert(ky>0,__FUNCTION__+":KY<=0"))
|
|
return;
|
|
|
|
State.m_gridtype=1;
|
|
State.m_kx=MathMax(kx,4);
|
|
State.m_ky=MathMax(ky,4);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function allows you to choose least squares solver used to |
|
|
//| perform fitting. This function sets solver algorithm to "FastDDM"|
|
|
//| which performs fast parallel fitting by splitting problem into |
|
|
//| smaller chunks and merging results together. |
|
|
//| This solver is optimized for large-scale problems, starting from |
|
|
//| 256x256 grids, and up to 10000x10000 grids. Of course, it will |
|
|
//| work for smaller grids too. |
|
|
//| More detailed description of the algorithm is given below: |
|
|
//| * algorithm generates hierarchy of nested grids, ranging from |
|
|
//| ~16x16 (topmost "layer" of the model) to ~KX*KY one (final |
|
|
//| layer). Upper layers model global behavior of the function, |
|
|
//| lower layers are used to model fine details. Moving from |
|
|
//| layer to layer doubles grid density. |
|
|
//| * fitting is started from topmost layer, subsequent layers are |
|
|
//| fitted using residuals from previous ones. |
|
|
//| * user may choose to skip generation of upper layers and |
|
|
//| generate only a few bottom ones, which will result in much |
|
|
//| better performance and parallelization efficiency, at the |
|
|
//| cost of algorithm inability to "patch" large holes in the |
|
|
//| dataset. |
|
|
//| * every layer is regularized using progressively increasing |
|
|
//| regularization coefficient; thus, increasing LambdaV |
|
|
//| penalizes fine details first, leaving lower frequencies |
|
|
//| almost intact for a while. |
|
|
//| * after fitting is done, all layers are merged together into |
|
|
//| one bicubic spline |
|
|
//| IMPORTANT: regularization coefficient used by this solver is |
|
|
//| different from the one used by BlockLLS. Latter |
|
|
//| utilizes nonlinearity penalty, which is global in |
|
|
//| nature (large regularization results in global linear |
|
|
//| trend being extracted); this solver uses another, |
|
|
//| localized form of penalty, which is suitable for |
|
|
//| parallel processing. |
|
|
//| Notes on memory and performance: |
|
|
//| * memory requirements: most memory is consumed during modeling |
|
|
//| of the higher layers; ~[512*NPoints] bytes is required for a |
|
|
//| model with full hierarchy of grids being generated. However, |
|
|
//| if you skip a few topmost layers, you will get nearly |
|
|
//| constant (wrt. points count and grid size) memory consumption|
|
|
//| * serial running time: O(K*K)+O(NPoints) for a KxK grid |
|
|
//| * parallelism potential: good. You may get nearly linear |
|
|
//| speed-up when performing fitting with just a few layers. |
|
|
//| Adding more layers results in model becoming more global, |
|
|
//| which somewhat reduces efficiency of the parallel code. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| NLayers - number of layers in the model: |
|
|
//| * NLayers>=1 means that up to chosen number of |
|
|
//| bottom layers is fitted |
|
|
//| * NLayers=0 means that maximum number of layers is |
|
|
//| chosen (according to current grid size) |
|
|
//| * NLayers<=-1 means that up to |NLayers| topmost |
|
|
//| layers is skipped |
|
|
//| Recommendations: |
|
|
//| *good "default" value is 2 layers |
|
|
//| * you may need more layers, if your dataset is very|
|
|
//| irregular and you want to "patch" large holes. |
|
|
//| For a grid step H (equal to AreaWidth/GridSize) |
|
|
//| you may expect that last layer reproduces |
|
|
//| variations at distance H (and can patch holes |
|
|
//| that wide); that higher layers operate at |
|
|
//| distances 2*H, 4*H, 8*H and so on. |
|
|
//| *good value for "bullletproof" mode is NLayers=0, |
|
|
//| which results in complete hierarchy of layers |
|
|
//| being generated. |
|
|
//| LambdaV - regularization coefficient, chosen in such a way |
|
|
//| that it penalizes bottom layers (fine details) |
|
|
//| first. LambdaV>=0, zero value means that no penalty|
|
|
//| is applied. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetAlgoFastDDM(CSpline2DBuilder &State,
|
|
int nlayers,
|
|
double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite value"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV<0"))
|
|
return;
|
|
|
|
State.m_solvertype=3;
|
|
State.m_nlayers=nlayers;
|
|
State.m_smoothing=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function allows you to choose least squares solver used to |
|
|
//| perform fitting. This function sets solver algorithm to |
|
|
//| "BlockLLS", which performs least squares fitting with fast sparse|
|
|
//| direct solver, with optional nonsmoothness penalty being applied.|
|
|
//| Nonlinearity penalty has the following form: |
|
|
//| [ ] |
|
|
//| P()~Lambda*integral[(d2S/dx2)^2+2*(d2S/dxdy)^2+(d2S/dy2)^2]dxdy |
|
|
//| [ ] |
|
|
//| here integral is calculated over entire grid, and "~" means |
|
|
//| "proportional" because integral is normalized after calcilation. |
|
|
//| Extremely large values of Lambda result in linear fit being |
|
|
//| performed. |
|
|
//| NOTE: this algorithm is the most robust and controllable one, but|
|
|
//| it is limited by 512x512 grids and (say) up to 1.000.000 |
|
|
//| points. However, ALGLIB has one more spline solver: FastDDM|
|
|
//| algorithm, which is intended for really large-scale |
|
|
//| problems (in 10M-100M range). FastDDM algorithm also has |
|
|
//| better parallelism properties. |
|
|
//| More information on BlockLLS solver: |
|
|
//| * memory requirements: ~[32*K^3+256*NPoints] bytes for KxK grid|
|
|
//| with NPoints-sized dataset |
|
|
//| * serial running time: O(K^4+NPoints) |
|
|
//| * parallelism potential: limited. You may get some sublinear |
|
|
//| gain when working with large grids (K's in 256..512 range) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| LambdaNS - non-negative value: |
|
|
//| * positive value means that some smoothing is |
|
|
//| applied |
|
|
//| * zero value means that no smoothing is applied, |
|
|
//| and corresponding entries of design matrix are |
|
|
//| numerically zero and dropped from consideration. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetAlgoBlockLLS(CSpline2DBuilder &State,
|
|
double lambdans)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is not finite value"))
|
|
return;
|
|
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0"))
|
|
return;
|
|
|
|
State.m_solvertype=1;
|
|
State.m_smoothing=lambdans;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function allows you to choose least squares solver used to |
|
|
//| perform fitting. This function sets solver algorithm to |
|
|
//| "NaiveLLS". |
|
|
//| IMPORTANT: NaiveLLS is NOT intended to be used in real life code!|
|
|
//| This algorithm solves problem by generated dense |
|
|
//| (K^2)x(K^2+NPoints) matrix and solves linear least |
|
|
//| squares problem with dense solver. |
|
|
//| It is here just to test BlockLLS against reference |
|
|
//| solver (and maybe for someone trying to compare well |
|
|
//| optimized solver against straightforward approach to |
|
|
//| the LLS problem). |
|
|
//| More information on naive LLS solver: |
|
|
//| * memory requirements: ~[8*K^4+256*NPoints] bytes for KxK grid.|
|
|
//| * serial running time: O(K^6+NPoints) for KxK grid |
|
|
//| * when compared with BlockLLS, NaiveLLS has ~K larger memory |
|
|
//| demand and ~K^2 larger running time. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - spline 2D builder object |
|
|
//| LambdaNS - nonsmoothness penalty |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DBuilderSetAlgoNaiveLLS(CSpline2DBuilder &State,
|
|
double lambdans)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdans),__FUNCTION__+": LambdaNS is not finite value"))
|
|
return;
|
|
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": LambdaNS<0"))
|
|
return;
|
|
|
|
State.m_solvertype=2;
|
|
State.m_smoothing=lambdans;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fits bicubic spline to current dataset, using |
|
|
//| current area/grid and current LLS solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| State - spline 2D builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - 2D spline, fit result |
|
|
//| Rep - fitting report, which provides some additional info|
|
|
//| about errors, R2 coefficient and so on. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DFit(CSpline2DBuilder &State,
|
|
CSpline2DInterpolant &s,
|
|
CSpline2DFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
double xa=0;
|
|
double xb=0;
|
|
double ya=0;
|
|
double yb=0;
|
|
double xaraw=0;
|
|
double xbraw=0;
|
|
double yaraw=0;
|
|
double ybraw=0;
|
|
int kx=0;
|
|
int ky=0;
|
|
double hx=0;
|
|
double hy=0;
|
|
double invhx=0;
|
|
double invhy=0;
|
|
int gridexpansion=0;
|
|
int nzwidth=0;
|
|
int bfrad=0;
|
|
int npoints=0;
|
|
int d=0;
|
|
int ew=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double vx=0;
|
|
double vy=0;
|
|
int arows=0;
|
|
int acopied=0;
|
|
int basecasex=0;
|
|
int basecasey=0;
|
|
double eps=0;
|
|
CRowDouble xywork;
|
|
CMatrixDouble vterm;
|
|
double tmpx[];
|
|
double tmpy[];
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
CRowDouble meany;
|
|
CRowInt xyindex;
|
|
CRowInt tmpi;
|
|
CSpline1DInterpolant basis1;
|
|
CSparseMatrix av;
|
|
CSparseMatrix ah;
|
|
CSpline2DXDesignMatrix xdesignmatrix;
|
|
CRowDouble z;
|
|
CSpline2DBlockLLSBuf blockllsbuf;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double tss=0;
|
|
int dstidx=0;
|
|
|
|
nzwidth=4;
|
|
bfrad=2;
|
|
npoints=State.m_npoints;
|
|
d=State.m_d;
|
|
ew=2+d;
|
|
//---Integrity checks
|
|
if(!CAp::Assert(State.m_sx==1.0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(State.m_sy==1.0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
//---Determine actual area size and grid step
|
|
//---NOTE: initialize vars by zeros in order to avoid spurious
|
|
//--- compiler warnings.
|
|
xa=0;
|
|
xb=0;
|
|
ya=0;
|
|
yb=0;
|
|
if(State.m_areatype==0)
|
|
{
|
|
if(npoints>0)
|
|
{
|
|
xa=State.m_xy[0];
|
|
xb=State.m_xy[0];
|
|
ya=State.m_xy[1];
|
|
yb=State.m_xy[1];
|
|
for(i=1; i<npoints; i++)
|
|
{
|
|
xa=MathMin(xa,State.m_xy[i*ew+0]);
|
|
xb=MathMax(xb,State.m_xy[i*ew+0]);
|
|
ya=MathMin(ya,State.m_xy[i*ew+1]);
|
|
yb=MathMax(yb,State.m_xy[i*ew+1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xa=-1;
|
|
xb=1;
|
|
ya=-1;
|
|
yb=1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(State.m_areatype==1)
|
|
{
|
|
xa=State.m_xa;
|
|
xb=State.m_xb;
|
|
ya=State.m_ya;
|
|
yb=State.m_yb;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
if(xa==xb)
|
|
{
|
|
v=xa;
|
|
if(v>=0.0)
|
|
{
|
|
xa=v/2-1;
|
|
xb=v*2+1;
|
|
}
|
|
else
|
|
{
|
|
xa=v*2-1;
|
|
xb=v/2+1;
|
|
}
|
|
}
|
|
if(ya==yb)
|
|
{
|
|
v=ya;
|
|
if(v>=0.0)
|
|
{
|
|
ya=v/2-1;
|
|
yb=v*2+1;
|
|
}
|
|
else
|
|
{
|
|
ya=v*2-1;
|
|
yb=v/2+1;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(xa<xb,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(ya<yb,__FUNCTION__+": integrity error"))
|
|
return;
|
|
kx=0;
|
|
ky=0;
|
|
switch(State.m_gridtype)
|
|
{
|
|
case 0:
|
|
kx=4;
|
|
ky=4;
|
|
break;
|
|
case 1:
|
|
kx=State.m_kx;
|
|
ky=State.m_ky;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(kx>0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(ky>0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
basecasex=-1;
|
|
basecasey=-1;
|
|
if(State.m_solvertype==3)
|
|
{
|
|
//---Large-scale solver with special requirements to grid size.
|
|
kx=MathMax(kx,nzwidth);
|
|
ky=MathMax(ky,nzwidth);
|
|
k=1;
|
|
while(MathMin(kx,ky)>State.m_maxcoresize+1)
|
|
{
|
|
kx=CApServ::IDivUp(kx-1,2)+1;
|
|
ky=CApServ::IDivUp(ky-1,2)+1;
|
|
k++;
|
|
}
|
|
basecasex=kx-1;
|
|
k0=1;
|
|
while(kx>State.m_maxcoresize+1)
|
|
{
|
|
basecasex=CApServ::IDivUp(kx-1,2);
|
|
kx=basecasex+1;
|
|
k0++;
|
|
}
|
|
while(k0>1)
|
|
{
|
|
kx=(kx-1)*2+1;
|
|
k0--;
|
|
}
|
|
basecasey=ky-1;
|
|
k1=1;
|
|
while(ky>State.m_maxcoresize+1)
|
|
{
|
|
basecasey=CApServ::IDivUp(ky-1,2);
|
|
ky=basecasey+1;
|
|
k1++;
|
|
}
|
|
while(k1>1)
|
|
{
|
|
ky=(ky-1)*2+1;
|
|
k1--;
|
|
}
|
|
while(k>1)
|
|
{
|
|
kx=(kx-1)*2+1;
|
|
ky=(ky-1)*2+1;
|
|
k--;
|
|
}
|
|
//---Grid is NOT expanded. We have very strict requirements on
|
|
//---grid size, and we do not want to overcomplicate it by
|
|
//---playing with grid size in order to add one more degree of
|
|
//---freedom. It is not relevant for such large tasks.
|
|
gridexpansion=0;
|
|
}
|
|
else
|
|
{
|
|
//---Medium-scale solvers which are tolerant to grid size.
|
|
kx=MathMax(kx,nzwidth);
|
|
ky=MathMax(ky,nzwidth);
|
|
//---Grid is expanded by 1 in order to add one more effective degree
|
|
//---of freedom to the spline. Having additional nodes outside of the
|
|
//---area allows us to emulate changes in the derivative at the bound
|
|
//---without having specialized "boundary" version of the basis function.
|
|
if(State.m_adddegreeoffreedom)
|
|
gridexpansion=1;
|
|
else
|
|
gridexpansion=0;
|
|
}
|
|
hx=CApServ::Coalesce(xb-xa,1.0)/(kx-1);
|
|
hy=CApServ::Coalesce(yb-ya,1.0)/(ky-1);
|
|
invhx=1/hx;
|
|
invhy=1/hy;
|
|
//---We determined "raw" grid size. Now perform a grid correction according
|
|
//---to current grid expansion size.
|
|
xaraw=xa;
|
|
yaraw=ya;
|
|
xbraw=xb;
|
|
ybraw=yb;
|
|
xa-=hx*gridexpansion;
|
|
ya-=hy*gridexpansion;
|
|
xb+=hx*gridexpansion;
|
|
yb+=hy*gridexpansion;
|
|
kx+=2*gridexpansion;
|
|
ky+=2*gridexpansion;
|
|
//---Create output spline using transformed (unit-scale)
|
|
//---coordinates, fill by zero values
|
|
s.m_d=d;
|
|
s.m_n=kx;
|
|
s.m_m=ky;
|
|
s.m_stype=-3;
|
|
sfx=s.m_n*s.m_m*d;
|
|
sfy=2*s.m_n*s.m_m*d;
|
|
sfxy=3*s.m_n*s.m_m*d;
|
|
s.m_x.Resize(s.m_n);
|
|
s.m_y.Resize(s.m_m);
|
|
s.m_f=vector<double>::Zeros(4*s.m_n*s.m_m*d);
|
|
for(i=0; i<s.m_n; i++)
|
|
s.m_x.Set(i,i);
|
|
for(i=0; i<s.m_m; i++)
|
|
s.m_y.Set(i,i);
|
|
//---Create local copy of dataset (only points in the grid are copied;
|
|
//---we allow small step out of the grid, by Eps*H, in order to deal
|
|
//---with numerical rounding errors).
|
|
//---An additional copy of Y-values is created at columns beyond 2+J;
|
|
//---it is preserved during all transformations. This copy is used
|
|
//---to calculate error-related metrics.
|
|
//---Calculate mean(Y), TSS
|
|
meany=vector<double>::Zeros(d);
|
|
CApServ::RVectorSetLengthAtLeast(xywork,npoints*ew);
|
|
acopied=0;
|
|
eps=1.0E-6;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
vx=State.m_xy[i*ew+0];
|
|
vy=State.m_xy[i*ew+1];
|
|
if((xaraw-eps*hx)<=vx && vx<=(xbraw+eps*hx) && (yaraw-eps*hy)<=vy && vy<=(ybraw+eps*hy))
|
|
{
|
|
xywork.Set(acopied*ew+0,(vx-xa)*invhx);
|
|
xywork.Set(acopied*ew+1,(vy-ya)*invhy);
|
|
for(j=0; j<=d-1; j++)
|
|
{
|
|
v=State.m_xy[i*ew+2+j];
|
|
xywork.Set(acopied*ew+2+j,v);
|
|
meany.Add(j,v);
|
|
}
|
|
acopied++;
|
|
}
|
|
}
|
|
npoints=acopied;
|
|
meany/=CApServ::Coalesce(npoints,1);
|
|
tss=0.0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<d; j++)
|
|
tss+=CMath::Sqr(xywork[i*ew+2+j]-meany[j]);
|
|
}
|
|
tss=CApServ::Coalesce(tss,1.0);
|
|
//---Handle prior term.
|
|
//---Modify output spline.
|
|
//---Quick exit if dataset is empty.
|
|
CIntFitServ::BuildPriorTerm1(xywork,npoints,2,d,State.m_priorterm,State.m_priortermval,vterm);
|
|
if(npoints==0)
|
|
{
|
|
//---Quick exit
|
|
for(k=0; k<s.m_n*s.m_m; k++)
|
|
{
|
|
k0=k%s.m_n;
|
|
k1=k/s.m_n;
|
|
for(j=0; j<d; j++)
|
|
{
|
|
dstidx=d*(k1*s.m_n+k0)+j;
|
|
s.m_f.Add(dstidx,vterm.Get(j,0)*s.m_x[k0]+vterm.Get(j,1)*s.m_y[k1]+vterm.Get(j,2));
|
|
s.m_f.Add(sfx+dstidx,vterm.Get(j,0));
|
|
s.m_f.Add(sfy+dstidx,vterm.Get(j,1));
|
|
}
|
|
}
|
|
s.m_x=s.m_x*hx+xa;
|
|
s.m_y=s.m_y*hy+ya;
|
|
for(i=0; i<=s.m_n*s.m_m*d-1; i++)
|
|
{
|
|
s.m_f.Mul(sfx+i,invhx);
|
|
s.m_f.Mul(sfy+i,invhy);
|
|
s.m_f.Mul(sfxy+i,invhx*invhy);
|
|
}
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_r2=1.0;
|
|
return;
|
|
}
|
|
//---Build 1D compact basis function
|
|
//---Generate design matrix
|
|
ArrayResize(tmpx,7);
|
|
ArrayResize(tmpy,7);
|
|
tmpx[0]=-3;
|
|
tmpx[1]=-2;
|
|
tmpx[2]=-1;
|
|
tmpx[3]=0;
|
|
tmpx[4]=1;
|
|
tmpx[5]=2;
|
|
tmpx[6]=3;
|
|
tmpy[0]=0;
|
|
tmpy[1]=0;
|
|
tmpy[2]=1.0/12.0;
|
|
tmpy[3]=2.0/6.0;
|
|
tmpy[4]=1.0/12.0;
|
|
tmpy[5]=0;
|
|
tmpy[6]=0;
|
|
CSpline1D::Spline1DBuildCubic(tmpx,tmpy,CAp::Len(tmpx),2,0.0,2,0.0,basis1);
|
|
//---Solve.
|
|
//---Update spline.
|
|
switch(State.m_solvertype)
|
|
{
|
|
case 1:
|
|
//---BlockLLS
|
|
ReorderDatasetAndBuildIndex(xywork,npoints,d,tmp0,0,kx,ky,xyindex,tmpi);
|
|
XDesignGenerate(xywork,xyindex,0,kx,kx,0,ky,ky,d,m_lambdaregblocklls,State.m_smoothing,basis1,xdesignmatrix);
|
|
BlockLLSFit(xdesignmatrix,State.m_lsqrcnt,z,rep,tss,blockllsbuf);
|
|
UpdateSplineTable(z,kx,ky,d,basis1,bfrad,s.m_f,s.m_m,s.m_n,1);
|
|
break;
|
|
case 2:
|
|
//---NaiveLLS, reference implementation
|
|
GenerateDesignMatrix(xywork,npoints,d,kx,ky,State.m_smoothing,m_lambdaregblocklls,basis1,av,ah,arows);
|
|
NaiveLLSFit(av,ah,arows,xywork,kx,ky,npoints,d,State.m_lsqrcnt,z,rep,tss);
|
|
UpdateSplineTable(z,kx,ky,d,basis1,bfrad,s.m_f,s.m_m,s.m_n,1);
|
|
break;
|
|
case 3:
|
|
//---FastDDM method
|
|
if(!CAp::Assert(basecasex>0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(basecasey>0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
FastDDMFit(xywork,npoints,d,kx,ky,basecasex,basecasey,State.m_maxcoresize,State.m_interfacesize,State.m_nlayers,State.m_smoothing,State.m_lsqrcnt,basis1,s,rep,tss);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity error");
|
|
return;
|
|
}
|
|
//---Append prior term.
|
|
//---Transform spline to original coordinates
|
|
for(k=0; k<s.m_n*s.m_m; k++)
|
|
{
|
|
k0=k%s.m_n;
|
|
k1=k/s.m_n;
|
|
for(j=0; j<d; j++)
|
|
{
|
|
dstidx=d*(k1*s.m_n+k0)+j;
|
|
s.m_f.Add(dstidx,vterm.Get(j,0)*s.m_x[k0]+vterm.Get(j,1)*s.m_y[k1]+vterm.Get(j,2));
|
|
s.m_f.Add(sfx+dstidx,vterm.Get(j,0));
|
|
s.m_f.Add(sfy+dstidx,vterm.Get(j,1));
|
|
}
|
|
}
|
|
s.m_x=s.m_x*hx+xa;
|
|
s.m_y=s.m_y*hy+ya;
|
|
for(i=0; i<s.m_n*s.m_m*d; i++)
|
|
{
|
|
s.m_f.Mul(sfx+i,invhx);
|
|
s.m_f.Mul(sfy+i,invhy);
|
|
s.m_f.Mul(sfxy+i,invhx*invhy);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DAlloc(CSerializer &s,CSpline2DInterpolant &spline)
|
|
{
|
|
//---Header
|
|
s.Alloc_Entry();
|
|
//---Data
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,spline.m_x,-1);
|
|
CApServ::AllocRealArray(s,spline.m_y,-1);
|
|
CApServ::AllocRealArray(s,spline.m_f,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DSerialize(CSerializer &s,
|
|
CSpline2DInterpolant &spline)
|
|
{
|
|
//---Header
|
|
s.Serialize_Int(CSCodes::GetSpline2DSerializationCode());
|
|
//---Data
|
|
s.Serialize_Int(spline.m_stype);
|
|
s.Serialize_Int(spline.m_n);
|
|
s.Serialize_Int(spline.m_m);
|
|
s.Serialize_Int(spline.m_d);
|
|
CApServ::SerializeRealArray(s,spline.m_x,-1);
|
|
CApServ::SerializeRealArray(s,spline.m_y,-1);
|
|
CApServ::SerializeRealArray(s,spline.m_f,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::Spline2DUnserialize(CSerializer &s,
|
|
CSpline2DInterpolant &spline)
|
|
{
|
|
//---Header
|
|
int scode=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(scode==CSCodes::GetSpline2DSerializationCode(),__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
//---Data
|
|
spline.m_stype=s.Unserialize_Int();
|
|
spline.m_n=s.Unserialize_Int();
|
|
spline.m_m=s.Unserialize_Int();
|
|
spline.m_d=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,spline.m_x);
|
|
CApServ::UnserializeRealArray(s,spline.m_y);
|
|
CApServ::UnserializeRealArray(s,spline.m_f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. |
|
|
//| Calculation of the first derivatives and the cross-derivative. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::BicubicCalcDerivatives(CMatrixDouble &a,CRowDouble &x,
|
|
CRowDouble &y,const int m,
|
|
const int n,CMatrixDouble &dx,
|
|
CMatrixDouble &dy,CMatrixDouble &dxy)
|
|
{
|
|
//--- create variables
|
|
double s=0;
|
|
double ds=0;
|
|
double d2s=0;
|
|
//--- create arrays
|
|
double xt[];
|
|
double ft[];
|
|
//--- object of class
|
|
CSpline1DInterpolant c;
|
|
//--- allocation
|
|
dx.Resize(m,n);
|
|
dy.Resize(m,n);
|
|
dxy.Resize(m,n);
|
|
//--- dF/dX
|
|
ArrayResize(xt,n);
|
|
ArrayResize(ft,n);
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
xt[j]=x[j];
|
|
ft[j]=a.Get(i,j);
|
|
}
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(xt,ft,n,0,0.0,0,0.0,c);
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(c,x[j],s,ds,d2s);
|
|
dx.Set(i,j,ds);
|
|
}
|
|
}
|
|
//--- dF/dY
|
|
ArrayResize(xt,m);
|
|
ArrayResize(ft,m);
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
for(int i=0; i<m; i++)
|
|
{
|
|
xt[i]=y[i];
|
|
ft[i]=a.Get(i,j);
|
|
}
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(xt,ft,m,0,0.0,0,0.0,c);
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(c,y[i],s,ds,d2s);
|
|
dy.Set(i,j,ds);
|
|
}
|
|
}
|
|
//--- d2F/dXdY
|
|
ArrayResize(xt,n);
|
|
ArrayResize(ft,n);
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
xt[j]=x[j];
|
|
ft[j]=dy.Get(i,j);
|
|
}
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(xt,ft,n,0,0.0,0,0.0,c);
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- function call
|
|
CSpline1D::Spline1DDiff(c,x[j],s,ds,d2s);
|
|
dxy.Set(i,j,ds);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates design matrix for the problem (in fact, |
|
|
//| two design matrices are generated: "vertical" one and transposed |
|
|
//| (horizontal) one. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints*(2+D)]; dataset after scaling in |
|
|
//| such way that grid step is equal to 1.0 in both |
|
|
//| dimensions. |
|
|
//| NPoints - dataset size, NPoints>=1 |
|
|
//| KX, KY - grid size, KX,KY>=4 |
|
|
//| Smoothing - nonlinearity penalty coefficient, >=0 |
|
|
//| LambdaReg - regularization coefficient, >=0 |
|
|
//| Basis1 - basis spline, expected to be non-zero only at |
|
|
//| [-2,+2] |
|
|
//| AV, AH - possibly preallocated buffers |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| AV - sparse matrix[ARows,KX*KY]; design matrix |
|
|
//| AH - transpose of AV |
|
|
//| ARows - number of rows in design matrix |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::GenerateDesignMatrix(CRowDouble &xy,
|
|
int npoints,
|
|
int d,
|
|
int kx,
|
|
int ky,
|
|
double smoothing,
|
|
double lambdareg,
|
|
CSpline1DInterpolant &basis1,
|
|
CSparseMatrix &av,
|
|
CSparseMatrix &ah,
|
|
int &arows)
|
|
{
|
|
//--- create variables
|
|
int nzwidth=0;
|
|
int nzshift=0;
|
|
int ew=0;
|
|
int i=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int dstidx=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double w0=0;
|
|
double w1=0;
|
|
double w2=0;
|
|
CRowInt crx;
|
|
CRowInt cry;
|
|
CRowInt nrs;
|
|
CMatrixDouble d2x;
|
|
CMatrixDouble d2y;
|
|
CMatrixDouble dxy;
|
|
|
|
arows=0;
|
|
nzwidth=4;
|
|
nzshift=1;
|
|
//--- check
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
ew=2+d;
|
|
//---Determine canonical rectangle for every point. Every point of the dataset is
|
|
//---influenced by at most NZWidth*NZWidth basis functions, which form NZWidth*NZWidth
|
|
//---canonical rectangle.
|
|
//---Thus, we have (KX-NZWidth+1)*(KY-NZWidth+1) overlapping canonical rectangles.
|
|
//---Assigning every point to its rectangle simplifies creation of sparse basis
|
|
//---matrix at the next steps.
|
|
crx.Resize(npoints);
|
|
cry.Resize(npoints);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
crx.Set(i,CApServ::BoundVal((int)MathFloor(xy[i*ew+0])-nzshift,0,kx-nzwidth));
|
|
cry.Set(i,CApServ::BoundVal((int)MathFloor(xy[i*ew+1])-nzshift,0,ky-nzwidth));
|
|
}
|
|
//---Create vertical and horizontal design matrices
|
|
arows=npoints+kx*ky;
|
|
if(smoothing!=0.0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(smoothing>0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
arows+=3*(kx-2)*(ky-2);
|
|
}
|
|
nrs.Resize(arows);
|
|
dstidx=0;
|
|
nrs.Fill(nzwidth*nzwidth,dstidx+i,npoints);
|
|
dstidx+=npoints;
|
|
nrs.Fill(1,dstidx,kx*ky);
|
|
dstidx+=kx*ky;
|
|
if(smoothing!=0.0)
|
|
{
|
|
nrs.Fill(9,dstidx,3*(kx-2)*(ky-2));
|
|
dstidx+=3*(kx-2)*(ky-2);
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(dstidx==arows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CSparse::SparseCreateCRS(arows,kx*ky,nrs,av);
|
|
dstidx=0;
|
|
for(i=0; i<npoints; i++)
|
|
for(j1=0; j1<nzwidth; j1++)
|
|
for(j0=0; j0<nzwidth; j0++)
|
|
{
|
|
v0=CSpline1D::Spline1DCalc(basis1,xy[i*ew+0]-(crx[i]+j0));
|
|
v1=CSpline1D::Spline1DCalc(basis1,xy[i*ew+1]-(cry[i]+j1));
|
|
CSparse::SparseSet(av,dstidx+i,(cry[i]+j1)*kx+(crx[i]+j0),v0*v1);
|
|
}
|
|
dstidx=dstidx+npoints;
|
|
for(i=0; i<kx*ky; i++)
|
|
CSparse::SparseSet(av,dstidx+i,i,lambdareg);
|
|
dstidx+=kx*ky;
|
|
if(smoothing!=0.0)
|
|
{
|
|
//---Smoothing is applied. Because all grid nodes are same,
|
|
//---we apply same smoothing kernel, which is calculated only
|
|
//---once at the beginning of design matrix generation.
|
|
d2x=matrix<double>::Zeros(3,3);
|
|
d2y=matrix<double>::Zeros(3,3);
|
|
dxy=matrix<double>::Zeros(3,3);
|
|
for(k1=0; k1<=2; k1++)
|
|
{
|
|
for(k0=0; k0<=2; k0++)
|
|
{
|
|
CSpline1D::Spline1DDiff(basis1,-(k0-1),v0,v1,v2);
|
|
CSpline1D::Spline1DDiff(basis1,-(k1-1),w0,w1,w2);
|
|
d2x.Add(k0,k1,v2*w0);
|
|
d2y.Add(k0,k1,w2*v0);
|
|
dxy.Add(k0,k1,v1*w1);
|
|
}
|
|
}
|
|
//---Now, kernel is ready - apply it to all inner nodes of the grid.
|
|
for(j1=1; j1<ky-1; j1++)
|
|
{
|
|
for(j0=1; j0<kx-1; j0++)
|
|
{
|
|
//---d2F/dx2 term
|
|
v=smoothing;
|
|
for(k1=-1; k1<=1; k1++)
|
|
for(k0=-1; k0<=1; k0++)
|
|
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*d2x.Get(1+k0,1+k1));
|
|
dstidx++;
|
|
//---d2F/dy2 term
|
|
v=smoothing;
|
|
for(k1=-1; k1<=1; k1++)
|
|
for(k0=-1; k0<=1; k0++)
|
|
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*d2y.Get(1+k0,1+k1));
|
|
dstidx++;
|
|
//---2*d2F/dxdy term
|
|
v=MathSqrt(2)*smoothing;
|
|
for(k1=-1; k1<=1; k1++)
|
|
for(k0=-1; k0<=1; k0++)
|
|
CSparse::SparseSet(av,dstidx,(j1+k1)*kx+(j0+k0),v*dxy.Get(1+k0,1+k1));
|
|
dstidx=dstidx+1;
|
|
}
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(dstidx==arows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CSparse::SparseCopy(av,ah);
|
|
CSparse::SparseTransposeCRS(ah);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function updates table of spline values/derivatives using |
|
|
//| coefficients for a layer of basis functions. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::UpdateSplineTable(CRowDouble &z,
|
|
int kx,
|
|
int ky,
|
|
int d,
|
|
CSpline1DInterpolant &basis1,
|
|
int bfrad,
|
|
CRowDouble &ftbl,
|
|
int m,
|
|
int n,
|
|
int scalexy)
|
|
{
|
|
//--- create variables
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int j0a=0;
|
|
int j0b=0;
|
|
int j1a=0;
|
|
int j1b=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v01=0;
|
|
double v11=0;
|
|
double rdummy=0;
|
|
int dstidx=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double invscalexy=0;
|
|
//--- check
|
|
if(!CAp::Assert(n==(kx-1)*scalexy+1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(m==(ky-1)*scalexy+1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
invscalexy=1.0/(double)scalexy;
|
|
sfx=n*m*d;
|
|
sfy=2*n*m*d;
|
|
sfxy=3*n*m*d;
|
|
for(k=0; k<kx*ky; k++)
|
|
{
|
|
k0=k%kx;
|
|
k1=k/kx;
|
|
j0a=CApServ::BoundVal(k0*scalexy-(bfrad*scalexy-1),0,n-1);
|
|
j0b=CApServ::BoundVal(k0*scalexy+(bfrad*scalexy-1),0,n-1);
|
|
j1a=CApServ::BoundVal(k1*scalexy-(bfrad*scalexy-1),0,m-1);
|
|
j1b=CApServ::BoundVal(k1*scalexy+(bfrad*scalexy-1),0,m-1);
|
|
for(j1=j1a; j1<=j1b; j1++)
|
|
{
|
|
CSpline1D::Spline1DDiff(basis1,(j1-k1*scalexy)*invscalexy,v1,v11,rdummy);
|
|
v11=v11*invscalexy;
|
|
for(j0=j0a; j0<=j0b; j0++)
|
|
{
|
|
CSpline1D::Spline1DDiff(basis1,(j0-k0*scalexy)*invscalexy,v0,v01,rdummy);
|
|
v01*=invscalexy;
|
|
for(j=0; j<=d-1; j++)
|
|
{
|
|
dstidx=d*(j1*n+j0)+j;
|
|
v=z[j*kx*ky+k];
|
|
ftbl.Add(dstidx,v0*v1*v);
|
|
ftbl.Add(sfx+dstidx,v01*v1*v);
|
|
ftbl.Add(sfy+dstidx,v0*v11*v);
|
|
ftbl.Add(sfxy+dstidx,v01*v11*v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs fitting with FastDDM solver. |
|
|
//| Internal function, never use it directly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints*(2+D)], dataset; destroyed in process|
|
|
//| KX, KY - grid size |
|
|
//| TileSize - tile size |
|
|
//| InterfaceSize - interface size |
|
|
//| NPoints - points count |
|
|
//| D - number of components in vector-valued spline, D>=1 |
|
|
//| LSQRCnt - number of iterations, non-zero: |
|
|
//| * LSQRCnt>0 means that specified amount of |
|
|
//| preconditioned LSQR iterations will be performed |
|
|
//| to solve problem; usually we need 2..5 its. |
|
|
//| Recommended option - best convergence and |
|
|
//| stability/quality. |
|
|
//| * LSQRCnt<0 means that instead of LSQR we use |
|
|
//| iterative refinement on normal equations. Again, |
|
|
//| 2..5 its is enough. |
|
|
//| Basis1 - basis spline, expected to be non-zero only |
|
|
//| at [-2,+2] |
|
|
//| Z - possibly preallocated buffer for solution |
|
|
//| Residuals- possibly preallocated buffer for residuals at |
|
|
//| dataset points |
|
|
//| Rep - report structure; fields which are not set by this |
|
|
//| function are left intact |
|
|
//| TSS - total sum of squares; used to calculate R2 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XY - destroyed in process |
|
|
//| Z - array[KX*KY*D], filled by solution; KX*KY |
|
|
//| coefficients corresponding to each of D dimensions |
|
|
//| are stored contiguously. |
|
|
//| Rep - following fields are set: |
|
|
//| * Rep.m_rmserror |
|
|
//| * Rep.AvgError |
|
|
//| * Rep.MaxError |
|
|
//| * Rep.R2 |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::FastDDMFit(CRowDouble &xy,
|
|
int npoints,
|
|
int d,
|
|
int kx,
|
|
int ky,
|
|
int basecasex,
|
|
int basecasey,
|
|
int maxcoresize,
|
|
int interfacesize,
|
|
int nlayers,
|
|
double smoothing,
|
|
int lsqrcnt,
|
|
CSpline1DInterpolant &basis1,
|
|
CSpline2DInterpolant &spline,
|
|
CSpline2DFitReport &rep,
|
|
double tss)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int nzwidth=0;
|
|
int xew=0;
|
|
int ntotallayers=0;
|
|
int scaleidx=0;
|
|
int scalexy=0;
|
|
double invscalexy=0;
|
|
int kxcur=0;
|
|
int kycur=0;
|
|
int tilescount0=0;
|
|
int tilescount1=0;
|
|
double v=0;
|
|
double rss=0;
|
|
CRowDouble yraw;
|
|
CRowInt xyindex;
|
|
CRowDouble tmp0;
|
|
CRowInt bufi;
|
|
CSpline2DFastDDMBuf seed;
|
|
CSpline2DFastDDMBuf pool;
|
|
CSpline2DXDesignMatrix xdesignmatrix;
|
|
CSpline2DBlockLLSBuf blockllsbuf;
|
|
CSpline2DFitReport dummyrep;
|
|
//---Dataset metrics and integrity checks
|
|
nzwidth=4;
|
|
xew=2+d;
|
|
//--- check
|
|
if(!CAp::Assert(maxcoresize>=2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(interfacesize>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Verify consistency of the grid size (KX,KY) with basecase sizes.
|
|
//---Determine full number of layers.
|
|
if(!CAp::Assert(basecasex<=maxcoresize,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(basecasey<=maxcoresize,__FUNCTION__+": integrity error"))
|
|
return;
|
|
ntotallayers=1;
|
|
scalexy=1;
|
|
kxcur=kx;
|
|
kycur=ky;
|
|
while(kxcur>basecasex+1 && kycur>basecasey+1)
|
|
{
|
|
if(!CAp::Assert(kxcur%2==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(kycur%2==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
kxcur=(kxcur-1)/2+1;
|
|
kycur=(kycur-1)/2+1;
|
|
scalexy=scalexy*2;
|
|
ntotallayers++;
|
|
}
|
|
invscalexy=1.0/(double)scalexy;
|
|
if(!CAp::Assert((kxcur<=maxcoresize+1 && kxcur==basecasex+1) || kxcur%basecasex==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert((kycur<=maxcoresize+1 && kycur==basecasey+1) || kycur%basecasey==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(kxcur==basecasex+1 || kycur==basecasey+1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
//---Initial scaling of dataset.
|
|
//---Store original target values to YRaw.
|
|
CApServ::RVectorSetLengthAtLeast(yraw,npoints*d);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
xy.Mul(xew*i,invscalexy);
|
|
xy.Mul(xew*i+1,invscalexy);
|
|
for(j=0; j<d; j++)
|
|
yraw.Set(i*d+j,xy[xew*i+2+j]);
|
|
}
|
|
kxcur=(kx-1)/scalexy+1;
|
|
kycur=(ky-1)/scalexy+1;
|
|
//---Build initial dataset index; area is divided into (KXCur-1)*(KYCur-1)
|
|
//---cells, with contiguous storage of points in the same cell.
|
|
//---Iterate over different scales
|
|
pool=seed;
|
|
ReorderDatasetAndBuildIndex(xy,npoints,d,yraw,d,kxcur,kycur,xyindex,bufi);
|
|
for(scaleidx=ntotallayers-1; scaleidx>=0; scaleidx--)
|
|
{
|
|
if((nlayers>0 && scaleidx<nlayers) || (nlayers<=0 && scaleidx<MathMax(ntotallayers+nlayers,1)))
|
|
{
|
|
//---Fit current layer
|
|
if(!CAp::Assert(kxcur%basecasex==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(kycur%basecasey==1,__FUNCTION__+": integrity error"))
|
|
return;
|
|
tilescount0=kxcur/basecasex;
|
|
tilescount1=kycur/basecasey;
|
|
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,0,tilescount0,tilescount0,basecasey,0,tilescount1,tilescount1,maxcoresize,interfacesize,lsqrcnt,m_lambdaregfastddm+smoothing*MathPow(m_lambdadecay,scaleidx),basis1,pool,spline);
|
|
//---Compute residuals and update XY
|
|
ComputeResidualsFromScratch(xy,yraw,npoints,d,scalexy,spline);
|
|
}
|
|
//---Move to the next level
|
|
if(scaleidx!=0)
|
|
{
|
|
//---Transform dataset (multply everything by 2.0) and refine grid.
|
|
kxcur=2*kxcur-1;
|
|
kycur=2*kycur-1;
|
|
scalexy=scalexy/2;
|
|
invscalexy=1.0/(double)scalexy;
|
|
RescaleDatasetAndRefineIndex(xy,npoints,d,yraw,d,kxcur,kycur,xyindex,bufi);
|
|
}
|
|
}
|
|
//---Post-check
|
|
if(!CAp::Assert(kxcur==kx,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(kycur==ky,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(scalexy==1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Report
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rss=0.0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<=d-1; j++)
|
|
{
|
|
v=xy[i*xew+2+j];
|
|
rss=rss+v*v;
|
|
rep.m_rmserror+=CMath::Sqr(v);
|
|
rep.m_avgerror+=MathAbs(v);
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(npoints*d,1.0));
|
|
rep.m_avgerror=rep.m_avgerror/CApServ::Coalesce(npoints*d,1.0);
|
|
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive fitting function for FastDDM algorithm. |
|
|
//| Works with KX*KY grid, with KX=BasecaseX*TilesCountX+1 and |
|
|
//| KY=BasecaseY*TilesCountY+1, which is partitioned into |
|
|
//| TilesCountX*TilesCountY tiles, each having size |
|
|
//| BasecaseX*BasecaseY. |
|
|
//| This function processes tiles in range |
|
|
//| [TileX0,TileX1)x[TileY0,TileY1) and recursively divides this |
|
|
//| range until we move down to single tile, which is processed with |
|
|
//| BlockLLS solver. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::FastDDMFitLayer(CRowDouble &xy,int d,int scalexy,
|
|
CRowInt &xyindex,int basecasex,
|
|
int tilex0,int tilex1,int tilescountx,
|
|
int basecasey,int tiley0,int tiley1,
|
|
int tilescounty,int maxcoresize,
|
|
int interfacesize,int lsqrcnt,
|
|
double lambdareg,
|
|
CSpline1DInterpolant &basis1,
|
|
CSpline2DFastDDMBuf &pool,
|
|
CSpline2DInterpolant &spline)
|
|
{
|
|
//--- create variables
|
|
int kx=0;
|
|
int ky=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int bfrad=0;
|
|
int xa=0;
|
|
int xb=0;
|
|
int ya=0;
|
|
int yb=0;
|
|
int tile0=0;
|
|
int tile1=0;
|
|
int tilesize0=0;
|
|
int tilesize1=0;
|
|
int sfx=0;
|
|
int sfy=0;
|
|
int sfxy=0;
|
|
double dummytss=0;
|
|
double invscalexy=0;
|
|
int cnt0=0;
|
|
int cnt1=0;
|
|
int offs=0;
|
|
double vs=0;
|
|
double vsx=0;
|
|
double vsy=0;
|
|
double vsxy=0;
|
|
CSpline2DFastDDMBuf buf;
|
|
//---Dataset metrics and fast integrity checks;
|
|
//---no code with side effects is allowed before parallel split.
|
|
bfrad=2;
|
|
invscalexy=1.0/(double)scalexy;
|
|
kx=basecasex*tilescountx+1;
|
|
ky=basecasey*tilescounty+1;
|
|
|
|
if(MathMax(tiley1-tiley0,tilex1-tilex0)>=2)
|
|
{
|
|
if(tiley1-tiley0>tilex1-tilex0)
|
|
{
|
|
//---Split problem in Y dimension
|
|
//---NOTE: recursive calls to FastDDMFitLayer() compute
|
|
//--- residuals in the inner cells defined by XYIndex[],
|
|
//--- but we still have to compute residuals for cells
|
|
//--- BETWEEN two recursive subdivisions of the task.
|
|
CApServ::TiledSplit(tiley1-tiley0,1,j0,j1);
|
|
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex1,tilescountx,basecasey,tiley0,tiley0+j0,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
|
|
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex1,tilescountx,basecasey,tiley0+j0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
|
|
}
|
|
else
|
|
{
|
|
//---Split problem in X dimension
|
|
//---NOTE: recursive calls to FastDDMFitLayer() compute
|
|
//--- residuals in the inner cells defined by XYIndex[],
|
|
//--- but we still have to compute residuals for cells
|
|
//--- BETWEEN two recursive subdivisions of the task.
|
|
CApServ::TiledSplit(tilex1-tilex0,1,j0,j1);
|
|
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex0+j0,tilescountx,basecasey,tiley0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
|
|
FastDDMFitLayer(xy,d,scalexy,xyindex,basecasex,tilex0+j0,tilex1,tilescountx,basecasey,tiley0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline);
|
|
}
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(tiley0==tiley1-1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(tilex0==tilex1-1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
tile1=tiley0;
|
|
tile0=tilex0;
|
|
//---Retrieve temporaries
|
|
buf=pool;
|
|
//---Analyze dataset
|
|
xa=CApServ::BoundVal(tile0*basecasex-interfacesize,0,kx);
|
|
xb=CApServ::BoundVal((tile0+1)*basecasex+interfacesize,0,kx);
|
|
ya=CApServ::BoundVal(tile1*basecasey-interfacesize,0,ky);
|
|
yb=CApServ::BoundVal((tile1+1)*basecasey+interfacesize,0,ky);
|
|
tilesize0=xb-xa;
|
|
tilesize1=yb-ya;
|
|
//---Solve current chunk with BlockLLS
|
|
dummytss=1.0;
|
|
XDesignGenerate(xy,xyindex,xa,xb,kx,ya,yb,ky,d,lambdareg,0.0,basis1,buf.m_xdesignmatrix);
|
|
BlockLLSFit(buf.m_xdesignmatrix,lsqrcnt,buf.m_tmpz,buf.m_dummyrep,dummytss,buf.m_blockllsbuf);
|
|
buf.m_localmodel.m_d=d;
|
|
buf.m_localmodel.m_m=tilesize1;
|
|
buf.m_localmodel.m_n=tilesize0;
|
|
buf.m_localmodel.m_stype=-3;
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_x,tilesize0);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_y,tilesize1);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_localmodel.m_f,tilesize0*tilesize1*d*4);
|
|
for(i=0; i<=tilesize0-1; i++)
|
|
buf.m_localmodel.m_x.Set(i,xa+i);
|
|
for(i=0; i<=tilesize1-1; i++)
|
|
buf.m_localmodel.m_y.Set(i,ya+i);
|
|
for(i=0; i<=tilesize0*tilesize1*d*4-1; i++)
|
|
buf.m_localmodel.m_f.Set(i,0.0);
|
|
UpdateSplineTable(buf.m_tmpz,tilesize0,tilesize1,d,basis1,bfrad,buf.m_localmodel.m_f,tilesize1,tilesize0,1);
|
|
//---Transform local spline to original coordinates
|
|
sfx=buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
|
|
sfy=2*buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
|
|
sfxy=3*buf.m_localmodel.m_n*buf.m_localmodel.m_m*d;
|
|
for(i=0; i<tilesize0; i++)
|
|
buf.m_localmodel.m_x.Mul(i,scalexy);
|
|
for(i=0; i<tilesize1; i++)
|
|
buf.m_localmodel.m_y.Mul(i,scalexy);
|
|
for(i=0; i<tilesize0*tilesize1*d; i++)
|
|
{
|
|
buf.m_localmodel.m_f.Mul(sfx+i,invscalexy);
|
|
buf.m_localmodel.m_f.Mul(sfy+i,invscalexy);
|
|
buf.m_localmodel.m_f.Mul(sfxy+i,(invscalexy*invscalexy));
|
|
}
|
|
//---Output results; for inner and topmost/leftmost tiles we output only BasecaseX*BasecaseY
|
|
//---inner elements; for rightmost/bottom ones we also output one column/row of the interface
|
|
//---part.
|
|
//---Such complexity is explained by the fact that area size (by design) is not evenly divisible
|
|
//---by the tile size; it is divisible with remainder=1, and we expect that interface size is
|
|
//---at least 1, so we can fill the missing rightmost/bottom elements of Z by the interface
|
|
//---values.
|
|
//--- check
|
|
if(!CAp::Assert(interfacesize>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
sfx=spline.m_n*spline.m_m*d;
|
|
sfy=2*spline.m_n*spline.m_m*d;
|
|
sfxy=3*spline.m_n*spline.m_m*d;
|
|
cnt0=basecasex*scalexy;
|
|
cnt1=basecasey*scalexy;
|
|
if(tile0==tilescountx-1)
|
|
cnt0++;
|
|
if(tile1==tilescounty-1)
|
|
cnt1++;
|
|
offs=d*(spline.m_n*tile1*basecasey*scalexy+tile0*basecasex*scalexy);
|
|
for(j1=0; j1<cnt1; j1++)
|
|
for(j0=0; j0<cnt0; j0++)
|
|
for(j=0; j<d; j++)
|
|
{
|
|
Spline2DDiffVi(buf.m_localmodel,tile0*basecasex*scalexy+j0,tile1*basecasey*scalexy+j1,j,vs,vsx,vsy,vsxy);
|
|
spline.m_f.Add(offs+d*(spline.m_n*j1+j0)+j,vs);
|
|
spline.m_f.Add(sfx+offs+d*(spline.m_n*j1+j0)+j,vsx);
|
|
spline.m_f.Add(sfy+offs+d*(spline.m_n*j1+j0)+j,vsy);
|
|
spline.m_f.Add(sfxy+offs+d*(spline.m_n*j1+j0)+j,vsxy);
|
|
}
|
|
//---Recycle temporaries
|
|
pool=buf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs fitting with BlockLLS solver. Internal |
|
|
//| function, never use it directly. |
|
|
//| IMPORTANT: performance and memory requirements of this function |
|
|
//| are asymmetric w.r.t. KX and KY: it has |
|
|
//| * O(KY*KX^2) memory requirements |
|
|
//| * O(KY*KX^3) running time |
|
|
//| Thus, if you have large KY and small KX, simple |
|
|
//| transposition of your dataset may give you great |
|
|
//| speedup. |
|
|
//| INPUT PARAMETERS: |
|
|
//| AV - sparse matrix, [ARows,KX*KY] in size. "Vertical" |
|
|
//| version of design matrix, rows [0,NPoints) contain |
|
|
//| values of basis functions at dataset points. Other |
|
|
//| rows are used for nonlinearity penalty and other |
|
|
//| stuff like that. |
|
|
//| AH - transpose(AV), "horizontal" version of AV |
|
|
//| ARows - rows count |
|
|
//| XY - array[NPoints*(2+D)], dataset |
|
|
//| KX, KY - grid size |
|
|
//| NPoints - points count |
|
|
//| D - number of components in vector-valued spline, D>=1 |
|
|
//| LSQRCnt - number of iterations, non-zero: |
|
|
//| * LSQRCnt>0 means that specified amount of |
|
|
//| preconditioned LSQR iterations will be performed |
|
|
//| to solve problem; usually we need 2..5 its. |
|
|
//| Recommended option - best convergence and |
|
|
//| stability/quality. |
|
|
//| * LSQRCnt<0 means that instead of LSQR we use |
|
|
//| iterative refinement on normal equations. Again, |
|
|
//| 2..5 its is enough. |
|
|
//| Z - possibly preallocated buffer for solution |
|
|
//| Rep - report structure; fields which are not set by this |
|
|
//| function are left intact |
|
|
//| TSS - total sum of squares; used to calculate R2 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XY - destroyed in process |
|
|
//| Z - array[KX*KY*D], filled by solution; KX*KY |
|
|
//| coefficients corresponding to each of D dimensions |
|
|
//| are stored contiguously. |
|
|
//| Rep - following fields are set: |
|
|
//| * Rep.RmsError |
|
|
//| * Rep.AvgError |
|
|
//| * Rep.MaxError |
|
|
//| * Rep.R2 |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::BlockLLSFit(CSpline2DXDesignMatrix &xdesign,
|
|
int lsqrcnt,
|
|
CRowDouble &z,
|
|
CSpline2DFitReport &rep,
|
|
double tss,
|
|
CSpline2DBlockLLSBuf &buf)
|
|
{
|
|
//--- create variables
|
|
int blockbandwidth=0;
|
|
int d=0;
|
|
int i=0;
|
|
int j=0;
|
|
double lambdachol=0;
|
|
double mxata;
|
|
double v=0;
|
|
int celloffset=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
double rss=0;
|
|
int arows=0;
|
|
int bw2=0;
|
|
int kx=0;
|
|
int ky=0;
|
|
//--- check
|
|
if(!CAp::Assert(xdesign.m_blockwidth==4,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
blockbandwidth=3;
|
|
d=xdesign.m_d;
|
|
arows=xdesign.m_nrows;
|
|
kx=xdesign.m_kx;
|
|
ky=xdesign.m_ky;
|
|
bw2=xdesign.m_blockwidth*xdesign.m_blockwidth;
|
|
//---Initial values for Z/Residuals
|
|
z=vector<double>::Zeros(kx*ky*d);
|
|
//---Create and factorize design matrix. Add regularizer if
|
|
//---factorization failed (happens sometimes with zero
|
|
//---smoothing and sparsely populated datasets).
|
|
//---The algorithm below is refactoring of NaiveLLS algorithm,
|
|
//---which uses sparsity properties and compressed block storage.
|
|
//---Problem sparsity pattern results in block-band-diagonal
|
|
//---matrix (block matrix with limited bandwidth, equal to 3
|
|
//---for bicubic splines). Thus, we have KY*KY blocks, each
|
|
//---of them is KX*KX in size. Design matrix is stored in
|
|
//---large NROWS*KX matrix, with NROWS=(BlockBandwidth+1)*KY*KX.
|
|
//---We use adaptation of block skyline storage format, with
|
|
//---TOWERSIZE*KX skyline bands (towers) stored sequentially;
|
|
//---here TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY
|
|
//---"towers", stored one below other, in BlockATA matrix.
|
|
//---Every "tower" is a sequence of BlockBandwidth+1 cells,
|
|
//---each of them being KX*KX in size.
|
|
lambdachol=m_cholreg;
|
|
CApServ::RMatrixSetLengthAtLeast(buf.m_blockata,(blockbandwidth+1)*ky*kx,kx);
|
|
while(true)
|
|
{
|
|
//---Parallel generation of squared design matrix.
|
|
XDesignBlockATA(xdesign,buf.m_blockata,mxata);
|
|
//---Regularization
|
|
v=CApServ::Coalesce(mxata,1.0)*lambdachol;
|
|
for(i1=0; i1<ky; i1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i1);
|
|
for(i0=0; i0<=kx-1; i0++)
|
|
buf.m_blockata.Add(celloffset+i0,i0,v);
|
|
}
|
|
//---Try Cholesky factorization.
|
|
if(!BlockLLSCholesky(buf.m_blockata,kx,ky,buf.m_trsmbuf2,buf.m_cholbuf2,buf.m_cholbuf1))
|
|
{
|
|
//---Factorization failed, increase regularizer and repeat
|
|
lambdachol=CApServ::Coalesce(10*lambdachol,1.0E-12);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
//---Solve
|
|
rss=0.0;
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
//--- check
|
|
if(!CAp::Assert(lsqrcnt>0,__FUNCTION__+": integrity failure"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_tmp0,arows);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_tmp1,kx*ky);
|
|
CLinLSQR::LinLSQRCreateBuf(arows,kx*ky,buf.m_solver);
|
|
for(j=0; j<d; j++)
|
|
{
|
|
//---Preconditioned LSQR:
|
|
//---use Cholesky factor U of squared design matrix A'*A to
|
|
//---transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
|
|
//---Preconditioned problem is solved with LSQR solver, which
|
|
//---gives superior results than normal equations.
|
|
for(i=0; i<arows; i++)
|
|
{
|
|
if(i<xdesign.m_npoints)
|
|
buf.m_tmp0.Set(i,xdesign.m_vals.Get(i,bw2+j));
|
|
else
|
|
buf.m_tmp0.Set(i,0.0);
|
|
}
|
|
CLinLSQR::LinLSQRRestart(buf.m_solver);
|
|
CLinLSQR::LinLSQRSetB(buf.m_solver,buf.m_tmp0);
|
|
CLinLSQR::LinLSQRSetCond(buf.m_solver,1.0E-14,1.0E-14,lsqrcnt);
|
|
while(CLinLSQR::LinLSQRIteration(buf.m_solver))
|
|
{
|
|
if(buf.m_solver.m_needmv)
|
|
{
|
|
//---Use Cholesky factorization of the system matrix
|
|
//---as preconditioner: solve TRSV(U,Solver.X)
|
|
for(i=0; i<kx*ky; i++)
|
|
buf.m_tmp1.Set(i,buf.m_solver.m_x[i]);
|
|
BlockLLSTrsV(buf.m_blockata,kx,ky,false,buf.m_tmp1);
|
|
//---After preconditioning is done, multiply by A
|
|
XDesignMV(xdesign,buf.m_tmp1,buf.m_solver.m_mv);
|
|
}
|
|
if(buf.m_solver.m_needmtv)
|
|
{
|
|
//---Multiply by design matrix A
|
|
XDesignMTV(xdesign,buf.m_solver.m_x,buf.m_solver.m_mtv);
|
|
//---Multiply by preconditioner: solve TRSV(U',A*Solver.X)
|
|
BlockLLSTrsV(buf.m_blockata,kx,ky,true,buf.m_solver.m_mtv);
|
|
}
|
|
}
|
|
//---Get results and post-multiply by preconditioner to get
|
|
//---original variables.
|
|
CLinLSQR::LinLSQRResults(buf.m_solver,buf.m_tmp1,buf.m_solverrep);
|
|
BlockLLSTrsV(buf.m_blockata,kx,ky,false,buf.m_tmp1);
|
|
for(i=0; i<kx*ky; i++)
|
|
z.Set(kx*ky*j+i,buf.m_tmp1[i]);
|
|
//---Calculate model values
|
|
XDesignMV(xdesign,buf.m_tmp1,buf.m_tmp0);
|
|
for(i=0; i<xdesign.m_npoints; i++)
|
|
{
|
|
v=xdesign.m_vals.Get(i,bw2+j)-buf.m_tmp0[i];
|
|
rss+=v*v;
|
|
rep.m_rmserror+=CMath::Sqr(v);
|
|
rep.m_avgerror+=MathAbs(v);
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(xdesign.m_npoints*d,1.0));
|
|
rep.m_avgerror/=CApServ::Coalesce(xdesign.m_npoints*d,1.0);
|
|
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs fitting with NaiveLLS solver. Internal |
|
|
//| function, never use it directly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| AV - sparse matrix, [ARows,KX*KY] in size. "Vertical" |
|
|
//| version of design matrix, rows [0,NPoints] contain |
|
|
//| values of basis functions at dataset points. Other |
|
|
//| rows are used for nonlinearity penalty and other |
|
|
//| stuff like that. |
|
|
//| AH - transpose(AV), "horizontal" version of AV |
|
|
//| ARows - rows count |
|
|
//| XY - array[NPoints*(2+D)], dataset |
|
|
//| KX, KY - grid size |
|
|
//| NPoints - points count |
|
|
//| D - number of components in vector-valued spline, D>=1 |
|
|
//| LSQRCnt - number of iterations, non-zero: |
|
|
//| * LSQRCnt>0 means that specified amount of |
|
|
//| preconditioned LSQR iterations will be performed |
|
|
//| to solve problem; usually we need 2..5 its. |
|
|
//| Recommended option - best convergence and |
|
|
//| stability/quality. |
|
|
//| * LSQRCnt<0 means that instead of LSQR we use |
|
|
//| iterative refinement on normal equations. Again, |
|
|
//| 2..5 its is enough. |
|
|
//| Z - possibly preallocated buffer for solution |
|
|
//| Rep - report structure; fields which are not set by this |
|
|
//| function are left intact |
|
|
//| TSS - total sum of squares; used to calculate R2 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XY - destroyed in process |
|
|
//| Z - array[KX*KY*D], filled by solution; KX*KY |
|
|
//| coefficients corresponding to each of D dimensions |
|
|
//| are stored contiguously. |
|
|
//| Rep - following fields are set: |
|
|
//| * Rep.m_rmserror |
|
|
//| * Rep.AvgError |
|
|
//| * Rep.MaxError |
|
|
//| * Rep.R2 |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::NaiveLLSFit(CSparseMatrix &av,
|
|
CSparseMatrix &ah,
|
|
int arows,
|
|
CRowDouble &xy,
|
|
int kx,
|
|
int ky,
|
|
int npoints,
|
|
int d,
|
|
int lsqrcnt,
|
|
CRowDouble &z,
|
|
CSpline2DFitReport &rep,
|
|
double tss)
|
|
{
|
|
//--- create variables
|
|
int ew=0;
|
|
int i=0;
|
|
int j=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double v=0;
|
|
int blockbandwidth=0;
|
|
double lambdareg=0;
|
|
int srci=0;
|
|
int srcj=0;
|
|
int idxi=0;
|
|
int idxj=0;
|
|
int endi=0;
|
|
int endj=0;
|
|
int rfsidx=0;
|
|
CMatrixDouble ata;
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
double mxata=0;
|
|
CLinLSQRState solver;
|
|
CLinLSQRReport solverrep;
|
|
double rss=0;
|
|
|
|
blockbandwidth=3;
|
|
ew=2+d;
|
|
//---Initial values for Z/Residuals
|
|
z=vector<double>::Zeros(kx*ky*d);
|
|
//---Create and factorize design matrix.
|
|
//---Add regularizer if factorization failed (happens sometimes
|
|
//---with zero smoothing and sparsely populated datasets).
|
|
lambdareg=m_cholreg;
|
|
while(true)
|
|
{
|
|
mxata=0.0;
|
|
//---Initialize by zero
|
|
ata=matrix<double>::Zeros(kx*ky,kx*ky);
|
|
for(i=0; i<kx*ky; i++)
|
|
{
|
|
for(j=i; j<kx*ky; j++)
|
|
{
|
|
//---Determine grid nodes corresponding to I and J;
|
|
//---skip if too far away
|
|
i0=i%kx;
|
|
i1=i/kx;
|
|
j0=j%kx;
|
|
j1=j/kx;
|
|
if(MathAbs(i0-j0)>blockbandwidth || MathAbs(i1-j1)>blockbandwidth)
|
|
continue;
|
|
//---Nodes are close enough, calculate product of columns I and J of A.
|
|
v=0;
|
|
srci=ah.m_RIdx[i];
|
|
srcj=ah.m_RIdx[j];
|
|
endi=ah.m_RIdx[i+1];
|
|
endj=ah.m_RIdx[j+1];
|
|
while(true)
|
|
{
|
|
if(srci>=endi || srcj>=endj)
|
|
break;
|
|
idxi=ah.m_Idx[srci];
|
|
idxj=ah.m_Idx[srcj];
|
|
if(idxi==idxj)
|
|
{
|
|
v+=ah.m_Vals[srci]*ah.m_Vals[srcj];
|
|
srci++;
|
|
srcj++;
|
|
continue;
|
|
}
|
|
if(idxi<idxj)
|
|
srci++;
|
|
else
|
|
srcj++;
|
|
}
|
|
ata.Set(i,j,v);
|
|
mxata=MathMax(mxata,MathAbs(v));
|
|
}
|
|
}
|
|
v=CApServ::Coalesce(mxata,1.0)*lambdareg;
|
|
for(i=0; i<kx*ky; i++)
|
|
ata.Add(i,i,v);
|
|
if(CTrFac::SPDMatrixCholesky(ata,kx*ky,true))
|
|
{
|
|
//---Success!
|
|
break;
|
|
}
|
|
//---Factorization failed, increase regularizer and repeat
|
|
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
|
|
}
|
|
//---Solve
|
|
//---NOTE: we expect that Z is zero-filled, and we treat it
|
|
//--- like initial approximation to solution.
|
|
CApServ::RVectorSetLengthAtLeast(tmp0,arows);
|
|
CApServ::RVectorSetLengthAtLeast(tmp1,kx*ky);
|
|
if(lsqrcnt>0)
|
|
CLinLSQR::LinLSQRCreate(arows,kx*ky,solver);
|
|
for(j=0; j<d; j++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(lsqrcnt!=0,__FUNCTION__+": integrity failure"))
|
|
return;
|
|
if(lsqrcnt>0)
|
|
{
|
|
//---Preconditioned LSQR:
|
|
//---use Cholesky factor U of squared design matrix A'*A to
|
|
//---transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
|
|
//---Preconditioned problem is solved with LSQR solver, which
|
|
//---gives superior results than normal equations.
|
|
CLinLSQR::LinLSQRCreate(arows,kx*ky,solver);
|
|
for(i=0; i<arows; i++)
|
|
{
|
|
if(i<npoints)
|
|
tmp0.Set(i,xy[i*ew+2+j]);
|
|
else
|
|
tmp0.Set(i,0.0);
|
|
}
|
|
CLinLSQR::LinLSQRSetB(solver,tmp0);
|
|
CLinLSQR::LinLSQRSetCond(solver,1.0E-14,1.0E-14,lsqrcnt);
|
|
while(CLinLSQR::LinLSQRIteration(solver))
|
|
{
|
|
if(solver.m_needmv)
|
|
{
|
|
//---Use Cholesky factorization of the system matrix
|
|
//---as preconditioner: solve TRSV(U,Solver.X)
|
|
tmp1=solver.m_x;
|
|
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
|
|
//---After preconditioning is done, multiply by A
|
|
CSparse::SparseMV(av,tmp1,solver.m_mv);
|
|
}
|
|
if(solver.m_needmtv)
|
|
{
|
|
//---Multiply by design matrix A
|
|
CSparse::SparseMV(ah,solver.m_x,solver.m_mtv);
|
|
//---Multiply by preconditioner: solve TRSV(U',A*Solver.X)
|
|
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,1,solver.m_mtv,0);
|
|
}
|
|
}
|
|
CLinLSQR::LinLSQRResults(solver,tmp1,solverrep);
|
|
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
|
|
for(i=0; i<kx*ky; i++)
|
|
z.Set(kx*ky*j+i,tmp1[i]);
|
|
//---Calculate model values
|
|
CSparse::SparseMV(av,tmp1,tmp0);
|
|
for(i=0; i<npoints; i++)
|
|
xy.Add(i*ew+2+j,-tmp0[i]);
|
|
}
|
|
else
|
|
{
|
|
//---Iterative refinement, inferior to LSQR
|
|
//---For each dimension D:
|
|
//---* fetch current estimate for solution from Z to Tmp1
|
|
//---* calculate residual r for current estimate, store in Tmp0
|
|
//---* calculate product of residual and design matrix A'*r, store it in Tmp1
|
|
//---* Cholesky solver
|
|
//---* update current estimate
|
|
for(rfsidx=1; rfsidx<=-lsqrcnt; rfsidx++)
|
|
{
|
|
for(i=0; i<=kx*ky-1; i++)
|
|
tmp1.Set(i,z[kx*ky*j+i]);
|
|
CSparse::SparseMV(av,tmp1,tmp0);
|
|
for(i=0; i<arows; i++)
|
|
{
|
|
if(i<npoints)
|
|
v=xy[i*ew+2+j];
|
|
else
|
|
v=0;
|
|
tmp0.Set(i,v-tmp0[i]);
|
|
}
|
|
CSparse::SparseMV(ah,tmp0,tmp1);
|
|
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,1,tmp1,0);
|
|
CAblas::RMatrixTrsVect(kx*ky,ata,0,0,true,false,0,tmp1,0);
|
|
for(i=0; i<kx*ky; i++)
|
|
z.Add(kx*ky*j+i,tmp1[i]);
|
|
}
|
|
//---Calculate model values
|
|
for(i=0; i<kx*ky; i++)
|
|
tmp1.Set(i,z[kx*ky*j+i]);
|
|
CSparse::SparseMV(av,tmp1,tmp0);
|
|
for(i=0; i<npoints; i++)
|
|
xy.Add(i*ew+2+j,-tmp0[i]);
|
|
}
|
|
}
|
|
//---Generate report
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_maxerror=0;
|
|
rss=0.0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<d; j++)
|
|
{
|
|
v=xy[i*ew+2+j];
|
|
rss=rss+v*v;
|
|
rep.m_rmserror+=CMath::Sqr(v);
|
|
rep.m_avgerror+=MathAbs(v);
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/CApServ::Coalesce(npoints*d,1.0));
|
|
rep.m_avgerror=rep.m_avgerror/CApServ::Coalesce(npoints*d,1.0);
|
|
rep.m_r2=1.0-rss/CApServ::Coalesce(tss,1.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is convenience function for band block storage format; it |
|
|
//| returns offset of KX*KX-sized block (I,J) in a compressed 2D |
|
|
//| array. |
|
|
//| For specific offset=OFFSET, block (I,J) will be stored in entries|
|
|
//| BlockMatrix[OFFSET:OFFSET+KX-1,0:KX-1] |
|
|
//+------------------------------------------------------------------+
|
|
int CSpline2D::GetCellOffset(int kx,
|
|
int ky,
|
|
int blockbandwidth,
|
|
int i,
|
|
int j)
|
|
{
|
|
int result=0;
|
|
//--- check
|
|
if(!CAp::Assert(i>=0 && i<ky,__FUNCTION__+": GetCellOffset() integrity error"))
|
|
return(0);
|
|
if(!CAp::Assert(j>=0 && j<ky,__FUNCTION__+": GetCellOffset() integrity error"))
|
|
return(0);
|
|
if(!CAp::Assert(j>=i && j<=i+blockbandwidth,__FUNCTION__+": GetCellOffset() integrity error"))
|
|
return(0);
|
|
|
|
result=j*(blockbandwidth+1)*kx;
|
|
result+=(blockbandwidth-(j-i))*kx;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is convenience function for band block storage format; it |
|
|
//| copies cell (I,J) from compressed format to uncompressed general |
|
|
//| matrix, at desired position. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::CopyCellTo(int kx,int ky,int blockbandwidth,
|
|
CMatrixDouble &blockata,
|
|
int i,int j,
|
|
CMatrixDouble &dst,
|
|
int dst0,
|
|
int dst1)
|
|
{
|
|
int celloffset=GetCellOffset(kx,ky,blockbandwidth,i,j);
|
|
|
|
for(int idx0=0; idx0<kx; idx0++)
|
|
for(int idx1=0; idx1<kx; idx1++)
|
|
dst.Set(dst0+idx0,dst1+idx1,blockata.Get(celloffset+idx0,idx1));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is convenience function for band block storage format; it |
|
|
//| truncates all elements of cell (I,J) which are less than Eps in |
|
|
//| magnitude. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::FlushToZeroCell(int kx,int ky,int blockbandwidth,
|
|
CMatrixDouble &blockata,int i,
|
|
int j,double eps)
|
|
{
|
|
int celloffset=GetCellOffset(kx,ky,blockbandwidth,i,j);
|
|
double eps2=eps*eps;
|
|
|
|
for(int idx0=0; idx0<kx; idx0++)
|
|
{
|
|
for(int idx1=0; idx1<kx; idx1++)
|
|
{
|
|
double v=blockata.Get(celloffset+idx0,idx1);
|
|
if(v*v<eps2)
|
|
blockata.Set(celloffset+idx0,idx1,0);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates squared design matrix stored in block |
|
|
//| band format. |
|
|
//| We use adaptation of block skyline storage format, with |
|
|
//| TOWERSIZE*KX skyline bands (towers) stored sequentially; here |
|
|
//| TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY "towers", stored |
|
|
//| one below other, in BlockATA matrix. Every "tower" is a sequence |
|
|
//| of BlockBandwidth+1 cells, each of them being KX*KX in size. |
|
|
//| INPUT PARAMETERS: |
|
|
//| AH - sparse matrix, [KX*KY,ARows] in size. "Horizontal" |
|
|
//| version of design matrix, cols [0,NPoints] contain |
|
|
//| values of basis functions at dataset points. Other |
|
|
//| cols are used for nonlinearity penalty and other |
|
|
//| stuff like that. |
|
|
//| KY0, KY1 - subset of output matrix bands to process; on entry |
|
|
//| it MUST be set to 0 and KY respectively. |
|
|
//| KX, KY - grid size |
|
|
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], preallocated |
|
|
//| storage for output matrix in compressed block band |
|
|
//| format |
|
|
//| MXATA - on entry MUST be zero |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| BlockATA - AH*AH', stored in compressed block band format |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::BlockLLSGenerateATA(CSparseMatrix &ah,int ky0,
|
|
int ky1,int kx,int ky,
|
|
CMatrixDouble &blockata,
|
|
double mxata)
|
|
{
|
|
//--- create variables
|
|
int blockbandwidth=3;
|
|
double avgrowlen=0;
|
|
double cellcost=0;
|
|
double totalcost=0;
|
|
double tmpmxata=0;
|
|
int i=0;
|
|
int j=0;
|
|
int i1=0;
|
|
int celloffset=0;
|
|
double v=0;
|
|
int srci=0;
|
|
int srcj=0;
|
|
int idxi=0;
|
|
int idxj=0;
|
|
int endi=0;
|
|
int endj=0;
|
|
//--- check
|
|
if(!CAp::Assert(mxata>=0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Determine problem cost, perform recursive subdivision
|
|
//---(with optional parallelization)
|
|
avgrowlen=(double)ah.m_RIdx[kx*ky]/(double)(kx*ky);
|
|
cellcost=kx*(1+2*blockbandwidth)*avgrowlen;
|
|
totalcost=(ky1-ky0)*(1+2*blockbandwidth)*cellcost;
|
|
|
|
if(ky1-ky0>=2)
|
|
{
|
|
//---Split X: X*A = (X1 X2)^T*A
|
|
j=(ky1-ky0)/2;
|
|
BlockLLSGenerateATA(ah,ky0,ky0+j,kx,ky,blockata,tmpmxata);
|
|
BlockLLSGenerateATA(ah,ky0+j,ky1,kx,ky,blockata,mxata);
|
|
mxata=MathMax(mxata,tmpmxata);
|
|
return;
|
|
}
|
|
//---Splitting in Y-dimension is done, fill I1-th "tower"
|
|
//--- check
|
|
if(!CAp::Assert(ky1==ky0+1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
i1=ky0;
|
|
for(int j1=i1; j1<=MathMin(ky-1,i1+blockbandwidth); j1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,j1);
|
|
//---Clear cell (I1,J1)
|
|
for(int i0=0; i0<kx; i0++)
|
|
for(int j0=0; j0<kx; j0++)
|
|
blockata.Set(celloffset+i0,j0,0.0);
|
|
//---Initialize cell internals
|
|
for(int i0=0; i0<kx; i0++)
|
|
for(int j0=0; j0<kx; j0++)
|
|
if(MathAbs(i0-j0)<=blockbandwidth)
|
|
{
|
|
//---Nodes are close enough, calculate product of columns I and J of A.
|
|
v=0;
|
|
i=i1*kx+i0;
|
|
j=j1*kx+j0;
|
|
srci=ah.m_RIdx[i];
|
|
srcj=ah.m_RIdx[j];
|
|
endi=ah.m_RIdx[i+1];
|
|
endj=ah.m_RIdx[j+1];
|
|
while(true)
|
|
{
|
|
if(srci>=endi || srcj>=endj)
|
|
break;
|
|
idxi=ah.m_Idx[srci];
|
|
idxj=ah.m_Idx[srcj];
|
|
if(idxi==idxj)
|
|
{
|
|
v+=ah.m_Vals[srci]*ah.m_Vals[srcj];
|
|
srci++;
|
|
srcj++;
|
|
continue;
|
|
}
|
|
if(idxi<idxj)
|
|
srci++;
|
|
else
|
|
srcj++;
|
|
}
|
|
blockata.Set(celloffset+i0,j0,v);
|
|
mxata=MathMax(mxata,MathAbs(v));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs Cholesky decomposition of squared design |
|
|
//| matrix stored in block band format. |
|
|
//| INPUT PARAMETERS: |
|
|
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], matrix in |
|
|
//| compressed block band format |
|
|
//| KX, KY - grid size |
|
|
//| TrsmBuf2, |
|
|
//| CholBuf2, |
|
|
//| CholBuf1 - buffers; reused by this function on subsequent |
|
|
//| calls, automatically preallocated on the first |
|
|
//| call |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| BlockATA - Cholesky factor, in compressed block band format|
|
|
//| Result: |
|
|
//| True on success, False on Cholesky failure |
|
|
//+------------------------------------------------------------------+
|
|
bool CSpline2D::BlockLLSCholesky(CMatrixDouble &blockata,int kx,
|
|
int ky,
|
|
CMatrixDouble &trsmbuf2,
|
|
CMatrixDouble &cholbuf2,
|
|
CRowDouble &cholbuf1)
|
|
{
|
|
//--- create variables
|
|
int blockbandwidth=3;
|
|
int celloffset=0;
|
|
int celloffset1=0;
|
|
|
|
CApServ::RMatrixSetLengthAtLeast(trsmbuf2,(blockbandwidth+1)*kx,(blockbandwidth+1)*kx);
|
|
CApServ::RMatrixSetLengthAtLeast(cholbuf2,kx,kx);
|
|
CApServ::RVectorSetLengthAtLeast(cholbuf1,kx);
|
|
|
|
for(int blockidx=0; blockidx<ky; blockidx++)
|
|
{
|
|
//---TRSM for TRAIL*TRAIL block matrix before current cell;
|
|
//---here TRAIL=MinInt(BlockIdx,BlockBandwidth).
|
|
for(int i=0; i<MathMin(blockidx,blockbandwidth); i++)
|
|
for(int j=i; j<MathMin(blockidx,blockbandwidth); j++)
|
|
CopyCellTo(kx,ky,blockbandwidth,blockata,MathMax(blockidx-blockbandwidth,0)+i,MathMax(blockidx-blockbandwidth,0)+j,trsmbuf2,i*kx,j*kx);
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,MathMax(blockidx-blockbandwidth,0),blockidx);
|
|
CAblas::RMatrixLeftTrsM(MathMin(blockidx,blockbandwidth)*kx,kx,trsmbuf2,0,0,true,false,1,blockata,celloffset,0);
|
|
//---SYRK for diagonal cell: MaxInt(BlockIdx-BlockBandwidth,0)
|
|
//---cells above diagonal one are used for update.
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,MathMax(blockidx-blockbandwidth,0),blockidx);
|
|
celloffset1=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
|
|
CAblas::RMatrixSyrk(kx,MathMin(blockidx,blockbandwidth)*kx,-1.0,blockata,celloffset,0,1,1.0,blockata,celloffset1,0,true);
|
|
//---Factorize diagonal cell
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
|
|
CAblas::RMatrixCopy(kx,kx,blockata,celloffset,0,cholbuf2,0,0);
|
|
if(!CTrFac::SPDMatrixCholeskyRec(cholbuf2,0,kx,true,cholbuf1))
|
|
return(false);
|
|
CAblas::RMatrixCopy(kx,kx,cholbuf2,0,0,blockata,celloffset,0);
|
|
//---PERFORMANCE TWEAK: drop nearly-denormals from last "tower".
|
|
//---Sparse matrices like these may produce denormal numbers on
|
|
//---sparse datasets, with significant (10x!) performance penalty
|
|
//---on Intel chips. In order to avoid it, we manually truncate
|
|
//---small enough numbers.
|
|
//---We use 1.0E-50 as clipping level (not really denormal, but
|
|
//---such small numbers are not actually important anyway).
|
|
for(int i=MathMax(blockidx-blockbandwidth,0); i<=blockidx; i++)
|
|
FlushToZeroCell(kx,ky,blockbandwidth,blockata,i,blockidx,1.0E-50);
|
|
}
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs TRSV on upper triangular Cholesky factor |
|
|
//| U, solving either U*x=b or U'*x=b. |
|
|
//| INPUT PARAMETERS: |
|
|
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], matrix U in |
|
|
//| compressed block band format |
|
|
//| KX, KY - grid size |
|
|
//| TransU - whether to transpose U or not |
|
|
//| B - array[KX*KY], on entry - stores right part B |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B - replaced by X |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::BlockLLSTrsV(CMatrixDouble &blockata,int kx,int ky,
|
|
bool transu,CRowDouble &b)
|
|
{
|
|
//--- create variables
|
|
int blockbandwidth=3;
|
|
int celloffset=0;
|
|
|
|
if(!transu)
|
|
{
|
|
//---Solve U*x=b
|
|
for(int blockidx=ky-1; blockidx>=0; blockidx--)
|
|
{
|
|
for(int blockidx1=1; blockidx1<=MathMin(ky-(blockidx+1),blockbandwidth); blockidx1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx+blockidx1);
|
|
CAblas::RMatrixGemVect(kx,kx,-1.0,blockata,celloffset,0,0,b,(blockidx+blockidx1)*kx,1.0,b,blockidx*kx);
|
|
}
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
|
|
CAblas::RMatrixTrsVect(kx,blockata,celloffset,0,true,false,0,b,blockidx*kx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//---Solve U'*x=b
|
|
for(int blockidx=0; blockidx<ky; blockidx++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx);
|
|
CAblas::RMatrixTrsVect(kx,blockata,celloffset,0,true,false,1,b,blockidx*kx);
|
|
for(int blockidx1=1; blockidx1<=MathMin(ky-(blockidx+1),blockbandwidth); blockidx1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,blockidx,blockidx+blockidx1);
|
|
CAblas::RMatrixGemVect(kx,kx,-1.0,blockata,celloffset,0,1,b,blockidx*kx,1.0,b,(blockidx+blockidx1)*kx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes residuals for dataset XY[], using array of|
|
|
//| original values YRaw[], and loads residuals to XY. |
|
|
//| Processing is performed in parallel manner. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::ComputeResidualsFromScratch(CRowDouble &xy,
|
|
CRowDouble &yraw,
|
|
int npoints,int d,
|
|
int scalexy,
|
|
CSpline2DInterpolant &spline)
|
|
{
|
|
//--- create variables
|
|
CRowDouble seed;
|
|
CRowDouble pool;
|
|
int chunksize=0;
|
|
double pointcost=0;
|
|
//---Setting up
|
|
chunksize=1000;
|
|
pointcost=100.0;
|
|
|
|
pool=seed;
|
|
//---Call compute workhorse
|
|
ComputeResidualsFromScratchRec(xy,yraw,0,npoints,chunksize,d,scalexy,spline,pool);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive workhorse for ComputeResidualsFromScratch. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::ComputeResidualsFromScratchRec(CRowDouble &xy,
|
|
CRowDouble &yraw,
|
|
int pt0,
|
|
int pt1,
|
|
int chunksize,
|
|
int d,
|
|
int scalexy,
|
|
CSpline2DInterpolant &spline,
|
|
CRowDouble &pool)
|
|
{
|
|
//--- create variables
|
|
int xew=2+d;
|
|
int i=0;
|
|
int j=0;
|
|
CRowDouble pbuf;
|
|
//---Parallelism
|
|
if(pt1-pt0>chunksize)
|
|
{
|
|
CApServ::TiledSplit(pt1-pt0,chunksize,i,j);
|
|
ComputeResidualsFromScratchRec(xy,yraw,pt0,pt0+i,chunksize,d,scalexy,spline,pool);
|
|
ComputeResidualsFromScratchRec(xy,yraw,pt0+i,pt1,chunksize,d,scalexy,spline,pool);
|
|
return;
|
|
}
|
|
//---Serial execution
|
|
pbuf=pool;
|
|
for(i=pt0; i<pt1; i++)
|
|
{
|
|
Spline2DCalcVBuf(spline,xy[i*xew+0]*scalexy,xy[i*xew+1]*scalexy,pbuf);
|
|
for(j=0; j<d; j++)
|
|
xy.Set(i*xew+2+j,yraw[i*d+j]-pbuf[j]);
|
|
}
|
|
pool=pbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function reorders dataset and builds index: |
|
|
//| * it is assumed that all points have X in [0,KX-1], |
|
|
//| Y in [0,KY-1] |
|
|
//| * area is divided into (KX-1)*(KY-1) cells |
|
|
//| * all points are reordered in such way that points in same cell|
|
|
//| are stored contiguously |
|
|
//| * dataset index, array[(KX-1)*(KY-1)+1], is generated. Points |
|
|
//| of cell I now have indexes XYIndex[I]..XYIndex[I+1]-1; |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints*(2+D)], dataset |
|
|
//| KX, KY, D - grid size and dimensionality of the outputs |
|
|
//| Shadow - shadow array[NPoints*NS], which is sorted |
|
|
//| together with XY; if NS=0, it is not referenced |
|
|
//| at all. |
|
|
//| NS - entry width of shadow array |
|
|
//| BufI - possibly preallocated temporary buffer; resized |
|
|
//| if needed. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XY - reordered |
|
|
//| XYIndex - array[(KX-1)*(KY-1)+1], dataset index |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::ReorderDatasetAndBuildIndex(CRowDouble &xy,
|
|
int npoints,
|
|
int d,
|
|
CRowDouble &shadow,
|
|
int ns,int kx,
|
|
int ky,
|
|
CRowInt &xyindex,
|
|
CRowInt &bufi)
|
|
{
|
|
//--- create variables
|
|
int i0=0;
|
|
int i1=0;
|
|
int entrywidth=0;
|
|
//---Set up
|
|
//--- check
|
|
if(!CAp::Assert(kx>=2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(ky>=2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
entrywidth=2+d;
|
|
CApServ::IVectorSetLengthAtLeast(xyindex,(kx-1)*(ky-1)+1);
|
|
CApServ::IVectorSetLengthAtLeast(bufi,npoints);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
i0=CApServ::BoundVal((int)MathFloor(xy[i*entrywidth+0]),0,kx-2);
|
|
i1=CApServ::BoundVal((int)MathFloor(xy[i*entrywidth+1]),0,ky-2);
|
|
bufi.Set(i,i1*(kx-1)+i0);
|
|
}
|
|
//---Reorder
|
|
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,bufi,0,npoints,xyindex,0,(kx-1)*(ky-1),true);
|
|
xyindex.Set((kx-1)*(ky-1),npoints);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function multiplies all points in dataset by 2.0 and |
|
|
//| rebuilds index, given previous index built for KX_prev=(KX-1)/2 |
|
|
//| and KY_prev=(KY-1)/2 |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints*(2+D)], dataset BEFORE scaling |
|
|
//| NPoints, D - dataset size and dimensionality of the outputs |
|
|
//| Shadow - shadow array[NPoints*NS], which is sorted |
|
|
//| together with XY; if NS=0, it is not referenced |
|
|
//| at all. |
|
|
//| NS - entry width of shadow array |
|
|
//| KX, KY - new grid dimensionality |
|
|
//| XYIndex - index built for previous values of KX and KY |
|
|
//| BufI - possibly preallocated temporary buffer; resized |
|
|
//| if needed. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XY - reordered and multiplied by 2.0 |
|
|
//| XYIndex - array[(KX-1)*(KY-1)+1], dataset index |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::RescaleDatasetAndRefineIndex(CRowDouble &xy,
|
|
int npoints,int d,
|
|
CRowDouble &shadow,
|
|
int ns,int kx,int ky,
|
|
CRowInt &xyindex,
|
|
CRowInt &bufi)
|
|
{
|
|
CRowInt xyindexprev;
|
|
//---Set up
|
|
//--- check
|
|
if(!CAp::Assert(kx>=2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(ky>=2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert((kx-1)%2==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert((ky-1)%2==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
|
|
CAp::Swap(xyindex,xyindexprev);
|
|
CApServ::IVectorSetLengthAtLeast(xyindex,(kx-1)*(ky-1)+1);
|
|
CApServ::IVectorSetLengthAtLeast(bufi,npoints);
|
|
//---Refine
|
|
ExpandIndexRows(xy,d,shadow,ns,bufi,0,npoints,xyindexprev,0,(ky+1)/2-1,xyindex,kx,ky,true);
|
|
xyindex.Set((kx-1)*(ky-1),npoints);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recurrent divide-and-conquer indexing function |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::ExpandIndexRows(CRowDouble &xy,int d,
|
|
CRowDouble &shadow,int ns,
|
|
CRowInt &cidx,int pt0,int pt1,
|
|
CRowInt &xyindexprev,int row0,
|
|
int row1,CRowInt &xyindexnew,
|
|
int kxnew,int kynew,bool rootcall)
|
|
{
|
|
//--- create variables
|
|
int entrywidth=0;
|
|
int kxprev=0;
|
|
double v=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
double efficiency=0;
|
|
double cost=0;
|
|
int rowmid=0;
|
|
kxprev=(kxnew+1)/2;
|
|
entrywidth=2+d;
|
|
efficiency=0.1;
|
|
cost=d*(pt1-pt0+1)*(MathLog(kxnew)/MathLog(2))/efficiency;
|
|
//--- check
|
|
if(!CAp::Assert(xyindexprev[row0*(kxprev-1)+0]==pt0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(xyindexprev[row1*(kxprev-1)+0]==pt1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Partition
|
|
if(row1-row0>=2)
|
|
{
|
|
CApServ::TiledSplit(row1-row0,1,i0,i1);
|
|
rowmid=row0+i0;
|
|
ExpandIndexRows(xy,d,shadow,ns,cidx,pt0,xyindexprev[rowmid*(kxprev-1)+0],xyindexprev,row0,rowmid,xyindexnew,kxnew,kynew,false);
|
|
ExpandIndexRows(xy,d,shadow,ns,cidx,xyindexprev[rowmid*(kxprev-1)+0],pt1,xyindexprev,rowmid,row1,xyindexnew,kxnew,kynew,false);
|
|
return;
|
|
}
|
|
//---Serial execution
|
|
for(int i=pt0; i<pt1; i++)
|
|
{
|
|
v=2*xy[i*entrywidth+0];
|
|
xy.Set(i*entrywidth,v);
|
|
i0=CApServ::BoundVal((int)MathFloor(v),0,kxnew-2);
|
|
v=2*xy[i*entrywidth+1];
|
|
xy.Set(i*entrywidth+1,v);
|
|
i1=CApServ::BoundVal((int)MathFloor(v),0,kynew-2);
|
|
cidx.Set(i,i1*(kxnew-1)+i0);
|
|
}
|
|
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,pt0,pt1,xyindexnew,2*row0*(kxnew-1)+0,2*row1*(kxnew-1)+0,false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recurrent divide-and-conquer indexing function |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::ReorderDatasetAndBuildIndexRec(CRowDouble &xy,
|
|
int d,
|
|
CRowDouble &shadow,
|
|
int ns,
|
|
CRowInt &cidx,
|
|
int pt0,int pt1,
|
|
CRowInt &xyindex,
|
|
int idx0,int idx1,
|
|
bool rootcall)
|
|
{
|
|
//--- create variables
|
|
int idxmid=0;
|
|
int wrk0=0;
|
|
int wrk1=0;
|
|
//---Efficiency - performance of the code when compared with that
|
|
//---of linear algebra code.
|
|
int entrywidth=2+d;
|
|
double efficiency=0.1;
|
|
double cost=d*(pt1-pt0+1)*MathLog(idx1-idx0+1)/MathLog(2)/efficiency;
|
|
//---Store left bound to XYIndex
|
|
xyindex.Set(idx0,pt0);
|
|
//---Quick exit strategies
|
|
if(idx1<=idx0+1)
|
|
return;
|
|
if(pt0==pt1)
|
|
{
|
|
xyindex.Fill(pt1,idx0+1,idx1-idx0);
|
|
return;
|
|
}
|
|
//---Select middle element
|
|
idxmid=idx0+(idx1-idx0)/2;
|
|
//--- check
|
|
if(!CAp::Assert(idx0<idxmid && idxmid<idx1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
wrk0=pt0;
|
|
wrk1=pt1-1;
|
|
while(true)
|
|
{
|
|
while(wrk0<pt1 && cidx[wrk0]<idxmid)
|
|
wrk0++;
|
|
while(wrk1>=pt0 && cidx[wrk1]>=idxmid)
|
|
wrk1--;
|
|
if(wrk1<=wrk0)
|
|
break;
|
|
CApServ::SwapEntries(xy,wrk0,wrk1,entrywidth);
|
|
if(ns>0)
|
|
CApServ::SwapEntries(shadow,wrk0,wrk1,ns);
|
|
CApServ::SwapElementsI(cidx,wrk0,wrk1);
|
|
}
|
|
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,pt0,wrk0,xyindex,idx0,idxmid,false);
|
|
ReorderDatasetAndBuildIndexRec(xy,d,shadow,ns,cidx,wrk0,pt1,xyindex,idxmid,idx1,false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs fitting with BlockLLS solver. Internal |
|
|
//| function, never use it directly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - dataset, array[NPoints,2+D] |
|
|
//| XYIndex - dataset index, see ReorderDatasetAndBuildIndex() |
|
|
//| for more info |
|
|
//| KX0, KX1 - X-indices of basis functions to select and fit; |
|
|
//| range [KX0,KX1) is processed |
|
|
//| KXTotal - total number of indexes in the entire grid |
|
|
//| KY0, KY1 - Y-indices of basis functions to select and fit; |
|
|
//| range [KY0,KY1) is processed |
|
|
//| KYTotal - total number of indexes in the entire grid |
|
|
//| D - number of components in vector-valued spline, D>=1 |
|
|
//| LambdaReg- regularization coefficient |
|
|
//| LambdaNS - nonlinearity penalty, exactly zero value is |
|
|
//| specially handled (entire set of rows is not added |
|
|
//| to the matrix) |
|
|
//| Basis1 - single-dimensional B-spline |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A - design matrix |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::XDesignGenerate(CRowDouble &xy,CRowInt &xyindex,
|
|
int kx0,int kx1,int kxtotal,
|
|
int ky0,int ky1,int kytotal,
|
|
int d,double lambdareg,
|
|
double lambdans,
|
|
CSpline1DInterpolant &basis1,
|
|
CSpline2DXDesignMatrix &a)
|
|
{
|
|
//--- create variables
|
|
int entrywidth=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int kx=0;
|
|
int ky=0;
|
|
int rowsdone=0;
|
|
int batchesdone=0;
|
|
int pt0=0;
|
|
int pt1=0;
|
|
int base0=0;
|
|
int base1=0;
|
|
int baseidx=0;
|
|
int nzshift=0;
|
|
int nzwidth=0;
|
|
CMatrixDouble d2x;
|
|
CMatrixDouble d2y;
|
|
CMatrixDouble dxy;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double w0=0;
|
|
double w1=0;
|
|
double w2=0;
|
|
|
|
nzshift=1;
|
|
nzwidth=4;
|
|
entrywidth=2+d;
|
|
kx=kx1-kx0;
|
|
ky=ky1-ky0;
|
|
a.m_lambdareg=lambdareg;
|
|
a.m_blockwidth=4;
|
|
a.m_kx=kx;
|
|
a.m_ky=ky;
|
|
a.m_d=d;
|
|
a.m_npoints=0;
|
|
a.m_ndenserows=0;
|
|
a.m_ndensebatches=0;
|
|
a.m_maxbatch=0;
|
|
for(j1=ky0; j1<ky1-1; j1++)
|
|
{
|
|
for(j0=kx0; j0<kx1-1; j0++)
|
|
{
|
|
i=xyindex[j1*(kxtotal-1)+j0+1]-xyindex[j1*(kxtotal-1)+j0];
|
|
a.m_npoints+=i;
|
|
a.m_ndenserows+=i;
|
|
a.m_ndensebatches+=1;
|
|
a.m_maxbatch=MathMax(a.m_maxbatch,i);
|
|
}
|
|
}
|
|
if(lambdans!=0.0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
a.m_ndenserows+=3*(kx-2)*(ky-2);
|
|
a.m_ndensebatches+=(kx-2)*(ky-2);
|
|
a.m_maxbatch=MathMax(a.m_maxbatch,3);
|
|
}
|
|
a.m_nrows=a.m_ndenserows+kx*ky;
|
|
CApServ::RMatrixSetLengthAtLeast(a.m_vals,a.m_ndenserows,a.m_blockwidth*a.m_blockwidth+d);
|
|
CApServ::IVectorSetLengthAtLeast(a.m_batches,a.m_ndensebatches+1);
|
|
CApServ::IVectorSetLengthAtLeast(a.m_batchbases,a.m_ndensebatches);
|
|
//---Setup output counters
|
|
batchesdone=0;
|
|
rowsdone=0;
|
|
//---Generate rows corresponding to dataset points
|
|
//--- check
|
|
if(!CAp::Assert(kx>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(ky>=nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth);
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,nzwidth);
|
|
a.m_batches.Set(batchesdone,0);
|
|
for(j1=ky0; j1<ky1-1; j1++)
|
|
{
|
|
for(j0=kx0; j0<kx1-1; j0++)
|
|
{
|
|
pt0=xyindex[j1*(kxtotal-1)+j0];
|
|
pt1=xyindex[j1*(kxtotal-1)+j0+1];
|
|
base0=CApServ::BoundVal(j0-kx0-nzshift,0,kx-nzwidth);
|
|
base1=CApServ::BoundVal(j1-ky0-nzshift,0,ky-nzwidth);
|
|
baseidx=base1*kx+base0;
|
|
a.m_batchbases.Set(batchesdone,baseidx);
|
|
for(i=pt0; i<pt1; i++)
|
|
{
|
|
for(k0=0; k0<nzwidth; k0++)
|
|
a.m_tmp0.Set(k0,CSpline1D::Spline1DCalc(basis1,xy[i*entrywidth]-(base0+kx0+k0)));
|
|
for(k1=0; k1<nzwidth; k1++)
|
|
a.m_tmp1.Set(k1,CSpline1D::Spline1DCalc(basis1,xy[i*entrywidth+1]-(base1+ky0+k1)));
|
|
for(k1=0; k1<nzwidth; k1++)
|
|
for(k0=0; k0<nzwidth; k0++)
|
|
a.m_vals.Set(rowsdone,k1*nzwidth+k0,a.m_tmp0[k0]*a.m_tmp1[k1]);
|
|
for(j=0; j<d; j++)
|
|
a.m_vals.Set(rowsdone,nzwidth*nzwidth+j,xy[i*entrywidth+2+j]);
|
|
rowsdone++;
|
|
}
|
|
batchesdone++;
|
|
a.m_batches.Set(batchesdone,rowsdone);
|
|
}
|
|
}
|
|
//---Generate rows corresponding to nonlinearity penalty
|
|
if(lambdans>0.0)
|
|
{
|
|
//---Smoothing is applied. Because all grid nodes are same,
|
|
//---we apply same smoothing kernel, which is calculated only
|
|
//---once at the beginning of design matrix generation.
|
|
d2x=matrix<double>::Zeros(3,3);
|
|
d2y=matrix<double>::Zeros(3,3);
|
|
dxy=matrix<double>::Zeros(3,3);
|
|
for(k1=0; k1<=2; k1++)
|
|
for(k0=0; k0<=2; k0++)
|
|
{
|
|
CSpline1D::Spline1DDiff(basis1,-(k0-1),v0,v1,v2);
|
|
CSpline1D::Spline1DDiff(basis1,-(k1-1),w0,w1,w2);
|
|
d2x.Add(k0,k1,v2*w0);
|
|
d2y.Add(k0,k1,w2*v0);
|
|
dxy.Add(k0,k1,v1*w1);
|
|
}
|
|
//---Now, kernel is ready - apply it to all inner nodes of the grid.
|
|
for(j1=1; j1<=ky-2; j1++)
|
|
{
|
|
for(j0=1; j0<=kx-2; j0++)
|
|
{
|
|
base0=MathMax(j0-2,0);
|
|
base1=MathMax(j1-2,0);
|
|
baseidx=base1*kx+base0;
|
|
a.m_batchbases.Set(batchesdone,baseidx);
|
|
//---d2F/dx2 term
|
|
v=lambdans;
|
|
for(j=0; j<nzwidth*nzwidth+d; j++)
|
|
a.m_vals.Set(rowsdone,j,0);
|
|
for(k1=j1-1; k1<=j1+1; k1++)
|
|
{
|
|
for(k0=j0-1; k0<=j0+1; k0++)
|
|
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*d2x.Get(1+(k0-j0),1+(k1-j1)));
|
|
}
|
|
rowsdone++;
|
|
//---d2F/dy2 term
|
|
v=lambdans;
|
|
for(j=0; j<nzwidth*nzwidth+d; j++)
|
|
a.m_vals.Set(rowsdone,j,0);
|
|
for(k1=j1-1; k1<=j1+1; k1++)
|
|
{
|
|
for(k0=j0-1; k0<=j0+1; k0++)
|
|
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*d2y.Get(1+(k0-j0),1+(k1-j1)));
|
|
}
|
|
rowsdone++;
|
|
//---2*d2F/dxdy term
|
|
v=MathSqrt(2)*lambdans;
|
|
for(j=0; j<nzwidth*nzwidth+d; j++)
|
|
a.m_vals.Set(rowsdone,j,0);
|
|
for(k1=j1-1; k1<=j1+1; k1++)
|
|
{
|
|
for(k0=j0-1; k0<=j0+1; k0++)
|
|
a.m_vals.Set(rowsdone,nzwidth*(k1-base1)+(k0-base0),v*dxy.Get(1+(k0-j0),1+(k1-j1)));
|
|
}
|
|
rowsdone++;
|
|
batchesdone++;
|
|
a.m_batches.Set(batchesdone,rowsdone);
|
|
}
|
|
}
|
|
}
|
|
//---Integrity post-check
|
|
if(!CAp::Assert(batchesdone==a.m_ndensebatches,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(rowsdone==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs matrix-vector product of design matrix and|
|
|
//| dense vector. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - design matrix, (a.m_nrows) X (a.m_kx*a.m_ky); |
|
|
//| some fields of A are used for temporaries, so it |
|
|
//| is non-constant. |
|
|
//| X - array[A.KX*A.KY] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - product, array[A.NRows], automatically allocated |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::XDesignMV(CSpline2DXDesignMatrix &a,CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int cnt=0;
|
|
double v=0;
|
|
int baseidx=0;
|
|
int outidx=0;
|
|
int batchsize=0;
|
|
int kx=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int nzwidth=4;
|
|
|
|
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=a.m_kx*a.m_ky,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Prepare
|
|
CApServ::RVectorSetLengthAtLeast(y,a.m_nrows);
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth*nzwidth);
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,a.m_maxbatch);
|
|
kx=a.m_kx;
|
|
outidx=0;
|
|
//---Process dense part
|
|
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
|
|
{
|
|
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
|
|
{
|
|
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
|
|
baseidx=a.m_batchbases[bidx];
|
|
for(k1=0; k1<nzwidth; k1++)
|
|
{
|
|
for(k0=0; k0<nzwidth; k0++)
|
|
a.m_tmp0.Set(k1*nzwidth+k0,x[baseidx+k1*kx+k0]);
|
|
}
|
|
CAblas::RMatrixGemVect(batchsize,nzwidth*nzwidth,1.0,a.m_vals,a.m_batches[bidx],0,0,a.m_tmp0,0,0.0,a.m_tmp1,0);
|
|
for(int i=0; i<batchsize; i++)
|
|
y.Set(outidx+i,a.m_tmp1[i]);
|
|
outidx+=batchsize;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(outidx==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Process regularizer
|
|
v=a.m_lambdareg;
|
|
cnt=a.m_kx*a.m_ky;
|
|
for(int i=0; i<cnt; i++)
|
|
y.Set(outidx+i,v*x[i]);
|
|
outidx+=cnt;
|
|
//---Post-check
|
|
if(!CAp::Assert(outidx==a.m_nrows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs matrix-vector product of transposed design|
|
|
//| matrix and dense vector. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - design matrix, (a.m_nrows) X (a.m_kx*a.m_ky); |
|
|
//| some fields of A are used for temporaries, so |
|
|
//| it is non-constant. |
|
|
//| X - array[A.NRows] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - product, array[A.KX*A.KY], automatically |
|
|
//| allocated |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::XDesignMTV(CSpline2DXDesignMatrix &a,
|
|
CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int cnt=0;
|
|
double v=0;
|
|
int baseidx=0;
|
|
int inidx=0;
|
|
int batchsize=0;
|
|
int kx=0;
|
|
int nzwidth=4;
|
|
//--- check
|
|
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=a.m_nrows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Prepare
|
|
CApServ::RVectorSetLengthAtLeast(y,a.m_kx*a.m_ky);
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp0,nzwidth*nzwidth);
|
|
CApServ::RVectorSetLengthAtLeast(a.m_tmp1,a.m_maxbatch);
|
|
kx=a.m_kx;
|
|
inidx=0;
|
|
cnt=a.m_kx*a.m_ky;
|
|
y.Fill(0);
|
|
//---Process dense part
|
|
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
|
|
{
|
|
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
|
|
{
|
|
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
|
|
baseidx=a.m_batchbases[bidx];
|
|
for(int i=0; i<batchsize ; i++)
|
|
a.m_tmp1.Set(i,x[inidx+i]);
|
|
CAblas::RMatrixGemVect(nzwidth*nzwidth,batchsize,1.0,a.m_vals,a.m_batches[bidx],0,1,a.m_tmp1,0,0.0,a.m_tmp0,0);
|
|
for(int k1=0; k1<nzwidth; k1++)
|
|
{
|
|
for(int k0=0; k0<nzwidth; k0++)
|
|
y.Add(baseidx+k1*kx+k0,a.m_tmp0[k1*nzwidth+k0]);
|
|
}
|
|
inidx=inidx+batchsize;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(inidx==a.m_ndenserows,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//---Process regularizer
|
|
v=a.m_lambdareg;
|
|
cnt=a.m_kx*a.m_ky;
|
|
for(int i=0; i<cnt; i++)
|
|
y.Add(i,v*x[inidx+i]);
|
|
inidx+=cnt;
|
|
//---Post-check
|
|
CAp::Assert(inidx==a.m_nrows,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates squared design matrix stored in block |
|
|
//| band format. |
|
|
//| We use an adaptation of block skyline storage format, with|
|
|
//| TOWERSIZE*KX skyline bands (towers) stored sequentially; here |
|
|
//| TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY "towers", stored |
|
|
//| one below other, in BlockATA matrix. Every "tower" is a sequence |
|
|
//| of BlockBandwidth+1 cells, each of them being KX*KX in size. |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - design matrix; some of its fields are used for |
|
|
//| temporaries |
|
|
//| BlockATA - array[KY*(BlockBandwidth+1)*KX,KX], preallocated |
|
|
//| storage for output matrix in compressed block band |
|
|
//| format |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| BlockATA - AH*AH', stored in compressed block band format |
|
|
//| MXATA - max(|AH*AH'|), elementwise |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline2D::XDesignBlockATA(CSpline2DXDesignMatrix &a,
|
|
CMatrixDouble &blockata,
|
|
double &mxata)
|
|
{
|
|
//--- create variables
|
|
int blockbandwidth=3;
|
|
int nzwidth=4;
|
|
int kx=a.m_kx;
|
|
int ky=a.m_ky;
|
|
int celloffset=0;
|
|
int baseidx=0;
|
|
int batchsize=0;
|
|
int offs0=0;
|
|
int offs1=0;
|
|
double v=0;
|
|
|
|
//--- check
|
|
if(!CAp::Assert(a.m_blockwidth==nzwidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CApServ::RMatrixSetLengthAtLeast(a.m_tmp2,nzwidth*nzwidth,nzwidth*nzwidth);
|
|
//---Initial zero-fill:
|
|
//---* zero-fill ALL elements of BlockATA
|
|
//---* zero-fill ALL elements of Tmp2
|
|
//---Filling ALL elements, including unused ones, is essential for the
|
|
//---purposes of calculating max(BlockATA).
|
|
for(int i1=0; i1<=ky-1; i1++)
|
|
{
|
|
for(int i0=i1; i0<=MathMin(ky-1,i1+blockbandwidth); i0++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i0);
|
|
for(int j1=0; j1<=kx-1; j1++)
|
|
for(int j0=0; j0<=kx-1; j0++)
|
|
blockata.Set(celloffset+j1,j0,0.0);
|
|
}
|
|
}
|
|
for(int j1=0; j1<nzwidth*nzwidth; j1++)
|
|
{
|
|
for(int j0=0; j0<nzwidth*nzwidth; j0++)
|
|
a.m_tmp2.Set(j1,j0,0.0);
|
|
}
|
|
//---Process dense part of A
|
|
for(int bidx=0; bidx<a.m_ndensebatches; bidx++)
|
|
{
|
|
if(a.m_batches[bidx+1]-a.m_batches[bidx]>0)
|
|
{
|
|
//---Generate 16x16 U = BATCH'*BATCH and add it to ATA.
|
|
//---NOTE: it is essential that lower triangle of Tmp2 is
|
|
//--- filled by zeros.
|
|
batchsize=a.m_batches[bidx+1]-a.m_batches[bidx];
|
|
CAblas::RMatrixSyrk(nzwidth*nzwidth,batchsize,1.0,a.m_vals,a.m_batches[bidx],0,2,0.0,a.m_tmp2,0,0,true);
|
|
baseidx=a.m_batchbases[bidx];
|
|
for(int i1=0; i1<nzwidth; i1++)
|
|
{
|
|
for(int j1=i1; j1<nzwidth; j1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,baseidx/kx+i1,baseidx/kx+j1);
|
|
offs0=baseidx%kx;
|
|
offs1=baseidx%kx;
|
|
for(int i0=0; i0<nzwidth; i0++)
|
|
for(int j0=0; j0<nzwidth; j0++)
|
|
{
|
|
v=a.m_tmp2.Get(i1*nzwidth+i0,j1*nzwidth+j0);
|
|
blockata.Set(celloffset+offs1+i0,offs0+j0,blockata.Get(celloffset+offs1+i0,offs0+j0)+v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---Process regularizer term
|
|
for(int i1=0; i1<ky; i1++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i1);
|
|
for(int j1=0; j1<kx; j1++)
|
|
blockata.Add(celloffset+j1,j1,CMath::Sqr(a.m_lambdareg));
|
|
}
|
|
//---Calculate max(ATA)
|
|
//---NOTE: here we rely on zero initialization of unused parts of
|
|
//--- BlockATA and Tmp2.
|
|
mxata=0.0;
|
|
for(int i1=0; i1<ky; i1++)
|
|
{
|
|
for(int i0=i1; i0<=MathMin(ky-1,i1+blockbandwidth); i0++)
|
|
{
|
|
celloffset=GetCellOffset(kx,ky,blockbandwidth,i1,i0);
|
|
for(int j1=0; j1<kx; j1++)
|
|
for(int j0=0; j0<kx; j0++)
|
|
mxata=MathMax(mxata,MathAbs(blockata.Get(celloffset+j1,j0)));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CIntFitServ
|
|
{
|
|
public:
|
|
static void LSFitScaleXY(CRowDouble&x,CRowDouble&y,CRowDouble&w,int n,CRowDouble&xc,CRowDouble&yc,CRowInt&dc,int k,double&xa,double&xb,double&sa,double&sb,CRowDouble&xoriginal,CRowDouble&yoriginal);
|
|
static void BuildPriorTerm(CMatrixDouble&xy,int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
|
|
static void BuildPriorTerm1(CRowDouble&xy1,int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
|
|
static void BuildPriorTerm1(double &xy1[],int n,int nx,int ny,int modeltype,double priorval,CMatrixDouble&v);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: automatic scaling for LLS tasks. |
|
|
//| NEVER CALL IT DIRECTLY! |
|
|
//| Maps abscissas to [-1,1], standartizes ordinates and |
|
|
//| correspondingly scales constraints. It also scales weights so |
|
|
//| that max(W[i])=1 |
|
|
//| Transformations performed: |
|
|
//| * X, XC [XA,XB] => [-1,+1] |
|
|
//| transformation makes min(X)=-1, max(X)=+1 |
|
|
//| * Y [SA,SB] => [0,1] |
|
|
//| transformation makes mean(Y)=0, stddev(Y)=1 |
|
|
//| * YC transformed accordingly to SA, SB, DC[I] |
|
|
//+------------------------------------------------------------------+
|
|
void CIntFitServ::LSFitScaleXY(CRowDouble &X,CRowDouble &Y,
|
|
CRowDouble &w,int n,
|
|
CRowDouble &XC,CRowDouble &YC,
|
|
CRowInt &dc,int k,double &xa,
|
|
double &xb,double &sa,
|
|
double &sb,CRowDouble &xoriginal,
|
|
CRowDouble &yoriginal)
|
|
{
|
|
//--- create variables
|
|
double xmin=0;
|
|
double xmax=0;
|
|
double mx=0;
|
|
vector<double> x=X.ToVector();
|
|
vector<double> xc=XC.ToVector();
|
|
vector<double> yc=YC.ToVector();
|
|
vector<double> y=Y.ToVector();
|
|
|
|
xa=0;
|
|
xb=0;
|
|
sa=0;
|
|
sb=0;
|
|
xoriginal.Resize(0);
|
|
yoriginal.Resize(0);
|
|
x.Resize(n);
|
|
y.Resize(n);
|
|
xc.Resize(k);
|
|
yc.Resize(k);
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": incorrect N"))
|
|
return;
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": incorrect K"))
|
|
return;
|
|
|
|
xmin=MathMin(x.Min(),xc.Min());
|
|
xmax=MathMax(x.Max(),xc.Max());
|
|
if(xmin==xmax)
|
|
{
|
|
if(xmin==0.0)
|
|
{
|
|
xmin=-1;
|
|
xmax=1;
|
|
}
|
|
else
|
|
{
|
|
if(xmin>0.0)
|
|
xmin=0.5*xmin;
|
|
else
|
|
xmax=0.5*xmax;
|
|
}
|
|
}
|
|
xoriginal=x;
|
|
xa=xmin;
|
|
xb=xmax;
|
|
x=(x-0.5*(xa+xb))*2/(xb-xa);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(dc[i]>=0,__FUNCTION__+": internal error!"))
|
|
return;
|
|
}
|
|
xc=(xc-0.5*(xa+xb))*2.0/(xb-xa);
|
|
yc=yc*MathPow(0.5*(xb-xa),dc[0]);
|
|
yoriginal=y;
|
|
sa=y.Mean();
|
|
sb=MathPow(y-sa,2).Sum();
|
|
sb=MathSqrt(sb/n)+sa;
|
|
if(sb==sa)
|
|
sb=2*sa;
|
|
if(sb==sa)
|
|
sb=sa+1;
|
|
y=(y-sa)/(sb-sa);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
if(dc[i]==0)
|
|
YC.Set(i,(yc[i]-sa)/(sb-sa));
|
|
else
|
|
YC.Set(i,yc[i]/(sb-sa));
|
|
}
|
|
yc=w.Abs();
|
|
yc.Resize(n);
|
|
mx=yc.Max();
|
|
if(mx!=0.0)
|
|
{
|
|
if(w.Size()==yc.Size())
|
|
w=yc/mx;
|
|
else
|
|
for(int i=0; i<n; i++)
|
|
w.Set(i,yc[i]/mx);
|
|
}
|
|
//--- copy back
|
|
X.Copy(x,n);
|
|
Y.Copy(y,n);
|
|
XC.Copy(xc,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CIntFitServ::BuildPriorTerm(CMatrixDouble &xy,int n,int nx,int ny,
|
|
int modeltype,double priorval,CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
double rj=0;
|
|
CMatrixDouble araw;
|
|
CMatrixDouble amod;
|
|
CMatrixDouble braw;
|
|
CRowDouble tmp0;
|
|
double lambdareg=0;
|
|
int rfsits=0;
|
|
|
|
v.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
|
|
return;
|
|
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
|
|
return;
|
|
|
|
v=matrix<double>::Zeros(ny,nx+1);
|
|
if(n==0)
|
|
{
|
|
switch(modeltype)
|
|
{
|
|
case 0:
|
|
v.Col(nx,vector<double>::Full(ny,priorval));
|
|
return;
|
|
case 1:
|
|
return;
|
|
case 2:
|
|
return;
|
|
case 3:
|
|
return;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unexpected model type");
|
|
return;
|
|
}
|
|
}
|
|
switch(modeltype)
|
|
{
|
|
case 0:
|
|
v.Col(nx,vector<double>::Full(ny,priorval));
|
|
xy-=priorval;
|
|
return;
|
|
case 2:
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<ny; j++)
|
|
v.Add(j,nx,xy.Get(i,nx+j));
|
|
v.Col(nx,v.Col(nx)/CApServ::Coalesce(n,1));
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
xy.Add(i,nx+j,- v.Get(j,nx));
|
|
}
|
|
return;
|
|
case 3:
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(modeltype==1,__FUNCTION__+": unexpected model type"))
|
|
return;
|
|
|
|
lambdareg=0.0;
|
|
araw=matrix<double>::Zeros(nx+1,nx+1);
|
|
braw.Resize(nx+1,ny);
|
|
tmp0.Resize(nx+1);
|
|
amod.Resize(nx+1,nx+1);
|
|
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy.Get(i,j));
|
|
tmp0.Set(nx,1.0);
|
|
for(j0=0; j0<=nx; j0++)
|
|
for(j1=0; j1<=nx; j1++)
|
|
araw.Add(j0,j1,tmp0[j0]*tmp0[j1]);
|
|
}
|
|
for(rfsits=1; rfsits<=3; rfsits++)
|
|
{
|
|
braw.Fill(0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy.Get(i,j));
|
|
tmp0.Set(nx,1.0);
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
rj=xy.Get(i,nx+j);
|
|
for(j0=0; j0<=nx; j0++)
|
|
rj-=tmp0[j0]*v.Get(j,j0);
|
|
braw.Col(j,braw.Col(j)+tmp0*rj);
|
|
}
|
|
}
|
|
while(true)
|
|
{
|
|
for(i=0; i<=nx; i++)
|
|
{
|
|
amod.Row(i,araw,i);
|
|
amod.Add(i,i,lambdareg*CApServ::Coalesce(amod.Get(i,i),1));
|
|
}
|
|
if(CTrFac::SPDMatrixCholesky(amod,nx+1,true))
|
|
break;
|
|
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
|
|
}
|
|
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,1,braw,0,0);
|
|
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,0,braw,0,0);
|
|
v+=braw.Transpose();
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy.Get(i,j));
|
|
tmp0.Set(nx,1.0);
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
rj=tmp0.DotR(v,j);
|
|
xy.Add(i,nx+j,-rj);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CIntFitServ::BuildPriorTerm1(double &xy1[],int n,int nx,int ny,
|
|
int modeltype,double priorval,CMatrixDouble &v)
|
|
{
|
|
CRowDouble XY=xy1;
|
|
BuildPriorTerm1(XY,n,nx,ny,modeltype,priorval,v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CIntFitServ::BuildPriorTerm1(CRowDouble &xy1,int n,int nx,int ny,
|
|
int modeltype,double priorval,CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int ew=0;
|
|
double rj=0;
|
|
CMatrixDouble araw;
|
|
CMatrixDouble amod;
|
|
CMatrixDouble braw;
|
|
CRowDouble tmp0;
|
|
double lambdareg=0;
|
|
int rfsits=0;
|
|
v.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
|
|
return;
|
|
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
|
|
return;
|
|
ew=nx+ny;
|
|
v=matrix<double>::Zeros(ny,nx+1);
|
|
if(n==0)
|
|
{
|
|
switch(modeltype)
|
|
{
|
|
case 0:
|
|
v.Col(nx,vector<double>::Full(ny,priorval));
|
|
return;
|
|
case 1:
|
|
return;
|
|
case 2:
|
|
return;
|
|
case 3:
|
|
return;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unexpected model type");
|
|
return;
|
|
}
|
|
}
|
|
switch(modeltype)
|
|
{
|
|
case 0:
|
|
v.Col(nx,vector<double>::Full(ny,priorval));
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
xy1.Add(i*ew+nx+j,-priorval);
|
|
}
|
|
return;
|
|
case 2:
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
v.Add(j,nx,xy1[i*ew+nx+j]);
|
|
}
|
|
v.Col(nx,v.Col(nx)/CApServ::Coalesce(n,1));
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
xy1.Add(i*ew+nx+j,-v.Get(j,nx));
|
|
}
|
|
return;
|
|
case 3:
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(modeltype==1,__FUNCTION__+": unexpected model type"))
|
|
return;
|
|
lambdareg=0.0;
|
|
araw=matrix<double>::Zeros(nx+1,nx+1);
|
|
braw.Resize(nx+1,ny);
|
|
tmp0.Resize(nx+1);
|
|
amod.Resize(nx+1,nx+1);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy1[i*ew+j]);
|
|
tmp0.Set(nx,1.0);
|
|
for(j0=0; j0<=nx; j0++)
|
|
{
|
|
for(j1=0; j1<=nx; j1++)
|
|
araw.Add(j0,j1,tmp0[j0]*tmp0[j1]);
|
|
}
|
|
}
|
|
for(rfsits=1; rfsits<=3; rfsits++)
|
|
{
|
|
braw=matrix<double>::Zeros(nx+1,ny);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy1[i*ew+j]);
|
|
tmp0.Set(nx,1.0);
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
rj=xy1[i*ew+nx+j];
|
|
for(j0=0; j0<=nx; j0++)
|
|
rj-=tmp0[j0]*v.Get(j,j0);
|
|
braw.Col(j,braw.Col(j)+tmp0*rj);
|
|
}
|
|
}
|
|
while(true)
|
|
{
|
|
amod=araw;
|
|
for(i=0; i<=nx; i++)
|
|
amod.Add(i,i,lambdareg*CApServ::Coalesce(amod.Get(i,i),1));
|
|
if(CTrFac::SPDMatrixCholesky(amod,nx+1,true))
|
|
break;
|
|
lambdareg=CApServ::Coalesce(10*lambdareg,1.0E-12);
|
|
}
|
|
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,1,braw,0,0);
|
|
CAblas::RMatrixLeftTrsM(nx+1,ny,amod,0,0,true,false,0,braw,0,0);
|
|
v+= braw.Transpose()+0;
|
|
}
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
tmp0.Set(j,xy1[i*ew+j]);
|
|
tmp0.Set(nx,1.0);
|
|
for(j=0; j<=ny-1; j++)
|
|
{
|
|
rj=tmp0.DotR(v,j);
|
|
xy1.Add(i*ew+nx+j,-rj);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
struct CFitSphereInternalReport
|
|
{
|
|
public:
|
|
int m_nfev;
|
|
int m_iterationscount;
|
|
//--- constructor / destructor
|
|
CFitSphereInternalReport(void) { m_nfev=0; m_iterationscount=0; }
|
|
~CFitSphereInternalReport(void) {}
|
|
void Copy(const CFitSphereInternalReport&obj) { m_nfev=obj.m_nfev; m_iterationscount=obj.m_iterationscount; }
|
|
//--- overloading
|
|
void operator=(const CFitSphereInternalReport&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CFitSphere
|
|
{
|
|
public:
|
|
static void FitSphereLS(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&r);
|
|
static void FitSphereMC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rhi);
|
|
static void FitSphereMI(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo);
|
|
static void FitSphereMZ(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo,double&rhi);
|
|
static void FitSphereX(CMatrixDouble&xy,int npoints,int nx,int problemtype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi);
|
|
static void FitSphereInternal(CMatrixDouble&xy,int npoints,int nx,int problemtype,int solvertype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi,CFitSphereInternalReport&rep);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Fits least squares (LS) circle (or NX-dimensional sphere) to data|
|
|
//| (a set of points in NX-dimensional space). |
|
|
//| Least squares circle minimizes sum of squared deviations between |
|
|
//| distances from points to the center and some "candidate" radius,|
|
|
//| which is also fitted to the data. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| R - radius |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereLS(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nx,
|
|
CRowDouble &cx,
|
|
double &r)
|
|
{
|
|
double dummy=0;
|
|
cx.Resize(0);
|
|
r=0;
|
|
//--- function call
|
|
FitSphereX(xy,npoints,nx,0,0.0,0,0.0,cx,dummy,r);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fits minimum circumscribed (MC) circle (or NX-dimensional sphere)|
|
|
//| to data (a set of points in NX-dimensional space). |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| RHi - radius |
|
|
//| NOTE: this function is an easy-to-use wrapper around more |
|
|
//| powerful "expert" function FitSphereX(). |
|
|
//| This wrapper is optimized for ease of use and stability - at the |
|
|
//| cost of somewhat lower performance (we have to use very tight |
|
|
//| stopping criteria for inner optimizer because we want to make |
|
|
//| sure that it will converge on any dataset). |
|
|
//| If you are ready to experiment with settings of "expert" |
|
|
//| function, you can achieve ~2-4x speedup over standard |
|
|
//| "bulletproof" settings. |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereMC(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nx,
|
|
CRowDouble &cx,
|
|
double &rhi)
|
|
{
|
|
double dummy=0;
|
|
cx.Resize(0);
|
|
rhi=0;
|
|
//--- function call
|
|
FitSphereX(xy,npoints,nx,1,0.0,0,0.0,cx,dummy,rhi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fits maximum inscribed circle (or NX-dimensional sphere) to data |
|
|
//| (a set of points in NX-dimensional space). |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| RLo - radius |
|
|
//| NOTE: this function is an easy-to-use wrapper around more |
|
|
//| powerful "expert" function FitSphereX(). |
|
|
//| This wrapper is optimized for ease of use and stability - at |
|
|
//| the cost of somewhat lower performance (we have to use very|
|
|
//| tight stopping criteria for inner optimizer because we want to |
|
|
//| make sure that it will converge on any dataset). |
|
|
//| If you are ready to experiment with settings of "expert" |
|
|
//| function, you can achieve ~2-4x speedup over standard |
|
|
//| "bulletproof" settings. |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereMI(CMatrixDouble &xy,int npoints,int nx,
|
|
CRowDouble &cx,double &rlo)
|
|
{
|
|
double dummy=0;
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
//--- function call
|
|
FitSphereX(xy,npoints,nx,2,0.0,0,0.0,cx,rlo,dummy);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fits minimum zone circle (or NX-dimensional sphere) to data (a |
|
|
//| set of points in NX-dimensional space). |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| RLo - radius of inscribed circle |
|
|
//| RHo - radius of circumscribed circle |
|
|
//| NOTE: this function is an easy-to-use wrapper around more |
|
|
//| powerful "expert" function FitSphereX(). |
|
|
//| This wrapper is optimized for ease of use and stability - at |
|
|
//| the cost of somewhat lower performance (we have to use very|
|
|
//| tight stopping criteria for inner optimizer because we want to |
|
|
//| make sure that it will converge on any dataset). |
|
|
//| If you are ready to experiment with settings of "expert" |
|
|
//| function, you can achieve ~2-4x speedup over standard |
|
|
//| "bulletproof" settings. |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereMZ(CMatrixDouble &xy,int npoints,int nx,
|
|
CRowDouble &cx,double &rlo,double &rhi)
|
|
{
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
rhi=0;
|
|
//--- function call
|
|
FitSphereX(xy,npoints,nx,3,0.0,0,0.0,cx,rlo,rhi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fitting minimum circumscribed, maximum inscribed or minimum zone |
|
|
//| circles (or NX-dimensional spheres) to data (a set of points |
|
|
//| in NX-dimensional space). |
|
|
//| This is expert function which allows to tweak many parameters of |
|
|
//| underlying nonlinear solver: |
|
|
//| * stopping criteria for inner iterations |
|
|
//| * number of outer iterations |
|
|
//| * penalty coefficient used to handle nonlinear constraints (we |
|
|
//| convert unconstrained nonsmooth optimization problem ivolving|
|
|
//| max() and/or min() operations to quadratically constrained |
|
|
//| smooth one). |
|
|
//| You may tweak all these parameters or only some of them, leaving |
|
|
//| other ones at their default State - just specify zero value, and |
|
|
//| solver will fill it with appropriate default one. |
|
|
//| These comments also include some discussion of approach used to |
|
|
//| handle such unusual fitting problem, its stability, drawbacks of |
|
|
//| alternative methods, and convergence properties. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| ProblemType - used to encode problem type: |
|
|
//| * 0 for least squares circle |
|
|
//| * 1 for minimum circumscribed circle/sphere fitting|
|
|
//| (MC) |
|
|
//| * 2 for maximum inscribed circle/sphere fitting(MI)|
|
|
//| * 3 for minimum zone circle fitting (difference |
|
|
//| between Rhi and Rlo is minimized), denoted as |
|
|
//| MZ |
|
|
//| EpsX - stopping condition for NLC optimizer: |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (1.0E-12 is used by|
|
|
//| default) |
|
|
//| * you may specify larger values, up to 1.0E-6, if |
|
|
//| you want to speed-up solver; NLC solver performs |
|
|
//| several preconditioned outer iterations, so final|
|
|
//| result typically has precision much better than |
|
|
//| EpsX. |
|
|
//| AULIts - number of outer iterations performed by NLC |
|
|
//| optimizer: |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (20 is used by |
|
|
//| default) |
|
|
//| * you may specify values smaller than 20 if you |
|
|
//| want to speed up solver; 10 often results in good|
|
|
//| combination of precision and speed; sometimes you|
|
|
//| may get good results with just 6 outer iterations|
|
|
//| Ignored for ProblemType=0. |
|
|
//| Penalty - penalty coefficient for NLC optimizer: |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (1.0E6 in current |
|
|
//| version) |
|
|
//| * it should be really large, 1.0E6...1.0E7 is a |
|
|
//| good value to start from; |
|
|
//| * generally, default value is good enough |
|
|
//| Ignored for ProblemType=0. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| RLo - radius: |
|
|
//| * for ProblemType=2,3, radius of the inscribed |
|
|
//| sphere |
|
|
//| * for ProblemType=0 - radius of the least squares |
|
|
//| sphere |
|
|
//| * for ProblemType=1 - zero |
|
|
//| RHo - radius: |
|
|
//| * for ProblemType=1,3, radius of the circumscribed |
|
|
//| sphere |
|
|
//| * for ProblemType=0 - radius of the least squares |
|
|
//| sphere |
|
|
//| * for ProblemType=2 - zero |
|
|
//| NOTE: ON THE UNIQUENESS OF SOLUTIONS |
|
|
//| ALGLIB provides solution to several related circle fitting |
|
|
//| problems: MC (minimum circumscribed), MI (maximum inscribed) and |
|
|
//| MZ (minimum zone) fitting, LS (least squares) fitting. |
|
|
//| It is important to note that among these problems only MC and LS |
|
|
//| are convex and have unique solution independently from starting |
|
|
//| point. |
|
|
//| As for MI, it may (or may not, depending on dataset properties) |
|
|
//| have multiple solutions, and it always has one degenerate |
|
|
//| solution C=infinity which corresponds to infinitely large radius.|
|
|
//| Thus, there are no guarantees that solution to MI returned by |
|
|
//| this solver will be the best one (and no one can provide you with|
|
|
//| such guarantee because problem is NP-hard). The only guarantee |
|
|
//| you have is that this solution is locally optimal, i.e. it can |
|
|
//| not be improved by infinitesimally small tweaks in the parameters|
|
|
//| It is also possible to "run away" to infinity when started from |
|
|
//| bad initial point located outside of point cloud (or when point |
|
|
//| cloud does not span entire circumference/surface of the sphere). |
|
|
//| Finally, MZ (minimum zone circle) stands somewhere between MC and|
|
|
//| MI in stability. It is somewhat regularized by "circumscribed" |
|
|
//| term of the merit function; however, solutions to MZ may be |
|
|
//| non-unique, and in some unlucky cases it is also possible to "run|
|
|
//| away to infinity". |
|
|
//| NOTE: ON THE NONLINEARLY CONSTRAINED PROGRAMMING APPROACH |
|
|
//| The problem formulation for MC (minimum circumscribed circle; for|
|
|
//| the sake of simplicity we omit MZ and MI here) is: |
|
|
//| [ [ ]2 ] |
|
|
//| min [ max [ XY[i]-C ] ] |
|
|
//| C [ i [ ] ] |
|
|
//| i.e. it is unconstrained nonsmooth optimization problem of |
|
|
//| finding "best" central point, with radius R being unambiguously |
|
|
//| determined from C. In order to move away from non-smoothness we |
|
|
//| use following reformulation: |
|
|
//| [ ] [ ]2 |
|
|
//| min [ R ] subject to R>=0, [ XY[i]-C ] <= R^2 |
|
|
//| C,R [ ] [ ] |
|
|
//| i.e. it becomes smooth quadratically constrained optimization |
|
|
//| problem with linear target function. Such problem statement is |
|
|
//| 100% equivalent to the original nonsmooth one, but much easier |
|
|
//| to approach. We solve it with MinNLC solver provided by ALGLIB. |
|
|
//| NOTE: ON INSTABILITY OF SEQUENTIAL LINEARIZATION APPROACH |
|
|
//| ALGLIB has nonlinearly constrained solver which proved to be |
|
|
//| stable on such problems. However, some authors proposed to |
|
|
//| linearize constraints in the vicinity of current approximation |
|
|
//| (Ci,Ri) and to get next approximate solution (Ci+1,Ri+1) as |
|
|
//| solution to linear programming problem. Obviously, LP problems |
|
|
//| are easier than nonlinearly constrained ones. |
|
|
//| Indeed, such approach to MC/MI/MZ resulted in ~10-20x increase in|
|
|
//| performance (when compared with NLC solver). However, it turned |
|
|
//| out that in some cases linearized model fails to predict correct |
|
|
//| direction for next step and tells us that we converged to |
|
|
//| solution even when we are still 2-4 digits of precision away from|
|
|
//| it. |
|
|
//| It is important that it is not failure of LP solver - it is |
|
|
//| failure of the linear model; even when solved exactly, it fails |
|
|
//| to handle subtle nonlinearities which arise near the solution. |
|
|
//| We validated it by comparing results returned by ALGLIB linear |
|
|
//| solver with that of MATLAB. |
|
|
//| In our experiments with linearization: |
|
|
//| * MC failed most often, at both realistic and synthetic |
|
|
//| datasets |
|
|
//| * MI sometimes failed, but sometimes succeeded |
|
|
//| * MZ often succeeded; our guess is that presence of two |
|
|
//| independent sets of constraints (one set for Rlo and another |
|
|
//| one for Rhi) and two terms in the target function (Rlo and |
|
|
//| Rhi) regularizes task, so when linear model fails to handle |
|
|
//| nonlinearities from Rlo, it uses Rhi as a hint (and vice |
|
|
//| versa). |
|
|
//| Because linearization approach failed to achieve stable results, |
|
|
//| we do not include it in ALGLIB. |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereX(CMatrixDouble &xy,int npoints,int nx,
|
|
int problemtype,double epsx,int aulits,
|
|
double penalty,CRowDouble &cx,double &rlo,
|
|
double &rhi)
|
|
{
|
|
CFitSphereInternalReport rep;
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
rhi=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(penalty) && penalty>=0.0,__FUNCTION__+": Penalty<0 or is not finite"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx) && (double)(epsx)>=0.0,__FUNCTION__+": EpsX<0 or is not finite"))
|
|
return;
|
|
if(!CAp::Assert(aulits>=0,__FUNCTION__+": AULIts<0"))
|
|
return;
|
|
//--- function call
|
|
FitSphereInternal(xy,npoints,nx,problemtype,0,epsx,aulits,penalty,cx,rlo,rhi,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Fitting minimum circumscribed, maximum inscribed or minimum zone |
|
|
//| circles (or NX-dimensional spheres) to data (a set of points in |
|
|
//| NX-dimensional space). |
|
|
//| Internal computational function. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints,NX] (or larger), contains dataset. |
|
|
//| One row = one point in NX-dimensional space. |
|
|
//| NPoints - dataset size, NPoints>0 |
|
|
//| NX - space dimensionality, NX>0(1, 2, 3, 4, 5 and so on)|
|
|
//| ProblemType - used to encode problem type: |
|
|
//| * 0 for least squares circle |
|
|
//| * 1 for minimum circumscribed circle/sphere fitting|
|
|
//| (MC) |
|
|
//| * 2 for maximum inscribed circle/sphere fitting(MI)|
|
|
//| * 3 for minimum zone circle fitting (difference |
|
|
//| between Rhi and Rlo is minimized), denoted as |
|
|
//| MZ |
|
|
//| SolverType - solver to use: |
|
|
//| * 0 use best solver available(1 in current version)|
|
|
//| * 1 use nonlinearly constrained optimization |
|
|
//| approach, AUL (it is roughly 10-20 times slower|
|
|
//| than SPC-LIN, but much more stable) |
|
|
//| * 2 use special fast IMPRECISE solver, SPC-LIN |
|
|
//| sequential linearization approach; SPC-LIN is |
|
|
//| fast, but sometimes fails to converge with more|
|
|
//| than 3 digits of precision; see comments below.|
|
|
//| NOT RECOMMENDED UNLESS YOU REALLY NEED HIGH |
|
|
//| PERFORMANCE AT THE COST OF SOME PRECISION. |
|
|
//| * 3 use nonlinearly constrained optimization |
|
|
//| approach, SLP (most robust one, but somewhat |
|
|
//| slower than AUL) |
|
|
//| Ignored for ProblemType=0. |
|
|
//| EpsX - stopping criteria for SLP and NLC optimizers: |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (1.0E-12 is used by|
|
|
//| default) |
|
|
//| * if you use SLP solver, you should use default |
|
|
//| values |
|
|
//| * if you use NLC solver, you may specify larger |
|
|
//| values, up to 1.0E-6, if you want to speed-up |
|
|
//| solver;NLC solver performs several preconditioned|
|
|
//| outer iterations, so final result typically has |
|
|
//| precision much better than EpsX. |
|
|
//| AULIts - number of iterations performed by NLC optimizer: |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (20 is used by |
|
|
//| default) |
|
|
//| * you may specify values smaller than 20 if you |
|
|
//| want to speed up solver; 10 often results in |
|
|
//| good combination of precision and speed |
|
|
//| Ignored for ProblemType=0. |
|
|
//| Penalty - penalty coefficient for NLC optimizer (ignored for |
|
|
//| SLP): |
|
|
//| * must be non-negative |
|
|
//| * use 0 to choose default value (1.0E6 in current |
|
|
//| version) |
|
|
//| * it should be really large, 1.0E6...1.0E7 is a |
|
|
//| good value to start from; |
|
|
//| * generally, default value is good enough |
|
|
//| * ignored by SLP optimizer |
|
|
//| Ignored for ProblemType=0. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CX - central point for a sphere |
|
|
//| RLo - radius: |
|
|
//| * for ProblemType=2,3, radius of the inscribed |
|
|
//| sphere |
|
|
//| * for ProblemType=0 - radius of the least squares |
|
|
//| sphere |
|
|
//| * for ProblemType=1 - zero |
|
|
//| RHo - radius: |
|
|
//| * for ProblemType=1,3, radius of the circumscribed |
|
|
//| sphere |
|
|
//| * for ProblemType=0 - radius of the least squares |
|
|
//| sphere |
|
|
//| * for ProblemType=2 - zero |
|
|
//+------------------------------------------------------------------+
|
|
void CFitSphere::FitSphereInternal(CMatrixDouble &XY,int npoints,
|
|
int nx,int problemtype,
|
|
int solvertype,double epsx,
|
|
int aulits,double penalty,
|
|
CRowDouble &cx,double &rlo,
|
|
double &rhi,
|
|
CFitSphereInternalReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int cpr=0;
|
|
bool userlo=false;
|
|
bool userhi=false;
|
|
double vlo=0;
|
|
double vhi=0;
|
|
vector<double> vmin;
|
|
vector<double> vmax;
|
|
vector<double> std;
|
|
double spread=0;
|
|
CRowDouble pcr;
|
|
CRowDouble scr;
|
|
CRowDouble bl;
|
|
CRowDouble bu;
|
|
int suboffset=0;
|
|
int dstrow=0;
|
|
CMinNLCState nlcstate;
|
|
CMinNLCReport nlcrep;
|
|
CMatrixDouble cmatrix;
|
|
CRowInt ct;
|
|
int outeridx=0;
|
|
int maxouterits=0;
|
|
int maxits=0;
|
|
double safeguard=0;
|
|
double bi=0;
|
|
CMinBLEICState blcstate;
|
|
CMinBLEICReport blcrep;
|
|
CRowDouble prevc;
|
|
CMinLMState lmstate;
|
|
CMinLMReport lmrep;
|
|
matrix<double> xy=XY.ToMatrix();
|
|
|
|
cx.Resize(0);
|
|
xy.Resize(npoints,nx);
|
|
rlo=0;
|
|
rhi=0;
|
|
//--- Check input parameters
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__+": NPoints<=0"))
|
|
return;
|
|
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(XY,npoints,nx),__FUNCTION__+": XY contains infinite or NAN values"))
|
|
return;
|
|
if(!CAp::Assert(problemtype>=0 && problemtype<=3,__FUNCTION__+": ProblemType is neither 0,1,2 or 3"))
|
|
return;
|
|
if(!CAp::Assert(solvertype>=0 && solvertype<=3,__FUNCTION__+": ProblemType is neither 1,2 or 3"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(penalty) && penalty>=0.0,__FUNCTION__+": Penalty<0 or is not finite"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epsx) && epsx>=0.0,__FUNCTION__+": EpsX<0 or is not finite"))
|
|
return;
|
|
if(!CAp::Assert(aulits>=0,__FUNCTION__+": AULIts<0"))
|
|
return;
|
|
if(solvertype==0)
|
|
solvertype=1;
|
|
if(penalty==0.0)
|
|
penalty=1.0E6;
|
|
if(epsx==0.0)
|
|
epsx=1.0E-12;
|
|
if(aulits==0)
|
|
aulits=20;
|
|
safeguard=10;
|
|
maxouterits=10;
|
|
maxits=10000;
|
|
rep.m_nfev=0;
|
|
rep.m_iterationscount=0;
|
|
//--- Determine initial values, initial estimates and spread of the points
|
|
vmin=xy.Min(0)+0;
|
|
vmax=xy.Max(0)+0;
|
|
cx=xy.Mean(0);
|
|
std=xy.Std(0);
|
|
spread=(vmax-vmin).Max();
|
|
rlo=std.Min();
|
|
rhi=std.Max();
|
|
//--- Handle degenerate case of zero spread
|
|
if(spread==0.0)
|
|
{
|
|
cx=vmin;
|
|
rhi=0;
|
|
rlo=0;
|
|
return;
|
|
}
|
|
//--- Prepare initial point for optimizer, scale vector and box constraints
|
|
pcr=cx;
|
|
bl=cx.ToVector()-safeguard*spread;
|
|
bu=cx.ToVector()+safeguard*spread;
|
|
scr=vector<double>::Full(nx+2,0.1*spread);
|
|
pcr.Resize(nx+2);
|
|
bl.Resize(nx+2);
|
|
bu.Resize(nx+2);
|
|
pcr.Set(nx+0,rlo);
|
|
pcr.Set(nx+1,rhi);
|
|
scr.Set(nx+0,0.5*spread);
|
|
scr.Set(nx+1,0.5*spread);
|
|
bl.Set(nx+0,0);
|
|
bl.Set(nx+1,0);
|
|
bu.Set(nx+0,safeguard*rhi);
|
|
bu.Set(nx+1,safeguard*rhi);
|
|
//--- First branch: least squares fitting vs MI/MC/MZ fitting
|
|
if(problemtype==0)
|
|
{
|
|
//--- Solve problem with Levenberg-Marquardt algorithm
|
|
pcr.Set(nx,rhi);
|
|
CMinLM::MinLMCreateVJ(nx+1,npoints,pcr,lmstate);
|
|
CMinLM::MinLMSetScale(lmstate,scr);
|
|
CMinLM::MinLMSetBC(lmstate,bl,bu);
|
|
CMinLM::MinLMSetCond(lmstate,epsx,maxits);
|
|
while(CMinLM::MinLMIteration(lmstate))
|
|
{
|
|
if(lmstate.m_needfij || lmstate.m_needfi)
|
|
{
|
|
rep.m_nfev++;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
v+=CMath::Sqr(lmstate.m_x[j]-xy[i,j]);
|
|
lmstate.m_fi.Set(i,MathSqrt(v)-lmstate.m_x[nx]);
|
|
if(lmstate.m_needfij)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
lmstate.m_j.Set(i,j,0.5/(1.0E-9*spread+MathSqrt(v))*2*(lmstate.m_x[j]-xy[i,j]));
|
|
lmstate.m_j.Set(i,nx,-1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
CMinLM::MinLMResults(lmstate,pcr,lmrep);
|
|
//--- check
|
|
if(!CAp::Assert(lmrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of LM solver"))
|
|
return;
|
|
rep.m_iterationscount+=lmrep.m_iterationscount;
|
|
//--- Offload center coordinates from PCR to CX,
|
|
//--- re-calculate exact value of RLo/RHi using CX.
|
|
for(j=0; j<nx; j++)
|
|
cx.Set(j,pcr[j]);
|
|
vv=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=0;
|
|
for(j=0; j<=nx-1; j++)
|
|
v=CMath::Sqr(xy[i,j]-cx[j]);
|
|
v=MathSqrt(v);
|
|
vv=vv+v/npoints;
|
|
}
|
|
rlo=vv;
|
|
rhi=vv;
|
|
}
|
|
else
|
|
{
|
|
//--- MI, MC, MZ fitting.
|
|
//--- Prepare problem metrics
|
|
userlo=problemtype==2 || problemtype==3;
|
|
userhi=problemtype==1 || problemtype==3;
|
|
if(userlo && userhi)
|
|
cpr=2;
|
|
else
|
|
cpr=1;
|
|
if(userlo)
|
|
vlo=1;
|
|
else
|
|
vlo=0;
|
|
if(userhi)
|
|
vhi=1;
|
|
else
|
|
vhi=0;
|
|
//--- Solve with NLC solver; problem is treated as general nonlinearly constrained
|
|
//--- programming, with augmented Lagrangian solver or SLP being used.
|
|
if(solvertype==1 || solvertype==3)
|
|
{
|
|
CMinNLC::MinNLCCreate(nx+2,pcr,nlcstate);
|
|
CMinNLC::MinNLCSetScale(nlcstate,scr);
|
|
CMinNLC::MinNLCSetBC(nlcstate,bl,bu);
|
|
CMinNLC::MinNLCSetNLC(nlcstate,0,cpr*npoints);
|
|
CMinNLC::MinNLCSetCond(nlcstate,epsx,maxits);
|
|
CMinNLC::MinNLCSetPrecExactRobust(nlcstate,5);
|
|
CMinNLC::MinNLCSetSTPMax(nlcstate,0.1);
|
|
if(solvertype==1)
|
|
CMinNLC::MinNLCSetAlgoAUL(nlcstate,penalty,aulits);
|
|
else
|
|
CMinNLC::MinNLCSetAlgoSLP(nlcstate);
|
|
CMinNLC::MinNLCRestartFrom(nlcstate,pcr);
|
|
while(CMinNLC::MinNLCIteration(nlcstate))
|
|
{
|
|
if(nlcstate.m_needfij)
|
|
{
|
|
rep.m_nfev++;
|
|
nlcstate.m_fi.Set(0,vhi*nlcstate.m_x[nx+1]-vlo*nlcstate.m_x[nx+0]);
|
|
nlcstate.m_j.Row(0,vector<double>::Zeros(nx+2));
|
|
nlcstate.m_j.Set(0,nx+0,-(1*vlo));
|
|
nlcstate.m_j.Set(0,nx+1,1*vhi);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
suboffset=0;
|
|
if(userhi)
|
|
{
|
|
dstrow=1+cpr*i+suboffset;
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
vv=nlcstate.m_x[j]-xy[i,j];
|
|
v=v+vv*vv;
|
|
nlcstate.m_j.Set(dstrow,j,2*vv);
|
|
}
|
|
vv=nlcstate.m_x[nx+1];
|
|
v=v-vv*vv;
|
|
nlcstate.m_j.Set(dstrow,nx+0,0);
|
|
nlcstate.m_j.Set(dstrow,nx+1,-(2*vv));
|
|
nlcstate.m_fi.Set(dstrow,v);
|
|
suboffset++;
|
|
}
|
|
if(userlo)
|
|
{
|
|
dstrow=1+cpr*i+suboffset;
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
vv=nlcstate.m_x[j]-xy[i,j];
|
|
v=v-vv*vv;
|
|
nlcstate.m_j.Set(dstrow,j,-(2*vv));
|
|
}
|
|
vv=nlcstate.m_x[nx+0];
|
|
v=v+vv*vv;
|
|
nlcstate.m_j.Set(dstrow,nx+0,2*vv);
|
|
nlcstate.m_j.Set(dstrow,nx+1,0);
|
|
nlcstate.m_fi.Set(dstrow,v);
|
|
suboffset++;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(suboffset==cpr))
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
CMinNLC::MinNLCResults(nlcstate,pcr,nlcrep);
|
|
//--- check
|
|
if(!CAp::Assert(nlcrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of NLC solver"))
|
|
return;
|
|
rep.m_iterationscount=rep.m_iterationscount+nlcrep.m_iterationscount;
|
|
//--- Offload center coordinates from PCR to CX,
|
|
//--- re-calculate exact value of RLo/RHi using CX.
|
|
for(j=0; j<nx; j++)
|
|
cx.Set(j,pcr[j]);
|
|
rlo=CMath::m_maxrealnumber;
|
|
rhi=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
v+=MathPow(xy[i,j]-cx[j],2.0);
|
|
v=MathSqrt(v);
|
|
rhi=MathMax(rhi,v);
|
|
rlo=MathMin(rlo,v);
|
|
}
|
|
if(!userlo)
|
|
rlo=0;
|
|
if(!userhi)
|
|
rhi=0;
|
|
return;
|
|
}
|
|
//--- Solve problem with SLP (sequential LP) approach; this approach
|
|
//--- is much faster than NLP, but often fails for MI and MC (for MZ
|
|
//--- it performs well enough).
|
|
//--- REFERENCE: "On a sequential linear programming approach to finding
|
|
//--- the smallest circumscribed, largest inscribed, and minimum
|
|
//--- zone circle or sphere", Helmuth Spath and G.A.Watson
|
|
if(solvertype==2)
|
|
{
|
|
cmatrix.Resize(cpr*npoints,nx+3);
|
|
ct.Resize(cpr*npoints);
|
|
prevc.Resize(nx);
|
|
CMinBLEIC::MinBLEICCreate(nx+2,pcr,blcstate);
|
|
CMinBLEIC::MinBLEICSetScale(blcstate,scr);
|
|
CMinBLEIC::MinBLEICSetBC(blcstate,bl,bu);
|
|
CMinBLEIC::MinBLEICSetCond(blcstate,0,0,epsx,maxits);
|
|
for(outeridx=0; outeridx<maxouterits; outeridx++)
|
|
{
|
|
//--- Prepare initial point for algorithm; center coordinates at
|
|
//--- PCR are used to calculate RLo/RHi and update PCR with them.
|
|
rlo=CMath::m_maxrealnumber;
|
|
rhi=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
v+=CMath::Sqr(xy[i,j]-pcr[j]);
|
|
v=MathSqrt(v);
|
|
rhi=MathMax(rhi,v);
|
|
rlo=MathMin(rlo,v);
|
|
}
|
|
pcr.Set(nx+0,rlo*0.99999);
|
|
pcr.Set(nx+1,rhi/0.99999);
|
|
//--- Generate matrix of linear constraints
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=MathPow(xy.Row(i)+0,2.0).Sum();
|
|
bi=-(v/2);
|
|
suboffset=0;
|
|
if(userhi)
|
|
{
|
|
dstrow=cpr*i+suboffset;
|
|
for(j=0; j<nx; j++)
|
|
cmatrix.Set(dstrow,j,pcr[j]/2-xy[i,j]);
|
|
cmatrix.Set(dstrow,nx+0,0);
|
|
cmatrix.Set(dstrow,nx+1,-(rhi/2));
|
|
cmatrix.Set(dstrow,nx+2,bi);
|
|
ct.Set(dstrow,-1);
|
|
suboffset++;
|
|
}
|
|
if(userlo)
|
|
{
|
|
dstrow=cpr*i+suboffset;
|
|
for(j=0; j<nx; j++)
|
|
cmatrix.Set(dstrow,j,-(pcr[j]/2-xy[i,j]));
|
|
cmatrix.Set(dstrow,nx+0,rlo/2);
|
|
cmatrix.Set(dstrow,nx+1,0);
|
|
cmatrix.Set(dstrow,nx+2,-bi);
|
|
ct.Set(dstrow,-1);
|
|
suboffset++;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(suboffset==cpr))
|
|
return;
|
|
}
|
|
//--- Solve LP subproblem with MinBLEIC
|
|
prevc=pcr;
|
|
prevc.Resize(nx);
|
|
CMinBLEIC::MinBLEICSetLC(blcstate,cmatrix,ct,cpr*npoints);
|
|
CMinBLEIC::MinBLEICRestartFrom(blcstate,pcr);
|
|
while(CMinBLEIC::MinBLEICIteration(blcstate))
|
|
{
|
|
if(blcstate.m_needfg)
|
|
{
|
|
rep.m_nfev++;
|
|
blcstate.m_f=vhi*blcstate.m_x[nx+1]-vlo*blcstate.m_x[nx+0];
|
|
for(j=0; j<nx; j++)
|
|
blcstate.m_g.Set(j,0);
|
|
blcstate.m_g.Set(nx+0,-(1*vlo));
|
|
blcstate.m_g.Set(nx+1,1*vhi);
|
|
continue;
|
|
}
|
|
}
|
|
CMinBLEIC::MinBLEICResults(blcstate,pcr,blcrep);
|
|
//--- check
|
|
if(!CAp::Assert(blcrep.m_terminationtype>0,__FUNCTION__+": unexpected failure of BLEIC solver"))
|
|
return;
|
|
rep.m_iterationscount+=blcrep.m_iterationscount;
|
|
//--- Terminate iterations early if we converged
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
v+=CMath::Sqr(prevc[j]-pcr[j]);
|
|
v=MathSqrt(v);
|
|
if(v<=epsx)
|
|
break;
|
|
}
|
|
//--- Offload center coordinates from PCR to CX,
|
|
//--- re-calculate exact value of RLo/RHi using CX.
|
|
for(j=0; j<nx; j++)
|
|
cx.Set(j,pcr[j]);
|
|
rlo=CMath::m_maxrealnumber;
|
|
rhi=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=MathPow(xy.Row(i)-cx.ToVector(),2.0).Sum();
|
|
v=MathSqrt(v);
|
|
rhi=MathMax(rhi,v);
|
|
rlo=MathMin(rlo,v);
|
|
}
|
|
if(!userlo)
|
|
rlo=0;
|
|
if(!userhi)
|
|
rhi=0;
|
|
return;
|
|
}
|
|
//--- Oooops...!
|
|
if(!CAp::Assert(false,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform nearest neighbor requests |
|
|
//| in the multithreaded mode (multiple threads working with same |
|
|
//| KD-tree object). |
|
|
//| This object should be created with KDTreeCreateBuffer(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV1CalcBuffer
|
|
{
|
|
CRowInt m_calcbuftags;
|
|
CRowDouble m_calcbufxcx;
|
|
CMatrixDouble m_calcbufx;
|
|
CKDTreeRequestBuffer m_requestbuffer;
|
|
//--- constructor / destructor
|
|
CRBFV1CalcBuffer(void) {}
|
|
~CRBFV1CalcBuffer(void) {}
|
|
void Copy(const CRBFV1CalcBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV1CalcBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1CalcBuffer::Copy(const CRBFV1CalcBuffer &obj)
|
|
{
|
|
m_calcbuftags=obj.m_calcbuftags;
|
|
m_calcbufxcx=obj.m_calcbufxcx;
|
|
m_calcbufx=obj.m_calcbufx;
|
|
m_requestbuffer=obj.m_requestbuffer;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF model. |
|
|
//| Never try to directly work with fields of this object - always |
|
|
//| use ALGLIB functions to use this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV1Model
|
|
{
|
|
int m_nc;
|
|
int m_nl;
|
|
int m_nx;
|
|
int m_ny;
|
|
double m_rmax;
|
|
CRowInt m_calcbuftags;
|
|
CRowDouble m_calcbufxcx;
|
|
CMatrixDouble m_calcbufx;
|
|
CMatrixDouble m_v;
|
|
CMatrixDouble m_wr;
|
|
CMatrixDouble m_xc;
|
|
CKDTree m_tree;
|
|
//--- constructor / destructor
|
|
CRBFV1Model(void);
|
|
~CRBFV1Model(void) {}
|
|
//---
|
|
void Copy(const CRBFV1Model&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV1Model&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBFV1Model::CRBFV1Model(void)
|
|
{
|
|
m_nc=0;
|
|
m_nl=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_rmax=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1Model::Copy(const CRBFV1Model &obj)
|
|
{
|
|
m_nc=obj.m_nc;
|
|
m_nl=obj.m_nl;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_rmax=obj.m_rmax;
|
|
m_calcbuftags=obj.m_calcbuftags;
|
|
m_calcbufxcx=obj.m_calcbufxcx;
|
|
m_calcbufx=obj.m_calcbufx;
|
|
m_v=obj.m_v;
|
|
m_wr=obj.m_wr;
|
|
m_xc=obj.m_xc;
|
|
m_tree=obj.m_tree;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal buffer for GridCalc3 |
|
|
//+------------------------------------------------------------------+
|
|
struct CGridCalc3v1Buf
|
|
{
|
|
bool m_flag0[];
|
|
bool m_flag12[];
|
|
bool m_flag1[];
|
|
bool m_flag2[];
|
|
CRowInt m_calcbuftags;
|
|
CRowDouble m_cx;
|
|
CRowDouble m_expbuf0;
|
|
CRowDouble m_expbuf1;
|
|
CRowDouble m_expbuf2;
|
|
CRowDouble m_tx;
|
|
CRowDouble m_ty;
|
|
CMatrixDouble m_calcbufx;
|
|
CKDTreeRequestBuffer m_requestbuf;
|
|
//--- constructor / destructor
|
|
CGridCalc3v1Buf(void) {}
|
|
~CGridCalc3v1Buf(void) {}
|
|
//---
|
|
void Copy(const CGridCalc3v1Buf&obj);
|
|
//--- overloading
|
|
void operator=(const CGridCalc3v1Buf&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CGridCalc3v1Buf::Copy(const CGridCalc3v1Buf &obj)
|
|
{
|
|
ArrayCopy(m_flag0,obj.m_flag0);
|
|
ArrayCopy(m_flag12,obj.m_flag12);
|
|
ArrayCopy(m_flag1,obj.m_flag1);
|
|
ArrayCopy(m_flag2,obj.m_flag2);
|
|
m_calcbuftags=obj.m_calcbuftags;
|
|
m_cx=obj.m_cx;
|
|
m_expbuf0=obj.m_expbuf0;
|
|
m_expbuf1=obj.m_expbuf1;
|
|
m_expbuf2=obj.m_expbuf2;
|
|
m_tx=obj.m_tx;
|
|
m_ty=obj.m_ty;
|
|
m_calcbufx=obj.m_calcbufx;
|
|
m_requestbuf=obj.m_requestbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF solution report: |
|
|
//| * TerminationType - termination type, positive values-success,|
|
|
//| non-positive - failure. |
|
|
//+------------------------------------------------------------------+
|
|
class CRBFV1Report
|
|
{
|
|
public:
|
|
int m_acols;
|
|
int m_annz;
|
|
int m_arows;
|
|
int m_iterationscount;
|
|
int m_nmv;
|
|
int m_terminationtype;
|
|
//--- constructor /destructor
|
|
CRBFV1Report(void) { ZeroMemory(this); }
|
|
~CRBFV1Report(void) {}
|
|
//---
|
|
void Copy(const CRBFV1Report&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV1Report&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1Report::Copy(const CRBFV1Report &obj)
|
|
{
|
|
m_acols=obj.m_acols;
|
|
m_annz=obj.m_annz;
|
|
m_arows=obj.m_arows;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nmv=obj.m_nmv;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CRBFV1
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_mxnx;
|
|
static const double m_rbffarradius;
|
|
static const double m_rbfnearradius;
|
|
static const double m_rbfmlradius;
|
|
static const double m_minbasecasecost;
|
|
//---
|
|
static void RBFV1Create(int nx,int ny,CRBFV1Model&s);
|
|
static void RBFV1CreateCalcBuffer(CRBFV1Model&s,CRBFV1CalcBuffer&buf);
|
|
static void RBFV1BuildModel(CMatrixDouble&x,CMatrixDouble&y,int n,int aterm,int algorithmtype,int nlayers,double radvalue,double radzvalue,double lambdav,double epsort,double epserr,int maxits,CRBFV1Model&s,CRBFV1Report&rep);
|
|
static void RBFV1Alloc(CSerializer&s,CRBFV1Model&model);
|
|
static void RBFV1Serialize(CSerializer&s,CRBFV1Model&model);
|
|
static void RBFV1Unserialize(CSerializer&s,CRBFV1Model&model);
|
|
static double RBFV1Calc2(CRBFV1Model&s,double x0,double x1);
|
|
static double RBFV1Calc3(CRBFV1Model&s,double x0,double x1,double x2);
|
|
static void RBFV1CalcBuf(CRBFV1Model&s,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV1TSCalcBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV1TSDiffBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFV1TSHessBuf(CRBFV1Model&s,CRBFV1CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFV1GridCalc2(CRBFV1Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
|
|
static void RBFV1GridCalc3VRec(CRBFV1Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowInt&blocks0,int block0a,int block0b,CRowInt&blocks1,int block1a,int block1b,CRowInt&blocks2,int block2a,int block2b,bool &flagy[],bool sparsey,double searchradius,double avgfuncpernode,CGridCalc3v1Buf&bufpool,CRowDouble&y);
|
|
static void RBFV1Unpack(CRBFV1Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
|
|
|
|
private:
|
|
static bool RBFV1BuildLinearModel(CMatrixDouble&x,CMatrixDouble&y,int n,int ny,int modeltype,CMatrixDouble&v);
|
|
static void BuildRDFModellSQR(CMatrixDouble&x,CMatrixDouble&y,CMatrixDouble&xc,CRowDouble&r,int n,int nc,int ny,CKDTree&pointstree,CKDTree¢erstree,double epsort,double epserr,int maxits,int &gnnz,int &snnz,CMatrixDouble&w,int &info,int &iterationscount,int &nmv);
|
|
static void BuildRBFMLayersModellSQR(CMatrixDouble&x,CMatrixDouble&y,CMatrixDouble&xc,double rval,CRowDouble&r,int n,int &nc,int ny,int nlayers,CKDTree¢erstree,double epsort,double epserr,int maxits,double lambdav,int &annz,CMatrixDouble&w,int &info,int &iterationscount,int &nmv);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const int CRBFV1::m_mxnx=3;
|
|
const double CRBFV1::m_rbffarradius=6;
|
|
const double CRBFV1::m_rbfnearradius=2.1;
|
|
const double CRBFV1::m_rbfmlradius=3;
|
|
const double CRBFV1::m_minbasecasecost=100000;
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates RBF model for a scalar (NY=) or vector |
|
|
//| (NY>1) function in a NX-dimensional space (NX=2 or NX=3). |
|
|
//| INPUT PARAMETERS: |
|
|
//| NX - dimension of the space, NX=2 or NX=3 |
|
|
//| NY - function dimension, NY>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - RBF model (initially equals to zero) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1Create(int nx,int ny,CRBFV1Model &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nx==2 || nx==3,__FUNCTION__+": NX<>2 and NX<>3"))
|
|
return;
|
|
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
|
|
return;
|
|
|
|
s.m_nx=nx;
|
|
s.m_ny=ny;
|
|
s.m_nl=0;
|
|
s.m_nc=0;
|
|
s.m_v=matrix<double>::Zeros(ny,m_mxnx+1);
|
|
s.m_rmax=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to|
|
|
//| perform parallel RBF model evaluations (with one RBF model|
|
|
//| instance being used from multiple threads, as long as different|
|
|
//| threads use different instances of buffer). |
|
|
//| This buffer object can be used with RBFTSCalcBuf() function (here|
|
|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
|
|
//| function which reuses previously allocated output space). |
|
|
//| How to use it: |
|
|
//| * create RBF model structure with RBFV1Create() |
|
|
//| * load data, tune parameters |
|
|
//| * call RBFV1BuildModel() |
|
|
//| * call RBFV1CreateCalcBuffer(), once per thread working with |
|
|
//| RBF model (you should call this function only AFTER call to |
|
|
//| RBFV1BuildModel(), see below for more information) |
|
|
//| * call RBFTSCalcBuf() from different threads, with each thread |
|
|
//| working with its own copy of buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with RBF model |
|
|
//| object which was used to initialize buffer. Any |
|
|
//| attempt to use buffer with different object is |
|
|
//| dangerous - you may get memory violation error because|
|
|
//| sizes of internal arrays do not fit to dimensions of |
|
|
//| RBF structure. |
|
|
//| IMPORTANT: you should call thisfunction only for model which was |
|
|
//| built with RBFV1BuildModel() function, after |
|
|
//| successful invocation of RBFV1BuildModel(). Sizes of |
|
|
//| some internal structures are determined only after |
|
|
//| model is built, so buffer object created before model|
|
|
//| construction stage will be useless (and any attempt to|
|
|
//| use it will result in exception). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1CreateCalcBuffer(CRBFV1Model &s,CRBFV1CalcBuffer &buf)
|
|
{
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_tree,buf.m_requestbuffer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds RBF model and returns report (contains some|
|
|
//| information which can be used for evaluation of the algorithm |
|
|
//| properties). |
|
|
//| Call to this function modifies RBF model by calculating its |
|
|
//| centers/radii/weights and saving them into RBFModel structure. |
|
|
//| Initially RBFModel contain zero coefficients, but after call to |
|
|
//| this function we will have coefficients which were calculated in |
|
|
//| order to fit our dataset. |
|
|
//| After you called this function you can call RBFCalc(), |
|
|
//| RBFGridCalc() and other model calculation functions. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFV1Create() call |
|
|
//| Rep - report: |
|
|
//| * Rep.TerminationType: |
|
|
//| * -5 - non-distinct basis function centers were |
|
|
//| detected, interpolation aborted |
|
|
//| * -4 - nonconvergence of the internal SVD solver|
|
|
//| * 1 - successful termination |
|
|
//| Fields are used for debugging purposes: |
|
|
//| * Rep.IterationsCount - iterations count of the |
|
|
//| LSQR solver |
|
|
//| * Rep.NMV - number of matrix-vector products |
|
|
//| * Rep.ARows - rows count for the system matrix |
|
|
//| * Rep.ACols - columns count for the system matrix |
|
|
//| * Rep.ANNZ - number of significantly non-zero |
|
|
//| elements (elements above some |
|
|
//| algorithm-determined threshold) |
|
|
//| NOTE: failure to build model will leave current State of the |
|
|
//| structure unchanged. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1BuildModel(CMatrixDouble &x,CMatrixDouble &y,
|
|
int n,int aterm,int algorithmtype,
|
|
int nlayers,double radvalue,
|
|
double radzvalue,double lambdav,
|
|
double epsort,double epserr,
|
|
int maxits,CRBFV1Model &s,
|
|
CRBFV1Report &rep)
|
|
{
|
|
//--- create variables
|
|
CKDTree tree;
|
|
CKDTree ctree;
|
|
CRowDouble dist;
|
|
CRowDouble xcx;
|
|
CMatrixDouble a;
|
|
CMatrixDouble v;
|
|
CMatrixDouble omega;
|
|
CMatrixDouble residualy;
|
|
CRowDouble radius;
|
|
CMatrixDouble xc;
|
|
int nc=0;
|
|
double rmax=0;
|
|
CRowInt tags;
|
|
CRowInt ctags;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int snnz=0;
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
int layerscnt=0;
|
|
bool modelstatus=false;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": S.NX<>2 or S.NX<>3!"))
|
|
return;
|
|
//--- Quick exit when we have no points
|
|
if(n==0)
|
|
{
|
|
rep.m_terminationtype=1;
|
|
rep.m_iterationscount=0;
|
|
rep.m_nmv=0;
|
|
rep.m_arows=0;
|
|
rep.m_acols=0;
|
|
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,0,m_mxnx,0,2,s.m_tree);
|
|
s.m_xc.Resize(0,0);
|
|
s.m_wr.Resize(0,0);
|
|
s.m_nc=0;
|
|
s.m_rmax=0;
|
|
s.m_v=matrix<double>::Zeros(s.m_ny,m_mxnx+1);
|
|
return;
|
|
}
|
|
//--- General case, N>0
|
|
rep.m_annz=0;
|
|
rep.m_iterationscount=0;
|
|
rep.m_nmv=0;
|
|
xcx.Resize(m_mxnx);
|
|
//--- First model in a sequence - linear model.
|
|
//--- Residuals from linear regression are stored in the ResidualY variable
|
|
//--- (used later to build RBF models).
|
|
residualy=y;
|
|
residualy.Resize(n,s.m_ny);
|
|
if(!RBFV1BuildLinearModel(x,residualy,n,s.m_ny,aterm,v))
|
|
{
|
|
rep.m_terminationtype=-5;
|
|
return;
|
|
}
|
|
//--- Handle special case: multilayer model with NLayers=0.
|
|
//--- Quick exit.
|
|
if(algorithmtype==2 && nlayers==0)
|
|
{
|
|
rep.m_terminationtype=1;
|
|
rep.m_iterationscount=0;
|
|
rep.m_nmv=0;
|
|
rep.m_arows=0;
|
|
rep.m_acols=0;
|
|
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,0,m_mxnx,0,2,s.m_tree);
|
|
s.m_xc.Resize(0,0);
|
|
s.m_wr.Resize(0,0);
|
|
s.m_nc=0;
|
|
s.m_rmax=0;
|
|
s.m_v=v;
|
|
s.m_v.Resize(s.m_ny,m_mxnx+1);
|
|
return;
|
|
}
|
|
//--- Second model in a sequence - RBF term.
|
|
//--- NOTE: assignments below are not necessary, but without them
|
|
//--- MSVC complains about unitialized variables.
|
|
nc=0;
|
|
rmax=0;
|
|
layerscnt=0;
|
|
modelstatus=false;
|
|
if(algorithmtype==1)
|
|
{
|
|
//--- Add RBF model.
|
|
//--- This model uses local KD-trees to speed-up nearest neighbor searches.
|
|
nc=n;
|
|
xc=x;
|
|
xc.Resize(nc,m_mxnx);
|
|
rmax=0;
|
|
radius=vector<double>::Zeros(nc);
|
|
ctags.Resize(nc);
|
|
for(i=0; i<nc; i++)
|
|
ctags.Set(i,i);
|
|
CNearestNeighbor::KDTreeBuildTagged(xc,ctags,nc,m_mxnx,0,2,ctree);
|
|
if(nc==0)
|
|
rmax=1;
|
|
else
|
|
{
|
|
if(nc==1)
|
|
{
|
|
radius.Set(0,radvalue);
|
|
rmax=radius[0];
|
|
}
|
|
else
|
|
{
|
|
//--- NC>1, calculate radii using distances to nearest neigbors
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
xcx=xc[i]+0;
|
|
if(CNearestNeighbor::KDTreeQueryKNN(ctree,xcx,1,false)>0)
|
|
{
|
|
CNearestNeighbor::KDTreeQueryResultsDistances(ctree,dist);
|
|
radius.Set(i,radvalue*dist[0]);
|
|
}
|
|
else
|
|
{
|
|
//--- No neighbors found (it will happen when we have only one center).
|
|
//--- Initialize radius with default value.
|
|
radius.Set(i,1.0);
|
|
}
|
|
}
|
|
//--- Apply filtering
|
|
tmp0=radius;
|
|
CTSort::TagSortFast(tmp0,tmp1,nc);
|
|
for(i=0; i<nc; i++)
|
|
radius.Set(i,MathMin(radius[i],radzvalue*tmp0[nc/2]));
|
|
//--- Calculate RMax, check that all radii are non-zero
|
|
for(i=0; i<nc; i++)
|
|
rmax=MathMax(rmax,radius[i]);
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
if(radius[i]==0.0)
|
|
{
|
|
rep.m_terminationtype=-5;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tags.Resize(n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
tags.Set(i,i);
|
|
}
|
|
CNearestNeighbor::KDTreeBuildTagged(x,tags,n,m_mxnx,0,2,tree);
|
|
BuildRDFModellSQR(x,residualy,xc,radius,n,nc,s.m_ny,tree,ctree,epsort,epserr,maxits,rep.m_annz,snnz,omega,rep.m_terminationtype,rep.m_iterationscount,rep.m_nmv);
|
|
layerscnt=1;
|
|
modelstatus=true;
|
|
}
|
|
if(algorithmtype==2)
|
|
{
|
|
rmax=radvalue;
|
|
BuildRBFMLayersModellSQR(x,residualy,xc,radvalue,radius,n,nc,s.m_ny,nlayers,ctree,1.0E-6,1.0E-6,50,lambdav,rep.m_annz,omega,rep.m_terminationtype,rep.m_iterationscount,rep.m_nmv);
|
|
layerscnt=nlayers;
|
|
modelstatus=true;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(modelstatus,__FUNCTION__+": integrity error"))
|
|
return;
|
|
if(rep.m_terminationtype<=0)
|
|
return;
|
|
//--- Model is built
|
|
s.m_nc=nc/layerscnt;
|
|
s.m_rmax=rmax;
|
|
s.m_nl=layerscnt;
|
|
s.m_xc=xc;
|
|
s.m_xc.Resize(s.m_nc,m_mxnx);
|
|
s.m_wr=matrix<double>::Zeros(s.m_nc,1+s.m_nl*s.m_ny);
|
|
tags.Resize(s.m_nc);
|
|
for(i=0; i<s.m_nc; i++)
|
|
tags.Set(i,i);
|
|
CNearestNeighbor::KDTreeBuildTagged(s.m_xc,tags,s.m_nc,m_mxnx,0,2,s.m_tree);
|
|
s.m_wr.Col(0,radius);
|
|
for(i=0; i<s.m_nc; i++)
|
|
for(k=0; k<layerscnt; k++)
|
|
for(j=0; j<s.m_ny; j++)
|
|
s.m_wr.Set(i,1+k*s.m_ny+j,omega.Get(k*s.m_nc+i,j));
|
|
s.m_v=v;
|
|
s.m_v.Resize(s.m_ny,m_mxnx+1);
|
|
rep.m_terminationtype=1;
|
|
rep.m_arows=n;
|
|
rep.m_acols=s.m_nc;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1Alloc(CSerializer &s,CRBFV1Model &model)
|
|
{
|
|
//--- Data
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
|
|
CApServ::AllocRealMatrix(s,model.m_xc,-1,-1);
|
|
CApServ::AllocRealMatrix(s,model.m_wr,-1,-1);
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealMatrix(s,model.m_v,-1,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1Serialize(CSerializer &s,CRBFV1Model &model)
|
|
{
|
|
//--- Data
|
|
//
|
|
s.Serialize_Int(model.m_nx);
|
|
s.Serialize_Int(model.m_ny);
|
|
s.Serialize_Int(model.m_nc);
|
|
s.Serialize_Int(model.m_nl);
|
|
CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
|
|
CApServ::SerializeRealMatrix(s,model.m_xc,-1,-1);
|
|
CApServ::SerializeRealMatrix(s,model.m_wr,-1,-1);
|
|
s.Serialize_Double(model.m_rmax);
|
|
CApServ::SerializeRealMatrix(s,model.m_v,-1,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1Unserialize(CSerializer &s,CRBFV1Model &model)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
//--- Unserialize primary model parameters, initialize model.
|
|
//--- It is necessary to call RBFV1Create() because some internal fields
|
|
//--- which are NOT unserialized will need initialization.
|
|
nx=s.Unserialize_Int();
|
|
ny=s.Unserialize_Int();
|
|
RBFV1Create(nx,ny,model);
|
|
model.m_nc=s.Unserialize_Int();
|
|
model.m_nl=s.Unserialize_Int();
|
|
CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
|
|
CApServ::UnserializeRealMatrix(s,model.m_xc);
|
|
CApServ::UnserializeRealMatrix(s,model.m_wr);
|
|
model.m_rmax=s.Unserialize_Double();
|
|
CApServ::UnserializeRealMatrix(s,model.m_v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY=1 (scalar function) |
|
|
//| and NX=2 (2-dimensional space). If you have 3-dimensional space, |
|
|
//| use RBFCalc3(). If you have general situation (NX-dimensional |
|
|
//| space, NY-dimensional function) you should use general, less |
|
|
//| efficient implementation RBFCalc(). |
|
|
//| If you want to calculate function values many times, consider |
|
|
//| using RBFGridCalc2(), which is far more efficient than many |
|
|
//| subsequent calls to RBFCalc2(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV1::RBFV1Calc2(CRBFV1Model &s,double x0,double x1)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int lx=0;
|
|
int tg=0;
|
|
double d2=0;
|
|
double t=0;
|
|
double bfcur=0;
|
|
double rcur=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,m_mxnx);
|
|
if(s.m_nc==0)
|
|
return(result);
|
|
|
|
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
|
|
s.m_calcbufxcx.Set(0,x0);
|
|
s.m_calcbufxcx.Set(1,x1);
|
|
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
|
|
for(int i=0; i<lx; i++)
|
|
{
|
|
tg=s.m_calcbuftags[i];
|
|
d2=CMath::Sqr(x0-s.m_calcbufx.Get(i,0))+CMath::Sqr(x1-s.m_calcbufx.Get(i,1));
|
|
rcur=s.m_wr.Get(tg,0);
|
|
bfcur=MathExp(-(d2/(rcur*rcur)));
|
|
for(int j=0; j<s.m_nl; j++)
|
|
{
|
|
result=result+bfcur*s.m_wr.Get(tg,1+j);
|
|
rcur=0.5*rcur;
|
|
t=bfcur*bfcur;
|
|
bfcur=t*t;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY=1 (scalar function) |
|
|
//| and NX=3 (3-dimensional space). If you have 2-dimensional space, |
|
|
//| use RBFCalc2(). If you have general situation (NX-dimensional |
|
|
//| space, NY-dimensional function) you should use general, less |
|
|
//| efficient implementation RBFCalc(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| X2 - third coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV1::RBFV1Calc3(CRBFV1Model &s,double x0,double x1,double x2)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int lx=0;
|
|
int tg=0;
|
|
double t=0;
|
|
double rcur=0;
|
|
double bf=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=3)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,m_mxnx);
|
|
if(s.m_nc==0)
|
|
return(result);
|
|
//--- calculating value for F(X)
|
|
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
|
|
s.m_calcbufxcx.Set(0,x0);
|
|
s.m_calcbufxcx.Set(1,x1);
|
|
s.m_calcbufxcx.Set(2,x2);
|
|
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
|
|
for(int i=0; i<lx; i++)
|
|
{
|
|
tg=s.m_calcbuftags[i];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
bf=MathExp(-((CMath::Sqr(x0-s.m_calcbufx.Get(i,0))+CMath::Sqr(x1-s.m_calcbufx.Get(i,1))+CMath::Sqr(x2-s.m_calcbufx.Get(i,2)))/CMath::Sqr(rcur)));
|
|
for(int j=0; j<s.m_nl; j++)
|
|
{
|
|
result+=bf*s.m_wr.Get(tg,1+j);
|
|
t=bf*bf;
|
|
bf=t*t;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point. |
|
|
//| Same as RBFCalc(), but does not reallocate Y when in is large |
|
|
//| enough to store function values. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1CalcBuf(CRBFV1Model &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int lx=0;
|
|
int tg=0;
|
|
double t=0;
|
|
double rcur=0;
|
|
double bf=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(CAp::Len(y)<s.m_ny)
|
|
{
|
|
y.Resize(s.m_ny);
|
|
}
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,m_mxnx));
|
|
for(int j=0; j<s.m_nx; j++)
|
|
y.Add(i,x[j]*s.m_v.Get(i,j));
|
|
}
|
|
if(s.m_nc==0)
|
|
return;
|
|
s.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
|
|
for(int i=0; i<s.m_nx; i++)
|
|
s.m_calcbufxcx.Set(i,x[i]);
|
|
lx=CNearestNeighbor::KDTreeQueryRNN(s.m_tree,s.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeQueryResultsX(s.m_tree,s.m_calcbufx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(s.m_tree,s.m_calcbuftags);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
for(int j=0; j<lx; j++)
|
|
{
|
|
tg=s.m_calcbuftags[j];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
bf=MathExp(-((CMath::Sqr(s.m_calcbufxcx[0]-s.m_calcbufx.Get(j,0))+CMath::Sqr(s.m_calcbufxcx[1]-s.m_calcbufx.Get(j,1))+CMath::Sqr(s.m_calcbufxcx[2]-s.m_calcbufx.Get(j,2)))/CMath::Sqr(rcur)));
|
|
for(int k=0; k<s.m_nl; k++)
|
|
{
|
|
y.Add(i,bf*s.m_wr.Get(tg,1+k*s.m_ny+i));
|
|
t=bf*bf;
|
|
bf=t*t;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point, using external buffer object (internal temporaries of|
|
|
//| RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different |
|
|
//| instances of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFV1CreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1TSCalcBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int lx=0;
|
|
int tg=0;
|
|
double t=0;
|
|
double rcur=0;
|
|
double bf=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,m_mxnx));
|
|
for(int j=0; j<s.m_nx; j++)
|
|
y.Add(i,s.m_v.Get(i,j)*x[j]);
|
|
}
|
|
if(s.m_nc==0)
|
|
return;
|
|
buf.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
|
|
for(int i=0; i<s.m_nx; i++)
|
|
buf.m_calcbufxcx.Set(i,x[i]);
|
|
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
for(int j=0; j<lx; j++)
|
|
{
|
|
tg=buf.m_calcbuftags[j];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
bf=MathExp(-((CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))/CMath::Sqr(rcur)));
|
|
for(int k=0; k<s.m_nl; k++)
|
|
{
|
|
y.Add(i,bf*s.m_wr.Get(tg,1+k*s.m_ny+i));
|
|
t=bf*bf;
|
|
bf=t*t;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its derivatives, using external buffer object (internal|
|
|
//| temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances |
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFV1CreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y, DY - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1TSDiffBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,CRowDouble &dy)
|
|
{
|
|
//--- create variables
|
|
int kk=0;
|
|
int lx=0;
|
|
int tg=0;
|
|
double rcur=0;
|
|
double invrcur2=0;
|
|
double f=0;
|
|
double df=0;
|
|
double w=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,m_mxnx));
|
|
for(int j=0; j<s.m_nx; j++)
|
|
{
|
|
y.Add(i,s.m_v.Get(i,j)*x[j]);
|
|
dy.Set(i*s.m_nx+j,s.m_v.Get(i,j));
|
|
}
|
|
}
|
|
if(s.m_nc==0)
|
|
return;
|
|
|
|
buf.m_calcbufxcx=vector<double>::Zeros(m_mxnx);
|
|
for(int i=0; i<s.m_nx; i++)
|
|
buf.m_calcbufxcx.Set(i,x[i]);
|
|
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
for(int j=0; j<lx; j++)
|
|
{
|
|
tg=buf.m_calcbuftags[j];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
invrcur2=1/(rcur*rcur);
|
|
f=MathExp(-(CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))*invrcur2);
|
|
df=-f;
|
|
for(int k=0; k<s.m_nl; k++)
|
|
{
|
|
w=s.m_wr.Get(tg,1+k*s.m_ny+i);
|
|
y.Add(i,f*w);
|
|
for(kk=0; kk<s.m_nx; kk++)
|
|
dy.Add(i*s.m_nx+kk,w*df*invrcur2*2*(buf.m_calcbufxcx[kk]-buf.m_calcbufx.Get(j,kk)));
|
|
f=MathPow(f,4.0);
|
|
df=-f;
|
|
invrcur2*=4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its first/second derivatives, using external buffer|
|
|
//| object (internal temporaries of the RBF model are not modified).|
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different |
|
|
//| instances of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFV1CreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y, DY, D2Y - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//| D2Y - derivatives, array[NY*NX*NX]. D2Y is not |
|
|
//| reallocated when it is larger than NY*NX*NX. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1TSHessBuf(CRBFV1Model &s,CRBFV1CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,CRowDouble &dy,
|
|
CRowDouble &d2y)
|
|
{
|
|
//--- create variables
|
|
int lx=0;
|
|
int tg=0;
|
|
double t=0;
|
|
double rcur=0;
|
|
double invrcur2=0;
|
|
double f=0;
|
|
double df=0;
|
|
double d2f=0;
|
|
double w=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
if(CAp::Len(d2y)<s.m_ny*s.m_nx*s.m_nx)
|
|
d2y.Resize(s.m_ny*s.m_nx*s.m_nx);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,m_mxnx));
|
|
for(int j=0; j<s.m_nx; j++)
|
|
{
|
|
y.Add(i,s.m_v.Get(i,j)*x[j]);
|
|
dy.Set(i*s.m_nx+j,s.m_v.Get(i,j));
|
|
}
|
|
}
|
|
for(int i=0; i<(s.m_ny*s.m_nx*s.m_nx); i++)
|
|
d2y.Set(i,0);
|
|
if(s.m_nc==0)
|
|
return;
|
|
|
|
for(int i=0; i<(m_mxnx); i++)
|
|
buf.m_calcbufxcx.Set(i,0);
|
|
for(int i=0; i<s.m_nx; i++)
|
|
buf.m_calcbufxcx.Set(i,x[i]);
|
|
lx=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,buf.m_requestbuffer,buf.m_calcbufxcx,s.m_rmax*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,buf.m_requestbuffer,buf.m_calcbufx);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,buf.m_requestbuffer,buf.m_calcbuftags);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
{
|
|
for(int j=0; j<lx; j++)
|
|
{
|
|
tg=buf.m_calcbuftags[j];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
invrcur2=1/(rcur*rcur);
|
|
f=MathExp(-((CMath::Sqr(buf.m_calcbufxcx[0]-buf.m_calcbufx.Get(j,0))+CMath::Sqr(buf.m_calcbufxcx[1]-buf.m_calcbufx.Get(j,1))+CMath::Sqr(buf.m_calcbufxcx[2]-buf.m_calcbufx.Get(j,2)))*invrcur2));
|
|
df=-f;
|
|
d2f=f;
|
|
for(int k=0; k<s.m_nl; k++)
|
|
{
|
|
w=s.m_wr.Get(tg,1+k*s.m_ny+i);
|
|
y.Add(i,f*w);
|
|
for(int i0=0; i0<s.m_nx; i0++)
|
|
{
|
|
for(int i1=0; i1<s.m_nx; i1++)
|
|
{
|
|
if(i0==i1)
|
|
{
|
|
//--- Compute derivative and diagonal element of the Hessian
|
|
dy.Add(i*s.m_nx+i0,w*df*invrcur2*2*(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0)));
|
|
d2y.Add(i*s.m_nx*s.m_nx+i0*s.m_nx+i1,w*(d2f*invrcur2*invrcur2*4*CMath::Sqr(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0))+df*invrcur2*2));
|
|
}
|
|
else
|
|
{
|
|
//--- Compute off-diagonal element of the Hessian
|
|
d2y.Add(i*s.m_nx*s.m_nx+i0*s.m_nx+i1,w*d2f*invrcur2*invrcur2*4*(buf.m_calcbufxcx[i0]-buf.m_calcbufx.Get(j,i0))*(buf.m_calcbufxcx[i1]-buf.m_calcbufx.Get(j,i1)));
|
|
}
|
|
}
|
|
}
|
|
t=f*f;
|
|
f=MathPow(f,4.0);
|
|
df=-f;
|
|
d2f=f;
|
|
invrcur2*=4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the regular |
|
|
//| grid. |
|
|
//| Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J]) |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0] |
|
|
//| N0 - grid size (number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| N1 - grid size (number of nodes) in the second dimension|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[N0,N1]. Y is out-variable |
|
|
//| and is reallocated by this function. |
|
|
//| NOTE: as a special exception, this function supports unordered |
|
|
//| arrays X0 and X1. However, future versions may be more |
|
|
//| efficient for X0/X1 ordered by ascending. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1GridCalc2(CRBFV1Model &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CMatrixDouble &y)
|
|
{
|
|
//--- create variables
|
|
CRowDouble cpx0;
|
|
CRowDouble cpx1;
|
|
CRowInt p01;
|
|
CRowInt p11;
|
|
CRowInt p2;
|
|
double rlimit=0;
|
|
double xcnorm2=0;
|
|
int hp01=0;
|
|
double hcpx0=0;
|
|
double xc0=0;
|
|
double xc1=0;
|
|
double omega=0;
|
|
double radius=0;
|
|
int i00=0;
|
|
int i01=0;
|
|
int i10=0;
|
|
int i11=0;
|
|
y.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
|
|
y=matrix<double>::Zeros(n0,n1);
|
|
if((s.m_ny!=1 || s.m_nx!=2) || s.m_nc==0)
|
|
return;
|
|
//--- create and sort arrays
|
|
cpx0.Resize(n0);
|
|
for(int i=0; i<n0; i++)
|
|
cpx0.Set(i,x0[i]);
|
|
CTSort::TagSort(cpx0,n0,p01,p2);
|
|
cpx1.Resize(n1);
|
|
for(int i=0; i<n1; i++)
|
|
cpx1.Set(i,x1[i]);
|
|
CTSort::TagSort(cpx1,n1,p11,p2);
|
|
//--- calculate function's value
|
|
for(int i=0; i<s.m_nc; i++)
|
|
{
|
|
radius=s.m_wr.Get(i,0);
|
|
for(int d=0; d<s.m_nl; d++)
|
|
{
|
|
omega=s.m_wr.Get(i,1+d);
|
|
rlimit=radius*m_rbffarradius;
|
|
//search lower and upper indexes
|
|
i00=CTSort::LowerBound(cpx0,n0,s.m_xc.Get(i,0)-rlimit);
|
|
i01=CTSort::UpperBound(cpx0,n0,s.m_xc.Get(i,0)+rlimit);
|
|
i10=CTSort::LowerBound(cpx1,n1,s.m_xc.Get(i,1)-rlimit);
|
|
i11=CTSort::UpperBound(cpx1,n1,s.m_xc.Get(i,1)+rlimit);
|
|
xc0=s.m_xc.Get(i,0);
|
|
xc1=s.m_xc.Get(i,1);
|
|
for(int j=i00; j<i01 ; j++)
|
|
{
|
|
hcpx0=cpx0[j];
|
|
hp01=p01[j];
|
|
for(int k=i10; k<=i11-1; k++)
|
|
{
|
|
xcnorm2=CMath::Sqr(hcpx0-xc0)+CMath::Sqr(cpx1[k]-xc1);
|
|
if((double)(xcnorm2)<=(double)(rlimit*rlimit))
|
|
y.Add(hp01,p11[k],MathExp(-(xcnorm2/CMath::Sqr(radius)))*omega);
|
|
}
|
|
}
|
|
radius=0.5*radius;
|
|
}
|
|
}
|
|
//--- add linear term
|
|
for(int i=0; i<n0; i++)
|
|
{
|
|
for(int j=0; j<n1; j++)
|
|
y.Add(i,j,s.m_v.Get(0,0)*x0[i]+s.m_v.Get(0,1)*x1[j]+s.m_v.Get(0,m_mxnx));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1GridCalc3VRec(CRBFV1Model &s,CRowDouble &x0,
|
|
int n0,CRowDouble &x1,int n1,
|
|
CRowDouble &x2,int n2,
|
|
CRowInt &blocks0,int block0a,
|
|
int block0b,CRowInt &blocks1,
|
|
int block1a,int block1b,
|
|
CRowInt &blocks2,int block2a,
|
|
int block2b,bool &flagy[],
|
|
bool sparsey,double searchradius,
|
|
double avgfuncpernode,
|
|
CGridCalc3v1Buf &bufpool,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int t=0;
|
|
int l=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
int i2=0;
|
|
int ic=0;
|
|
CGridCalc3v1Buf pbuf;
|
|
int flag12dim1=0;
|
|
int flag12dim2=0;
|
|
double problemcost=0;
|
|
int maxbs=0;
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
double v=0;
|
|
int kc=0;
|
|
int tg=0;
|
|
double rcur=0;
|
|
double rcur2=0;
|
|
double basisfuncval=0;
|
|
int dstoffs=0;
|
|
int srcoffs=0;
|
|
int ubnd=0;
|
|
double w0=0;
|
|
double w1=0;
|
|
double w2=0;
|
|
bool allnodes=false;
|
|
bool somenodes=false;
|
|
|
|
pbuf=bufpool;
|
|
//--- Calculate RBF model
|
|
for(i2=block2a; i2<=block2b-1; i2++)
|
|
{
|
|
for(i1=block1a; i1<=block1b-1; i1++)
|
|
{
|
|
for(i0=block0a; i0<=block0b-1; i0++)
|
|
{
|
|
//--- Analyze block - determine what elements are needed and what are not.
|
|
//--- After this block is done, two flag variables can be used:
|
|
//--- * SomeNodes, which is True when there are at least one node which have
|
|
//--- to be calculated
|
|
//--- * AllNodes, which is True when all nodes are required
|
|
somenodes=true;
|
|
allnodes=true;
|
|
flag12dim1=blocks1[i1+1]-blocks1[i1];
|
|
flag12dim2=blocks2[i2+1]-blocks2[i2];
|
|
if(sparsey)
|
|
{
|
|
//--- Use FlagY to determine what is required.
|
|
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag0,n0);
|
|
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag1,n1);
|
|
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag2,n2);
|
|
CApServ::BVectorSetLengthAtLeast(pbuf.m_flag12,flag12dim1*flag12dim2);
|
|
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
|
|
pbuf.m_flag0[i]=false;
|
|
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
|
|
pbuf.m_flag1[j]=false;
|
|
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
|
|
pbuf.m_flag2[k]=false;
|
|
for(i=0; i<flag12dim1*flag12dim2; i++)
|
|
pbuf.m_flag12[i]=false;
|
|
somenodes=false;
|
|
allnodes=true;
|
|
for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
|
|
{
|
|
for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
|
|
{
|
|
dstoffs=j-blocks1[i1]+flag12dim1*(k-blocks2[i2]);
|
|
srcoffs=j*n0+k*n0*n1;
|
|
for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
|
|
if(flagy[srcoffs+i])
|
|
{
|
|
pbuf.m_flag0[i]=true;
|
|
pbuf.m_flag1[j]=true;
|
|
pbuf.m_flag2[k]=true;
|
|
pbuf.m_flag12[dstoffs]=true;
|
|
somenodes=true;
|
|
}
|
|
else
|
|
allnodes=false;
|
|
}
|
|
}
|
|
}
|
|
//--- Skip block if it is completely empty.
|
|
if(!somenodes)
|
|
continue;
|
|
//--- compute linear term for block (I0,I1,I2)
|
|
for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
|
|
{
|
|
for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
|
|
{
|
|
//--- do we need this micro-row?
|
|
if(!allnodes && !pbuf.m_flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])])
|
|
continue;
|
|
//--- Compute linear term
|
|
for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
|
|
{
|
|
pbuf.m_tx.Set(0,x0[i]);
|
|
pbuf.m_tx.Set(1,x1[j]);
|
|
pbuf.m_tx.Set(2,x2[k]);
|
|
for(l=0; l<s.m_ny; l++)
|
|
{
|
|
v=s.m_v.Get(l,m_mxnx);
|
|
for(t=0; t<nx; t++)
|
|
v+=s.m_v.Get(l,t)*pbuf.m_tx[t];
|
|
y.Set(l+ny*(i+j*n0+k*n0*n1),v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- compute RBF term for block (I0,I1,I2)
|
|
pbuf.m_tx.Set(0,0.5*(x0[blocks0[i0]]+x0[blocks0[i0+1]-1]));
|
|
pbuf.m_tx.Set(1,0.5*(x1[blocks1[i1]]+x1[blocks1[i1+1]-1]));
|
|
pbuf.m_tx.Set(2,0.5*(x2[blocks2[i2]]+x2[blocks2[i2+1]-1]));
|
|
kc=CNearestNeighbor::KDTreeTsQueryRNN(s.m_tree,pbuf.m_requestbuf,pbuf.m_tx,searchradius,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsX(s.m_tree,pbuf.m_requestbuf,pbuf.m_calcbufx);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(s.m_tree,pbuf.m_requestbuf,pbuf.m_calcbuftags);
|
|
for(ic=0; ic<kc; ic++)
|
|
{
|
|
pbuf.m_cx.Set(0,pbuf.m_calcbufx.Get(ic,0));
|
|
pbuf.m_cx.Set(1,pbuf.m_calcbufx.Get(ic,1));
|
|
pbuf.m_cx.Set(2,pbuf.m_calcbufx.Get(ic,2));
|
|
tg=pbuf.m_calcbuftags[ic];
|
|
rcur=s.m_wr.Get(tg,0);
|
|
rcur2=rcur*rcur;
|
|
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
|
|
{
|
|
if(allnodes || pbuf.m_flag0[i])
|
|
pbuf.m_expbuf0.Set(i,MathExp(-(CMath::Sqr(x0[i]-pbuf.m_cx[0])/rcur2)));
|
|
else
|
|
pbuf.m_expbuf0.Set(i,0.0);
|
|
}
|
|
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
|
|
{
|
|
if(allnodes || pbuf.m_flag1[j])
|
|
pbuf.m_expbuf1.Set(j,MathExp(-(CMath::Sqr(x1[j]-pbuf.m_cx[1])/rcur2)));
|
|
else
|
|
pbuf.m_expbuf1.Set(j,0.0);
|
|
}
|
|
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
|
|
{
|
|
if(allnodes || pbuf.m_flag2[k])
|
|
pbuf.m_expbuf2.Set(k,MathExp(-(CMath::Sqr(x2[k]-pbuf.m_cx[2])/rcur2)));
|
|
else
|
|
pbuf.m_expbuf2.Set(k,0.0);
|
|
}
|
|
for(t=0; t<s.m_nl; t++)
|
|
{
|
|
//--- Calculate
|
|
for(k=blocks2[i2]; k<blocks2[i2+1]; k++)
|
|
{
|
|
for(j=blocks1[i1]; j<blocks1[i1+1]; j++)
|
|
{
|
|
//--- do we need this micro-row?
|
|
if(!allnodes && !pbuf.m_flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])])
|
|
continue;
|
|
//--- Prepare local variables
|
|
dstoffs=ny*(blocks0[i0]+j*n0+k*n0*n1);
|
|
v=pbuf.m_expbuf1[j]*pbuf.m_expbuf2[k];
|
|
//--- Optimized for NY=1
|
|
if(s.m_ny==1)
|
|
{
|
|
w0=s.m_wr.Get(tg,1+t*s.m_ny+0);
|
|
ubnd=blocks0[i0+1]-1;
|
|
for(i=blocks0[i0]; i<=ubnd; i++)
|
|
{
|
|
basisfuncval=pbuf.m_expbuf0[i]*v;
|
|
y.Add(dstoffs,basisfuncval*w0);
|
|
dstoffs++;
|
|
}
|
|
continue;
|
|
}
|
|
//--- Optimized for NY=2
|
|
if(s.m_ny==2)
|
|
{
|
|
w0=s.m_wr.Get(tg,1+t*s.m_ny);
|
|
w1=s.m_wr.Get(tg,1+t*s.m_ny+1);
|
|
ubnd=blocks0[i0+1]-1;
|
|
for(i=blocks0[i0]; i<=ubnd; i++)
|
|
{
|
|
basisfuncval=pbuf.m_expbuf0[i]*v;
|
|
y.Add(dstoffs,basisfuncval*w0);
|
|
y.Add(dstoffs+1,basisfuncval*w1);
|
|
dstoffs+=2;
|
|
}
|
|
continue;
|
|
}
|
|
//--- Optimized for NY=3
|
|
if(s.m_ny==3)
|
|
{
|
|
w0=s.m_wr.Get(tg,1+t*s.m_ny);
|
|
w1=s.m_wr.Get(tg,1+t*s.m_ny+1);
|
|
w2=s.m_wr.Get(tg,1+t*s.m_ny+2);
|
|
ubnd=blocks0[i0+1]-1;
|
|
for(i=blocks0[i0]; i<=ubnd; i++)
|
|
{
|
|
basisfuncval=pbuf.m_expbuf0[i]*v;
|
|
y.Add(dstoffs,basisfuncval*w0);
|
|
y.Add(dstoffs+1,basisfuncval*w1);
|
|
y.Add(dstoffs+2,basisfuncval*w2);
|
|
dstoffs+=3;
|
|
}
|
|
continue;
|
|
}
|
|
//--- General case
|
|
for(i=blocks0[i0]; i<blocks0[i0+1]; i++)
|
|
{
|
|
basisfuncval=pbuf.m_expbuf0[i]*v;
|
|
for(l=0; l<s.m_ny; l++)
|
|
y.Add(l+dstoffs,basisfuncval*s.m_wr.Get(tg,1+t*s.m_ny+l));
|
|
dstoffs+=ny;
|
|
}
|
|
}
|
|
}
|
|
//--- Update basis functions
|
|
if(t!=s.m_nl-1)
|
|
{
|
|
ubnd=blocks0[i0+1]-1;
|
|
for(i=blocks0[i0]; i<=ubnd; i++)
|
|
{
|
|
if(allnodes || pbuf.m_flag0[i])
|
|
pbuf.m_expbuf0.Set(i,MathPow(pbuf.m_expbuf0[i],4));
|
|
}
|
|
ubnd=blocks1[i1+1]-1;
|
|
for(j=blocks1[i1]; j<=ubnd; j++)
|
|
{
|
|
if(allnodes || pbuf.m_flag1[j])
|
|
pbuf.m_expbuf1.Set(j,MathPow(pbuf.m_expbuf1[j],4));
|
|
}
|
|
ubnd=blocks2[i2+1]-1;
|
|
for(k=blocks2[i2]; k<=ubnd; k++)
|
|
{
|
|
if(allnodes || pbuf.m_flag2[k])
|
|
pbuf.m_expbuf2.Set(k,MathPow(pbuf.m_expbuf2[k],4));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Recycle buffer object back to pool
|
|
bufpool=pbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "unpacks" RBF model by extracting its coefficients.|
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NX - dimensionality of argument |
|
|
//| NY - dimensionality of the target function |
|
|
//| XWR - model information, array[NC,NX+NY+1]. |
|
|
//| One row of the array corresponds to one basis |
|
|
//| function: |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of|
|
|
//| the function being modelled |
|
|
//| * last column - radius, same for all dimensions|
|
|
//| of the function being modelled |
|
|
//| NC - number of the centers |
|
|
//| V - polynomial term , array[NY,NX+1]. One row per one |
|
|
//| dimension of the function being modelled. First NX |
|
|
//| elements are linear coefficients, V[NX] is equal to|
|
|
//| the constant part. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::RBFV1Unpack(CRBFV1Model &s,int &nx,int &ny,
|
|
CMatrixDouble &xwr,int &nc,CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
double rcur=0;
|
|
int i1_=0;
|
|
|
|
nx=0;
|
|
ny=0;
|
|
xwr.Resize(0,0);
|
|
nc=0;
|
|
v.Resize(0,0);
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
nc=s.m_nc;
|
|
//--- Fill V
|
|
v=s.m_v;
|
|
v.Resize(s.m_ny,s.m_nx+1);
|
|
v.Col(s.m_nx,s.m_v.Col(m_mxnx)+0);
|
|
//--- Fill XWR and V
|
|
if(nc*s.m_nl>0)
|
|
{
|
|
xwr.Resize(s.m_nc*s.m_nl,s.m_nx+s.m_ny+1);
|
|
for(int i=0; i<s.m_nc; i++)
|
|
{
|
|
rcur=s.m_wr.Get(i,0);
|
|
for(int j=0; j<s.m_nl; j++)
|
|
{
|
|
for(int i_=0; i_<s.m_nx; i_++)
|
|
xwr.Set(i*s.m_nl+j,i_,s.m_xc.Get(i,i_));
|
|
i1_=(1+j*s.m_ny)-(s.m_nx);
|
|
for(int i_=s.m_nx; i_<=s.m_nx+s.m_ny-1; i_++)
|
|
xwr.Set(i*s.m_nl+j,i_,s.m_wr.Get(i,i_+i1_));
|
|
xwr.Set(i*s.m_nl+j,s.m_nx+s.m_ny,rcur);
|
|
rcur=0.5*rcur;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CRBFV1::RBFV1BuildLinearModel(CMatrixDouble &x,CMatrixDouble &y,
|
|
int n,int ny,int modeltype,CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
CRowDouble tmpy;
|
|
CMatrixDouble a;
|
|
double scaling=0;
|
|
CRowDouble shifting;
|
|
double mn=0;
|
|
double mx=0;
|
|
CRowDouble c;
|
|
CLSFitReport rep;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int info=0;
|
|
v.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return(false);
|
|
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
|
|
return(false);
|
|
//--- Handle degenerate case (N=0)
|
|
result=true;
|
|
v=matrix<double>::Zeros(ny,m_mxnx+1);
|
|
if(n==0)
|
|
return(result);
|
|
//--- Allocate temporaries
|
|
tmpy.Resize(n);
|
|
//--- General linear model.
|
|
switch(modeltype)
|
|
{
|
|
case 1:
|
|
//--- Calculate scaling/shifting, transform variables, prepare LLS problem
|
|
a.Resize(n,m_mxnx+1);
|
|
shifting.Resize(m_mxnx);
|
|
scaling=0;
|
|
for(i=0; i<m_mxnx; i++)
|
|
{
|
|
mn=x.Get(0,i);
|
|
mx=mn;
|
|
for(j=1; j<n; j++)
|
|
{
|
|
double value=x.Get(j,i);
|
|
if(mn>value)
|
|
mn=value;
|
|
if(mx<value)
|
|
mx=value;
|
|
}
|
|
scaling=MathMax(scaling,mx-mn);
|
|
shifting.Set(i,0.5*(mx+mn));
|
|
}
|
|
if(scaling==0.0)
|
|
scaling=1;
|
|
else
|
|
scaling/=2;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<m_mxnx; j++)
|
|
a.Set(i,j,(x.Get(i,j)-shifting[j])/scaling);
|
|
a.Set(i,m_mxnx,1);
|
|
}
|
|
//--- Solve linear system in transformed variables, make backward
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
tmpy=y.Col(i)+0;
|
|
CLSFit::LSFitLinear(tmpy,a,n,m_mxnx+1,info,c,rep);
|
|
if(info<=0)
|
|
{
|
|
result=false;
|
|
return(result);
|
|
}
|
|
for(j=0; j<m_mxnx; j++)
|
|
v.Set(i,j,c[j]/scaling);
|
|
v.Set(i,m_mxnx,c[m_mxnx]-CAblasF::RDotVR(m_mxnx,shifting,v,i));
|
|
for(j=0; j<n; j++)
|
|
y.Add(j,i,-(CAblasF::RDotRR(m_mxnx,x,j,v,i)+v.Get(i,m_mxnx)));
|
|
}
|
|
break;
|
|
//--- Constant model, very simple
|
|
case 2:
|
|
v.Fill(0,ny,m_mxnx+1);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
v.Add(i,m_mxnx,y.Get(j,i));
|
|
if(n>0)
|
|
v.Mul(i,m_mxnx,1.0/n);
|
|
for(j=0; j<n; j++)
|
|
y.Add(j,i,-v.Get(i,m_mxnx));
|
|
}
|
|
break;
|
|
case 3:
|
|
//--- Zero model
|
|
v.Fill(0,ny,m_mxnx+1);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unknown model type");
|
|
return(false);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::BuildRDFModellSQR(CMatrixDouble &x,CMatrixDouble &y,
|
|
CMatrixDouble &xc,CRowDouble &r,
|
|
int n,int nc,int ny,CKDTree &pointstree,
|
|
CKDTree ¢erstree,double epsort,
|
|
double epserr,int maxits,int &gnnz,
|
|
int &snnz,CMatrixDouble &w,int &info,
|
|
int &iterationscount,int &nmv)
|
|
{
|
|
//--- create variables
|
|
CLinLSQRState State;
|
|
CLinLSQRReport lsqrrep;
|
|
CSparseMatrix spg;
|
|
CSparseMatrix sps;
|
|
CRowInt nearcenterscnt;
|
|
CRowInt nearpointscnt;
|
|
CRowInt skipnearpointscnt;
|
|
CRowInt farpointscnt;
|
|
int maxnearcenterscnt=0;
|
|
int maxnearpointscnt=0;
|
|
int maxfarpointscnt=0;
|
|
int sumnearcenterscnt=0;
|
|
int sumnearpointscnt=0;
|
|
int sumfarpointscnt=0;
|
|
double maxrad=0;
|
|
CRowInt pointstags;
|
|
CRowInt centerstags;
|
|
CMatrixDouble nearpoints;
|
|
CMatrixDouble nearcenters;
|
|
CMatrixDouble farpoints;
|
|
int tmpi=0;
|
|
int pointscnt=0;
|
|
int centerscnt=0;
|
|
CRowDouble xcx;
|
|
CRowDouble tmpy;
|
|
CRowDouble tc;
|
|
CRowDouble g;
|
|
CRowDouble c;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int sind=0;
|
|
CMatrixDouble a;
|
|
double vv=0;
|
|
double vx=0;
|
|
double vy=0;
|
|
double vz=0;
|
|
double vr=0;
|
|
double gnorm2=0;
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
CRowDouble tmp2;
|
|
double fx=0;
|
|
CMatrixDouble xx;
|
|
CMatrixDouble cx;
|
|
double mrad=0;
|
|
int i_=0;
|
|
|
|
gnnz=0;
|
|
snnz=0;
|
|
w.Resize(0,0);
|
|
info=0;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
//--- Handle special cases: NC=0
|
|
if(nc==0)
|
|
{
|
|
info=1;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
return;
|
|
}
|
|
//--- Prepare for general case, NC>0
|
|
xcx=vector<double>::Zeros(m_mxnx);
|
|
pointstags.Resize(n);
|
|
centerstags.Resize(nc);
|
|
info=-1;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
//--- This block prepares quantities used to compute approximate cardinal basis functions (ACBFs):
|
|
//--- * NearCentersCnt[] - array[NC], whose elements store number of near centers used to build ACBF
|
|
//--- * NearPointsCnt[] - array[NC], number of near points used to build ACBF
|
|
//--- * FarPointsCnt[] - array[NC], number of far points (ones where ACBF is nonzero)
|
|
//--- * MaxNearCentersCnt - max(NearCentersCnt)
|
|
//--- * MaxNearPointsCnt - max(NearPointsCnt)
|
|
//--- * SumNearCentersCnt - sum(NearCentersCnt)
|
|
//--- * SumNearPointsCnt - sum(NearPointsCnt)
|
|
//--- * SumFarPointsCnt - sum(FarPointsCnt)
|
|
nearcenterscnt.Resize(nc);
|
|
nearpointscnt.Resize(nc);
|
|
skipnearpointscnt.Resize(nc);
|
|
farpointscnt.Resize(nc);
|
|
maxnearcenterscnt=0;
|
|
maxnearpointscnt=0;
|
|
maxfarpointscnt=0;
|
|
sumnearcenterscnt=0;
|
|
sumnearpointscnt=0;
|
|
sumfarpointscnt=0;
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
xcx=xc[i]+0;
|
|
//--- Determine number of near centers and maximum radius of near centers
|
|
nearcenterscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(centerstree,xcx,r[i]*m_rbfnearradius,true));
|
|
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
|
|
maxrad=0;
|
|
for(j=0; j<nearcenterscnt[i]; j++)
|
|
maxrad=MathMax(maxrad,MathAbs(r[centerstags[j]]));
|
|
//--- Determine number of near points (ones which used to build ACBF)
|
|
//--- and skipped points (the most near points which are NOT used to build ACBF
|
|
//--- and are NOT included in the near points count
|
|
skipnearpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,0.1*r[i],true));
|
|
nearpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,(r[i]+maxrad)*m_rbfnearradius,true)-skipnearpointscnt[i]);
|
|
//--- check
|
|
if(!CAp::Assert(nearpointscnt[i]>=0,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- Determine number of far points
|
|
farpointscnt.Set(i,CNearestNeighbor::KDTreeQueryRNN(pointstree,xcx,MathMax(r[i]*m_rbfnearradius+maxrad*m_rbffarradius,r[i]*m_rbffarradius),true));
|
|
//--- calculate sum and max, make some basic checks
|
|
//--- check
|
|
if(!CAp::Assert(nearcenterscnt[i]>0,__FUNCTION__+": internal error"))
|
|
return;
|
|
maxnearcenterscnt=MathMax(maxnearcenterscnt,nearcenterscnt[i]);
|
|
maxnearpointscnt=MathMax(maxnearpointscnt,nearpointscnt[i]);
|
|
maxfarpointscnt=MathMax(maxfarpointscnt,farpointscnt[i]);
|
|
sumnearcenterscnt=sumnearcenterscnt+nearcenterscnt[i];
|
|
sumnearpointscnt=sumnearpointscnt+nearpointscnt[i];
|
|
sumfarpointscnt=sumfarpointscnt+farpointscnt[i];
|
|
}
|
|
snnz=sumnearcenterscnt;
|
|
gnnz=sumfarpointscnt;
|
|
//--- check
|
|
if(!CAp::Assert(maxnearcenterscnt>0,__FUNCTION__+": internal error"))
|
|
return;
|
|
//--- Allocate temporaries.
|
|
//--- NOTE: we want to avoid allocation of zero-size arrays, so we
|
|
//--- use max(desired_size,1) instead of desired_size when performing
|
|
//--- memory allocation.
|
|
a=matrix<double>::Zeros(maxnearpointscnt+maxnearcenterscnt,maxnearcenterscnt);
|
|
tmpy=vector<double>::Zeros(maxnearpointscnt+maxnearcenterscnt);
|
|
g=vector<double>::Zeros(maxnearcenterscnt);
|
|
c=vector<double>::Zeros(maxnearcenterscnt);
|
|
nearcenters=matrix<double>::Zeros(maxnearcenterscnt,m_mxnx);
|
|
nearpoints=matrix<double>::Zeros(MathMax(maxnearpointscnt,1),m_mxnx);
|
|
farpoints=matrix<double>::Zeros(MathMax(maxfarpointscnt,1),m_mxnx);
|
|
//--- fill matrix SpG
|
|
CSparse::SparseCreate(n,nc,gnnz,spg);
|
|
CSparse::SparseCreate(nc,nc,snnz,sps);
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
centerscnt=nearcenterscnt[i];
|
|
//--- main center
|
|
xcx=xc[i]+0;
|
|
//--- center's tree
|
|
tmpi=CNearestNeighbor::KDTreeQueryKNN(centerstree,xcx,centerscnt,true);
|
|
//--- check
|
|
if(!CAp::Assert(tmpi==centerscnt,__FUNCTION__+": internal error"))
|
|
return;
|
|
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
|
|
//--- point's tree
|
|
mrad=0;
|
|
for(j=0; j<centerscnt; j++)
|
|
mrad=MathMax(mrad,r[centerstags[j]]);
|
|
//--- we need to be sure that 'CTree' contains
|
|
//--- at least one side center
|
|
CSparse::SparseSet(sps,i,i,1);
|
|
c.Fill(0);
|
|
c.Set(0,1.0);
|
|
//---
|
|
if(centerscnt>1 && nearpointscnt[i]>0)
|
|
{
|
|
//--- first KDTree request for points
|
|
pointscnt=nearpointscnt[i];
|
|
tmpi=CNearestNeighbor::KDTreeQueryKNN(pointstree,xcx,skipnearpointscnt[i]+nearpointscnt[i],true);
|
|
//--- check
|
|
if(!CAp::Assert(tmpi==skipnearpointscnt[i]+nearpointscnt[i],__FUNCTION__+": internal error"))
|
|
return;
|
|
CNearestNeighbor::KDTreeQueryResultsX(pointstree,xx);
|
|
sind=skipnearpointscnt[i];
|
|
for(j=0; j<pointscnt; j++)
|
|
{
|
|
vx=xx.Get(sind+j,0);
|
|
vy=xx.Get(sind+j,1);
|
|
vz=xx.Get(sind+j,2);
|
|
for(k=0; k<centerscnt; k++)
|
|
{
|
|
vv=vx-cx.Get(k,0);
|
|
vr=vv*vv;
|
|
vv=vy-cx.Get(k,1);
|
|
vr+=vv*vv;
|
|
vv=vz-cx.Get(k,2);
|
|
vr+=vv*vv;
|
|
vv=r[centerstags[k]];
|
|
a.Set(j,k,MathExp(-(vr/(vv*vv))));
|
|
}
|
|
}
|
|
for(j=0; j<centerscnt; j++)
|
|
g.Set(j,MathExp(-(CMath::Sqr(xcx[0]-cx.Get(j,0))+CMath::Sqr(xcx[1]-cx.Get(j,1))+CMath::Sqr(xcx[2]-cx.Get(j,2)))/CMath::Sqr(r[centerstags[j]])));
|
|
//--- calculate the problem
|
|
gnorm2=CAblasF::RDotV2(centerscnt,g);
|
|
for(j=0; j<pointscnt; j++)
|
|
{
|
|
vv=CAblasF::RDotVR(centerscnt,g,a,j);
|
|
vv=vv/gnorm2;
|
|
tmpy.Set(j,-vv);
|
|
for(i_=0; i_<centerscnt; i_++)
|
|
a.Add(j,i_,-vv*g[i_]);
|
|
}
|
|
for(j=pointscnt; j<pointscnt+centerscnt; j++)
|
|
{
|
|
for(k=0; k<centerscnt; k++)
|
|
a.Set(j,k,0.0);
|
|
a.Set(j,j-pointscnt,1.0E-6);
|
|
tmpy.Set(j,0.0);
|
|
}
|
|
CFbls::FblsSolveLS(a,tmpy,pointscnt+centerscnt,centerscnt,tmp0,tmp1,tmp2);
|
|
for(i_=0; i_<centerscnt; i_++)
|
|
c.Set(i_,tmpy[i_]);
|
|
vv=CAblasF::RDotV(centerscnt,g,c);
|
|
vv=vv/gnorm2;
|
|
for(i_=0; i_<centerscnt; i_++)
|
|
c.Add(i_,-vv*g[i_]);
|
|
vv=1/gnorm2;
|
|
for(i_=0; i_<centerscnt; i_++)
|
|
c.Add(i_,vv*g[i_]);
|
|
for(j=0; j<centerscnt; j++)
|
|
CSparse::SparseSet(sps,i,centerstags[j],c[j]);
|
|
}
|
|
//--- second KDTree request for points
|
|
pointscnt=farpointscnt[i];
|
|
tmpi=CNearestNeighbor::KDTreeQueryKNN(pointstree,xcx,pointscnt,true);
|
|
//--- check
|
|
if(!CAp::Assert(tmpi==pointscnt,__FUNCTION__+": internal error"))
|
|
return;
|
|
CNearestNeighbor::KDTreeQueryResultsX(pointstree,xx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(pointstree,pointstags);
|
|
//fill SpG matrix
|
|
for(j=0; j<pointscnt; j++)
|
|
{
|
|
fx=0;
|
|
vx=xx.Get(j,0);
|
|
vy=xx.Get(j,1);
|
|
vz=xx.Get(j,2);
|
|
for(k=0; k<centerscnt; k++)
|
|
{
|
|
vv=vx-cx.Get(k,0);
|
|
vr=vv*vv;
|
|
vv=vy-cx.Get(k,1);
|
|
vr+=vv*vv;
|
|
vv=vz-cx.Get(k,2);
|
|
vr+=vv*vv;
|
|
vv=r[centerstags[k]];
|
|
vv=vv*vv;
|
|
fx+=c[k]*MathExp(-(vr/vv));
|
|
}
|
|
CSparse::SparseSet(spg,pointstags[j],i,fx);
|
|
}
|
|
}
|
|
CSparse::SparseConvertToCRS(spg);
|
|
CSparse::SparseConvertToCRS(sps);
|
|
//--- solve by LSQR method
|
|
tmpy.Resize(n);
|
|
tc.Resize(nc);
|
|
w=matrix<double>::Zeros(nc,ny);
|
|
CLinLSQR::LinLSQRCreate(n,nc,State);
|
|
CLinLSQR::LinLSQRSetCond(State,epsort,epserr,maxits);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
tmpy=y.Col(i)+0;
|
|
CLinLSQR::LinLSQRSolveSparse(State,spg,tmpy);
|
|
CLinLSQR::LinLSQRResults(State,c,lsqrrep);
|
|
if(lsqrrep.m_terminationtype<=0)
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
CSparse::SparseMTV(sps,c,tc);
|
|
for(j=0; j<nc; j++)
|
|
w.Set(j,i,tc[j]);
|
|
iterationscount+=lsqrrep.m_iterationscount;
|
|
nmv+=lsqrrep.m_nmv;
|
|
}
|
|
info=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV1::BuildRBFMLayersModellSQR(CMatrixDouble &x,CMatrixDouble &y,
|
|
CMatrixDouble &xc,double rval,
|
|
CRowDouble &r,int n,int &nc,
|
|
int ny,int nlayers,
|
|
CKDTree ¢erstree,double epsort,
|
|
double epserr,int maxits,
|
|
double lambdav,int &annz,
|
|
CMatrixDouble &w,int &info,
|
|
int &iterationscount,int &nmv)
|
|
{
|
|
//--- create variables
|
|
CLinLSQRState State;
|
|
CLinLSQRReport lsqrrep;
|
|
CSparseMatrix spa;
|
|
double anorm=0;
|
|
CRowDouble omega;
|
|
CRowDouble xx;
|
|
CRowDouble tmpy;
|
|
CMatrixDouble cx;
|
|
double yval=0;
|
|
int nec=0;
|
|
CRowInt centerstags;
|
|
int layer=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double rmaxbefore=0;
|
|
double rmaxafter=0;
|
|
|
|
xc.Resize(0,0);
|
|
r.Resize(0);
|
|
nc=0;
|
|
annz=0;
|
|
w.Resize(0,0);
|
|
info=0;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
//--- check
|
|
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": invalid argument(NLayers<0)"))
|
|
return;
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": invalid argument(N<0)"))
|
|
return;
|
|
if(!CAp::Assert(m_mxnx>0 && m_mxnx<=3,__FUNCTION__+": internal error(invalid global const MxNX: either MxNX<=0 or MxNX>3)"))
|
|
return;
|
|
annz=0;
|
|
if(n==0 || nlayers==0)
|
|
{
|
|
info=1;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
return;
|
|
}
|
|
|
|
nc=n*nlayers;
|
|
xx.Resize(m_mxnx);
|
|
centerstags.Resize(n);
|
|
xc.Resize(nc,m_mxnx);
|
|
r.Resize(nc);
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
for(j=0; j<m_mxnx; j++)
|
|
xc.Set(i,j,x.Get(i%n,j));
|
|
}
|
|
for(i=0; i<nc; i++)
|
|
r.Set(i,rval/MathPow(2,i/n));
|
|
for(i=0; i<n; i++)
|
|
centerstags.Set(i,i);
|
|
CNearestNeighbor::KDTreeBuildTagged(xc,centerstags,n,m_mxnx,0,2,centerstree);
|
|
omega.Resize(n);
|
|
tmpy.Resize(n);
|
|
w.Resize(nc,ny);
|
|
info=-1;
|
|
iterationscount=0;
|
|
nmv=0;
|
|
CLinLSQR::LinLSQRCreate(n,n,State);
|
|
CLinLSQR::LinLSQRSetCond(State,epsort,epserr,maxits);
|
|
CLinLSQR::LinLSQRSetLambdaI(State,1.0E-6);
|
|
//--- calculate number of non-zero elements for sparse matrix
|
|
for(i=0; i<n; i++)
|
|
{
|
|
xx=x[i]+0;
|
|
annz+=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[0]*m_rbfmlradius,true);
|
|
}
|
|
for(layer=0; layer<nlayers; layer++)
|
|
{
|
|
//--- Fill sparse matrix, calculate norm(A)
|
|
anorm=0.0;
|
|
CSparse::SparseCreate(n,n,annz,spa);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
xx=x[i]+0;
|
|
nec=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[layer*n]*m_rbfmlradius,true);
|
|
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
|
|
for(j=0; j<nec; j++)
|
|
{
|
|
v=MathExp(-((CMath::Sqr(xx[0]-cx.Get(j,0))+CMath::Sqr(xx[1]-cx.Get(j,1))+CMath::Sqr(xx[2]-cx.Get(j,2)))/CMath::Sqr(r[layer*n+centerstags[j]])));
|
|
CSparse::SparseSet(spa,i,centerstags[j],v);
|
|
anorm+=CMath::Sqr(v);
|
|
}
|
|
}
|
|
anorm=MathSqrt(anorm);
|
|
CSparse::SparseConvertToCRS(spa);
|
|
//--- Calculate maximum residual before adding new layer.
|
|
//--- This value is not used by algorithm, the only purpose is to make debugging easier.
|
|
rmaxbefore=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
for(i=0; i<ny; i++)
|
|
rmaxbefore=MathMax(rmaxbefore,MathAbs(y.Get(j,i)));
|
|
}
|
|
//--- Process NY dimensions of the target function
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
tmpy=y.Col(i)+0;
|
|
//--- calculate Omega for current layer
|
|
CLinLSQR::LinLSQRSetLambdaI(State,lambdav*anorm/n);
|
|
CLinLSQR::LinLSQRSolveSparse(State,spa,tmpy);
|
|
CLinLSQR::LinLSQRResults(State,omega,lsqrrep);
|
|
if(lsqrrep.m_terminationtype<=0)
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
//--- calculate error for current layer
|
|
for(j=0; j<n; j++)
|
|
{
|
|
yval=0;
|
|
for(k=0; k<m_mxnx; k++)
|
|
xx.Set(k,x.Get(j,k));
|
|
nec=CNearestNeighbor::KDTreeQueryRNN(centerstree,xx,r[layer*n]*m_rbffarradius,true);
|
|
CNearestNeighbor::KDTreeQueryResultsX(centerstree,cx);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(centerstree,centerstags);
|
|
for(k=0; k<nec; k++)
|
|
yval+=omega[centerstags[k]]*MathExp(-((CMath::Sqr(xx[0]-cx.Get(k,0))+CMath::Sqr(xx[1]-cx.Get(k,1))+CMath::Sqr(xx[2]-cx.Get(k,2)))/CMath::Sqr(r[layer*n+centerstags[k]])));
|
|
y.Add(j,i,-yval);
|
|
}
|
|
//--- write Omega in out parameter W
|
|
for(j=0; j<n; j++)
|
|
w.Set(layer*n+j,i,omega[j]);
|
|
iterationscount+=lsqrrep.m_iterationscount;
|
|
nmv+=lsqrrep.m_nmv;
|
|
}
|
|
//--- Calculate maximum residual before adding new layer.
|
|
//--- This value is not used by algorithm, the only purpose is to make debugging easier.
|
|
rmaxafter=0.0;
|
|
for(j=0; j<n; j++)
|
|
{
|
|
for(i=0; i<ny; i++)
|
|
rmaxafter=MathMax(rmaxafter,MathAbs(y.Get(j,i)));
|
|
}
|
|
}
|
|
info=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform nearest neighbor requests |
|
|
//| in the multithreaded mode (multiple threads working with same |
|
|
//| KD-tree object). |
|
|
//| This object should be created with KDTreeCreateBuffer(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV2CalcBuffer
|
|
{
|
|
double m_curdist2;
|
|
CRowDouble m_curboxmax;
|
|
CRowDouble m_curboxmin;
|
|
CRowDouble m_x123;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y123;
|
|
//--- constructor / destructor
|
|
CRBFV2CalcBuffer(void) { m_curdist2=0; }
|
|
~CRBFV2CalcBuffer(void) {}
|
|
//---
|
|
void Copy(const CRBFV2CalcBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV2CalcBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2CalcBuffer::Copy(const CRBFV2CalcBuffer &obj)
|
|
{
|
|
m_curdist2=obj.m_curdist2;
|
|
m_curboxmax=obj.m_curboxmax;
|
|
m_curboxmin=obj.m_curboxmin;
|
|
m_x123=obj.m_x123;
|
|
m_x=obj.m_x;
|
|
m_y123=obj.m_y123;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF model. |
|
|
//| Never try to work with fields of this object directly - always |
|
|
//| use ALGLIB functions to use this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV2Model
|
|
{
|
|
int m_basisfunction;
|
|
int m_bf;
|
|
int m_maxits;
|
|
int m_nh;
|
|
int m_nx;
|
|
int m_ny;
|
|
double m_lambdareg;
|
|
double m_supportr;
|
|
CRowInt m_kdnodes;
|
|
CRowInt m_kdroots;
|
|
CRowDouble m_cw;
|
|
CRowDouble m_kdboxmax;
|
|
CRowDouble m_kdboxmin;
|
|
CRowDouble m_kdsplits;
|
|
CRowDouble m_ri;
|
|
CRowDouble m_s;
|
|
CRBFV2CalcBuffer m_calcbuf;
|
|
CMatrixDouble m_v;
|
|
//--- constructor / destructor
|
|
CRBFV2Model(void);
|
|
~CRBFV2Model(void) {}
|
|
//---
|
|
void Copy(const CRBFV2Model&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV2Model&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBFV2Model::CRBFV2Model(void)
|
|
{
|
|
m_basisfunction=0;
|
|
m_bf=0;
|
|
m_maxits=0;
|
|
m_nh=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_lambdareg=0;
|
|
m_supportr=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2Model::Copy(const CRBFV2Model &obj)
|
|
{
|
|
m_basisfunction=obj.m_basisfunction;
|
|
m_bf=obj.m_bf;
|
|
m_maxits=obj.m_maxits;
|
|
m_nh=obj.m_nh;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_lambdareg=obj.m_lambdareg;
|
|
m_supportr=obj.m_supportr;
|
|
m_kdnodes=obj.m_kdnodes;
|
|
m_kdroots=obj.m_kdroots;
|
|
m_cw=obj.m_cw;
|
|
m_kdboxmax=obj.m_kdboxmax;
|
|
m_kdboxmin=obj.m_kdboxmin;
|
|
m_kdsplits=obj.m_kdsplits;
|
|
m_ri=obj.m_ri;
|
|
m_s=obj.m_s;
|
|
m_calcbuf=obj.m_calcbuf;
|
|
m_v=obj.m_v;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal buffer for GridCalc3 |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV2GridCalcBuffer
|
|
{
|
|
bool m_rf[];
|
|
CRowDouble m_cx;
|
|
CRowDouble m_rx;
|
|
CRowDouble m_ry;
|
|
CRowDouble m_tx;
|
|
CRowDouble m_ty;
|
|
CRBFV2CalcBuffer m_calcbuf;
|
|
//--- constructor / destructor
|
|
CRBFV2GridCalcBuffer(void) {}
|
|
~CRBFV2GridCalcBuffer(void) {}
|
|
//---
|
|
void Copy(const CRBFV2GridCalcBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV2GridCalcBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2GridCalcBuffer::Copy(const CRBFV2GridCalcBuffer &obj)
|
|
{
|
|
ArrayCopy(m_rf,obj.m_rf);
|
|
m_cx=obj.m_cx;
|
|
m_rx=obj.m_rx;
|
|
m_ry=obj.m_ry;
|
|
m_tx=obj.m_tx;
|
|
m_ty=obj.m_ty;
|
|
m_calcbuf=obj.m_calcbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF solution report: |
|
|
//| * TerminationType - termination type, positive values-success,|
|
|
//| non-positive - failure. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV2Report
|
|
{
|
|
int m_terminationtype;
|
|
double m_maxerror;
|
|
double m_rmserror;
|
|
//--- constructor / destructor
|
|
CRBFV2Report(void) { ZeroMemory(this); }
|
|
~CRBFV2Report(void) {}
|
|
//---
|
|
void Copy(const CRBFV2Report&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV2Report&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2Report::Copy(const CRBFV2Report &obj)
|
|
{
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_rmserror=obj.m_rmserror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CRBFV2
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_defaultlambdareg;
|
|
static const double m_defaultsupportr;
|
|
static const int m_defaultmaxits;
|
|
static const int m_defaultbf;
|
|
static const int m_maxnodesize;
|
|
static const double m_complexitymultiplier;
|
|
//---
|
|
static void RBFV2Create(int nx,int ny,CRBFV2Model&s);
|
|
static void RBFV2CreateCalcBuffer(CRBFV2Model&s,CRBFV2CalcBuffer&buf);
|
|
static void RBFV2BuildHierarchical(CMatrixDouble&x,CMatrixDouble&y,int n,CRowDouble&scalevec,int aterm,int nh,double rbase,double lambdans,CRBFV2Model&s,int &progress10000,bool&terminationrequest,CRBFV2Report&rep);
|
|
static void RBFV2Alloc(CSerializer&s,CRBFV2Model&model);
|
|
static void RBFV2Serialize(CSerializer&s,CRBFV2Model&model);
|
|
static void RBFV2Unserialize(CSerializer&s,CRBFV2Model&model);
|
|
static double RBFV2FarRadius(int bf);
|
|
static double RBFV2NearRadius(int bf);
|
|
static double RBFV2BasisFunc(int bf,double d2);
|
|
static void RBFV2BasisFuncDiff2(int bf,double d2,double&f,double&df,double&d2f);
|
|
static double RBFV2Calc1(CRBFV2Model&s,double x0);
|
|
static double RBFV2Calc2(CRBFV2Model&s,double x0,double x1);
|
|
static double RBFV2Calc3(CRBFV2Model&s,double x0,double x1,double x2);
|
|
static void RBFV2CalcBuf(CRBFV2Model&s,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV2TsCalcBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV2TsDiffBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFV2TSHessBuf(CRBFV2Model&s,CRBFV2CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFV2GridCalc2(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
|
|
static void RBFV2GridCalcVX(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y);
|
|
static void RBFV2PartialGridCalcRec(CRBFV2Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,CRowInt&blocks0,int block0a,int block0b,CRowInt&blocks1,int block1a,int block1b,CRowInt&blocks2,int block2a,int block2b,CRowInt&blocks3,int block3a,int block3b,bool &flagy[],bool sparsey,int levelidx,double avgfuncpernode,CRBFV2GridCalcBuffer &bufpool[],CRowDouble&y);
|
|
static void RBFV2Unpack(CRBFV2Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
|
|
|
|
private:
|
|
static bool RBFV2BuildLinearModel(CMatrixDouble&x,CMatrixDouble&y,int n,int nx,int ny,int modeltype,CMatrixDouble&v);
|
|
static void AllocateCalcBuffer(CRBFV2Model&s,CRBFV2CalcBuffer&buf);
|
|
static void ConvertAndAppendTree(CKDTree&curtree,int n,int nx,int ny,CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw);
|
|
static void ConvertTreeRec(CKDTree&curtree,int n,int nx,int ny,int nodeoffset,int nodesbase,int splitsbase,int cwbase,CRowInt&localnodes,int &localnodessize,CRowDouble&localsplits,int &localsplitssize,CRowDouble&localcw,int &localcwsize,CMatrixDouble&xybuf);
|
|
static void PartialCalcRec(CRBFV2Model&s,CRBFV2CalcBuffer&buf,int rootidx,double invr2,double queryr2,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y,int needdy);
|
|
static void PartialRowCalcRec(CRBFV2Model&s,CRBFV2CalcBuffer&buf,int rootidx,double invr2,double rquery2,double rfar2,CRowDouble&cx,CRowDouble&rx,bool &rf[],int rowsize,CRowDouble&ry);
|
|
static void PreparePartialQuery(CRowDouble&x,CRowDouble&kdboxmin,CRowDouble&kdboxmax,int nx,CRBFV2CalcBuffer&buf,int &cnt);
|
|
static void PartialQueryRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,int nx,int ny,CRBFV2CalcBuffer&buf,int rootidx,double queryr2,CRowDouble&x,CRowDouble&r2,CRowInt&offs,int &k);
|
|
static int PartialCountRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,int nx,int ny,CRBFV2CalcBuffer&buf,int rootidx,double queryr2,CRowDouble&x);
|
|
static void PartialUnpackRec(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&s,int nx,int ny,int rootidx,double r,CMatrixDouble&xwr,int &k);
|
|
static int DesignMatrixRowSize(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&ri,CRowInt&kdroots,CRowDouble&kdboxmin,CRowDouble&kdboxmax,int nx,int ny,int nh,int level,double rcoeff,CRowDouble&x0,CRBFV2CalcBuffer&calcbuf);
|
|
static void DesignMatrixGenerateRow(CRowInt&kdnodes,CRowDouble&kdsplits,CRowDouble&cw,CRowDouble&ri,CRowInt&kdroots,CRowDouble&kdboxmin,CRowDouble&kdboxmax,CRowInt&cwrange,int nx,int ny,int nh,int level,int bf,double rcoeff,int rowsperpoint,double penalty,CRowDouble&x0,CRBFV2CalcBuffer&calcbuf,CRowDouble&tmpr2,CRowInt&tmpoffs,CRowInt&rowidx,CRowDouble&rowval,int &rowsize);
|
|
static void ZeroFill(CRBFV2Model&s,int nx,int ny,int bf);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CRBFV2::m_defaultlambdareg=1.0E-6;
|
|
const double CRBFV2::m_defaultsupportr=0.10;
|
|
const int CRBFV2::m_defaultmaxits=400;
|
|
const int CRBFV2::m_defaultbf=1;
|
|
const int CRBFV2::m_maxnodesize=6;
|
|
const double CRBFV2::m_complexitymultiplier=100.0;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates RBF model for a scalar (NY=1) or vector |
|
|
//| (NY>1) function in a NX-dimensional space (NX=2 or NX=3). |
|
|
//| INPUT PARAMETERS: |
|
|
//| NX - dimension of the space, NX=2 or NX=3 |
|
|
//| NY - function dimension, NY>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - RBF model (initially equals to zero) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2Create(int nx,int ny,CRBFV2Model &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
|
|
return;
|
|
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
|
|
return;
|
|
//--- Serializable parameters
|
|
s.m_nx=nx;
|
|
s.m_ny=ny;
|
|
s.m_bf=0;
|
|
s.m_nh=0;
|
|
s.m_v=matrix<double>::Zeros(ny,nx+1);
|
|
//--- Non-serializable parameters
|
|
s.m_lambdareg=m_defaultlambdareg;
|
|
s.m_maxits=m_defaultmaxits;
|
|
s.m_supportr=m_defaultsupportr;
|
|
s.m_basisfunction=m_defaultbf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel RBF model evaluations (with one RBF model |
|
|
//| instance being used from multiple threads, as long as different |
|
|
//| threads use different instances of buffer). |
|
|
//| This buffer object can be used with RBFTSCalcBuf() function (here|
|
|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
|
|
//| function which reuses previously allocated output space). |
|
|
//| How to use it: |
|
|
//| * create RBF model structure with RBFCreate() |
|
|
//| * load data, tune parameters |
|
|
//| * call RBFBuildModel() |
|
|
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
|
|
//| model (you should call this function only AFTER call to |
|
|
//| RBFBuildModel(), see below for more information) |
|
|
//| * call RBFTSCalcBuf() from different threads, with each thread |
|
|
//| working with its own copy of buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with RBF model |
|
|
//| object which was used to initialize buffer. Any |
|
|
//| attempt to use buffer with different object is |
|
|
//| dangerous - you may get memory violation error because|
|
|
//| sizes of internal arrays do not fit to dimensions of |
|
|
//| RBF structure. |
|
|
//| IMPORTANT: you should call this function only for model which was|
|
|
//| built with RBFBuildModel() function, after successful |
|
|
//| invocation of RBFBuildModel(). Sizes of some internal |
|
|
//| structures are determined only after model is built, |
|
|
//| so buffer object created before model construction |
|
|
//| stage will be useless (and any attempt to use it will |
|
|
//| result in exception). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2CreateCalcBuffer(CRBFV2Model &s,
|
|
CRBFV2CalcBuffer &buf)
|
|
{
|
|
AllocateCalcBuffer(s,buf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds hierarchical RBF model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N,S.NX], X-values |
|
|
//| Y - array[N,S.NY], Y-values |
|
|
//| ScaleVec - array[S.NX], vector of per-dimension scales |
|
|
//| N - points count |
|
|
//| ATerm - linear term type, 1 for linear, 2 for constant, |
|
|
//| 3 for zero. |
|
|
//| NH - hierarchy height |
|
|
//| RBase - base RBF radius |
|
|
//| BF - basis function type: 0 for Gaussian, 1 for compact |
|
|
//| LambdaNS - non-smoothness penalty coefficient. Exactly zero |
|
|
//| value means that no penalty is applied, and even |
|
|
//| system matrix does not contain penalty-related rows|
|
|
//| Value of 1 means |
|
|
//| S - RBF model, initialized by RBFCreate() call. |
|
|
//| progress10000 - variable used for progress reports, it is |
|
|
//| regularly set to the current progress multiplied by|
|
|
//| 10000, in order to get value in [0,10000] range. |
|
|
//| The rationale for such scaling is that it allows us|
|
|
//| to use integer type to store progress, which has |
|
|
//| less potential for non-atomic corruption on |
|
|
//| unprotected reads from another threads. You can |
|
|
//| read this variable from some other thread to get |
|
|
//| estimate of the current progress. Initial value of |
|
|
//| this variable is ignored, it is written by this |
|
|
//| function, but not read. |
|
|
//| terminationrequest - variable used for termination requests; its |
|
|
//| initial value must be False, and you can set it to |
|
|
//| True from some other thread. This routine regularly|
|
|
//| checks this variable and will terminate model |
|
|
//| construction shortly upon discovering that |
|
|
//| termination was requested. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - updated model (for rep.m_terminationtype>0, unchanged|
|
|
//| otherwise) |
|
|
//| Rep - report: |
|
|
//| * Rep.TerminationType: |
|
|
//| * -5 - non-distinct basis function centers were |
|
|
//| detected, interpolation aborted |
|
|
//| * -4 - nonconvergence of the internal SVD solver|
|
|
//| * 1 - successful termination |
|
|
//| * 8 terminated by user via RBFRequestTermination|
|
|
//| Fields are used for debugging purposes: |
|
|
//| * Rep.IterationsCount - iterations count of the |
|
|
//| LSQR solver |
|
|
//| * Rep.NMV - number of matrix-vector products |
|
|
//| * Rep.ARows - rows count for the system matrix |
|
|
//| * Rep.ACols - columns count for the system matrix |
|
|
//| * Rep.ANNZ - number of significantly non-zero |
|
|
//| elements (elements above some |
|
|
//| algorithm-determined threshold) |
|
|
//| NOTE: failure to build model will leave current State of the |
|
|
//| structure unchanged. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2BuildHierarchical(CMatrixDouble &x,
|
|
CMatrixDouble &y,
|
|
int n,
|
|
CRowDouble &scalevec,
|
|
int aterm,
|
|
int nh,
|
|
double rbase,
|
|
double lambdans,
|
|
CRBFV2Model &s,
|
|
int &progress10000,
|
|
bool &terminationrequest,
|
|
CRBFV2Report &rep)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
int bf=0;
|
|
CMatrixDouble rhs;
|
|
CMatrixDouble residualy;
|
|
CMatrixDouble v;
|
|
int rowsperpoint=0;
|
|
CRowInt hidx;
|
|
CRowDouble xr;
|
|
CRowDouble ri;
|
|
CRowInt kdroots;
|
|
CRowInt kdnodes;
|
|
CRowDouble kdsplits;
|
|
CRowDouble kdboxmin;
|
|
CRowDouble kdboxmax;
|
|
CRowDouble cw;
|
|
CRowInt cwrange;
|
|
CMatrixDouble curxy;
|
|
int curn=0;
|
|
int nbasis=0;
|
|
CKDTree curtree;
|
|
CKDTree globaltree;
|
|
CRowDouble x0;
|
|
CRowDouble x1;
|
|
CRowInt tags;
|
|
CRowDouble dist;
|
|
CRowInt nncnt;
|
|
CRowInt rowsizes;
|
|
CRowDouble diagata;
|
|
CRowDouble prec;
|
|
CRowDouble tmpx;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k2=0;
|
|
int levelidx=0;
|
|
int offsi=0;
|
|
int offsj=0;
|
|
double val=0;
|
|
double criticalr=0;
|
|
int cnt=0;
|
|
double avgdiagata=0;
|
|
CRowDouble avgrowsize;
|
|
double sumrowsize=0;
|
|
double rprogress=0;
|
|
int maxits=0;
|
|
CLinLSQRState linstate;
|
|
CLinLSQRReport lsqrrep;
|
|
CSparseMatrix sparseacrs;
|
|
CRowDouble densew1;
|
|
CRowDouble denseb1;
|
|
CRBFV2CalcBuffer calcbuf;
|
|
CRowDouble vr2;
|
|
CRowInt voffs;
|
|
CRowInt rowindexes;
|
|
CRowDouble rowvals;
|
|
double penalty=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx>0,__FUNCTION__+": incorrect NX"))
|
|
return;
|
|
if(!CAp::Assert(s.m_ny>0,__FUNCTION__+": incorrect NY"))
|
|
return;
|
|
if(!CAp::Assert(lambdans>=0.0,__FUNCTION__+": incorrect LambdaNS"))
|
|
return;
|
|
for(j=0; j<s.m_nx; j++)
|
|
if(!CAp::Assert(scalevec[j]>0.0,__FUNCTION__+": incorrect ScaleVec"))
|
|
return;
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
bf=s.m_basisfunction;
|
|
//--- check
|
|
if(!CAp::Assert(bf==0 || bf==1,__FUNCTION__+": incorrect BF"))
|
|
return;
|
|
//--- Clean up communication and report fields
|
|
progress10000=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_rmserror=0;
|
|
//--- Quick exit when we have no points
|
|
if(n==0)
|
|
{
|
|
ZeroFill(s,nx,ny,bf);
|
|
rep.m_terminationtype=1;
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
//--- First model in a sequence - linear model.
|
|
//--- Residuals from linear regression are stored in the ResidualY variable
|
|
//--- (used later to build RBF models).
|
|
residualy=y;
|
|
residualy.Resize(n,ny);
|
|
if(!RBFV2BuildLinearModel(x,residualy,n,nx,ny,aterm,v))
|
|
{
|
|
ZeroFill(s,nx,ny,bf);
|
|
rep.m_terminationtype=-5;
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
//--- Handle special case: multilayer model with NLayers=0.
|
|
//--- Quick exit.
|
|
if(nh==0)
|
|
{
|
|
rep.m_terminationtype=1;
|
|
ZeroFill(s,nx,ny,bf);
|
|
s.m_v=v;
|
|
rep.m_maxerror=0;
|
|
rep.m_rmserror=0;
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(residualy.Get(i,j)));
|
|
rep.m_rmserror+=CMath::Sqr(residualy.Get(i,j));
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/(n*ny));
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
//--- Penalty coefficient is set to LambdaNS*RBase^2.
|
|
//--- We use such normalization because VALUES of radial basis
|
|
//--- functions have roughly unit magnitude, but their DERIVATIVES
|
|
//--- are (roughly) inversely proportional to the radius. Thus,
|
|
//--- without additional scaling, regularization coefficient
|
|
//--- looses invariancy w.m_r.t. scaling of variables.
|
|
if(lambdans==0.0)
|
|
rowsperpoint=1;
|
|
else
|
|
{
|
|
//--- NOTE: simplified penalty function is used, which does not provide rotation invariance
|
|
rowsperpoint=1+nx;
|
|
}
|
|
penalty=lambdans*CMath::Sqr(rbase);
|
|
//--- Prepare temporary structures
|
|
rhs=matrix<double>::Zeros(n*rowsperpoint,ny);
|
|
curxy.Resize(n,nx+ny);
|
|
x0.Resize(nx);
|
|
x1.Resize(nx);
|
|
tags.Resize(n);
|
|
dist.Resize(n);
|
|
vr2.Resize(n);
|
|
voffs.Resize(n);
|
|
nncnt.Resize(n);
|
|
rowsizes.Resize(n*rowsperpoint);
|
|
denseb1.Resize(n*rowsperpoint);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
curxy.Row(i,x[i]/scalevec.ToVector());
|
|
rhs.Row(i*rowsperpoint,residualy[i]+0);
|
|
tags.Set(i,i);
|
|
}
|
|
CNearestNeighbor::KDTreeBuildTagged(curxy,tags,n,nx,0,2,globaltree);
|
|
//--- Generate sequence of layer radii.
|
|
//--- Prepare assignment of different levels to points.
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
ri.Resize(nh);
|
|
for(levelidx=0; levelidx<nh; levelidx++)
|
|
ri.Set(levelidx,rbase*MathPow(2,-levelidx));
|
|
hidx.Resize(n);
|
|
hidx.Fill(nh);
|
|
xr=vector<double>::Full(n,CMath::m_maxrealnumber);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(xr[i]>ri[0],__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
}
|
|
for(levelidx=0; levelidx<nh; levelidx++)
|
|
{
|
|
//--- Scan dataset points, for each such point that distance to nearest
|
|
//--- "support" point is larger than SupportR*Ri[LevelIdx] we:
|
|
//--- * set distance of current point to 0 (it is support now) and update HIdx
|
|
//--- * perform R-NN request with radius SupportR*Ri[LevelIdx]
|
|
//--- * for each point in request update its distance
|
|
criticalr=s.m_supportr*ri[levelidx];
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(xr[i]>criticalr)
|
|
{
|
|
//--- Mark point as support
|
|
if(!CAp::Assert(hidx[i]==nh,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
hidx.Set(i,levelidx);
|
|
xr.Set(i,0);
|
|
//--- Update neighbors
|
|
x0=x[i]/scalevec.ToVector();
|
|
k=CNearestNeighbor::KDTreeQueryRNN(globaltree,x0,criticalr,true);
|
|
CNearestNeighbor::KDTreeQueryResultsTags(globaltree,tags);
|
|
CNearestNeighbor::KDTreeQueryResultsDistances(globaltree,dist);
|
|
for(j=0; j<k; j++)
|
|
xr.Set(tags[j],MathMin(xr[tags[j]],dist[j]));
|
|
}
|
|
}
|
|
}
|
|
//--- Build multitree (with zero weights) according to hierarchy.
|
|
//--- NOTE: this code assumes that during every iteration kdNodes,
|
|
//--- kdSplits and CW have size which EXACTLY fits their
|
|
//--- contents, and that these variables are resized at each
|
|
//--- iteration when we add new hierarchical model.
|
|
kdroots.Resize(nh+1);
|
|
kdnodes.Resize(0);
|
|
kdsplits.Resize(0);
|
|
kdboxmin.Resize(nx);
|
|
kdboxmax.Resize(nx);
|
|
cw.Resize(0);
|
|
cwrange.Resize(nh+1);
|
|
CNearestNeighbor::KDTreeExploreBox(globaltree,kdboxmin,kdboxmax);
|
|
cwrange.Set(0,0);
|
|
for(levelidx=0; levelidx<nh; levelidx++)
|
|
{
|
|
//--- Prepare radius and root offset
|
|
kdroots.Set(levelidx,CAp::Len(kdnodes));
|
|
//--- Generate LevelIdx-th tree and append to multi-tree
|
|
curn=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
if(hidx[i]<=levelidx)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
curxy.Set(curn,j,x.Get(i,j)/scalevec[j]);
|
|
for(j=0; j<ny; j++)
|
|
curxy.Set(curn,nx+j,0);
|
|
curn++;
|
|
}
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(curn>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CNearestNeighbor::KDTreeBuild(curxy,curn,nx,ny,2,curtree);
|
|
ConvertAndAppendTree(curtree,curn,nx,ny,kdnodes,kdsplits,cw);
|
|
//--- Fill entry of CWRange (we assume that length of CW exactly fits its actual size)
|
|
cwrange.Set(levelidx+1,CAp::Len(cw));
|
|
}
|
|
kdroots.Set(nh,CAp::Len(kdnodes));
|
|
//--- Prepare buffer and scaled dataset
|
|
AllocateCalcBuffer(s,calcbuf);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
curxy.Set(i,j,x.Get(i,j)/scalevec[j]);
|
|
}
|
|
//--- Calculate average row sizes for each layer; these values are used
|
|
//--- for smooth progress reporting (it adds some overhead, but in most
|
|
//--- cases - insignificant one).
|
|
CApServ::RVectorSetLengthAtLeast(avgrowsize,nh);
|
|
sumrowsize=0;
|
|
for(levelidx=0; levelidx<nh; levelidx++)
|
|
{
|
|
cnt=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
x0.Set(j,curxy.Get(i,j));
|
|
cnt+=DesignMatrixRowSize(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,nx,ny,nh,levelidx,RBFV2NearRadius(bf),x0,calcbuf);
|
|
}
|
|
avgrowsize.Set(levelidx,CApServ::Coalesce(cnt,1)/CApServ::Coalesce(n,1));
|
|
sumrowsize+=avgrowsize[levelidx];
|
|
}
|
|
//--- Build unconstrained model with LSQR solver, applied layer by layer
|
|
for(levelidx=0; levelidx<nh; levelidx++)
|
|
{
|
|
//--- Generate A - matrix of basis functions (near radius is used)
|
|
//--- NOTE: AvgDiagATA is average value of diagonal element of A^T*A.
|
|
//--- It is used to calculate value of Tikhonov regularization
|
|
//--- coefficient.
|
|
nbasis=(cwrange[levelidx+1]-cwrange[levelidx])/(nx+ny);
|
|
//--- check
|
|
if(!CAp::Assert(cwrange[levelidx+1]-cwrange[levelidx]==nbasis*(nx+ny)))
|
|
return;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
x0.Set(j,curxy.Get(i,j));
|
|
cnt=DesignMatrixRowSize(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,nx,ny,nh,levelidx,RBFV2NearRadius(bf),x0,calcbuf);
|
|
nncnt.Set(i,cnt);
|
|
for(j=0; j<rowsperpoint; j++)
|
|
rowsizes.Set(i*rowsperpoint+j,cnt);
|
|
}
|
|
CApServ::IVectorSetLengthAtLeast(rowindexes,nbasis);
|
|
CApServ::RVectorSetLengthAtLeast(rowvals,nbasis*rowsperpoint);
|
|
CApServ::RVectorSetLengthAtLeast(diagata,nbasis);
|
|
CSparse::SparseCreateCRSBuf(n*rowsperpoint,nbasis,rowsizes,sparseacrs);
|
|
avgdiagata=0.0;
|
|
diagata.Fill(0);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- Fill design matrix row, diagonal of A^T*A
|
|
for(j=0; j<nx; j++)
|
|
x0.Set(j,curxy.Get(i,j));
|
|
DesignMatrixGenerateRow(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,cwrange,nx,ny,nh,levelidx,bf,RBFV2NearRadius(bf),rowsperpoint,penalty,x0,calcbuf,vr2,voffs,rowindexes,rowvals,cnt);
|
|
//--- check
|
|
if(!CAp::Assert(cnt==nncnt[i],__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(k=0; k<rowsperpoint; k++)
|
|
{
|
|
for(j=0; j<cnt; j++)
|
|
{
|
|
val=rowvals[j*rowsperpoint+k];
|
|
CSparse::SparseSet(sparseacrs,i*rowsperpoint+k,rowindexes[j],val);
|
|
avgdiagata+=CMath::Sqr(val);
|
|
diagata.Add(rowindexes[j],CMath::Sqr(val));
|
|
}
|
|
}
|
|
//--- Handle possible termination requests
|
|
if(terminationrequest)
|
|
{
|
|
//--- Request for termination was submitted, terminate immediately
|
|
ZeroFill(s,nx,ny,bf);
|
|
rep.m_terminationtype=8;
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
}
|
|
avgdiagata=avgdiagata/nbasis;
|
|
CApServ::RVectorSetLengthAtLeast(prec,nbasis);
|
|
for(j=0; j<nbasis; j++)
|
|
prec.Set(j,1/CApServ::Coalesce(MathSqrt(diagata[j]),1));
|
|
//--- solve
|
|
maxits=CApServ::CoalesceI(s.m_maxits,m_defaultmaxits);
|
|
CApServ::RVectorSetLengthAtLeast(tmpx,nbasis);
|
|
CLinLSQR::LinLSQRCreate(n*rowsperpoint,nbasis,linstate);
|
|
CLinLSQR::LinLSQRSetCond(linstate,0.0,0.0,maxits);
|
|
CLinLSQR::LinLSQRSetLambdaI(linstate,MathSqrt(s.m_lambdareg*avgdiagata));
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
for(i=0; i<n*rowsperpoint; i++)
|
|
denseb1.Set(i,rhs.Get(i,j));
|
|
CLinLSQR::LinLSQRSetB(linstate,denseb1);
|
|
CLinLSQR::LinLSQRRestart(linstate);
|
|
CLinLSQR::LinLSQRSetXRep(linstate,true);
|
|
while(CLinLSQR::LinLSQRIteration(linstate))
|
|
{
|
|
if(terminationrequest)
|
|
{
|
|
//--- Request for termination was submitted, terminate immediately
|
|
ZeroFill(s,nx,ny,bf);
|
|
rep.m_terminationtype=8;
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
if(linstate.m_needmv)
|
|
{
|
|
for(i=0; i<nbasis; i++)
|
|
tmpx.Set(i,prec[i]*linstate.m_x[i]);
|
|
CSparse::SparseMV(sparseacrs,tmpx,linstate.m_mv);
|
|
continue;
|
|
}
|
|
if(linstate.m_needmtv)
|
|
{
|
|
CSparse::SparseMTV(sparseacrs,linstate.m_x,linstate.m_mtv);
|
|
for(i=0; i<nbasis; i++)
|
|
linstate.m_mtv.Mul(i,prec[i]);
|
|
continue;
|
|
}
|
|
if(linstate.m_xupdated)
|
|
{
|
|
rprogress=0;
|
|
for(i=0; i<levelidx; i++)
|
|
rprogress+=maxits*ny*avgrowsize[i];
|
|
rprogress+=(CLinLSQR::LinLSQRPeekIterationsCount(linstate)+j*maxits)*avgrowsize[levelidx];
|
|
rprogress/=(sumrowsize*maxits*ny);
|
|
rprogress*=10000;
|
|
rprogress=MathMax(rprogress,0);
|
|
rprogress=MathMin(rprogress,10000);
|
|
//--- check
|
|
if(!CAp::Assert(progress10000<=(int)MathRound(rprogress)+1,"HRBF: integrity check failed (progress indicator) even after +1 safeguard correction"))
|
|
return;
|
|
progress10000=(int)MathRound(rprogress);
|
|
continue;
|
|
}
|
|
CAp::Assert(false,"HRBF: unexpected request from LSQR solver");
|
|
return;
|
|
}
|
|
CLinLSQR::LinLSQRResults(linstate,densew1,lsqrrep);
|
|
//--- check
|
|
if(!CAp::Assert(lsqrrep.m_terminationtype>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(i=0; i<nbasis; i++)
|
|
densew1.Mul(i,prec[i]);
|
|
for(i=0; i<nbasis; i++)
|
|
{
|
|
offsi=cwrange[levelidx]+(nx+ny)*i;
|
|
cw.Set(offsi+nx+j,densew1[i]);
|
|
}
|
|
}
|
|
//--- Update residuals (far radius is used)
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
x0.Set(j,curxy.Get(i,j));
|
|
DesignMatrixGenerateRow(kdnodes,kdsplits,cw,ri,kdroots,kdboxmin,kdboxmax,cwrange,nx,ny,nh,levelidx,bf,RBFV2FarRadius(bf),rowsperpoint,penalty,x0,calcbuf,vr2,voffs,rowindexes,rowvals,cnt);
|
|
for(j=0; j<cnt; j++)
|
|
{
|
|
offsj=cwrange[levelidx]+(nx+ny)*rowindexes[j]+nx;
|
|
for(k=0; k<rowsperpoint; k++)
|
|
{
|
|
val=rowvals[j*rowsperpoint+k];
|
|
for(k2=0; k2<ny; k2++)
|
|
rhs.Add(i*rowsperpoint+k,k2,-val*cw[offsj+k2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Model is built.
|
|
//--- Copy local variables by swapping, global ones (ScaleVec) are copied
|
|
//--- explicitly.
|
|
s.m_bf=bf;
|
|
s.m_nh=nh;
|
|
CAp::Swap(s.m_ri,ri);
|
|
CAp::Swap(s.m_kdroots,kdroots);
|
|
CAp::Swap(s.m_kdnodes,kdnodes);
|
|
CAp::Swap(s.m_kdsplits,kdsplits);
|
|
CAp::Swap(s.m_kdboxmin,kdboxmin);
|
|
CAp::Swap(s.m_kdboxmax,kdboxmax);
|
|
CAp::Swap(s.m_cw,cw);
|
|
CAp::Swap(s.m_v,v);
|
|
s.m_s=scalevec;
|
|
s.m_s.Resize(nx);
|
|
rep.m_terminationtype=1;
|
|
//--- Calculate maximum and RMS errors
|
|
rep.m_maxerror=0;
|
|
rep.m_rmserror=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(rhs.Get(i*rowsperpoint,j)));
|
|
rep.m_rmserror+=CMath::Sqr(rhs.Get(i*rowsperpoint,j));
|
|
}
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/(n*ny));
|
|
//--- Update progress reports
|
|
progress10000=10000;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2Alloc(CSerializer &s,CRBFV2Model &model)
|
|
{
|
|
//--- Data
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,model.m_ri,-1);
|
|
CApServ::AllocRealArray(s,model.m_s,-1);
|
|
CApServ::AllocIntegerArray(s,model.m_kdroots,-1);
|
|
CApServ::AllocIntegerArray(s,model.m_kdnodes,-1);
|
|
CApServ::AllocRealArray(s,model.m_kdsplits,-1);
|
|
CApServ::AllocRealArray(s,model.m_kdboxmin,-1);
|
|
CApServ::AllocRealArray(s,model.m_kdboxmax,-1);
|
|
CApServ::AllocRealArray(s,model.m_cw,-1);
|
|
CApServ::AllocRealMatrix(s,model.m_v,-1,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2Serialize(CSerializer &s,CRBFV2Model &model)
|
|
{
|
|
//--- Data
|
|
s.Serialize_Int(model.m_nx);
|
|
s.Serialize_Int(model.m_ny);
|
|
s.Serialize_Int(model.m_nh);
|
|
s.Serialize_Int(model.m_bf);
|
|
CApServ::SerializeRealArray(s,model.m_ri,-1);
|
|
CApServ::SerializeRealArray(s,model.m_s,-1);
|
|
CApServ::SerializeIntegerArray(s,model.m_kdroots,-1);
|
|
CApServ::SerializeIntegerArray(s,model.m_kdnodes,-1);
|
|
CApServ::SerializeRealArray(s,model.m_kdsplits,-1);
|
|
CApServ::SerializeRealArray(s,model.m_kdboxmin,-1);
|
|
CApServ::SerializeRealArray(s,model.m_kdboxmax,-1);
|
|
CApServ::SerializeRealArray(s,model.m_cw,-1);
|
|
CApServ::SerializeRealMatrix(s,model.m_v,-1,-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2Unserialize(CSerializer &s,CRBFV2Model &model)
|
|
{
|
|
//--- Unserialize primary model parameters, initialize model.
|
|
//--- It is necessary to call RBFCreate() because some internal fields
|
|
//--- which are NOT unserialized will need initialization.
|
|
int nx=s.Unserialize_Int();
|
|
int ny=s.Unserialize_Int();
|
|
|
|
RBFV2Create(nx,ny,model);
|
|
model.m_nh=s.Unserialize_Int();
|
|
model.m_bf=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,model.m_ri);
|
|
CApServ::UnserializeRealArray(s,model.m_s);
|
|
CApServ::UnserializeIntegerArray(s,model.m_kdroots);
|
|
CApServ::UnserializeIntegerArray(s,model.m_kdnodes);
|
|
CApServ::UnserializeRealArray(s,model.m_kdsplits);
|
|
CApServ::UnserializeRealArray(s,model.m_kdboxmin);
|
|
CApServ::UnserializeRealArray(s,model.m_kdboxmax);
|
|
CApServ::UnserializeRealArray(s,model.m_cw);
|
|
CApServ::UnserializeRealMatrix(s,model.m_v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns far radius for basis function type |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2FarRadius(int bf)
|
|
{
|
|
double result=0;
|
|
|
|
switch(bf)
|
|
{
|
|
case 0:
|
|
result=5.0;
|
|
break;
|
|
case 1:
|
|
result=3;
|
|
break;
|
|
default:
|
|
result=1;
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns near radius for basis function type |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2NearRadius(int bf)
|
|
{
|
|
double result=0;
|
|
|
|
switch(bf)
|
|
{
|
|
case 0:
|
|
result=3.0;
|
|
break;
|
|
case 1:
|
|
result=3;
|
|
break;
|
|
default:
|
|
result=1;
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns basis function value. |
|
|
//| Assumes that D2>=0 |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2BasisFunc(int bf,double d2)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
|
|
switch(bf)
|
|
{
|
|
case 0:
|
|
result=MathExp(-d2);
|
|
break;
|
|
case 1:
|
|
//--- if D2<3:
|
|
//--- Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
|
|
//--- else:
|
|
//--- 0
|
|
v=1-d2/9;
|
|
if(v<=0.0)
|
|
{
|
|
result=0;
|
|
break;
|
|
}
|
|
result=2.718281828459045*MathExp(-d2)*MathExp(-(1/v));
|
|
break;
|
|
default:
|
|
CAp::Assert(false,"RBFV2BasisFunc: unknown BF type");
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns basis function value, first and second derivatives |
|
|
//| Assumes that D2>=0 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2BasisFuncDiff2(int bf,double d2,double &f,
|
|
double &df,double &d2f)
|
|
{
|
|
double v=0;
|
|
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
|
|
switch(bf)
|
|
{
|
|
case 0:
|
|
f=MathExp(-d2);
|
|
df=-f;
|
|
d2f=f;
|
|
break;
|
|
case 1:
|
|
//--- if D2<3:
|
|
//--- F = Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
|
|
//--- dF = -F * [pow(D2/9-1,-2)/9 + 1]
|
|
//--- d2F = -dF * [pow(D2/9-1,-2)/9 + 1] - F*(2/81)*pow(D2/9-1,-3)
|
|
//--- else:
|
|
//--- 0
|
|
v=1-d2/9;
|
|
if(v<=0.0)
|
|
{
|
|
f=0;
|
|
df=0;
|
|
d2f=0;
|
|
break;
|
|
}
|
|
f=MathExp(1)*MathExp(-d2)*MathExp(-(1/v));
|
|
df=-(f*(1/(9*v*v)+1));
|
|
d2f=-(df*(1/(9*v*v)+1))-f*((double)2/(double)81)/(v*v*v);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,"RBFV2BasisFuncDiff2: unknown BF type");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY=1 (scalar function) |
|
|
//| and NX=1 (1-dimensional space). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>1 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - X-coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2Calc1(CRBFV2Model &s,double x0)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return (0);
|
|
if(s.m_ny!=1 || s.m_nx!=1)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0-s.m_v.Get(0,1);
|
|
if(s.m_nh==0)
|
|
return(result);
|
|
|
|
AllocateCalcBuffer(s,s.m_calcbuf);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY=1 (scalar function) |
|
|
//| and NX=2 (2-dimensional space). If you have 3-dimensional space, |
|
|
//| use RBFCalc3(). If you have general situation (NX-dimensional |
|
|
//| space, NY-dimensional function) you should use general, less |
|
|
//| efficient implementation RBFCalc(). |
|
|
//| If you want to calculate function values many times, consider |
|
|
//| using RBFGridCalc2(), which is far more efficient than many |
|
|
//| subsequent calls to RBFCalc2(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2Calc2(CRBFV2Model &s,double x0,double x1)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2);
|
|
if(s.m_nh==0)
|
|
return(result);
|
|
|
|
AllocateCalcBuffer(s,s.m_calcbuf);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
s.m_calcbuf.m_x123.Set(1,x1);
|
|
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY=1 (scalar function) |
|
|
//| and NX=3 (3-dimensional space). If you have 2-dimensional space, |
|
|
//| use RBFCalc2(). If you have general situation (NX-dimensional |
|
|
//| space, NY-dimensional function) you should use general, less |
|
|
//| efficient implementation RBFCalc(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| X2 - third coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV2::RBFV2Calc3(CRBFV2Model &s,double x0,double x1,
|
|
double x2)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=3)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,3);
|
|
if(s.m_nh==0)
|
|
return(result);
|
|
|
|
AllocateCalcBuffer(s,s.m_calcbuf);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
s.m_calcbuf.m_x123.Set(1,x1);
|
|
s.m_calcbuf.m_x123.Set(2,x2);
|
|
RBFV2TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point. |
|
|
//| Same as RBFCalc(), but does not reallocate Y when in is large |
|
|
//| enough to store function values. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2CalcBuf(CRBFV2Model &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
RBFV2TsCalcBuf(s,s.m_calcbuf,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point, using external buffer object (internal temporaries of RBF |
|
|
//| model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2TsCalcBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
double rcur=0;
|
|
double rquery2=0;
|
|
double invrc2=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
//--- Handle linear term
|
|
if(CAp::Len(y)<ny)
|
|
y.Resize(ny);
|
|
for(int i=0; i<ny; i++)
|
|
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
|
|
if(s.m_nh==0)
|
|
return;
|
|
//--- Handle nonlinear term
|
|
AllocateCalcBuffer(s,buf);
|
|
for(int j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
for(int levelidx=0; levelidx<s.m_nh; levelidx++)
|
|
{
|
|
//--- Prepare fields of Buf required by PartialCalcRec()
|
|
buf.m_curdist2=0;
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
|
|
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
|
|
if(buf.m_x[j]<buf.m_curboxmin[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
|
|
else
|
|
{
|
|
if(buf.m_x[j]>buf.m_curboxmax[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
|
|
}
|
|
}
|
|
//--- Call PartialCalcRec()
|
|
rcur=s.m_ri[levelidx];
|
|
invrc2=1/(rcur*rcur);
|
|
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,y,y,0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its derivatives, using external buffer object (internal|
|
|
//| temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y, DY - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2TsDiffBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
double rcur=0;
|
|
double rquery2=0;
|
|
double invrc2=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
//--- Handle linear term
|
|
for(int i=0; i<ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,s.m_v.Get(i,j));
|
|
}
|
|
if(s.m_nh==0)
|
|
return;
|
|
//--- Handle nonlinear term
|
|
AllocateCalcBuffer(s,buf);
|
|
for(int j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
for(int i=0; i<ny; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
|
|
}
|
|
for(int levelidx=0; levelidx<s.m_nh; levelidx++)
|
|
{
|
|
//--- Prepare fields of Buf required by PartialCalcRec()
|
|
buf.m_curdist2=0;
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
|
|
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
|
|
if(buf.m_x[j]<buf.m_curboxmin[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
|
|
else
|
|
{
|
|
if(buf.m_x[j]>buf.m_curboxmax[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
|
|
}
|
|
}
|
|
//--- Call PartialCalcRec()
|
|
rcur=s.m_ri[levelidx];
|
|
invrc2=1/(rcur*rcur);
|
|
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,dy,dy,1);
|
|
}
|
|
for(int i=0; i<ny; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its first and second derivatives, using external buffer|
|
|
//| object (internal temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. |
|
|
//| X may have more than NX elements, in this case only|
|
|
//| leading NX will be used. |
|
|
//| Y,DY,D2Y - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//| DY - derivatives, array[NY*NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//| D2Y - second derivatives, array[NY*NX*NX] |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2TSHessBuf(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy,
|
|
CRowDouble &d2y)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
double rcur=0;
|
|
double rquery2=0;
|
|
double invrc2=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
if(CAp::Len(d2y)<ny*nx*nx)
|
|
d2y.Resize(ny*nx*nx);
|
|
//--- Handle linear term
|
|
for(int i=0; i<ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,s.m_v.Get(i,j));
|
|
}
|
|
d2y.Fill(0);
|
|
if(s.m_nh==0)
|
|
return;
|
|
//--- Handle nonlinear term
|
|
AllocateCalcBuffer(s,buf);
|
|
for(int j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
for(int i=0; i<ny; i++)
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
|
|
for(int levelidx=0; levelidx<=s.m_nh-1; levelidx++)
|
|
{
|
|
//--- Prepare fields of Buf required by PartialCalcRec()
|
|
buf.m_curdist2=0;
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_curboxmin.Set(j,s.m_kdboxmin[j]);
|
|
buf.m_curboxmax.Set(j,s.m_kdboxmax[j]);
|
|
if(buf.m_x[j]<buf.m_curboxmin[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-buf.m_x[j]);
|
|
else
|
|
{
|
|
if(buf.m_x[j]>buf.m_curboxmax[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_x[j]-buf.m_curboxmax[j]);
|
|
}
|
|
}
|
|
//--- Call PartialCalcRec()
|
|
rcur=s.m_ri[levelidx];
|
|
invrc2=1/(rcur*rcur);
|
|
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
PartialCalcRec(s,buf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_x,y,dy,d2y,2);
|
|
}
|
|
for(int i=0; i<ny; i++)
|
|
for(int j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
|
|
for(int i=0; i<ny; i++)
|
|
for(int j=0; j<nx; j++)
|
|
for(int k=0; k<nx; k++)
|
|
d2y.Set(i*nx*nx+j*nx+k,d2y[i*nx*nx+j*nx+k]/(s.m_s[j]*s.m_s[k]));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the regular |
|
|
//| grid. |
|
|
//| Grid have N0*N1 points, with Point[I,J] = (X0[I], X1[J]) |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0] |
|
|
//| N0 - grid size (number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| N1 - grid size (number of nodes) in the second dimension|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[N0,N1]. Y is out-variable |
|
|
//| and is reallocated by this function. |
|
|
//| NOTE: as a special exception, this function supports unordered |
|
|
//| arrays X0 and X1. However, future versions may be more |
|
|
//| efficient for X0/X1 ordered by ascending. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2GridCalc2(CRBFV2Model &s,CRowDouble &x0,
|
|
int n0,CRowDouble &x1,int n1,
|
|
CMatrixDouble &y)
|
|
{
|
|
//--- create variables
|
|
CRowDouble cpx0;
|
|
CRowDouble cpx1;
|
|
CRowDouble dummyx2;
|
|
CRowDouble dummyx3;
|
|
bool dummyflag[];
|
|
CRowInt p01;
|
|
CRowInt p11;
|
|
CRowInt p2;
|
|
CRowDouble vy;
|
|
y.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
y=matrix<double>::Zeros(n0,n1);
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return;
|
|
//create and sort arrays
|
|
cpx0=x0;
|
|
cpx0.Resize(n0);
|
|
CTSort::TagSort(cpx0,n0,p01,p2);
|
|
cpx1=x1;
|
|
cpx1.Resize(n1);
|
|
CTSort::TagSort(cpx1,n1,p11,p2);
|
|
dummyx2=vector<double>::Zeros(1);
|
|
dummyx3=vector<double>::Zeros(1);
|
|
vy.Resize(n0*n1);
|
|
RBFV2GridCalcVX(s,cpx0,n0,cpx1,n1,dummyx2,1,dummyx3,1,dummyflag,false,vy);
|
|
for(int i=0; i<n0; i++)
|
|
for(int j=0; j<n1; j++)
|
|
y.Set(i,j,vy[i+j*n0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to perform gridded calculation for 2D, 3D |
|
|
//| or 4D problems. It accepts parameters X0...X3 and counters |
|
|
//| N0...N3. If RBF model has dimensionality less than 4, |
|
|
//| corresponding arrays should contain just one element equal to |
|
|
//| zero, and corresponding N's should be equal to 1. |
|
|
//| NOTE: array Y should be preallocated by caller. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2GridCalcVX(CRBFV2Model &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,
|
|
CRowDouble &x2,int n2,
|
|
CRowDouble &x3,int n3,
|
|
bool &flagy[],bool sparsey,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
CRowDouble tx;
|
|
CRowDouble ty;
|
|
CRowDouble z;
|
|
int dstoffs=0;
|
|
int dummy=0;
|
|
CRBFV2GridCalcBuffer bufseedv2;
|
|
CRBFV2GridCalcBuffer bufpool[];
|
|
int rowidx=0;
|
|
int rowcnt=0;
|
|
double v=0;
|
|
double rcur=0;
|
|
int levelidx=0;
|
|
double searchradius2=0;
|
|
int ntrials=0;
|
|
double avgfuncpernode=0;
|
|
CHighQualityRandState rs;
|
|
CRowInt blocks0;
|
|
CRowInt blocks1;
|
|
CRowInt blocks2;
|
|
CRowInt blocks3;
|
|
int blockscnt0=0;
|
|
int blockscnt1=0;
|
|
int blockscnt2=0;
|
|
int blockscnt3=0;
|
|
double blockwidth0=0;
|
|
double blockwidth1=0;
|
|
double blockwidth2=0;
|
|
double blockwidth3=0;
|
|
int maxblocksize=0;
|
|
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
CHighQualityRand::HQRndSeed(532,54734,rs);
|
|
//--- Perform integrity checks
|
|
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=4 || (CAp::Len(x3)>=1 && x3[0]==0.0 && n3==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=3 || (CAp::Len(x2)>=1 && x2[0]==0.0 && n2==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=2 || (CAp::Len(x1)>=1 && x1[0]==0.0 && n1==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Allocate arrays
|
|
if(!CAp::Assert(s.m_nx<=4,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
z.Resize(ny);
|
|
tx.Resize(4);
|
|
ty.Resize(ny);
|
|
//--- Calculate linear term
|
|
rowcnt=n1*n2*n3;
|
|
for(rowidx=0; rowidx<rowcnt; rowidx++)
|
|
{
|
|
//--- Calculate TX - current position
|
|
k=rowidx;
|
|
tx.Set(0,0);
|
|
tx.Set(1,x1[k%n1]);
|
|
k=k/n1;
|
|
tx.Set(2,x2[k%n2]);
|
|
k=k/n2;
|
|
tx.Set(3,x3[k%n3]);
|
|
k=k/n3;
|
|
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
v=s.m_v.Get(j,nx);
|
|
for(k=1; k<nx; k++)
|
|
v=v+tx[k]*s.m_v.Get(j,k);
|
|
z.Set(j,v);
|
|
}
|
|
for(i=0; i<n0; i++)
|
|
{
|
|
dstoffs=ny*(rowidx*n0+i);
|
|
if(sparsey && !flagy[rowidx*n0+i])
|
|
{
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j+dstoffs,0);
|
|
continue;
|
|
}
|
|
v=x0[i];
|
|
for(j=0; j<ny; j++)
|
|
y.Set(j+dstoffs,z[j]+v*s.m_v.Get(j,0));
|
|
}
|
|
}
|
|
if(s.m_nh==0)
|
|
return;
|
|
//--- Process RBF terms, layer by layer
|
|
ArrayResize(bufpool,s.m_nh);
|
|
for(levelidx=0; levelidx<s.m_nh; levelidx++)
|
|
{
|
|
rcur=s.m_ri[levelidx];
|
|
blockwidth0=1;
|
|
blockwidth1=1;
|
|
blockwidth2=1;
|
|
blockwidth3=1;
|
|
if(nx>=1)
|
|
blockwidth0=rcur*s.m_s[0];
|
|
if(nx>=2)
|
|
blockwidth1=rcur*s.m_s[1];
|
|
if(nx>=3)
|
|
blockwidth2=rcur*s.m_s[2];
|
|
if(nx>=4)
|
|
blockwidth3=rcur*s.m_s[3];
|
|
maxblocksize=8;
|
|
//--- Group grid nodes into blocks according to current radius
|
|
blocks0.Resize(n0+1);
|
|
blockscnt0=0;
|
|
blocks0.Set(0,0);
|
|
for(i=1; i<n0; i++)
|
|
{
|
|
if((x0[i]-x0[blocks0[blockscnt0]])>blockwidth0 || i-blocks0[blockscnt0]>=maxblocksize)
|
|
{
|
|
blockscnt0++;
|
|
blocks0.Set(blockscnt0,i);
|
|
}
|
|
}
|
|
blockscnt0++;
|
|
blocks0.Set(blockscnt0,n0);
|
|
blocks1.Resize(n1+1);
|
|
blockscnt1=0;
|
|
blocks1.Set(0,0);
|
|
for(i=1; i<n1; i++)
|
|
{
|
|
if((x1[i]-x1[blocks1[blockscnt1]])>blockwidth1 || i-blocks1[blockscnt1]>=maxblocksize)
|
|
{
|
|
blockscnt1++;
|
|
blocks1.Set(blockscnt1,i);
|
|
}
|
|
}
|
|
blockscnt1++;
|
|
blocks1.Set(blockscnt1,n1);
|
|
blocks2.Resize(n2+1);
|
|
blockscnt2=0;
|
|
blocks2.Set(0,0);
|
|
for(i=1; i<n2; i++)
|
|
{
|
|
if((x2[i]-x2[blocks2[blockscnt2]])>blockwidth2 || i-blocks2[blockscnt2]>=maxblocksize)
|
|
{
|
|
blockscnt2++;
|
|
blocks2.Set(blockscnt2,i);
|
|
}
|
|
}
|
|
blockscnt2++;
|
|
blocks2.Set(blockscnt2,n2);
|
|
blocks3.Resize(n3+1);
|
|
blockscnt3=0;
|
|
blocks3.Set(0,0);
|
|
for(i=1; i<n3; i++)
|
|
{
|
|
if((x3[i]-x3[blocks3[blockscnt3]])>blockwidth3 || i-blocks3[blockscnt3]>=maxblocksize)
|
|
{
|
|
blockscnt3++;
|
|
blocks3.Set(blockscnt3,i);
|
|
}
|
|
}
|
|
blockscnt3++;
|
|
blocks3.Set(blockscnt3,n3);
|
|
//--- Prepare seed for shared pool
|
|
AllocateCalcBuffer(s,bufseedv2.m_calcbuf);
|
|
bufpool[levelidx]=bufseedv2;
|
|
//--- Determine average number of neighbor per node
|
|
searchradius2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
ntrials=100;
|
|
avgfuncpernode=0.0;
|
|
for(i=0; i<=ntrials-1; i++)
|
|
{
|
|
tx.Set(0,x0[CHighQualityRand::HQRndUniformI(rs,n0)]);
|
|
tx.Set(1,x1[CHighQualityRand::HQRndUniformI(rs,n1)]);
|
|
tx.Set(2,x2[CHighQualityRand::HQRndUniformI(rs,n2)]);
|
|
tx.Set(3,x3[CHighQualityRand::HQRndUniformI(rs,n3)]);
|
|
PreparePartialQuery(tx,s.m_kdboxmin,s.m_kdboxmax,nx,bufseedv2.m_calcbuf,dummy);
|
|
avgfuncpernode+=(double)PartialCountRec(s.m_kdnodes,s.m_kdsplits,s.m_cw,nx,ny,bufseedv2.m_calcbuf,s.m_kdroots[levelidx],searchradius2,tx)/(double)ntrials;
|
|
}
|
|
//--- Perform calculation in multithreaded mode
|
|
RBFV2PartialGridCalcRec(s,x0,n0,x1,n1,x2,n2,x3,n3,blocks0,0,blockscnt0,blocks1,0,blockscnt1,blocks2,0,blockscnt2,blocks3,0,blockscnt3,flagy,sparsey,levelidx,avgfuncpernode,bufpool,y);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2PartialGridCalcRec(CRBFV2Model &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,
|
|
CRowDouble &x2,int n2,
|
|
CRowDouble &x3,int n3,
|
|
CRowInt &blocks0,int block0a,int block0b,
|
|
CRowInt &blocks1,int block1a,int block1b,
|
|
CRowInt &blocks2,int block2a,int block2b,
|
|
CRowInt &blocks3,int block3a,int block3b,
|
|
bool &flagy[],bool sparsey,int levelidx,
|
|
double avgfuncpernode,CRBFV2GridCalcBuffer &bufpool[],
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
int k=0;
|
|
int l=0;
|
|
int blkidx=0;
|
|
int blkcnt=0;
|
|
int nodeidx=0;
|
|
int nodescnt=0;
|
|
int rowidx=0;
|
|
int rowscnt=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
int i2=0;
|
|
int i3=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int j2=0;
|
|
int j3=0;
|
|
double rcur=0;
|
|
double invrc2=0;
|
|
double rquery2=0;
|
|
double rfar2=0;
|
|
int dstoffs=0;
|
|
int srcoffs=0;
|
|
int dummy=0;
|
|
double rowwidth=0;
|
|
double maxrowwidth=0;
|
|
double problemcost=0;
|
|
int maxbs=0;
|
|
int midpoint=0;
|
|
bool emptyrow=false;
|
|
CRBFV2GridCalcBuffer buf;
|
|
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
//--- Integrity checks
|
|
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Retrieve buffer object from pool (it will be returned later)
|
|
buf=bufpool[levelidx];
|
|
//--- Calculate RBF model
|
|
if(!CAp::Assert(nx<=4,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
buf.m_tx.Resize(4);
|
|
buf.m_cx.Resize(4);
|
|
buf.m_ty.Resize(ny);
|
|
rcur=s.m_ri[levelidx];
|
|
invrc2=1/(rcur*rcur);
|
|
blkcnt=(block3b-block3a)*(block2b-block2a)*(block1b-block1a)*(block0b-block0a);
|
|
for(blkidx=0; blkidx<blkcnt; blkidx++)
|
|
{
|
|
//--- Select block (I0,I1,I2,I3).
|
|
//--- NOTE: for problems with NX<4 corresponding I_? are zero.
|
|
k=blkidx;
|
|
i0=block0a+k%(block0b-block0a);
|
|
k=k/(block0b-block0a);
|
|
i1=block1a+k%(block1b-block1a);
|
|
k=k/(block1b-block1a);
|
|
i2=block2a+k%(block2b-block2a);
|
|
k=k/(block2b-block2a);
|
|
i3=block3a+k%(block3b-block3a);
|
|
k=k/(block3b-block3a);
|
|
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- We partitioned grid into blocks and selected block with
|
|
//--- index (I0,I1,I2,I3). This block is a 4D cube (some dimensions
|
|
//--- may be zero) of nodes with indexes (J0,J1,J2,J3), which is
|
|
//--- further partitioned into a set of rows, each row corresponding
|
|
//--- to indexes J1...J3 being fixed.
|
|
//--- We process block row by row, and each row may be handled
|
|
//--- by either "generic" (nodes are processed separately) or
|
|
//--- batch algorithm (that's the reason to use rows, after all).
|
|
//--- Process nodes of the block
|
|
rowscnt=(blocks3[i3+1]-blocks3[i3])*(blocks2[i2+1]-blocks2[i2])*(blocks1[i1+1]-blocks1[i1]);
|
|
for(rowidx=0; rowidx<rowscnt; rowidx++)
|
|
{
|
|
//--- Find out node indexes (*,J1,J2,J3).
|
|
//--- NOTE: for problems with NX<4 corresponding J_? are zero.
|
|
k=rowidx;
|
|
j1=blocks1[i1]+k%(blocks1[i1+1]-blocks1[i1]);
|
|
k=k/(blocks1[i1+1]-blocks1[i1]);
|
|
j2=blocks2[i2]+k%(blocks2[i2+1]-blocks2[i2]);
|
|
k=k/(blocks2[i2+1]-blocks2[i2]);
|
|
j3=blocks3[i3]+k%(blocks3[i3+1]-blocks3[i3]);
|
|
k=k/(blocks3[i3+1]-blocks3[i3]);
|
|
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Analyze row, skip completely empty rows
|
|
nodescnt=blocks0[i0+1]-blocks0[i0];
|
|
srcoffs=blocks0[i0]+(j1+(j2+j3*n2)*n1)*n0;
|
|
emptyrow=true;
|
|
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
|
|
emptyrow=emptyrow && (sparsey && !flagy[srcoffs+nodeidx]);
|
|
if(emptyrow)
|
|
continue;
|
|
//--- Process row-use either "batch" (rowsize>1) or "generic"
|
|
//--- (row size is 1) algorithm.
|
|
//--- NOTE: "generic" version may also be used as fallback code for
|
|
//--- situations when we do not want to use batch code.
|
|
maxrowwidth=0.5*RBFV2NearRadius(s.m_bf)*rcur*s.m_s[0];
|
|
rowwidth=x0[blocks0[i0+1]-1]-x0[blocks0[i0]];
|
|
if(nodescnt>1 && rowwidth<=maxrowwidth)
|
|
{
|
|
//--- "Batch" code which processes entire row at once, saving
|
|
//--- some time in kd-tree search code.
|
|
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf)+0.5*rowwidth/s.m_s[0]);
|
|
rfar2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
j0=blocks0[i0];
|
|
if(nx>0)
|
|
buf.m_cx.Set(0,(x0[j0]+0.5*rowwidth)/s.m_s[0]);
|
|
if(nx>1)
|
|
buf.m_cx.Set(1,x1[j1]/s.m_s[1]);
|
|
if(nx>2)
|
|
buf.m_cx.Set(2,x2[j2]/s.m_s[2]);
|
|
if(nx>3)
|
|
buf.m_cx.Set(3,x3[j3]/s.m_s[3]);
|
|
srcoffs=j0+(j1+(j2+j3*n2)*n1)*n0;
|
|
dstoffs=ny*srcoffs;
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_rx,nodescnt);
|
|
CApServ::BVectorSetLengthAtLeast(buf.m_rf,nodescnt);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_ry,nodescnt*ny);
|
|
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
|
|
{
|
|
buf.m_rx.Set(nodeidx,x0[j0+nodeidx]/s.m_s[0]);
|
|
buf.m_rf[nodeidx]=!sparsey || flagy[srcoffs+nodeidx];
|
|
}
|
|
for(k=0; k<nodescnt*ny; k++)
|
|
buf.m_ry.Set(k,0);
|
|
PreparePartialQuery(buf.m_cx,s.m_kdboxmin,s.m_kdboxmax,nx,buf.m_calcbuf,dummy);
|
|
PartialRowCalcRec(s,buf.m_calcbuf,s.m_kdroots[levelidx],invrc2,rquery2,rfar2,buf.m_cx,buf.m_rx,buf.m_rf,nodescnt,buf.m_ry);
|
|
for(k=0; k<nodescnt*ny; k++)
|
|
y.Add(dstoffs+k,buf.m_ry[k]);
|
|
}
|
|
else
|
|
{
|
|
//--- "Generic" code. Although we usually move here
|
|
//--- only when NodesCnt=1, we still use a loop on
|
|
//--- NodeIdx just to be able to use this branch as
|
|
//--- fallback code without any modifications.
|
|
rquery2=CMath::Sqr(rcur*RBFV2FarRadius(s.m_bf));
|
|
for(nodeidx=0; nodeidx<nodescnt; nodeidx++)
|
|
{
|
|
//--- Prepare TX - current point
|
|
j0=blocks0[i0]+nodeidx;
|
|
if(nx>0)
|
|
buf.m_tx.Set(0,x0[j0]/s.m_s[0]);
|
|
if(nx>1)
|
|
buf.m_tx.Set(1,x1[j1]/s.m_s[1]);
|
|
if(nx>2)
|
|
buf.m_tx.Set(2,x2[j2]/s.m_s[2]);
|
|
if(nx>3)
|
|
buf.m_tx.Set(3,x3[j3]/s.m_s[3]);
|
|
//--- Evaluate and add to Y
|
|
srcoffs=j0+(j1+(j2+j3*n2)*n1)*n0;
|
|
dstoffs=ny*srcoffs;
|
|
for(l=0; l<ny; l++)
|
|
buf.m_ty.Set(l,0);
|
|
if(!sparsey || flagy[srcoffs])
|
|
{
|
|
PreparePartialQuery(buf.m_tx,s.m_kdboxmin,s.m_kdboxmax,nx,buf.m_calcbuf,dummy);
|
|
PartialCalcRec(s,buf.m_calcbuf,s.m_kdroots[levelidx],invrc2,rquery2,buf.m_tx,buf.m_ty,buf.m_ty,buf.m_ty,0);
|
|
}
|
|
for(l=0; l<ny; l++)
|
|
y.Add(dstoffs+l,buf.m_ty[l]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Recycle buffer object back to pool
|
|
bufpool[levelidx]=buf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "unpacks" RBF model by extracting its coefficients.|
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NX - dimensionality of argument |
|
|
//| NY - dimensionality of the target function |
|
|
//| XWR - model information, array[NC,NX+NY+1]. |
|
|
//| One row of the array corresponds to one basis |
|
|
//| function: |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of |
|
|
//| the function being modelled |
|
|
//| * last NX columns - radii, per dimension |
|
|
//| NC - number of the centers |
|
|
//| V - polynomial term , array[NY,NX+1]. One row per one |
|
|
//| dimension of the function being modelled. First NX |
|
|
//| elements are linear coefficients, V[NX] is equal to|
|
|
//| the constant part. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::RBFV2Unpack(CRBFV2Model &s,int &nx,int &ny,CMatrixDouble &xwr,
|
|
int &nc,CMatrixDouble &v)
|
|
{
|
|
int ncactual=0;
|
|
|
|
nx=0;
|
|
ny=0;
|
|
xwr.Resize(0,0);
|
|
nc=0;
|
|
v.Resize(0,0);
|
|
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
nc=0;
|
|
//--- Fill V
|
|
v=s.m_v;
|
|
v.Resize(s.m_ny,s.m_nx+1);
|
|
//--- Fill XWR
|
|
if(!CAp::Assert(CAp::Len(s.m_cw)%(s.m_nx+s.m_ny)==0,__FUNCTION__+": integrity error"))
|
|
return;
|
|
nc=CAp::Len(s.m_cw)/(s.m_nx+s.m_ny);
|
|
ncactual=0;
|
|
if(nc>0)
|
|
{
|
|
xwr.Resize(nc,s.m_nx+s.m_ny+s.m_nx);
|
|
for(int i=0; i<s.m_nh; i++)
|
|
PartialUnpackRec(s.m_kdnodes,s.m_kdsplits,s.m_cw,s.m_s,s.m_nx,s.m_ny,s.m_kdroots[i],s.m_ri[i],xwr,ncactual);
|
|
}
|
|
CAp::Assert(nc==ncactual,__FUNCTION__+": integrity error");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CRBFV2::RBFV2BuildLinearModel(CMatrixDouble &x,CMatrixDouble &y,
|
|
int n,int nx,int ny,int modeltype,
|
|
CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
bool result=false;
|
|
CRowDouble tmpy;
|
|
CMatrixDouble a;
|
|
double scaling=0;
|
|
CRowDouble shifting;
|
|
double mn=0;
|
|
double mx=0;
|
|
CRowDouble c;
|
|
CLSFitReport rep;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int info=0;
|
|
v.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return(false);
|
|
if(!CAp::Assert(nx>0,__FUNCTION__+": NX<=0"))
|
|
return(false);
|
|
if(!CAp::Assert(ny>0,__FUNCTION__+": NY<=0"))
|
|
return(false);
|
|
//--- Handle degenerate case (N=0)
|
|
result=true;
|
|
v=matrix<double>::Zeros(ny,nx+1);
|
|
if(n==0)
|
|
return(result);
|
|
//--- Allocate temporaries
|
|
tmpy.Resize(n);
|
|
//--- General linear model.
|
|
switch(modeltype)
|
|
{
|
|
case 1:
|
|
//--- Calculate scaling/shifting, transform variables, prepare LLS problem
|
|
a.Resize(n,nx+1);
|
|
shifting.Resize(nx);
|
|
scaling=0;
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
mn=x.Get(0,i);
|
|
mx=mn;
|
|
for(j=1; j<n; j++)
|
|
{
|
|
if(mn>x.Get(j,i))
|
|
mn=x.Get(j,i);
|
|
if(mx<x.Get(j,i))
|
|
mx=x.Get(j,i);
|
|
}
|
|
scaling=MathMax(scaling,mx-mn);
|
|
shifting.Set(i,0.5*(mx+mn));
|
|
}
|
|
if(scaling==0.0)
|
|
scaling=1;
|
|
else
|
|
scaling=0.5*scaling;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
a.Set(i,j,(x.Get(i,j)-shifting[j])/scaling);
|
|
}
|
|
for(i=0; i<n; i++)
|
|
a.Set(i,nx,1);
|
|
//--- Solve linear system in transformed variables, make backward
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
tmpy.Set(j,y.Get(j,i));
|
|
CLSFit::LSFitLinear(tmpy,a,n,nx+1,info,c,rep);
|
|
if(info<=0)
|
|
return(false);
|
|
for(j=0; j<nx; j++)
|
|
v.Set(i,j,c[j]/scaling);
|
|
v.Set(i,nx,c[nx]);
|
|
v.Add(i,nx,- CAblasF::RDotVR(nx,shifting,v,i));
|
|
for(j=0; j<n; j++)
|
|
y.Add(j,i,- CAblasF::RDotRR(nx,x,j,v,i)-v.Get(i,nx));
|
|
}
|
|
break;
|
|
//--- Constant model, very simple
|
|
case 2:
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
v.Add(i,nx,y.Get(j,i));
|
|
if(n>0)
|
|
v.Mul(i,nx,1.0/(double)n);
|
|
for(j=0; j<n; j++)
|
|
y.Add(j,i,- v.Get(i,nx));
|
|
}
|
|
break;
|
|
//--- Zero model
|
|
case 3:
|
|
break;
|
|
//---
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unknown model type");
|
|
result=false;
|
|
break;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reallocates calcBuf if necessary, reuses previously allocated |
|
|
//| space if possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::AllocateCalcBuffer(CRBFV2Model &s,CRBFV2CalcBuffer &buf)
|
|
{
|
|
if(CAp::Len(buf.m_x)<s.m_nx)
|
|
buf.m_x.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_curboxmin)<s.m_nx)
|
|
buf.m_curboxmin.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_curboxmax)<s.m_nx)
|
|
buf.m_curboxmax.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_x123)<s.m_nx)
|
|
buf.m_x123.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_y123)<s.m_ny)
|
|
buf.m_y123.Resize(s.m_ny);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Extracts structure (and XY - values too) from kd-tree built for |
|
|
//| a small subset of points and appends it to multi-tree. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::ConvertAndAppendTree(CKDTree &curtree,int n,int nx,int ny,
|
|
CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw)
|
|
{
|
|
//--- create variables
|
|
int nodesbase=0;
|
|
int splitsbase=0;
|
|
int cwbase=0;
|
|
CRowInt localnodes;
|
|
CRowDouble localsplits;
|
|
CRowDouble localcw;
|
|
CMatrixDouble xybuf;
|
|
int localnodessize=0;
|
|
int localsplitssize=0;
|
|
int localcwsize=0;
|
|
//--- Calculate base offsets
|
|
nodesbase=CAp::Len(kdnodes);
|
|
splitsbase=CAp::Len(kdsplits);
|
|
cwbase=CAp::Len(cw);
|
|
//--- Prepare local copy of tree
|
|
localnodes.Resize(n*m_maxnodesize);
|
|
localsplits.Resize(n);
|
|
localcw.Resize((nx+ny)*n);
|
|
localnodessize=0;
|
|
localsplitssize=0;
|
|
localcwsize=0;
|
|
ConvertTreeRec(curtree,n,nx,ny,0,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
|
|
//--- Append to multi-tree
|
|
kdnodes.Resize(CAp::Len(kdnodes)+localnodessize);
|
|
kdsplits.Resize(CAp::Len(kdsplits)+localsplitssize);
|
|
cw.Resize(CAp::Len(cw)+localcwsize);
|
|
for(int i=0; i<localnodessize; i++)
|
|
kdnodes.Set(nodesbase+i,localnodes[i]);
|
|
for(int i=0; i<localsplitssize; i++)
|
|
kdsplits.Set(splitsbase+i,localsplits[i]);
|
|
for(int i=0; i<localcwsize; i++)
|
|
cw.Set(cwbase+i,localcw[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recurrent tree conversion |
|
|
//| CurTree - tree to convert |
|
|
//| N, NX, NY - dataset metrics |
|
|
//| NodeOffset - offset of current tree node, 0 for root |
|
|
//| NodesBase - a value which is added to intra-tree node |
|
|
//| indexes; although this tree is stored in |
|
|
//| separate array, it is intended to be stored in |
|
|
//| the larger tree, with localNodes being moved to |
|
|
//| offset NodesBase. |
|
|
//| SplitsBase - similarly, offset of localSplits in the final |
|
|
//| tree |
|
|
//| CWBase - similarly, offset of localCW in the final tree |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::ConvertTreeRec(CKDTree &curtree,int n,int nx,int ny,
|
|
int nodeoffset,int nodesbase,int splitsbase,
|
|
int cwbase,CRowInt &localnodes,int &localnodessize,
|
|
CRowDouble &localsplits,int &localsplitssize,
|
|
CRowDouble &localcw,int &localcwsize,
|
|
CMatrixDouble &xybuf)
|
|
{
|
|
//--- create variables
|
|
int nodetype=0;
|
|
int cnt=0;
|
|
int d=0;
|
|
double s=0;
|
|
int nodele=0;
|
|
int nodege=0;
|
|
int oldnodessize=0;
|
|
CNearestNeighbor::KDTreeExploreNodeType(curtree,nodeoffset,nodetype);
|
|
//--- Leaf node
|
|
switch(nodetype)
|
|
{
|
|
case 0:
|
|
CNearestNeighbor::KDTreeExploreLeaf(curtree,nodeoffset,xybuf,cnt);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(localnodes)>=localnodessize+2,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(localcw)>=localcwsize+cnt*(nx+ny),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
localnodes.Set(localnodessize,cnt);
|
|
localnodes.Set(localnodessize+1,cwbase+localcwsize);
|
|
localnodessize+=2;
|
|
for(int i=0; i<cnt; i++)
|
|
{
|
|
for(int j=0; j<nx+ny; j++)
|
|
localcw.Set(localcwsize+i*(nx+ny)+j,xybuf.Get(i,j));
|
|
}
|
|
localcwsize+=cnt*(nx+ny);
|
|
break;
|
|
//--- Split node
|
|
case 1:
|
|
CNearestNeighbor::KDTreeExploreSplit(curtree,nodeoffset,d,s,nodele,nodege);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(localnodes)>=localnodessize+m_maxnodesize,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(localsplits)>=localsplitssize+1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
oldnodessize=localnodessize;
|
|
localnodes.Set(localnodessize,0);
|
|
localnodes.Set(localnodessize+1,d);
|
|
localnodes.Set(localnodessize+2,splitsbase+localsplitssize);
|
|
localnodes.Set(localnodessize+3,-1);
|
|
localnodes.Set(localnodessize+4,-1);
|
|
localnodessize+=5;
|
|
localsplits.Set(localsplitssize,s);
|
|
localsplitssize++;
|
|
localnodes.Set(oldnodessize+3,nodesbase+localnodessize);
|
|
ConvertTreeRec(curtree,n,nx,ny,nodele,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
|
|
localnodes.Set(oldnodessize+4,nodesbase+localnodessize);
|
|
ConvertTreeRec(curtree,n,nx,ny,nodege,nodesbase,splitsbase,cwbase,localnodes,localnodessize,localsplits,localsplitssize,localcw,localcwsize,xybuf);
|
|
break;
|
|
default:
|
|
//--- Integrity error
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs partial calculation of hierarchical model:|
|
|
//| given evaluation point X and partially computed value Y, it |
|
|
//| updates Y by values computed using part of multi-tree given by |
|
|
//| RootIdx. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - V2 model |
|
|
//| Buf - calc - buffer, this function uses following fields:|
|
|
//| * Buf.CurBoxMin - should be set by caller |
|
|
//| * Buf.CurBoxMax - should be set by caller |
|
|
//| * Buf.CurDist2 - squared distance from X to |
|
|
//| current bounding box, should be |
|
|
//| set by caller |
|
|
//| RootIdx - offset of partial kd-tree |
|
|
//| InvR2 - 1 / R ^ 2, where R is basis function radius |
|
|
//| QueryR2 - squared query radius, usually it is |
|
|
//| (R*FarRadius(BasisFunction)) ^ 2 |
|
|
//| X - evaluation point, array[NX] |
|
|
//| Y - current value for target, array[NY] |
|
|
//| DY - current value for derivative, array[NY * NX], if |
|
|
//| NeedDY >= 1 |
|
|
//| D2Y - current value for derivative, array[NY * NX * NX], |
|
|
//| if NeedDY >= 2 |
|
|
//| NeedDY - whether derivatives are required or not: |
|
|
//| * 0 if only Y is needed |
|
|
//| * 1 if Y and DY are needed |
|
|
//| * 2 if Y, DY, D2Y are needed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - updated partial value |
|
|
//| DY - updated derivatives, if NeedDY >= 1 |
|
|
//| D2Y - updated Hessian, if NeedDY >= 2 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::PartialCalcRec(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
|
|
int rootidx,double invr2,double queryr2,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy,CRowDouble &d2y,
|
|
int needdy)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double ptdist2=0;
|
|
double w=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
int cwoffs=0;
|
|
int cwcnt=0;
|
|
int itemoffs=0;
|
|
double arg=0;
|
|
double val=0;
|
|
double df=0;
|
|
double d2f=0;
|
|
int d=0;
|
|
double split=0;
|
|
int childle=0;
|
|
int childge=0;
|
|
int childoffs=0;
|
|
bool updatemin=false;
|
|
double prevdist2=0;
|
|
double t1=0;
|
|
int nx=s.m_nx;;
|
|
int ny=s.m_ny;
|
|
//--- Helps to avoid spurious warnings
|
|
val=0;
|
|
//--- Leaf node.
|
|
if(s.m_kdnodes[rootidx]>0)
|
|
{
|
|
cwcnt=s.m_kdnodes[rootidx+0];
|
|
cwoffs=s.m_kdnodes[rootidx+1];
|
|
for(i=0; i<cwcnt; i++)
|
|
{
|
|
//--- Calculate distance
|
|
itemoffs=cwoffs+i*(nx+ny);
|
|
ptdist2=0;
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
v=s.m_cw[itemoffs+j]-x[j];
|
|
ptdist2+=v*v;
|
|
}
|
|
//--- Skip points if distance too large
|
|
if(ptdist2>=queryr2)
|
|
continue;
|
|
//--- Update Y
|
|
arg=ptdist2*invr2;
|
|
val=0;
|
|
df=0;
|
|
d2f=0;
|
|
if(needdy==2)
|
|
{
|
|
if(s.m_bf==0)
|
|
{
|
|
val=MathExp(-arg);
|
|
df=-val;
|
|
d2f=val;
|
|
}
|
|
else
|
|
{
|
|
if(s.m_bf==1)
|
|
RBFV2BasisFuncDiff2(s.m_bf,arg,val,df,d2f);
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return;
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
|
|
w=s.m_cw[itemoffs+nx+j];
|
|
v=w*df*invr2*2;
|
|
for(k0=0; k0<nx; k0++)
|
|
{
|
|
for(k1=0; k1<nx; k1++)
|
|
{
|
|
if(k0==k1)
|
|
{
|
|
//--- Compute derivative and diagonal element of the Hessian
|
|
dy.Add(j*nx+k0,v*(x[k0]-s.m_cw[itemoffs+k0]));
|
|
d2y.Add(j*nx*nx+k0*nx+k1,w*(d2f*invr2*invr2*4*CMath::Sqr(x[k0]-s.m_cw[itemoffs+k0])+df*invr2*2));
|
|
}
|
|
else
|
|
{
|
|
//--- Compute offdiagonal element of the Hessian
|
|
d2y.Add(j*nx*nx+k0*nx+k1,w*d2f*invr2*invr2*4*(x[k0]-s.m_cw[itemoffs+k0])*(x[k1]-s.m_cw[itemoffs+k1]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(needdy==1)
|
|
{
|
|
if(s.m_bf==0)
|
|
{
|
|
val=MathExp(-arg);
|
|
df=-val;
|
|
}
|
|
else
|
|
{
|
|
if(s.m_bf==1)
|
|
RBFV2BasisFuncDiff2(s.m_bf,arg,val,df,d2f);
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return;
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
|
|
v=s.m_cw[itemoffs+nx+j]*df*invr2*2;
|
|
for(k=0; k<nx; k++)
|
|
dy.Add(j*nx+k,v*(x[k]-s.m_cw[itemoffs+k]));
|
|
}
|
|
}
|
|
if(needdy==0)
|
|
{
|
|
if(s.m_bf==0)
|
|
val=MathExp(-arg);
|
|
else
|
|
{
|
|
if(s.m_bf==1)
|
|
val=RBFV2BasisFunc(s.m_bf,arg);
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
return;
|
|
}
|
|
}
|
|
for(j=0; j<ny; j++)
|
|
y.Add(j,val*s.m_cw[itemoffs+nx+j]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- Simple split
|
|
if(s.m_kdnodes[rootidx]==0)
|
|
{
|
|
//--- Load:
|
|
//--- * D dimension to split
|
|
//--- * Split split position
|
|
//--- * ChildLE, ChildGE - indexes of childs
|
|
d=s.m_kdnodes[rootidx+1];
|
|
split=s.m_kdsplits[s.m_kdnodes[rootidx+2]];
|
|
childle=s.m_kdnodes[rootidx+3];
|
|
childge=s.m_kdnodes[rootidx+4];
|
|
//--- Navigate through childs
|
|
for(i=0; i<=1; i++)
|
|
{
|
|
//--- Select child to process:
|
|
//--- * ChildOffs current child offset in Nodes[]
|
|
//--- * UpdateMin whether minimum or maximum value
|
|
//--- of bounding box is changed on update
|
|
updatemin=i!=0;
|
|
if(i==0)
|
|
childoffs=childle;
|
|
else
|
|
childoffs=childge;
|
|
//--- Update bounding box and current distance
|
|
prevdist2=buf.m_curdist2;
|
|
t1=x[d];
|
|
if(updatemin)
|
|
{
|
|
v=buf.m_curboxmin[d];
|
|
if(t1<=split)
|
|
{
|
|
v0=v-t1;
|
|
if(v0<0)
|
|
v0=0;
|
|
v1=split-t1;
|
|
buf.m_curdist2-=v0*v0-v1*v1;
|
|
}
|
|
buf.m_curboxmin.Set(d,split);
|
|
}
|
|
else
|
|
{
|
|
v=buf.m_curboxmax[d];
|
|
if(t1>=split)
|
|
{
|
|
v0=t1-v;
|
|
if(v0<0)
|
|
v0=0;
|
|
v1=t1-split;
|
|
buf.m_curdist2-=v0*v0-v1*v1;
|
|
}
|
|
buf.m_curboxmax.Set(d,split);
|
|
}
|
|
//--- Decide: to dive into cell or not to dive
|
|
if(buf.m_curdist2<queryr2)
|
|
PartialCalcRec(s,buf,childoffs,invr2,queryr2,x,y,dy,d2y,needdy);
|
|
//--- Restore bounding box and distance
|
|
if(updatemin)
|
|
buf.m_curboxmin.Set(d,v);
|
|
else
|
|
buf.m_curboxmax.Set(d,v);
|
|
buf.m_curdist2=prevdist2;
|
|
}
|
|
return;
|
|
}
|
|
//--- Integrity failure
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs same operation as PartialCalcRec(), but |
|
|
//| for entire row of the grid. "Row" is a set of nodes(x0, x1, x2, |
|
|
//| x3) which share x1..x3, but have different x0's. (note: for 2D/3D|
|
|
//| problems x2..x3 are zero). |
|
|
//| Row is given by: |
|
|
//| * central point XC, which is located at the center of the row, |
|
|
//| and used to perform kd-tree requests |
|
|
//| * set of x0 coordinates stored in RX array (array may be |
|
|
//| unordered, but it is expected that spread of x0 is no more |
|
|
//| than R; function may be inefficient for larger spreads). |
|
|
//| * set of YFlag values stored in RF |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - V2 model |
|
|
//| Buf - calc - buffer, this function uses following fields:|
|
|
//| * Buf.CurBoxMin - should be set by caller |
|
|
//| * Buf.CurBoxMax - should be set by caller |
|
|
//| * Buf.CurDist2 - squared distance from X to |
|
|
//| current bounding box, should be |
|
|
//| set by caller |
|
|
//| RootIdx - offset of partial kd-tree |
|
|
//| InvR2 - 1 / R ^ 2, where R is basis function radius |
|
|
//| RQuery2 - squared query radius, usually it is |
|
|
//| (R*FarRadius(BasisFunction) + 0.5 * RowWidth) ^ 2, |
|
|
//| where RowWidth is its spatial extent (after scaling|
|
|
//| of variables). This radius is used to perform |
|
|
//| initial query for neighbors of CX. |
|
|
//| RFar2 - squared far radius; far radius is used to perform |
|
|
//| actual filtering of results of query made with |
|
|
//| RQuery2. |
|
|
//| CX - central point, array[NX], used for queries |
|
|
//| RX - x0 coordinates, array[RowSize] |
|
|
//| RF - sparsity flags, array[RowSize] |
|
|
//| RowSize - row size in elements |
|
|
//| RY - input partial value, array[NY] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| RY - updated partial value (function adds its results |
|
|
//| to RY) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::PartialRowCalcRec(CRBFV2Model &s,CRBFV2CalcBuffer &buf,
|
|
int rootidx,double invr2,
|
|
double rquery2,double rfar2,
|
|
CRowDouble &cx,CRowDouble &rx,
|
|
bool &rf[],int rowsize,
|
|
CRowDouble &ry)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int i0=0;
|
|
int i1=0;
|
|
double partialptdist2=0;
|
|
double ptdist2=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
int cwoffs=0;
|
|
int cwcnt=0;
|
|
int itemoffs=0;
|
|
int woffs=0;
|
|
double val=0;
|
|
int d=0;
|
|
double split=0;
|
|
int childle=0;
|
|
int childge=0;
|
|
int childoffs=0;
|
|
bool updatemin=false;
|
|
double prevdist2=0;
|
|
double t1=0;
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
//--- Leaf node.
|
|
if(s.m_kdnodes[rootidx]>0)
|
|
{
|
|
cwcnt=s.m_kdnodes[rootidx+0];
|
|
cwoffs=s.m_kdnodes[rootidx+1];
|
|
for(i0=0; i0<cwcnt; i0++)
|
|
{
|
|
//--- Calculate partial distance (components from 1 to NX-1)
|
|
itemoffs=cwoffs+i0*(nx+ny);
|
|
partialptdist2=0;
|
|
for(j=1; j<nx; j++)
|
|
{
|
|
v=s.m_cw[itemoffs+j]-cx[j];
|
|
partialptdist2+=v*v;
|
|
}
|
|
//--- Process each element of the row
|
|
for(i1=0; i1<rowsize; i1++)
|
|
{
|
|
if(rf[i1])
|
|
{
|
|
//--- Calculate distance
|
|
v=s.m_cw[itemoffs]-rx[i1];
|
|
ptdist2=partialptdist2+v*v;
|
|
//--- Skip points if distance too large
|
|
if(ptdist2>=rfar2)
|
|
continue;
|
|
//--- Update Y
|
|
val=RBFV2BasisFunc(s.m_bf,ptdist2*invr2);
|
|
woffs=itemoffs+nx;
|
|
for(j=0; j<ny; j++)
|
|
ry.Add(j+i1*ny,val*s.m_cw[woffs+j]);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- Simple split
|
|
if(s.m_kdnodes[rootidx]==0)
|
|
{
|
|
//--- Load:
|
|
//--- * D dimension to split
|
|
//--- * Split split position
|
|
//--- * ChildLE, ChildGE - indexes of childs
|
|
d=s.m_kdnodes[rootidx+1];
|
|
split=s.m_kdsplits[s.m_kdnodes[rootidx+2]];
|
|
childle=s.m_kdnodes[rootidx+3];
|
|
childge=s.m_kdnodes[rootidx+4];
|
|
//--- Navigate through childs
|
|
for(i=0; i<=1; i++)
|
|
{
|
|
//--- Select child to process:
|
|
//--- * ChildOffs current child offset in Nodes[]
|
|
//--- * UpdateMin whether minimum or maximum value
|
|
//--- of bounding box is changed on update
|
|
updatemin=i!=0;
|
|
if(i==0)
|
|
childoffs=childle;
|
|
else
|
|
childoffs=childge;
|
|
//--- Update bounding box and current distance
|
|
prevdist2=buf.m_curdist2;
|
|
t1=cx[d];
|
|
if(updatemin)
|
|
{
|
|
v=buf.m_curboxmin[d];
|
|
if(t1<=split)
|
|
{
|
|
v0=v-t1;
|
|
if(v0<0)
|
|
v0=0;
|
|
v1=split-t1;
|
|
buf.m_curdist2-=v0*v0+v1*v1;
|
|
}
|
|
buf.m_curboxmin.Set(d,split);
|
|
}
|
|
else
|
|
{
|
|
v=buf.m_curboxmax[d];
|
|
if(t1>=split)
|
|
{
|
|
v0=t1-v;
|
|
if(v0<0)
|
|
v0=0;
|
|
v1=t1-split;
|
|
buf.m_curdist2-=v0*v0+v1*v1;
|
|
}
|
|
buf.m_curboxmax.Set(d,split);
|
|
}
|
|
//--- Decide: to dive into cell or not to dive
|
|
if(buf.m_curdist2<rquery2)
|
|
PartialRowCalcRec(s,buf,childoffs,invr2,rquery2,rfar2,cx,rx,rf,rowsize,ry);
|
|
//--- Restore bounding box and distance
|
|
if(updatemin)
|
|
buf.m_curboxmin.Set(d,v);
|
|
else
|
|
buf.m_curboxmax.Set(d,v);
|
|
buf.m_curdist2=prevdist2;
|
|
}
|
|
return;
|
|
}
|
|
//--- Integrity failure
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares partial query |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - query point |
|
|
//| kdBoxMin, kdBoxMax - current bounding box |
|
|
//| NX - problem size |
|
|
//| Buf - preallocated buffer; this function just loads data,|
|
|
//| but does not allocate place for them. |
|
|
//| Cnt - counter variable which is set to zery by this |
|
|
//| function, as convenience, and to remember about |
|
|
//| necessity to zero counter prior to calling |
|
|
//| PartialQueryRec(). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - calc-buffer: |
|
|
//| * Buf.CurBoxMin - current box |
|
|
//| * Buf.CurBoxMax - current box |
|
|
//| * Buf.CurDist2 - squared distance from X to |
|
|
//| current box |
|
|
//| Cnt - set to zero |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::PreparePartialQuery(CRowDouble &x,
|
|
CRowDouble &kdboxmin,
|
|
CRowDouble &kdboxmax,int nx,
|
|
CRBFV2CalcBuffer &buf,int &cnt)
|
|
{
|
|
cnt=0;
|
|
buf.m_curdist2=0;
|
|
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_curboxmin.Set(j,kdboxmin[j]);
|
|
buf.m_curboxmax.Set(j,kdboxmax[j]);
|
|
if(x[j]<buf.m_curboxmin[j])
|
|
buf.m_curdist2+=CMath::Sqr(buf.m_curboxmin[j]-x[j]);
|
|
else
|
|
{
|
|
if(x[j]>buf.m_curboxmax[j])
|
|
buf.m_curdist2+=CMath::Sqr(x[j]-buf.m_curboxmax[j]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs partial(for just one subtree of |
|
|
//| multi-tree) query for neighbors located in R-sphere around X. It |
|
|
//| returns squared distances from X to points and offsets in S.CW[] |
|
|
//| array for points being found. |
|
|
//| INPUT PARAMETERS: |
|
|
//| kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 |
|
|
//| model |
|
|
//| Buf - calc - buffer, this function uses following fields:|
|
|
//| * Buf.CurBoxMin - should be set by caller |
|
|
//| * Buf.CurBoxMax - should be set by caller |
|
|
//| * Buf.CurDist2 - squared distance from X to |
|
|
//| current bounding box, should be |
|
|
//| set by caller |
|
|
//| You may use PreparePartialQuery() function to |
|
|
//| initialize these fields. |
|
|
//| RootIdx - offset of partial kd-tree |
|
|
//| QueryR2 - squared query radius |
|
|
//| X - array[NX], point being queried |
|
|
//| R2 - preallocated output buffer; it is caller's |
|
|
//| responsibility to make sure that R2 has enough |
|
|
//| space. |
|
|
//| Offs - preallocated output buffer; it is caller's |
|
|
//| responsibility to make sure that Offs has enough |
|
|
//| space. |
|
|
//| K - MUST BE ZERO ON INITIAL CALL. This variable is |
|
|
//| incremented, not set. So, any no-zero value will |
|
|
//| result in the incorrect points count being returned|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| R2 - squared distances in first K elements |
|
|
//| Offs - offsets in S.CW in first K elements |
|
|
//| K - points count |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::PartialQueryRec(CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw,int nx,int ny,
|
|
CRBFV2CalcBuffer &buf,int rootidx,
|
|
double queryr2,CRowDouble &x,
|
|
CRowDouble &r2,CRowInt &offs,int &k)
|
|
{
|
|
//--- create variables
|
|
double ptdist2=0;
|
|
double v=0;
|
|
int cwoffs=0;
|
|
int cwcnt=0;
|
|
int itemoffs=0;
|
|
int d=0;
|
|
double split=0;
|
|
int childle=0;
|
|
int childge=0;
|
|
int childoffs=0;
|
|
bool updatemin=false;
|
|
double prevdist2=0;
|
|
double t1=0;
|
|
//--- Leaf node.
|
|
if(kdnodes[rootidx]>0)
|
|
{
|
|
cwcnt=kdnodes[rootidx+0];
|
|
cwoffs=kdnodes[rootidx+1];
|
|
for(int i=0; i<cwcnt; i++)
|
|
{
|
|
//--- Calculate distance
|
|
itemoffs=cwoffs+i*(nx+ny);
|
|
ptdist2=0;
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
v=cw[itemoffs+j]-x[j];
|
|
ptdist2+=v*v;
|
|
}
|
|
//--- Skip points if distance too large
|
|
if(ptdist2>=queryr2)
|
|
continue;
|
|
//--- Output
|
|
r2.Set(k,ptdist2);
|
|
offs.Set(k,itemoffs);
|
|
k++;
|
|
}
|
|
return;
|
|
}
|
|
//--- Simple split
|
|
if(kdnodes[rootidx]==0)
|
|
{
|
|
//--- Load:
|
|
//--- * D dimension to split
|
|
//--- * Split split position
|
|
//--- * ChildLE, ChildGE - indexes of childs
|
|
d=kdnodes[rootidx+1];
|
|
split=kdsplits[kdnodes[rootidx+2]];
|
|
childle=kdnodes[rootidx+3];
|
|
childge=kdnodes[rootidx+4];
|
|
//--- Navigate through childs
|
|
for(int i=0; i<=1; i++)
|
|
{
|
|
//--- Select child to process:
|
|
//--- * ChildOffs current child offset in Nodes[]
|
|
//--- * UpdateMin whether minimum or maximum value
|
|
//--- of bounding box is changed on update
|
|
updatemin=i!=0;
|
|
if(i==0)
|
|
childoffs=childle;
|
|
else
|
|
childoffs=childge;
|
|
//--- Update bounding box and current distance
|
|
prevdist2=buf.m_curdist2;
|
|
t1=x[d];
|
|
if(updatemin)
|
|
{
|
|
v=buf.m_curboxmin[d];
|
|
if(t1<=split)
|
|
buf.m_curdist2-=CMath::Sqr(MathMax(v-t1,0))+CMath::Sqr(split-t1);
|
|
buf.m_curboxmin.Set(d,split);
|
|
}
|
|
else
|
|
{
|
|
v=buf.m_curboxmax[d];
|
|
if(t1>=split)
|
|
buf.m_curdist2-=CMath::Sqr(MathMax(t1-v,0))+CMath::Sqr(t1-split);
|
|
buf.m_curboxmax.Set(d,split);
|
|
}
|
|
//--- Decide: to dive into cell or not to dive
|
|
if(buf.m_curdist2<queryr2)
|
|
PartialQueryRec(kdnodes,kdsplits,cw,nx,ny,buf,childoffs,queryr2,x,r2,offs,k);
|
|
//--- Restore bounding box and distance
|
|
if(updatemin)
|
|
buf.m_curboxmin.Set(d,v);
|
|
else
|
|
buf.m_curboxmax.Set(d,v);
|
|
buf.m_curdist2=prevdist2;
|
|
}
|
|
return;
|
|
}
|
|
//--- Integrity failure
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs partial(for just one subtree of |
|
|
//| multi-tree) counting of neighbors located in R-sphere around X. |
|
|
//| This function does not guarantee consistency of results with |
|
|
//| other partial queries, it should be used only to get approximate |
|
|
//| estimates (well, we do not use approximate algorithms, but |
|
|
//| rounding errors may give us inconsistent results in |
|
|
//| just-at-the-boundary cases). |
|
|
//| INPUT PARAMETERS: |
|
|
//| kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 |
|
|
//| model |
|
|
//| Buf - calc - buffer, this function uses following fields:|
|
|
//| * Buf.CurBoxMin - should be set by caller |
|
|
//| * Buf.CurBoxMax - should be set by caller |
|
|
//| * Buf.CurDist2 - squared distance from X to |
|
|
//| current bounding box, should be |
|
|
//| set by caller |
|
|
//| You may use PreparePartialQuery() function to |
|
|
//| initialize these fields. |
|
|
//| RootIdx - offset of partial kd-tree |
|
|
//| QueryR2 - squared query radius |
|
|
//| X - array[NX], point being queried |
|
|
//| RESULT: |
|
|
//| points count |
|
|
//+------------------------------------------------------------------+
|
|
int CRBFV2::PartialCountRec(CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw,int nx,int ny,
|
|
CRBFV2CalcBuffer &buf,int rootidx,
|
|
double queryr2,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
double ptdist2=0;
|
|
double v=0;
|
|
int cwoffs=0;
|
|
int cwcnt=0;
|
|
int itemoffs=0;
|
|
int d=0;
|
|
double split=0;
|
|
int childle=0;
|
|
int childge=0;
|
|
int childoffs=0;
|
|
bool updatemin=false;
|
|
double prevdist2=0;
|
|
double t1=0;
|
|
//--- Leaf node.
|
|
if(kdnodes[rootidx]>0)
|
|
{
|
|
cwcnt=kdnodes[rootidx+0];
|
|
cwoffs=kdnodes[rootidx+1];
|
|
for(int i=0; i<cwcnt; i++)
|
|
{
|
|
//--- Calculate distance
|
|
itemoffs=cwoffs+i*(nx+ny);
|
|
ptdist2=0;
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
v=cw[itemoffs+j]-x[j];
|
|
ptdist2+=v*v;
|
|
}
|
|
//--- Skip points if distance too large
|
|
if(ptdist2>=queryr2)
|
|
continue;
|
|
//--- Output
|
|
result++;
|
|
}
|
|
return(result);
|
|
}
|
|
//--- Simple split
|
|
if(kdnodes[rootidx]==0)
|
|
{
|
|
//--- Load:
|
|
//--- * D dimension to split
|
|
//--- * Split split position
|
|
//--- * ChildLE, ChildGE - indexes of childs
|
|
d=kdnodes[rootidx+1];
|
|
split=kdsplits[kdnodes[rootidx+2]];
|
|
childle=kdnodes[rootidx+3];
|
|
childge=kdnodes[rootidx+4];
|
|
//--- Navigate through childs
|
|
for(int i=0; i<=1; i++)
|
|
{
|
|
//--- Select child to process:
|
|
//--- * ChildOffs current child offset in Nodes[]
|
|
//--- * UpdateMin whether minimum or maximum value
|
|
//--- of bounding box is changed on update
|
|
updatemin=i!=0;
|
|
if(i==0)
|
|
childoffs=childle;
|
|
else
|
|
childoffs=childge;
|
|
//--- Update bounding box and current distance
|
|
prevdist2=buf.m_curdist2;
|
|
t1=x[d];
|
|
if(updatemin)
|
|
{
|
|
v=buf.m_curboxmin[d];
|
|
if(t1<=split)
|
|
buf.m_curdist2-=CMath::Sqr(MathMax(v-t1,0))+CMath::Sqr(split-t1);
|
|
buf.m_curboxmin.Set(d,split);
|
|
}
|
|
else
|
|
{
|
|
v=buf.m_curboxmax[d];
|
|
if(t1>=split)
|
|
buf.m_curdist2-=CMath::Sqr(MathMax(t1-v,0))+CMath::Sqr(t1-split);
|
|
buf.m_curboxmax.Set(d,split);
|
|
}
|
|
//--- Decide: to dive into cell or not to dive
|
|
if(buf.m_curdist2<queryr2)
|
|
result+=PartialCountRec(kdnodes,kdsplits,cw,nx,ny,buf,childoffs,queryr2,x);
|
|
//--- Restore bounding box and distance
|
|
if(updatemin)
|
|
buf.m_curboxmin.Set(d,v);
|
|
else
|
|
buf.m_curboxmax.Set(d,v);
|
|
buf.m_curdist2=prevdist2;
|
|
}
|
|
return(result);
|
|
}
|
|
//--- Integrity failure
|
|
CAp::Assert(false,"PartialCountRec: integrity check failed");
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs partial (for just one subtree of |
|
|
//| multi-tree) unpack for RBF model. It appends center coordinates, |
|
|
//| weights and per-dimension radii (according to current scaling) |
|
|
//| to preallocated output array. |
|
|
//| INPUT PARAMETERS: |
|
|
//| kdNodes, kdSplits, CW, S, NX, NY - corresponding fields of V2 |
|
|
//| model |
|
|
//| RootIdx - offset of partial kd-tree |
|
|
//| R - radius for current partial tree |
|
|
//| XWR - preallocated output buffer; it is caller's |
|
|
//| responsibility to make sure that XWR has enough |
|
|
//| space. First K rows are already occupied. |
|
|
//| K - number of already occupied rows in XWR. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XWR - updated XWR |
|
|
//| K - updated rows count |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::PartialUnpackRec(CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw,CRowDouble &s,int nx,
|
|
int ny,int rootidx,double r,
|
|
CMatrixDouble &xwr,int &k)
|
|
{
|
|
//--- create variables
|
|
int childle=0;
|
|
int childge=0;
|
|
int itemoffs=0;
|
|
int cwoffs=0;
|
|
int cwcnt=0;
|
|
//--- Leaf node.
|
|
if(kdnodes[rootidx]>0)
|
|
{
|
|
cwcnt=kdnodes[rootidx+0];
|
|
cwoffs=kdnodes[rootidx+1];
|
|
for(int i=0; i<cwcnt; i++)
|
|
{
|
|
itemoffs=cwoffs+i*(nx+ny);
|
|
for(int j=0; j<nx+ny; j++)
|
|
xwr.Set(k,j,cw[itemoffs+j]);
|
|
for(int j=0; j<nx; j++)
|
|
xwr.Mul(k,j,s[j]);
|
|
for(int j=0; j<nx; j++)
|
|
xwr.Set(k,nx+ny+j,r*s[j]);
|
|
k++;
|
|
}
|
|
return;
|
|
}
|
|
//--- Simple split
|
|
if(kdnodes[rootidx]==0)
|
|
{
|
|
//--- Load:
|
|
//--- * ChildLE, ChildGE - indexes of childs
|
|
childle=kdnodes[rootidx+3];
|
|
childge=kdnodes[rootidx+4];
|
|
//--- Process both parts of split
|
|
PartialUnpackRec(kdnodes,kdsplits,cw,s,nx,ny,childle,r,xwr,k);
|
|
PartialUnpackRec(kdnodes,kdsplits,cw,s,nx,ny,childge,r,xwr,k);
|
|
return;
|
|
}
|
|
//--- Integrity failure
|
|
CAp::Assert(false,"PartialUnpackRec: integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns size of design matrix row for evaluation |
|
|
//| point X0, given: |
|
|
//| * query radius multiplier (either RBFV2NearRadius() or |
|
|
//| RBFV2FarRadius()) |
|
|
//| * hierarchy level: value in [0, NH) for single-level model, or |
|
|
//| negative value for multilevel model (all levels of hierarchy |
|
|
//| in single matrix, like one used by nonnegative RBF) |
|
|
//| INPUT PARAMETERS: |
|
|
//| kdNodes, kdSplits, |
|
|
//| CW, Ri, kdRoots, |
|
|
//| kdBoxMin, kdBoxMax, |
|
|
//| NX, NY, NH - corresponding fields of V2 model |
|
|
//| Level - value in [0, NH) for single-level |
|
|
//| design matrix, negative value for |
|
|
//| multilevel design matrix |
|
|
//| RCoeff - radius coefficient, either |
|
|
//| RBFV2NearRadius() or RBFV2FarRadius() |
|
|
//| X0 - query point |
|
|
//| CalcBuf - buffer for PreparePartialQuery(), |
|
|
//| allocated by caller |
|
|
//| RESULT: |
|
|
//| row size |
|
|
//+------------------------------------------------------------------+
|
|
int CRBFV2::DesignMatrixRowSize(CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw,CRowDouble &ri,
|
|
CRowInt &kdroots,CRowDouble &kdboxmin,
|
|
CRowDouble &kdboxmax,int nx,int ny,
|
|
int nh,int level,double rcoeff,
|
|
CRowDouble &x0,CRBFV2CalcBuffer &calcbuf)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int dummy=0;
|
|
int level0=0;
|
|
int level1=0;
|
|
double curradius2=0;
|
|
//--- check
|
|
if(!CAp::Assert(nh>0,__FUNCTION__+": integrity failure"))
|
|
return (0);
|
|
if(level>=0)
|
|
{
|
|
level0=level;
|
|
level1=level;
|
|
}
|
|
else
|
|
{
|
|
level0=0;
|
|
level1=nh-1;
|
|
}
|
|
|
|
result=0;
|
|
for(int levelidx=level0; levelidx<=level1; levelidx++)
|
|
{
|
|
curradius2=CMath::Sqr(ri[levelidx]*rcoeff);
|
|
PreparePartialQuery(x0,kdboxmin,kdboxmax,nx,calcbuf,dummy);
|
|
result+=PartialCountRec(kdnodes,kdsplits,cw,nx,ny,calcbuf,kdroots[levelidx],curradius2,x0);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates design matrix row for evaluation point |
|
|
//| X0, given: |
|
|
//| * query radius multiplier (either RBFV2NearRadius() or |
|
|
//| RBFV2FarRadius()) |
|
|
//| * hierarchy level: value in [0, NH) for single-level model, or |
|
|
//| negative value for multilevel model (all levels of hierarchy |
|
|
//| in single matrix, like one used by nonnegative RBF) |
|
|
//| INPUT PARAMETERS: |
|
|
//| kdNodes, kdSplits, |
|
|
//| CW, Ri, kdRoots, |
|
|
//| kdBoxMin, kdBoxMax, |
|
|
//| NX, NY, NH - corresponding fields of V2 model |
|
|
//| CWRange - internal array[NH + 1] used by RBF |
|
|
//| construction function, stores ranges of|
|
|
//| CW occupied by NH trees. |
|
|
//| Level - value in [0, NH) for single-level |
|
|
//| design matrix, negative value for |
|
|
//| multilevel design matrix |
|
|
//| BF - basis function type |
|
|
//| RCoeff - radius coefficient, either |
|
|
//| RBFV2NearRadius() or RBFV2FarRadius() |
|
|
//| RowsPerPoint - equal to: |
|
|
//| * 1 for unpenalized regression model |
|
|
//| * 1 + NX for basic form of nonsmoothness penalty|
|
|
//| Penalty - nonsmoothness penalty coefficient |
|
|
//| X0 - query point |
|
|
//| CalcBuf - buffer for PreparePartialQuery(), |
|
|
//| allocated by caller |
|
|
//| R2 - preallocated temporary buffer, size is |
|
|
//| at least NPoints; it is caller's |
|
|
//| responsibility to make sure that R2 has|
|
|
//| enough space. |
|
|
//| Offs - preallocated temporary buffer; size is |
|
|
//| at least NPoints; it is caller's |
|
|
//| responsibility to make sure that Offs |
|
|
//| has enough space. |
|
|
//| K - MUST BE ZERO ON INITIAL CALL. This |
|
|
//| variable is incremented, not set. So, |
|
|
//| any no-zero value will result in the |
|
|
//| incorrect points count being returned. |
|
|
//| RowIdx - preallocated array, at least RowSize |
|
|
//| elements |
|
|
//| RowVal - preallocated array, at least |
|
|
//| RowSize*RowsPerPoint elements |
|
|
//| RESULT: |
|
|
//| RowIdx - RowSize elements are filled with column|
|
|
//| indexes of non-zero design matrix |
|
|
//| entries |
|
|
//| RowVal - RowSize*RowsPerPoint elements are |
|
|
//| filled with design matrix values, with |
|
|
//| column RowIdx[0] being stored in first |
|
|
//| RowsPerPoint elements of RowVal, column|
|
|
//| RowIdx[1] being stored in next |
|
|
//| RowsPerPoint elements, and so on. First|
|
|
//| element in contiguous set of |
|
|
//| RowsPerPoint elements corresponds to |
|
|
//| RowSize - number of columns per row |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::DesignMatrixGenerateRow(CRowInt &kdnodes,CRowDouble &kdsplits,
|
|
CRowDouble &cw,CRowDouble &ri,
|
|
CRowInt &kdroots,CRowDouble &kdboxmin,
|
|
CRowDouble &kdboxmax,CRowInt &cwrange,
|
|
int nx,int ny,int nh,int level,
|
|
int bf,double rcoeff,int rowsperpoint,
|
|
double penalty,CRowDouble &x0,
|
|
CRBFV2CalcBuffer &calcbuf,
|
|
CRowDouble &tmpr2,
|
|
CRowInt &tmpoffs,
|
|
CRowInt &rowidx,
|
|
CRowDouble &rowval,
|
|
int &rowsize)
|
|
{
|
|
//--- create variables
|
|
int cnt=0;
|
|
int level0=0;
|
|
int level1=0;
|
|
double invri2=0;
|
|
double curradius2=0;
|
|
double val=0;
|
|
double dval=0;
|
|
double d2val=0;
|
|
|
|
rowsize=0;
|
|
//--- check
|
|
if(!CAp::Assert(nh>0,__FUNCTION__+": integrity failure (a)"))
|
|
return;
|
|
if(!CAp::Assert(rowsperpoint==1 || rowsperpoint==1+nx,__FUNCTION__+": integrity failure (b)"))
|
|
return;
|
|
|
|
if(level>=0)
|
|
{
|
|
level0=level;
|
|
level1=level;
|
|
}
|
|
else
|
|
{
|
|
level0=0;
|
|
level1=nh-1;
|
|
}
|
|
|
|
for(int levelidx=level0; levelidx<=level1; levelidx++)
|
|
{
|
|
curradius2=CMath::Sqr(ri[levelidx]*rcoeff);
|
|
invri2=1/CMath::Sqr(ri[levelidx]);
|
|
PreparePartialQuery(x0,kdboxmin,kdboxmax,nx,calcbuf,cnt);
|
|
PartialQueryRec(kdnodes,kdsplits,cw,nx,ny,calcbuf,kdroots[levelidx],curradius2,x0,tmpr2,tmpoffs,cnt);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(tmpr2)>=cnt,__FUNCTION__+": integrity failure (c)"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(tmpoffs)>=cnt,__FUNCTION__+": integrity failure (d)"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(rowidx)>=rowsize+cnt,__FUNCTION__+": integrity failure (e)"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(rowval)>=rowsperpoint*(rowsize+cnt),__FUNCTION__+": integrity failure (f)"))
|
|
return;
|
|
|
|
for(int j=0; j<cnt; j++)
|
|
{
|
|
//--- Generate element corresponding to fitting error.
|
|
//--- Store derivative information which may be required later.
|
|
if(!CAp::Assert((tmpoffs[j]-cwrange[level0])%(nx+ny)==0,__FUNCTION__+": integrity failure (g)"))
|
|
return;
|
|
RBFV2BasisFuncDiff2(bf,tmpr2[j]*invri2,val,dval,d2val);
|
|
rowidx.Set(rowsize+j,(tmpoffs[j]-cwrange[level0])/(nx+ny));
|
|
rowval.Set((rowsize+j)*rowsperpoint+0,val);
|
|
if(rowsperpoint==1)
|
|
continue;
|
|
//--- Generate elements corresponding to nonsmoothness penalty
|
|
if(!CAp::Assert(rowsperpoint==1+nx,__FUNCTION__+": integrity failure (h)"))
|
|
return;
|
|
for(int k=0; k<nx; k++)
|
|
rowval.Set((rowsize+j)*rowsperpoint+1+k,penalty*(dval*2*invri2+d2val*CMath::Sqr(2*(x0[k]-cw[tmpoffs[j]+k])*invri2)));
|
|
}
|
|
//--- Update columns counter
|
|
rowsize+=cnt;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fills RBF model by zeros. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV2::ZeroFill(CRBFV2Model &s,int nx,int ny,int bf)
|
|
{
|
|
s.m_bf=bf;
|
|
s.m_nh=0;
|
|
s.m_ri.Resize(0);
|
|
s.m_s.Resize(0);
|
|
s.m_kdroots.Resize(0);
|
|
s.m_kdnodes.Resize(0);
|
|
s.m_kdsplits.Resize(0);
|
|
s.m_kdboxmin.Resize(0);
|
|
s.m_kdboxmax.Resize(0);
|
|
s.m_cw.Resize(0);
|
|
s.m_v=matrix<double>::Zeros(ny,nx+1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object for parallel evaluation on the model matrix |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBF3EvaluatorBuffer
|
|
{
|
|
CRowDouble m_coeffbuf;
|
|
CRowDouble m_df1;
|
|
CRowDouble m_df2;
|
|
CRowDouble m_funcbuf;
|
|
CRowDouble m_mindist2;
|
|
CRowDouble m_wrkbuf;
|
|
CRowDouble m_x;
|
|
CMatrixDouble m_deltabuf;
|
|
//--- constructor / destructor
|
|
CRBF3EvaluatorBuffer(void) {}
|
|
~CRBF3EvaluatorBuffer(void) {}
|
|
//---
|
|
void Copy(const CRBF3EvaluatorBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBF3EvaluatorBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF3EvaluatorBuffer::Copy(const CRBF3EvaluatorBuffer &obj)
|
|
{
|
|
m_coeffbuf=obj.m_coeffbuf;
|
|
m_df1=obj.m_df1;
|
|
m_df2=obj.m_df2;
|
|
m_funcbuf=obj.m_funcbuf;
|
|
m_mindist2=obj.m_mindist2;
|
|
m_wrkbuf=obj.m_wrkbuf;
|
|
m_x=obj.m_x;
|
|
m_deltabuf=obj.m_deltabuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Model evaluator: |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBF3Evaluator
|
|
{
|
|
int m_chunksize;
|
|
int m_functype;
|
|
int m_n;
|
|
int m_nx;
|
|
int m_storagetype;
|
|
double m_funcparam;
|
|
CRowInt m_entireset;
|
|
CRowDouble m_chunk1;
|
|
CRBF3EvaluatorBuffer m_bufferpool;
|
|
CMatrixDouble m_f;
|
|
CMatrixDouble m_x;
|
|
CMatrixDouble m_xtchunked;
|
|
//--- constructor / destructor
|
|
CRBF3Evaluator(void);
|
|
~CRBF3Evaluator(void) {}
|
|
//---
|
|
void Copy(const CRBF3Evaluator&obj);
|
|
//--- overloading
|
|
void operator=(const CRBF3Evaluator&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBF3Evaluator::CRBF3Evaluator(void)
|
|
{
|
|
m_chunksize=0;
|
|
m_functype=0;
|
|
m_n=0;
|
|
m_nx=0;
|
|
m_storagetype=0;
|
|
m_funcparam=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF3Evaluator::Copy(const CRBF3Evaluator &obj)
|
|
{
|
|
m_chunksize=obj.m_chunksize;
|
|
m_functype=obj.m_functype;
|
|
m_n=obj.m_n;
|
|
m_nx=obj.m_nx;
|
|
m_storagetype=obj.m_storagetype;
|
|
m_funcparam=obj.m_funcparam;
|
|
m_entireset=obj.m_entireset;
|
|
m_chunk1=obj.m_chunk1;
|
|
m_f=obj.m_f;
|
|
m_x=obj.m_x;
|
|
m_xtchunked=obj.m_xtchunked;
|
|
m_bufferpool=obj.m_bufferpool;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform evaluation requests in the|
|
|
//| multithreaded mode(multiple threads working with same RBF object)|
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV3CalcBuffer
|
|
{
|
|
CRowDouble m_x123;
|
|
CRowDouble m_x;
|
|
CRowDouble m_xg;
|
|
CRowDouble m_y123;
|
|
CRowDouble m_yg;
|
|
CRBF3EvaluatorBuffer m_evalbuf;
|
|
//--- constructor / destructor
|
|
CRBFV3CalcBuffer(void) {}
|
|
~CRBFV3CalcBuffer(void) {}
|
|
//--- copy
|
|
void Copy(const CRBFV3CalcBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV3CalcBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3CalcBuffer::Copy(const CRBFV3CalcBuffer &obj)
|
|
{
|
|
m_x123=obj.m_x123;
|
|
m_x=obj.m_x;
|
|
m_xg=obj.m_xg;
|
|
m_y123=obj.m_y123;
|
|
m_yg=obj.m_yg;
|
|
m_evalbuf=obj.m_evalbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Temporary buffers used by divide-and-conquer ACBF preconditioner.|
|
|
//| This structure is initialized at the beginning of DC procedure |
|
|
//| and put into shared pool. Basecase handling routine retrieves it |
|
|
//| from the bool and returns back. |
|
|
//| Following fields can be used: |
|
|
//| * bFlags - boolean array[N], all values are set to False on|
|
|
//| the retrieval, and MUST be False when the buffer|
|
|
//| is returned to the pool |
|
|
//| * KDTBuf - KD-tree request buffer for thread-safe requests |
|
|
//| * KDT1Buf, KDT2Buf - buffers for simplified KD-trees |
|
|
//| Additional preallocated temporaries are provided: |
|
|
//| * tmpBoxMin - array[NX], no special properties |
|
|
//| * tmpBoxMax - array[NX], no special properties |
|
|
//| * TargetNodes - dynamically resized as needed |
|
|
//+------------------------------------------------------------------+
|
|
struct CACBFBuffer
|
|
{
|
|
bool m_bflags[];
|
|
CRowInt m_chosenneighbors;
|
|
CRowInt m_currentnodes;
|
|
CRowInt m_neighbors;
|
|
CRowInt m_perm;
|
|
CRowDouble m_choltmp;
|
|
CRowDouble m_d;
|
|
CRowDouble m_tau;
|
|
CRowDouble m_tmpboxmax;
|
|
CRowDouble m_tmpboxmin;
|
|
CRowDouble m_y;
|
|
CRowDouble m_z;
|
|
CMatrixDouble m_atwrk;
|
|
CMatrixDouble m_b;
|
|
CMatrixDouble m_c;
|
|
CMatrixDouble m_q1;
|
|
CMatrixDouble m_q;
|
|
CMatrixDouble m_r;
|
|
CMatrixDouble m_wrkq;
|
|
CMatrixDouble m_xq;
|
|
CKDTreeRequestBuffer m_kdt1buf;
|
|
CKDTreeRequestBuffer m_kdt2buf;
|
|
CKDTreeRequestBuffer m_kdtbuf;
|
|
//--- constructor / destructor
|
|
CACBFBuffer(void) {}
|
|
~CACBFBuffer(void) {}
|
|
//--- copy
|
|
void Copy(const CACBFBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CACBFBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CACBFBuffer::Copy(const CACBFBuffer &obj)
|
|
{
|
|
ArrayCopy(m_bflags,obj.m_bflags);
|
|
m_chosenneighbors=obj.m_chosenneighbors;
|
|
m_currentnodes=obj.m_currentnodes;
|
|
m_neighbors=obj.m_neighbors;
|
|
m_perm=obj.m_perm;
|
|
m_choltmp=obj.m_choltmp;
|
|
m_d=obj.m_d;
|
|
m_tau=obj.m_tau;
|
|
m_tmpboxmax=obj.m_tmpboxmax;
|
|
m_tmpboxmin=obj.m_tmpboxmin;
|
|
m_y=obj.m_y;
|
|
m_z=obj.m_z;
|
|
m_atwrk=obj.m_atwrk;
|
|
m_b=obj.m_b;
|
|
m_c=obj.m_c;
|
|
m_q1=obj.m_q1;
|
|
m_q=obj.m_q;
|
|
m_r=obj.m_r;
|
|
m_wrkq=obj.m_wrkq;
|
|
m_xq=obj.m_xq;
|
|
m_kdt1buf=obj.m_kdt1buf;
|
|
m_kdt2buf=obj.m_kdt2buf;
|
|
m_kdtbuf=obj.m_kdtbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Several rows of the ACBF preconditioner |
|
|
//+------------------------------------------------------------------+
|
|
struct CACBFChunk
|
|
{
|
|
int m_ntargetcols;
|
|
int m_ntargetrows;
|
|
CRowInt m_targetcols;
|
|
CRowInt m_targetrows;
|
|
CMatrixDouble m_s;
|
|
//--- constructor / destructor
|
|
CACBFChunk(void);
|
|
~CACBFChunk(void) {}
|
|
//--- copy
|
|
void Copy(const CACBFChunk&obj);
|
|
//--- overloading
|
|
void operator=(const CACBFChunk&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CACBFChunk::CACBFChunk(void)
|
|
{
|
|
m_ntargetcols=0;
|
|
m_ntargetrows=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CACBFChunk::Copy(const CACBFChunk &obj)
|
|
{
|
|
m_ntargetcols=obj.m_ntargetcols;
|
|
m_ntargetrows=obj.m_ntargetrows;
|
|
m_targetcols=obj.m_targetcols;
|
|
m_targetrows=obj.m_targetrows;
|
|
m_s=obj.m_s;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Approximate Cardinal Basis Function builder object |
|
|
//| Following fields store problem formulation: |
|
|
//| * NTotal - total points count in the dataset |
|
|
//| * NX - dimensions count |
|
|
//| * XX - array[NTotal, NX], points |
|
|
//| * FuncType - basis function type |
|
|
//| * FuncParam - basis function parameter |
|
|
//| * RoughDatasetDiameter - a rough upper bound on the dataset |
|
|
//| diameter |
|
|
//| Following global parameters are set: |
|
|
//| * NGlobal - global nodes count, >= 0 |
|
|
//| * GlobalGrid - global nodes |
|
|
//| * GlobalGridSeparation - maximum distance between any pair of |
|
|
//| grid nodes; also an upper bound on distance |
|
|
//| between any random point in the dataset and a |
|
|
//| nearest grid node |
|
|
//| * NLocal - number of nearest neighbors select for each |
|
|
//| node. |
|
|
//| * NCorrection - nodes count for each corrector layer |
|
|
//| * CorrectorGrowth - growth factor for corrector layer |
|
|
//| * BatchSize - batch size for ACBF construction |
|
|
//| * LambdaV - smoothing coefficient, LambdaV >= 0 |
|
|
//| * ATerm - linear term for basis functions: |
|
|
//| * 1 = linear polynomial(STRONGLY RECOMMENDED) |
|
|
//| * 2 = constant polynomial term (may break |
|
|
//| convergence for thin plate splines) |
|
|
//| * 3 = zero polynomial term (may break |
|
|
//| convergence for all types of splines) |
|
|
//| Following fields are initialized: |
|
|
//| * KDT - KD-tree search structure for the entire dataset |
|
|
//| * KDT1, KDT2 - simplified KD-trees (build with progressively |
|
|
//| sparsified dataset) |
|
|
//| * BufferPool - shared pool for ACBFBuffer instances |
|
|
//| * ChunksProducer - shared pool seeded with an instance of |
|
|
//| ACBFChunk object(several rows of the |
|
|
//| preconditioner) |
|
|
//| * ChunksPool - shared pool that contains computed |
|
|
//| preconditioner chunks as recycled entries |
|
|
//| Temporaries: |
|
|
//| * WrkIdx |
|
|
//+------------------------------------------------------------------+
|
|
struct CACBFBuilder
|
|
{
|
|
int m_aterm;
|
|
int m_batchsize;
|
|
int m_functype;
|
|
int m_ncorrection;
|
|
int m_nglobal;
|
|
int m_nlocal;
|
|
int m_ntotal;
|
|
int m_nx;
|
|
double m_correctorgrowth;
|
|
double m_funcparam;
|
|
double m_globalgridseparation;
|
|
double m_lambdav;
|
|
double m_roughdatasetdiameter;
|
|
bool m_dodetailedtrace;
|
|
CRowInt m_globalgrid;
|
|
CRowInt m_wrkidx;
|
|
CMatrixDouble m_xx;
|
|
CKDTree m_kdt1;
|
|
CKDTree m_kdt2;
|
|
CKDTree m_kdt;
|
|
CACBFChunk m_chunkspool;
|
|
CACBFChunk m_chunksproducer;
|
|
CACBFBuffer m_bufferpool;
|
|
//--- constructor / destructor
|
|
CACBFBuilder(void);
|
|
~CACBFBuilder(void) {}
|
|
//--- copy
|
|
void Copy(const CACBFBuilder&obj);
|
|
//--- overloading
|
|
void operator=(const CACBFBuilder&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CACBFBuilder::CACBFBuilder(void)
|
|
{
|
|
m_aterm=0;
|
|
m_batchsize=0;
|
|
m_functype=0;
|
|
m_ncorrection=0;
|
|
m_nglobal=0;
|
|
m_nlocal=0;
|
|
m_ntotal=0;
|
|
m_nx=0;
|
|
m_correctorgrowth=0;
|
|
m_funcparam=0;
|
|
m_globalgridseparation=0;
|
|
m_lambdav=0;
|
|
m_roughdatasetdiameter=0;
|
|
m_dodetailedtrace=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CACBFBuilder::Copy(const CACBFBuilder &obj)
|
|
{
|
|
m_aterm=obj.m_aterm;
|
|
m_batchsize=obj.m_batchsize;
|
|
m_functype=obj.m_functype;
|
|
m_ncorrection=obj.m_ncorrection;
|
|
m_nglobal=obj.m_nglobal;
|
|
m_nlocal=obj.m_nlocal;
|
|
m_ntotal=obj.m_ntotal;
|
|
m_nx=obj.m_nx;
|
|
m_correctorgrowth=obj.m_correctorgrowth;
|
|
m_funcparam=obj.m_funcparam;
|
|
m_globalgridseparation=obj.m_globalgridseparation;
|
|
m_lambdav=obj.m_lambdav;
|
|
m_roughdatasetdiameter=obj.m_roughdatasetdiameter;
|
|
m_dodetailedtrace=obj.m_dodetailedtrace;
|
|
m_globalgrid=obj.m_globalgrid;
|
|
m_wrkidx=obj.m_wrkidx;
|
|
m_xx=obj.m_xx;
|
|
m_kdt1=obj.m_kdt1;
|
|
m_kdt2=obj.m_kdt2;
|
|
m_kdt=obj.m_kdt;
|
|
m_chunkspool=obj.m_chunkspool;
|
|
m_chunksproducer=obj.m_chunksproducer;
|
|
m_bufferpool=obj.m_bufferpool;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Temporary buffers used by divide-and-conquer DDM solver |
|
|
//| This structure is initialized at the beginning of DC procedure |
|
|
//| and put into shared pool. Basecase handling routine retrieves it |
|
|
//| from the pool and returns back. |
|
|
//| Following fields can be used: |
|
|
//| * bFlags - boolean array[N], all values are set to False on|
|
|
//| the retrieval, and MUST be False when the buffer|
|
|
//| is returned to the pool |
|
|
//| * KDTBuf - KD-tree request buffer for thread-safe requests |
|
|
//| Additional preallocated temporaries are provided: |
|
|
//| * Idx2PrecCol - integer array[N + NX + 1], no special |
|
|
//| properties |
|
|
//| * tmpBoxMin - array[NX], no special properties |
|
|
//| * tmpBoxMax - array[NX], no special properties |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBF3DDMBuffer
|
|
{
|
|
bool m_bflags[];
|
|
CKDTreeRequestBuffer m_kdtbuf;
|
|
CRowInt m_idx2preccol;
|
|
CRowDouble m_tmpboxmax;
|
|
CRowDouble m_tmpboxmin;
|
|
//--- constructor / destructor
|
|
CRBF3DDMBuffer(void) {}
|
|
~CRBF3DDMBuffer(void) {}
|
|
//--- copy
|
|
void Copy(const CRBF3DDMBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBF3DDMBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF3DDMBuffer::Copy(const CRBF3DDMBuffer &obj)
|
|
{
|
|
ArrayCopy(m_bflags,obj.m_bflags);
|
|
m_kdtbuf=obj.m_kdtbuf;
|
|
m_idx2preccol=obj.m_idx2preccol;
|
|
m_tmpboxmax=obj.m_tmpboxmax;
|
|
m_tmpboxmin=obj.m_tmpboxmin;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Subproblem for DDM algorithm, stores precomputed factorization |
|
|
//| and other information. |
|
|
//| Following fields are set during construction: |
|
|
//| * IsValid - whether instance is valid subproblem or not |
|
|
//| * NTarget - number of target nodes in the subproblem, |
|
|
//| NTarget >= 1 |
|
|
//| * TargetNodes - array containing target node indexes |
|
|
//| * NWork - number of working nodes in the subproblem, |
|
|
//| NWork >= NTarget |
|
|
//| * WorkingNodes - array containing working node indexes |
|
|
//| * RegSystem - smoothed (regularized) working system |
|
|
//| * Decomposition - decomposition type: |
|
|
//| * 0 for LU |
|
|
//| * 1 for regularized QR |
|
|
//| * WrkLU - NWork*NWork sized LU factorization of the |
|
|
//| subproblem |
|
|
//| * WrkP - pivots for the LU decomposition |
|
|
//| * WrkQ, WrkR - NWork*NWork sized matrices, factors of QR |
|
|
//| decomposition of RegSystem. Due to |
|
|
//| regularization rows added, the Q factor is |
|
|
//| actually an 2NWork * NWork matrix, but in |
|
|
//| order to solve the system we need only |
|
|
//| leading NWork rows, so the rest is not stored|
|
|
//+------------------------------------------------------------------+
|
|
struct CRBF3DDMSubproblem
|
|
{
|
|
int m_decomposition;
|
|
int m_ntarget;
|
|
int m_nwork;
|
|
bool m_isvalid;
|
|
CRowInt m_targetnodes;
|
|
CRowInt m_workingnodes;
|
|
CRowInt m_wrkp;
|
|
CMatrixDouble m_pred;
|
|
CMatrixDouble m_qtrhs;
|
|
CMatrixDouble m_regsystem;
|
|
CMatrixDouble m_rhs;
|
|
CMatrixDouble m_sol;
|
|
CMatrixDouble m_wrklu;
|
|
CMatrixDouble m_wrkq;
|
|
CMatrixDouble m_wrkr;
|
|
//--- constructor / destructor
|
|
CRBF3DDMSubproblem(void);
|
|
~CRBF3DDMSubproblem(void) {}
|
|
//--- copy
|
|
void Copy(const CRBF3DDMSubproblem&obj);
|
|
//--- overloading
|
|
void operator=(const CRBF3DDMSubproblem&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBF3DDMSubproblem::CRBF3DDMSubproblem(void)
|
|
{
|
|
m_decomposition=0;
|
|
m_ntarget=0;
|
|
m_nwork=0;
|
|
m_isvalid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF3DDMSubproblem::Copy(const CRBF3DDMSubproblem &obj)
|
|
{
|
|
m_decomposition=obj.m_decomposition;
|
|
m_ntarget=obj.m_ntarget;
|
|
m_nwork=obj.m_nwork;
|
|
m_isvalid=obj.m_isvalid;
|
|
m_targetnodes=obj.m_targetnodes;
|
|
m_workingnodes=obj.m_workingnodes;
|
|
m_wrkp=obj.m_wrkp;
|
|
m_pred=obj.m_pred;
|
|
m_qtrhs=obj.m_qtrhs;
|
|
m_regsystem=obj.m_regsystem;
|
|
m_rhs=obj.m_rhs;
|
|
m_sol=obj.m_sol;
|
|
m_wrklu=obj.m_wrklu;
|
|
m_wrkq=obj.m_wrkq;
|
|
m_wrkr=obj.m_wrkr;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| DDM solver |
|
|
//| Following fields store information about problem: |
|
|
//| LambdaV - smoothing coefficient |
|
|
//| Following fields related to DDM part are present: |
|
|
//| SubproblemsCnt - number of subproblems created, |
|
|
//| SubproblemCnt >= 1 |
|
|
//| SubproblemsPool - shared pool seeded with instance of |
|
|
//| RBFV3DDMSubproblem class (default seed has |
|
|
//| Seed.IsValid = False). It also contains exactly |
|
|
//| SubproblemCnt subproblem instances as recycled |
|
|
//| entries, each of these instances has |
|
|
//| Seed.IsValid = True and contains a partition of |
|
|
//| the complete problem into subproblems and |
|
|
//| precomputed factorization |
|
|
//| SubproblemsBuffer - shared pool seeded with instance of |
|
|
//| RBFV3DDMSubproblem class (default seed has |
|
|
//| Seed.IsValid = False). Contains no recycled |
|
|
//| entries, should be used just for temporary |
|
|
//| storage of the already processed subproblems. |
|
|
//| Following fields store information about corrector spline: |
|
|
//| NCorrector - corrector nodes count, NCorrector > 0 |
|
|
//| CorrQ - Q factor from the QR decomposition of the |
|
|
//| corrector linear system, |
|
|
//| array[NCorrector, NCorrector] |
|
|
//| CorrR - R factor from the QR decomposition of the |
|
|
//| corrector linear system, |
|
|
//| array[NCorrector, NCorrector] |
|
|
//| CorrNodes - array[NCorrector], indexes of dataset nodes |
|
|
//| chosen for the corrector spline |
|
|
//| CorrX - array[NCorrector, NX], dataset points |
|
|
//| Following fields store information that is used for logging and |
|
|
//| testing: |
|
|
//| CntLU - number of subproblems solved with LU (well |
|
|
//| conditioned) |
|
|
//| CntRegQR - number of subproblems solved with Reg-QR (badly |
|
|
//| conditioned) |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBF3DDMSolver
|
|
{
|
|
int m_cntlu;
|
|
int m_cntregqr;
|
|
int m_ncorrector;
|
|
int m_subproblemscnt;
|
|
double m_lambdav;
|
|
CRowInt m_corrnodes;
|
|
CRBF3DDMSubproblem m_subproblemsbuffer;
|
|
CRBF3DDMSubproblem m_subproblemspool;
|
|
CRBF3DDMBuffer m_bufferpool;
|
|
CMatrixDouble m_corrq;
|
|
CMatrixDouble m_corrr;
|
|
CMatrixDouble m_corrx;
|
|
CMatrixDouble m_tmpres1;
|
|
CMatrixDouble m_tmpupd1;
|
|
CKDTree m_kdt;
|
|
//--- constructor / destructor
|
|
CRBF3DDMSolver(void);
|
|
~CRBF3DDMSolver(void) {}
|
|
//--- copy
|
|
void Copy(const CRBF3DDMSolver&obj);
|
|
//--- overloading
|
|
void operator=(const CRBF3DDMSolver&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBF3DDMSolver::CRBF3DDMSolver(void)
|
|
{
|
|
m_cntlu=0;
|
|
m_cntregqr=0;
|
|
m_ncorrector=0;
|
|
m_subproblemscnt=0;
|
|
m_lambdav=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF3DDMSolver::Copy(const CRBF3DDMSolver &obj)
|
|
{
|
|
m_cntlu=obj.m_cntlu;
|
|
m_cntregqr=obj.m_cntregqr;
|
|
m_ncorrector=obj.m_ncorrector;
|
|
m_subproblemscnt=obj.m_subproblemscnt;
|
|
m_lambdav=obj.m_lambdav;
|
|
m_corrnodes=obj.m_corrnodes;
|
|
m_subproblemsbuffer=obj.m_subproblemsbuffer;
|
|
m_subproblemspool=obj.m_subproblemspool;
|
|
m_bufferpool=obj.m_bufferpool;
|
|
m_corrq=obj.m_corrq;
|
|
m_corrr=obj.m_corrr;
|
|
m_corrx=obj.m_corrx;
|
|
m_tmpres1=obj.m_tmpres1;
|
|
m_tmpupd1=obj.m_tmpupd1;
|
|
m_kdt=obj.m_kdt;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF model. |
|
|
//| Never try to work with fields of this object directly - always |
|
|
//| use ALGLIB functions to use this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV3Model
|
|
{
|
|
int m_bftype;
|
|
int m_nc;
|
|
int m_nx;
|
|
int m_ny;
|
|
double m_bfparam;
|
|
bool m_dbgregqrusedforddm;
|
|
CRowInt m_pointindexes;
|
|
CRowDouble m_cw;
|
|
CRowDouble m_s;
|
|
CRBFV3CalcBuffer m_calcbuf;
|
|
CRBF3Evaluator m_evaluator;
|
|
CMatrixDouble m_v;
|
|
CMatrixDouble m_wchunked;
|
|
//--- constructor / destructor
|
|
CRBFV3Model(void);
|
|
~CRBFV3Model(void) {}
|
|
//---
|
|
void Copy(const CRBFV3Model&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV3Model&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBFV3Model::CRBFV3Model(void)
|
|
{
|
|
m_bftype=0;
|
|
m_nc=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_bfparam=0;
|
|
m_dbgregqrusedforddm=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3Model::Copy(const CRBFV3Model &obj)
|
|
{
|
|
m_bftype=obj.m_bftype;
|
|
m_nc=obj.m_nc;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_bfparam=obj.m_bfparam;
|
|
m_dbgregqrusedforddm=obj.m_dbgregqrusedforddm;
|
|
m_pointindexes=obj.m_pointindexes;
|
|
m_cw=obj.m_cw;
|
|
m_s=obj.m_s;
|
|
m_calcbuf=obj.m_calcbuf;
|
|
m_evaluator=obj.m_evaluator;
|
|
m_v=obj.m_v;
|
|
m_wchunked=obj.m_wchunked;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF solution report: |
|
|
//| * TerminationType - termination type, positive values-success,|
|
|
//| non-positive - failure. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFV3Report
|
|
{
|
|
int m_iterationscount;
|
|
int m_terminationtype;
|
|
double m_maxerror;
|
|
double m_rmserror;
|
|
//--- constructor / destructor
|
|
CRBFV3Report(void) { ZeroMemory(this); }
|
|
~CRBFV3Report(void) {}
|
|
//--- copy
|
|
void Copy(const CRBFV3Report&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFV3Report&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3Report::Copy(const CRBFV3Report &obj)
|
|
{
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_rmserror=obj.m_rmserror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CRBFV3
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_epsred;
|
|
static const int m_maxddmits;
|
|
static const double m_polyharmonic2scale;
|
|
static const int m_acbfparallelthreshold;
|
|
static const int m_ddmparallelthreshold;
|
|
static const int m_bfparallelthreshold;
|
|
//---
|
|
static void RBFV3Create(int nx,int ny,int bf,double bfp,CRBFV3Model&s);
|
|
static void RBFV3CreateCalcBuffer(CRBFV3Model&s,CRBFV3CalcBuffer&buf);
|
|
static void RBFV3Build(CMatrixDouble&xraw,CMatrixDouble&yraw,int nraw,CRowDouble&scaleraw,int bftype,double bfparamraw,double lambdavraw,int aterm,CRBFV3Model&s,int &progress10000,bool&terminationrequest,CRBFV3Report&rep);
|
|
static void RBFV3Alloc(CSerializer&s,CRBFV3Model&model);
|
|
static void RBFV3Serialize(CSerializer&s,CRBFV3Model&model);
|
|
static void RBFV3Unserialize(CSerializer&s,CRBFV3Model&model);
|
|
static double RBFV3Calc1(CRBFV3Model&s,double x0);
|
|
static double RBFV3Calc2(CRBFV3Model&s,double x0,double x1);
|
|
static double RBFV3Calc3(CRBFV3Model&s,double x0,double x1,double x2);
|
|
static void RBFV3CalcBuf(CRBFV3Model&s,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV3TsCalcBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y);
|
|
static void RBFV3TsDiffBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFV3TSHessBuf(CRBFV3Model&s,CRBFV3CalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFV3GridCalcVX(CRBFV3Model&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y);
|
|
static void RBFV3Unpack(CRBFV3Model&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v);
|
|
|
|
private:
|
|
static void CreateFastEvaluator(CRBFV3Model&model);
|
|
static void GridCalcRec(CRBFV3Model&s,int simdwidth,int tileidx0,int tileidx1,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&x3,int n3,bool &flagy[],bool sparsey,CRowDouble&y,CRBFV3CalcBuffer &calcpool[]);
|
|
static void ZeroFill(CRBFV3Model&s,int nx,int ny);
|
|
static void AllocateCalcBuffer(CRBFV3Model&s,CRBFV3CalcBuffer&buf);
|
|
static void PreprocessDatasetRec(CMatrixDouble&xbuf,CMatrixDouble&ybuf,CRowInt&initidx,int wrk0,int wrk1,int nx,int ny,double mergetol,CRowDouble&tmpboxmin,CRowDouble&tmpboxmax,CMatrixDouble&xout,CMatrixDouble&yout,CRowInt&raw2wrkmap,CRowInt&wrk2rawmap,int &nout);
|
|
static void PreprocessDataSet(CMatrixDouble&xraw,double mergetol,CMatrixDouble&yraw,CRowDouble&xscaleraw,int nraw,int nx,int ny,int bftype,double bfparamraw,double lambdavraw,CMatrixDouble&xwrk,CMatrixDouble&ywrk,CRowInt&raw2wrkmap,CRowInt&wrk2rawmap,int &nwrk,CRowDouble&xscalewrk,CRowDouble&xshift,double&bfparamwrk,double&lambdavwrk,double&addxrescaleaplied);
|
|
static void SelectGlobalNodes(CMatrixDouble&xx,int n,int nx,CRowInt&existingnodes,int nexisting,int nspec,CRowInt&nodes,int &nchosen,double&maxdist);
|
|
static void BuildSimplifiedKDTree(CMatrixDouble&xx,int n,int nx,int reducefactor,int minsize,CKDTree&kdt);
|
|
static void ComputeTargetScatterDesignMatrices(CMatrixDouble&xx,int ntotal,int nx,int functype,double funcparam,CRowInt&workingnodes,int nwrk,CRowInt&scatternodes,int nscatter,CMatrixDouble&atwrk,CMatrixDouble&atsctr);
|
|
static void ComputeACBFPreconditionerBasecase(CACBFBuilder&builder,CACBFBuffer&buf,int wrk0,int wrk1);
|
|
static void ComputeACBFPreconditionerRecV2(CACBFBuilder&builder,int wrk0,int wrk1);
|
|
static void ComputeACBFPreconditioner(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,int aterm,int batchsize,int nglobal,int nlocal,int ncorrection,int correctorgrowth,int simplificationfactor,double lambdav,CSparseMatrix&sp);
|
|
static void DDMSolverInitBasecase(CRBF3DDMSolver&solver,CMatrixDouble&x,int n,int nx,CRBF3Evaluator&bfmatrix,double lambdav,CSparseMatrix&sp,CRBF3DDMBuffer&buf,CRowInt&tgtidx,int tgt0,int tgt1,int nneighbors,bool dodetailedtrace);
|
|
static void DDMSolverInitRec(CRBF3DDMSolver&solver,CMatrixDouble&x,int n,int nx,CRBF3Evaluator&bfmatrix,double lambdav,CSparseMatrix&sp,CRowInt&wrkidx,int wrk0,int wrk1,int nneighbors,int nbatch,bool dodetailedtrace);
|
|
static void DDMSolverInit(CMatrixDouble&x,double rescaledby,int n,int nx,CRBF3Evaluator&bfmatrix,int bftype,double bfparam,double lambdav,int aterm,CSparseMatrix&sp,int nneighbors,int nbatch,int ncorrector,bool dotrace,bool dodetailedtrace,CRBF3DDMSolver&solver,int &timeddminit,int &timecorrinit);
|
|
static void DDMSolverRunRec(CRBF3DDMSolver&solver,CMatrixDouble&res,int n,int nx,int ny,CMatrixDouble&c,int cnt);
|
|
static void DDMSolverRun(CRBF3DDMSolver&solver,CMatrixDouble&res,int n,int nx,int ny,CSparseMatrix&sp,CRBF3Evaluator&bfmatrix,CMatrixDouble&upd,int &timeddmsolve,int &timecorrsolve);
|
|
static void DDMSolverRun1(CRBF3DDMSolver&solver,CRowDouble&res,int n,int nx,CSparseMatrix&sp,CRBF3Evaluator&bfmatrix,CRowDouble&upd,int &timeddmsolve,int &timecorrsolve);
|
|
static double AutoDetectScaleParameter(CMatrixDouble&xx,int n,int nx);
|
|
static void ComputeBFMatrixRec(CMatrixDouble&xx,int range0,int range1,int n,int nx,int functype,double funcparam,CMatrixDouble&f);
|
|
static void ComputeBFMatrix(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,CMatrixDouble&f);
|
|
static void ModelMatrixInit(CMatrixDouble&xx,int n,int nx,int functype,double funcparam,int storagetype,CRBF3Evaluator&modelmatrix);
|
|
static void ModelMatrixComputePartial(CRBF3Evaluator&modelmatrix,CRowInt&ridx,int m0,CRowInt&cidx,int m1,CMatrixDouble&r);
|
|
static void ComputeRowChunk(CRBF3Evaluator&evaluator,CRowDouble&x,CRBF3EvaluatorBuffer&buf,int chunksize,int chunkidx,double distance0,int needgradinfo);
|
|
static void ModelMatrixComputeProductRec(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowInt&rowidx,CRowDouble&r,int idx0,int idx1,bool toplevelcall);
|
|
static void ModelMatrixComputeProduct(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowDouble&r);
|
|
static void ModelMatrixComputeProductAtNodes(CRBF3Evaluator&modelmatrix,CRowDouble&c,CRowInt&idx,int m,CRowDouble&r);
|
|
static bool IsCPDFunction(int functype,int aterm);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CRBFV3::m_epsred=0.999999;
|
|
const int CRBFV3::m_maxddmits=50;
|
|
const double CRBFV3::m_polyharmonic2scale=4.0;
|
|
const int CRBFV3::m_acbfparallelthreshold=512;
|
|
const int CRBFV3::m_ddmparallelthreshold=512;
|
|
const int CRBFV3::m_bfparallelthreshold=512;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates RBF model for a scalar(NY = 1) or vector |
|
|
//| (NY > 1) function in a NX - dimensional space(NX >= 1). |
|
|
//| INPUT PARAMETERS: |
|
|
//| NX - dimension of the space, NX >= 1 |
|
|
//| NY - function dimension, NY >= 1 |
|
|
//| BF - basis function type: |
|
|
//| * 1 for biharmonic/multiquadric |
|
|
//| f = sqrt(r ^ 2 + alpha ^ 2) |
|
|
//| (with f = r being a special case) |
|
|
//| * 2 for polyharmonic f = r ^ 2 * ln(r) |
|
|
//| BFP - basis function parameter: |
|
|
//| * BF = 0 parameter ignored |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - RBF model (initially equals to zero) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Create(int nx,int ny,int bf,double bfp,CRBFV3Model &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
|
|
return;
|
|
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
|
|
return;
|
|
if(!CAp::Assert(bf==1 || bf==2,__FUNCTION__+": unsupported basis function type"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(bfp) && bfp>=0.0,__FUNCTION__+": infinite or negative basis function parameter"))
|
|
return;
|
|
//--- Serializable parameters
|
|
s.m_nx=nx;
|
|
s.m_ny=ny;
|
|
s.m_bftype=bf;
|
|
s.m_bfparam=bfp;
|
|
s.m_nc=0;
|
|
s.m_s=vector<double>::Ones(nx);
|
|
s.m_v=matrix<double>::Zeros(ny,nx+1);
|
|
AllocateCalcBuffer(s,s.m_calcbuf);
|
|
//--- Debug counters
|
|
s.m_dbgregqrusedforddm=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel RBF model evaluations (with one RBF model |
|
|
//| instance being used from multiple threads, as long as different |
|
|
//| threads use different instances of buffer). |
|
|
//| This buffer object can be used with RBFTSCalcBuf() function (here|
|
|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
|
|
//| function which reuses previously allocated output space). |
|
|
//| How to use it: |
|
|
//| * create RBF model structure with RBFCreate() |
|
|
//| * load data, tune parameters |
|
|
//| * call RBFBuildModel() |
|
|
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
|
|
//| model (you should call this function only AFTER call to |
|
|
//| RBFBuildModel(), see below for more information) |
|
|
//| * call RBFTSCalcBuf() from different threads, with each thread |
|
|
//| working with its own copy of buffer object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with RBF model |
|
|
//| object which was used to initialize buffer. Any |
|
|
//| attempt to use buffer with different object is |
|
|
//| dangerous - you may get memory violation error because|
|
|
//| sizes of internal arrays do not fit to dimensions of |
|
|
//| RBF structure. |
|
|
//| IMPORTANT: you should call this function only for model which was|
|
|
//| built with RBFBuildModel() function, after successful |
|
|
//| invocation of RBFBuildModel(). Sizes of some internal |
|
|
//| structures are determined only after model is built, |
|
|
//| so buffer object created before model construction |
|
|
//| stage will be useless (and any attempt to use it will |
|
|
//| result in exception). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3CreateCalcBuffer(CRBFV3Model &s,CRBFV3CalcBuffer &buf)
|
|
{
|
|
AllocateCalcBuffer(s,buf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds hierarchical RBF model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N, S.NX], X - values |
|
|
//| Y - array[N, S.NY], Y - values |
|
|
//| ScaleVec - array[S.NX], vector of per-dimension scales |
|
|
//| N - points count |
|
|
//| BFtype - basis function type: |
|
|
//| * 1 for biharmonic spline f = r or multiquadric |
|
|
//| f = sqrt(r ^ 2 + param ^ 2) |
|
|
//| * 2 for thin plate spline f = r ^ 2 * ln(r) |
|
|
//| BFParam - for BFType = 1 zero value means biharmonic, nonzero|
|
|
//| means multiquadric ignored for BFType = 2 |
|
|
//| LambdaV - regularization parameter |
|
|
//| ATerm - polynomial term type: |
|
|
//| * 1 for linear term (STRONGLY RECOMMENDED) |
|
|
//| * 2 for constant term (may break convergence |
|
|
//| guarantees for thin plate splines) |
|
|
//| * 3 for zero term (may break convergence guarantees|
|
|
//| for all types of splines) |
|
|
//| S - RBF model, already initialized by RBFCreate() call.|
|
|
//| progress10000 - variable used for progress reports, it is |
|
|
//| regularly set to the current progress multiplied by|
|
|
//| 10000, in order to get value in [0, 10000] range. |
|
|
//| The rationale for such scaling is that it allows us|
|
|
//| to use integer type to store progress, which has |
|
|
//| less potential for non - atomic corruption on |
|
|
//| unprotected reads from another threads. |
|
|
//| You can read this variable from some other thread |
|
|
//| to get estimate of the current progress. Initial |
|
|
//| value of this variable is ignored, it is written by|
|
|
//| this function, but not read. |
|
|
//| terminationrequest - variable used for termination requests; |
|
|
//| its initial value must be False, and you can set it|
|
|
//| to True from some other thread. This routine |
|
|
//| regularly checks this variable and will terminate |
|
|
//| model construction shortly upon discovering that |
|
|
//| termination was requested. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - updated model (for rep.m_terminationtype > 0, |
|
|
//| unchanged otherwise) |
|
|
//| Rep - report: |
|
|
//| * Rep.TerminationType: |
|
|
//| * 1 - successful termination |
|
|
//| * 8 terminated by user via |
|
|
//| RBFRequestTermination() |
|
|
//| Fields are used for debugging purposes: |
|
|
//| * Rep.IterationsCount - iterations count of the GMRES solver |
|
|
//| NOTE: failure to build model will leave current State of the |
|
|
//| structure unchanged. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Build(CMatrixDouble &xraw,
|
|
CMatrixDouble &yraw,
|
|
int nraw,
|
|
CRowDouble &scaleraw,
|
|
int bftype,
|
|
double bfparamraw,
|
|
double lambdavraw,
|
|
int aterm,
|
|
CRBFV3Model &s,
|
|
int &progress10000,
|
|
bool &terminationrequest,
|
|
CRBFV3Report &rep)
|
|
{
|
|
//--- create variables
|
|
double tol=0;
|
|
int n=0;
|
|
int nx=0;
|
|
int ny=0;
|
|
double bfparamscaled=0;
|
|
double lambdavwrk=0;
|
|
double rescaledby=0;
|
|
double mergetol=0;
|
|
int matrixformat=0;
|
|
int acbfbatch=0;
|
|
int nglobal=0;
|
|
int nlocal=0;
|
|
int ncorrection=0;
|
|
int nbatch=0;
|
|
int nneighbors=0;
|
|
int ncoarse=0;
|
|
CMatrixDouble xscaled;
|
|
CMatrixDouble yscaled;
|
|
CMatrixDouble xcoarse;
|
|
CMatrixDouble x1t;
|
|
CRBF3Evaluator bfmatrix;
|
|
CRowDouble b;
|
|
CRowDouble x0;
|
|
CRowDouble x1;
|
|
CRowDouble y0;
|
|
CRowDouble y1;
|
|
CRowDouble sft;
|
|
CRowDouble scalewrk;
|
|
CMatrixDouble c2;
|
|
CMatrixDouble res;
|
|
CMatrixDouble upd0;
|
|
CMatrixDouble upd1;
|
|
CMatrixDouble ortbasis;
|
|
int ortbasissize=0;
|
|
CRowInt raw2wrkmap;
|
|
CRowInt wrk2rawmap;
|
|
CRowInt idummy;
|
|
CSparseMatrix sp;
|
|
CSparseSolverState ss;
|
|
CSparseSolverReport ssrep;
|
|
CRBF3DDMSolver ddmsolver;
|
|
double resnrm=0;
|
|
double res0nrm=0;
|
|
int iteridx=0;
|
|
int yidx=0;
|
|
bool dotrace=false;
|
|
bool dodetailedtrace=false;
|
|
CFblsGMRESState gmressolver;
|
|
double orterr=0;
|
|
int timeprec=0;
|
|
int timedesign=0;
|
|
int timeddminit=0;
|
|
int timeddmsolve=0;
|
|
int timecorrinit=0;
|
|
int timecorrsolve=0;
|
|
int timereeval=0;
|
|
int timetotal=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
CMatrixDouble refrhs;
|
|
CRowDouble refrhs1;
|
|
CRowDouble refsol1;
|
|
mergetol=1000*CMath::m_machineepsilon;
|
|
tol=1.0E-6;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nx>0,__FUNCTION__+": incorrect NX"))
|
|
return;
|
|
if(!CAp::Assert(s.m_ny>0,__FUNCTION__+": incorrect NY"))
|
|
return;
|
|
if(!CAp::Assert(bftype==1 || bftype==2 || bftype==3,__FUNCTION__+": incorrect BFType"))
|
|
return;
|
|
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": incorrect BFType"))
|
|
return;
|
|
for(j=0; j<s.m_nx; j++)
|
|
{
|
|
if(!CAp::Assert(scaleraw[j]>0.0,__FUNCTION__+": incorrect ScaleVec"))
|
|
return;
|
|
}
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
bfparamscaled=bfparamraw;
|
|
//--- Trace output (if needed)
|
|
dotrace=CAp::IsTraceEnabled("RBF");
|
|
dodetailedtrace=dotrace && CAp::IsTraceEnabled("RBF.DETAILED");
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("\n\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
CAp::Trace("//--- DDM-RBF builder started //\n");
|
|
CAp::Trace("////////////////////////////////////////////////////////////////////////////////////////////////////\n");
|
|
}
|
|
//--- Clean up communication and report fields
|
|
progress10000=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_rmserror=0;
|
|
rep.m_iterationscount=0;
|
|
timeprec=0;
|
|
timedesign=0;
|
|
timeddminit=0;
|
|
timeddmsolve=0;
|
|
timecorrinit=0;
|
|
timecorrsolve=0;
|
|
timereeval=0;
|
|
timetotal=0-(int)(GetTickCount()/10000);
|
|
//--- Quick exit when we have no points
|
|
if(nraw==0)
|
|
{
|
|
ZeroFill(s,nx,ny);
|
|
rep.m_terminationtype=1;
|
|
progress10000=10000;
|
|
return;
|
|
}
|
|
//--- Preprocess dataset (scale points, merge nondistinct ones)
|
|
PreprocessDataSet(xraw,mergetol,yraw,scaleraw,nraw,nx,ny,bftype,bfparamraw,lambdavraw,xscaled,yscaled,raw2wrkmap,wrk2rawmap,n,scalewrk,sft,bfparamscaled,lambdavwrk,rescaledby);
|
|
x1t.Resize(nx+1,n);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
x1t.Set(j,i,xscaled.Get(i,j));
|
|
x1t.Set(nx,i,1.0);
|
|
}
|
|
//--- Compute design matrix
|
|
matrixformat=1;
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("=== MODEL MATRIX INITIALIZATION STARTED ============================================================\n");
|
|
CAp::Trace(StringFormat("N = %d\nNX = %d\nNY = %d\n",n,nx,ny));
|
|
CAp::Trace(StringFormat("BFType = %d",bftype));
|
|
if(bftype==1 && bfparamraw>0.0)
|
|
CAp::Trace(StringFormat(" ( f=sqrt(r^2+alpha^2),alpha=%.3f,multiquadric with manual radius)",bfparamraw));
|
|
if(bftype==1 && bfparamraw==0.0)
|
|
CAp::Trace(" ( f=r,biharmonic spline )");
|
|
if(bftype==1 && bfparamraw<0.0)
|
|
CAp::Trace(StringFormat(" ( f=sqrt(r^2+alpha^2),alpha=AUTO*%.3f=%.2E,multiquadric )",-bfparamraw,bfparamscaled));
|
|
if(bftype==2)
|
|
CAp::Trace(" ( f=log(r)*r^2,thin plate spline )");
|
|
if(bftype==3)
|
|
CAp::Trace(" ( f=r^3 )");
|
|
CAp::Trace("\n");
|
|
CAp::Trace(StringFormat("Polinom.term= %d ",aterm));
|
|
if(aterm==1)
|
|
CAp::Trace("(linear term)");
|
|
if(aterm==2)
|
|
CAp::Trace("(constant term)");
|
|
if(aterm==3)
|
|
CAp::Trace("(zero term)");
|
|
CAp::Trace("\n");
|
|
CAp::Trace(StringFormat("LambdaV = %.2E (raw value of the smoothing parameter; effective value after adjusting for data spread is %.2E)\n",lambdavraw,lambdavwrk));
|
|
CAp::Trace("VarScales = ");
|
|
CApServ::TraceVectorE3(scaleraw,0,nx);
|
|
CAp::Trace(" (raw values of variable scales)\n");
|
|
}
|
|
timedesign-=(int)(GetTickCount()/10000);
|
|
ModelMatrixInit(xscaled,n,nx,bftype,bfparamscaled,matrixformat,bfmatrix);
|
|
timedesign+=(int)(GetTickCount()/10000);
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> model matrix initialized in %d ms\n",timedesign));
|
|
//--- Build orthogonal basis of the subspace spanned by polynomials of 1st degree.
|
|
//--- This basis is used later to check orthogonality conditions for the coefficients.
|
|
ortbasis.Resize(nx+1,n);
|
|
CAblasF::RSetR(n,1/MathSqrt(n),ortbasis,0);
|
|
ortbasissize=1;
|
|
x0.Resize(n);
|
|
for(k=0; k<nx; k++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
x0.Set(j,xscaled.Get(j,k));
|
|
v=MathSqrt(CAblasF::RDotV2(n,x0));
|
|
CAblas::RowWiseGramSchmidt(ortbasis,ortbasissize,n,x0,x0,false);
|
|
vv=MathSqrt(CAblasF::RDotV2(n,x0));
|
|
if(vv>(MathSqrt(CMath::m_machineepsilon)*(v+1)))
|
|
{
|
|
CAblasF::RCopyMulVR(n,1/vv,x0,ortbasis,ortbasissize);
|
|
ortbasissize++;
|
|
}
|
|
}
|
|
//--- Build preconditioner
|
|
nglobal=0;
|
|
nlocal=(int)MathMax(MathRound(MathPow(5.5,nx)),25);
|
|
ncorrection=(int)MathRound(MathPow(5,nx));
|
|
acbfbatch=32;
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("=== PRECONDITIONER CONSTRUCTION STARTED ============================================================\n");
|
|
CAp::Trace(StringFormat("nglobal = %d\nnlocal = %d\nncorrection = %d\nnbatch = %d\n",nglobal,nlocal,ncorrection,acbfbatch));
|
|
}
|
|
timeprec-=(int)(GetTickCount()/10000);
|
|
ComputeACBFPreconditioner(xscaled,n,nx,bftype,bfparamscaled,aterm,acbfbatch,nglobal,nlocal,ncorrection,5,2,lambdavwrk,sp);
|
|
timeprec+=(int)(GetTickCount()/10000);
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> ACBF preconditioner computed in %d ms\n",timeprec));
|
|
//--- DDM
|
|
if(dotrace)
|
|
CAp::Trace("=== DOMAIN DECOMPOSITION METHOD STARTED ============================================================\n");
|
|
CAblasF::RSetAllocM(n+nx+1,ny,0.0,c2);
|
|
nneighbors=(int)MathRound(MathPow(5,nx));
|
|
if(nx==1)
|
|
nbatch=MathMin(100,n);
|
|
else
|
|
{
|
|
if(nx==2)
|
|
nbatch=MathMin(100,n);
|
|
else
|
|
nbatch=MathMin((int)MathRound(MathPow(10,nx)),MathMin(1000,n));
|
|
}
|
|
ncoarse=(int)MathRound(MathMax(4,MathPow(3.0,nx))*((double)n/(double)nbatch+1));
|
|
ncoarse=MathMax(ncoarse,(int)MathRound(MathPow(4,nx)));
|
|
ncoarse=MathMin(ncoarse,n);
|
|
if(dotrace)
|
|
{
|
|
CAp::Trace("> problem metrics and settings\n");
|
|
CAp::Trace(StringFormat("NNeighbors = %d\n",nneighbors));
|
|
CAp::Trace(StringFormat("NBatch = %d\n",nbatch));
|
|
CAp::Trace(StringFormat("NCoarse = %d\n",ncoarse));
|
|
}
|
|
DDMSolverInit(xscaled,rescaledby,n,nx,bfmatrix,bftype,bfparamscaled,lambdavwrk,aterm,sp,nneighbors,nbatch,ncoarse,dotrace,dodetailedtrace,ddmsolver,timeddminit,timecorrinit);
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> DDM initialization done in %d ms,%d subproblems solved (%d well-conditioned,%d ill-conditioned)\n",timeddminit,ddmsolver.m_subproblemscnt,ddmsolver.m_cntlu,ddmsolver.m_cntregqr));
|
|
//--- Use preconditioned GMRES
|
|
rep.m_rmserror=0;
|
|
rep.m_maxerror=0;
|
|
rep.m_iterationscount=0;
|
|
for(yidx=0; yidx<ny; yidx++)
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> solving for component %d:\n",yidx));
|
|
CAblasF::RSetAllocV(n+nx+1,0.0,y0);
|
|
CAblasF::RSetAllocV(n+nx+1,0.0,y1);
|
|
CAblasF::RCopyCV(n,yscaled,yidx,y0);
|
|
CFbls::FblsGMRESCreate(y0,n,MathMin(m_maxddmits,n),gmressolver);
|
|
gmressolver.m_epsres=tol;
|
|
gmressolver.m_epsred=m_epsred;
|
|
iteridx=0;
|
|
while(CFbls::FblsGMRESIteration(gmressolver))
|
|
{
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat(">> DDM iteration %d: %.2E relative residual\n",iteridx,gmressolver.m_reprelres));
|
|
CAblasF::RAllocV(n+nx+1,y0);
|
|
CAblasF::RAllocV(n+nx+1,y1);
|
|
DDMSolverRun1(ddmsolver,gmressolver.m_x,n,nx,sp,bfmatrix,y0,timeddmsolve,timecorrsolve);
|
|
timereeval-=(int)(GetTickCount()/10000);
|
|
ModelMatrixComputeProduct(bfmatrix,y0,y1);
|
|
CAblasF::RGemVX(n,nx+1,1.0,x1t,0,0,1,y0,n,1.0,y1,0);
|
|
for(i=0; i<n; i++)
|
|
y1.Add(i,lambdavwrk*y0[i]);
|
|
timereeval+=(int)(GetTickCount()/10000);
|
|
CAblasF::RCopyV(n,y1,gmressolver.m_ax);
|
|
rep.m_iterationscount++;
|
|
iteridx++;
|
|
}
|
|
DDMSolverRun1(ddmsolver,gmressolver.m_xs,n,nx,sp,bfmatrix,x1,timeddmsolve,timecorrsolve);
|
|
CAblasF::RCopyVC(n+nx+1,x1,c2,yidx);
|
|
//--- Compute predictions and errors
|
|
//--- NOTE: because dataset preprocessing may reorder and merge points we have
|
|
//--- to use raw-to-work mapping in order to be able to compute correct
|
|
//--- error metrics.
|
|
timereeval-=((int)(GetTickCount()/10000));
|
|
ModelMatrixComputeProduct(bfmatrix,x1,y1);
|
|
CAblasF::RGemVX(n,nx+1,1.0,x1t,0,0,1,x1,n,1.0,y1,0);
|
|
timereeval+=((int)(GetTickCount()/10000));
|
|
resnrm=0;
|
|
res0nrm=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
resnrm+=CMath::Sqr(yscaled.Get(i,yidx)-y1[i]-lambdavwrk*x1[i]);
|
|
res0nrm+=CMath::Sqr(yscaled.Get(i,yidx));
|
|
}
|
|
resnrm=MathSqrt(resnrm);
|
|
res0nrm=MathSqrt(res0nrm);
|
|
for(i=0; i<nraw; i++)
|
|
{
|
|
v=yraw.Get(i,yidx)-y1[raw2wrkmap[i]];
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v));
|
|
rep.m_rmserror=rep.m_rmserror+v*v;
|
|
}
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat(">> done with %.2E relative residual,GMRES completion code %d\n",resnrm / CApServ::Coalesce(res0nrm,1),gmressolver.m_retcode));
|
|
}
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/(nraw*ny));
|
|
timetotal+=(int)(GetTickCount()/10000);
|
|
if(dotrace)
|
|
{
|
|
CAblasF::RAllocV(n,y0);
|
|
orterr=0;
|
|
for(k=0; k<ny; k++)
|
|
{
|
|
CAblasF::RCopyCV(n,c2,k,y0);
|
|
for(i=0; i<ortbasissize; i++)
|
|
orterr=MathMax(orterr,MathAbs(CAblasF::RDotVR(n,y0,ortbasis,i)));
|
|
}
|
|
CAp::Trace("=== PRINTING RBF SOLVER RESULTS ====================================================================\n");
|
|
CAp::Trace("> errors\n");
|
|
CAp::Trace(StringFormat("RMS.err = %.2E\n",rep.m_rmserror));
|
|
CAp::Trace(StringFormat("MAX.err = %.2E\n",rep.m_maxerror));
|
|
CAp::Trace(StringFormat("ORT.err = %.2E (orthogonality condition)\n",orterr));
|
|
CAp::Trace("> DDM iterations\n");
|
|
CAp::Trace(StringFormat("ItsCnt = %d\n",rep.m_iterationscount));
|
|
CAp::Trace(StringFormat("> total running time is %d ms,including:\n",timetotal));
|
|
CAp::Trace(StringFormat(">> model matrix generation %8d ms\n",timedesign));
|
|
CAp::Trace(StringFormat(">> ACBF preconditioner construction %8d ms\n",timeprec));
|
|
CAp::Trace(StringFormat(">> DDM solver initialization %8d ms\n",timeddminit));
|
|
CAp::Trace(StringFormat(">> DDM corrector initialization %8d ms\n",timecorrinit));
|
|
CAp::Trace(StringFormat(">> DDM solution phase %8d ms\n",timeddmsolve));
|
|
CAp::Trace(StringFormat(">> DDM correction phase %8d ms\n",timecorrsolve));
|
|
CAp::Trace(StringFormat(">> DDM solver model reevaluation %8d ms\n",timereeval));
|
|
}
|
|
s.m_bftype=bftype;
|
|
s.m_bfparam=bfparamscaled;
|
|
CAblasF::RCopyAllocV(nx,scalewrk,s.m_s);
|
|
for(j=0; j<ny; j++)
|
|
{
|
|
s.m_v.Set(j,nx,c2.Get(n+nx,j));
|
|
for(i=0; i<nx; i++)
|
|
{
|
|
s.m_v.Set(j,i,c2.Get(n+i,j)/scalewrk[i]);
|
|
s.m_v.Set(j,nx,s.m_v.Get(j,nx)-c2.Get(n+i,j)*sft[i]/scalewrk[i]);
|
|
}
|
|
}
|
|
CAblasF::RAllocV(n*(nx+ny),s.m_cw);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<nx; j++)
|
|
s.m_cw.Set(i*(nx+ny)+j,xscaled.Get(i,j)+sft[j]/scalewrk[j]);
|
|
for(j=0; j<ny; j++)
|
|
s.m_cw.Set(i*(nx+ny)+nx+j,c2.Get(i,j));
|
|
}
|
|
CAblasF::ICopyAllocV(n,wrk2rawmap,s.m_pointindexes);
|
|
s.m_nc=n;
|
|
CreateFastEvaluator(s);
|
|
//--- Set up debug fields
|
|
s.m_dbgregqrusedforddm=ddmsolver.m_cntregqr>0;
|
|
//--- Update progress reports
|
|
rep.m_terminationtype=1;
|
|
progress10000=10000;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Alloc(CSerializer &s,CRBFV3Model &model)
|
|
{
|
|
//--- Data
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,model.m_s,model.m_nx);
|
|
CApServ::AllocRealMatrix(s,model.m_v,model.m_ny,model.m_nx+1);
|
|
CApServ::AllocRealArray(s,model.m_cw,model.m_nc*(model.m_nx+model.m_ny));
|
|
CApServ::AllocIntegerArray(s,model.m_pointindexes,model.m_nc);
|
|
//--- End of stream, no additional data
|
|
s.Alloc_Entry();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Serialize(CSerializer &s,CRBFV3Model &model)
|
|
{
|
|
//--- Data
|
|
s.Serialize_Int(model.m_nx);
|
|
s.Serialize_Int(model.m_ny);
|
|
s.Serialize_Int(model.m_bftype);
|
|
s.Serialize_Double(model.m_bfparam);
|
|
s.Serialize_Int(model.m_nc);
|
|
CApServ::SerializeRealArray(s,model.m_s,model.m_nx);
|
|
CApServ::SerializeRealMatrix(s,model.m_v,model.m_ny,model.m_nx+1);
|
|
CApServ::SerializeRealArray(s,model.m_cw,model.m_nc*(model.m_nx+model.m_ny));
|
|
CApServ::SerializeIntegerArray(s,model.m_pointindexes,model.m_nc);
|
|
//--- End of stream, no additional data
|
|
s.Serialize_Int(117256);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Unserialize(CSerializer &s,CRBFV3Model &model)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
int bftype=0;
|
|
int k=0;
|
|
double bfparam=0;
|
|
//--- Unserialize primary model parameters, initialize model.
|
|
//--- It is necessary to call RBFCreate() because some internal fields
|
|
//--- which are NOT unserialized will need initialization.
|
|
nx=s.Unserialize_Int();
|
|
ny=s.Unserialize_Int();
|
|
bftype=s.Unserialize_Int();
|
|
bfparam=s.Unserialize_Double();
|
|
RBFV3Create(nx,ny,bftype,bfparam,model);
|
|
model.m_nc=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,model.m_s);
|
|
CApServ::UnserializeRealMatrix(s,model.m_v);
|
|
CApServ::UnserializeRealArray(s,model.m_cw);
|
|
CApServ::UnserializeIntegerArray(s,model.m_pointindexes);
|
|
//--- End of stream, check that no additional data is present
|
|
k=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(k==117256,__FUNCTION__+": unexpected payload detected in the data stream. Integrity check failed"))
|
|
return;
|
|
//--- Finalize construction
|
|
CreateFastEvaluator(model);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY = 1(scalar function)|
|
|
//| and NX = 1 (1-dimensional space). |
|
|
//| This function returns 0.0 when: |
|
|
//| * the model is not initialized |
|
|
//| * NX<>1 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - X - coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV3::RBFV3Calc1(CRBFV3Model &s,double x0)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=1)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0-s.m_v.Get(0,1);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY = 1(scalar function)|
|
|
//| and NX = 2 (2-dimensional space). If you have 3-dimensional |
|
|
//| space, use RBFCalc3(). If you have general situation |
|
|
//| (NX-dimensional space, NY-dimensional function) you should use |
|
|
//| general, less efficient implementation RBFCalc(). |
|
|
//| If you want to calculate function values many times, consider |
|
|
//| using RBFGridCalc2(), which is far more efficient than many |
|
|
//| subsequent calls to RBFCalc2(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0(as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV3::RBFV3Calc2(CRBFV3Model &s,double x0,double x1)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2);
|
|
if(s.m_nc==0)
|
|
return(result);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
s.m_calcbuf.m_x123.Set(1,x1);
|
|
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model in the given |
|
|
//| point. |
|
|
//| This function should be used when we have NY = 1(scalar function)|
|
|
//| and NX = 3 (3-dimensional space). If you have 2-dimensional s |
|
|
//| pace, use RBFCalc2(). If you have general situation |
|
|
//| (NX-dimensional space, NY-dimensional function) you should use |
|
|
//| general, less efficient implementation RBFCalc(). |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| X2 - third coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0(as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV3::RBFV3Calc3(CRBFV3Model &s,double x0,double x1,double x2)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
|
|
return(0);
|
|
|
|
if(s.m_ny!=1 || s.m_nx!=3)
|
|
return(0);
|
|
|
|
result=s.m_v.Get(0,0)*x0+s.m_v.Get(0,1)*x1+s.m_v.Get(0,2)*x2+s.m_v.Get(0,3);
|
|
if(s.m_nc==0)
|
|
return(result);
|
|
s.m_calcbuf.m_x123.Set(0,x0);
|
|
s.m_calcbuf.m_x123.Set(1,x1);
|
|
s.m_calcbuf.m_x123.Set(2,x2);
|
|
RBFV3TsCalcBuf(s,s.m_calcbuf,s.m_calcbuf.m_x123,s.m_calcbuf.m_y123);
|
|
result=s.m_calcbuf.m_y123[0];
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point. |
|
|
//| Same as RBFCalc(), but does not reallocate Y when in is large |
|
|
//| enough to store function values. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3CalcBuf(CRBFV3Model &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
RBFV3TsCalcBuf(s,s.m_calcbuf,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point, using external buffer object (internal temporaries of RBF |
|
|
//| model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3TsCalcBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
double distance0=0;
|
|
int colidx=0;
|
|
int srcidx=0;
|
|
int widx=0;
|
|
int curchunk=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
//--- Handle linear term
|
|
if(CAp::Len(y)<ny)
|
|
y.Resize(ny);
|
|
for(int i=0; i<ny; i++)
|
|
y.Set(i,s.m_v.Get(i,nx)+CAblasF::RDotVR(nx,x,s.m_v,i));
|
|
if(s.m_nc==0)
|
|
return;
|
|
//--- Handle RBF term
|
|
//--- check
|
|
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2 || s.m_bftype==3,__FUNCTION__+": unsupported basis function type"))
|
|
return;
|
|
|
|
for(int j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
CAblasF::RAllocV(s.m_evaluator.m_chunksize,buf.m_evalbuf.m_funcbuf);
|
|
CAblasF::RAllocV(s.m_evaluator.m_chunksize,buf.m_evalbuf.m_wrkbuf);
|
|
colidx=0;
|
|
srcidx=0;
|
|
widx=0;
|
|
distance0=1.0E-50;
|
|
if(s.m_bftype==1)
|
|
{
|
|
//--- Kernels that add squared parameter to the squared distance
|
|
distance0=CMath::Sqr(s.m_bfparam);
|
|
}
|
|
while(colidx<s.m_nc)
|
|
{
|
|
//--- Handle basecase with size at most ChunkSize*ChunkSize
|
|
curchunk=MathMin(s.m_evaluator.m_chunksize,s.m_nc-colidx);
|
|
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,0);
|
|
for(int i=0; i<ny; i++)
|
|
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
|
|
colidx+=curchunk;
|
|
srcidx+=nx;
|
|
widx+=ny;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its derivatives, using external buffer object (internal|
|
|
//| temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance of |
|
|
//| RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used. |
|
|
//| Y, DY - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated when |
|
|
//| it is larger than NY. |
|
|
//| DY - derivatives, array[NY * NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3TsDiffBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
int i=0;
|
|
int j=0;
|
|
double smalldist2=0;
|
|
bool nograd=false;
|
|
int colidx=0;
|
|
int srcidx=0;
|
|
int widx=0;
|
|
int curchunk=0;
|
|
int maxchunksize=0;
|
|
double distance0=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
//--- Handle linear term
|
|
if(CAp::Len(y)<ny)
|
|
y.Resize(ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,nx));
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
y.Add(i,s.m_v.Get(i,j)*x[j]);
|
|
dy.Set(i*nx+j,s.m_v.Get(i,j));
|
|
}
|
|
}
|
|
if(s.m_nc==0)
|
|
return;
|
|
//--- Rescale X and DY to the internal scaling used by the RBF model
|
|
for(j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
for(i=0; i<ny; i++)
|
|
for(j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
|
|
//--- Prepare information necessary for the detection of the nonexistent gradient
|
|
nograd=false;
|
|
smalldist2=(CAblasF::RDotV2(nx,buf.m_x)+1.0)*CMath::Sqr(100*CMath::m_machineepsilon);
|
|
//--- Handle RBF term
|
|
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2 || s.m_bftype==3,__FUNCTION__+": unsupported basis function type"))
|
|
return;
|
|
if(!CAp::Assert(s.m_bftype!=1 || s.m_bfparam>=0.0,__FUNCTION__+": inconsistent BFType/BFParam"))
|
|
return;
|
|
maxchunksize=s.m_evaluator.m_chunksize;
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_funcbuf);
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_wrkbuf);
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df1);
|
|
CAblasF::RAllocM(nx,maxchunksize,buf.m_evalbuf.m_deltabuf);
|
|
CAblasF::RSetAllocV(maxchunksize,1.0E50,buf.m_evalbuf.m_mindist2);
|
|
colidx=0;
|
|
srcidx=0;
|
|
widx=0;
|
|
distance0=1.0E-50;
|
|
if(s.m_bftype==1)
|
|
{
|
|
//--- Kernels that add squared parameter to the squared distance
|
|
distance0=CMath::Sqr(s.m_bfparam);
|
|
}
|
|
while(colidx<s.m_nc)
|
|
{
|
|
//--- Handle basecase with size at most ChunkSize*ChunkSize
|
|
curchunk=MathMin(maxchunksize,s.m_nc-colidx);
|
|
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,1);
|
|
for(j=0; j<nx; j++)
|
|
CAblasF::RMergeMulVR(curchunk,buf.m_evalbuf.m_df1,buf.m_evalbuf.m_deltabuf,j);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
|
|
for(j=0; j<nx; j++)
|
|
dy.Add(i*nx+j,2*CAblasF::RDotRR(curchunk,s.m_wchunked,widx+i,buf.m_evalbuf.m_deltabuf,j));
|
|
}
|
|
colidx+=curchunk;
|
|
srcidx+=nx;
|
|
widx+=ny;
|
|
}
|
|
if(s.m_bftype==1 && s.m_bfparam==0.0)
|
|
{
|
|
//--- The kernel function is nondifferentiable at nodes, check whether we are close to one of the nodes or not
|
|
for(i=0; i<maxchunksize; i++)
|
|
nograd=nograd || buf.m_evalbuf.m_mindist2[i]<=smalldist2;
|
|
if(nograd)
|
|
{
|
|
//--- The gradient is undefined at the trial point, flush it to zero
|
|
CAblasF::RSetV(ny*nx,0.0,dy);
|
|
}
|
|
}
|
|
//--- Rescale derivatives back
|
|
for(i=0; i<ny; i++)
|
|
for(j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point and its first and second derivatives, using external buffer|
|
|
//| object (internal temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be |
|
|
//| used. |
|
|
//| Y, DY, D2Y - possibly preallocated arrays |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//| DY - derivatives, array[NY * NX]. DY is not reallocated |
|
|
//| when it is larger than NY*NX. |
|
|
//| D2Y - second derivatives, array[NY * NX * NX]. D2Y is not|
|
|
//| reallocated when it is larger than NY*NX*NX. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3TSHessBuf(CRBFV3Model &s,CRBFV3CalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,CRowDouble &dy,
|
|
CRowDouble &d2y)
|
|
{
|
|
//--- create variables
|
|
int nx=s.m_nx;
|
|
int ny=s.m_ny;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
bool nearnode=false;
|
|
bool nograd=false;
|
|
bool nohess=false;
|
|
double smalldist2=0;
|
|
int colidx=0;
|
|
int srcidx=0;
|
|
int widx=0;
|
|
int curchunk=0;
|
|
int maxchunksize=0;
|
|
double distance0=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
//--- Handle linear term
|
|
if(CAp::Len(y)<ny)
|
|
y.Resize(ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
if(CAp::Len(d2y)<ny*nx*nx)
|
|
d2y.Resize(ny*nx*nx);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
y.Set(i,s.m_v.Get(i,nx));
|
|
for(j=0; j<nx; j++)
|
|
{
|
|
y.Add(i,s.m_v.Get(i,j)*x[j]);
|
|
dy.Set(i*nx+j,s.m_v.Get(i,j));
|
|
}
|
|
}
|
|
CAblasF::RSetV(ny*nx*nx,0.0,d2y);
|
|
if(s.m_nc==0)
|
|
return;
|
|
//--- Rescale X and DY to the internal scaling used by the RBF model (D2Y is zero,
|
|
//--- so it does not need rescaling).
|
|
for(j=0; j<nx; j++)
|
|
buf.m_x.Set(j,x[j]/s.m_s[j]);
|
|
for(i=0; i<ny; i++)
|
|
for(j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]*s.m_s[j]);
|
|
//--- Prepare information necessary for the detection of the nonexistent Hessian
|
|
nograd=false;
|
|
nohess=false;
|
|
smalldist2=(CAblasF::RDotV2(nx,buf.m_x)+1.0)*CMath::Sqr(100*CMath::m_machineepsilon);
|
|
//--- Handle RBF term
|
|
if(!CAp::Assert(s.m_bftype==1 || s.m_bftype==2,__FUNCTION__+": unsupported basis function type"))
|
|
return;
|
|
if(!CAp::Assert(s.m_bftype!=1 || s.m_bfparam>=0.0,__FUNCTION__+": inconsistent BFType/BFParam"))
|
|
return;
|
|
maxchunksize=s.m_evaluator.m_chunksize;
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_funcbuf);
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_wrkbuf);
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df1);
|
|
CAblasF::RAllocV(maxchunksize,buf.m_evalbuf.m_df2);
|
|
CAblasF::RAllocM(nx,maxchunksize,buf.m_evalbuf.m_deltabuf);
|
|
CAblasF::RSetAllocV(maxchunksize,1.0E50,buf.m_evalbuf.m_mindist2);
|
|
colidx=0;
|
|
srcidx=0;
|
|
widx=0;
|
|
distance0=1.0E-50;
|
|
if(s.m_bftype==1)
|
|
{
|
|
//--- Kernels that add squared parameter to the squared distance
|
|
distance0=CMath::Sqr(s.m_bfparam);
|
|
}
|
|
while(colidx<s.m_nc)
|
|
{
|
|
//--- Handle basecase with size at most ChunkSize*ChunkSize
|
|
curchunk=MathMin(maxchunksize,s.m_nc-colidx);
|
|
ComputeRowChunk(s.m_evaluator,buf.m_x,buf.m_evalbuf,curchunk,srcidx,distance0,2);
|
|
for(i=0; i<ny; i++)
|
|
{
|
|
y.Add(i,CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_funcbuf,s.m_wchunked,widx+i));
|
|
for(k0=0; k0<nx; k0++)
|
|
{
|
|
CAblasF::RCopyRV(curchunk,buf.m_evalbuf.m_deltabuf,k0,buf.m_evalbuf.m_wrkbuf);
|
|
CAblasF::RMergeMulV(curchunk,buf.m_evalbuf.m_df1,buf.m_evalbuf.m_wrkbuf);
|
|
dy.Add(i*nx+k0,2*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_wrkbuf,s.m_wchunked,widx+i));
|
|
}
|
|
for(k0=0; k0<nx; k0++)
|
|
for(k1=0; k1<nx; k1++)
|
|
{
|
|
CAblasF::RCopyV(curchunk,buf.m_evalbuf.m_df2,buf.m_evalbuf.m_wrkbuf);
|
|
CAblasF::RMergeMulRV(curchunk,buf.m_evalbuf.m_deltabuf,k0,buf.m_evalbuf.m_wrkbuf);
|
|
CAblasF::RMergeMulRV(curchunk,buf.m_evalbuf.m_deltabuf,k1,buf.m_evalbuf.m_wrkbuf);
|
|
d2y.Add(i*nx*nx+k0*nx+k1,4*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_wrkbuf,s.m_wchunked,widx+i));
|
|
if(k0==k1)
|
|
d2y.Add(i*nx*nx+k0*nx+k1,2*CAblasF::RDotVR(curchunk,buf.m_evalbuf.m_df1,s.m_wchunked,widx+i));
|
|
}
|
|
}
|
|
colidx+=curchunk;
|
|
srcidx+=nx;
|
|
widx+=ny;
|
|
}
|
|
nearnode=false;
|
|
if((s.m_bftype==1 && s.m_bfparam==0.0) || s.m_bftype==2)
|
|
{
|
|
//--- The kernel function is nondifferentiable at nodes, check whether we are close to one of the nodes or not
|
|
for(i=0; i<maxchunksize; i++)
|
|
nearnode=nearnode || buf.m_evalbuf.m_mindist2[i]<=smalldist2;
|
|
}
|
|
nograd=nearnode && (s.m_bftype==1 && s.m_bfparam==0.0);
|
|
nohess=nearnode && ((s.m_bftype==1 && s.m_bfparam==0.0) || s.m_bftype==2);
|
|
if(nograd)
|
|
{
|
|
//--- The gradient is undefined at the trial point, flush it to zero
|
|
CAblasF::RSetV(ny*nx,0.0,dy);
|
|
}
|
|
if(nohess)
|
|
{
|
|
//--- The Hessian is undefined at the trial point, flush it to zero
|
|
CAblasF::RSetV(ny*nx*nx,0.0,d2y);
|
|
}
|
|
//--- Rescale derivatives back
|
|
for(i=0; i<ny; i++)
|
|
for(j=0; j<nx; j++)
|
|
dy.Set(i*nx+j,dy[i*nx+j]/s.m_s[j]);
|
|
for(i=0; i<ny; i++)
|
|
for(j=0; j<nx; j++)
|
|
for(k=0; k<nx; k++)
|
|
d2y.Set(i*nx*nx+j*nx+k,d2y[i*nx*nx+j*nx+k]/(s.m_s[j]*s.m_s[k]));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to perform gridded calculation for 2D, 3D |
|
|
//| or 4D problems. It accepts parameters X0..X3 and counters N0..N3.|
|
|
//| If RBF model has dimensionality less than 4, corresponding arrays|
|
|
//| should contain just one element equal to zero, and corresponding |
|
|
//| N's should be equal to 1. |
|
|
//| NOTE: array Y should be preallocated by caller. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3GridCalcVX(CRBFV3Model &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CRowDouble &x2,
|
|
int n2,CRowDouble &x3,int n3,
|
|
bool &flagy[],bool sparsey,CRowDouble &y)
|
|
{
|
|
CRBFV3CalcBuffer bufseed;
|
|
CRBFV3CalcBuffer bufpool[];
|
|
//--- Perform integrity checks
|
|
if(!CAp::Assert(s.m_nx==2 || s.m_nx==3,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(n0>=1 && n1>=1 && n2>=1 && n3>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=4 || (CAp::Len(x3)>=1 && x3[0]==0.0 && n3==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=3 || (CAp::Len(x2)>=1 && x2[0]==0.0 && n2==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nx>=2 || (CAp::Len(x1)>=1 && x1[0]==0.0 && n1==1),__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(!sparsey || CAp::Len(flagy)>=n0*n1*n2*n3,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Prepare shared pool
|
|
RBFV3CreateCalcBuffer(s,bufseed);
|
|
//--- Call worker function
|
|
int simdwidth=8;
|
|
int tilescnt=CApServ::IDivUp(n0,simdwidth)*CApServ::IDivUp(n1,simdwidth)*CApServ::IDivUp(n2,simdwidth)*CApServ::IDivUp(n3,simdwidth);
|
|
if(!CAp::Assert(ArrayResize(bufpool,tilescnt)>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(int i=0; i<tilescnt; i++)
|
|
bufpool[i]=bufseed;
|
|
//--- function call
|
|
GridCalcRec(s,simdwidth,0,tilescnt,x0,n0,x1,n1,x2,n2,x3,n3,flagy,sparsey,y,bufpool);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "unpacks" RBF model by extracting its coefficients.|
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NX - dimensionality of argument |
|
|
//| NY - dimensionality of the target function |
|
|
//| XWR - model information, array[NC, NX + NY + NX + 2]. |
|
|
//| One row of the array corresponds to one basis function |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of the function |
|
|
//| being modeled |
|
|
//| * next NX columns - radii, one per dimension |
|
|
//| * next column - basis function type: |
|
|
//| * 1 for f = r |
|
|
//| * 2 for f = r ^ 2 * ln(r) |
|
|
//| * 10 for multiquadric f = sqrt(r ^ 2 + alpha ^ 2) |
|
|
//| * next column - basis function parameter: |
|
|
//| * alpha, for basis function type 10 |
|
|
//| * ignored(zero) for other basis function types |
|
|
//| * next column - point index in the original dataset, or |
|
|
//| -1 for an artificial node created by the |
|
|
//| solver. The algorithm may reorder the |
|
|
//| nodes, drop some nodes or add artificial |
|
|
//| nodes. Thus, one parsing this column |
|
|
//| should expect all these kinds of |
|
|
//| alterations in the dataset. |
|
|
//| NC - number of the centers |
|
|
//| V - polynomial term, array[NY, NX + 1]. One row per one|
|
|
//| dimension of the function being modelled. First NX |
|
|
//| elements are linear coefficients, V[NX] is equal to|
|
|
//| the constant part. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::RBFV3Unpack(CRBFV3Model &s,int &nx,int &ny,
|
|
CMatrixDouble &xwr,int &nc,CMatrixDouble &v)
|
|
{
|
|
//--- create variables
|
|
int cwwidth=0;
|
|
bool recognized=false;
|
|
|
|
nx=0;
|
|
ny=0;
|
|
xwr.Resize(0,0);
|
|
nc=0;
|
|
v.Resize(0,0);
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
nc=s.m_nc;
|
|
//--- Fill V
|
|
v.Resize(s.m_ny,s.m_nx+1);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
CAblasF::RCopyRR(nx+1,s.m_v,i,v,i);
|
|
//--- Fill XWR
|
|
if(nc>0)
|
|
{
|
|
cwwidth=nx+ny;
|
|
xwr.Resize(nc,nx+ny+nx+3);
|
|
for(int i=0; i<nc; i++)
|
|
{
|
|
//--- Output centers (in the original variable scaling), weights and radii
|
|
for(int j=0; j<nx; j++)
|
|
xwr.Set(i,j,s.m_cw[i*cwwidth+j]*s.m_s[j]);
|
|
for(int j=0; j<ny; j++)
|
|
xwr.Set(i,nx+j,s.m_cw[i*cwwidth+nx+j]);
|
|
for(int j=0; j<nx; j++)
|
|
xwr.Set(i,nx+ny+j,s.m_s[j]);
|
|
//--- Recognize specific basis function used and perform post-processing
|
|
recognized=false;
|
|
if(s.m_bftype==1 && s.m_bfparam==0.0)
|
|
{
|
|
//--- Biharmonic kernel f=r
|
|
//--- Weights are multiplied by -1 because actually it is f=-r (the latter
|
|
//--- is conditionally positive definite basis function, and the former is
|
|
//--- how it is known to most users)
|
|
xwr.Set(i,nx+ny+nx,1);
|
|
xwr.Set(i,nx+ny+nx+1,0.0);
|
|
for(int j=0; j<ny; j++)
|
|
xwr.Mul(i,nx+j,(-1.0));
|
|
recognized=true;
|
|
}
|
|
if(s.m_bftype==1 && (double)(s.m_bfparam)>0.0)
|
|
{
|
|
//--- Multiquadric f=sqrt(r^2+alpha^2)
|
|
//--- Weights are multiplied by -1 because actually it is f=-sqrt(r^2+alpha^2)
|
|
//--- (the latter is conditionally positive definite basis function, and the
|
|
//--- former is how it is known to most users)
|
|
xwr.Set(i,nx+ny+nx,10);
|
|
xwr.Set(i,nx+ny+nx+1,s.m_bfparam);
|
|
for(int j=0; j<ny; j++)
|
|
xwr.Mul(i,nx+j,(-1.0));
|
|
recognized=true;
|
|
}
|
|
if(s.m_bftype==2)
|
|
{
|
|
//--- Thin plate spline f=r^2*ln(r)
|
|
xwr.Set(i,nx+ny+nx,2);
|
|
xwr.Set(i,nx+ny+nx+1,0);
|
|
recognized=true;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(recognized,__FUNCTION__+": integrity check 5342 failed"))
|
|
return;
|
|
//--- Output indexes
|
|
xwr.Set(i,nx+ny+nx+2,s.m_pointindexes[i]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Creates fast evaluation structures after initialization of the |
|
|
//| model |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::CreateFastEvaluator(CRBFV3Model &model)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int ontheflystorage=0;
|
|
int nchunks=0;
|
|
int srcoffs=0;
|
|
int dstoffs=0;
|
|
int curlen=0;
|
|
CMatrixDouble xx;
|
|
//--- Setup model matrix structure
|
|
ontheflystorage=1;
|
|
CAblasF::RAllocM(model.m_nc,model.m_nx,xx);
|
|
offs=0;
|
|
for(int i=0; i<model.m_nc; i++)
|
|
{
|
|
for(int j=0; j<model.m_nx; j++)
|
|
xx.Set(i,j,model.m_cw[offs+j]);
|
|
offs+=model.m_nx+model.m_ny;
|
|
}
|
|
ModelMatrixInit(xx,model.m_nc,model.m_nx,model.m_bftype,model.m_bfparam,ontheflystorage,model.m_evaluator);
|
|
//--- Store model coefficients in the efficient chunked format (chunk size is aligned with that
|
|
//--- of the Model.Evaluator).
|
|
//--- check
|
|
if(!CAp::Assert(model.m_evaluator.m_chunksize>=1,__FUNCTION__+": integrity check 3535 failed"))
|
|
return;
|
|
|
|
nchunks=CApServ::IDivUp(model.m_nc,model.m_evaluator.m_chunksize);
|
|
CAblasF::RSetAllocM(nchunks*model.m_ny,model.m_evaluator.m_chunksize,0.0,model.m_wchunked);
|
|
srcoffs=0;
|
|
dstoffs=0;
|
|
while(srcoffs<model.m_nc)
|
|
{
|
|
curlen=MathMin(model.m_evaluator.m_chunksize,model.m_nc-srcoffs);
|
|
for(int i=0; i<curlen; i++)
|
|
for(int j=0; j<model.m_ny; j++)
|
|
model.m_wchunked.Set(dstoffs+j,i,model.m_cw[(srcoffs+i)*(model.m_nx+model.m_ny)+model.m_nx+j]);
|
|
srcoffs+=curlen;
|
|
dstoffs+=model.m_ny;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive worker function for gridded calculation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::GridCalcRec(CRBFV3Model &s,int simdwidth,int tileidx0,
|
|
int tileidx1,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,
|
|
CRowDouble &x2,int n2,
|
|
CRowDouble &x3,int n3,
|
|
bool &flagy[],bool sparsey,
|
|
CRowDouble &y,CRBFV3CalcBuffer &calcpool[])
|
|
{
|
|
//--- create variables
|
|
int ny=s.m_ny;
|
|
int dstoffs=0;
|
|
double problemcost=0;
|
|
int tileidxm=0;
|
|
int k=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
int k2=0;
|
|
int r0a=0;
|
|
int r0b=0;
|
|
int r1a=0;
|
|
int r1b=0;
|
|
int r2a=0;
|
|
int r2b=0;
|
|
CRBFV3CalcBuffer buf;
|
|
|
|
for(int idx=tileidx0; idx<tileidx1; idx++)
|
|
{
|
|
//--- Handle basecase
|
|
k=idx;
|
|
k0=k%CApServ::IDivUp(n0,simdwidth);
|
|
k=k/CApServ::IDivUp(n0,simdwidth);
|
|
k1=k%CApServ::IDivUp(n1,simdwidth);
|
|
k=k/CApServ::IDivUp(n1,simdwidth);
|
|
k2=k%CApServ::IDivUp(n2,simdwidth);
|
|
k=k/CApServ::IDivUp(n2,simdwidth);
|
|
k=k/CApServ::IDivUp(n3,simdwidth);
|
|
//--- check
|
|
if(!CAp::Assert(k==0,__FUNCTION__+": integrity check 7350 failed"))
|
|
return;
|
|
r0a=k0*simdwidth;
|
|
r0b=MathMin(r0a+simdwidth,n0);
|
|
r1a=k1*simdwidth;
|
|
r1b=MathMin(r1a+simdwidth,n1);
|
|
r2a=k2*simdwidth;
|
|
r2b=MathMin(r2a+simdwidth,n2);
|
|
buf=calcpool[idx];
|
|
for(int i=r0a; i<r0b; i++)
|
|
{
|
|
for(int j=r1a; j<r1b; j++)
|
|
{
|
|
for(k=r2a; k<r2b; k++)
|
|
{
|
|
dstoffs=i+j*n0+k*n0*n1;
|
|
if(sparsey && !flagy[dstoffs])
|
|
{
|
|
for(int l=0; l<ny; l++)
|
|
y.Set(l+ny*dstoffs,0);
|
|
continue;
|
|
}
|
|
buf.m_xg.Set(0,x0[i]);
|
|
buf.m_xg.Set(1,x1[j]);
|
|
buf.m_xg.Set(2,x2[k]);
|
|
RBFV3TsCalcBuf(s,buf,buf.m_xg,buf.m_yg);
|
|
for(int l=0; l<ny; l++)
|
|
y.Set(l+ny*dstoffs,buf.m_yg[l]);
|
|
}
|
|
}
|
|
}
|
|
calcpool[idx]=buf;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function fills RBF model by zeros, also cleans up debug |
|
|
//| fields. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ZeroFill(CRBFV3Model &s,int nx,int ny)
|
|
{
|
|
s.m_bftype=0;
|
|
s.m_bfparam=0;
|
|
s.m_nc=0;
|
|
CAblasF::RSetAllocV(nx,1.0,s.m_s);
|
|
CAblasF::RSetAllocM(ny,nx+1,0.0,s.m_v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Reallocates calcBuf if necessary, reuses previously allocated |
|
|
//| space if possible. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::AllocateCalcBuffer(CRBFV3Model &s,CRBFV3CalcBuffer &buf)
|
|
{
|
|
if(CAp::Len(buf.m_x)<s.m_nx)
|
|
buf.m_x.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_x123)<s.m_nx)
|
|
buf.m_x123.Resize(s.m_nx);
|
|
if(CAp::Len(buf.m_y123)<s.m_ny)
|
|
buf.m_y123.Resize(s.m_ny);
|
|
if(CAp::Len(buf.m_xg)<4)
|
|
buf.m_xg.Resize(4);
|
|
if(CAp::Len(buf.m_yg)<s.m_ny)
|
|
buf.m_yg.Resize(s.m_ny);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive function that merges points, used by |
|
|
//| PreprocessDataset() |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::PreprocessDatasetRec(CMatrixDouble &xbuf,
|
|
CMatrixDouble &ybuf,
|
|
CRowInt &initidx,
|
|
int wrk0,int wrk1,int nx,
|
|
int ny,double mergetol,
|
|
CRowDouble &tmpboxmin,
|
|
CRowDouble &tmpboxmax,
|
|
CMatrixDouble &xout,
|
|
CMatrixDouble &yout,
|
|
CRowInt &raw2wrkmap,
|
|
CRowInt &wrk2rawmap,
|
|
int &nout)
|
|
{
|
|
//--- create variables
|
|
int k0=0;
|
|
int k1=0;
|
|
int largestdim=0;
|
|
double splitval=0;
|
|
|
|
if(wrk1<=wrk0)
|
|
return;
|
|
//--- Analyze current working set
|
|
CAblasF::RAllocV(nx,tmpboxmin);
|
|
CAblasF::RAllocV(nx,tmpboxmax);
|
|
CAblasF::RCopyRV(nx,xbuf,wrk0,tmpboxmin);
|
|
CAblasF::RCopyRV(nx,xbuf,wrk0,tmpboxmax);
|
|
for(int i=wrk0+1; i<wrk1; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
tmpboxmin.Set(j,MathMin(tmpboxmin[j],xbuf.Get(i,j)));
|
|
tmpboxmax.Set(j,MathMax(tmpboxmax[j],xbuf.Get(i,j)));
|
|
}
|
|
}
|
|
for(int j=1; j<nx; j++)
|
|
{
|
|
if((tmpboxmax[j]-tmpboxmin[j])>(tmpboxmax[largestdim]-tmpboxmin[largestdim]))
|
|
largestdim=j;
|
|
}
|
|
//--- Handle basecase or perform recursive split
|
|
if(wrk1-wrk0==1 || (tmpboxmax[largestdim]-tmpboxmin[largestdim])<(mergetol*MathMax(CAblasF::RMaxAbsV(nx,tmpboxmax),MathMax(CAblasF::RMaxAbsV(nx,tmpboxmin),1))))
|
|
{
|
|
//--- Merge all points, output
|
|
CAblasF::RSetR(nx,0.0,xout,nout);
|
|
CAblasF::RSetR(ny,0.0,yout,nout);
|
|
for(int i=wrk0; i<wrk1; i++)
|
|
{
|
|
CAblasF::RAddRR(nx,1.0/(double)(wrk1-wrk0),xbuf,i,xout,nout);
|
|
CAblasF::RAddRR(ny,1.0/(double)(wrk1-wrk0),ybuf,i,yout,nout);
|
|
raw2wrkmap.Set(initidx[i],nout);
|
|
}
|
|
wrk2rawmap.Set(nout,initidx[wrk0]);
|
|
nout++;
|
|
}
|
|
else
|
|
{
|
|
//--- Perform recursive split along largest axis
|
|
splitval=0.5*(tmpboxmax[largestdim]+tmpboxmin[largestdim]);
|
|
k0=wrk0;
|
|
k1=wrk1-1;
|
|
while(k0<=k1)
|
|
{
|
|
if(xbuf.Get(k0,largestdim)<=splitval)
|
|
{
|
|
k0++;
|
|
continue;
|
|
}
|
|
if(xbuf.Get(k1,largestdim)>splitval)
|
|
{
|
|
k1--;
|
|
continue;
|
|
}
|
|
CApServ::SwapRows(xbuf,k0,k1,nx);
|
|
CApServ::SwapRows(ybuf,k0,k1,ny);
|
|
CApServ::SwapElementsI(initidx,k0,k1);
|
|
k0++;
|
|
k1--;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(k0>wrk0 && k1<wrk1-1,__FUNCTION__+": integrity check 5843 in the recursive subdivision code failed"))
|
|
return;
|
|
if(!CAp::Assert(k0==k1+1,__FUNCTION__+": integrity check 5364 in the recursive subdivision code failed"))
|
|
return;
|
|
PreprocessDatasetRec(xbuf,ybuf,initidx,wrk0,k0,nx,ny,mergetol,tmpboxmin,tmpboxmax,xout,yout,raw2wrkmap,wrk2rawmap,nout);
|
|
PreprocessDatasetRec(xbuf,ybuf,initidx,k0,wrk1,nx,ny,mergetol,tmpboxmin,tmpboxmax,xout,yout,raw2wrkmap,wrk2rawmap,nout);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function preprocesses dataset by: |
|
|
//| * merging non - distinct points |
|
|
//| * centering points |
|
|
//| * applying user scale to X - values |
|
|
//| * performing additional scaling of X - values |
|
|
//| * normalizing Y - values |
|
|
//| INPUT PARAMETERS: |
|
|
//| XRaw - array[NRaw, NX], variable values |
|
|
//| YRaw - array[NRaw, NY], target values |
|
|
//| XScaleRaw - array[NX], user scales |
|
|
//| NRaw, NX, NY - metrics; N > 0, NX > 0, NY > 0 |
|
|
//| BFType - basis function type |
|
|
//| BFParamRaw - initial value for basis function paramerer (before|
|
|
//| applying additional rescaling AddXRescaleAplied) |
|
|
//| LambdaVRaw - smoothing coefficient, as specified by user |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XWrk - array[NWrk, NX], processed points, |
|
|
//| XWrk = (XRaw - XShift) / XScaleWrk |
|
|
//| YWrk - array[NWrk, NY], targets, scaled by dividing by |
|
|
//| YScale |
|
|
//| PointIndexes - array[NWrk], point indexes in the original |
|
|
//| dataset |
|
|
//| NWrk - number of points after preprocessing, |
|
|
//| 0 < NWrk <= NRaw |
|
|
//| XScaleWrk - array[NX], |
|
|
//| XScaleWrk[] = XScaleRaw[] * AddXRescaleAplied |
|
|
//| XShift - array[NX], centering coefficients |
|
|
//| YScale - common scaling for targets |
|
|
//| BFParamWrk - BFParamRaw / AddXRescaleAplied |
|
|
//| LambdaVWrk - LambdaV after dataset scaling, automatically |
|
|
//| adjusted for dataset spread |
|
|
//| AddXRescaleAplied - additional scaling applied after user |
|
|
//| scaling |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::PreprocessDataSet(CMatrixDouble &XRaw,double mergetol,
|
|
CMatrixDouble &YRaw,CRowDouble &XScaleRaw,
|
|
int nraw,int nx,int ny,int bftype,
|
|
double bfparamraw,double lambdavraw,
|
|
CMatrixDouble &xwrk,CMatrixDouble &ywrk,
|
|
CRowInt &raw2wrkmap,CRowInt &wrk2rawmap,
|
|
int &nwrk,CRowDouble &xscalewrk,
|
|
CRowDouble &xshift,double &bfparamwrk,
|
|
double &lambdavwrk,double &addxrescaleaplied)
|
|
{
|
|
//--- create variables
|
|
double diag2=0;
|
|
double v=0;
|
|
CMatrixDouble xbuf;
|
|
CMatrixDouble ybuf;
|
|
CRowDouble tmp0;
|
|
CRowDouble tmp1;
|
|
CRowDouble boxmin;
|
|
CRowDouble boxmax;
|
|
CRowInt initidx;
|
|
CMatrixDouble xraw=XRaw;
|
|
CMatrixDouble yraw=YRaw;
|
|
CRowDouble xscaleraw=XScaleRaw;
|
|
|
|
xwrk.Resize(0,0);
|
|
ywrk.Resize(0,0);
|
|
raw2wrkmap.Resize(0);
|
|
wrk2rawmap.Resize(0);
|
|
nwrk=0;
|
|
xscalewrk.Resize(0);
|
|
xshift.Resize(0);
|
|
bfparamwrk=0;
|
|
lambdavwrk=0;
|
|
addxrescaleaplied=0;
|
|
//--- check
|
|
if(!CAp::Assert(nraw>=1,__FUNCTION__+": integrity check 7295 failed"))
|
|
return;
|
|
//--- Scale dataset:
|
|
//--- * first, scale it according to user-supplied scale
|
|
//--- * second, analyze original dataset and rescale it one more time (same scaling across
|
|
//--- all dimensions) so it has zero mean and unit deviation
|
|
//--- As a result, user-supplied scaling handles dimensionality issues and our additional
|
|
//--- scaling normalizes data.
|
|
//--- After this block we have NRaw-sized dataset in XWrk/YWrk
|
|
CAblasF::RCopyAllocV(nx,xscaleraw,xscalewrk);
|
|
CAblasF::RSetAllocV(nx,0.0,xshift);
|
|
CAblasF::RAllocM(nraw,nx,xwrk);
|
|
for(int i=0; i<nraw; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
xwrk.Set(i,j,xraw.Get(i,j)/xscalewrk[j]);
|
|
xshift.Set(j,xshift[j]+xwrk.Get(i,j));
|
|
}
|
|
}
|
|
CAblasF::RMulV(nx,1.0/nraw,xshift);
|
|
v=0;
|
|
for(int i=0; i<=nraw-1; i++)
|
|
for(int j=0; j<nx; j++)
|
|
v+=CMath::Sqr(xwrk.Get(i,j)-xshift[j]);
|
|
addxrescaleaplied=MathSqrt((v+MathSqrt(CMath::m_machineepsilon))/(nraw*nx));
|
|
bfparamwrk=bfparamraw;
|
|
if(bftype==1)
|
|
{
|
|
//--- Basis function parameter needs rescaling
|
|
if(bfparamraw<0.0)
|
|
bfparamwrk=AutoDetectScaleParameter(xwrk,nraw,nx)*-bfparamraw/addxrescaleaplied;
|
|
else
|
|
bfparamwrk/=addxrescaleaplied;
|
|
}
|
|
else
|
|
{
|
|
if(bftype==2)
|
|
{
|
|
//--- Thin plate splines need special scaling; no params to rescale
|
|
addxrescaleaplied*=m_polyharmonic2scale;
|
|
}
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": integrity check 0632 failed");
|
|
return;
|
|
}
|
|
}
|
|
CAblasF::RMulV(nx,addxrescaleaplied,xscalewrk);
|
|
for(int i=0; i<nraw; i++)
|
|
for(int j=0; j<nx; j++)
|
|
xwrk.Set(i,j,(xraw.Get(i,j)-xshift[j])/xscalewrk[j]);
|
|
CAblasF::RCopyAllocM(nraw,ny,yraw,ywrk);
|
|
//--- Merge nondistinct points
|
|
CAblasF::IAllocV(nraw,initidx);
|
|
for(int i=0; i<nraw; i++)
|
|
initidx.Set(i,i);
|
|
CAblasF::RCopyAllocM(nraw,nx,xwrk,xbuf);
|
|
CAblasF::RCopyAllocM(nraw,ny,ywrk,ybuf);
|
|
CAblasF::IAllocV(nraw,raw2wrkmap);
|
|
CAblasF::IAllocV(nraw,wrk2rawmap);
|
|
nwrk=0;
|
|
PreprocessDatasetRec(xbuf,ybuf,initidx,0,nraw,nx,ny,mergetol,tmp0,tmp1,xwrk,ywrk,raw2wrkmap,wrk2rawmap,nwrk);
|
|
//--- Compute LambdaV:
|
|
//--- * compute bounding box
|
|
//--- * compute DIAG2 = squared diagonal of the box
|
|
//--- * set LambdaVWrk = LambdaVRaw times upper bound of the basis function value
|
|
CAblasF::RAllocV(nx,boxmin);
|
|
CAblasF::RAllocV(nx,boxmax);
|
|
CAblasF::RCopyRV(nx,xwrk,0,boxmin);
|
|
CAblasF::RCopyRV(nx,xwrk,0,boxmax);
|
|
for(int i=1; i<nwrk; i++)
|
|
{
|
|
CAblasF::RMergeMinRV(nx,xwrk,i,boxmin);
|
|
CAblasF::RMergeMaxRV(nx,xwrk,i,boxmax);
|
|
}
|
|
diag2=0;
|
|
for(int i=0; i<nx; i++)
|
|
diag2+=CMath::Sqr(boxmax[i]-boxmin[i]);
|
|
diag2=MathMax(diag2,1);
|
|
if(bftype==1)
|
|
lambdavwrk=lambdavraw*MathSqrt(diag2+bfparamwrk*bfparamwrk);
|
|
else
|
|
{
|
|
if(bftype==2)
|
|
lambdavwrk=lambdavraw*diag2*MathMax(MathAbs(0.5*MathLog(diag2)),1.0);
|
|
else
|
|
{
|
|
lambdavwrk=lambdavraw;
|
|
CAp::Assert(false,__FUNCTION__+": integrity check 7232 failed");
|
|
return;
|
|
}
|
|
}
|
|
lambdavwrk/=CMath::Sqr(addxrescaleaplied);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function selects NSpec global nodes for approximate cardinal|
|
|
//| basis functions. |
|
|
//| This function has O(N*NSpec) running time and O(N) memory |
|
|
//| requirements. |
|
|
//| Each approximate cardinal basis function is a combination of |
|
|
//| several local nodes (ones nearby to the center) and several |
|
|
//| global nodes(ones scattered over entire dataset span). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::SelectGlobalNodes(CMatrixDouble &xx,int n,int nx,
|
|
CRowInt &existingnodes,int nexisting,
|
|
int nspec,CRowInt &nodes,
|
|
int &nchosen,double &maxdist)
|
|
{
|
|
//--- create variables
|
|
int k=0;
|
|
bool busy[];
|
|
double v=0;
|
|
double vv=0;
|
|
CRowDouble d2;
|
|
CRowDouble x;
|
|
|
|
nchosen=0;
|
|
maxdist=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 6429 failed"))
|
|
return;
|
|
if(!CAp::Assert(nexisting>=0,__FUNCTION__+": integrity check 6412 failed"))
|
|
return;
|
|
if(!CAp::Assert(nspec>=1,__FUNCTION__+": integrity check 6430 failed"))
|
|
return;
|
|
|
|
nspec=MathMin(nspec,n);
|
|
CAblasF::RSetAllocV(n,1.0E50,d2);
|
|
CAblasF::RSetAllocV(nx,0.0,x);
|
|
CAblasF::BSetAllocV(n,false,busy);
|
|
if(nexisting==0)
|
|
{
|
|
//--- No initial grid is provided, start distance evaluation from the data center
|
|
for(int i=0; i<n; i++)
|
|
CAblasF::RCopyRV(nx,xx,i,x);
|
|
CAblasF::RMulV(nx,1.0/(double)n,x);
|
|
}
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": NExisting<>0");
|
|
return;
|
|
}
|
|
|
|
CAblasF::IAllocV(nspec,nodes);
|
|
nchosen=0;
|
|
maxdist=CMath::m_maxrealnumber;
|
|
while(nchosen<nspec)
|
|
{
|
|
//--- Update distances using last added point stored in X.
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
v=0;
|
|
for(k=0; k<nx; k++)
|
|
{
|
|
vv=x[k]-xx.Get(j,k);
|
|
v=v+vv*vv;
|
|
}
|
|
d2.Set(j,MathMin(d2[j],v));
|
|
}
|
|
//--- Select point with largest distance, add
|
|
k=0;
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
if(d2[j]>d2[k] && !busy[j])
|
|
k=j;
|
|
}
|
|
if(busy[k])
|
|
break;
|
|
maxdist=MathMin(maxdist,d2[k]);
|
|
nodes.Set(nchosen,k);
|
|
busy[k]=true;
|
|
CAblasF::RCopyRV(nx,xx,k,x);
|
|
nchosen++;
|
|
}
|
|
maxdist=MathSqrt(maxdist);
|
|
//--- check
|
|
if(!CAp::Assert(nchosen>=1 || nexisting>0,__FUNCTION__+": integrity check 6431 failed"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds simplified tagged KD-tree: it assigns a tag |
|
|
//| (index in the dataset) to each point, then drops most points |
|
|
//| (leaving approximately 1 / ReduceFactor of the entire dataset) |
|
|
//| trying to spread residual points uniformly, and then constructs |
|
|
//| KD-tree. |
|
|
//| It ensures that at least min(N, MinSize) points is retained. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::BuildSimplifiedKDTree(CMatrixDouble &xx,int n,int nx,
|
|
int reducefactor,int minsize,
|
|
CKDTree &kdt)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble xs;
|
|
CRowInt idx;
|
|
CHighQualityRandState rs;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
if(!CAp::Assert(reducefactor>=1,__FUNCTION__+": ReduceFactor<1"))
|
|
return;
|
|
if(!CAp::Assert(minsize>=0,__FUNCTION__+": ReduceFactor<1"))
|
|
return;
|
|
CHighQualityRand::HQRndSeed(7674,45775,rs);
|
|
int ns=MathMax((int)MathRound((double)n/(double)reducefactor),MathMax(minsize,1));
|
|
ns=MathMin(ns,n);
|
|
CAblasF::IAllocV(n,idx);
|
|
CAblasF::RAllocM(ns,nx,xs);
|
|
for(int i=0; i<n; i++)
|
|
idx.Set(i,i);
|
|
for(int i=0; i<=ns-1; i++)
|
|
{
|
|
int j=i+CHighQualityRand::HQRndUniformI(rs,n-i);
|
|
int k=idx[i];
|
|
idx.Set(i,idx[j]);
|
|
idx.Set(j,k);
|
|
CAblasF::RCopyRR(nx,xx,idx[i],xs,i);
|
|
}
|
|
CNearestNeighbor::KDTreeBuildTagged(xs,idx,ns,nx,0,2,kdt);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Compute design matrices for the target - scatter preconditioner |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeTargetScatterDesignMatrices(CMatrixDouble &xx,
|
|
int ntotal,
|
|
int nx,
|
|
int functype,
|
|
double funcparam,
|
|
CRowInt &workingnodes,
|
|
int nwrk,
|
|
CRowInt &scatternodes,
|
|
int nscatter,
|
|
CMatrixDouble &atwrk,
|
|
CMatrixDouble &atsctr)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
double vv=0;
|
|
int ni=0;
|
|
int nj=0;
|
|
double alpha2=0;
|
|
//--- Compute working set and scatter set design matrices ATWrk and ATSctr
|
|
//--- ATWrk is a (NWrk+NX+1)*NWrk matrix whose entries a[i,j] store:
|
|
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[wrj[j]])
|
|
//--- * for NWrk<=I<NWrk+NX X[wrk[j]], coordinate #(i-NWrk)
|
|
//--- * for I=NWrk+NX 1.0
|
|
//--- ATSctr is a (NWrk+NX+1)*NScatter matrix whose entries a[i,j] store:
|
|
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[scatter[j]])
|
|
//--- * for NWrk<=I<NWrk+NX X[scatter[j]], coordinate #(i-NWrk)
|
|
//--- * for I=NWrk+NX 1.0
|
|
//--- check
|
|
if(!CAp::Assert(functype==1 || functype==2 || functype==3,__FUNCTION__+": unexpected basis function type"))
|
|
return;
|
|
|
|
alpha2=funcparam*funcparam;
|
|
CAblasF::RAllocM(nwrk+nx+1,nwrk,atwrk);
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
ni=workingnodes[i];
|
|
for(j=i; j<nwrk; j++)
|
|
{
|
|
nj=workingnodes[j];
|
|
v=0;
|
|
for(k=0; k<nx; k++)
|
|
{
|
|
vv=xx.Get(ni,k)-xx.Get(nj,k);
|
|
v=v+vv*vv;
|
|
}
|
|
switch(functype)
|
|
{
|
|
case 1:
|
|
v=-MathSqrt(v+alpha2);
|
|
break;
|
|
case 2:
|
|
if(v!=0)
|
|
v*=0.5*MathLog(v);
|
|
else
|
|
v=0;
|
|
break;
|
|
case 3:
|
|
v*=MathSqrt(v);
|
|
break;
|
|
}
|
|
atwrk.Set(i,j,v);
|
|
atwrk.Set(j,i,v);
|
|
}
|
|
}
|
|
for(j=0; j<nwrk; j++)
|
|
{
|
|
nj=workingnodes[j];
|
|
for(i=0; i<nx; i++)
|
|
atwrk.Set(nwrk+i,j,xx.Get(nj,i));
|
|
}
|
|
for(j=0; j<nwrk; j++)
|
|
atwrk.Set(nwrk+nx,j,1.0);
|
|
if(nscatter>0)
|
|
{
|
|
//--- We have scattered points too
|
|
CAblasF::RAllocM(nwrk+nx+1,nscatter,atsctr);
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
ni=workingnodes[i];
|
|
for(j=0; j<nscatter; j++)
|
|
{
|
|
nj=scatternodes[j];
|
|
v=0;
|
|
for(k=0; k<nx; k++)
|
|
{
|
|
vv=xx.Get(ni,k)-xx.Get(nj,k);
|
|
v=v+vv*vv;
|
|
}
|
|
switch(functype)
|
|
{
|
|
case 1:
|
|
v=-MathSqrt(v+alpha2);
|
|
break;
|
|
case 2:
|
|
if(v!=0)
|
|
v*=0.5*MathLog(v);
|
|
else
|
|
v=0;
|
|
break;
|
|
case 3:
|
|
v*=MathSqrt(v);
|
|
break;
|
|
}
|
|
atsctr.Set(i,j,v);
|
|
}
|
|
}
|
|
for(j=0; j<nscatter; j++)
|
|
{
|
|
nj=scatternodes[j];
|
|
CAblasF::RCopyRR(nx,xx,nj,atsctr,nwrk+i);
|
|
}
|
|
for(j=0; j<nscatter; j++)
|
|
atsctr.Set(nwrk+nx,j,1.0);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| ACBF preconditioner generation basecase. |
|
|
//| PARAMETERS: |
|
|
//| Builder - ACBF builder object |
|
|
//| Wrk0, Wrk1 - elements [Wrk0...Wrk1 - 1] of Builder.WrkIdx[] |
|
|
//| array store row indexes of XX that are processed|
|
|
//| OUTPUT: |
|
|
//| Builder.OutputPool is updated with new chunks |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeACBFPreconditionerBasecase(CACBFBuilder &builder,
|
|
CACBFBuffer &buf,
|
|
int wrk0,
|
|
int wrk1)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int widx=0;
|
|
int targetidx=0;
|
|
int nx=0;
|
|
int nglobal=0;
|
|
int nlocal=0;
|
|
int ncorrection=0;
|
|
int ncenters=0;
|
|
int nchosen=0;
|
|
int ncoeff=0;
|
|
int batchsize=0;
|
|
int nk=0;
|
|
int nq=0;
|
|
CRowDouble x;
|
|
CRowDouble batchcenter;
|
|
CRowInt idummy;
|
|
double localrad=0;
|
|
double currentrad=0;
|
|
double reg=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double mx=0;
|
|
double maxdist2=0;
|
|
int ortbasissize=0;
|
|
CRowInt ortbasismap;
|
|
CACBFChunk precchunk;
|
|
int expansionscount=0;
|
|
CMatrixDouble dbgb;
|
|
double dbgerrnodes=0;
|
|
double dbgerrort=0;
|
|
double dbgcondq=0;
|
|
double dbgmaxc=0;
|
|
|
|
if(wrk1<=wrk0)
|
|
return;
|
|
nx=builder.m_nx;
|
|
nglobal=builder.m_nglobal;
|
|
nlocal=builder.m_nlocal;
|
|
ncorrection=builder.m_ncorrection;
|
|
reg=MathSqrt(CMath::m_machineepsilon);
|
|
CAblasF::RAllocV(nx,x);
|
|
expansionscount=0;
|
|
//--- First, select a batch of central points and compute batch center
|
|
batchsize=wrk1-wrk0;
|
|
CAblasF::IAllocV(batchsize,buf.m_currentnodes);
|
|
CAblasF::RSetAllocV(nx,0.0,batchcenter);
|
|
for(i=0; i<batchsize; i++)
|
|
{
|
|
targetidx=builder.m_wrkidx[wrk0+i];
|
|
buf.m_currentnodes.Set(i,targetidx);
|
|
buf.m_bflags[targetidx]=true;
|
|
CAblasF::RAddRV(nx,1.0/(double)batchsize,builder.m_xx,builder.m_wrkidx[wrk0+i],batchcenter);
|
|
}
|
|
ncenters=batchsize;
|
|
//--- Then, add a hull of nearest neighbors and compute its radius
|
|
localrad=0;
|
|
for(widx=0; widx<batchsize; widx++)
|
|
{
|
|
//--- Select immediate neighbors
|
|
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0+widx],x);
|
|
nq=CNearestNeighbor::KDTreeTsQueryKNN(builder.m_kdt,buf.m_kdtbuf,x,nlocal,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt,buf.m_kdtbuf,buf.m_neighbors);
|
|
CNearestNeighbor::KDTreeTsQueryResultsDistances(builder.m_kdt,buf.m_kdtbuf,buf.m_d);
|
|
for(k=0; k<nq; k++)
|
|
{
|
|
nk=buf.m_neighbors[k];
|
|
if(!buf.m_bflags[nk])
|
|
{
|
|
buf.m_bflags[nk]=true;
|
|
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
|
|
buf.m_currentnodes.Set(ncenters,nk);
|
|
ncenters++;
|
|
v=0;
|
|
for(j=0; j<nx; j++)
|
|
v+=(builder.m_xx.Get(nk,j)-batchcenter[j])*(builder.m_xx.Get(nk,j)-batchcenter[j]);
|
|
localrad=MathMax(localrad,v);
|
|
}
|
|
}
|
|
}
|
|
localrad=MathSqrt(localrad);
|
|
currentrad=localrad;
|
|
//--- Add global grid
|
|
if(nglobal>0)
|
|
{
|
|
for(k=0; k<nglobal; k++)
|
|
{
|
|
nk=builder.m_globalgrid[k];
|
|
if(!buf.m_bflags[nk])
|
|
{
|
|
buf.m_bflags[nk]=true;
|
|
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
|
|
buf.m_currentnodes.Set(ncenters,nk);
|
|
ncenters++;
|
|
}
|
|
}
|
|
}
|
|
//--- Add local correction grid: select more distant neighbors
|
|
while(currentrad>0.0 && currentrad<builder.m_roughdatasetdiameter)
|
|
{
|
|
//--- Select neighbors within CurrentRad*Builder.CorrectorGrowth
|
|
if(expansionscount==0)
|
|
{
|
|
//--- First expansion, use simplified kd-tree #1
|
|
nq=CNearestNeighbor::KDTreeTsQueryRNN(builder.m_kdt1,buf.m_kdt1buf,batchcenter,currentrad*builder.m_correctorgrowth,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt1,buf.m_kdt1buf,buf.m_neighbors);
|
|
}
|
|
else
|
|
{
|
|
//--- Subsequent expansions, use simplified kd-tree #2
|
|
nq=CNearestNeighbor::KDTreeTsQueryRNN(builder.m_kdt2,buf.m_kdt2buf,batchcenter,currentrad*builder.m_correctorgrowth,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(builder.m_kdt2,buf.m_kdt2buf,buf.m_neighbors);
|
|
}
|
|
//--- Compute a grid of well-separated nodes using neighbors
|
|
CAblasF::RAllocM(nq,nx,buf.m_xq);
|
|
for(k=0; k<nq; k++)
|
|
{
|
|
nk=buf.m_neighbors[k];
|
|
for(j=0; j<nx; j++)
|
|
buf.m_xq.Set(k,j,builder.m_xx.Get(nk,j));
|
|
}
|
|
SelectGlobalNodes(buf.m_xq,nq,nx,idummy,0,ncorrection,buf.m_chosenneighbors,nchosen,maxdist2);
|
|
//--- Select neighbrs that are NOT within CurrentRad from the batch center
|
|
//--- and that are NOT already chosen.
|
|
for(k=0; k<nchosen; k++)
|
|
{
|
|
nk=buf.m_neighbors[buf.m_chosenneighbors[k]];
|
|
v=CAblasF::RDotVR(nx,batchcenter,builder.m_xx,nk);
|
|
v=MathSqrt(v);
|
|
if(!buf.m_bflags[nk] && v>currentrad)
|
|
{
|
|
buf.m_bflags[nk]=true;
|
|
CAblasF::IGrowV(ncenters+1,buf.m_currentnodes);
|
|
buf.m_currentnodes.Set(ncenters,nk);
|
|
ncenters++;
|
|
}
|
|
}
|
|
//--- Update radius and debug counters
|
|
currentrad*=builder.m_correctorgrowth;
|
|
expansionscount++;
|
|
}
|
|
//--- Clean up bFlags[]
|
|
for(k=0; k<ncenters; k++)
|
|
buf.m_bflags[buf.m_currentnodes[k]]=false;
|
|
//--- Compute working set and scatter set design matrices ATWrk and ATSctr
|
|
//--- ATWrk is a (NWrk+NX+1)*NWrk matrix whose entries a[i,j] store:
|
|
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[wrj[j]])
|
|
//--- * for NWrk<=I<NWrk+NX X[wrk[j]], coordinate #(i-NWrk)
|
|
//--- * for I=NWrk+NX 1.0
|
|
//--- ATSctr is a (NWrk+NX+1)*NScatter matrix whose entries a[i,j] store:
|
|
//--- * for I<NWrk BasisFunc(X[wrk[i]]-X[scatter[j]])
|
|
//--- * for NWrk<=I<NWrk+NX X[scatter[j]], coordinate #(i-NWrk)
|
|
//--- * for I=NWrk+NX 1.0
|
|
ComputeTargetScatterDesignMatrices(builder.m_xx,builder.m_ntotal,nx,builder.m_functype,builder.m_funcparam,buf.m_currentnodes,ncenters,buf.m_currentnodes,0,buf.m_atwrk,buf.m_atwrk);
|
|
//--- Prepare and solve linear system, coefficients are stored in rows of Buf.B
|
|
//--- Depending on whether the basis is conditionally positive definite (given current polynomial term type),
|
|
//--- we either:
|
|
//--- * use generic QR solver to solve the linear system (when the basis is not CPD)
|
|
//--- * use specialized CPD solver that is several times faster and is more accurate
|
|
if(builder.m_aterm!=1 || !IsCPDFunction(builder.m_functype,builder.m_aterm))
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(builder.m_lambdav>=0.0,__FUNCTION__+": integrity check 8363 failed"))
|
|
return;
|
|
if(!CAp::Assert(builder.m_aterm==1 || builder.m_aterm==2 || builder.m_aterm==3,__FUNCTION__+": integrity check 8364 failed"))
|
|
return;
|
|
//--- Basis function has no conditional positive definiteness guarantees (given the linear term type).
|
|
//--- Solve using QR decomposition.
|
|
ncoeff=ncenters+nx+1;
|
|
CAblasF::RSetAllocM(ncoeff,ncoeff+batchsize,0.0,buf.m_q);
|
|
for(i=0; i<ncenters; i++)
|
|
{
|
|
CAblasF::RCopyRR(ncenters,buf.m_atwrk,i,buf.m_q,i);
|
|
buf.m_q.Add(i,i,builder.m_lambdav);
|
|
}
|
|
switch(builder.m_aterm)
|
|
{
|
|
case 1:
|
|
//--- Linear term is used
|
|
for(i=0; i<=nx; i++)
|
|
for(j=0; j<ncenters; j++)
|
|
{
|
|
buf.m_q.Set(ncenters+i,j,buf.m_atwrk.Get(ncenters+i,j));
|
|
buf.m_q.Set(j,ncenters+i,buf.m_atwrk.Get(ncenters+i,j));
|
|
}
|
|
break;
|
|
case 2:
|
|
//--- Constant term is used
|
|
for(i=0; i<nx; i++)
|
|
buf.m_q.Set(ncenters+i,ncenters+i,1.0);
|
|
for(j=0; j<ncenters; j++)
|
|
{
|
|
buf.m_q.Set(ncenters+nx,j,1.0);
|
|
buf.m_q.Set(j,ncenters+nx,1.0);
|
|
}
|
|
break;
|
|
case 3:
|
|
//--- Zero term is used
|
|
for(i=0; i<=nx; i++)
|
|
buf.m_q.Set(ncenters+i,ncenters+i,1.0);
|
|
break;
|
|
}
|
|
for(i=0; i<batchsize; i++)
|
|
buf.m_q.Set(i,ncoeff+i,1.0);
|
|
mx=1.0;
|
|
for(i=0; i<ncoeff; i++)
|
|
{
|
|
for(j=i; j<ncoeff; j++)
|
|
mx=MathMax(mx,MathAbs(buf.m_q.Get(i,j)));
|
|
}
|
|
for(j=0; j<ncoeff; j++)
|
|
buf.m_q.Add(j,j,reg*mx*CApServ::PosSign(buf.m_q.Get(j,j)));
|
|
COrtFac::RMatrixQR(buf.m_q,ncoeff,ncoeff+batchsize,buf.m_tau);
|
|
CAblasF::RAllocM(batchsize,ncoeff,buf.m_b);
|
|
CAblas::RMatrixTranspose(ncoeff,batchsize,buf.m_q,0,ncoeff,buf.m_b,0,0);
|
|
CAblas::RMatrixRightTrsM(batchsize,ncoeff,buf.m_q,0,0,true,false,1,buf.m_b,0,0);
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(builder.m_lambdav>=0.0,__FUNCTION__+": integrity check 8368 failed"))
|
|
return;
|
|
if(!CAp::Assert(builder.m_aterm==1,__FUNCTION__+": integrity check 7365 failed"))
|
|
return;
|
|
ncoeff=ncenters+nx+1;
|
|
//--- First, compute orthogonal basis of space spanned by polynomials of degree 1
|
|
CAblasF::RAllocM(ncenters,ncenters,buf.m_r);
|
|
CAblasF::RAllocM(nx+1,ncenters,buf.m_q1);
|
|
CAblasF::IAllocV(nx+1,ortbasismap);
|
|
CAblasF::RSetR(ncenters,1/MathSqrt(ncenters),buf.m_q1,0);
|
|
buf.m_r.Set(0,0,MathSqrt(ncenters));
|
|
ortbasismap.Set(0,nx);
|
|
ortbasissize=1;
|
|
CAblasF::RAllocV(ncenters,buf.m_z);
|
|
for(k=0; k<nx; k++)
|
|
{
|
|
CAblasF::RCopyRV(ncenters,buf.m_atwrk,ncenters+k,buf.m_z);
|
|
v=MathSqrt(CAblasF::RDotV2(ncenters,buf.m_z));
|
|
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,true);
|
|
vv=MathSqrt(CAblasF::RDotV2(ncenters,buf.m_z));
|
|
if(vv>(MathSqrt(CMath::m_machineepsilon)*(v+1)))
|
|
{
|
|
CAblasF::RCopyMulVR(ncenters,1/vv,buf.m_z,buf.m_q1,ortbasissize);
|
|
CAblasF::RCopyVC(ortbasissize,buf.m_y,buf.m_r,ortbasissize);
|
|
buf.m_r.Set(ortbasissize,ortbasissize,vv);
|
|
ortbasismap.Set(ortbasissize,k);
|
|
ortbasissize++;
|
|
}
|
|
}
|
|
//--- Second, compute system matrix Q and target values for cardinal basis functions B.
|
|
//--- The Q is conditionally positive definite, i.e. x'*Q*x>0 for any x satisfying orthogonality conditions
|
|
//--- (orthogonal with respect to basis stored in Q1).
|
|
CAblasF::RSetAllocM(ncenters,ncenters,0.0,buf.m_q);
|
|
for(i=0; i<ncenters; i++)
|
|
CAblasF::RCopyRR(ncenters,buf.m_atwrk,i,buf.m_q,i);
|
|
CAblasF::RSetAllocM(batchsize,ncoeff,0.0,buf.m_b);
|
|
for(i=0; i<batchsize; i++)
|
|
buf.m_b.Set(i,i,1.0);
|
|
for(i=0; i<ncenters; i++)
|
|
buf.m_q.Add(i,i,builder.m_lambdav);
|
|
//--- Transform Q from conditionally positive definite to the (simply) positive definite one:
|
|
//--- multiply linear system Q*x=RHS from both sides by (I-Q1'*Q1), apply additional regularization.
|
|
//--- NOTE: RHS is also multiplied by (I-Q1'*Q1), but from the left only.
|
|
CAblasF::RAllocV(ncenters,buf.m_z);
|
|
for(i=0; i<ncenters; i++)
|
|
{
|
|
CAblasF::RCopyRV(ncenters,buf.m_q,i,buf.m_z);
|
|
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
|
|
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_q,i);
|
|
}
|
|
for(i=0; i<ncenters; i++)
|
|
{
|
|
CAblasF::RCopyCV(ncenters,buf.m_q,i,buf.m_z);
|
|
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
|
|
CAblasF::RCopyVC(ncenters,buf.m_z,buf.m_q,i);
|
|
}
|
|
for(i=0; i<batchsize; i++)
|
|
{
|
|
CAblasF::RCopyRV(ncenters,buf.m_b,i,buf.m_z);
|
|
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,false);
|
|
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_b,i);
|
|
}
|
|
mx=1.0;
|
|
for(i=0; i<ncenters; i++)
|
|
mx=MathMax(mx,MathAbs(buf.m_q.Get(i,i)));
|
|
for(i=0; i<ncenters; i++)
|
|
{
|
|
CAblasF::RCopyRV(ncenters,buf.m_q,i,buf.m_z);
|
|
for(j=0; j<ortbasissize; j++)
|
|
CAblasF::RAddRV(ncenters,mx*buf.m_q1.Get(j,i),buf.m_q1,j,buf.m_z);
|
|
CAblasF::RCopyVR(ncenters,buf.m_z,buf.m_q,i);
|
|
}
|
|
if(builder.m_dodetailedtrace)
|
|
{
|
|
//--- Compute condition number for future reports
|
|
dbgcondq=1/(CRCond::SPDMatrixRCond(buf.m_q,ncenters,false)+CMath::m_machineepsilon);
|
|
}
|
|
else
|
|
{
|
|
dbgcondq=0;
|
|
}
|
|
for(i=0; i<ncenters; i++)
|
|
buf.m_q.Add(i,i,reg*mx);
|
|
//--- Perform Cholesky factorization, solve and obtain RBF coefficients (we still have
|
|
//--- to compute polynomial term - it will be done later)
|
|
if(!CTrFac::SPDMatrixCholeskyRec(buf.m_q,0,ncenters,false,buf.m_choltmp))
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": ACBF solver failed due to extreme degeneracy");
|
|
return;
|
|
}
|
|
CAblas::RMatrixRightTrsM(batchsize,ncenters,buf.m_q,0,0,false,false,1,buf.m_b,0,0);
|
|
CAblas::RMatrixRightTrsM(batchsize,ncenters,buf.m_q,0,0,false,false,0,buf.m_b,0,0);
|
|
//--- Now, having RBF coefficients we can compute residual from fitting ACBF targets
|
|
//--- with pure RBF term and fit polynomial term to this residual. In the ideal world
|
|
//--- it should result in the nice and precise polynomial coefficients.
|
|
CAblasF::RAllocV(ncenters,buf.m_z);
|
|
CAblasF::RSetAllocM(batchsize,ncenters,0.0,buf.m_c);
|
|
for(i=0; i<batchsize; i++)
|
|
buf.m_c.Set(i,i,1.0);
|
|
CAblas::RMatrixGemm(batchsize,ncenters,ncenters,-1.0,buf.m_b,0,0,0,buf.m_atwrk,0,0,1,1.0,buf.m_c,0,0);
|
|
for(i=0; i<batchsize; i++)
|
|
{
|
|
CAblasF::RCopyRV(ncenters,buf.m_c,i,buf.m_z);
|
|
CAblas::RowWiseGramSchmidt(buf.m_q1,ortbasissize,ncenters,buf.m_z,buf.m_y,true);
|
|
CAblas::RMatrixTrsVect(ortbasissize,buf.m_r,0,0,true,false,0,buf.m_y,0);
|
|
for(j=0; j<=nx; j++)
|
|
buf.m_b.Set(i,ncenters+j,0.0);
|
|
for(j=0; j<ortbasissize; j++)
|
|
buf.m_b.Set(i,ncenters+ortbasismap[j],buf.m_y[j]);
|
|
}
|
|
//--- Trace if needeed
|
|
if(builder.m_dodetailedtrace)
|
|
{
|
|
CAblasF::RAllocM(batchsize,ncenters,dbgb);
|
|
CAblas::RMatrixGemm(batchsize,ncenters,ncoeff,-1.0,buf.m_b,0,0,0,buf.m_atwrk,0,0,0,0.0,dbgb,0,0);
|
|
for(i=0; i<batchsize; i++)
|
|
dbgb.Add(i,i,1.0);
|
|
dbgmaxc=0;
|
|
for(i=0; i<batchsize; i++)
|
|
dbgmaxc=MathMax(dbgmaxc,CAblasF::RMaxAbsR(ncenters,buf.m_b,i));
|
|
dbgerrnodes=0;
|
|
for(i=0; i<batchsize; i++)
|
|
dbgerrnodes+=CAblasF::RDotRR(ncenters,dbgb,i,dbgb,i);
|
|
dbgerrnodes=MathSqrt(dbgerrnodes/(batchsize*ncenters));
|
|
dbgerrort=0;
|
|
for(i=0; i<batchsize; i++)
|
|
for(j=0; j<ortbasissize; j++)
|
|
dbgerrort=MathMax(dbgerrort,MathAbs(CAblasF::RDotRR(ncenters,buf.m_b,i,buf.m_q1,j)));
|
|
CAp::Trace(StringFormat("[ACBF_subprob] BatchSize=%3d NCenters=%4d RadiusExpansions=%d cond(Q)=%.2E max|C|=%.2E rmsErr=%.2E OrtErr=%.2E\n",batchsize,ncenters,expansionscount,dbgcondq,dbgmaxc,dbgerrnodes,dbgerrort));
|
|
}
|
|
}
|
|
//--- Solve and save solution to Builder.ChunksPool
|
|
precchunk=builder.m_chunksproducer;
|
|
//--- check
|
|
if(!CAp::Assert(precchunk.m_ntargetrows==-117,__FUNCTION__+": integrity check 9724 failed"))
|
|
return;
|
|
if(!CAp::Assert(precchunk.m_ntargetcols==-119,__FUNCTION__+": integrity check 9725 failed"))
|
|
return;
|
|
precchunk.m_ntargetrows=batchsize;
|
|
precchunk.m_ntargetcols=ncoeff;
|
|
CAblasF::IAllocV(precchunk.m_ntargetrows,precchunk.m_targetrows);
|
|
CAblasF::IAllocV(precchunk.m_ntargetcols,precchunk.m_targetcols);
|
|
CAblasF::RAllocM(batchsize,ncoeff,precchunk.m_s);
|
|
for(widx=0; widx<batchsize; widx++)
|
|
precchunk.m_targetrows.Set(widx,builder.m_wrkidx[wrk0+widx]);
|
|
CAblasF::IAllocV(ncoeff,buf.m_perm);
|
|
for(k=0; k<ncoeff; k++)
|
|
{
|
|
if(k<ncenters)
|
|
precchunk.m_targetcols.Set(k,buf.m_currentnodes[k]);
|
|
else
|
|
precchunk.m_targetcols.Set(k,builder.m_ntotal+(k-ncenters));
|
|
buf.m_perm.Set(k,k);
|
|
}
|
|
CTSort::TagSortMiddleII(precchunk.m_targetcols,buf.m_perm,0,ncoeff);
|
|
for(widx=0; widx<batchsize; widx++)
|
|
{
|
|
for(k=0; k<ncoeff; k++)
|
|
precchunk.m_s.Set(widx,k,buf.m_b.Get(widx,buf.m_perm[k]));
|
|
}
|
|
builder.m_chunkspool=precchunk;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive ACBF preconditioner generation subroutine. |
|
|
//| PARAMETERS: |
|
|
//| Builder - ACBF builder object |
|
|
//| Wrk0, Wrk1 - elements [Wrk0...Wrk1 - 1] of Builder.WrkIdx[] |
|
|
//| array store row indexes of XX that are processed|
|
|
//| OUTPUT: |
|
|
//| Builder.OutputPool is updated with new chunks |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeACBFPreconditionerRecV2(CACBFBuilder &builder,
|
|
int wrk0,int wrk1)
|
|
{
|
|
//--- create variables
|
|
int nx=builder.m_nx;
|
|
int k0=0;
|
|
int k1=0;
|
|
int largestdim=0;
|
|
double splitval=0;
|
|
double basecasecomplexity=0;
|
|
CACBFBuffer buf;
|
|
|
|
if(wrk1<=wrk0)
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(0<=wrk0,__FUNCTION__+": wrk0 less zero"))
|
|
return;
|
|
basecasecomplexity=MathPow(builder.m_nglobal+builder.m_nlocal+2*builder.m_ncorrection,3);
|
|
//--- Retrieve temporary buffer
|
|
buf=builder.m_bufferpool;
|
|
//--- Analyze current working set
|
|
CAblasF::RAllocV(nx,buf.m_tmpboxmin);
|
|
CAblasF::RAllocV(nx,buf.m_tmpboxmax);
|
|
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0],buf.m_tmpboxmin);
|
|
CAblasF::RCopyRV(nx,builder.m_xx,builder.m_wrkidx[wrk0],buf.m_tmpboxmax);
|
|
for(int i=wrk0+1; i<=wrk1-1; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_tmpboxmin.Set(j,MathMin(buf.m_tmpboxmin[j],builder.m_xx.Get(builder.m_wrkidx[i],j)));
|
|
buf.m_tmpboxmax.Set(j,MathMax(buf.m_tmpboxmax[j],builder.m_xx.Get(builder.m_wrkidx[i],j)));
|
|
}
|
|
}
|
|
largestdim=0;
|
|
for(int j=1; j<nx; j++)
|
|
{
|
|
if((buf.m_tmpboxmax[j]-buf.m_tmpboxmin[j])>(buf.m_tmpboxmax[largestdim]-buf.m_tmpboxmin[largestdim]))
|
|
largestdim=j;
|
|
}
|
|
//--- Perform batch processing
|
|
ComputeACBFPreconditionerBasecase(builder,buf,wrk0,wrk1);
|
|
//--- Recycle temporary buffers
|
|
builder.m_bufferpool=buf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function generates ACBF (approximate cardinal basis |
|
|
//| functions) preconditioner. |
|
|
//| PARAMETERS: |
|
|
//| XX - dataset(X - values), array[N, NX] |
|
|
//| N - points count, N >= 1 |
|
|
//| NX - dimensions count, NX >= 1 |
|
|
//| FuncType - basis function type |
|
|
//| OUTPUT: |
|
|
//| SP - preconditioner, sparse matrix in CRS format |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeACBFPreconditioner(CMatrixDouble &xx,int n,
|
|
int nx,int functype,
|
|
double funcparam,int aterm,
|
|
int batchsize,int nglobal,
|
|
int nlocal,int ncorrection,
|
|
int correctorgrowth,
|
|
int simplificationfactor,
|
|
double lambdav,
|
|
CSparseMatrix &sp)
|
|
{
|
|
//--- create variables
|
|
CACBFBuilder builder;
|
|
CACBFBuffer bufferseed;
|
|
CACBFChunk chunkseed;
|
|
CACBFChunk precchunk;
|
|
int offs=0;
|
|
CRowInt idummy;
|
|
CRowInt rowsizes;
|
|
CRowDouble boxmin;
|
|
CRowDouble boxmax;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 2524 failed"))
|
|
return;
|
|
//--- Prepare builder
|
|
builder.m_dodetailedtrace=CAp::IsTraceEnabled("RBF.DETAILED");
|
|
builder.m_functype=functype;
|
|
builder.m_funcparam=funcparam;
|
|
builder.m_ntotal=n;
|
|
builder.m_nx=nx;
|
|
builder.m_batchsize=batchsize;
|
|
if(nglobal>0)
|
|
SelectGlobalNodes(xx,n,nx,idummy,0,nglobal,builder.m_globalgrid,builder.m_nglobal,builder.m_globalgridseparation);
|
|
else
|
|
builder.m_nglobal=0;
|
|
builder.m_nlocal=nlocal;
|
|
builder.m_ncorrection=ncorrection;
|
|
builder.m_correctorgrowth=correctorgrowth;
|
|
builder.m_lambdav=lambdav;
|
|
builder.m_aterm=aterm;
|
|
CAblasF::RCopyAllocM(n,nx,xx,builder.m_xx);
|
|
CAblasF::RAllocV(nx,boxmin);
|
|
CAblasF::RAllocV(nx,boxmax);
|
|
CAblasF::RCopyRV(nx,xx,0,boxmin);
|
|
CAblasF::RCopyRV(nx,xx,0,boxmax);
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
CAblasF::RMergeMinRV(nx,xx,i,boxmin);
|
|
CAblasF::RMergeMaxRV(nx,xx,i,boxmax);
|
|
}
|
|
builder.m_roughdatasetdiameter=0;
|
|
for(int i=0; i<nx; i++)
|
|
builder.m_roughdatasetdiameter+=CMath::Sqr(boxmax[i]-boxmin[i]);
|
|
builder.m_roughdatasetdiameter=MathSqrt(builder.m_roughdatasetdiameter);
|
|
CAblasF::IAllocV(n,builder.m_wrkidx);
|
|
for(int i=0; i<n; i++)
|
|
builder.m_wrkidx.Set(i,i);
|
|
CNearestNeighbor::KDTreeBuildTagged(xx,builder.m_wrkidx,n,nx,0,2,builder.m_kdt);
|
|
BuildSimplifiedKDTree(xx,n,nx,(int)MathRound(MathPow(simplificationfactor,nx)),(int)MathRound(MathPow(5,nx)),builder.m_kdt1);
|
|
BuildSimplifiedKDTree(xx,n,nx,(int)MathRound(MathPow(simplificationfactor,2*nx)),(int)MathRound(MathPow(5,nx)),builder.m_kdt2);
|
|
CAblasF::BSetAllocV(n,false,bufferseed.m_bflags);
|
|
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmin);
|
|
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmax);
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt,bufferseed.m_kdtbuf);
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt1,bufferseed.m_kdt1buf);
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(builder.m_kdt2,bufferseed.m_kdt2buf);
|
|
builder.m_bufferpool=bufferseed;
|
|
chunkseed.m_ntargetrows=-117;
|
|
chunkseed.m_ntargetcols=-119;
|
|
builder.m_chunksproducer=chunkseed;
|
|
builder.m_chunkspool=chunkseed;
|
|
//--- Prepare preconditioner matrix
|
|
ComputeACBFPreconditionerRecV2(builder,0,n);
|
|
CAblasF::ISetAllocV(n,-1,rowsizes);
|
|
precchunk=builder.m_chunkspool;
|
|
for(int i=0; i<precchunk.m_ntargetrows; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(rowsizes[precchunk.m_targetrows[i]]==-1,__FUNCTION__+": integrity check 2568 failed"))
|
|
return;
|
|
rowsizes.Set(precchunk.m_targetrows[i],precchunk.m_ntargetcols);
|
|
}
|
|
sp.m_MatrixType=1;
|
|
sp.m_M=n+nx+1;
|
|
sp.m_N=n+nx+1;
|
|
CAblasF::IAllocV(n+nx+2,sp.m_RIdx);
|
|
sp.m_RIdx.Set(0,0);
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(rowsizes[i]>0,__FUNCTION__+": integrity check 2668 failed"))
|
|
return;
|
|
sp.m_RIdx.Set(i+1,sp.m_RIdx[i]+rowsizes[i]);
|
|
}
|
|
for(int i=n; i<=n+nx; i++)
|
|
sp.m_RIdx.Set(i+1,sp.m_RIdx[i]+1);
|
|
CAblasF::IAllocV(sp.m_RIdx[sp.m_M],sp.m_Idx);
|
|
CAblasF::RAllocV(sp.m_RIdx[sp.m_M],sp.m_Vals);
|
|
for(int i=n; i<=n+nx; i++)
|
|
{
|
|
sp.m_Idx.Set(sp.m_RIdx[i],i);
|
|
sp.m_Vals.Set(sp.m_RIdx[i],1.0);
|
|
}
|
|
precchunk=builder.m_chunkspool;
|
|
for(int i=0; i<=precchunk.m_ntargetrows-1; i++)
|
|
{
|
|
offs=sp.m_RIdx[precchunk.m_targetrows[i]];
|
|
for(int j=0; j<=precchunk.m_ntargetcols-1; j++)
|
|
{
|
|
sp.m_Idx.Set(offs+j,precchunk.m_targetcols[j]);
|
|
sp.m_Vals.Set(offs+j,precchunk.m_s.Get(i,j));
|
|
}
|
|
}
|
|
sp.m_NInitialized=sp.m_RIdx[sp.m_M];
|
|
CSparse::SparseInitDUIdx(sp);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Basecase initialization routine for DDM solver. |
|
|
//| Appends an instance of RBF3DDMSubproblem to |
|
|
//| Solver.SubproblemsPool. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Solver - solver object. This function may be called from |
|
|
//| the multiple threads, so it is important to work|
|
|
//| with Solver object using only thread-safe |
|
|
//| functions. |
|
|
//| X - array[N, NX], dataset points |
|
|
//| N, NX - dataset metrics, N > 0, NX > 0 |
|
|
//| BFMatrix - basis function matrix object |
|
|
//| LambdaV - smoothing parameter |
|
|
//| SP - sparse ACBF preconditioner, |
|
|
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
|
|
//| format |
|
|
//| Buf - an instance of RBF3DDMBuffer, reusable temporary|
|
|
//| buffers |
|
|
//| TgtIdx - array[], contains indexes of points in the |
|
|
//| current target set. Elements [Tgt0, Tgt1) are |
|
|
//| processed by this function. |
|
|
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
|
|
//| added to inner points of the chunk |
|
|
//| DoDetailedTrace - whether trace output is needed or not. When |
|
|
//| trace is activated, solver computes condition |
|
|
//| numbers. It results in the several - fold |
|
|
//| slowdown of the algorithm. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverInitBasecase(CRBF3DDMSolver &solver,
|
|
CMatrixDouble &x,
|
|
int n,
|
|
int nx,
|
|
CRBF3Evaluator &bfmatrix,
|
|
double lambdav,
|
|
CSparseMatrix &sp,
|
|
CRBF3DDMBuffer &buf,
|
|
CRowInt &tgtidx,
|
|
int tgt0,
|
|
int tgt1,
|
|
int nneighbors,
|
|
bool dodetailedtrace)
|
|
{
|
|
//--- create variables
|
|
CRBF3DDMSubproblem subproblem;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int nc=0;
|
|
int nk=0;
|
|
double v=0;
|
|
double reg=0;
|
|
int nwrk=0;
|
|
int npreccol=0;
|
|
CRowInt neighbors;
|
|
CRowInt workingnodes;
|
|
CRowInt preccolumns;
|
|
CRowDouble tau;
|
|
CMatrixDouble q;
|
|
CRowDouble x0;
|
|
double lurcond=0;
|
|
bool lusuccess=false;
|
|
int ni=0;
|
|
int j0=0;
|
|
int j1=0;
|
|
int jj=0;
|
|
CMatrixDouble suba;
|
|
CMatrixDouble subsp;
|
|
CMatrixDouble dbga;
|
|
//--- check
|
|
if(!CAp::Assert(tgt1-tgt0>0,__FUNCTION__+": integrity check 7364 failed"))
|
|
return;
|
|
if(!CAp::Assert(nneighbors>=1,__FUNCTION__+": integrity check 7365 failed"))
|
|
return;
|
|
|
|
reg=(100+MathSqrt(tgt1-tgt0+nneighbors))*CMath::m_machineepsilon;
|
|
//--- Retrieve fresh subproblem. We expect that Solver.SubproblemsBuffer contains
|
|
//--- no recycled entries and that fresh subproblem with Subproblem.IsValid=False
|
|
//--- is returned.
|
|
//--- Start initialization
|
|
subproblem=solver.m_subproblemsbuffer;
|
|
//--- check
|
|
if(!CAp::Assert(!subproblem.m_isvalid,__FUNCTION__+": SubproblemsBuffer integrity check failed"))
|
|
return;
|
|
subproblem.m_isvalid=true;
|
|
subproblem.m_ntarget=tgt1-tgt0;
|
|
CAblasF::IAllocV(tgt1-tgt0,subproblem.m_targetnodes);
|
|
CAblasF::ICopyVX(tgt1-tgt0,tgtidx,tgt0,subproblem.m_targetnodes,0);
|
|
//--- Prepare working arrays
|
|
CAblasF::RAllocV(nx,x0);
|
|
//--- Determine working set: target nodes + neighbors of targets.
|
|
//--- Prepare mapping from node index to position in WorkingNodes[]
|
|
nwrk=0;
|
|
CAblasF::IAllocV(tgt1-tgt0,workingnodes);
|
|
for(i=tgt0; i<tgt1; i++)
|
|
{
|
|
nk=tgtidx[i];
|
|
buf.m_bflags[nk]=true;
|
|
workingnodes.Set(nwrk,nk);
|
|
nwrk++;
|
|
}
|
|
for(i=tgt0; i<tgt1; i++)
|
|
{
|
|
CAblasF::RCopyRV(nx,x,tgtidx[i],x0);
|
|
nc=CNearestNeighbor::KDTreeTsQueryKNN(solver.m_kdt,buf.m_kdtbuf,x0,nneighbors,true);
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(solver.m_kdt,buf.m_kdtbuf,neighbors);
|
|
for(k=0; k<nc; k++)
|
|
{
|
|
nk=neighbors[k];
|
|
if(!buf.m_bflags[nk])
|
|
{
|
|
buf.m_bflags[nk]=true;
|
|
CAblasF::IGrowV(nwrk+1,workingnodes);
|
|
workingnodes.Set(nwrk,nk);
|
|
nwrk++;
|
|
}
|
|
}
|
|
}
|
|
for(i=0; i<nwrk; i++)
|
|
buf.m_bflags[workingnodes[i]]=false;
|
|
//--- check
|
|
if(!CAp::Assert(nwrk>0,__FUNCTION__+": integrity check for NWrk failed"))
|
|
return;
|
|
subproblem.m_nwork=nwrk;
|
|
CAblasF::ICopyAllocV(nwrk,workingnodes,subproblem.m_workingnodes);
|
|
//--- Determine preconditioner columns that have nonzeros in rows corresponding
|
|
//--- to working nodes. Prepare mapping from [0,N+NX+1) column indexing to [0,NPrecCol)
|
|
//--- compressed one. Only these columns are extracted from the preconditioner
|
|
//--- during design system computation.
|
|
//--- NOTE: we ensure that preconditioner columns N...N+NX which correspond to linear
|
|
//--- terms are placed last. It greatly simplifies desi
|
|
npreccol=0;
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
j0=sp.m_RIdx[workingnodes[i]];
|
|
j1=sp.m_RIdx[workingnodes[i]+1];
|
|
for(jj=j0; jj<j1; jj++)
|
|
{
|
|
j=sp.m_Idx[jj];
|
|
if(j<n && !buf.m_bflags[j])
|
|
{
|
|
buf.m_bflags[j]=true;
|
|
CAblasF::IGrowV(npreccol+1,preccolumns);
|
|
preccolumns.Set(npreccol,j);
|
|
npreccol++;
|
|
}
|
|
}
|
|
}
|
|
for(j=n; j<=n+nx; j++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(!buf.m_bflags[j],__FUNCTION__+": integrity check 9435 failed"))
|
|
return;
|
|
buf.m_bflags[j]=true;
|
|
CAblasF::IGrowV(npreccol+1,preccolumns);
|
|
preccolumns.Set(npreccol,j);
|
|
npreccol++;
|
|
}
|
|
for(i=0; i<=npreccol-1; i++)
|
|
{
|
|
buf.m_idx2preccol.Set(preccolumns[i],i);
|
|
buf.m_bflags[preccolumns[i]]=false;
|
|
}
|
|
//--- Generate working system, apply regularization
|
|
CAblasF::RSetAllocM(nwrk,npreccol,0.0,suba);
|
|
ModelMatrixComputePartial(bfmatrix,workingnodes,nwrk,preccolumns,npreccol-(nx+1),suba);
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
ni=workingnodes[i];
|
|
for(j=0; j<nx; j++)
|
|
suba.Set(i,npreccol-(nx+1)+j,x.Get(ni,j));
|
|
suba.Set(i,npreccol-1,1.0);
|
|
}
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
j=buf.m_idx2preccol[workingnodes[i]];
|
|
suba.Add(i,j,lambdav);
|
|
}
|
|
CAblasF::RSetAllocM(nwrk,npreccol,0.0,subsp);
|
|
for(i=0; i<nwrk; i++)
|
|
{
|
|
ni=workingnodes[i];
|
|
j0=sp.m_RIdx[ni];
|
|
j1=sp.m_RIdx[ni+1];
|
|
for(jj=j0; jj<j1; jj++)
|
|
subsp.Set(i,buf.m_idx2preccol[sp.m_Idx[jj]],sp.m_Vals[jj]);
|
|
}
|
|
CAblasF::RAllocM(nwrk,nwrk,subproblem.m_regsystem);
|
|
CAblas::RMatrixGemm(nwrk,nwrk,npreccol,1.0,suba,0,0,0,subsp,0,0,1,0.0,subproblem.m_regsystem,0,0);
|
|
//--- Try solving with LU decomposition
|
|
CAblasF::RCopyAllocM(nwrk,nwrk,subproblem.m_regsystem,subproblem.m_wrklu);
|
|
CTrFac::RMatrixLU(subproblem.m_wrklu,nwrk,nwrk,subproblem.m_wrkp);
|
|
lurcond=CRCond::RMatrixLURCondInf(subproblem.m_wrklu,nwrk);
|
|
if(lurcond>MathSqrt(CMath::m_machineepsilon))
|
|
{
|
|
//--- LU success
|
|
subproblem.m_decomposition=0;
|
|
lusuccess=true;
|
|
if(dodetailedtrace)
|
|
CAp::Trace(StringFormat(">> DDM subproblem: LU success,|target|=%4d,|wrk|=%4d,|preccol|=%4d,cond(LU)=%.2E\n",tgt1 - tgt0,nwrk,npreccol,1 / (lurcond + CMath::m_machineepsilon)));
|
|
}
|
|
else
|
|
lusuccess=false;
|
|
//--- Apply regularized QR if needed
|
|
if(!lusuccess)
|
|
{
|
|
CAblasF::RSetAllocM(2*nwrk,nwrk,0.0,subproblem.m_wrkr);
|
|
CAblasF::RCopyM(nwrk,nwrk,subproblem.m_regsystem,subproblem.m_wrkr);
|
|
v=MathSqrt(reg);
|
|
for(i=0; i<nwrk; i++)
|
|
subproblem.m_wrkr.Set(nwrk+i,i,v);
|
|
COrtFac::RMatrixQR(subproblem.m_wrkr,2*nwrk,nwrk,tau);
|
|
COrtFac::RMatrixQRUnpackQ(subproblem.m_wrkr,2*nwrk,nwrk,tau,nwrk,subproblem.m_wrkq);
|
|
subproblem.m_decomposition=1;
|
|
if(dodetailedtrace)
|
|
CAp::Trace(StringFormat(">> DDM subproblem: LU failure,using reg-QR,|target|=%4d,|wrk|=%4d,|preccol|=%4d,cond(R)=%.2E (cond(LU)=%.2E)\n",tgt1 - tgt0,nwrk,npreccol,1 / (CRCond::RMatrixTrRCondInf(subproblem.m_wrkr,nwrk,true,false) + CMath::m_machineepsilon),1 / (lurcond + CMath::m_machineepsilon)));
|
|
}
|
|
//--- Subproblem is ready.
|
|
//--- Move it to the SubproblemsPool
|
|
solver.m_subproblemspool=subproblem;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive initialization routine for DDM solver |
|
|
//| INPUT PARAMETERS: |
|
|
//| Solver - solver structure |
|
|
//| X - array[N, NX], dataset points |
|
|
//| N, NX - dataset metrics, N > 0, NX > 0 |
|
|
//| BFMatrix - basis function evaluator |
|
|
//| LambdaV - smoothing parameter |
|
|
//| SP - sparse ACBF preconditioner, |
|
|
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
|
|
//| format |
|
|
//| WrkIdx - array[], contains indexes of points in the |
|
|
//| current working set. Elements [Wrk0, Wrk1) are |
|
|
//| processed by this function. |
|
|
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
|
|
//| added to inner points of the chunk |
|
|
//| NBatch - batch size |
|
|
//| DoDetailedTrace - whether trace output is needed or not. When |
|
|
//| trace is activated, solver computes condition |
|
|
//| numbers. It results in the several - fold |
|
|
//| slowdown of the algorithm. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverInitRec(CRBF3DDMSolver &solver,
|
|
CMatrixDouble &x,
|
|
int n,
|
|
int nx,
|
|
CRBF3Evaluator &bfmatrix,
|
|
double lambdav,
|
|
CSparseMatrix &sp,
|
|
CRowInt &wrkidx,
|
|
int wrk0,
|
|
int wrk1,
|
|
int nneighbors,
|
|
int nbatch,
|
|
bool dodetailedtrace)
|
|
{
|
|
//--- create variables
|
|
int largestdim=0;
|
|
double splitval=0;
|
|
double basecasecomplexity=0;
|
|
CRBF3DDMBuffer buf;
|
|
|
|
if(wrk1<=wrk0)
|
|
return;
|
|
basecasecomplexity=MathPow(nbatch+nneighbors+nx+1,3.0);
|
|
//--- Retrieve temporary buffer
|
|
buf=solver.m_bufferpool;
|
|
//--- Analyze current working set
|
|
CAblasF::RAllocV(nx,buf.m_tmpboxmin);
|
|
CAblasF::RAllocV(nx,buf.m_tmpboxmax);
|
|
CAblasF::RCopyRV(nx,x,wrkidx[wrk0],buf.m_tmpboxmin);
|
|
CAblasF::RCopyRV(nx,x,wrkidx[wrk0],buf.m_tmpboxmax);
|
|
for(int i=wrk0+1; i<wrk1; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
{
|
|
buf.m_tmpboxmin.Set(j,MathMin(buf.m_tmpboxmin[j],x.Get(wrkidx[i],j)));
|
|
buf.m_tmpboxmax.Set(j,MathMax(buf.m_tmpboxmax[j],x.Get(wrkidx[i],j)));
|
|
}
|
|
}
|
|
largestdim=0;
|
|
for(int j=1; j<nx; j++)
|
|
{
|
|
if((buf.m_tmpboxmax[j]-buf.m_tmpboxmin[j])>(buf.m_tmpboxmax[largestdim]-buf.m_tmpboxmin[largestdim]))
|
|
largestdim=j;
|
|
}
|
|
//--- Perform either batch processing or recursive split
|
|
//--- Either working set size is small enough or all points are non-distinct.
|
|
//--- Stop recursive subdivision.
|
|
DDMSolverInitBasecase(solver,x,n,nx,bfmatrix,lambdav,sp,buf,wrkidx,wrk0,wrk1,nneighbors,dodetailedtrace);
|
|
//--- Recycle temporary buffers
|
|
solver.m_bufferpool=buf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares domain decomposition method for RBF |
|
|
//| interpolation problem - it partitions problem into subproblems |
|
|
//| and precomputes factorizations, and prepares a smaller correction|
|
|
//| spline that is used to correct distortions introduced by domain |
|
|
//| decomposition and imperfections in approximate cardinal basis. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N, NX], dataset points |
|
|
//| RescaledBy - additional scaling coefficient that was applied |
|
|
//| to the dataset by preprocessor. Used ONLY for |
|
|
//| logging purposes - without it all distances will|
|
|
//| be reported in [0, 1] scale, not one set by user|
|
|
//| N, NX - dataset metrics, N > 0, NX > 0 |
|
|
//| BFMatrix - RBF evaluator |
|
|
//| BFType - basis function type |
|
|
//| BFParam - basis function parameter |
|
|
//| LambdaV - regularization parameter, >= 0 |
|
|
//| ATerm - polynomial term type(1 for linear, 2 for |
|
|
//| constant, 3 for zero) |
|
|
//| SP - sparse ACBF preconditioner, |
|
|
//| (N + NX + 1)*(N + NX + 1) matrix stored in CRS |
|
|
//| format |
|
|
//| NNeighbors - neighbors count; NNeighbors nearby nodes are |
|
|
//| added to inner points of the batch |
|
|
//| NBatch - batch size |
|
|
//| NCorrector - nodes count for correction spline |
|
|
//| DoTrace - whether low overhead logging is needed or not |
|
|
//| DoDetailedTrace - whether detailed trace output is needed or |
|
|
//| not. When trace is activated, solver computes |
|
|
//| condition numbers. It results in the small |
|
|
//| slowdown of the algorithm. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Solver - DDM solver |
|
|
//| timeDDMInit - time used by the DDM part initialization, ms |
|
|
//| timeCorrInit - time used by the corrector initialization, ms |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverInit(CMatrixDouble &x,
|
|
double rescaledby,
|
|
int n,
|
|
int nx,
|
|
CRBF3Evaluator &bfmatrix,
|
|
int bftype,
|
|
double bfparam,
|
|
double lambdav,
|
|
int aterm,
|
|
CSparseMatrix &sp,
|
|
int nneighbors,
|
|
int nbatch,
|
|
int ncorrector,
|
|
bool dotrace,
|
|
bool dodetailedtrace,
|
|
CRBF3DDMSolver &solver,
|
|
int &timeddminit,
|
|
int &timecorrinit)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
CRowInt idx;
|
|
CRBF3DDMBuffer bufferseed;
|
|
CRBF3DDMSubproblem subproblem;
|
|
CRBF3DDMSubproblem p;
|
|
double correctorgridseparation=0;
|
|
CMatrixDouble corrsys;
|
|
CRowDouble corrtau;
|
|
CRowInt idummy;
|
|
|
|
timeddminit=0;
|
|
timecorrinit=0;
|
|
//--- check
|
|
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": integrity check 3320 failed"))
|
|
return;
|
|
//--- Start DDM part
|
|
timeddminit=((int)(GetTickCount()/10000));
|
|
//--- Save problem info
|
|
solver.m_lambdav=lambdav;
|
|
//--- Prepare KD-tree
|
|
CAblasF::IAllocV(n,idx);
|
|
for(i=0; i<n; i++)
|
|
idx.Set(i,i);
|
|
CNearestNeighbor::KDTreeBuildTagged(x,idx,n,nx,0,2,solver.m_kdt);
|
|
//--- Prepare temporary buffer pool
|
|
CAblasF::BSetAllocV(n+nx+1,false,bufferseed.m_bflags);
|
|
CAblasF::IAllocV(n+nx+1,bufferseed.m_idx2preccol);
|
|
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmin);
|
|
CAblasF::RAllocV(nx,bufferseed.m_tmpboxmax);
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(solver.m_kdt,bufferseed.m_kdtbuf);
|
|
solver.m_bufferpool=bufferseed;
|
|
//--- Prepare default subproblems buffer, run recursive procedure
|
|
//--- and count subproblems in the buffer
|
|
solver.m_subproblemspool=subproblem;
|
|
solver.m_subproblemsbuffer=subproblem;
|
|
DDMSolverInitRec(solver,x,n,nx,bfmatrix,solver.m_lambdav,sp,idx,0,n,nneighbors,nbatch,dodetailedtrace);
|
|
solver.m_subproblemscnt=0;
|
|
solver.m_cntlu=0;
|
|
solver.m_cntregqr=0;
|
|
p=solver.m_subproblemspool;
|
|
solver.m_subproblemscnt++;
|
|
if(p.m_decomposition==0)
|
|
solver.m_cntlu++;
|
|
if(p.m_decomposition==1)
|
|
solver.m_cntregqr++;
|
|
//--- check
|
|
if(!CAp::Assert(solver.m_cntlu+solver.m_cntregqr==solver.m_subproblemscnt,__FUNCTION__+": integrity check 5296 failed"))
|
|
return;
|
|
if(!CAp::Assert(solver.m_subproblemscnt>0,__FUNCTION__+": subproblems pool is empty,critical integrity check failed"))
|
|
return;
|
|
//--- DDM part is done
|
|
timeddminit=((int)(GetTickCount()/10000))-timeddminit;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> DDM part was prepared in %d ms,%d subproblems solved (%d well-conditioned,%d ill-conditioned)\n",timeddminit,solver.m_subproblemscnt,solver.m_cntlu,solver.m_cntregqr));
|
|
//--- Prepare correction spline
|
|
timecorrinit=((int)(GetTickCount()/10000));
|
|
SelectGlobalNodes(x,n,nx,idummy,0,ncorrector,solver.m_corrnodes,solver.m_ncorrector,correctorgridseparation);
|
|
ncorrector=solver.m_ncorrector;
|
|
//--- check
|
|
if(!CAp::Assert(ncorrector>0,__FUNCTION__+": NCorrector=0"))
|
|
return;
|
|
CAblasF::RSetAllocM(ncorrector+nx+1,ncorrector+nx+1,0.0,corrsys);
|
|
CAblasF::RAllocM(ncorrector,nx,solver.m_corrx);
|
|
for(i=0; i<=ncorrector-1; i++)
|
|
CAblasF::RCopyRR(nx,x,solver.m_corrnodes[i],solver.m_corrx,i);
|
|
ComputeBFMatrix(solver.m_corrx,ncorrector,nx,bftype,bfparam,corrsys);
|
|
if(aterm==1)
|
|
{
|
|
//--- Use linear term
|
|
for(i=0; i<nx; i++)
|
|
for(j=0; j<ncorrector; j++)
|
|
{
|
|
corrsys.Set(ncorrector+i,j,x.Get(solver.m_corrnodes[j],i));
|
|
corrsys.Set(j,ncorrector+i,x.Get(solver.m_corrnodes[j],i));
|
|
}
|
|
for(j=0; j<ncorrector; j++)
|
|
{
|
|
corrsys.Set(ncorrector+nx,j,1.0);
|
|
corrsys.Set(j,ncorrector+nx,1.0);
|
|
}
|
|
}
|
|
if(aterm==2)
|
|
{
|
|
//--- Use constant term
|
|
for(i=0; i<nx; i++)
|
|
corrsys.Set(ncorrector+i,ncorrector+i,1.0);
|
|
for(j=0; j<=ncorrector-1; j++)
|
|
{
|
|
corrsys.Set(ncorrector+nx,j,1.0);
|
|
corrsys.Set(j,ncorrector+nx,1.0);
|
|
}
|
|
}
|
|
if(aterm==3)
|
|
{
|
|
//--- Use zero term
|
|
for(i=0; i<=nx; i++)
|
|
corrsys.Set(ncorrector+i,ncorrector+i,1.0);
|
|
}
|
|
for(j=0; j<ncorrector; j++)
|
|
corrsys.Add(j,j,solver.m_lambdav);
|
|
COrtFac::RMatrixQR(corrsys,ncorrector+nx+1,ncorrector+nx+1,corrtau);
|
|
COrtFac::RMatrixQRUnpackQ(corrsys,ncorrector+nx+1,ncorrector+nx+1,corrtau,ncorrector+nx+1,solver.m_corrq);
|
|
COrtFac::RMatrixQRUnpackR(corrsys,ncorrector+nx+1,ncorrector+nx+1,solver.m_corrr);
|
|
timecorrinit=((int)(GetTickCount()/10000))-timecorrinit;
|
|
if(dotrace)
|
|
CAp::Trace(StringFormat("> Corrector spline was prepared in %d ms (%d nodes,max distance from dataset points to nearest grid node is %.2E)\n",timecorrinit,ncorrector,correctorgridseparation * rescaledby));
|
|
if(dodetailedtrace)
|
|
{
|
|
CAp::Trace("> printing condition numbers for correction spline:\n");
|
|
CAp::Trace(StringFormat("cond(A) = %.2E (Linf norm,leading NCoarsexNCoarse block)\n",1 / (CRCond::RMatrixTrRCondInf(solver.m_corrr,ncorrector,true,false) + CMath::m_machineepsilon)));
|
|
CAp::Trace(StringFormat("cond(A) = %.2E (Linf norm,full system)\n",1 / (CRCond::RMatrixTrRCondInf(solver.m_corrr,ncorrector + nx + 1,true,false) + CMath::m_machineepsilon)));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive subroutine for DDM method. Given initial subproblems |
|
|
//| count Cnt, it perform two recursive calls(spawns children in |
|
|
//| parallel when possible) with Cnt~Cnt / 2 until we end up with |
|
|
//| Cnt = 1. |
|
|
//| Case with Cnt = 1 is handled by retrieving subproblem from |
|
|
//| Solver.SubproblemsPool, solving it and pushing subproblem to |
|
|
//| Solver.SubproblemsBuffer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Solver - DDM solver object |
|
|
//| Res - array[N, NY], current residuals |
|
|
//| N, NX, NY - dataset metrics, N > 0, NX > 0, NY > 0 |
|
|
//| C - preallocated array[N + NX + 1, NY] |
|
|
//| Cnt - number of subproblems to process |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - rows 0..N - 1 contain spline coefficients rows |
|
|
//| N..N + NX are filled by zeros |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverRunRec(CRBF3DDMSolver &solver,
|
|
CMatrixDouble &res,
|
|
int n,
|
|
int nx,
|
|
int ny,
|
|
CMatrixDouble &c,
|
|
int cnt)
|
|
{
|
|
//--- create variables
|
|
int nwrk=0;
|
|
int ntarget=0;
|
|
double v=0;
|
|
CRBF3DDMSubproblem subproblem;
|
|
//--- Retrieve subproblem from the source pool, solve it
|
|
subproblem=solver.m_subproblemspool;
|
|
//--- check
|
|
if(!CAp::Assert(subproblem.m_isvalid,__FUNCTION__+": integrity check 1742 failed"))
|
|
return;
|
|
nwrk=subproblem.m_nwork;
|
|
ntarget=subproblem.m_ntarget;
|
|
if(subproblem.m_decomposition==0)
|
|
{
|
|
//--- Solve using LU decomposition (the fastest option)
|
|
CAblasF::RAllocM(nwrk,ny,subproblem.m_rhs);
|
|
for(int i=0; i<nwrk; i++)
|
|
for(int j=0; j<ny; j++)
|
|
subproblem.m_rhs.Set(i,j,res.Get(subproblem.m_workingnodes[i],j));
|
|
for(int i=0; i<nwrk; i++)
|
|
{
|
|
if(subproblem.m_wrkp[i]!=i)
|
|
{
|
|
for(int j=0; j<ny; j++)
|
|
{
|
|
v=subproblem.m_rhs.Get(i,j);
|
|
subproblem.m_rhs.Set(i,j,subproblem.m_rhs.Get(subproblem.m_wrkp[i],j));
|
|
subproblem.m_rhs.Set(subproblem.m_wrkp[i],j,v);
|
|
}
|
|
}
|
|
}
|
|
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrklu,0,0,false,true,0,subproblem.m_rhs,0,0);
|
|
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrklu,0,0,true,false,0,subproblem.m_rhs,0,0);
|
|
CAblasF::RCopyAllocM(nwrk,ny,subproblem.m_rhs,subproblem.m_sol);
|
|
}
|
|
else
|
|
{
|
|
//--- Solve using regularized QR (well, we tried LU but it failed)
|
|
//--- check
|
|
if(!CAp::Assert(subproblem.m_decomposition==1,__FUNCTION__+": integrity check 1743 failed"))
|
|
return;
|
|
CAblasF::RAllocM(nwrk,ny,subproblem.m_rhs);
|
|
for(int i=0; i<nwrk; i++)
|
|
{
|
|
for(int j=0; j<ny; j++)
|
|
subproblem.m_rhs.Set(i,j,res.Get(subproblem.m_workingnodes[i],j));
|
|
}
|
|
CAblasF::RAllocM(nwrk,ny,subproblem.m_qtrhs);
|
|
CAblas::RMatrixGemm(nwrk,ny,nwrk,1.0,subproblem.m_wrkq,0,0,1,subproblem.m_rhs,0,0,0,0.0,subproblem.m_qtrhs,0,0);
|
|
CAblas::RMatrixLeftTrsM(nwrk,ny,subproblem.m_wrkr,0,0,true,false,0,subproblem.m_qtrhs,0,0);
|
|
CAblasF::RCopyAllocM(nwrk,ny,subproblem.m_qtrhs,subproblem.m_sol);
|
|
}
|
|
for(int i=0; i<ntarget; i++)
|
|
{
|
|
for(int j=0; j<ny; j++)
|
|
c.Set(subproblem.m_targetnodes[i],j,subproblem.m_sol.Get(i,j));
|
|
}
|
|
//--- Push to the destination pool
|
|
solver.m_subproblemsbuffer=subproblem;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function APPROXIMATELY solves RBF interpolation problem |
|
|
//| using domain decomposition method. Given current residuals Res, |
|
|
//| it computes approximate basis function coefficients C (but does |
|
|
//| NOT compute linear coefficients - these are set to zero). |
|
|
//| This function is a linear operator with respect to its input RES,|
|
|
//| thus it can be used as a preconditioner for an iterative linear |
|
|
//| solver like GMRES. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Solver - DDM solver object |
|
|
//| Res - array[N, NY], current residuals |
|
|
//| N, NX, NY - dataset metrics, N > 0, NX > 0, NY > 0 |
|
|
//| SP - preconditioner, (N + NX + 1)*(N + NX + 1) sparse|
|
|
//| matrix |
|
|
//| BFMatrix - basis functions evaluator |
|
|
//| C - preallocated array[N + NX + 1, NY] |
|
|
//| timeDDMSolve, |
|
|
//| timeCorrSolve - on input contain already accumulated timings |
|
|
//| for DDM and CORR parts |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - rows 0..N - 1 contain spline coefficients rows |
|
|
//| N..N + NX are filled by zeros |
|
|
//| timeDDMSolve, |
|
|
//| timeCorrSolve - updated with new timings |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverRun(CRBF3DDMSolver &solver,
|
|
CMatrixDouble &res,
|
|
int n,
|
|
int nx,
|
|
int ny,
|
|
CSparseMatrix &sp,
|
|
CRBF3Evaluator &bfmatrix,
|
|
CMatrixDouble &upd,
|
|
int &timeddmsolve,
|
|
int &timecorrsolve)
|
|
{
|
|
//--- create variables
|
|
CRBF3DDMSubproblem subproblem;
|
|
CMatrixDouble c;
|
|
CRowDouble x0;
|
|
CRowDouble x1;
|
|
CRowDouble refrhs1;
|
|
CMatrixDouble updt;
|
|
CAblasF::RSetAllocM(ny,n+nx+1,0.0,updt);
|
|
CAblasF::RSetAllocM(n+nx+1,ny,0.0,c);
|
|
//--- Solve DDM part:
|
|
//--- * run recursive procedure that computes DDM part.
|
|
//--- * clean-up: move processed subproblems from Solver.SubproblemsBuffer back to Solver.SubproblemsPool
|
|
//--- * multiply solution by the preconditioner matrix
|
|
timeddmsolve-=((int)(GetTickCount()/10000));
|
|
DDMSolverRunRec(solver,res,n,nx,ny,c,solver.m_subproblemscnt);
|
|
for(int i=0; i<solver.m_subproblemscnt; i++)
|
|
{
|
|
subproblem=solver.m_subproblemsbuffer;
|
|
//--- check
|
|
if(!CAp::Assert(subproblem.m_isvalid,__FUNCTION__+": integrity check 5223 failed"))
|
|
return;
|
|
solver.m_subproblemspool=subproblem;
|
|
}
|
|
timeddmsolve+=((int)(GetTickCount()/10000));
|
|
CAblasF::RAllocV(n+nx+1,x0);
|
|
CAblasF::RAllocV(n+nx+1,x1);
|
|
for(int j=0; j<ny; j++)
|
|
{
|
|
CAblasF::RCopyCV(n+nx+1,c,j,x0);
|
|
CSparse::SparseGemV(sp,1.0,1,x0,0,0.0,x1,0);
|
|
CAblasF::RCopyVR(n+nx+1,x1,updt,j);
|
|
}
|
|
//--- Compute correction spline that fixes oscillations introduced by the DDM part
|
|
timecorrsolve-=((int)(GetTickCount()/10000));
|
|
CAblasF::RAllocV(solver.m_ncorrector+nx+1,x0);
|
|
CAblasF::RAllocV(n+nx+1,x1);
|
|
for(int j=0; j<ny; j++)
|
|
{
|
|
//--- Prepare right-hand side for the QR solver
|
|
CAblasF::RSetAllocV(solver.m_ncorrector+nx+1,0.0,refrhs1);
|
|
CAblasF::RCopyRV(n+nx+1,updt,j,x1);
|
|
ModelMatrixComputeProductAtNodes(bfmatrix,x1,solver.m_corrnodes,solver.m_ncorrector,refrhs1);
|
|
for(int i=0; i<solver.m_ncorrector; i++)
|
|
{
|
|
refrhs1.Set(i,res.Get(solver.m_corrnodes[i],j)-refrhs1[i]);
|
|
for(int k=0; k<nx; k++)
|
|
refrhs1.Add(i,- solver.m_corrx.Get(i,k)*x1[n+k]);
|
|
refrhs1.Add(i,- x1[n+nx]-solver.m_lambdav*x1[solver.m_corrnodes[i]]);
|
|
}
|
|
//--- Solve QR-factorized system
|
|
CAblasF::RGemV(solver.m_ncorrector+nx+1,solver.m_ncorrector+nx+1,1.0,solver.m_corrq,1,refrhs1,0.0,x0);
|
|
CAblas::RMatrixTrsVect(solver.m_ncorrector+nx+1,solver.m_corrr,0,0,true,false,0,x0,0);
|
|
for(int i=0; i<solver.m_ncorrector; i++)
|
|
updt.Add(j,solver.m_corrnodes[i],x0[i]);
|
|
for(int i=0; i<=nx; i++)
|
|
updt.Add(j,n+i,x0[solver.m_ncorrector+i]);
|
|
}
|
|
timecorrsolve+=((int)(GetTickCount()/10000));
|
|
CAblasF::RAllocM(n+nx+1,ny,upd);
|
|
CAblas::RMatrixTranspose(ny,n+nx+1,updt,0,0,upd,0,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is a specialized version of DDMSolverRun() for |
|
|
//| NY = 1. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::DDMSolverRun1(CRBF3DDMSolver &solver,
|
|
CRowDouble &res,
|
|
int n,
|
|
int nx,
|
|
CSparseMatrix &sp,
|
|
CRBF3Evaluator &bfmatrix,
|
|
CRowDouble &upd,
|
|
int &timeddmsolve,
|
|
int &timecorrsolve)
|
|
{
|
|
CAblasF::RAllocM(n,1,solver.m_tmpres1);
|
|
CAblasF::RCopyVC(n,res,solver.m_tmpres1,0);
|
|
DDMSolverRun(solver,solver.m_tmpres1,n,nx,1,sp,bfmatrix,solver.m_tmpres1,timeddmsolve,timecorrsolve);
|
|
CAblasF::RAllocV(n+nx+1,upd);
|
|
CAblasF::RCopyCV(n+nx+1,solver.m_tmpres1,0,upd);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Automatically detect scale parameter as a mean distance towards |
|
|
//| nearest neighbor (not counting nearest neighbors that are too |
|
|
//| close) |
|
|
//| PARAMETERS: |
|
|
//| XX - dataset(X - values), array[N, NX] |
|
|
//| N - points count, N >= 1 |
|
|
//| NX - dimensions count, NX >= 1 |
|
|
//| RESULT: |
|
|
//| suggested scale |
|
|
//+------------------------------------------------------------------+
|
|
double CRBFV3::AutoDetectScaleParameter(CMatrixDouble &xx,int n,int nx)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int nq=0;
|
|
int nlocal=0;
|
|
CKDTree kdt;
|
|
CRowDouble x;
|
|
CRowDouble d;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": integrity check 7624 failed"))
|
|
return(0);
|
|
|
|
CAblasF::RAllocV(nx,x);
|
|
CNearestNeighbor::KDTreeBuild(xx,n,nx,0,2,kdt);
|
|
nlocal=(int)MathRound(MathPow(2,nx)+1);
|
|
result=0;
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
//--- Query a batch of nearest neighbors
|
|
CAblasF::RCopyRV(nx,xx,i,x);
|
|
nq=CNearestNeighbor::KDTreeQueryKNN(kdt,x,nlocal,true);
|
|
//--- check
|
|
if(!CAp::Assert(nq>=1,__FUNCTION__+": integrity check 7625 failed"))
|
|
return(0);
|
|
CNearestNeighbor::KDTreeQueryResultsDistances(kdt,d);
|
|
//--- In order to filter out nearest neighbors that are too close,
|
|
//--- we use distance R toward most distant of NQ nearest neighbors as
|
|
//--- a reference and select nearest neighbor with distance >=0.5*R/NQ
|
|
for(int j=0; j<nq; j++)
|
|
{
|
|
if(d[j]>=(0.5*d[nq-1]/nq))
|
|
{
|
|
result+=d[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
result/=n;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive functions matrix computation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeBFMatrixRec(CMatrixDouble &xx,
|
|
int range0,
|
|
int range1,
|
|
int n,
|
|
int nx,
|
|
int functype,
|
|
double funcparam,
|
|
CMatrixDouble &f)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double vv=0;
|
|
double elemcost=0;
|
|
double alpha2=0;
|
|
CRowDouble temp;
|
|
//--- check
|
|
if(!CAp::Assert(functype==1 || functype==2 || functype==3,__FUNCTION__+": unexpected FuncType"))
|
|
return;
|
|
//--- Serial processing
|
|
alpha2=funcparam*funcparam;
|
|
for(int i=range0; i<range1; i++)
|
|
{
|
|
for(int j=i; j<n; j++)
|
|
{
|
|
temp=xx[i]-xx[j];
|
|
v=CAblasF::RDotV2(nx,temp);
|
|
switch(functype)
|
|
{
|
|
case 1:
|
|
v=-MathSqrt(v+alpha2);
|
|
break;
|
|
case 2:
|
|
if(v!=0.0)
|
|
v=v*0.5*MathLog(v);
|
|
else
|
|
v=0.0;
|
|
break;
|
|
case 3:
|
|
v=v*MathSqrt(v);
|
|
break;
|
|
}
|
|
f.Set(i,j,v);
|
|
f.Set(j,i,v);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function computes basis functions matrix (both upper and |
|
|
//| lower triangles) |
|
|
//| [ f(dist(x0, x0)) ... f(dist(x0, x(n - 1))) ]|
|
|
//| [ f(dist(x1, x0)) ... f(dist(x1, x(n - 1))) ]|
|
|
//| [ ............................................................. ]|
|
|
//| [ f(dist(x(n - 1), x0)) ... f(dist(x(n - 1), x(n - 1))) ]|
|
|
//| NOTE: if F is large enough to store result, it is not reallocated|
|
|
//| Values outside of [0, N) x[0, N) range are not modified. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeBFMatrix(CMatrixDouble &xx,int n,int nx,
|
|
int functype,double funcparam,
|
|
CMatrixDouble &f)
|
|
{
|
|
CAblasF::RAllocM(n,n,f);
|
|
ComputeBFMatrixRec(xx,0,n,n,nx,functype,funcparam,f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initializes model matrix using specified matrix storage format: |
|
|
//| * StorageType = 0 a N * N matrix of basis function values is |
|
|
//| stored |
|
|
//| * StorageType = 1 basis function values are recomputed on |
|
|
//| demand |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ModelMatrixInit(CMatrixDouble &xx,
|
|
int n,
|
|
int nx,
|
|
int functype,
|
|
double funcparam,
|
|
int storagetype,
|
|
CRBF3Evaluator &modelmatrix)
|
|
{
|
|
//--- create variables
|
|
int nchunks=0;
|
|
int srcoffs=0;
|
|
int dstoffs=0;
|
|
int curlen=0;
|
|
CRBF3EvaluatorBuffer bufseed;
|
|
//--- check
|
|
if(!CAp::Assert(storagetype==0 || storagetype==1,__FUNCTION__+": unexpected StorageType for ModelMatrixInit()"))
|
|
return;
|
|
|
|
modelmatrix.m_n=n;
|
|
modelmatrix.m_storagetype=storagetype;
|
|
|
|
switch(storagetype)
|
|
{
|
|
case 0:
|
|
ComputeBFMatrix(xx,n,nx,functype,funcparam,modelmatrix.m_f);
|
|
break;
|
|
case 1:
|
|
//--- Save model parameters
|
|
modelmatrix.m_nx=nx;
|
|
modelmatrix.m_functype=functype;
|
|
modelmatrix.m_funcparam=funcparam;
|
|
modelmatrix.m_chunksize=128;
|
|
//--- Prepare temporary buffers
|
|
bufseed=modelmatrix.m_bufferpool;
|
|
CAblasF::RSetAllocV(modelmatrix.m_chunksize,1.0,modelmatrix.m_chunk1);
|
|
//--- Store dataset in the chunked row storage format (rows with size at most ChunkSize, one row per dimension/chunk)
|
|
CAblasF::IAllocV(n,modelmatrix.m_entireset);
|
|
for(int i=0; i<n; i++)
|
|
modelmatrix.m_entireset.Set(i,i);
|
|
CAblasF::RCopyAllocM(n,nx,xx,modelmatrix.m_x);
|
|
nchunks=CApServ::IDivUp(n,modelmatrix.m_chunksize);
|
|
CAblasF::RSetAllocM(nchunks*nx,modelmatrix.m_chunksize,0.0,modelmatrix.m_xtchunked);
|
|
srcoffs=0;
|
|
dstoffs=0;
|
|
while(srcoffs<n)
|
|
{
|
|
curlen=MathMin(modelmatrix.m_chunksize,n-srcoffs);
|
|
for(int i=0; i<=curlen-1; i++)
|
|
{
|
|
for(int j=0; j<nx; j++)
|
|
modelmatrix.m_xtchunked.Set(dstoffs+j,i,xx.Get(srcoffs+i,j));
|
|
}
|
|
srcoffs+=curlen;
|
|
dstoffs+=nx;
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes subset of the model matrix (subset of rows, subset of |
|
|
//| columns) and writes result to R. |
|
|
//| NOTE: If R is longer than M0xM1, it is not reallocated and |
|
|
//| additional elements are not modified. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ModelMatrixComputePartial(CRBF3Evaluator &modelmatrix,
|
|
CRowInt &ridx,
|
|
int m0,
|
|
CRowInt &cidx,
|
|
int m1,
|
|
CMatrixDouble &r)
|
|
{
|
|
//--- create variables
|
|
int ni=0;
|
|
int nj=0;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
|
|
return;
|
|
CAblasF::RAllocM(m0,m1,r);
|
|
switch(modelmatrix.m_storagetype)
|
|
{
|
|
case 0:
|
|
for(int i=0; i<m0; i++)
|
|
{
|
|
ni=ridx[i];
|
|
for(int j=0; j<m1; j++)
|
|
r.Set(i,j,modelmatrix.m_f.Get(ni,cidx[j]));
|
|
}
|
|
break;
|
|
case 1:
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_functype==1 || modelmatrix.m_functype==2,__FUNCTION__+": unexpected FuncType"))
|
|
return;
|
|
for(int i=0; i<m0; i++)
|
|
{
|
|
ni=ridx[i];
|
|
for(int j=0; j<m1; j++)
|
|
{
|
|
nj=cidx[j];
|
|
v=0;
|
|
switch(modelmatrix.m_functype)
|
|
{
|
|
case 1:
|
|
v=modelmatrix.m_funcparam*modelmatrix.m_funcparam;
|
|
break;
|
|
case 2:
|
|
v=1.0E-50;
|
|
break;
|
|
}
|
|
for(int k=0; k<modelmatrix.m_nx; k++)
|
|
{
|
|
vv=modelmatrix.m_x.Get(ni,k)-modelmatrix.m_x.Get(nj,k);
|
|
v=v+vv*vv;
|
|
}
|
|
switch(modelmatrix.m_functype)
|
|
{
|
|
case 1:
|
|
v=-MathSqrt(v);
|
|
break;
|
|
case 2:
|
|
v=v*0.5*MathLog(v);
|
|
break;
|
|
}
|
|
r.Set(i,j,v);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//--- This function computes ChunkSize basis function values and stores |
|
|
//| them in the evaluator buffer. This function does not modify |
|
|
//| Evaluator object, thus it can be used in multiple threads with |
|
|
//| the same evaluator as long as different buffers are used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Evaluator - evaluator object |
|
|
//| X - origin point |
|
|
//| Buf - preallocated buffers. Following fields are used |
|
|
//| and must have at least ChunkSize elements: |
|
|
//| * Buf.FuncBuf |
|
|
//| * Buf.WrkBuf |
|
|
//| When NeedGradInfo >= 1, additionally we need the following fields|
|
|
//| to be preallocated: |
|
|
//| * Buf.MinDist2 - array[ChunkSize], filled by |
|
|
//| some positive values; on the |
|
|
//| very first call it is 1.0E50 |
|
|
//| or something comparably large |
|
|
//| * Buf.DeltaBuf - array[NX, ChunkSize] |
|
|
//| * Buf.DF1 - array[ChunkSize] |
|
|
//| When NeedGradInfo >= 2, additionally we need the following fields|
|
|
//| to be preallocated: |
|
|
//| * Buf.DF2 - array[ChunkSize] |
|
|
//| ChunkSize - amount of basis functions to compute, |
|
|
//| 0 < ChunkSize <= Evaluator.ChunkSize |
|
|
//| ChunkIdx - index of the chunk in Evaluator.XTChunked times |
|
|
//| NX |
|
|
//| Distance0 - strictly positive value that is added to the |
|
|
//| squared distance prior to passing it to the |
|
|
//| multiquadric kernel function. For other kernels-|
|
|
//| set it to small nonnegative value like 1.0E-50. |
|
|
//| NeedGradInfo - whether gradient - related information is needed|
|
|
//| or not: |
|
|
//| * if 0, only FuncBuf is set on exit |
|
|
//| * if 1, MinDist2, DeltaBuf and DF1 are also set |
|
|
//| on exit |
|
|
//| * if 2, additionally DF2 is set on exit |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf.FuncBuf - array[ChunkSize], basis function values |
|
|
//| Buf.MinDist2 - array[ChunkSize], if NeedGradInfo >= 1 then its |
|
|
//| I-th element is updated as |
|
|
//| MinDist2[I]:= min(MinDist2[I], DISTANCE_SQUARED(X, CENTER[I])) |
|
|
//| Buf.DeltaBuf - array[NX, ChunkSize], if NeedGradInfo >= 1 then |
|
|
//| J-th element of K-th row is set to |
|
|
//| X[K] - CENTER[J, K] |
|
|
//| Buf.DF1 - array[ChunkSize], if NeedGradInfo >= 1 then |
|
|
//| J-th element is derivative of the kernel |
|
|
//| function with respect to its input (squared |
|
|
//| distance) |
|
|
//| Buf.DF2 - array[ChunkSize], if NeedGradInfo >= 2 then |
|
|
//| J-th element is derivative of the kernel |
|
|
//| function with respect to its input (squared |
|
|
//| distance) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ComputeRowChunk(CRBF3Evaluator &evaluator,
|
|
CRowDouble &x,
|
|
CRBF3EvaluatorBuffer &buf,
|
|
int chunksize,
|
|
int chunkidx,
|
|
double distance0,
|
|
int needgradinfo)
|
|
{
|
|
//--- create variables
|
|
double r2=0;
|
|
double lnr=0;
|
|
//--- Compute squared distance in Buf.FuncBuf
|
|
CAblasF::RSetV(chunksize,distance0,buf.m_funcbuf);
|
|
for(int k=0; k<evaluator.m_nx; k++)
|
|
{
|
|
CAblasF::RSetV(chunksize,x[k],buf.m_wrkbuf);
|
|
CAblasF::RAddRV(chunksize,-1.0,evaluator.m_xtchunked,chunkidx+k,buf.m_wrkbuf);
|
|
CAblasF::RMulAddV(chunksize,buf.m_wrkbuf,buf.m_wrkbuf,buf.m_funcbuf);
|
|
if(needgradinfo>=1)
|
|
CAblasF::RCopyVR(chunksize,buf.m_wrkbuf,buf.m_deltabuf,k);
|
|
}
|
|
if(needgradinfo>=1)
|
|
CAblasF::RMergeMinV(chunksize,buf.m_funcbuf,buf.m_mindist2);
|
|
//--- Apply kernel function
|
|
switch(evaluator.m_functype)
|
|
{
|
|
case 1:
|
|
//--- f=-sqrt(r^2+alpha^2), including f=-r as a special case
|
|
switch(needgradinfo)
|
|
{
|
|
case 0:
|
|
//--- Only target f(r2)=-sqrt(r2) is needed
|
|
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
|
|
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
|
|
break;
|
|
case 1:
|
|
//--- First derivative is needed:
|
|
//--- f(r2) = -sqrt(r2)
|
|
//--- f'(r2) = -0.5/sqrt(r2)
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle zero value as a special case
|
|
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
|
|
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
|
|
CAblasF::RSetV(chunksize,0.5,buf.m_df1);
|
|
CAblasF::RMergeDivV(chunksize,buf.m_funcbuf,buf.m_df1);
|
|
break;
|
|
case 2:
|
|
//--- Second derivatives is needed:
|
|
//--- f(r2) = -sqrt(r2+alpha2)
|
|
//--- f'(r2) = -0.5/sqrt(r2+alpha2)
|
|
//--- f''(r2) = 0.25/((r2+alpha2)^(3/2))
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle zero value as a special case
|
|
CAblasF::RCopyMulV(chunksize,-2.0,buf.m_funcbuf,buf.m_wrkbuf);
|
|
CAblasF::RSqrtV(chunksize,buf.m_funcbuf);
|
|
CAblasF::RMulV(chunksize,-1.0,buf.m_funcbuf);
|
|
CAblasF::RSetV(chunksize,0.5,buf.m_df1);
|
|
CAblasF::RMergeDivV(chunksize,buf.m_funcbuf,buf.m_df1);
|
|
CAblasF::RCopyV(chunksize,buf.m_df1,buf.m_df2);
|
|
CAblasF::RMergeDivV(chunksize,buf.m_wrkbuf,buf.m_df2);
|
|
break;
|
|
}
|
|
break;
|
|
case 2:
|
|
//--- f=r^2*ln(r)
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle ln(0) as a special case.
|
|
switch(needgradinfo)
|
|
{
|
|
case 0:
|
|
//--- No gradient info is required
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle zero value as a special case
|
|
for(int k=0; k<chunksize; k++)
|
|
buf.m_funcbuf.Mul(k,0.5*MathLog(buf.m_funcbuf[k]));
|
|
break;
|
|
case 1:
|
|
//--- First derivative is needed:
|
|
//--- f(r2) = 0.5*r2*ln(r2)
|
|
//--- f'(r2) = 0.5*ln(r2) + 0.5 = 0.5*(ln(r2)+1) =ln(r)+0.5
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle zero value as a special case
|
|
for(int k=0; k<chunksize; k++)
|
|
{
|
|
r2=buf.m_funcbuf[k];
|
|
lnr=0.5*MathLog(r2);
|
|
buf.m_funcbuf.Set(k,r2*lnr);
|
|
buf.m_df1.Set(k,lnr+0.5);
|
|
}
|
|
break;
|
|
case 2:
|
|
//--- Second derivative is needed:
|
|
//--- f(r2) = 0.5*r2*ln(r2)
|
|
//--- f'(r2) = 0.5*ln(r2) + 0.5 = 0.5*(ln(r2)+1) =ln(r)+0.5
|
|
//--- f''(r2)= 0.5/r2
|
|
//--- NOTE: FuncBuf[] is always positive due to small correction added,
|
|
//--- thus we have no need to handle zero value as a special case
|
|
for(int k=0; k<=chunksize-1; k++)
|
|
{
|
|
r2=buf.m_funcbuf[k];
|
|
lnr=0.5*MathLog(r2);
|
|
buf.m_funcbuf.Set(k,r2*lnr);
|
|
buf.m_df1.Set(k,lnr+0.5);
|
|
buf.m_df2.Set(k,0.5/r2);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": unexpected FuncType in ComputeRowChunk()");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recursive subroutine for parallel divide-and-conquer computation |
|
|
//| of matrix-vector product with coefficients vector. Works only for|
|
|
//| on-the-fly models with StorageType = 1 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ModelMatrixComputeProductRec(CRBF3Evaluator &modelmatrix,
|
|
CRowDouble &c,
|
|
CRowInt &rowidx,
|
|
CRowDouble &r,
|
|
int idx0,
|
|
int idx1,
|
|
bool toplevelcall)
|
|
{
|
|
//--- create variables
|
|
int s0=0;
|
|
int s1=0;
|
|
int colidx=0;
|
|
int curchunk=0;
|
|
int srcidx=0;
|
|
double distance0=0;
|
|
CRBF3EvaluatorBuffer buf;
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
|
|
return;
|
|
//--- Now split column indexes
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_functype==1 || modelmatrix.m_functype==2,__FUNCTION__+": unexpected FuncType"))
|
|
return;
|
|
|
|
buf=modelmatrix.m_bufferpool;
|
|
CAblasF::RSetAllocV(modelmatrix.m_nx,0.0,buf.m_x);
|
|
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_coeffbuf);
|
|
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_funcbuf);
|
|
CAblasF::RSetAllocV(modelmatrix.m_chunksize,0.0,buf.m_wrkbuf);
|
|
colidx=0;
|
|
srcidx=0;
|
|
distance0=1.0E-50;
|
|
if(modelmatrix.m_functype==1)
|
|
{
|
|
//--- Kernels that add squared parameter to the squared distance
|
|
distance0=CMath::Sqr(modelmatrix.m_funcparam);
|
|
}
|
|
while(colidx<modelmatrix.m_n)
|
|
{
|
|
//--- Handle basecase with size at most ChunkSize*ChunkSize
|
|
curchunk=MathMin(modelmatrix.m_chunksize,modelmatrix.m_n-colidx);
|
|
CAblasF::RCopyVX(curchunk,c,colidx,buf.m_coeffbuf,0);
|
|
for(int i=idx0; i<idx1; i++)
|
|
{
|
|
CAblasF::RCopyRV(modelmatrix.m_nx,modelmatrix.m_x,rowidx[i],buf.m_x);
|
|
ComputeRowChunk(modelmatrix,buf.m_x,buf,curchunk,srcidx,distance0,0);
|
|
r.Add(i,CAblasF::RDotV(curchunk,buf.m_funcbuf,buf.m_coeffbuf));
|
|
}
|
|
colidx+=curchunk;
|
|
srcidx+=modelmatrix.m_nx;
|
|
}
|
|
modelmatrix.m_bufferpool=buf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes product of the model matrix with vector C, writes result|
|
|
//| to R. |
|
|
//| NOTE: this function is thread safe and can be used with the same |
|
|
//| model matrix from different threads |
|
|
//| NOTE: If R is longer than M, it is not reallocated and additional|
|
|
//| elements are not modified. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ModelMatrixComputeProduct(CRBF3Evaluator &modelmatrix,
|
|
CRowDouble &c,
|
|
CRowDouble &r)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
|
|
return;
|
|
|
|
CAblasF::RAllocV(modelmatrix.m_n,r);
|
|
switch(modelmatrix.m_storagetype)
|
|
{
|
|
case 0:
|
|
CAblas::RMatrixGemVect(modelmatrix.m_n,modelmatrix.m_n,1.0,modelmatrix.m_f,0,0,0,c,0,0.0,r,0);
|
|
break;
|
|
case 1:
|
|
CAblasF::RSetV(modelmatrix.m_n,0.0,r);
|
|
ModelMatrixComputeProductRec(modelmatrix,c,modelmatrix.m_entireset,r,0,modelmatrix.m_n,true);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Computes product of the subset of the model matrix (only rows |
|
|
//| with indexes from Idx[]) with vector C, writes result to R. |
|
|
//| NOTE: this function is thread safe and can be used with the same |
|
|
//| model matrix from different threads |
|
|
//| NOTE: If R is longer than M, it is not reallocated and additional|
|
|
//| elements are not modified. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFV3::ModelMatrixComputeProductAtNodes(CRBF3Evaluator &modelmatrix,
|
|
CRowDouble &c,
|
|
CRowInt &idx,
|
|
int m,
|
|
CRowDouble &r)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(modelmatrix.m_storagetype==0 || modelmatrix.m_storagetype==1,__FUNCTION__+": unexpected StorageType"))
|
|
return;
|
|
|
|
CAblasF::RAllocV(m,r);
|
|
switch(modelmatrix.m_storagetype)
|
|
{
|
|
case 0:
|
|
for(int i=0; i<m; i++)
|
|
r.Set(i,CAblasF::RDotVR(modelmatrix.m_n,c,modelmatrix.m_f,idx[i]));
|
|
break;
|
|
case 1:
|
|
CAblasF::RSetV(m,0.0,r);
|
|
ModelMatrixComputeProductRec(modelmatrix,c,idx,r,0,m,true);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks whether basis function is conditionally positive definite |
|
|
//| or not, given the polynomial term type (ATerm = 1 means linear, |
|
|
//| ATerm = 2 means constant, ATerm = 3 means no polynomial term). |
|
|
//+------------------------------------------------------------------+
|
|
bool CRBFV3::IsCPDFunction(int functype,
|
|
int aterm)
|
|
{
|
|
bool result=false;
|
|
//--- check
|
|
if(!CAp::Assert(aterm==1 || aterm==2 || aterm==3,__FUNCTION__+": integrity check 3563 failed"))
|
|
return(false);
|
|
|
|
switch(functype)
|
|
{
|
|
case 1:
|
|
result=(aterm==2 || aterm==1);
|
|
break;
|
|
case 2:
|
|
result=(aterm==1);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,"IsCPDFunction: unexpected FuncType");
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform RBF model calculation in |
|
|
//| the multithreaded mode (multiple threads working with same RBF |
|
|
//| object). |
|
|
//| This object should be created with RBFCreateCalcBuffer(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFCalcBuffer
|
|
{
|
|
int m_modelversion;
|
|
CRowDouble m_dy;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CRBFV3CalcBuffer m_bufv3;
|
|
CRBFV2CalcBuffer m_bufv2;
|
|
CRBFV1CalcBuffer m_bufv1;
|
|
//--- constructor / destructor
|
|
CRBFCalcBuffer(void) { m_modelversion=0; }
|
|
~CRBFCalcBuffer(void) {}
|
|
//---
|
|
void Copy(const CRBFCalcBuffer&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFCalcBuffer&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFCalcBuffer::Copy(const CRBFCalcBuffer &obj)
|
|
{
|
|
m_modelversion=obj.m_modelversion;
|
|
m_dy=obj.m_dy;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_bufv3=obj.m_bufv3;
|
|
m_bufv2=obj.m_bufv2;
|
|
m_bufv1=obj.m_bufv1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF model. |
|
|
//| Never try to directly work with fields of this object - always |
|
|
//| use ALGLIB functions to use this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFModel
|
|
{
|
|
int m_algorithmtype;
|
|
int m_aterm;
|
|
int m_bftype;
|
|
int m_maxits;
|
|
int m_modelversion;
|
|
int m_n;
|
|
int m_nlayers;
|
|
int m_nnmaxits;
|
|
int m_nx;
|
|
int m_ny;
|
|
int m_progress10000;
|
|
double m_bfparam;
|
|
double m_epserr;
|
|
double m_epsort;
|
|
double m_lambdav;
|
|
double m_radvalue;
|
|
double m_radzvalue;
|
|
bool m_hasscale;
|
|
bool m_terminationrequest;
|
|
CRBFV1Model m_model1;
|
|
CRowDouble m_s;
|
|
CRBFV3Model m_model3;
|
|
CRBFV2Model m_model2;
|
|
CRBFCalcBuffer m_calcbuf;
|
|
CMatrixDouble m_x;
|
|
CMatrixDouble m_y;
|
|
//--- constructor / destructor
|
|
CRBFModel(void);
|
|
~CRBFModel(void) {}
|
|
//--- copy
|
|
void Copy(const CRBFModel&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFModel&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRBFModel::CRBFModel(void)
|
|
{
|
|
m_algorithmtype=0;
|
|
m_aterm=0;
|
|
m_bftype=0;
|
|
m_maxits=0;
|
|
m_modelversion=0;
|
|
m_n=0;
|
|
m_nlayers=0;
|
|
m_nnmaxits=0;
|
|
m_nx=0;
|
|
m_ny=0;
|
|
m_progress10000=0;
|
|
m_bfparam=0;
|
|
m_epserr=0;
|
|
m_epsort=0;
|
|
m_lambdav=0;
|
|
m_radvalue=0;
|
|
m_radzvalue=0;
|
|
m_hasscale=false;
|
|
m_terminationrequest=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFModel::Copy(const CRBFModel &obj)
|
|
{
|
|
m_algorithmtype=obj.m_algorithmtype;
|
|
m_aterm=obj.m_aterm;
|
|
m_bftype=obj.m_bftype;
|
|
m_maxits=obj.m_maxits;
|
|
m_modelversion=obj.m_modelversion;
|
|
m_n=obj.m_n;
|
|
m_nlayers=obj.m_nlayers;
|
|
m_nnmaxits=obj.m_nnmaxits;
|
|
m_nx=obj.m_nx;
|
|
m_ny=obj.m_ny;
|
|
m_progress10000=obj.m_progress10000;
|
|
m_bfparam=obj.m_bfparam;
|
|
m_epserr=obj.m_epserr;
|
|
m_epsort=obj.m_epsort;
|
|
m_lambdav=obj.m_lambdav;
|
|
m_radvalue=obj.m_radvalue;
|
|
m_radzvalue=obj.m_radzvalue;
|
|
m_hasscale=obj.m_hasscale;
|
|
m_terminationrequest=obj.m_terminationrequest;
|
|
m_model1=obj.m_model1;
|
|
m_s=obj.m_s;
|
|
m_model3=obj.m_model3;
|
|
m_model2=obj.m_model2;
|
|
m_calcbuf=obj.m_calcbuf;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RBF solution report: |
|
|
//| * TerminationType - termination type, positive values - |
|
|
//| success, non-positive - failure. |
|
|
//| Fields which are set by modern RBF solvers (hierarchical): |
|
|
//| * RMSError - root-mean-square error; NAN for old solvers |
|
|
//| (ML, QNN) |
|
|
//| * MaxError - maximum error; NAN for old solvers (ML, QNN) |
|
|
//+------------------------------------------------------------------+
|
|
struct CRBFReport
|
|
{
|
|
int m_acols;
|
|
int m_annz;
|
|
int m_arows;
|
|
int m_iterationscount;
|
|
int m_nmv;
|
|
int m_terminationtype;
|
|
double m_maxerror;
|
|
double m_rmserror;
|
|
//--- constructor / destructor
|
|
CRBFReport(void) { ZeroMemory(this); }
|
|
~CRBFReport(void) {}
|
|
//--- copy
|
|
void Copy(const CRBFReport&obj);
|
|
//--- overloading
|
|
void operator=(const CRBFReport&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CRBFReport::Copy(const CRBFReport &obj)
|
|
{
|
|
m_acols=obj.m_acols;
|
|
m_annz=obj.m_annz;
|
|
m_arows=obj.m_arows;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_nmv=obj.m_nmv;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_maxerror=obj.m_maxerror;
|
|
m_rmserror=obj.m_rmserror;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CRBF
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const double m_eps;
|
|
static const double m_rbffarradius;
|
|
static const int m_rbffirstversion;
|
|
static const int m_rbfversion2;
|
|
static const int m_rbfversion3;
|
|
|
|
static void RBFCreate(int nx,int ny,CRBFModel&s);
|
|
static void RBFCreateCalcBuffer(CRBFModel&s,CRBFCalcBuffer&buf);
|
|
static void RBFSetPoints(CRBFModel&s,CMatrixDouble&xy,int n);
|
|
static void RBFSetPointsAndScales(CRBFModel&r,CMatrixDouble&xy,int n,CRowDouble&s);
|
|
static void RBFSetAlgoQNN(CRBFModel&s,double q,double z);
|
|
static void RBFSetAlgoMultilayer(CRBFModel&s,double rbase,int nlayers,double lambdav);
|
|
static void RBFSetAlgoHierarchical(CRBFModel&s,double rbase,int nlayers,double lambdans);
|
|
static void RBFSetAlgoThinPlateSpline(CRBFModel&s,double lambdav);
|
|
static void RBFSetAlgoMultiQuadricManual(CRBFModel&s,double alpha,double lambdav);
|
|
static void RBFSetAlgoMultiQuadricAuto(CRBFModel&s,double lambdav);
|
|
static void RBFSetAlgoBiharmonic(CRBFModel&s,double lambdav);
|
|
static void RBFSetLinTerm(CRBFModel&s);
|
|
static void RBFSetConstTerm(CRBFModel&s);
|
|
static void RBFSetZeroTerm(CRBFModel&s);
|
|
static void RBFSetV2BF(CRBFModel&s,int bf);
|
|
static void RBFSetV2Its(CRBFModel&s,int maxits);
|
|
static void RBFSetV2SupportR(CRBFModel&s,double r);
|
|
static void RBFSetCond(CRBFModel&s,double epsort,double epserr,int maxits);
|
|
static void RBFBuildModel(CRBFModel&s,CRBFReport&rep);
|
|
static double RBFCalc1(CRBFModel&s,double x0);
|
|
static double RBFCalc2(CRBFModel&s,double x0,double x1);
|
|
static double RBFCalc3(CRBFModel&s,double x0,double x1,double x2);
|
|
static void RBFDiff1(CRBFModel&s,double x0,double&y,double&dy0);
|
|
static void RBFDiff2(CRBFModel&s,double x0,double x1,double&y,double&dy0,double&dy1);
|
|
static void RBFDiff3(CRBFModel&s,double x0,double x1,double x2,double&y,double&dy0,double&dy1,double&dy2);
|
|
static void RBFCalc(CRBFModel&s,CRowDouble&x,CRowDouble&y);
|
|
static void RBFDiff(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFHess(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFCalcBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y);
|
|
static void RBFDiffBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFHessBuf(CRBFModel&s,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFTSCalcBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y);
|
|
static void RBFTSDiffBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy);
|
|
static void RBFTSHessBuf(CRBFModel&s,CRBFCalcBuffer&buf,CRowDouble&x,CRowDouble&y,CRowDouble&dy,CRowDouble&d2y);
|
|
static void RBFGridCalc2(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CMatrixDouble&y);
|
|
static void RBFGridCalc2V(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&y);
|
|
static void RBFGridCalc2VSubset(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,bool &flagy[],CRowDouble&y);
|
|
static void RBFGridCalc3V(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,CRowDouble&y);
|
|
static void RBFGridCalc3VSubset(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,bool &flagy[],CRowDouble&y);
|
|
static void RBFGridCalc2VX(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,bool &flagy[],bool sparsey,CRowDouble&y);
|
|
static void RBFGridCalc3VX(CRBFModel&s,CRowDouble&x0,int n0,CRowDouble&x1,int n1,CRowDouble&x2,int n2,bool &flagy[],bool sparsey,CRowDouble&y);
|
|
static void RBFUnpack(CRBFModel&s,int &nx,int &ny,CMatrixDouble&xwr,int &nc,CMatrixDouble&v,int &modelversion);
|
|
static int RBFGetModelVersion(CRBFModel&s);
|
|
static double RBFPeekProgress(CRBFModel&s);
|
|
static void RBFRequestTermination(CRBFModel&s);
|
|
static void RBFAlloc(CSerializer&s,CRBFModel&model);
|
|
static void RBFSerialize(CSerializer&s,CRBFModel&model);
|
|
static void RBFUnserialize(CSerializer&s,CRBFModel&model);
|
|
|
|
private:
|
|
static void RBFPrepareNonSerializableFields(CRBFModel&s);
|
|
static void InitializeV1(int nx,int ny,CRBFV1Model&s);
|
|
static void InitializeV2(int nx,int ny,CRBFV2Model&s);
|
|
static void InitializeV3(int nx,int ny,CRBFV3Model&s);
|
|
static void ClearReportFields(CRBFReport&rep);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constants |
|
|
//+------------------------------------------------------------------+
|
|
const double CRBF::m_eps=1.0E-6;
|
|
const double CRBF::m_rbffarradius=6;
|
|
const int CRBF::m_rbffirstversion=0;
|
|
const int CRBF::m_rbfversion2=2;
|
|
const int CRBF::m_rbfversion3=3;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates RBF model for a scalar(NY = 1) or vector |
|
|
//| (NY > 1) function in a NX - dimensional space(NX >= 1). |
|
|
//| Newly created model is empty. It can be used for interpolation |
|
|
//| right after creation, but it just returns zeros. You have to add |
|
|
//| points to the model, tune interpolation settings, and then call |
|
|
//| model construction function RBFBuildModel() which will update |
|
|
//| model according to your specification. |
|
|
//| USAGE: |
|
|
//| 1. User creates model with RBFCreate() |
|
|
//| 2. User adds dataset with RBFSetPoints() or |
|
|
//| RBFSetPointsAndScales() |
|
|
//| 3. User selects RBF solver by calling: |
|
|
//| * RBFSetAlgoHierarchical() - for a HRBF solver, a |
|
|
//| hierarchical large - scale Gaussian RBFs (works well for |
|
|
//| uniformly distributed point clouds, but may fail when the |
|
|
//| data are non-uniform; use other solvers below in such |
|
|
//| cases) |
|
|
//| * RBFSetAlgoThinPlateSpline() - for a large - scale DDM-RBF |
|
|
//| solver with thin plate spline basis function being used |
|
|
//| * RBFSetAlgoBiharmonic() - for a large-scale DDM-RBF solver |
|
|
//| with biharmonic basis function being used |
|
|
//| * RBFSetAlgoMultiQuadricAuto() - for a large-scale DDM-RBF |
|
|
//| solver with multiquadric basis function being used |
|
|
//| (automatic selection of the scale parameter Alpha) |
|
|
//| * RBFSetAlgoMultiQuadricManual() - for a large-scale DDM-RBF|
|
|
//| solver with multiquadric basis function being used (manual|
|
|
//| selection of the scale parameter Alpha) |
|
|
//| 4.(OPTIONAL) User chooses polynomial term by calling: |
|
|
//| * RBFLinTerm() to set linear term (default) |
|
|
//| * RBFConstTerm() to set constant term |
|
|
//| * RBFZeroTerm() to set zero term |
|
|
//| 5. User calls RBFBuildModel() function which rebuilds model |
|
|
//| according to the specification |
|
|
//| INPUT PARAMETERS: |
|
|
//| NX - dimension of the space, NX >= 1 |
|
|
//| NY - function dimension, NY >= 1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - RBF model(initially equals to zero) |
|
|
//| NOTE 1: memory requirements. RBF models require amount of memory |
|
|
//| which is proportional to the number of data points. Some |
|
|
//| additional memory is allocated during model construction,|
|
|
//| but most of this memory is freed after the model |
|
|
//| coefficients are calculated. Amount of this additional |
|
|
//| memory depends on model construction algorithm being used|
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFCreate(int nx,int ny,CRBFModel &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nx>=1,__FUNCTION__+": NX<1"))
|
|
return;
|
|
if(!CAp::Assert(ny>=1,__FUNCTION__+": NY<1"))
|
|
return;
|
|
|
|
s.m_nx=nx;
|
|
s.m_ny=ny;
|
|
RBFPrepareNonSerializableFields(s);
|
|
//--- Select default model version according to NX.
|
|
//--- The idea is that when we call this function with NX=2 or NX=3, backward
|
|
//--- compatible dummy (zero) V1 model is created, so serialization produces
|
|
//--- model which are compatible with pre-3.11 ALGLIB.
|
|
InitializeV1(nx,ny,s.m_model1);
|
|
InitializeV2(nx,ny,s.m_model2);
|
|
InitializeV3(nx,ny,s.m_model3);
|
|
if(nx==2 || nx==3)
|
|
s.m_modelversion=1;
|
|
else
|
|
s.m_modelversion=2;
|
|
//--- Report fields
|
|
s.m_progress10000=0;
|
|
s.m_terminationrequest=false;
|
|
//--- Prepare buffers
|
|
RBFCreateCalcBuffer(s,s.m_calcbuf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel RBF model evaluations (with one RBF model |
|
|
//| instance being used from multiple threads, as long as different |
|
|
//| threads use different instances of the buffer). |
|
|
//| This buffer object can be used with RBFTSCalcBuf() function (here|
|
|
//| "ts" stands for "thread-safe", "buf" is a suffix which denotes |
|
|
//| function which reuses previously allocated output space). |
|
|
//| A buffer creation function (this function) is also thread-safe. |
|
|
//| I.e. you may safely create multiple buffers for the same RBF |
|
|
//| model from multiple threads. |
|
|
//| NOTE: the buffer object is just a collection of several |
|
|
//| preallocated dynamic arrays and precomputed values. If you |
|
|
//| delete its "parent" RBF model when the buffer is still |
|
|
//| alive, nothing bad will happen (no dangling pointers or |
|
|
//| resource leaks). The buffer will simply become useless. |
|
|
//| How to use it: |
|
|
//| * create RBF model structure with RBFCreate() |
|
|
//| * load data, tune parameters |
|
|
//| * call RBFBuildModel() |
|
|
//| * call RBFCreateCalcBuffer(), once per thread working with RBF |
|
|
//| model (you should call this function only AFTER call to |
|
|
//| RBFBuildModel(), see below for more information) |
|
|
//| * call RBFTSCalcBuf() from different threads, with each thread |
|
|
//| working with its own copy of buffer object. |
|
|
//| * it is recommended to reuse buffer as much as possible because|
|
|
//| buffer creation involves allocation of several large dynamic |
|
|
//| arrays. It is a huge waste of resource to use it just once. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with RBF model |
|
|
//| object which was used to initialize buffer. Any |
|
|
//| attempt to use buffer with different object is |
|
|
//| dangerous - you may get memory violation error because|
|
|
//| sizes of internal arrays do not fit to dimensions of |
|
|
//| RBF structure. |
|
|
//| IMPORTANT: you should call this function only for model which was|
|
|
//| built with RBFBuildModel() function, after successful |
|
|
//| invocation of RBFBuildModel(). Sizes of some |
|
|
//| internal structures are determined only after model is|
|
|
//| built, so buffer object created before model |
|
|
//| construction stage will be useless (and any attempt to|
|
|
//| use it will result in exception). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFCreateCalcBuffer(CRBFModel &s,CRBFCalcBuffer &buf)
|
|
{
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
buf.m_modelversion=1;
|
|
CRBFV1::RBFV1CreateCalcBuffer(s.m_model1,buf.m_bufv1);
|
|
break;
|
|
case 2:
|
|
buf.m_modelversion=2;
|
|
CRBFV2::RBFV2CreateCalcBuffer(s.m_model2,buf.m_bufv2);
|
|
break;
|
|
case 3:
|
|
buf.m_modelversion=3;
|
|
CRBFV3::RBFV3CreateCalcBuffer(s.m_model3,buf.m_bufv3);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset. |
|
|
//| This function overrides results of the previous calls, i.e. |
|
|
//| multiple calls of this function will result in only the last set |
|
|
//| being added. |
|
|
//| IMPORTANT: ALGLIB version 3.11 and later allows you to specify a |
|
|
//| set of per-dimension scales. Interpolation radii are |
|
|
//| multiplied by the scale vector. It may be useful if |
|
|
//| you have mixed spatio - temporal data (say, a set of |
|
|
//| 3D slices recorded at different times). You should |
|
|
//| call RBFSetPointsAndScales() function to use this |
|
|
//| feature. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call. |
|
|
//| XY - points, array[N, NX + NY]. One row corresponds to |
|
|
//| one point in the dataset. First NX elements are |
|
|
//| coordinates, next NY elements are function values. |
|
|
//| Array may be larger than specified, in this case |
|
|
//| only leading [N, NX+NY] elements will be used. |
|
|
//| N - number of points in the dataset |
|
|
//| After you've added dataset and (optionally) tuned algorithm |
|
|
//| settings you should call RBFBuildModel() in order to build a |
|
|
//| model for you. |
|
|
//| NOTE: dataset added by this function is not saved during model |
|
|
//| serialization. MODEL ITSELF is serialized, but data used |
|
|
//| to build it are not. |
|
|
//| So, if you 1) add dataset to empty RBF model, 2) serialize and |
|
|
//| unserialize it, then you will get an empty RBF model with no |
|
|
//| dataset being attached. |
|
|
//| From the other side, if you call RBFBuildModel() between(1) and |
|
|
//| (2), then after(2) you will get your fully constructed RBF model-|
|
|
//| but again with no dataset attached, so subsequent calls to |
|
|
//| RBFBuildModel() will produce empty model. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetPoints(CRBFModel &s,CMatrixDouble &xy,int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(xy)>=s.m_nx+s.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,n,s.m_nx+s.m_ny),__FUNCTION__+": XY contains infinite or NaN values!"))
|
|
return;
|
|
|
|
s.m_n=n;
|
|
s.m_hasscale=false;
|
|
s.m_x.Resize(s.m_n,s.m_nx);
|
|
s.m_y.Resize(s.m_n,s.m_ny);
|
|
for(int i=0; i<s.m_n; i++)
|
|
{
|
|
for(int j=0; j<s.m_nx; j++)
|
|
s.m_x.Set(i,j,xy.Get(i,j));
|
|
for(int j=0; j<s.m_ny; j++)
|
|
s.m_y.Set(i,j,xy.Get(i,j+s.m_nx));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset and a vector of per-dimension scales. |
|
|
//| It may be useful if you have mixed spatio - temporal data - say, |
|
|
//| a set of 3D slices recorded at different times. Such data |
|
|
//| typically require different RBF radii for spatial and temporal |
|
|
//| dimensions. ALGLIB solves this problem by specifying single RBF |
|
|
//| radius, which is (optionally) multiplied by the scale vector. |
|
|
//| This function overrides results of the previous calls, i.e. |
|
|
//| multiple calls of this function will result in only the last set |
|
|
//| being added. |
|
|
//| IMPORTANT: only modern RBF algorithms support variable scaling. |
|
|
//| Legacy algorithms like RBF-ML or QNN algorithms will |
|
|
//| result in - 3 completion code being returned(incorrect|
|
|
//| algorithm). |
|
|
//| INPUT PARAMETERS: |
|
|
//| R - RBF model, initialized by RBFCreate() call. |
|
|
//| XY - points, array[N, NX + NY]. One row corresponds to |
|
|
//| one point in the dataset. First NX elements are |
|
|
//| coordinates, next NY elements are function values. |
|
|
//| Array may be larger than specified, in this case |
|
|
//| only leading [N, NX+NY] elements will be used. |
|
|
//| N - number of points in the dataset |
|
|
//| S - array[NX], scale vector, S[i] > 0. |
|
|
//| After you've added dataset and (optionally) tuned algorithm |
|
|
//| settings you should call RBFBuildModel() in order to build a |
|
|
//| model for you. |
|
|
//| NOTE: dataset added by this function is not saved during model |
|
|
//| serialization. MODEL ITSELF is serialized, but data used |
|
|
//| to build it are not. |
|
|
//| So, if you 1) add dataset to empty RBF model, 2) serialize and |
|
|
//| unserialize it, then you will get an empty RBF model with no |
|
|
//| dataset being attached. |
|
|
//| From the other side, if you call RBFBuildModel() between(1) and |
|
|
//| (2), then after(2) you will get your fully constructed RBF model-|
|
|
//| but again with no dataset attached, so subsequent calls to |
|
|
//| RBFBuildModel() will produce empty model. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetPointsAndScales(CRBFModel &r,
|
|
CMatrixDouble &xy,
|
|
int n,
|
|
CRowDouble &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>0,__FUNCTION__+": N<=0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Rows(xy)>=n,__FUNCTION__+": Rows(XY)<N"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Cols(xy)>=r.m_nx+r.m_ny,__FUNCTION__+": Cols(XY)<NX+NY"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(s)>=r.m_nx,__FUNCTION__+": Length(S)<NX"))
|
|
return;
|
|
|
|
r.m_n=n;
|
|
r.m_hasscale=true;
|
|
r.m_x.Resize(r.m_n,r.m_nx);
|
|
r.m_y.Resize(r.m_n,r.m_ny);
|
|
for(int i=0; i<r.m_n; i++)
|
|
{
|
|
for(int j=0; j<=r.m_nx-1; j++)
|
|
r.m_x.Set(i,j,xy.Get(i,j));
|
|
for(int j=0; j<=r.m_ny-1; j++)
|
|
r.m_y.Set(i,j,xy.Get(i,j+r.m_nx));
|
|
}
|
|
r.m_s.Resize(r.m_nx);
|
|
for(int i=0; i<r.m_nx; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(s[i]),__FUNCTION__+": S[i] is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(s[i]>0.0,__FUNCTION__+": S[i]<=0"))
|
|
return;
|
|
r.m_s.Set(i,s[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| DEPRECATED: this function is deprecated. ALGLIB includes new RBF |
|
|
//| model algorithms: |
|
|
//| DDM - RBF (since version 3.19) and HRBF (since version 3.11). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoQNN(CRBFModel &s,double q,double z)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(q),__FUNCTION__+": Q is infinite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(q>0.0,__FUNCTION__+": Q<=0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(z),__FUNCTION__+": Z is infinite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(z>0.0,__FUNCTION__+": Z<=0"))
|
|
return;
|
|
|
|
s.m_radvalue=q;
|
|
s.m_radzvalue=z;
|
|
s.m_algorithmtype=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| DEPRECATED: this function is deprecated. ALGLIB includes new RBF |
|
|
//| model algorithms: |
|
|
//| DDM - RBF(since version 3.19) and HRBF (since version 3.11). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoMultilayer(CRBFModel &s,double rbase,
|
|
int nlayers,double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(rbase),__FUNCTION__+": RBase is infinite or NaN"))
|
|
return;
|
|
if(!CAp::Assert(rbase>0.0,__FUNCTION__+": RBase<=0"))
|
|
return;
|
|
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": NLayers<0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is infinite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV<0"))
|
|
return;
|
|
|
|
s.m_radvalue=rbase;
|
|
s.m_nlayers=nlayers;
|
|
s.m_algorithmtype=2;
|
|
s.m_lambdav=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function chooses HRBF solver, a 2nd version of ALGLIB RBFs. |
|
|
//| This algorithm is called Hierarchical RBF. It similar to its |
|
|
//| previous incarnation, RBF-ML, i.e. it also builds a sequence of |
|
|
//| models with decreasing radii. However, it uses more economical |
|
|
//| way of building upper layers (ones with large radii), which |
|
|
//| results in faster model construction and evaluation, as well as |
|
|
//| smaller memory footprint during construction. |
|
|
//| This algorithm has following important features: |
|
|
//| * ability to handle millions of points |
|
|
//| * controllable smoothing via nonlinearity penalization |
|
|
//| * support for specification of per - dimensional radii via |
|
|
//| scale vector, which is set by means of RBFSetPointsAndScales |
|
|
//| function. This feature is useful if you solve spatio - |
|
|
//| temporal interpolation problems, where different radii are |
|
|
//| required for spatial and temporal dimensions. |
|
|
//| Running times are roughly proportional to: |
|
|
//| * N*log(N) |
|
|
//| * NLayers - for the model construction |
|
|
//| * N*NLayers - for the model evaluation |
|
|
//| You may see that running time does not depend on search radius or|
|
|
//| points density, just on the number of layers in the hierarchy. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| RBase - RBase parameter, RBase > 0 |
|
|
//| NLayers - NLayers parameter, NLayers > 0, recommended value |
|
|
//| to start with - about 5. |
|
|
//| LambdaNS - >= 0, nonlinearity penalty coefficient, negative |
|
|
//| values are not allowed. This parameter adds |
|
|
//| controllable smoothing to the problem, which may |
|
|
//| reduce noise. Specification of non-zero lambda |
|
|
//| means that in addition to fitting error solver will|
|
|
//| also minimize LambdaNS* | S''(x) | 2 (appropriately|
|
|
//| generalized to multiple dimensions. |
|
|
//| Specification of exactly zero value means that no penalty is |
|
|
//| added (we do not even evaluate matrix of second derivatives which|
|
|
//| is necessary for smoothing). |
|
|
//| Calculation of nonlinearity penalty is costly - it results in |
|
|
//| several - fold increase of model construction time. Evaluation |
|
|
//| time remains the same. |
|
|
//| Optimal lambda is problem - dependent and requires trial and |
|
|
//| error. Good value to start from is 1e-5...1e-6, which corresponds|
|
|
//| to slightly noticeable smoothing of the function. Value 1e-2 |
|
|
//| usually means that quite heavy smoothing is applied. |
|
|
//| TUNING ALGORITHM |
|
|
//| In order to use this algorithm you have to choose three |
|
|
//| parameters: |
|
|
//| * initial radius RBase |
|
|
//| * number of layers in the model NLayers |
|
|
//| * penalty coefficient LambdaNS |
|
|
//| Initial radius is easy to choose - you can pick any number |
|
|
//| several times larger than the average distance between points. |
|
|
//| Algorithm won't break down if you choose radius which is too |
|
|
//| large (model construction time will increase, but model will be |
|
|
//| built correctly). |
|
|
//| Choose such number of layers that RLast = RBase / 2^(NLayers - 1)|
|
|
//| (radius used by the last layer) will be smaller than the typical |
|
|
//| distance between points. In case model error is too large, you |
|
|
//| can increase number of layers. Having more layers will make model|
|
|
//| construction and evaluation proportionally slower, but it will |
|
|
//| allow you to have model which precisely fits your data. From the |
|
|
//| other side, if you want to suppress noise, you can DECREASE |
|
|
//| number of layers to make your model less flexible (or specify |
|
|
//| non-zero LambdaNS). |
|
|
//| TYPICAL ERRORS: |
|
|
//| 1. Using too small number of layers - RBF models with large |
|
|
//| radius are not flexible enough to reproduce small variations|
|
|
//| in the target function. You need many layers with different |
|
|
//| radii, from large to small, in order to have good model. |
|
|
//| 2. Using initial radius which is too small. You will get model |
|
|
//| with "holes" in the areas which are too far away from |
|
|
//| interpolation centers. However, algorithm will work |
|
|
//| correctly (and quickly) in this case. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoHierarchical(CRBFModel &s,double rbase,
|
|
int nlayers,double lambdans)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(rbase),__FUNCTION__+": RBase is infinite or NaN"))
|
|
return;
|
|
if(!CAp::Assert(rbase>0.0,__FUNCTION__+": RBase<=0"))
|
|
return;
|
|
if(!CAp::Assert(nlayers>=0,__FUNCTION__+": NLayers<0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(lambdans) && lambdans>=0.0,__FUNCTION__+": LambdaNS<0 or infinite"))
|
|
return;
|
|
|
|
s.m_radvalue=rbase;
|
|
s.m_nlayers=nlayers;
|
|
s.m_algorithmtype=3;
|
|
s.m_lambdav=lambdans;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function chooses a thin plate spline DDM-RBF solver, a fast |
|
|
//| RBF solver with f(r) = r ^ 2 * ln(r) basis function. |
|
|
//| This algorithm has following important features: |
|
|
//| * easy setup - no tunable parameters |
|
|
//| * C1 continuous RBF model (gradient is defined everywhere, but |
|
|
//| Hessian is undefined at nodes), high - quality interpolation |
|
|
//| * fast model construction algorithm with O(N) memory and O(N^2)|
|
|
//| running time requirements. Hundreds of thousands of points |
|
|
//| can be handled with this algorithm. |
|
|
//| * controllable smoothing via optional nonlinearity penalty |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
|
|
//| * LambdaV = 0 means that no smoothing is applied, |
|
|
//| i.e. the spline tries to pass through|
|
|
//| all dataset points exactly |
|
|
//| * LambdaV > 0 means that a smoothing thin plate |
|
|
//| spline is built, with larger LambdaV |
|
|
//| corresponding to models with less |
|
|
//| nonlinearities. Smoothing spline |
|
|
//| reproduces target values at nodes |
|
|
//| with small error; from the other |
|
|
//| side, it is much more stable. |
|
|
//| Recommended values: |
|
|
//| * 1.0E-6 for minimal stability improving smoothing |
|
|
//| * 1.0E-3 a good value to start experiments; first results are |
|
|
//| visible |
|
|
//| * 1.0 for strong smoothing |
|
|
//| IMPORTANT: this model construction algorithm was introduced in |
|
|
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
|
|
//| with previous versions of ALGLIB. You can not |
|
|
//| unserialize models produced with this function in |
|
|
//| ALGLIB 3.18 or earlier. |
|
|
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
|
|
//| somewhat slower than compactly supported RBFs built with |
|
|
//| HRBF algorithm due to the fact that non-compact basis |
|
|
//| function does not vanish far away from the nodes. From the |
|
|
//| other side, polyharmonic RBFs often produce much better |
|
|
//| results than HRBFs. |
|
|
//| NOTE: this algorithm supports specification of per-dimensional |
|
|
//| radii via scale vector, which is set by means of |
|
|
//| RBFSetPointsAndScales() function. This feature is useful if|
|
|
//| you solve spatio-temporal interpolation problems where |
|
|
//| different radii are required for spatial and temporal |
|
|
//| dimensions. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoThinPlateSpline(CRBFModel &s,double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
|
|
return;
|
|
|
|
s.m_algorithmtype=4;
|
|
s.m_bftype=2;
|
|
s.m_bfparam=0;
|
|
s.m_lambdav=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function chooses a multiquadric DDM - RBF solver, a fast RBF|
|
|
//| solver with f(r) = sqrt(r ^ 2 + Alpha ^ 2) as a basis function, |
|
|
//| with manual choice of the scale parameter Alpha. |
|
|
//| This algorithm has following important features: |
|
|
//| * C2 continuous RBF model(when Alpha > 0 is used; for Alpha = 0|
|
|
//| the model is merely C0 continuous) |
|
|
//| * fast model construction algorithm with O(N) memory and O(N^2)|
|
|
//| running time requirements. Hundreds of thousands of points |
|
|
//| can be handled with this algorithm. |
|
|
//| * controllable smoothing via optional nonlinearity penalty |
|
|
//| One important point is that this algorithm includes tunable |
|
|
//| parameter Alpha, which should be carefully chosen. Selecting too |
|
|
//| large value will result in extremely badly conditioned problems |
|
|
//| (interpolation accuracy may degrade up to complete breakdown) |
|
|
//| whilst selecting too small value may produce models that are |
|
|
//| precise but nearly nonsmooth at the nodes. |
|
|
//| Good value to start from is mean distance between nodes. |
|
|
//| Generally, choosing too small Alpha is better than choosing too |
|
|
//| large - in the former case you still have model that reproduces |
|
|
//| target values at the nodes. |
|
|
//| In most cases, better option is to choose good Alpha |
|
|
//| automatically - it is done by another version of the same |
|
|
//| algorithm that is activated by calling RBFSetAlgoMultiQuadricAuto|
|
|
//| method. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| Alpha - basis function parameter, Alpha >= 0: |
|
|
//| * Alpha > 0 means that multiquadric algorithm is |
|
|
//| used which produces C2-continuous RBF |
|
|
//| model |
|
|
//| * Alpha = 0 means that the multiquadric kernel |
|
|
//| effectively becomes a biharmonic one: |
|
|
//| f = r. As a result, the model becomes |
|
|
//| nonsmooth at nodes, and hence is C0 |
|
|
//| continuous |
|
|
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
|
|
//| * LambdaV = 0 means that no smoothing is applied, |
|
|
//| i.e. the spline tries to pass through|
|
|
//| all dataset points exactly |
|
|
//| * LambdaV > 0 means that a multiquadric spline is |
|
|
//| built with larger LambdaV |
|
|
//| corresponding to models with less |
|
|
//| nonlinearities. Smoothing spline |
|
|
//| reproduces target values at nodes |
|
|
//| with small error; from the other |
|
|
//| side, it is much more stable. |
|
|
//| Recommended values: |
|
|
//| * 1.0E-6 for minimal stability improving smoothing |
|
|
//| * 1.0E-3 a good value to start experiments; first results are |
|
|
//| visible |
|
|
//| * 1.0 for strong smoothing |
|
|
//| IMPORTANT: this model construction algorithm was introduced in |
|
|
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
|
|
//| with previous versions of ALGLIB. You can not |
|
|
//| unserialize models produced with this function in |
|
|
//| ALGLIB 3.18 or earlier. |
|
|
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
|
|
//| somewhat slower than compactly supported RBFs built with |
|
|
//| HRBF algorithm due to the fact that non-compact basis |
|
|
//| function does not vanish far away from the nodes. From the |
|
|
//| other side, polyharmonic RBFs often produce much better |
|
|
//| results than HRBFs. |
|
|
//| NOTE: this algorithm supports specification of per-dimensional |
|
|
//| radii via scale vector, which is set by means of |
|
|
//| RBFSetPointsAndScales() function. This feature is useful if|
|
|
//| you solve spatio-temporal interpolation problems where |
|
|
//| different radii are required for spatial and temporal |
|
|
//| dimensions. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoMultiQuadricManual(CRBFModel &s,double alpha,
|
|
double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(alpha),__FUNCTION__+": Alpha is infinite or NAN"))
|
|
return;
|
|
if(!CAp::Assert(alpha>=0.0,__FUNCTION__+": Alpha<0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
|
|
return;
|
|
|
|
s.m_algorithmtype=4;
|
|
s.m_bftype=1;
|
|
s.m_bfparam=alpha;
|
|
s.m_lambdav=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function chooses a multiquadric DDM-RBF solver, a fast RBF |
|
|
//| solver with f(r) = sqrt(r ^ 2 + Alpha ^ 2) as a basis function, |
|
|
//| with Alpha being automatically determined. |
|
|
//| This algorithm has following important features: |
|
|
//| * easy setup - no need to tune Alpha, good value is |
|
|
//| automatically assigned |
|
|
//| * C2 continuous RBF model |
|
|
//| * fast model construction algorithm with O(N) memory and O(N^2)|
|
|
//| running time requirements. Hundreds of thousands of points |
|
|
//| can be handled with this algorithm. |
|
|
//| * controllable smoothing via optional nonlinearity penalty |
|
|
//| This algorithm automatically selects Alpha as a mean distance to |
|
|
//| the nearest neighbor(ignoring neighbors that are too close). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
|
|
//| * LambdaV = 0 means that no smoothing is applied, |
|
|
//| i.e. the spline tries to pass through|
|
|
//| all dataset points exactly |
|
|
//| * LambdaV > 0 means that a multiquadric spline is |
|
|
//| built with larger LambdaV |
|
|
//| corresponding to models with less |
|
|
//| nonlinearities. Smoothing spline |
|
|
//| reproduces target values at nodes |
|
|
//| with small error; from the other |
|
|
//| side, it is much more stable. |
|
|
//| Recommended values: |
|
|
//| * 1.0E-6 for minimal stability improving smoothing |
|
|
//| * 1.0E-3 a good value to start experiments; first results are |
|
|
//| visible |
|
|
//| * 1.0 for strong smoothing |
|
|
//| IMPORTANT: this model construction algorithm was introduced in |
|
|
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
|
|
//| with previous versions of ALGLIB. You can not |
|
|
//| unserialize models produced with this function in |
|
|
//| ALGLIB 3.18 or earlier. |
|
|
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
|
|
//| somewhat slower than compactly supported RBFs built with |
|
|
//| HRBF algorithm due to the fact that non-compact basis |
|
|
//| function does not vanish far away from the nodes. From the |
|
|
//| other side, polyharmonic RBFs often produce much better |
|
|
//| results than HRBFs. |
|
|
//| NOTE: this algorithm supports specification of per-dimensional |
|
|
//| radii via scale vector, which is set by means of |
|
|
//| RBFSetPointsAndScales() function. This feature is useful if|
|
|
//| you solve spatio - temporal interpolation problems where |
|
|
//| different radii are required for spatial and temporal |
|
|
//| dimensions. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoMultiQuadricAuto(CRBFModel &s,double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
|
|
return;
|
|
|
|
s.m_algorithmtype=4;
|
|
s.m_bftype=1;
|
|
s.m_bfparam=-1.0;
|
|
s.m_lambdav=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function chooses a biharmonic DDM-RBF solver, a fast RBF |
|
|
//| solver with f(r) = r as a basis function. |
|
|
//| This algorithm has following important features: |
|
|
//| * no tunable parameters |
|
|
//| * C0 continuous RBF model (the model has discontinuous |
|
|
//| derivatives at the interpolation nodes) |
|
|
//| * fast model construction algorithm with O(N) memory and O(N^2)|
|
|
//| running time requirements. Hundreds of thousands of points |
|
|
//| can be handled with this algorithm. |
|
|
//| * controllable smoothing via optional nonlinearity penalty |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| LambdaV - smoothing parameter, LambdaV >= 0, defaults to 0.0:|
|
|
//| * LambdaV = 0 means that no smoothing is applied, |
|
|
//| i.e. the spline tries to pass through|
|
|
//| all dataset points exactly |
|
|
//| * LambdaV > 0 means that a multiquadric spline is |
|
|
//| built with larger LambdaV |
|
|
//| corresponding to models with less |
|
|
//| nonlinearities. Smoothing spline |
|
|
//| reproduces target values at nodes |
|
|
//| with small error; from the other |
|
|
//| side, it is much more stable. |
|
|
//| Recommended values: |
|
|
//| * 1.0E-6 for minimal stability improving smoothing |
|
|
//| * 1.0E-3 a good value to start experiments; first results are |
|
|
//| visible |
|
|
//| * 1.0 for strong smoothing |
|
|
//| IMPORTANT: this model construction algorithm was introduced in |
|
|
//| ALGLIB 3.19 and produces models which are INCOMPATIBLE|
|
|
//| with previous versions of ALGLIB. You can not |
|
|
//| unserialize models produced with this function in |
|
|
//| ALGLIB 3.18 or earlier. |
|
|
//| NOTE: polyharmonic RBFs, including thin plate splines, are |
|
|
//| somewhat slower than compactly supported RBFs built with |
|
|
//| HRBF algorithm due to the fact that non-compact basis |
|
|
//| function does not vanish far away from the nodes. From the |
|
|
//| other side, polyharmonic RBFs often produce much better |
|
|
//| results than HRBFs. |
|
|
//| NOTE: this algorithm supports specification of per-dimensional |
|
|
//| radii via scale vector, which is set by means of |
|
|
//| RBFSetPointsAndScales() function. This feature is useful if|
|
|
//| you solve spatio - temporal interpolation problems where |
|
|
//| different radii are required for spatial and temporal |
|
|
//| dimensions. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetAlgoBiharmonic(CRBFModel &s,double lambdav)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(lambdav),__FUNCTION__+": LambdaV is not finite number"))
|
|
return;
|
|
if(!CAp::Assert(lambdav>=0.0,__FUNCTION__+": LambdaV is negative"))
|
|
return;
|
|
|
|
s.m_algorithmtype=4;
|
|
s.m_bftype=1;
|
|
s.m_bfparam=0;
|
|
s.m_lambdav=lambdav;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets linear term (model is a sum of radial basis |
|
|
//| functions plus linear polynomial). This function won't have |
|
|
//| effect until next call to RBFBuildModel(). |
|
|
//| Using linear term is a default option and it is the best one-it |
|
|
//| provides best convergence guarantees for all RBF model types: |
|
|
//| legacy RBF-QNN and RBF-ML, Gaussian HRBFs and all types of |
|
|
//| DDM-RBF models. |
|
|
//| Other options, like constant or zero term, work for HRBFs, almost|
|
|
//| always work for DDM-RBFs but provide no stability guarantees in |
|
|
//| the latter case (e.g. the solver may fail on some carefully |
|
|
//| prepared problems). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetLinTerm(CRBFModel &s)
|
|
{
|
|
s.m_aterm=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets constant term (model is a sum of radial basis |
|
|
//| functions plus constant). This function won't have effect until |
|
|
//| next call to RBFBuildModel(). |
|
|
//| IMPORTANT: thin plate splines require polynomial term to be |
|
|
//| linear, not constant, in order to provide |
|
|
//| interpolation guarantees. Although failures are |
|
|
//| exceptionally rare, some small toy problems may result|
|
|
//| in degenerate linear systems. Thus, it is advised to |
|
|
//| use linear term when one fits data with TPS. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetConstTerm(CRBFModel &s)
|
|
{
|
|
s.m_aterm=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets zero term (model is a sum of radial basis |
|
|
//| functions without polynomial term). This function won't have |
|
|
//| effect until next call to RBFBuildModel(). |
|
|
//| IMPORTANT: only Gaussian RBFs(HRBF algorithm) provide |
|
|
//| interpolation guarantees when no polynomial term is |
|
|
//| used. Most other RBFs, including biharmonic splines, |
|
|
//| thin plate splines and multiquadrics, require at least|
|
|
//| constant term(biharmonic and multiquadric) or linear |
|
|
//| one (thin plate splines) in order to guarantee |
|
|
//| non-degeneracy of linear systems being solved. |
|
|
//| Although failures are exceptionally rare, some small toy problems|
|
|
//| still may result in degenerate linear systems. Thus, it is |
|
|
//| advised to use constant / linear term, unless one is 100 % sure |
|
|
//| that he needs zero term. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetZeroTerm(CRBFModel &s)
|
|
{
|
|
s.m_aterm=3;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets basis function type, which can be: |
|
|
//| * 0 for classic Gaussian |
|
|
//| * 1 for fast and compact bell - like basis function, which |
|
|
//| becomes exactly zero at distance equal to 3 * R (default |
|
|
//| option). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| BF - basis function type: |
|
|
//| * 0 - classic Gaussian |
|
|
//| * 1 - fast and compact one |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetV2BF(CRBFModel &s,int bf)
|
|
{
|
|
if(!CAp::Assert(bf==0 || bf==1,__FUNCTION__+": BF<>0 and BF<>1"))
|
|
return;
|
|
s.m_model2.m_basisfunction=bf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping criteria of the underlying linear |
|
|
//| solver for hierarchical (version 2) RBF constructor. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| MaxIts - this criterion will stop algorithm after MaxIts |
|
|
//| iterations. Typically a few hundreds iterations is |
|
|
//| required, with 400 being a good default value to |
|
|
//| start experimentation. Zero value means that |
|
|
//| default value will be selected. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetV2Its(CRBFModel &s,int maxits)
|
|
{
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts is negative"))
|
|
return;
|
|
s.m_model2.m_maxits=maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets support radius parameter of hierarchical |
|
|
//| (version 2) RBF constructor. |
|
|
//| Hierarchical RBF model achieves great speed-up by removing from |
|
|
//| the model excessive (too dense) nodes. Say, if you have RBF |
|
|
//| radius equal to 1 meter, and two nodes are just 1 millimeter |
|
|
//| apart, you may remove one of them without reducing model quality.|
|
|
//| Support radius parameter is used to justify which points need |
|
|
//| removal, and which do not. If two points are less than |
|
|
//| SUPPORT_R*CUR_RADIUS units of distance apart, one of them is |
|
|
//| removed from the model. The larger support radius is, the faster |
|
|
//| model construction AND evaluation are. However, too large values |
|
|
//| result in "bumpy" models. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| R - support radius coefficient, >= 0. |
|
|
//| Recommended values are [0.1, 0.4] range, with 0.1 being default |
|
|
//| value. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetV2SupportR(CRBFModel &s,double r)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(r),__FUNCTION__+": R is not finite"))
|
|
return;
|
|
if(!CAp::Assert(r>=0.0,__FUNCTION__+": R<0"))
|
|
return;
|
|
|
|
s.m_model2.m_supportr=r;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping criteria of the underlying linear |
|
|
//| solver. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| EpsOrt - orthogonality stopping criterion, EpsOrt >= 0. |
|
|
//| Algorithm will stop when ||A'*r||<=EpsOrt where A' |
|
|
//| is a transpose of the system matrix, r is a |
|
|
//| residual vector. Recommended value of EpsOrt is |
|
|
//| equal to 1E-6. This criterion will stop algorithm |
|
|
//| when we have "bad fit" situation, i.e. when we |
|
|
//| should stop in a point with large, nonzero residual|
|
|
//| EpsErr - residual stopping criterion. Algorithm will stop |
|
|
//| when ||r|| <= EpsErr* ||b||, where r is a residual |
|
|
//| vector, b is a right part of the system (function |
|
|
//| values). Recommended value of EpsErr is equal to |
|
|
//| 1E-3 or 1E-6. This criterion will stop algorithm in|
|
|
//| a "good fit" situation when we have near-zero |
|
|
//| residual near the desired solution. |
|
|
//| MaxIts - this criterion will stop algorithm after MaxIts |
|
|
//| iterations. It should be used for debugging |
|
|
//| purposes only! Zero MaxIts means that no limit is |
|
|
//| placed on the number of iterations. |
|
|
//| We recommend to set moderate non-zero values EpsOrt and EpsErr |
|
|
//| simultaneously. Values equal to 10E-6 are good to start with. In |
|
|
//| case you need high performance and do not need high precision, |
|
|
//| you may decrease EpsErr down to 0.001. However, we do not |
|
|
//| recommend decreasing EpsOrt. |
|
|
//| As for MaxIts, we recommend to leave it zero unless you know what|
|
|
//| you do. |
|
|
//| NOTE: this function has some serialization - related subtleties. |
|
|
//| We recommend you to study serialization examples from |
|
|
//| ALGLIB Reference Manual if you want to perform |
|
|
//| serialization of your models. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSetCond(CRBFModel &s,double epsort,double epserr,
|
|
int maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(epsort) && epsort>=0.0,__FUNCTION__+": EpsOrt is negative,INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(epserr) && epserr>=0.0,__FUNCTION__+": EpsB is negative,INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts is negative"))
|
|
return;
|
|
|
|
if(epsort==0.0 && epserr==0.0 && maxits==0)
|
|
{
|
|
s.m_epsort=m_eps;
|
|
s.m_epserr=m_eps;
|
|
s.m_maxits=0;
|
|
}
|
|
else
|
|
{
|
|
s.m_epsort=epsort;
|
|
s.m_epserr=epserr;
|
|
s.m_maxits=maxits;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds RBF model and returns report (contains some |
|
|
//| information which can be used for evaluation of the algorithm |
|
|
//| properties). |
|
|
//| Call to this function modifies RBF model by calculating its |
|
|
//| centers/radii/weights and saving them into RBFModel structure. |
|
|
//| Initially RBFModel contain zero coefficients, but after call to |
|
|
//| this function we will have coefficients which were calculated in |
|
|
//| order to fit our dataset. |
|
|
//| After you called this function you can call RBFCalc(), |
|
|
//| RBFGridCalc() and other model calculation functions. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, initialized by RBFCreate() call |
|
|
//| Rep - report: |
|
|
//| * Rep.TerminationType: |
|
|
//| * -5 - non-distinct basis function centers were |
|
|
//| detected, interpolation aborted; only QNN|
|
|
//| returns this error code, other algorithms|
|
|
//| can handle non-distinct nodes. |
|
|
//| * -4 - nonconvergence of the internal SVD solver|
|
|
//| * -3 incorrect model construction algorithm |
|
|
//| was chosen: QNN or RBF-ML, combined with |
|
|
//| one of the incompatible features: |
|
|
//| * NX = 1 or NX > 3 |
|
|
//| * points with per - dimension scales. |
|
|
//| * 1 - successful termination |
|
|
//| * 8 - a termination request was submitted via |
|
|
//| RBFRequestTermination() function. |
|
|
//| Fields which are set only by modern RBF solvers (hierarchical or |
|
|
//| nonnegative; older solvers like QNN and ML initialize these |
|
|
//| fields by NANs): |
|
|
//| * rep.m_rmserror - root-mean-square error at nodes |
|
|
//| * rep.m_maxerror - maximum error at nodes |
|
|
//| Fields are used for debugging purposes: |
|
|
//| * Rep.IterationsCount - iterations count of the LSQR solver |
|
|
//| * Rep.NMV - number of matrix - vector products |
|
|
//| * Rep.ARows - rows count for the system matrix |
|
|
//| * Rep.ACols - columns count for the system matrix |
|
|
//| * Rep.ANNZ - number of significantly non - zero elements |
|
|
//| (elements above some algorithm - determined |
|
|
//| threshold) |
|
|
//| NOTE: failure to build model will leave current State of the |
|
|
//| structure unchanged. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFBuildModel(CRBFModel &s,CRBFReport &rep)
|
|
{
|
|
//--- create variables
|
|
CRBFV1Report rep1;
|
|
CRBFV2Report rep2;
|
|
CRBFV3Report rep3;
|
|
CMatrixDouble x3;
|
|
CRowDouble scalevec;
|
|
int i=0;
|
|
int v3bftype=0;
|
|
double v3bfparam=0;
|
|
int curalgorithmtype=0;
|
|
//--- Clean fields prior to processing
|
|
ClearReportFields(rep);
|
|
s.m_progress10000=0;
|
|
s.m_terminationrequest=false;
|
|
//--- Autoselect algorithm
|
|
v3bftype=-999;
|
|
v3bfparam=0.0;
|
|
if(s.m_algorithmtype==0)
|
|
{
|
|
curalgorithmtype=4;
|
|
v3bftype=2;
|
|
v3bfparam=0.0;
|
|
}
|
|
else
|
|
{
|
|
curalgorithmtype=s.m_algorithmtype;
|
|
if(s.m_algorithmtype==4)
|
|
{
|
|
v3bftype=s.m_bftype;
|
|
v3bfparam=s.m_bfparam;
|
|
}
|
|
}
|
|
//--- Algorithms which generate V1 models
|
|
if(curalgorithmtype==1 || curalgorithmtype==2)
|
|
{
|
|
//--- Perform compatibility checks
|
|
if(s.m_nx<2 || s.m_nx>3 || s.m_hasscale)
|
|
{
|
|
rep.m_terminationtype=-3;
|
|
return;
|
|
}
|
|
//--- Try to build model.
|
|
//--- NOTE: due to historical reasons RBFV1BuildModel() accepts points
|
|
//--- cast to 3-dimensional space, even if they are really 2-dimensional.
|
|
//--- So, for 2D data we have to explicitly convert them to 3D.
|
|
if(s.m_nx==2)
|
|
{
|
|
//--- Convert data to 3D
|
|
CApServ::RMatrixSetLengthAtLeast(x3,s.m_n,3);
|
|
for(i=0; i<s.m_n; i++)
|
|
{
|
|
x3.Set(i,0,s.m_x.Get(i,0));
|
|
x3.Set(i,1,s.m_x.Get(i,1));
|
|
x3.Set(i,2,0);
|
|
}
|
|
CRBFV1::RBFV1BuildModel(x3,s.m_y,s.m_n,s.m_aterm,curalgorithmtype,s.m_nlayers,s.m_radvalue,s.m_radzvalue,s.m_lambdav,s.m_epsort,s.m_epserr,s.m_maxits,s.m_model1,rep1);
|
|
}
|
|
else
|
|
{
|
|
//--- Work with raw data
|
|
CRBFV1::RBFV1BuildModel(s.m_x,s.m_y,s.m_n,s.m_aterm,curalgorithmtype,s.m_nlayers,s.m_radvalue,s.m_radzvalue,s.m_lambdav,s.m_epsort,s.m_epserr,s.m_maxits,s.m_model1,rep1);
|
|
}
|
|
s.m_modelversion=1;
|
|
RBFCreateCalcBuffer(s,s.m_calcbuf);
|
|
//--- Convert report fields
|
|
rep.m_arows=rep1.m_arows;
|
|
rep.m_acols=rep1.m_acols;
|
|
rep.m_annz=rep1.m_annz;
|
|
rep.m_iterationscount=rep1.m_iterationscount;
|
|
rep.m_nmv=rep1.m_nmv;
|
|
rep.m_terminationtype=rep1.m_terminationtype;
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Algorithms which generate V2 models
|
|
if(curalgorithmtype==3)
|
|
{
|
|
//--- Prepare scale vector - use unit values or user supplied ones
|
|
scalevec.Resize(s.m_nx);
|
|
for(i=0; i<s.m_nx; i++)
|
|
{
|
|
if(s.m_hasscale)
|
|
scalevec.Set(i,s.m_s[i]);
|
|
else
|
|
scalevec.Set(i,1);
|
|
}
|
|
//--- Build model
|
|
CRBFV2::RBFV2BuildHierarchical(s.m_x,s.m_y,s.m_n,scalevec,s.m_aterm,s.m_nlayers,s.m_radvalue,s.m_lambdav,s.m_model2,s.m_progress10000,s.m_terminationrequest,rep2);
|
|
s.m_modelversion=2;
|
|
RBFCreateCalcBuffer(s,s.m_calcbuf);
|
|
//--- Convert report fields
|
|
rep.m_terminationtype=rep2.m_terminationtype;
|
|
rep.m_rmserror=rep2.m_rmserror;
|
|
rep.m_maxerror=rep2.m_maxerror;
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Algorithms which generate DDM-RBF models
|
|
if(curalgorithmtype==4)
|
|
{
|
|
//--- Prepare scale vector - use unit values or user supplied ones
|
|
scalevec.Resize(s.m_nx);
|
|
for(i=0; i<s.m_nx; i++)
|
|
{
|
|
if(s.m_hasscale)
|
|
scalevec.Set(i,s.m_s[i]);
|
|
else
|
|
scalevec.Set(i,1);
|
|
}
|
|
//--- Build model
|
|
CRBFV3::RBFV3Build(s.m_x,s.m_y,s.m_n,scalevec,v3bftype,v3bfparam,s.m_lambdav,s.m_aterm,s.m_model3,s.m_progress10000,s.m_terminationrequest,rep3);
|
|
s.m_modelversion=3;
|
|
RBFCreateCalcBuffer(s,s.m_calcbuf);
|
|
//--- Convert report fields
|
|
rep.m_iterationscount=rep3.m_iterationscount;
|
|
rep.m_terminationtype=rep3.m_terminationtype;
|
|
rep.m_rmserror=rep3.m_rmserror;
|
|
rep.m_maxerror=rep3.m_maxerror;
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Critical error
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failure");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the 1-dimensional RBF model |
|
|
//| with scalar output (NY = 1) at the given point. |
|
|
//| IMPORTANT: this function works only with modern (hierarchical) |
|
|
//| RBFs. It can not be used with legacy (version 1) RBFs |
|
|
//| because older RBF code does not support 1-dimensional |
|
|
//| models. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 when: |
|
|
//| * the model is not initialized |
|
|
//| * NX<>1 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - X - coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBF::RBFCalc1(CRBFModel &s,double x0)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=1)
|
|
return(0);
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
result=0;
|
|
break;
|
|
case 2:
|
|
result=CRBFV2::RBFV2Calc1(s.m_model2,x0);
|
|
break;
|
|
case 3:
|
|
result=CRBFV3::RBFV3Calc1(s.m_model3,x0);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the 2-dimensional RBF model |
|
|
//| with scalar output (NY = 1) at the given point. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBF::RBFCalc2(CRBFModel &s,double x0,double x1)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return(0);
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
result=CRBFV1::RBFV1Calc2(s.m_model1,x0,x1);
|
|
break;
|
|
case 2:
|
|
result=CRBFV2::RBFV2Calc2(s.m_model2,x0,x1);
|
|
break;
|
|
case 3:
|
|
result=CRBFV3::RBFV3Calc2(s.m_model3,x0,x1);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the 3-dimensional RBF model |
|
|
//| with scalar output (NY = 1) at the given point. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| * NY<>1 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| X2 - third coordinate, finite number |
|
|
//| RESULT: |
|
|
//| value of the model or 0.0 (as defined above) |
|
|
//+------------------------------------------------------------------+
|
|
double CRBF::RBFCalc3(CRBFModel &s,double x0,double x1,double x2)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
|
|
return(0);
|
|
if(s.m_ny!=1 || s.m_nx!=3)
|
|
return(0);
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
result=CRBFV1::RBFV1Calc3(s.m_model1,x0,x1,x2);
|
|
break;
|
|
case 2:
|
|
result=CRBFV2::RBFV2Calc3(s.m_model2,x0,x1,x2);
|
|
break;
|
|
case 3:
|
|
result=CRBFV3::RBFV3Calc3(s.m_model3,x0,x1,x2);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates value and derivatives of the |
|
|
//| 1-dimensional RBF model with scalar output (NY = 1) at the given |
|
|
//| point. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 in Y and/or DY in the following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0) |
|
|
//| * NX<>1 or NY<>1 (Y = 0, DY = 0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only DY is set to zero (Y is |
|
|
//| still returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - value of the model or 0.0 (as defined above) |
|
|
//| DY0 - derivative with respect to X0 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFDiff1(CRBFModel &s,double x0,double &y,double &dy0)
|
|
{
|
|
//--- init variables
|
|
y=0;
|
|
dy0=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return;
|
|
y=0;
|
|
dy0=0;
|
|
if(s.m_ny!=1 || s.m_nx!=1)
|
|
return;
|
|
|
|
CAblasF::RAllocV(1,s.m_calcbuf.m_x);
|
|
s.m_calcbuf.m_x.Set(0,x0);
|
|
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
|
|
y=s.m_calcbuf.m_y[0];
|
|
dy0=s.m_calcbuf.m_dy[0];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates value and derivatives of the |
|
|
//| 2-dimensional RBF model with scalar output (NY = 1) at the given |
|
|
//| point. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 in Y and/or DY in the following cases: |
|
|
//| * the model is not initialized(Y = 0, DY = 0) |
|
|
//| * NX<>2 or NY<>1 (Y=0, DY=0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient at |
|
|
//| the nodes In these cases only DY is set to zero (Y is still |
|
|
//| returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - value of the model or 0.0 (as defined above) |
|
|
//| DY0 - derivative with respect to X0 |
|
|
//| DY1 - derivative with respect to X1 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFDiff2(CRBFModel &s,double x0,double x1,double &y,
|
|
double &dy0,double &dy1)
|
|
{
|
|
y=0;
|
|
dy0=0;
|
|
dy1=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return;
|
|
if(s.m_ny!=1 || s.m_nx!=2)
|
|
return;
|
|
|
|
CAblasF::RAllocV(2,s.m_calcbuf.m_x);
|
|
s.m_calcbuf.m_x.Set(0,x0);
|
|
s.m_calcbuf.m_x.Set(1,x1);
|
|
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
|
|
y=s.m_calcbuf.m_y[0];
|
|
dy0=s.m_calcbuf.m_dy[0];
|
|
dy1=s.m_calcbuf.m_dy[1];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates value and derivatives of the |
|
|
//| 3-dimensional RBF model with scalar output (NY = 1) at the given |
|
|
//| point. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 in Y and/or DY in the following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0) |
|
|
//| * NX<>3 or NY<>1 (Y = 0, DY = 0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only DY is set to zero (Y is |
|
|
//| still returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X0 - first coordinate, finite number |
|
|
//| X1 - second coordinate, finite number |
|
|
//| X2 - third coordinate, finite number |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - value of the model or 0.0 (as defined above) |
|
|
//| DY0 - derivative with respect to X0 |
|
|
//| DY1 - derivative with respect to X1 |
|
|
//| DY2 - derivative with respect to X2 |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFDiff3(CRBFModel &s,double x0,double x1,double x2,
|
|
double &y,double &dy0,double &dy1,double &dy2)
|
|
{
|
|
y=0;
|
|
dy0=0;
|
|
dy1=0;
|
|
dy2=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x0),__FUNCTION__+": invalid value for X0 (X0 is Inf or NaN)!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x1),__FUNCTION__+": invalid value for X1 (X1 is Inf or NaN)!"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x2),__FUNCTION__+": invalid value for X2 (X2 is Inf or NaN)!"))
|
|
return;
|
|
if(s.m_ny!=1 || s.m_nx!=3)
|
|
return;
|
|
|
|
CAblasF::RAllocV(3,s.m_calcbuf.m_x);
|
|
s.m_calcbuf.m_x.Set(0,x0);
|
|
s.m_calcbuf.m_x.Set(1,x1);
|
|
s.m_calcbuf.m_x.Set(2,x2);
|
|
RBFTSDiffBuf(s,s.m_calcbuf,s.m_calcbuf.m_x,s.m_calcbuf.m_y,s.m_calcbuf.m_dy);
|
|
y=s.m_calcbuf.m_y[0];
|
|
dy0=s.m_calcbuf.m_dy[0];
|
|
dy1=s.m_calcbuf.m_dy[1];
|
|
dy2=s.m_calcbuf.m_dy[2];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point. |
|
|
//| This is general function which can be used for arbitrary NX |
|
|
//| (dimension of the space of arguments) and NY (dimension of the |
|
|
//| function itself). However when you have NY = 1 you may find more |
|
|
//| convenient to use RBFCalc2() or RBFCalc3(). |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| This function returns 0.0 when model is not initialized. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is out - parameter and|
|
|
//| reallocated after call to this function. In case |
|
|
//| you want to reuse previously allocated Y, you may |
|
|
//| use RBFCalcBuf(), which reallocates Y only when it |
|
|
//| is too small. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFCalc(CRBFModel &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
RBFCalcBuf(s,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its |
|
|
//| derivatives at the given point. |
|
|
//| This is general function which can be used for arbitrary NX |
|
|
//| (dimension of the space of arguments) and NY(dimension of the |
|
|
//| function itself). However if you have NX = 3 and NY = 1, you may |
|
|
//| find more convenient to use RBFDiff3(). |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). |
|
|
//| If you want to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSDiffBuf() with per-thread buffer object. |
|
|
//| This function returns 0.0 in Y and/or DY in the following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only DY is set to zero (Y is |
|
|
//| still returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is out-parameter and |
|
|
//| reallocated after call to this function. In case |
|
|
//| you want to reuse previously allocated Y, you may |
|
|
//| use RBFDiffBuf(), which reallocates Y only when it |
|
|
//| is too small. |
|
|
//| DY - derivatives, array[NX * NY]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX-dimensional gradient |
|
|
//| of the scalar NX-dimensional function DY is |
|
|
//| out-parameter and reallocated after call to this |
|
|
//| function. In case you want to reuse previously |
|
|
//| allocated DY, you may use RBFDiffBuf(), which |
|
|
//| reallocates DY only when it is too small to store|
|
|
//| the result. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFDiff(CRBFModel &s,CRowDouble &x,CRowDouble &y,CRowDouble &dy)
|
|
{
|
|
y.Resize(0);
|
|
dy.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
RBFDiffBuf(s,x,y,dy);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its first |
|
|
//| and second derivatives (Hessian matrix) at the given point. |
|
|
//| This function supports both scalar (NY = 1) and vector - valued |
|
|
//| (NY > 1) RBFs. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). |
|
|
//| If you want to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTsHessBuf() with per - thread buffer object. |
|
|
//| This function returns 0 in Y and/or DY and/or D2Y in the |
|
|
//| following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
|
|
//| * the gradient and/or Hessian is undefined at the trial point. |
|
|
//| Some basis functions have discontinuous derivatives at the |
|
|
//| interpolation nodes: |
|
|
//| * thin plate splines have no Hessian at the nodes |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only corresponding derivative |
|
|
//| is set to zero, and the rest of the derivatives is still |
|
|
//| returned. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is out-parameter and |
|
|
//| reallocated after call to this function. In case |
|
|
//| you want to reuse previously allocated Y, you may |
|
|
//| use RBFHessBuf(), which reallocates Y only when |
|
|
//| it is too small. |
|
|
//| DY - first derivatives, array[NY * NX]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX - dimensional gradient|
|
|
//| of the scalar NX-dimensional function DY is |
|
|
//| out-parameter and reallocated after call to this |
|
|
//| function. In case you want to reuse previously |
|
|
//| allocated DY, you may use RBFHessBuf(), which |
|
|
//| reallocates DY only when it is too small to store|
|
|
//| the result. |
|
|
//| D2Y - second derivatives, array[NY * NX * NX]: |
|
|
//| * for NY = 1 it is NX*NX array that stores Hessian |
|
|
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
|
|
//| * for a vector - valued RBF with NY > 1 it contains|
|
|
//| NY subsequently stored Hessians: an element |
|
|
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
|
|
//| 0 <= I < NX and 0 <= J < NX stores second |
|
|
//| derivative of the function #K with respect to |
|
|
//| inputs #I and #J. |
|
|
//| D2Y is out-parameter and reallocated after call to |
|
|
//| this function. In case you want to reuse previously|
|
|
//| allocated D2Y, you may use RBFHessBuf(), which |
|
|
//| reallocates D2Y only when it is too small to store |
|
|
//| the result. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFHess(CRBFModel &s,CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy,CRowDouble &d2y)
|
|
{
|
|
y.Resize(0);
|
|
dy.Resize(0);
|
|
d2y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
RBFTSHessBuf(s,s.m_calcbuf,x,y,dy,d2y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point. |
|
|
//| Same as RBFCalc(), but does not reallocate Y when in is large |
|
|
//| enough to store function values. |
|
|
//| IMPORTANT: THIS FUNCTION IS THREAD - UNSAFE. It uses fields of |
|
|
//| CRBFModel as temporary arrays, i.e. it is impossible |
|
|
//| to perform parallel evaluation on the same CRBFModel |
|
|
//| object (parallel calls of this function for |
|
|
//| independent CRBFModel objects are safe). If you want |
|
|
//| to perform parallel model evaluation from multiple |
|
|
//| threads, use RBFTSCalcBuf() with per-thread buffer |
|
|
//| object. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFCalcBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
for(i=0; i<s.m_ny; i++)
|
|
y.Set(i,0);
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1CalcBuf(s.m_model1,x,y);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2CalcBuf(s.m_model2,x,y);
|
|
break;
|
|
case 3:
|
|
CRBFV3::RBFV3CalcBuf(s.m_model3,x,y);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its |
|
|
//| derivatives at the given point. It is a buffered version of the |
|
|
//| RBFDiff() which tries to reuse possibly preallocated output |
|
|
//| arrays Y / DY as much as possible. |
|
|
//| This is general function which can be used for arbitrary NX |
|
|
//| (dimension of the space of arguments) and NY (dimension of the |
|
|
//| function itself). However if you have NX = 1, 2 or 3 and NY = 1, |
|
|
//| you may find more convenient to use RBFDiff1(), RBFDiff2() or |
|
|
//| RBFDiff3(). |
|
|
//| This function returns 0.0 in Y and/or DY in the following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only DY is set to zero (Y is |
|
|
//| still returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y, DY - possibly preallocated arrays; if array size is |
|
|
//| large enough to store results, this function does |
|
|
//| not reallocate array to fit output size exactly. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. |
|
|
//| DY - derivatives, array[NX * NY]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX - dimensional gradient|
|
|
//| of the scalar NX - dimensional function |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFDiffBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(s.m_modelversion==s.m_calcbuf.m_modelversion,__FUNCTION__+": integrity check 3945 failed"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
y.Set(i,0);
|
|
for(int i=0; i<s.m_ny*s.m_nx; i++)
|
|
dy.Set(i,0);
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1TSDiffBuf(s.m_model1,s.m_calcbuf.m_bufv1,x,y,dy);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2TsDiffBuf(s.m_model2,s.m_calcbuf.m_bufv2,x,y,dy);
|
|
break;
|
|
case 3:
|
|
CRBFV3::RBFV3TsDiffBuf(s.m_model3,s.m_calcbuf.m_bufv3,x,y,dy);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its first |
|
|
//| and second derivatives (Hessian matrix) at the given point. It is|
|
|
//| a buffered version that reuses memory allocated in output buffers|
|
|
//| Y/DY/D2Y as much as possible. |
|
|
//| This function supports both scalar(NY = 1) and vector - valued |
|
|
//| (NY > 1) RBFs. |
|
|
//| This function returns 0 in Y and/or DY and/or D2Y in the |
|
|
//| following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
|
|
//| * the gradient and/or Hessian is undefined at the trial point |
|
|
//| Some basis functions have discontinuous derivatives at the |
|
|
//| interpolation nodes: |
|
|
//| * thin plate splines have no Hessian at the nodes |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only corresponding derivative |
|
|
//| is set to zero, and the rest of the derivatives is still |
|
|
//| returned. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y, DY, D2Y - possible preallocated output arrays. If these |
|
|
//| arrays are smaller than required to store the |
|
|
//| result, they are automatically reallocated. If |
|
|
//| array is large enough, it is not resized. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. |
|
|
//| DY - first derivatives, array[NY * NX]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX - dimensional gradient|
|
|
//| of the scalar NX - dimensional function |
|
|
//| D2Y - second derivatives, array[NY * NX * NX]: |
|
|
//| * for NY = 1 it is NX*NX array that stores Hessian |
|
|
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
|
|
//| * for a vector - valued RBF with NY > 1 it contains|
|
|
//| NY subsequently stored Hessians: an element |
|
|
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
|
|
//| 0 <= I < NX and 0 <= J < NX stores second |
|
|
//| derivative of the function #K with respect to |
|
|
//| inputs #I and #J. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFHessBuf(CRBFModel &s,CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy,CRowDouble &d2y)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
|
|
RBFTSHessBuf(s,s.m_calcbuf,x,y,dy,d2y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the given |
|
|
//| point, using external buffer object (internal temporaries of RBF |
|
|
//| model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of buffer structure. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y - possibly preallocated array |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. Y is not reallocated |
|
|
//| when it is larger than NY. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFTSCalcBuf(CRBFModel &s,CRBFCalcBuffer &buf,CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,__FUNCTION__+": buffer object is not compatible with RBF model"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
y.Set(i,0);
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1TSCalcBuf(s.m_model1,buf.m_bufv1,x,y);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2TsCalcBuf(s.m_model2,buf.m_bufv2,x,y);
|
|
break;
|
|
case 3:
|
|
CRBFV3::RBFV3TsCalcBuf(s.m_model3,buf.m_bufv3,x,y);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its |
|
|
//| derivatives at the given point, using external buffer object |
|
|
//| (internal temporaries of the RBF model are not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances |
|
|
//| of the buffer structure. |
|
|
//| This function returns 0.0 in Y and/or DY in the following |
|
|
//| cases: |
|
|
//| * the model is not initialized(Y = 0, DY = 0) |
|
|
//| * the gradient is undefined at the trial point. Some basis |
|
|
//| functions have discontinuous derivatives at the interpolation|
|
|
//| nodes: |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient |
|
|
//| at the nodes In these cases only DY is set to zero (Y is |
|
|
//| still returned) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y, DY - possibly preallocated arrays; if array size is |
|
|
//| large enough to store results, this function does |
|
|
//| not reallocate array to fit output size exactly. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. |
|
|
//| DY - derivatives, array[NX * NY]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX-dimensional gradient |
|
|
//| of the scalar NX-dimensional function |
|
|
//| Zero is returned when the first derivative is |
|
|
//| undefined. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFTSDiffBuf(CRBFModel &s,CRBFCalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,"RBFTsDiffBuf: Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),"RBFTsDiffBuf: X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,"RBFTsDiffBuf: integrity check 3985 failed"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
for(int i=0; i<=s.m_ny-1; i++)
|
|
y.Set(i,0);
|
|
for(int i=0; i<=s.m_ny*s.m_nx-1; i++)
|
|
dy.Set(i,0);
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1TSDiffBuf(s.m_model1,buf.m_bufv1,x,y,dy);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2TsDiffBuf(s.m_model2,buf.m_bufv2,x,y,dy);
|
|
break;
|
|
case 3:
|
|
CRBFV3::RBFV3TsDiffBuf(s.m_model3,buf.m_bufv3,x,y,dy);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model and its first |
|
|
//| and second derivatives (Hessian matrix) at the given point, using|
|
|
//| external buffer object (internal temporaries of the RBF model are|
|
|
//| not modified). |
|
|
//| This function allows to use same RBF model object in different |
|
|
//| threads, assuming that different threads use different instances|
|
|
//| of the buffer structure. |
|
|
//| This function returns 0 in Y and/or DY and/or D2Y in the |
|
|
//| following cases: |
|
|
//| * the model is not initialized (Y = 0, DY = 0, D2Y = 0) |
|
|
//| * the gradient and/or Hessian is undefined at the trial point. |
|
|
//| Some basis functions have discontinuous derivatives at the |
|
|
//| interpolation nodes: |
|
|
//| * thin plate splines have no Hessian at the nodes |
|
|
//| * biharmonic splines f = r have no Hessian and no gradient at |
|
|
//| the nodes In these cases only corresponding derivative is set|
|
|
//| to zero, and the rest of the derivatives is still returned. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, may be shared between different threads |
|
|
//| Buf - buffer object created for this particular instance |
|
|
//| of RBF model with RBFCreateCalcBuffer(). |
|
|
//| X - coordinates, array[NX]. X may have more than NX |
|
|
//| elements, in this case only leading NX will be used|
|
|
//| Y, DY, D2Y - possible preallocated output arrays. If these |
|
|
//| arrays are smaller than required to store the |
|
|
//| result, they are automatically reallocated. If |
|
|
//| array is large enough, it is not resized. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function value, array[NY]. |
|
|
//| DY - first derivatives, array[NY * NX]: |
|
|
//| * Y[I * NX + J] with 0 <= I < NY and 0 <= J < NX |
|
|
//| stores derivative of function component I with |
|
|
//| respect to input J. |
|
|
//| * for NY = 1 it is simply NX - dimensional gradient|
|
|
//| of the scalar NX - dimensional function Zero is |
|
|
//| returned when the first derivative is undefined. |
|
|
//| D2Y - second derivatives, array[NY * NX * NX]: |
|
|
//| * for NY = 1 it is NX*NX array that stores Hessian |
|
|
//| matrix, with Y[I * NX + J] = Y[J * NX + I]. |
|
|
//| * for a vector - valued RBF with NY > 1 it contains|
|
|
//| NY subsequently stored Hessians: an element |
|
|
//| Y[K * NX * NX + I * NX + J] with 0 <= K < NY, |
|
|
//| 0 <= I < NX and 0 <= J < NX stores second |
|
|
//| derivative of the function #K with respect to |
|
|
//| inputs and #J. |
|
|
//| Zero is returned when the second derivative is |
|
|
//| undefined. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFTSHessBuf(CRBFModel &s,CRBFCalcBuffer &buf,
|
|
CRowDouble &x,CRowDouble &y,
|
|
CRowDouble &dy,CRowDouble &d2y)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=s.m_nx,__FUNCTION__+": Length(X)<NX"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,s.m_nx),__FUNCTION__+": X contains infinite or NaN values"))
|
|
return;
|
|
if(!CAp::Assert(s.m_modelversion==buf.m_modelversion,__FUNCTION__+": integrity check 3953 failed"))
|
|
return;
|
|
|
|
if(CAp::Len(y)<s.m_ny)
|
|
y.Resize(s.m_ny);
|
|
if(CAp::Len(dy)<s.m_ny*s.m_nx)
|
|
dy.Resize(s.m_ny*s.m_nx);
|
|
if(CAp::Len(d2y)<s.m_ny*s.m_nx*s.m_nx)
|
|
d2y.Resize(s.m_ny*s.m_nx*s.m_nx);
|
|
for(int i=0; i<s.m_ny; i++)
|
|
y.Set(i,0);
|
|
for(int i=0; i<s.m_ny*s.m_nx; i++)
|
|
dy.Set(i,0);
|
|
for(int i=0; i<s.m_ny*s.m_nx*s.m_nx; i++)
|
|
d2y.Set(i,0);
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1TSHessBuf(s.m_model1,buf.m_bufv1,x,y,dy,d2y);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2TSHessBuf(s.m_model2,buf.m_bufv2,x,y,dy,d2y);
|
|
break;
|
|
case 3:
|
|
CRBFV3::RBFV3TSHessBuf(s.m_model3,buf.m_bufv3,x,y,dy,d2y);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is legacy function for gridded calculation of RBF model. |
|
|
//| It is superseded by RBFGridCalc2V() and RBFGridCalc2VSubset() |
|
|
//| functions. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc2(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CMatrixDouble &y)
|
|
{
|
|
//--- create variables
|
|
CRowDouble yy;
|
|
y.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
|
|
switch(s.m_modelversion)
|
|
{
|
|
case 1:
|
|
CRBFV1::RBFV1GridCalc2(s.m_model1,x0,n0,x1,n1,y);
|
|
break;
|
|
case 2:
|
|
CRBFV2::RBFV2GridCalc2(s.m_model2,x0,n0,x1,n1,y);
|
|
break;
|
|
case 3:
|
|
CAblasF::RAllocM(n0,n1,y);
|
|
if(s.m_nx!=2 || s.m_ny!=1)
|
|
{
|
|
CAblasF::RSetM(n0,n1,0.0,y);
|
|
break;
|
|
}
|
|
RBFGridCalc2V(s,x0,n0,x1,n1,yy);
|
|
for(int i=0; i<n0; i++)
|
|
{
|
|
for(int j=0; j<n1; j++)
|
|
y.Set(i,j,yy[i+j*n0]);
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the regular |
|
|
//| grid, which has N0*N1 points, with Point[I, J] = (X0[I], X1[J]). |
|
|
//| Vector - valued RBF models are supported. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, used in read - only mode, can be shared |
|
|
//| between multiple invocations of this function from|
|
|
//| multiple threads. |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0]. |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N0 - grid size(number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N1 - grid size(number of nodes) in the second dimension |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[NY * N0 * N1], where NY is a|
|
|
//| number of "output" vector values(this function |
|
|
//| supports vector - valued RBF models). Y is |
|
|
//| out-variable and is reallocated by this function. |
|
|
//| Y[K + NY * (I0 + I1 * N0)] = F_k(X0[I0], X1[I1]), |
|
|
//| for: |
|
|
//| * K = 0...NY - 1 |
|
|
//| * I0 = 0...N0 - 1 |
|
|
//| * I1 = 0...N1 - 1 |
|
|
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
|
|
//| may have X[i] = X[i + 1] for some i. It does not provide |
|
|
//| you any performance benefits due to duplication of points,|
|
|
//| just convenience and flexibility. |
|
|
//| NOTE: this function is re-entrant, i.e. you may use same |
|
|
//| CRBFModel structure in multiple threads calling this |
|
|
//| function for different grids. |
|
|
//| NOTE: if you need function values on some subset of regular grid,|
|
|
//| which may be described as "several compact and dense |
|
|
//| islands", you may use RBFGridCalc2VSubset(). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc2V(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CRowDouble &y)
|
|
{
|
|
bool dummy[];
|
|
y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
//--- function call
|
|
RBFGridCalc2VX(s,x0,n0,x1,n1,dummy,false,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at some subset |
|
|
//| of regular grid: |
|
|
//| * grid has N0*N1 points, with Point[I, J] = (X0[I], X1[J]) |
|
|
//| * only values at some subset of this grid are required |
|
|
//| Vector - valued RBF models are supported. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>2 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, used in read - only mode, can be shared |
|
|
//| between multiple invocations of this function from |
|
|
//| multiple threads. |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0]. |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N0 - grid size (number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N1 - grid size(number of nodes) in the second dimension |
|
|
//| FlagY - array[N0 * N1]: |
|
|
//| * Y[I0 + I1 * N0] corresponds to node (X0[I0], |
|
|
//| X1[I1]) |
|
|
//| *it is a "bitmap" array which contains False for |
|
|
//| nodes which are NOT calculated, and True for |
|
|
//| nodes which are required. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
|
|
//| is a number of "output" vector values (this |
|
|
//| function supports vector - valued RBF models): |
|
|
//| * Y[K + NY * (I0 + I1 * N0)] = F_k(X0[I0], X1[I1]),|
|
|
//| for K = 0...NY-1, I0 = 0...N0-1, I1 = 0...N1-1. |
|
|
//| * elements of Y[] which correspond to FlagY[]=True |
|
|
//| are loaded by model values(which may be exactly |
|
|
//| zero for some nodes). |
|
|
//| * elements of Y[] which correspond to FlagY[]=False|
|
|
//| MAY be initialized by zeros OR may be calculated|
|
|
//| This function processes grid as a hierarchy of |
|
|
//| nested blocks and micro-rows. If just one |
|
|
//| element of micro-row is required, entire micro- |
|
|
//| row (up to 8 nodes in the current version, but |
|
|
//| no promises) is calculated. |
|
|
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
|
|
//| may have X[i] = X[i + 1] for some i. It does not provide |
|
|
//| you any performance benefits due to duplication of points, |
|
|
//| just convenience and flexibility. |
|
|
//| NOTE: this function is re - entrant, i.e. you may use same |
|
|
//| CRBFModel structure in multiple threads calling this |
|
|
//| function for different grids. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc2VSubset(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,bool &flagy[],
|
|
CRowDouble &y)
|
|
{
|
|
y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(flagy)>=n0*n1,__FUNCTION__+": Length(FlagY)<N0*N1*N2"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
//--- function call
|
|
RBFGridCalc2VX(s,x0,n0,x1,n1,flagy,true,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at the regular |
|
|
//| grid, which has N0*N1*N2 points, with Point[I, J, K] = (X0[I], |
|
|
//| X1[J], X2[K]). Vector - valued RBF models are supported. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, used in read-only mode, can be shared |
|
|
//| between multiple invocations of this function from|
|
|
//| multiple threads. |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0]. |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N0 - grid size(number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N1 - grid size(number of nodes) in the second dimension |
|
|
//| X2 - array of grid nodes, third coordinates, array[N2] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N2 - grid size(number of nodes) in the third dimension |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
|
|
//| is a number of "output" vector values (this |
|
|
//| function supports vector-valued RBF models). Y is |
|
|
//| out-variable and is reallocated by this function. |
|
|
//| Y[K+NY*(I0+I1*N0+I2*N0*N1)] = F_k(X0[I0],X1[I1],X2[I2]),|
|
|
//| for: |
|
|
//| * K = 0...NY - 1 |
|
|
//| * I0 = 0...N0 - 1 |
|
|
//| * I1 = 0...N1 - 1 |
|
|
//| * I2 = 0...N2 - 1 |
|
|
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
|
|
//| may have X[i] = X[i + 1] for some i. It does not provide |
|
|
//| you any performance benefits due to duplication of points,|
|
|
//| just convenience and flexibility. |
|
|
//| NOTE: this function is re-entrant, i.e. you may use same |
|
|
//| CRBFModel structure in multiple threads calling this |
|
|
//| function for different grids. |
|
|
//| NOTE: if you need function values on some subset of regular grid,|
|
|
//| which may be described as "several compact and dense |
|
|
//| islands", you may use RBFGridCalc3VSubset(). |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc3V(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CRowDouble &x2,int n2,
|
|
CRowDouble &y)
|
|
{
|
|
bool dummy[];
|
|
y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n2-1; i++)
|
|
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
|
|
return;
|
|
//--- function call
|
|
RBFGridCalc3VX(s,x0,n0,x1,n1,x2,n2,dummy,false,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function calculates values of the RBF model at some subset |
|
|
//| of regular grid: |
|
|
//| * grid has N0*N1*N2 points, with Point[I, J, K] = (X0[I], |
|
|
//| X1[J], X2[K]) |
|
|
//| * only values at some subset of this grid are required |
|
|
//| Vector - valued RBF models are supported. |
|
|
//| This function returns 0.0 when: |
|
|
//| * model is not initialized |
|
|
//| * NX<>3 |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model, used in read - only mode, can be shared |
|
|
//| between multiple invocations of this function from|
|
|
//| multiple threads. |
|
|
//| X0 - array of grid nodes, first coordinates, array[N0]. |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N0 - grid size(number of nodes) in the first dimension |
|
|
//| X1 - array of grid nodes, second coordinates, array[N1] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N1 - grid size(number of nodes) in the second dimension |
|
|
//| X2 - array of grid nodes, third coordinates, array[N2] |
|
|
//| Must be ordered by ascending. Exception is |
|
|
//| generated if the array is not correctly ordered. |
|
|
//| N2 - grid size(number of nodes) in the third dimension |
|
|
//| FlagY - array[N0 * N1 * N2]: |
|
|
//| * Y[I0 + I1 * N0 + I2 * N0 * N1] corresponds to |
|
|
//| node (X0[I0], X1[I1], X2[I2]) |
|
|
//| *it is a "bitmap" array which contains False for |
|
|
//| nodes which are NOT calculated, and True for |
|
|
//| nodes which are required. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - function values, array[NY * N0 * N1 * N2], where NY|
|
|
//| is a number of "output" vector values(this function|
|
|
//| supports vector- valued RBF models): |
|
|
//| * Y[K+NY*(I0+I1*N0+I2*N0*N1)] = F_k(X0[I0],X1[I1],X2[I2]),|
|
|
//| for K = 0...NY-1, I0 = 0...N0-1, I1 = 0...N1-1, |
|
|
//| I2 = 0...N2-1. |
|
|
//| * elements of Y[] which correspond to FlagY[]=True |
|
|
//| are loaded by model values(which may be exactly |
|
|
//| zero for some nodes). |
|
|
//| * elements of Y[] which correspond to FlagY[]=False|
|
|
//| MAY be initialized by zeros OR may be calculated.|
|
|
//| This function processes grid as a hierarchy of |
|
|
//| nested blocks and micro-rows. If just one element|
|
|
//| of micro-row is required, entire micro-row (up to|
|
|
//| 8 nodes in the current version, but no promises) |
|
|
//| is calculated. |
|
|
//| NOTE: this function supports weakly ordered grid nodes, i.e. you |
|
|
//| may have X[i] = X[i + 1] for some i. It does not provide |
|
|
//| you any performance benefits due to duplication of points,|
|
|
//| just convenience and flexibility. |
|
|
//| NOTE: this function is re-entrant, i.e. you may use same |
|
|
//| CRBFModel structure in multiple threads calling this |
|
|
//| function for different grids. |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc3VSubset(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CRowDouble &x2,
|
|
int n2,bool &flagy[],CRowDouble &y)
|
|
{
|
|
y.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(flagy)>=n0*n1*n2,__FUNCTION__+": Length(FlagY)<N0*N1*N2"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n2-1; i++)
|
|
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
|
|
return;
|
|
//--- function call
|
|
RBFGridCalc3VX(s,x0,n0,x1,n1,x2,n2,flagy,true,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function, depending on SparseY, acts as RBFGridCalc2V |
|
|
//| (SparseY = False) or RBFGridCalc2VSubset (SparseY = True) |
|
|
//| function. See comments for these functions for more information |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc2VX(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,bool &flagy[],
|
|
bool sparsey,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nx=0;
|
|
int ny=0;
|
|
int ylen=0;
|
|
int k=0;
|
|
int dstoffs=0;
|
|
CHighQualityRandState rs;
|
|
CRowDouble dummyx2;
|
|
CRowDouble dummyx3;
|
|
CRowDouble tx;
|
|
CRowDouble ty;
|
|
CRBFCalcBuffer calcbuf;
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
for(int i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(int i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
//--- Prepare local variables
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
CHighQualityRand::HQRndSeed(325,46345,rs);
|
|
//--- Prepare output array
|
|
ylen=ny*n0*n1;
|
|
y=vector<double>::Zeros(ylen);
|
|
if(s.m_nx!=2)
|
|
return;
|
|
//--- Reference code for V3 models
|
|
if(s.m_modelversion==3)
|
|
{
|
|
dummyx2=vector<double>::Zeros(1);
|
|
dummyx3=vector<double>::Zeros(1);
|
|
CRBFV3::RBFV3GridCalcVX(s.m_model3,x0,n0,x1,n1,dummyx2,1,dummyx3,1,flagy,sparsey,y);
|
|
return;
|
|
}
|
|
//--- Process V2 model
|
|
if(s.m_modelversion==2)
|
|
{
|
|
dummyx2=vector<double>
|
|
::Zeros(1);
|
|
dummyx3=vector<double>::Zeros(1);
|
|
CRBFV2::RBFV2GridCalcVX(s.m_model2,x0,n0,x1,n1,dummyx2,1,dummyx3,1,flagy,sparsey,y);
|
|
return;
|
|
}
|
|
//--- Reference code for V1 models
|
|
if(s.m_modelversion==1)
|
|
{
|
|
tx.Resize(nx);
|
|
RBFCreateCalcBuffer(s,calcbuf);
|
|
for(int i=0; i<n0; i++)
|
|
{
|
|
for(int j=0; j<n1; j++)
|
|
{
|
|
k=i+j*n0;
|
|
dstoffs=ny*k;
|
|
if(sparsey && !flagy[k])
|
|
{
|
|
for(int l=0; l<ny; l++)
|
|
y.Set(l+dstoffs,0);
|
|
continue;
|
|
}
|
|
tx.Set(0,x0[i]);
|
|
tx.Set(1,x1[j]);
|
|
RBFTSCalcBuf(s,calcbuf,tx,ty);
|
|
for(int l=0; l<ny; l++)
|
|
y.Set(l+dstoffs,ty[l]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//--- Unknown model
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function, depending on SparseY, acts as RBFGridCalc3V |
|
|
//| (SparseY = False) or RBFGridCalc3VSubset (SparseY = True) |
|
|
//| function. See comments for these functions for more information |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFGridCalc3VX(CRBFModel &s,CRowDouble &x0,int n0,
|
|
CRowDouble &x1,int n1,CRowDouble &x2,
|
|
int n2,bool &flagy[],bool sparsey,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int ylen=0;
|
|
int nx=0;
|
|
int ny=0;
|
|
double rmax=0;
|
|
CRowInt blocks0;
|
|
CRowInt blocks1;
|
|
CRowInt blocks2;
|
|
int blockscnt0=0;
|
|
int blockscnt1=0;
|
|
int blockscnt2=0;
|
|
double blockwidth=0;
|
|
double searchradius=0;
|
|
double avgfuncpernode=0;
|
|
int ntrials=0;
|
|
int maxblocksize=0;
|
|
CGridCalc3v1Buf bufseedv1;
|
|
CGridCalc3v1Buf bufpool;
|
|
CHighQualityRandState rs;
|
|
CRowDouble dummyx3;
|
|
//--- check
|
|
if(!CAp::Assert(n0>0,__FUNCTION__+": invalid value for N0 (N0<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n1>0,__FUNCTION__+": invalid value for N1 (N1<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(n2>0,__FUNCTION__+": invalid value for N2 (N2<=0)!"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x0)>=n0,__FUNCTION__+": Length(X0)<N0"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x1)>=n1,__FUNCTION__+": Length(X1)<N1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x2)>=n2,__FUNCTION__+": Length(X2)<N2"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x0,n0),__FUNCTION__+": X0 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x1,n1),__FUNCTION__+": X1 contains infinite or NaN values!"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x2,n2),__FUNCTION__+": X2 contains infinite or NaN values!"))
|
|
return;
|
|
for(i=0; i<n0-1; i++)
|
|
if(!CAp::Assert(x0[i]<=x0[i+1],__FUNCTION__+": X0 is not ordered by ascending"))
|
|
return;
|
|
for(i=0; i<n1-1; i++)
|
|
if(!CAp::Assert(x1[i]<=x1[i+1],__FUNCTION__+": X1 is not ordered by ascending"))
|
|
return;
|
|
for(i=0; i<n2-1; i++)
|
|
if(!CAp::Assert(x2[i]<=x2[i+1],__FUNCTION__+": X2 is not ordered by ascending"))
|
|
return;
|
|
//--- Prepare local variables
|
|
nx=s.m_nx;
|
|
ny=s.m_ny;
|
|
CHighQualityRand::HQRndSeed(325,46345,rs);
|
|
//--- Prepare output array
|
|
ylen=ny*n0*n1*n2;
|
|
y=vector<double>::Zeros(ylen);
|
|
if(s.m_nx!=3)
|
|
return;
|
|
//--- Process V1 model
|
|
if(s.m_modelversion==1)
|
|
{
|
|
//--- Fast exit for models without centers
|
|
if(s.m_model1.m_nc==0)
|
|
return;
|
|
//--- Prepare seed, create shared pool of temporary buffers
|
|
bufseedv1.m_cx=vector<double>::Zeros(nx);
|
|
bufseedv1.m_tx=vector<double>::Zeros(nx);
|
|
bufseedv1.m_ty=vector<double>::Zeros(ny);
|
|
bufseedv1.m_expbuf0=vector<double>::Zeros(n0);
|
|
bufseedv1.m_expbuf1=vector<double>::Zeros(n1);
|
|
bufseedv1.m_expbuf2=vector<double>::Zeros(n2);
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(s.m_model1.m_tree,bufseedv1.m_requestbuf);
|
|
bufpool=bufseedv1;
|
|
//--- Analyze input grid:
|
|
//--- * analyze average number of basis functions per grid node
|
|
//--- * partition grid in into blocks
|
|
rmax=s.m_model1.m_rmax;
|
|
blockwidth=2*rmax;
|
|
maxblocksize=8;
|
|
searchradius=rmax*m_rbffarradius+0.5*MathSqrt(s.m_nx)*blockwidth;
|
|
ntrials=100;
|
|
avgfuncpernode=0.0;
|
|
for(i=0; i<=ntrials-1; i++)
|
|
{
|
|
bufseedv1.m_tx.Set(0,x0[CHighQualityRand::HQRndUniformI(rs,n0)]);
|
|
bufseedv1.m_tx.Set(1,x1[CHighQualityRand::HQRndUniformI(rs,n1)]);
|
|
bufseedv1.m_tx.Set(2,x2[CHighQualityRand::HQRndUniformI(rs,n2)]);
|
|
avgfuncpernode+=(double)CNearestNeighbor::KDTreeTsQueryRNN(s.m_model1.m_tree,bufseedv1.m_requestbuf,bufseedv1.m_tx,searchradius,true)/(double)ntrials;
|
|
}
|
|
blocks0.Resize(n0+1);
|
|
blockscnt0=0;
|
|
blocks0.Set(0,0);
|
|
for(i=1; i<n0; i++)
|
|
{
|
|
if((double)(x0[i]-x0[blocks0[blockscnt0]])>(double)(blockwidth) || i-blocks0[blockscnt0]>=maxblocksize)
|
|
{
|
|
blockscnt0++;
|
|
blocks0.Set(blockscnt0,i);
|
|
}
|
|
}
|
|
blockscnt0++;
|
|
blocks0.Set(blockscnt0,n0);
|
|
blocks1.Resize(n1+1);
|
|
blocks1.Fill(0);
|
|
blockscnt1=0;
|
|
for(i=1; i<n1; i++)
|
|
{
|
|
if((double)(x1[i]-x1[blocks1[blockscnt1]])>(double)(blockwidth) || i-blocks1[blockscnt1]>=maxblocksize)
|
|
{
|
|
blockscnt1++;
|
|
blocks1.Set(blockscnt1,i);
|
|
}
|
|
}
|
|
blockscnt1++;
|
|
blocks1.Set(blockscnt1,n1);
|
|
blocks2.Resize(n2+1);
|
|
blockscnt2=0;
|
|
blocks2.Set(0,0);
|
|
for(i=1; i<n2; i++)
|
|
{
|
|
if((double)(x2[i]-x2[blocks2[blockscnt2]])>(double)(blockwidth) || i-blocks2[blockscnt2]>=maxblocksize)
|
|
{
|
|
blockscnt2++;
|
|
blocks2.Set(blockscnt2,i);
|
|
}
|
|
}
|
|
blockscnt2++;
|
|
blocks2.Set(blockscnt2,n2);
|
|
//--- Perform calculation in multithreaded mode
|
|
CRBFV1::RBFV1GridCalc3VRec(s.m_model1,x0,n0,x1,n1,x2,n2,blocks0,0,blockscnt0,blocks1,0,blockscnt1,blocks2,0,blockscnt2,flagy,sparsey,searchradius,avgfuncpernode,bufpool,y);
|
|
//--- Done
|
|
return;
|
|
}
|
|
//--- Process V2 model
|
|
if(s.m_modelversion==2)
|
|
{
|
|
dummyx3=vector<double>::Zeros(1);
|
|
CRBFV2::RBFV2GridCalcVX(s.m_model2,x0,n0,x1,n1,x2,n2,dummyx3,1,flagy,sparsey,y);
|
|
return;
|
|
}
|
|
//--- Process V3 model
|
|
if(s.m_modelversion==3)
|
|
{
|
|
dummyx3=vector<double>::Zeros(1);
|
|
CRBFV3::RBFV3GridCalcVX(s.m_model3,x0,n0,x1,n1,x2,n2,dummyx3,1,flagy,sparsey,y);
|
|
return;
|
|
}
|
|
//--- Unknown model
|
|
CAp::Assert(false,"RBFGridCalc3VX: integrity check failed");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "unpacks" RBF model by extracting its coefficients.|
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| NX - dimensionality of argument |
|
|
//| NY - dimensionality of the target function |
|
|
//| XWR - model information, 2D array. One row of the array |
|
|
//| corresponds to one basis function. |
|
|
//| For ModelVersion = 1 we have NX + NY + 1 columns: |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of the function|
|
|
//| being modeled |
|
|
//| * last column - radius, same for all dimensions of the |
|
|
//| function being modeled |
|
|
//| For ModelVersion = 2 we have NX + NY + NX columns: |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of the function|
|
|
//| being modeled |
|
|
//| * last NX columns - radii, one per dimension |
|
|
//| For ModelVersion = 3 we have NX + NY + NX + 3 columns: |
|
|
//| * first NX columns - coordinates of the center |
|
|
//| * next NY columns - weights, one per dimension of the |
|
|
//| function being modeled |
|
|
//| * next NX columns - radii, one per dimension |
|
|
//| * next column - basis function type: |
|
|
//| * 1 for f = r |
|
|
//| * 2 for f = r ^ 2 * ln(r) |
|
|
//| * 10 for multiquadric f=sqrt(r^2+alpha^2) |
|
|
//| * next column - basis function parameter: |
|
|
//| * alpha, for basis function type 10 |
|
|
//| * ignored(zero) for other basis function |
|
|
//| types |
|
|
//| * next column - point index in the original dataset, or -1|
|
|
//| for an artificial node created by the |
|
|
//| solver. The algorithm may reorder the |
|
|
//| nodes, drop some nodes or add artificial |
|
|
//| nodes. Thus, one parsing this column |
|
|
//| should expect all these kinds of |
|
|
//| alterations in the dataset. |
|
|
//| NC - number of the centers |
|
|
//| V - polynomial term, array[NY, NX + 1]. One row per |
|
|
//| one dimension of the function being modelled. |
|
|
//| First NX elements are linear coefficients, V[NX]|
|
|
//| is equal to the constant part. |
|
|
//| ModelVersion - version of the RBF model: |
|
|
//| * 1 - for models created by QNN and RBF-ML |
|
|
//| algorithms, compatible with ALGLIB 3.10 or|
|
|
//| earlier. |
|
|
//| * 2 - for models created by HierarchicalRBF, |
|
|
//| requires ALGLIB 3.11 or later |
|
|
//| * 3 - for models created by DDM-RBF, requires |
|
|
//| ALGLIB 3.19 or later |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFUnpack(CRBFModel &s,int &nx,int &ny,CMatrixDouble &xwr,
|
|
int &nc,CMatrixDouble &v,int &modelversion)
|
|
{
|
|
nx=0;
|
|
ny=0;
|
|
xwr.Resize(0,0);
|
|
nc=0;
|
|
v.Resize(0,0);
|
|
modelversion=0;
|
|
|
|
if(s.m_modelversion==1)
|
|
{
|
|
modelversion=1;
|
|
CRBFV1::RBFV1Unpack(s.m_model1,nx,ny,xwr,nc,v);
|
|
return;
|
|
}
|
|
if(s.m_modelversion==2)
|
|
{
|
|
modelversion=2;
|
|
CRBFV2::RBFV2Unpack(s.m_model2,nx,ny,xwr,nc,v);
|
|
return;
|
|
}
|
|
if(s.m_modelversion==3)
|
|
{
|
|
modelversion=3;
|
|
CRBFV3::RBFV3Unpack(s.m_model3,nx,ny,xwr,nc,v);
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": integrity check failure");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//|This function returns model version. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model |
|
|
//| RESULT: |
|
|
//| * 1 - for models created by QNN and RBF-ML algorithms, |
|
|
//| compatible with ALGLIB 3.10 or earlier. |
|
|
//| * 2 - for models created by HierarchicalRBF, requires |
|
|
//| ALGLIB 3.11 or later |
|
|
//+------------------------------------------------------------------+
|
|
int CRBF::RBFGetModelVersion(CRBFModel &s)
|
|
{
|
|
return(s.m_modelversion);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to peek into hierarchical RBF construction |
|
|
//| process from some other thread and get current progress indicator|
|
|
//| It returns value in [0, 1]. |
|
|
//| IMPORTANT: only HRBFs (hierarchical RBFs) support peeking into |
|
|
//| progress indicator. Legacy RBF-ML and RBF-QNN do not |
|
|
//| support it. You will always get 0 value. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model object |
|
|
//| RESULT: |
|
|
//| progress value, in [0, 1] |
|
|
//+------------------------------------------------------------------+
|
|
double CRBF::RBFPeekProgress(CRBFModel &s)
|
|
{
|
|
double result=(double)s.m_progress10000/10000.0;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to submit a request for termination of the |
|
|
//| hierarchical RBF construction process from some other thread. As |
|
|
//| result, RBF construction is terminated smoothly (with proper |
|
|
//| deallocation of all necessary resources) and resultant model is |
|
|
//| filled by zeros. |
|
|
//| A rep.m_terminationtype = 8 will be returned upon receiving such |
|
|
//| request. |
|
|
//| IMPORTANT: only HRBFs(hierarchical RBFs) support termination |
|
|
//| requests. Legacy RBF-ML and RBF-QNN do not support it.|
|
|
//| An attempt to terminate their construction will be |
|
|
//| ignored. |
|
|
//| IMPORTANT: termination request flag is cleared when the model |
|
|
//| construction starts. Thus, any pre-construction |
|
|
//| termination requests will be silently ignored - only |
|
|
//| ones submitted AFTER construction has actually began |
|
|
//| will be handled. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - RBF model object |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFRequestTermination(CRBFModel &s)
|
|
{
|
|
s.m_terminationrequest=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFAlloc(CSerializer &s,CRBFModel &model)
|
|
{
|
|
//--- Header
|
|
s.Alloc_Entry();
|
|
//--- V1 model
|
|
if(model.m_modelversion==1)
|
|
{
|
|
//--- Header
|
|
s.Alloc_Entry();
|
|
CRBFV1::RBFV1Alloc(s,model.m_model1);
|
|
return;
|
|
}
|
|
//--- V2 model
|
|
if(model.m_modelversion==2)
|
|
{
|
|
//--- Header
|
|
s.Alloc_Entry();
|
|
CRBFV2::RBFV2Alloc(s,model.m_model2);
|
|
return;
|
|
}
|
|
//--- V3 model
|
|
if(model.m_modelversion==3)
|
|
{
|
|
//--- Header
|
|
s.Alloc_Entry();
|
|
CRBFV3::RBFV3Alloc(s,model.m_model3);
|
|
return;
|
|
}
|
|
CAp::Assert(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFSerialize(CSerializer &s,CRBFModel &model)
|
|
{
|
|
//--- Header
|
|
s.Serialize_Int(CSCodes::GetRBFSerializationCode());
|
|
//--- V1 model
|
|
if(model.m_modelversion==1)
|
|
{
|
|
s.Serialize_Int(m_rbffirstversion);
|
|
CRBFV1::RBFV1Serialize(s,model.m_model1);
|
|
return;
|
|
}
|
|
//--- V2 model
|
|
if(model.m_modelversion==2)
|
|
{
|
|
//--- Header
|
|
s.Serialize_Int(m_rbfversion2);
|
|
CRBFV2::RBFV2Serialize(s,model.m_model2);
|
|
return;
|
|
}
|
|
//--- V3 model
|
|
if(model.m_modelversion==3)
|
|
{
|
|
//--- Header
|
|
s.Serialize_Int(m_rbfversion3);
|
|
CRBFV3::RBFV3Serialize(s,model.m_model3);
|
|
return;
|
|
}
|
|
CAp::Assert(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFUnserialize(CSerializer &s,CRBFModel &model)
|
|
{
|
|
//--- create variables
|
|
int i0=0;
|
|
int i1=0;
|
|
RBFPrepareNonSerializableFields(model);
|
|
//--- Header
|
|
i0=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(i0==CSCodes::GetRBFSerializationCode(),__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
i1=s.Unserialize_Int();
|
|
//--- check
|
|
if(!CAp::Assert(i1==m_rbffirstversion || i1==m_rbfversion2 || i1==m_rbfversion3,__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
//--- V1 model
|
|
if(i1==m_rbffirstversion)
|
|
{
|
|
CRBFV1::RBFV1Unserialize(s,model.m_model1);
|
|
model.m_modelversion=1;
|
|
model.m_ny=model.m_model1.m_ny;
|
|
model.m_nx=model.m_model1.m_nx;
|
|
InitializeV2(model.m_nx,model.m_ny,model.m_model2);
|
|
InitializeV3(model.m_nx,model.m_ny,model.m_model3);
|
|
RBFCreateCalcBuffer(model,model.m_calcbuf);
|
|
return;
|
|
}
|
|
//--- V2 model
|
|
if(i1==m_rbfversion2)
|
|
{
|
|
CRBFV2::RBFV2Unserialize(s,model.m_model2);
|
|
model.m_modelversion=2;
|
|
model.m_ny=model.m_model2.m_ny;
|
|
model.m_nx=model.m_model2.m_nx;
|
|
InitializeV1(model.m_nx,model.m_ny,model.m_model1);
|
|
InitializeV3(model.m_nx,model.m_ny,model.m_model3);
|
|
RBFCreateCalcBuffer(model,model.m_calcbuf);
|
|
return;
|
|
}
|
|
//--- V3 model
|
|
if(i1==m_rbfversion3)
|
|
{
|
|
CRBFV3::RBFV3Unserialize(s,model.m_model3);
|
|
model.m_modelversion=3;
|
|
model.m_ny=model.m_model3.m_ny;
|
|
model.m_nx=model.m_model3.m_nx;
|
|
InitializeV1(model.m_nx,model.m_ny,model.m_model1);
|
|
InitializeV2(model.m_nx,model.m_ny,model.m_model2);
|
|
RBFCreateCalcBuffer(model,model.m_calcbuf);
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__+": unserialiation error (unexpected model type)");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize empty model |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::RBFPrepareNonSerializableFields(CRBFModel &s)
|
|
{
|
|
s.m_n=0;
|
|
s.m_hasscale=false;
|
|
s.m_radvalue=1;
|
|
s.m_radzvalue=5;
|
|
s.m_nlayers=0;
|
|
s.m_lambdav=0;
|
|
s.m_aterm=1;
|
|
s.m_algorithmtype=0;
|
|
s.m_epsort=m_eps;
|
|
s.m_epserr=m_eps;
|
|
s.m_maxits=0;
|
|
s.m_nnmaxits=100;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize V1 model (skip initialization for NX = 1 or NX > 3) |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::InitializeV1(int nx,int ny,CRBFV1Model &s)
|
|
{
|
|
if(nx==2 || nx==3)
|
|
CRBFV1::RBFV1Create(nx,ny,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize V2 model |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::InitializeV2(int nx,int ny,CRBFV2Model &s)
|
|
{
|
|
CRBFV2::RBFV2Create(nx,ny,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize V3 model |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::InitializeV3(int nx,int ny,CRBFV3Model &s)
|
|
{
|
|
CRBFV3::RBFV3Create(nx,ny,2,0,s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Cleans report fields |
|
|
//+------------------------------------------------------------------+
|
|
void CRBF::ClearReportFields(CRBFReport &rep)
|
|
{
|
|
rep.m_rmserror=AL_NaN;
|
|
rep.m_maxerror=AL_NaN;
|
|
rep.m_arows=0;
|
|
rep.m_acols=0;
|
|
rep.m_annz=0;
|
|
rep.m_iterationscount=0;
|
|
rep.m_nmv=0;
|
|
rep.m_terminationtype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 3-dimensional spline inteprolant |
|
|
//+------------------------------------------------------------------+
|
|
struct CSpline3DInterpolant
|
|
{
|
|
public:
|
|
int m_d;
|
|
int m_k;
|
|
int m_l;
|
|
int m_m;
|
|
int m_n;
|
|
int m_stype;
|
|
CRowDouble m_f;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CRowDouble m_z;
|
|
//--- constructor / destructor
|
|
CSpline3DInterpolant(void);
|
|
~CSpline3DInterpolant(void) {}
|
|
//--- copy
|
|
void Copy(const CSpline3DInterpolant&obj);
|
|
//--- overloading
|
|
void operator=(const CSpline3DInterpolant&obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSpline3DInterpolant::CSpline3DInterpolant(void)
|
|
{
|
|
m_d=0;
|
|
m_k=0;
|
|
m_l=0;
|
|
m_m=0;
|
|
m_n=0;
|
|
m_stype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3DInterpolant::Copy(const CSpline3DInterpolant &obj)
|
|
{
|
|
m_d=obj.m_d;
|
|
m_k=obj.m_k;
|
|
m_l=obj.m_l;
|
|
m_m=obj.m_m;
|
|
m_n=obj.m_n;
|
|
m_stype=obj.m_stype;
|
|
m_f=obj.m_f;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_z=obj.m_z;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CSpline3D
|
|
{
|
|
public:
|
|
static double Spline3DCalc(CSpline3DInterpolant&c,double x,double y,double z);
|
|
static void Spline3DLinTransXYZ(CSpline3DInterpolant&c,double ax,double bx,double ay,double by,double az,double bz);
|
|
static void Spline3DLinTransF(CSpline3DInterpolant&c,double a,double b);
|
|
static void Spline3DCopy(CSpline3DInterpolant&c,CSpline3DInterpolant&cc);
|
|
static void Spline3DResampleTrilinear(CRowDouble&a,int oldzcount,int oldycount,int oldxcount,int newzcount,int newycount,int newxcount,CRowDouble&b);
|
|
static void Spline3DBuildTrilinearV(CRowDouble&x,int n,CRowDouble&y,int m,CRowDouble&z,int l,CRowDouble&f,int d,CSpline3DInterpolant&c);
|
|
static void Spline3DCalcVBuf(CSpline3DInterpolant&c,double x,double y,double z,CRowDouble&f);
|
|
static void Spline3DCalcV(CSpline3DInterpolant&c,double x,double y,double z,CRowDouble&f);
|
|
static void Spline3DUnpackV(CSpline3DInterpolant&c,int &n,int &m,int &l,int &d,int &stype,CMatrixDouble&tbl);
|
|
|
|
private:
|
|
static void Spline3DDiff(CSpline3DInterpolant&c,double x,double y,double z,double&f,double&fx,double&fy,double&fxy);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates the value of the trilinear or tricubic|
|
|
//| spline at the given point (X,Y,Z). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - coefficients table. Built by BuildBilinearSpline or|
|
|
//| BuildBicubicSpline. |
|
|
//| X, Y, |
|
|
//| Z - point |
|
|
//| Result: |
|
|
//| S(x,y,z) |
|
|
//+------------------------------------------------------------------+
|
|
double CSpline3D::Spline3DCalc(CSpline3DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
double z)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
double vx=0;
|
|
double vy=0;
|
|
double vxy=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return(0);
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X=NaN/Infinite,Y=NaN/Infinite or Z=NaN/Infinite"))
|
|
return(0);
|
|
if(c.m_d!=1)
|
|
{
|
|
result=0;
|
|
return(result);
|
|
}
|
|
|
|
Spline3DDiff(c,x,y,z,v,vx,vy,vxy);
|
|
result=v;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline |
|
|
//| argument. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| AX, BX - transformation coefficients: x = A*u + B |
|
|
//| AY, BY - transformation coefficients: y = A*v + B |
|
|
//| AZ, BZ - transformation coefficients: z = A*w + B |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DLinTransXYZ(CSpline3DInterpolant &c,
|
|
double ax,
|
|
double bx,
|
|
double ay,
|
|
double by,
|
|
double az,
|
|
double bz)
|
|
{
|
|
//--- create variables
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
CRowDouble z;
|
|
CRowDouble f;
|
|
CRowDouble v;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int di=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
//--- prepare
|
|
x=c.m_x;
|
|
y=c.m_y;
|
|
z=c.m_z;
|
|
x.Resize(c.m_n);
|
|
y.Resize(c.m_m);
|
|
z.Resize(c.m_l);
|
|
f.Resize(c.m_m*c.m_n*c.m_l*c.m_d);
|
|
//---Handle different combinations of zero/nonzero AX/AY/AZ
|
|
if(ax!=0.0 && ay!=0.0 && az!=0.0)
|
|
f=c.m_f;
|
|
|
|
if(ax==0.0 && ay!=0.0 && az!=0.0)
|
|
{
|
|
for(i=0; i<c.m_m; i++)
|
|
for(j=0; j<c.m_l; j++)
|
|
{
|
|
Spline3DCalcV(c,bx,y[i],z[j],v);
|
|
for(k=0; k<c.m_n; k++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*j+i)+k)+di,v[di]);
|
|
}
|
|
ax=1;
|
|
bx=0;
|
|
}
|
|
|
|
if(ax!=0.0 && ay==0.0 && az!=0.0)
|
|
{
|
|
for(i=0; i<c.m_n; i++)
|
|
for(j=0; j<c.m_l; j++)
|
|
{
|
|
Spline3DCalcV(c,x[i],by,z[j],v);
|
|
for(k=0; k<c.m_m; k++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*j+k)+i)+di,v[di]);
|
|
}
|
|
ay=1;
|
|
by=0;
|
|
}
|
|
|
|
if(ax!=0.0 && ay!=0.0 && az==0.0)
|
|
{
|
|
for(i=0; i<c.m_n; i++)
|
|
for(j=0; j<c.m_m; j++)
|
|
{
|
|
Spline3DCalcV(c,x[i],y[j],bz,v);
|
|
for(k=0; k<c.m_l; k++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
|
|
}
|
|
az=1;
|
|
bz=0;
|
|
}
|
|
|
|
if(ax==0.0 && ay==0.0 && az!=0.0)
|
|
{
|
|
for(i=0; i<c.m_l; i++)
|
|
{
|
|
Spline3DCalcV(c,bx,by,z[i],v);
|
|
for(k=0; k<c.m_m; k++)
|
|
for(j=0; j<c.m_n; j++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*i+k)+j)+di,v[di]);
|
|
}
|
|
ax=1;
|
|
bx=0;
|
|
ay=1;
|
|
by=0;
|
|
}
|
|
|
|
if(ax==0.0 && ay!=0.0 && az==0.0)
|
|
{
|
|
for(i=0; i<c.m_m; i++)
|
|
{
|
|
Spline3DCalcV(c,bx,y[i],bz,v);
|
|
for(k=0; k<c.m_l; k++)
|
|
for(j=0; j<c.m_n; j++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*k+i)+j)+di,v[di]);
|
|
}
|
|
ax=1;
|
|
bx=0;
|
|
az=1;
|
|
bz=0;
|
|
}
|
|
|
|
if(ax!=0.0 && ay==0.0 && az==0.0)
|
|
{
|
|
for(i=0; i<c.m_n; i++)
|
|
{
|
|
Spline3DCalcV(c,x[i],by,bz,v);
|
|
for(k=0; k<c.m_l; k++)
|
|
for(j=0; j<c.m_m; j++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
|
|
}
|
|
ay=1;
|
|
by=0;
|
|
az=1;
|
|
bz=0;
|
|
}
|
|
|
|
if(ax==0.0 && ay==0.0 && az==0.0)
|
|
{
|
|
Spline3DCalcV(c,bx,by,bz,v);
|
|
for(k=0; k<c.m_l; k++)
|
|
for(j=0; j<c.m_m; j++)
|
|
for(i=0; i<c.m_n; i++)
|
|
for(di=0; di<c.m_d; di++)
|
|
f.Set(c.m_d*(c.m_n*(c.m_m*k+j)+i)+di,v[di]);
|
|
ax=1;
|
|
bx=0;
|
|
ay=1;
|
|
by=0;
|
|
az=1;
|
|
bz=0;
|
|
}
|
|
//---General case: AX<>0, AY<>0, AZ<>0
|
|
//---Unpack, scale and pack again.
|
|
x=(x-bx)/ax;
|
|
y=(y-by)/ay;
|
|
z=(z-bz)/az;
|
|
if(c.m_stype==-1)
|
|
Spline3DBuildTrilinearV(x,c.m_n,y,c.m_m,z,c.m_l,f,c.m_d,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine performs linear transformation of the spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| A, B - transformation coefficients: S2(x,y)=A*S(x,y,z)+B |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - transformed spline |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DLinTransF(CSpline3DInterpolant &c,
|
|
double a,
|
|
double b)
|
|
{
|
|
//--- create variables
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
CRowDouble z;
|
|
CRowDouble f;
|
|
int i=0;
|
|
int j=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-3 || c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
|
|
x=c.m_x;
|
|
y=c.m_y;
|
|
z=c.m_z;
|
|
f=c.m_f*a+b;
|
|
|
|
x.Resize(c.m_n);
|
|
y.Resize(c.m_m);
|
|
z.Resize(c.m_l);
|
|
f.Resize(c.m_m*c.m_n*c.m_l*c.m_d);
|
|
if(c.m_stype==-1)
|
|
Spline3DBuildTrilinearV(x,c.m_n,y,c.m_m,z,c.m_l,f,c.m_d,c);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine makes the copy of the spline model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CC - spline copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DCopy(CSpline3DInterpolant &c,
|
|
CSpline3DInterpolant &cc)
|
|
{
|
|
//--- create variables
|
|
int tblsize=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_k==1 || c.m_k==3,__FUNCTION__+": incorrect C (incorrect parameter C.K)"))
|
|
return;
|
|
|
|
cc.m_k=c.m_k;
|
|
cc.m_n=c.m_n;
|
|
cc.m_m=c.m_m;
|
|
cc.m_l=c.m_l;
|
|
cc.m_d=c.m_d;
|
|
tblsize=c.m_n*c.m_m*c.m_l*c.m_d;
|
|
cc.m_stype=c.m_stype;
|
|
cc.m_x=c.m_x;
|
|
cc.m_y=c.m_y;
|
|
cc.m_z=c.m_z;
|
|
cc.m_f=c.m_f;
|
|
cc.m_x.Resize(cc.m_n);
|
|
cc.m_y.Resize(cc.m_m);
|
|
cc.m_z.Resize(cc.m_l);
|
|
cc.m_f.Resize(tblsize);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Trilinear spline resampling |
|
|
//| INPUT PARAMETERS: |
|
|
//| A - array[0..OldXCount*OldYCount*OldZCount-1], function|
|
|
//| values at the old grid: |
|
|
//| A[0] x=0,y=0,z=0 |
|
|
//| A[1] x=1,y=0,z=0 |
|
|
//| A[..] ... |
|
|
//| A[..] x=oldxcount-1,y=0,z=0 |
|
|
//| A[..] x=0,y=1,z=0 |
|
|
//| A[..] ... |
|
|
//| ... |
|
|
//| OldZCount - old Z-count, OldZCount>1 |
|
|
//| OldYCount - old Y-count, OldYCount>1 |
|
|
//| OldXCount - old X-count, OldXCount>1 |
|
|
//| NewZCount - new Z-count, NewZCount>1 |
|
|
//| NewYCount - new Y-count, NewYCount>1 |
|
|
//| NewXCount - new X-count, NewXCount>1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| B - array[0..NewXCount*NewYCount*NewZCount-1], function|
|
|
//| values at the new grid: |
|
|
//| B[0] x=0,y=0,z=0 |
|
|
//| B[1] x=1,y=0,z=0 |
|
|
//| B[..] ... |
|
|
//| B[..] x=newxcount-1,y=0,z=0 |
|
|
//| B[..] x=0,y=1,z=0 |
|
|
//| B[..] ... |
|
|
//| ... |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DResampleTrilinear(CRowDouble &a,
|
|
int oldzcount,
|
|
int oldycount,
|
|
int oldxcount,
|
|
int newzcount,
|
|
int newycount,
|
|
int newxcount,
|
|
CRowDouble &b)
|
|
{
|
|
//--- create variables
|
|
double xd=0;
|
|
double yd=0;
|
|
double zd=0;
|
|
double c0=0;
|
|
double c1=0;
|
|
double c2=0;
|
|
double c3=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int iz=0;
|
|
|
|
b.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(oldycount>1 && oldzcount>1 && oldxcount>1,__FUNCTION__+": length/width/height less than 1"))
|
|
return;
|
|
if(!CAp::Assert(newycount>1 && newzcount>1 && newxcount>1,__FUNCTION__+": length/width/height less than 1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(a)>=oldycount*oldzcount*oldxcount,__FUNCTION__+": length/width/height less than 1"))
|
|
return;
|
|
|
|
b.Resize(newxcount*newycount*newzcount);
|
|
for(int i=0; i<newxcount; i++)
|
|
for(int j=0; j<newycount; j++)
|
|
for(int k=0; k<newzcount; k++)
|
|
{
|
|
ix=i*(oldxcount-1)/(newxcount-1);
|
|
if(ix==oldxcount-1)
|
|
ix=oldxcount-2;
|
|
xd=(double)(i*(oldxcount-1))/(double)(newxcount-1)-ix;
|
|
iy=j*(oldycount-1)/(newycount-1);
|
|
if(iy==oldycount-1)
|
|
iy=oldycount-2;
|
|
yd=(double)(j*(oldycount-1))/(double)(newycount-1)-iy;
|
|
iz=k*(oldzcount-1)/(newzcount-1);
|
|
if(iz==oldzcount-1)
|
|
iz=oldzcount-2;
|
|
zd=(double)(k*(oldzcount-1))/(double)(newzcount-1)-iz;
|
|
c0=a[oldxcount*(oldycount*iz+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*iz+iy)+(ix+1)]*xd;
|
|
c1=a[oldxcount*(oldycount*iz+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*iz+(iy+1))+(ix+1)]*xd;
|
|
c2=a[oldxcount*(oldycount*(iz+1)+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+iy)+(ix+1)]*xd;
|
|
c3=a[oldxcount*(oldycount*(iz+1)+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+(iy+1))+(ix+1)]*xd;
|
|
c0=c0*(1-yd)+c1*yd;
|
|
c1=c2*(1-yd)+c3*yd;
|
|
b.Set(newxcount*(newycount*k+j)+i,c0*(1-zd)+c1*zd);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds trilinear vector-valued spline. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - spline abscissas, array[0..N-1] |
|
|
//| Y - spline ordinates, array[0..M-1] |
|
|
//| Z - spline applicates, array[0..L-1] |
|
|
//| F - function values, array[0..M*N*L*D-1]: |
|
|
//| * first D elements store D values at (X[0],Y[0],Z[0]) |
|
|
//| * next D elements store D values at (X[1],Y[0],Z[0]) |
|
|
//| * next D elements store D values at (X[2],Y[0],Z[0]) |
|
|
//| * ... |
|
|
//| * next D elements store D values at (X[0],Y[1],Z[0]) |
|
|
//| * next D elements store D values at (X[1],Y[1],Z[0]) |
|
|
//| * next D elements store D values at (X[2],Y[1],Z[0]) |
|
|
//| * ... |
|
|
//| * next D elements store D values at (X[0],Y[0],Z[1]) |
|
|
//| * next D elements store D values at (X[1],Y[0],Z[1]) |
|
|
//| * next D elements store D values at (X[2],Y[0],Z[1]) |
|
|
//| * ... |
|
|
//| * general form - D function values at (X[i],Y[j]) are |
|
|
//| stored at F[D*(N*(M*K+J)+I)...D*(N*(M*K+J)+I)+D-1]. |
|
|
//| M,N, |
|
|
//| L - grid size, M>=2, N>=2, L>=2 |
|
|
//| D - vector dimension, D>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| C - spline interpolant |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DBuildTrilinearV(CRowDouble &x,int n,
|
|
CRowDouble &y,int m,
|
|
CRowDouble &z,int l,
|
|
CRowDouble &f,int d,
|
|
CSpline3DInterpolant &c)
|
|
{
|
|
//--- create variables
|
|
double t=0;
|
|
int tblsize=n*m*l*d;;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int i0=0;
|
|
int j0=0;
|
|
//--- check
|
|
if(!CAp::Assert(m>=2,__FUNCTION__+": M<2"))
|
|
return;
|
|
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
|
|
return;
|
|
if(!CAp::Assert(l>=2,__FUNCTION__+": L<2"))
|
|
return;
|
|
if(!CAp::Assert(d>=1,__FUNCTION__+": D<1"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(x)>=n && CAp::Len(y)>=m && CAp::Len(z)>=l,__FUNCTION__+": length of X,Y or Z is too short (Length(X/Y/Z)<N/M/L)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n) && CApServ::IsFiniteVector(y,m) && CApServ::IsFiniteVector(z,l),__FUNCTION__+": X,Y or Z contains NaN or Infinite value"))
|
|
return;
|
|
if(!CAp::Assert(CAp::Len(f)>=tblsize,__FUNCTION__+": length of F is too short (Length(F)<N*M*L*D)"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(f,tblsize),__FUNCTION__+": F contains NaN or Infinite value"))
|
|
return;
|
|
//---Fill interpolant
|
|
c.m_k=1;
|
|
c.m_n=n;
|
|
c.m_m=m;
|
|
c.m_l=l;
|
|
c.m_d=d;
|
|
c.m_stype=-1;
|
|
c.m_x=x;
|
|
c.m_y=y;
|
|
c.m_z=z;
|
|
c.m_f=f;
|
|
c.m_x.Resize(c.m_n);
|
|
c.m_y.Resize(c.m_m);
|
|
c.m_z.Resize(c.m_l);
|
|
c.m_f.Resize(tblsize);
|
|
//---Sort points:
|
|
//--- * sort x;
|
|
//--- * sort y;
|
|
//--- * sort z.
|
|
for(j=0; j<c.m_n; j++)
|
|
{
|
|
k=j;
|
|
for(i=j+1; i<c.m_n; i++)
|
|
{
|
|
if(c.m_x[i]<c.m_x[k])
|
|
k=i;
|
|
}
|
|
if(k!=j)
|
|
{
|
|
for(i=0; i<c.m_m; i++)
|
|
{
|
|
for(j0=0; j0<c.m_l; j0++)
|
|
for(i0=0; i0<c.m_d; i0++)
|
|
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*j0+i)+j)+i0,c.m_d*(c.m_n*(c.m_m*j0+i)+k)+i0);
|
|
}
|
|
c.m_x.Swap(j,k);
|
|
}
|
|
}
|
|
for(i=0; i<c.m_m; i++)
|
|
{
|
|
k=i;
|
|
for(j=i+1; j<c.m_m; j++)
|
|
{
|
|
if(c.m_y[j]<c.m_y[k])
|
|
k=j;
|
|
}
|
|
if(k!=i)
|
|
{
|
|
for(j=0; j<c.m_n; j++)
|
|
{
|
|
for(j0=0; j0<c.m_l; j0++)
|
|
for(i0=0; i0<c.m_d; i0++)
|
|
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*j0+i)+j)+i0,c.m_d*(c.m_n*(c.m_m*j0+k)+j)+i0);
|
|
}
|
|
c.m_y.Swap(i,k);
|
|
}
|
|
}
|
|
for(k=0; k<c.m_l; k++)
|
|
{
|
|
i=k;
|
|
for(j=i+1; j<c.m_l; j++)
|
|
{
|
|
if(c.m_z[j]<c.m_z[i])
|
|
i=j;
|
|
}
|
|
if(i!=k)
|
|
{
|
|
for(j=0; j<c.m_m; j++)
|
|
{
|
|
for(j0=0; j0<c.m_n; j0++)
|
|
for(i0=0; i0<c.m_d; i0++)
|
|
c.m_f.Swap(c.m_d*(c.m_n*(c.m_m*k+j)+j0)+i0,c.m_d*(c.m_n*(c.m_m*i+j)+j0)+i0);
|
|
}
|
|
c.m_z.Swap(k,i);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates bilinear or bicubic vector-valued |
|
|
//| spline at the given point (X,Y,Z). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y, |
|
|
//| Z - point |
|
|
//| F - output buffer, possibly preallocated array. In case|
|
|
//| array size is large enough to store result, it is |
|
|
//| not reallocated. Array which is too short will be |
|
|
//| reallocated |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - array[D] (or larger) which stores function values |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DCalcVBuf(CSpline3DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
double z,
|
|
CRowDouble &f)
|
|
{
|
|
//--- create variables
|
|
double xd=0;
|
|
double yd=0;
|
|
double zd=0;
|
|
double c0=0;
|
|
double c1=0;
|
|
double c2=0;
|
|
double c3=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int iz=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
int i=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X,Y or Z contains NaN/Infinite"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(f,c.m_d);
|
|
//---Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
ix=l;
|
|
//---Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
iy=l;
|
|
//---Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
|
|
l=0;
|
|
r=c.m_l-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_z[h]>=z)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
iz=l;
|
|
xd=(x-c.m_x[ix])/(c.m_x[ix+1]-c.m_x[ix]);
|
|
yd=(y-c.m_y[iy])/(c.m_y[iy+1]-c.m_y[iy]);
|
|
zd=(z-c.m_z[iz])/(c.m_z[iz+1]-c.m_z[iz]);
|
|
for(i=0; i<c.m_d; i++)
|
|
{
|
|
//---Trilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
c0=c.m_f[c.m_d*(c.m_n*(c.m_m*iz+iy)+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*iz+iy)+(ix+1))+i]*xd;
|
|
c1=c.m_f[c.m_d*(c.m_n*(c.m_m*iz+(iy+1))+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*iz+(iy+1))+(ix+1))+i]*xd;
|
|
c2=c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+iy)+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+iy)+(ix+1))+i]*xd;
|
|
c3=c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+(iy+1))+ix)+i]*(1-xd)+c.m_f[c.m_d*(c.m_n*(c.m_m*(iz+1)+(iy+1))+(ix+1))+i]*xd;
|
|
c0=c0*(1-yd)+c1*yd;
|
|
c1=c2*(1-yd)+c3*yd;
|
|
f.Set(i,c0*(1-zd)+c1*zd);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates trilinear or tricubic vector-valued |
|
|
//| spline at the given point (X,Y,Z). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y, |
|
|
//| Z - point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - array[D] which stores function values. F is |
|
|
//| out-parameter and it is reallocated after call to |
|
|
//| this function. In case you want to reuse |
|
|
//| previously allocated F, you may use |
|
|
//| Spline2DCalcVBuf(), which reallocates F only when |
|
|
//| it is too small. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DCalcV(CSpline3DInterpolant &c,
|
|
double x,
|
|
double y,
|
|
double z,
|
|
CRowDouble &f)
|
|
{
|
|
f.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y) && MathIsValidNumber(z),__FUNCTION__+": X=NaN/Infinite,Y=NaN/Infinite or Z=NaN/Infinite"))
|
|
return;
|
|
|
|
f.Resize(c.m_d);
|
|
Spline3DCalcVBuf(c,x,y,z,f);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine unpacks tri-dimensional spline into the |
|
|
//| coefficients table |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| Result: |
|
|
//| N - grid size (X) |
|
|
//| M - grid size (Y) |
|
|
//| L - grid size (Z) |
|
|
//| D - number of components |
|
|
//| SType - spline type. Currently, only one spline type is |
|
|
//| supported: trilinear spline, as indicated |
|
|
//| by SType=1. |
|
|
//| Tbl - spline coefficients: |
|
|
//| [0..(N-1)*(M-1)*(L-1)*D-1, 0..13]. |
|
|
//| For T=0..D-1 (component index), I = 0...N-2 |
|
|
//| (x index), J=0..M-2 (y index), K=0..L-2 (z index): |
|
|
//| Q := T + I*D + J*D*(N-1) + K*D*(N-1)*(M-1), |
|
|
//| Q-th row stores decomposition for T-th component |
|
|
//| of the vector-valued function |
|
|
//| Tbl[Q,0] = X[i] |
|
|
//| Tbl[Q,1] = X[i+1] |
|
|
//| Tbl[Q,2] = Y[j] |
|
|
//| Tbl[Q,3] = Y[j+1] |
|
|
//| Tbl[Q,4] = Z[k] |
|
|
//| Tbl[Q,5] = Z[k+1] |
|
|
//| Tbl[Q,6] = C000 |
|
|
//| Tbl[Q,7] = C100 |
|
|
//| Tbl[Q,8] = C010 |
|
|
//| Tbl[Q,9] = C110 |
|
|
//| Tbl[Q,10]= C001 |
|
|
//| Tbl[Q,11]= C101 |
|
|
//| Tbl[Q,12]= C011 |
|
|
//| Tbl[Q,13]= C111 |
|
|
//| On each grid square spline is equals to: |
|
|
//| S(x) = SUM(c[i,j,k]*(x^i)*(y^j)*(z^k), i=0..1, j=0..1, k=0..1) |
|
|
//| t = x-x[j] |
|
|
//| u = y-y[i] |
|
|
//| v = z-z[k] |
|
|
//| NOTE: format of Tbl is given for SType=1. Future versions of |
|
|
//| ALGLIB can use different formats for different values of |
|
|
//| SType. |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DUnpackV(CSpline3DInterpolant &c,
|
|
int &n,
|
|
int &m,
|
|
int &l,
|
|
int &d,
|
|
int &stype,
|
|
CMatrixDouble &tbl)
|
|
{
|
|
//--- create variables
|
|
int p=0;
|
|
int ci=0;
|
|
int cj=0;
|
|
int ck=0;
|
|
double du=0;
|
|
double dv=0;
|
|
double dw=0;
|
|
int i0=0;
|
|
|
|
n=0;
|
|
m=0;
|
|
l=0;
|
|
d=0;
|
|
stype=0;
|
|
tbl.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
|
|
n=c.m_n;
|
|
m=c.m_m;
|
|
l=c.m_l;
|
|
d=c.m_d;
|
|
stype=MathAbs(c.m_stype);
|
|
tbl.Resize((n-1)*(m-1)*(l-1)*d,14);
|
|
//---Fill
|
|
for(int i=0; i<n-1; i++)
|
|
for(int j=0; j<m-1; j++)
|
|
for(int k=0; k<l-1; k++)
|
|
for(int di=0; di<d; di++)
|
|
{
|
|
p=d*((n-1)*((m-1)*k+j)+i)+di;
|
|
tbl.Set(p,0,c.m_x[i]);
|
|
tbl.Set(p,1,c.m_x[i+1]);
|
|
tbl.Set(p,2,c.m_y[j]);
|
|
tbl.Set(p,3,c.m_y[j+1]);
|
|
tbl.Set(p,4,c.m_z[k]);
|
|
tbl.Set(p,5,c.m_z[k+1]);
|
|
du=1.0/(tbl.Get(p,1)-tbl.Get(p,0));
|
|
dv=1.0/(tbl.Get(p,3)-tbl.Get(p,2));
|
|
dw=1.0/(tbl.Get(p,5)-tbl.Get(p,4));
|
|
//---Trilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
for(i0=6; i0<=13; i0++)
|
|
tbl.Set(p,i0,0);
|
|
tbl.Set(p,6,c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,7,c.m_f[d*(n*(m*k+j)+(i+1))+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,8,c.m_f[d*(n*(m*k+(j+1))+i)+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,9,c.m_f[d*(n*(m*k+(j+1))+(i+1))+di]-c.m_f[d*(n*(m*k+(j+1))+i)+di]-c.m_f[d*(n*(m*k+j)+(i+1))+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,10,c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,11,c.m_f[d*(n*(m*(k+1)+j)+(i+1))+di]-c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+j)+(i+1))+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,12,c.m_f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+(j+1))+i)+di]+c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
tbl.Set(p,13,c.m_f[d*(n*(m*(k+1)+(j+1))+(i+1))+di]-c.m_f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.m_f[d*(n*(m*(k+1)+j)+(i+1))+di]+c.m_f[d*(n*(m*(k+1)+j)+i)+di]-c.m_f[d*(n*(m*k+(j+1))+(i+1))+di]+c.m_f[d*(n*(m*k+(j+1))+i)+di]+c.m_f[d*(n*(m*k+j)+(i+1))+di]-c.m_f[d*(n*(m*k+j)+i)+di]);
|
|
}
|
|
//---Rescale Cij
|
|
for(ci=0; ci<=1; ci++)
|
|
{
|
|
for(cj=0; cj<=1; cj++)
|
|
for(ck=0; ck<=1; ck++)
|
|
tbl.Mul(p,6+2*(2*ck+cj)+ci,MathPow(du,ci)*MathPow(dv,cj)*MathPow(dw,ck));
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine calculates the value of the trilinear (or |
|
|
//| tricubic;possible will be later) spline at the given point X (and|
|
|
//| its derivatives; possible will be later). |
|
|
//| INPUT PARAMETERS: |
|
|
//| C - spline interpolant. |
|
|
//| X, Y, Z - point |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| F - S(x,y,z) |
|
|
//| FX - dS(x,y,z)/dX |
|
|
//| FY - dS(x,y,z)/dY |
|
|
//| FXY - d2S(x,y,z)/dXdY |
|
|
//+------------------------------------------------------------------+
|
|
void CSpline3D::Spline3DDiff(CSpline3DInterpolant &c,
|
|
double x,double y,
|
|
double z,double &f,
|
|
double &fx,double &fy,
|
|
double &fxy)
|
|
{
|
|
//--- create variables
|
|
double xd=0;
|
|
double yd=0;
|
|
double zd=0;
|
|
double c0=0;
|
|
double c1=0;
|
|
double c2=0;
|
|
double c3=0;
|
|
int ix=0;
|
|
int iy=0;
|
|
int iz=0;
|
|
int l=0;
|
|
int r=0;
|
|
int h=0;
|
|
//---Prepare F, dF/dX, dF/dY, d2F/dXdY
|
|
f=0;
|
|
fx=0;
|
|
fy=0;
|
|
fxy=0;
|
|
//--- check
|
|
if(!CAp::Assert(c.m_stype==-1 || c.m_stype==-3,__FUNCTION__+": incorrect C (incorrect parameter C.SType)"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(x) && MathIsValidNumber(y),__FUNCTION__+": X or Y contains NaN or Infinite value"))
|
|
return;
|
|
if(c.m_d!=1)
|
|
return;
|
|
//---Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
|
|
l=0;
|
|
r=c.m_n-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_x[h]>=x)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
ix=l;
|
|
//---Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
|
|
l=0;
|
|
r=c.m_m-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_y[h]>=y)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
iy=l;
|
|
//---Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
|
|
l=0;
|
|
r=c.m_l-1;
|
|
while(l!=r-1)
|
|
{
|
|
h=(l+r)/2;
|
|
if(c.m_z[h]>=z)
|
|
r=h;
|
|
else
|
|
l=h;
|
|
}
|
|
iz=l;
|
|
xd=(x-c.m_x[ix])/(c.m_x[ix+1]-c.m_x[ix]);
|
|
yd=(y-c.m_y[iy])/(c.m_y[iy+1]-c.m_y[iy]);
|
|
zd=(z-c.m_z[iz])/(c.m_z[iz+1]-c.m_z[iz]);
|
|
//---Trilinear interpolation
|
|
if(c.m_stype==-1)
|
|
{
|
|
c0=c.m_f[c.m_n*(c.m_m*iz+iy)+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*iz+iy)+(ix+1)]*xd;
|
|
c1=c.m_f[c.m_n*(c.m_m*iz+(iy+1))+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*iz+(iy+1))+(ix+1)]*xd;
|
|
c2=c.m_f[c.m_n*(c.m_m*(iz+1)+iy)+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*(iz+1)+iy)+(ix+1)]*xd;
|
|
c3=c.m_f[c.m_n*(c.m_m*(iz+1)+(iy+1))+ix]*(1-xd)+c.m_f[c.m_n*(c.m_m*(iz+1)+(iy+1))+(ix+1)]*xd;
|
|
c0=c0*(1-yd)+c1*yd;
|
|
c1=c2*(1-yd)+c3*yd;
|
|
f=c0*(1-zd)+c1*zd;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CIntComp
|
|
{
|
|
public:
|
|
static void NSFitSphereMCC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rhi);
|
|
static void NSFitSphereMIC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo);
|
|
static void NSFitSphereMZC(CMatrixDouble&xy,int npoints,int nx,CRowDouble&cx,double&rlo,double&rhi);
|
|
static void NSFitSphereX(CMatrixDouble&xy,int npoints,int nx,int problemtype,double epsx,int aulits,double penalty,CRowDouble&cx,double&rlo,double&rhi);
|
|
static void Spline1DFitPenalized(double &cx[],double &cy[],const int n,const int m,const double rho,int &info,CSpline1DInterpolant&s,CSpline1DFitReport&rep);
|
|
static void Spline1DFitPenalizedW(double &cx[],double &cy[],double &cw[],const int n,const int m,double rho,int &info,CSpline1DInterpolant&s,CSpline1DFitReport&rep);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This function is left for backward compatibility. |
|
|
//| Use fitspheremc() instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::NSFitSphereMCC(CMatrixDouble &xy,int npoints,
|
|
int nx,CRowDouble &cx,double &rhi)
|
|
{
|
|
double dummy=0;
|
|
|
|
cx.Resize(0);
|
|
rhi=0;
|
|
//--- function call
|
|
NSFitSphereX(xy,npoints,nx,1,0.0,0,0.0,cx,dummy,rhi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//|This function is left for backward compatibility. |
|
|
//| Use fitspheremi() instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::NSFitSphereMIC(CMatrixDouble &xy,int npoints,
|
|
int nx,CRowDouble &cx,double &rlo)
|
|
{
|
|
double dummy=0;
|
|
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
//--- function call
|
|
NSFitSphereX(xy,npoints,nx,2,0.0,0,0.0,cx,rlo,dummy);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is left for backward compatibility. |
|
|
//| Use fitspheremz() instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::NSFitSphereMZC(CMatrixDouble &xy,int npoints,int nx,
|
|
CRowDouble &cx,double &rlo,double &rhi)
|
|
{
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
rhi=0;
|
|
//--- function call
|
|
NSFitSphereX(xy,npoints,nx,3,0.0,0,0.0,cx,rlo,rhi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is left for backward compatibility. |
|
|
//| Use FitSphereX() instead. |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::NSFitSphereX(CMatrixDouble &xy,int npoints,int nx,
|
|
int problemtype,double epsx,int aulits,
|
|
double penalty,CRowDouble &cx,double &rlo,
|
|
double &rhi)
|
|
{
|
|
cx.Resize(0);
|
|
rlo=0;
|
|
rhi=0;
|
|
//--- function call
|
|
CFitSphere::FitSphereX(xy,npoints,nx,problemtype,epsx,aulits,penalty,cx,rlo,rhi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is an obsolete and deprecated version of fitting by|
|
|
//| penalized cubic spline. |
|
|
//| It was superseded by Spline1DFit(), which is an orders of |
|
|
//| magnitude faster and more memory-efficient implementation. |
|
|
//| Do NOT use this function in the new code! |
|
|
//+------------------------------------------------------------------+
|
|
//| Rational least squares fitting using Floater-Hormann rational |
|
|
//| functions with optimal D chosen from [0,9]. |
|
|
//| Equidistant grid with M node on [min(x),max(x)] is used to build |
|
|
//| basis functions. Different values of D are tried, optimal D |
|
|
//| (least root mean square error) is chosen. Task is linear, so |
|
|
//| linear least squares solver is used. Complexity of this |
|
|
//| computational scheme is O(N*M^2) (mostly dominated by the least |
|
|
//| squares solver). |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| N - number of points, N>0. |
|
|
//| M - number of basis functions ( = number_of_nodes), M>=2.|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearWC() subroutine. |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| -3 means inconsistent constraints |
|
|
//| B - barycentric interpolant. |
|
|
//| Rep - report, same format as in LSFitLinearWC() subroutine.|
|
|
//| Following fields are set: |
|
|
//| * DBest best value of the D parameter |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::Spline1DFitPenalized(double &cx[],double &cy[],
|
|
const int n,const int m,
|
|
const double rho,int &info,
|
|
CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create arrays
|
|
double w[];
|
|
double x[];
|
|
double y[];
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(rho),__FUNCTION__+": Rho is infinite!"))
|
|
return;
|
|
//--- allocation
|
|
ArrayResize(w,n);
|
|
//--- initialization
|
|
for(int i=0; i<n; i++)
|
|
w[i]=1;
|
|
//--- function call
|
|
Spline1DFitPenalizedW(x,y,w,n,m,rho,info,s,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is an obsolete and deprecated version of fitting by|
|
|
//| penalized cubic spline. |
|
|
//| It was superseded by Spline1DFit(), which is an orders of |
|
|
//| magnitude faster and more memory-efficient implementation. |
|
|
//| Do NOT use this function in the new code! |
|
|
//+------------------------------------------------------------------+
|
|
//| Weighted fitting by penalized cubic spline. |
|
|
//| Equidistant grid with M nodes on [min(x,xc),max(x,xc)] is used to|
|
|
//| build basis functions. Basis functions are cubic splines with |
|
|
//| natural boundary conditions. Problem is regularized by adding |
|
|
//| non-linearity penalty to the usual least squares penalty |
|
|
//| function: |
|
|
//| S(x) = arg min { LS + P }, where |
|
|
//| LS = SUM { w[i]^2*(y[i] - S(x[i]))^2 } - least squares |
|
|
//| penalty |
|
|
//| P = C*10^rho*integral{ S''(x)^2*dx } - non-linearity |
|
|
//| penalty |
|
|
//| rho - tunable constant given by user |
|
|
//| C - automatically determined scale parameter, |
|
|
//| makes penalty invariant with respect to scaling of X, |
|
|
//| Y, W. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - points, array[0..N-1]. |
|
|
//| Y - function values, array[0..N-1]. |
|
|
//| W - weights, array[0..N-1] |
|
|
//| Each summand in square sum of approximation |
|
|
//| deviations from given values is multiplied by the |
|
|
//| square of corresponding weight. Fill it by 1's if |
|
|
//| you don't want to solve weighted problem. |
|
|
//| N - number of points (optional): |
|
|
//| * N>0 |
|
|
//| * if given, only first N elements of X/Y/W are |
|
|
//| processed |
|
|
//| * if not given, automatically determined from X/Y/W |
|
|
//| sizes |
|
|
//| M - number of basis functions ( = number_of_nodes), M>=4.|
|
|
//| Rho - regularization constant passed by user. It penalizes |
|
|
//| nonlinearity in the regression spline. It is |
|
|
//| logarithmically scaled, i.e. actual value of |
|
|
//| regularization constant is calculated as 10^Rho. It |
|
|
//| is automatically scaled so that: |
|
|
//| * Rho=2.0 corresponds to moderate amount of |
|
|
//| nonlinearity |
|
|
//| * generally, it should be somewhere in the |
|
|
//| [-8.0,+8.0] |
|
|
//| If you do not want to penalize nonlineary, |
|
|
//| pass small Rho. Values as low as -15 should work. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Info- same format as in LSFitLinearWC() subroutine. |
|
|
//| * Info>0 task is solved |
|
|
//| * Info<=0 an error occured: |
|
|
//| -4 means inconvergence of internal SVD |
|
|
//| or Cholesky decomposition; problem |
|
|
//| may be too ill-conditioned (very |
|
|
//| rare) |
|
|
//| S - spline interpolant. |
|
|
//| Rep - Following fields are set: |
|
|
//| * RMSError rms error on the (X,Y). |
|
|
//| * AvgError average error on the (X,Y). |
|
|
//| * AvgRelError average relative error on the |
|
|
//| non-zero Y |
|
|
//| * MaxError maximum error |
|
|
//| NON-WEIGHTED ERRORS ARE CALCULATED |
|
|
//| IMPORTANT: |
|
|
//| this subroitine doesn't calculate task's condition number |
|
|
//| for K<>0. |
|
|
//| NOTE 1: additional nodes are added to the spline outside of the |
|
|
//| fitting interval to force linearity when x<min(x,xc) or |
|
|
//| x>max(x,xc). It is done for consistency - we penalize |
|
|
//| non-linearity at [min(x,xc),max(x,xc)], so it is natural to |
|
|
//| force linearity outside of this interval. |
|
|
//| NOTE 2: function automatically sorts points, so caller may pass |
|
|
//| unsorted array. |
|
|
//+------------------------------------------------------------------+
|
|
void CIntComp::Spline1DFitPenalizedW(double &cx[],double &cy[],
|
|
double &cw[],const int n,
|
|
const int m,double rho,
|
|
int &info,CSpline1DInterpolant &s,
|
|
CSpline1DFitReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int b=0;
|
|
double v=0;
|
|
double relcnt=0;
|
|
double xa=0;
|
|
double xb=0;
|
|
double sa=0;
|
|
double sb=0;
|
|
double pdecay=0;
|
|
double tdecay=0;
|
|
double fdmax=0;
|
|
double admax=0;
|
|
double fa=0;
|
|
double ga=0;
|
|
double fb=0;
|
|
double gb=0;
|
|
double lambdav=0;
|
|
int i_=0;
|
|
int i1_=0;
|
|
//--- create arrays
|
|
double xoriginal[];
|
|
double yoriginal[];
|
|
double fcolumn[];
|
|
double y2[];
|
|
double w2[];
|
|
double xc[];
|
|
double yc[];
|
|
int dc[];
|
|
double bx[];
|
|
double by[];
|
|
double bd1[];
|
|
double bd2[];
|
|
double tx[];
|
|
double ty[];
|
|
double td[];
|
|
double rightpart[];
|
|
double c[];
|
|
double tmp0[];
|
|
double x[];
|
|
double y[];
|
|
double w[];
|
|
//--- create matrix
|
|
CMatrixDouble fmatrix;
|
|
CMatrixDouble amatrix;
|
|
CMatrixDouble d2matrix;
|
|
CMatrixDouble nmatrix;
|
|
//--- objects of classes
|
|
CSpline1DInterpolant bs;
|
|
CFblsLinCgState cgstate;
|
|
//--- copy arrays
|
|
ArrayCopy(x,cx);
|
|
ArrayCopy(y,cy);
|
|
ArrayCopy(w,cw);
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(m>=4,__FUNCTION__+": M<4!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(x)>=n,__FUNCTION__+": Length(X)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(y)>=n,__FUNCTION__+": Length(Y)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(w)>=n,__FUNCTION__+": Length(W)<N!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(y,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(w,n),__FUNCTION__+": Y contains infinite or NAN values!"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CMath::IsFinite(rho),__FUNCTION__+": Rho is infinite!"))
|
|
return;
|
|
//--- Prepare LambdaV
|
|
v=-(MathLog(CMath::m_machineepsilon)/MathLog(10));
|
|
//--- check
|
|
if(rho<-v)
|
|
rho=-v;
|
|
//--- check
|
|
if(rho>v)
|
|
rho=v;
|
|
lambdav=MathPow(10,rho);
|
|
//--- Sort X,Y,W
|
|
CSpline1D::HeapSortDPoints(x,y,w,n);
|
|
//--- Scale X,Y,XC,YC
|
|
CLSFit::LSFitScaleXY(x,y,w,n,xc,yc,dc,0,xa,xb,sa,sb,xoriginal,yoriginal);
|
|
//--- Allocate space
|
|
fmatrix.Resize(n,m);
|
|
amatrix.Resize(m,m);
|
|
d2matrix.Resize(m,m);
|
|
ArrayResize(bx,m);
|
|
ArrayResize(by,m);
|
|
ArrayResize(fcolumn,n);
|
|
nmatrix.Resize(m,m);
|
|
ArrayResize(rightpart,m);
|
|
ArrayResize(tmp0,MathMax(m,n));
|
|
ArrayResize(c,m);
|
|
//--- Fill:
|
|
//--- * FMatrix by values of basis functions
|
|
//--- * TmpAMatrix by second derivatives of I-th function at J-th point
|
|
//--- * CMatrix by constraints
|
|
fdmax=0;
|
|
for(b=0; b<=m-1; b++)
|
|
{
|
|
//--- Prepare I-th basis function
|
|
for(j=0; j<=m-1; j++)
|
|
{
|
|
bx[j]=(double)(2*j)/(double)(m-1)-1;
|
|
by[j]=0;
|
|
}
|
|
by[b]=1;
|
|
//--- function call
|
|
CSpline1D::Spline1DGridDiff2Cubic(bx,by,m,2,0.0,2,0.0,bd1,bd2);
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildCubic(bx,by,m,2,0.0,2,0.0,bs);
|
|
//--- Calculate B-th column of FMatrix
|
|
//--- Update FDMax (maximum column norm)
|
|
CSpline1D::Spline1DConvCubic(bx,by,m,2,0.0,2,0.0,x,n,fcolumn);
|
|
for(i_=0; i_<n; i_++)
|
|
fmatrix.Set(i_,b,fcolumn[i_]);
|
|
v=0;
|
|
for(i=0; i<n; i++)
|
|
v=v+CMath::Sqr(w[i]*fcolumn[i]);
|
|
fdmax=MathMax(fdmax,v);
|
|
//--- Fill temporary with second derivatives of basis function
|
|
for(i_=0; i_<=m-1; i_++)
|
|
d2matrix.Set(b,i_,bd2[i_]);
|
|
}
|
|
//--- * calculate penalty matrix A
|
|
//--- * calculate max of diagonal elements of A
|
|
//--- * calculate PDecay - coefficient before penalty matrix
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
for(j=i; j<=m-1; j++)
|
|
{
|
|
//--- calculate integral(B_i''*B_j'') where B_i and B_j are
|
|
//--- i-th and j-th basis splines.
|
|
//--- B_i and B_j are piecewise linear functions.
|
|
v=0;
|
|
for(b=0; b<=m-2; b++)
|
|
{
|
|
//--- change values
|
|
fa=d2matrix[i][b];
|
|
fb=d2matrix[i][b+1];
|
|
ga=d2matrix[j][b];
|
|
gb=d2matrix[j][b+1];
|
|
v=v+(bx[b+1]-bx[b])*(fa*ga+(fa*(gb-ga)+ga*(fb-fa))/2+(fb-fa)*(gb-ga)/3);
|
|
}
|
|
amatrix.Set(i,j,v);
|
|
amatrix.Set(j,i,v);
|
|
}
|
|
}
|
|
//--- change values
|
|
admax=0;
|
|
for(i=0; i<=m-1; i++)
|
|
admax=MathMax(admax,MathAbs(amatrix[i][i]));
|
|
pdecay=lambdav*fdmax/admax;
|
|
//--- Calculate TDecay for Tikhonov regularization
|
|
tdecay=fdmax*(1+pdecay)*10*CMath::m_machineepsilon;
|
|
//--- Prepare system
|
|
//--- NOTE: FMatrix is spoiled during this process
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=w[i];
|
|
for(i_=0; i_<=m-1; i_++)
|
|
fmatrix.Set(i,i_,v*fmatrix.Get(i,i_));
|
|
}
|
|
//--- function call
|
|
CAblas::RMatrixGemm(m,m,n,1.0,fmatrix,0,0,1,fmatrix,0,0,0,0.0,nmatrix,0,0);
|
|
for(i=0; i<=m-1; i++)
|
|
{
|
|
for(j=0; j<=m-1; j++)
|
|
nmatrix.Set(i,j,nmatrix[i][j]+pdecay*amatrix[i][j]);
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<=m-1; i++)
|
|
nmatrix.Set(i,i,nmatrix[i][i]+tdecay);
|
|
for(i=0; i<=m-1; i++)
|
|
rightpart[i]=0;
|
|
//--- change values
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=y[i]*w[i];
|
|
for(i_=0; i_<=m-1; i_++)
|
|
rightpart[i_]=rightpart[i_]+v*fmatrix.Get(i,i_);
|
|
}
|
|
//--- Solve system
|
|
if(!CTrFac::SPDMatrixCholesky(nmatrix,m,true))
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
//--- function call
|
|
CFbls::FblsCholeskySolve(nmatrix,1.0,m,true,rightpart,tmp0);
|
|
//--- copy
|
|
for(i_=0; i_<=m-1; i_++)
|
|
c[i_]=rightpart[i_];
|
|
//--- add nodes to force linearity outside of the fitting interval
|
|
CSpline1D::Spline1DGridDiffCubic(bx,c,m,2,0.0,2,0.0,bd1);
|
|
//--- allocation
|
|
ArrayResize(tx,m+2);
|
|
ArrayResize(ty,m+2);
|
|
ArrayResize(td,m+2);
|
|
//--- copy
|
|
i1_=-1;
|
|
for(i_=1; i_<=m; i_++)
|
|
tx[i_]=bx[i_+i1_];
|
|
i1_=-1;
|
|
for(i_=1; i_<=m; i_++)
|
|
ty[i_]=rightpart[i_+i1_];
|
|
i1_=-1;
|
|
for(i_=1; i_<=m; i_++)
|
|
td[i_]=bd1[i_+i1_];
|
|
//--- change values
|
|
tx[0]=tx[1]-(tx[2]-tx[1]);
|
|
ty[0]=ty[1]-td[1]*(tx[2]-tx[1]);
|
|
td[0]=td[1];
|
|
tx[m+1]=tx[m]+(tx[m]-tx[m-1]);
|
|
ty[m+1]=ty[m]+td[m]*(tx[m]-tx[m-1]);
|
|
td[m+1]=td[m];
|
|
//--- function call
|
|
CSpline1D::Spline1DBuildHermite(tx,ty,td,m+2,s);
|
|
//--- function call
|
|
CSpline1D::Spline1DLinTransX(s,2/(xb-xa),-((xa+xb)/(xb-xa)));
|
|
//--- function call
|
|
CSpline1D::Spline1DLinTransY(s,sb-sa,sa);
|
|
//--- change value
|
|
info=1;
|
|
//--- Fill report
|
|
rep.m_rmserror=0;
|
|
rep.m_avgerror=0;
|
|
rep.m_avgrelerror=0;
|
|
rep.m_maxerror=0;
|
|
relcnt=0;
|
|
//--- function call
|
|
CSpline1D::Spline1DConvCubic(bx,rightpart,m,2,0.0,2,0.0,x,n,fcolumn);
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- change values
|
|
v=(sb-sa)*fcolumn[i]+sa;
|
|
rep.m_rmserror+=CMath::Sqr(v-yoriginal[i]);
|
|
rep.m_avgerror=rep.m_avgerror+MathAbs(v-yoriginal[i]);
|
|
//--- check
|
|
if(yoriginal[i]!=0.0)
|
|
{
|
|
rep.m_avgrelerror=rep.m_avgrelerror+MathAbs(v-yoriginal[i])/MathAbs(yoriginal[i]);
|
|
relcnt=relcnt+1;
|
|
}
|
|
rep.m_maxerror=MathMax(rep.m_maxerror,MathAbs(v-yoriginal[i]));
|
|
}
|
|
//--- change values
|
|
rep.m_rmserror=MathSqrt(rep.m_rmserror/n);
|
|
rep.m_avgerror=rep.m_avgerror/n;
|
|
//--- check
|
|
if(relcnt!=0.0)
|
|
rep.m_avgrelerror=rep.m_avgrelerror/relcnt;
|
|
}
|
|
//+------------------------------------------------------------------+
|