From ed7a5d8920ed4ac36cc6fa5399b62a43ad34ac7a Mon Sep 17 00:00:00 2001 From: caes Date: Thu, 30 Jun 2016 03:00:32 -0400 Subject: [PATCH] updated to latest version of psdlag, compiles well provided libalglib-dev is installed, looks to still be reading headers from the src/inc directory, this could cause problems --- src/archive/newsrc.tar | Bin 0 -> 13516 bytes src/archive/oldsrc.tar | Bin 0 -> 11148288 bytes src/inc/alglib/alglibinternal.cpp | 15919 ++++++++++++ src/inc/alglib/alglibinternal.h | 1074 + src/inc/alglib/alglibmisc.cpp | 3611 +++ src/inc/alglib/alglibmisc.h | 769 + src/inc/alglib/ap.cpp | 10661 ++++++++ src/inc/alglib/ap.h | 1575 ++ src/inc/alglib/dataanalysis.cpp | 35078 ++++++++++++++++++++++++++ src/inc/alglib/dataanalysis.h | 7394 ++++++ src/inc/alglib/diffequations.cpp | 1187 + src/inc/alglib/diffequations.h | 267 + src/inc/alglib/fasttransforms.cpp | 3554 +++ src/inc/alglib/fasttransforms.h | 691 + src/inc/alglib/integration.cpp | 3961 +++ src/inc/alglib/integration.h | 837 + src/inc/alglib/interpolation.cpp | 30715 ++++++++++++++++++++++ src/inc/alglib/interpolation.h | 5906 +++++ src/inc/alglib/linalg.cpp | 33805 +++++++++++++++++++++++++ src/inc/alglib/linalg.h | 5187 ++++ src/inc/alglib/optimization.cpp | 25034 ++++++++++++++++++ src/inc/alglib/optimization.h | 4379 ++++ src/inc/alglib/solvers.cpp | 8709 +++++++ src/inc/alglib/solvers.h | 2016 ++ src/inc/alglib/specialfunctions.cpp | 9637 +++++++ src/inc/alglib/specialfunctions.h | 1976 ++ src/inc/alglib/statistics.cpp | 19718 +++++++++++++++ src/inc/alglib/statistics.h | 1305 + src/inc/alglib/stdafx.h | 2 + src/inc/lag_rms.hpp | 58 + src/inc/psd_rms.hpp | 41 + src/inc/psdlag_rms.hpp | 56 + src/lag_rms.cpp | 230 + src/main.cpp | 11 - src/makefile | 1 + src/psd_rms.cpp | 108 + src/psdlag_rms.cpp | 255 + 37 files changed, 235716 insertions(+), 11 deletions(-) create mode 100644 src/archive/newsrc.tar create mode 100644 src/archive/oldsrc.tar create mode 100644 src/inc/alglib/alglibinternal.cpp create mode 100644 src/inc/alglib/alglibinternal.h create mode 100644 src/inc/alglib/alglibmisc.cpp create mode 100644 src/inc/alglib/alglibmisc.h create mode 100644 src/inc/alglib/ap.cpp create mode 100644 src/inc/alglib/ap.h create mode 100644 src/inc/alglib/dataanalysis.cpp create mode 100644 src/inc/alglib/dataanalysis.h create mode 100644 src/inc/alglib/diffequations.cpp create mode 100644 src/inc/alglib/diffequations.h create mode 100644 src/inc/alglib/fasttransforms.cpp create mode 100644 src/inc/alglib/fasttransforms.h create mode 100644 src/inc/alglib/integration.cpp create mode 100644 src/inc/alglib/integration.h create mode 100644 src/inc/alglib/interpolation.cpp create mode 100644 src/inc/alglib/interpolation.h create mode 100644 src/inc/alglib/linalg.cpp create mode 100644 src/inc/alglib/linalg.h create mode 100644 src/inc/alglib/optimization.cpp create mode 100644 src/inc/alglib/optimization.h create mode 100644 src/inc/alglib/solvers.cpp create mode 100644 src/inc/alglib/solvers.h create mode 100644 src/inc/alglib/specialfunctions.cpp create mode 100644 src/inc/alglib/specialfunctions.h create mode 100644 src/inc/alglib/statistics.cpp create mode 100644 src/inc/alglib/statistics.h create mode 100644 src/inc/alglib/stdafx.h create mode 100644 src/inc/lag_rms.hpp create mode 100644 src/inc/psd_rms.hpp create mode 100644 src/inc/psdlag_rms.hpp create mode 100644 src/lag_rms.cpp create mode 100644 src/psd_rms.cpp create mode 100644 src/psdlag_rms.cpp diff --git a/src/archive/newsrc.tar b/src/archive/newsrc.tar new file mode 100644 index 0000000000000000000000000000000000000000..cbc313cbd7f502a9aa8fad3a2fb200f04d38aa2e GIT binary patch literal 13516 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

^F+{f550Q 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 GxB&p>?6fQZ literal 0 HcmV?d00001 diff --git a/src/archive/oldsrc.tar b/src/archive/oldsrc.tar new file mode 100644 index 0000000000000000000000000000000000000000..8f1eca253e84f2e989104238935433790270bfd1 GIT binary patch literal 11148288 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


=RK&SsnMCj4K2e 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 )pG7B|Q7j2a$yi>C?`$34iyRv+RZ9 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⁣Q$`A@HKM8+R=J0(g40#O>8jv#*st8f({c}2 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�G_N`V9ETVYPuniAUI6UBDNSg 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 &UPGjLH1s`}j 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 _?zdCyMEBcRbSsN)X3;w|I|;wdqD}e5 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�j{BJW#~NPq0i&GO@Mib1!HefMcKlJ#v~)L{nuyn!+Y960=r(rs8r{YY z&Z*nj;X8F3JN(4lhO3N>Tt~xm&F~a4j=DlS(-)c4rSf43C&Xpzsu(@|Jf9(6=;=9D zs|`Pn)oO!p@hBi!FEzGi&Cm4w{{yoW!864XxI&!R#Xw&x6AVMbwO$Sr95zf$xVQ?Z z*tYz{Rd^iyL=;9y>HJHRS3gezNF7{^AjaI$3A`306QFPGW3Bj#6(nupWA~}VEt%Nn zc5W9GZR9cqMH{%d&IHP~k|(Yc+2AGOJlxK0$f-f102s=7VZl=zdsPQQ9ysVmcCt*$ z7@j$~TN{uS|8Txb1A$05!|%zN9tkN8Zb`%r^1XdhRzI@X6cbN`&VqynJaOcWG}Bk6 zaLx{1n$qss#9Nlh59b3gni3g5#(`ITI}!Ru-^L8u;m7jvxOlOB-6)#Q-;ISr03|B5 z<5%bNc0k3cxC}y4LWjyPND`4R1f*HSiVB&F$)QFy1xPWEk%nO1ayq11=lA#B_6}3&G3H z{>xx^eca9M?h>gINIjBc)ji{XR38*l)x`M9y&$DnTt>7w5- zyqG?22OmF#GwK$8%}ThB5udiG$JUoQWi49k{n$WX^UEhXI{8KJgdNLeCf_D;4ryq#E@i8)-TG57=ws3s+Bj0o>@nc6e_51$e zxe3mnV?)H{LpwR4JEg`>h4JG?mUaL=#We)4AJ%=yIb&FK)pk`nIKXPe*Y6jNPrwR4 zgPcV)?1MD4Kh7_%FEc^-408Kt0`z0R^L?!up&A;$jS+2VN1hJ)CE@o`Ixr9x}xIch*oz&XOnfwM2qxCIGh&!X1TU)?cXV8WEumVjS zX<`FpYDr)k-m7BRMWffN3Rh-?v!GQ*HdE{Lr{b4ze0JvF*9_|NMqooW90K!5)!Ghj z_zpS<27GQmyx~QD^mXBNHLKerM1R2bQaF0}PS_@7No-^$uF9(DXc^aN!G`Dwu*^x< z0ZpHa$o3V}XREItL?*^5&$cVK z8+E$%3st6i?M9vP@G2ebnbY|L)5FB0#Dml~9=~O0O`6Qx0Xb3uds%fdS$YXxzQ--g z0Lg7a!mV`~U%D`qI@Gq<9>}JC2ioS{6iLx$Oo77!WaP`*u~n%~4B~y-{6(a~;J8Qx z(u$s)aAK;*3eQfsczB6TM07RN?KFadEK4mE6bnvNkR_Z01zF-bP>?0w(J6>HoPL<2 z<;$RuE%edA3eAXDtuHDXF%Y|+O(AZI0guNHnS@|#)B+NRZRnZ}W=+KG(J|qK2ptoy zk_A~?xp}<~;ds!trRTt*NNcsTg9FEki4RfLOT#MLGRQ-#SxvaDSvm`jvEV9J7-Tj{ z+{CijY;7w!)#FHF^R%SrM4MJ@I?$qkmW5LjR*gq3FzQ1sFg6RVfHn%fpQxpA^kjsy z(~}WSXcaoW8{vdj5fhFNVTi|BDU8V>vsH{Zs)Akr2LjwbkWxI`T8K_!=c3I1RQmkxa% z{^5RSf-{&V9vnA~Eu2Hs*y1}hjV*p+V@~z_Inhgr67mK-`faOWa*-lACqeQAwBiA_ z@apyl?-k^FW`@c0fd;(d5$t>fD<{RN8Xq;<&dDS=3sH+-Hz(Jf_+W2kPuty;Fn^e-?D$ED*its0k(!!&DLIx*6& z;7#%URJiXSUfqBfN~GFQph41`WkOc2ce>(%<`|P7v5D&m=-VaF_QvB+opY(DMdHm+%5-qbvY3e@EH?`Mfk4K7xzQTldb!b(1-@WtS(xxLBo-T1 z5rv+>Oc%->$qjG`$qn$8yU1s)kYdCuaS;N~a z$QnK&zZZp5HKfsnwc1pO8gbWaRk#@IwR#x(riw*PkU$~E*V#$MWlF}6Mu>J)%QIl5 z3&w@?B)f1ws#Xz*!J=CvdrEmD zi&0*Q^ThV$rDOoh%ZmfvFkHk-Tdwh=juL~+9+93xW^nKi7*%r=1D~8?jB-NUz8uK+ zRVzy7HL73ufS$j9pgn5i3)gxh@IUJZ6XL+bQwtuPNKzkIhy!1*gdywX2p-<&2?rV! z2@W!Nn_z0)q=cnu3Y;UvhYEtBK$F*_cw~NYG=E1Sb#N7(t_|=An+vo@v^ZSKFbgo@ z-3FCTsxvw|FzxvU?H!20RiSE&2f;W$!_-m%8T^`;!+T`_LGEzSgb~Vz@=}J*@j#9s zlQfP#EWq^oU;wGk=!jr~NSv<}cs~w1p5ZwSO#OQ@p5>Vu3DRlnMtZ}$yQ+6yL4FJ>QvQCMw4%t1(;snoZ&e7wg4093=`$D zBt;aZ%9kq0CxCMks&FY)Ue7cEK8wHf<3Z< zz!cc?>74@mEa+YfzNs@T;U_Vt!E=Ij65hdzX~&~mgIC8ArW&6v<9Ax3v>oxAFxB|{ z2Kjo*w8WGVk6)E;Vq~rDrfV`sp#(dB(9und(U|gA;~PpF*n!x8o30ygxk-$uo&3C= z5q^)ZrN!4n4P7OHS#`op$KxADpG`3At4%%H)*PpOaQa6acDf+>Ui{ zDaZe%J~;iO;60{3i2of0(RlhGbm5RJ+!&Uq?^*Jz#S20?QJS{)BO{)gkOJxA+PeCq zA#3dy0^MmxN!U^k0^QoW#Mq5T5BN-sAXX~;#G_lAAk}#ErX@(wo*wnje`UNt?D8-A zyFf3|UN@e+2@W^3@pCd^icBBGu_Pa#rlgQ^{+!+*pSvz4*);(EL;ZyRE~R&O<@=i| zf*xphWnTPxp19Bk-%A72=&Cy0o96^RE8uHv@Dr3`SoL97X$L}ZlYN<@z&l;|_jtDt z?(!>xEJaw!4`-E@;Y#Tp?G0|Y-+a1yS0Cn;!;N-_>tg{@xI867`NZ1`Vu3TdIdDHW zeqI=0$($SCr`2EaDGhLi4{V&QmGB+jz@o%BQ|IC0#{Q({KqEc&uYHvicyPBlQV;K8 zO1K#2?G4^AoC1Iw$SJGz7EhoJZ#I==Q?9e>18}BmN^`bCE^fzL8m!C6yrgm`T z{x1h`c29f?i-YWnu)+#~Ik1oZbBVVJu2+C7{zGm!{2;t82CvXk5gEQ9fWE-UvA)y+ zpUhE>eCj{P;8PDM@To5_@~JO%z^B0r=$Z&~OVu}MdQ58vLIFz}7Uz`0D?+@T2<1>9 zrVaT^P7?QSm#&3UM8`V2mF1*QycL;VpKXRJ_9Kl54Z3A1NpJ~{)ghL zb|i8z>p|Gigcjvw9*-9Tx&4aD|C#*>{{K(`)X;Yv zKZZtvG3g`W!d~b2<2n9#asMa29E^OxFDeQ8G4b<1_Ynw$X%gs;DUAM!qajrw`5RQD z`!jtc4<)4d{|6bez3#dI>|i#?B7D}#r2Ec=(e+i#Ps!Yo^_Kciuwe%qmVPWND*8w9 zlHmW(^+>pY{xh6JW1&I5SaG!IANCg*|9>byw__3JBc7Z@LuB=q8lNB)uz?8sRQ}KW zp$Oyu5A`8P!KLL9fv`rTWkArCa2Vc_aQ82Oz%)&4m-J7qJ(9n*7(@TWF-Gt|Oo0Sc z?bQPI1`>hd_9M8^{ou`}U`ELkfJIJ>0*_+`rjZ#F4An@VBH`70ZPZGIA^7-S)mmj5 zjDNI4)_im;0a0!jiA>Py-A#%%4QmL%fNEMjy!2ZGRl=wf)2&g|@iSju<#7Ych3kWz zjQUgEPAepLbvUR{kWU-e!DIvG)zDeNnlP1y44Z4c@RTT-XyOZ~>BcReQVVy{2BeU; zu08@%Pd@YwTO@ zuJ*w|7v7k|GI#<|0>i;T5YN%!FaVQ=P|^BA`lsfn7RN(CEslQG%Mi0oF_cW zOH1KTPre_n$|2`%*hG(gC!3jIo~}+)4bb}U92#`O3QGMRcxtykKsv=-ND`FSE2Y?U zq&7S}A@`bFK{I}se#-+DK~U3Z*k_>ltCbKLgYiA%a9IUoJ6?HaHhFe2=xfewE)gwy zF11YC_=E16DJA7;Qb{?EE9qJ#=0bjoL(Lda-#Z;Au{vgU>c1wSm9Gd^21#rR^#)+umy82>O* zwjgDc#qw-`$QP;Pr}WQY|i(4lpdlCtLvJ5| z;{|X@63hJm(q5RJ>OjHAh(D4HRf~; zJlU5Eyv3xWV>3p54)`ssVBD1|*~N7KJR$ALVSc-nAE%>Jrfx8NOglOqeP!goPDbD>^5-oFdj&iEc3$8YS* z1@1t8dpJF#_HYjPWm*u7qf?#mU0}|PBywafwe|}$oCAJAFJ?SDzhIF$4g_uP+dkrnPTE#euAPQe-1Io?yXe=#06O z*##x_yTF1ONnqT9qyrvR=TqyW{$u_w@L?_#`7oC{;DZIDlEMnH8`s8}h5~J*Yloo! zuEA?Az^Wu41pdX(g3M|^h)jd+hn(YnLg9^(Nm4B?|+@Hs*l7y?jrX^ zk~){cOH{bC4)d8gn?c$657SfPwc6;1(epJXPBE5$|7-MA{*K;)vH3iDx_lp2>Yyq= z*NdfptH}|~zezqtoqV!`5wJ>7gLhJC@UWi>vYinxEE_*hA4RHBur%JU`3sdl~)PDV3I7Kc?$!V~!uaD$U3x++e6v^`cTzGk; zAC4_ySF)7is`mO*`bh9QwNGCdf5GYpC=3poRN-ASt}xC6MqDY$$>_p~W}VC{jNGr`)HT)pTIu0?W5>@QdE-n}JUj(>XflBh}GAH{G7lON}W zFcGN^PPqB|xg^Oo5LX7;0q`CJ`VCMd;ddMH6Flkz3P?+*u*{{e-fD!htP1&Ff^R3X3pUO%48_1EQbk;Ku~V5JslZ{RlX<1??3te@uf@KT=I& zC`8^748f~nFzVw&D4>&-2lZv;8LOjTI2`a^6lhY)1HcjOOkkZQ5QJ#N@9V)XT|a z20IG*k{Q=Cp-Fa-K^rf@?Z-V=_}!~iRDgY06g#$SO>#9 z;BfL%B}*?YuqcngGbEBBKaFi@fydPiuZwCKG7Gp&0I=7-Rt49q4gWx2&lq6is zQ34?gw{^NCT@ngh4zVi_&L4XrK0tRA$b^<_#D_Kquac5*QUh0OMMCi0dL#@avc?tV zkmU;UW`M9tPa~o$*XsjLvPqmIX?B4UxzULUO#KdmL0KeN!~3q8!kH z*HR2}skMNvU^gr;hx`ZGRuFE0ewcjB%H!LcSaWB0p8!3r$f;i$Na4W1TeOazvxuQX z(Si{T61qYhI^stLK!MOhdrY|NFC4-BT{V6^SgyPuE zOaN54t%mhG@j?Nzp?DA%YJjAcvVA?PvA%#Rvg$`J?@TK(Np7<}AgWo>v$n4TdJxU5=-Edx3wYIj*fC<2^XR4WMdrsx6-0mp@NI?f%(yCk zV_h8^kdT2fwyhyXu!;3}B2`HbsCL>fI58xPPD^H2s?uhBQfZG#}@ z@r+1U2$K3>$N7)rylhMit<}P7**JpKr_!-@+E{e>ctlUf+F4`K;o}j3j_Sm!lR{nw zgu!z>pIzbgu`Z4rh}DD&V;_nui)FXV zL3F+pWbkjBlT~!5`8*@!S%&(zzUh&ShHeL zq_QGn#^FOHW6uYVcbE@D@D*Bo+z@Ob292Zo0#yjmlU5!}inwrGn>^O)E9ADdE+y=M zKE=}5%uxHrg(plg$H7j==095JZR~WJ-&nw@ov5xUnzBJ8u3FWj;)_jF?1TmR(KE)px%k65UpK@Jm~6Xs&#*)auGg8!Pb=de{JUvA+(#nQc$k-GG^hSpug<0fXn@BS(CbKH6HAEQ) zysQ8$ZIXpr2N(oi)k=mx;;my#AJ~)P6VT%`TRK3s#EV@Wpr}i(>UoqbSX0H4Lt)PJ zpbw56ifbz|R%V%~2x6 zqXG?zbUcO7rQkEv;@?^LWE8xuSKOO)`YK#0J~@Hk+|xm`J~|8TH`W(8d1(~nhpxUb ztMVUpYy`hJCkwA`YXpn)4Iijnc)5?tM(xJyr_!;~oUGn}&ige&KMNPxn4>@_s=e2OxKaN}R>!P!`1Vb5Eo`jNF+ z?H&pfgo6nk)}aY6{`^(Xo(M0F_sHB`044KRLv`vr(oPOM8gmjA$CXgcsFf5JF;$Hb z=FP%4a=IJaM))S^lqMd8GG}b4)bd7KREt0A>R|fUVL7OXxW2$UTqdD60^Z<I~Y z7^A7PjYyadP5G%@`;oLAK3uQBPV`#iLYyg8$nCAxaR$n>p}bCIN%7$QLOBlRBQ|*Y ztw~gR(l6P-TSY%xc({Hb?mWq=9>BBa3lEU+R=%;qR!O`|7A_d_;F~`z_E7S6K!Qv@ z?4BZNdSeAiKsKWEBizEVXuJiO?scL2y1JEJ!iy0& zd+m|v{#Lw506`sRnJIDLkoai=JQ4V#5yrA232p$OKe6f3D_Pj4dA#F$jJqaK4X5{| zBtKmCGHJ429)Z6`zjzHyw&IJpEBllGkStvx+|#2sxRg{rl5gn|=oo?ET9Vv+tj+RJ zqk`>^Z_ogS_+^E#iI(tW6dTil>!M*-at+?0B-OfMuQS}Gz~BV?;+?-fg{psOD5&o)$VUB3a(f^A25m%B~X#lv$dZag|f-KIxhz*LU(1Y>V!daACi3+q&D zQk8XmBA#QBiKE4*=kz@t@K2bhd25b>e;wYxz`u@O0{qw1#b(~l-Wr}`<}LNE(XnP4 z>bE=xLqkIiM3IP+;2B2s+z^~EA^|4;`62;KyRllOGnCPR4fNsxDSf%G!B~x2o&s#} zVvW3ZzG(=i?`trS*UCx#Q-d+-^I}{2Q-ikjlc9Q6jL|vYsVRgn+z^dblXk}8QU+r7 zH89QsFHcQY>F8Kc%Sk=N!7b|{l?i;2`UG(6`Urq!eJMnrHFd_yI6o=aUc8tS7waeX zs;Scv7t1j|^lPNt3V#6CmmWf`AD3(Fs}$67Y%f;5S}4|!%b7dja=Z2gCRZZ9L~pgX zr$<>%oa7)l8ip@`$0x*w!Y_Is{9yVo;Gh4%xfinTQcACb&kZS4X=<_t248qtfNfA| zB)7%GAVrzlsQ7D|>4=SBtD`7bDuF=;oEL*J8NYVklD;@kmvEIXmkP&E)Y$M5qU8xQ zVo6`eM_DOe|K)IaKBn!|F(?C~24n^JZXGyPr_{I`!!U$n2hk)#Mb(R-6+Rr+)l&hJ zWlN?1JM|QnjVmRY(Em$`Kw_buezu7gUfzK-p9fHK+rQ zf;%XR3L?m;L{V@97gP{bqC|0x`$`mJq9pN+zKIFn^E+MLGu=Hiz|6fS@BgFVoT{g* z`l-{Ws;jH3`}PW$E&EP!g5J4(o|7y6V^@o_U?Xc)h|S*ayNkyi_9L; z>MtM1`_WLfoCX{T)nLu>uHhuDN=?0s3s5US9$v*z@ghWR=%nuE4QQfppj=457IXw zD>f42qRXiK^s2hY2cD~h-|^w)#3*eYHaHns2(m?HI$-%GTk4H(sxWkEAdTm3;@~1v zot;sS)6yQ#Sadp#+E2|pTJQ8~-7ffpDP7CU+q0nXu~|jO)o75Yg16!|FZ3siPGP+p zy}*P=7mjm%c!=tl!fkxWNcE@hUq_7?a-OYmReJBbvGU2aoodQ&@;$c9j6!rj(CJJJ zP2SE#_i^ppm1kFaztGY4x0{jZ;~lCc;@97MEx#cuFA?RpuaI4#f+$~qPd2(?Dc`-E zzg>AYyNCPfzEK$?E;vu`;*8N7;}v!_AB5%F9}p`RSrFwP9P-2uG0NZH?QhxT-zU-E zi!C0ak1hnOoFh^qH=pP zF^j9*NB&Ln>pKk>Trhf6k$$#&{_wNLv3#w!VbbNg4I6&E{rQ@TB`Yac2_JN+dMo8q z%}M)ujk>T>%1X*rN@QhzVK*=6hXL%QmHh;+TjkJU$$MvXloakwe_dUT7GNSf6n zmRpG(U5Ag-UlbcOqGCB3AW2kNIgTw^PX0E!k0RRD)=fjF@7fD=^%8` zNu}EqJ>d$TsD#^(8#LyHp!q zp>@)6x);z35anC#HKvnK(W^YcE)}xu>u(bK?wIz){h6%R47$&N6&jRQ<Et==Y`j30#EBMGpylAZY#7-MM%nDTQ z1deZs-CR_f6*OkWdPP+AyAE-VQ{s2*;fvfA8>^CWMVn%FC5pSez_3JKm4+qCRbiMK z+}RVPVrIlPMSZ+#Zv^G5sOk7YT)q1Y(H|Mm7p!b#jctk5tn)BlzKX*P3Uw(Oa$!Zv z04E457Oj@8&uIH2q!kIHO$km$Zf9q>Q?=CnhUib-=uLXQVS)$B_pg`}uO#5Fm|sN| z#unOBvAph5f*1cA_tUsh>2s3{#sn8W3Xa@=GoMTb6<5j&tu8h-p{2=sJQWb`Y>9LELm580JJ}4-dDHpzJwp z=q=G5Q4luFd^h9UDtNe~ZMs+W|#^{p&~N*8Zs}{$^IU6p2P&CNo3gUnqtBa}{SMN@4$E01Epb zR_s0NfN)@cyA*#b%abBO8|&*78Gncr{MNo>l-R`fT{AN2pX~CFN%7~)I141WH@{Ef zUnPb84@;~^VgHK&6#iK*@m_U6QQ)5jDgK!McT=&+68~%|`01>f^Ca-!3qZj?SK>Y7 zfFjrbM{I5B^8R07Sc=37Nq@DO-%$$wL5ee7q~NE~O~HSjfOnY`z=HMv6o1TqI`?xD z|05~*w}En<1pd7MC@6oW*lXc{uKO7x$104Vy;>k{u{2NVVUr(cSHZ_AS+K^-$B{)JNT z&y{2*O5k4%K*7(*;XUhsBG>pf{J)p@i>0vtGl_K~?B}3NVe(%j-oK?{_BV+5|5{R#@rNltb3BSb zVh2fE{BV|p)4{^6(tRa|DaZdw3F_`evAR;!eFgwUb*Cuy&UQdJXq)M0Cw<5C%>oJd znUwgCO2Pl7Bts8{|DOO9{2Zv~C;2<1_;~_mfy5z_%Ow6RDfm}QtT)5|82|_oAQwvf=~D1>vB=ydf!`*2DEQx!cwuEq2mE7G{IP3KKNTA-@voPH z|0zl4HVOQ%0Z{P2Eb-_k01NcpT_XN}L~S4Iy;rH|V0qPFq68h`Ac49sD$y#;;d)Bl zeHCYRk)rOA02I~H2fT3(2nWYjC*xg8dTMd#!xbeNzYCif6D^RSRyskZr=$}J!sQZj zW&m!GG$KLRFf-!m7;Fh{6(~)?3_a@r5(IuaP0m!nWJv`ccb}26#>~bV#KMM|Z{R=r zYo=+iDM&zl2OFq#UkQzq883-)?i9dF4@$gyq<$f>z%g<>voe0ec1rCe8OkR5Ze-Pl znbST0MtN6DdME;koh5cY7Aw&}(wZ{(iy^5cV11frL5_l6V|q>ey?$|3WoO z%TvU>y@ba0P}KR5BvT@ZdWjA~VZ+QjCTgd^qu6AD1WneMLN`dD*cWiCgq(LJKJ7>U zw_;B#!J+O!u{R}KNo=1Flz`w567P>v5%qy8OHOme9uvk29)TtZ)wOvaiiK_74lq+f zo3{eIB*7>u%Df?=Hp}^|612JPvxBmuHs_?L&ATb~7|8)W0cS};U}01HEBDzgviukpD(E+q4#@Z zr3xe#LYuej^Yi(wV*4+p71~H>{{a$f|AtCf=r2txTCp>gp#6tSJO)Dgi-nD~|5++U z`!m3MoEw|a{#*75W_;QA8|}|!mbTWKv>@`-=4Edce2ioL-u#|X}K zIpfIrNQz23K>ee@&Wg?IgSDl;z!{3Y?o!PJEW~{n%_+Kv`6Qs}F5_(dkU2#G;Ccze zY%{=f60R?{48p7vb1jGK9M>1yr>@FI*B7ohTv85I>>cYsA>dRg2rOJ~IzAs7!M3iF z&Xy^)VdnCQB_+BavLS+iO;0Jxx~UeX&h|D9#DdP9+!Aqx!RwD;1^O5 za53}`kYXRp!EE-yGv06qmMgXuEB3ifz`t3FeJp3DD|U}!Z=O_pvs3`;wTk^uq}a!DzH-I>qSz}?&x2qC`?M2a zpA!`O$4Ie{<@9&O1}XM#aKK~7TLswXF~$BuDfY3PC9c?V#a{2cpxAlrbD97>_)@?q z0eWx;?dV_Ta;A|p&FymnIdfdjJ>)#(dC$JGX$`4E;)5|vxVQD zWXrA&v;j01z?>5l`|TaDV#f$*-;5ROFF^4@ioI1*u)V;R-wWX0*8zWYpjZv;|H~CS zlAQY5!ak+s>>)*^wogk3jO|PTDz%)UE@zx#?{b%8Y?B3Gokq?*QuN*-Y}r~0>V}HF z_6~Ff9Ob}i0Ga}G_5nL>V+Z2ku`f#WCIkxL=AKyVJrI(FKGpxQ`V;KTnEc4Ai#T z&oNqHpeBa}d3lWdAZ(cVnCJh-FqZ}kB(|62X&lL1=R{E^v%3n?3O8ybGf#4lW-|(I z*yamZ_=5nw@evjC{~-X|azFzqa-IU5A%#)jE6#99XR*k_4=#r>)%!{6mtjua_E#sC zWc(F&|BMqJS5`s-i;>a$|!D#1QMqg|>g_A&chtQ@0O z?B5|p&WGgO?Q&L;vqFk`Eaw$h>}|!~7cR%xzHvGKL(bk}f$dY+T1rumO%B6}p5??Q zdL!LFpR*4WJodRwv467^7FfT7)?n__)jf4v9 zG{ebzNG!Z5O>I^zwuZrp2SIBHekA}wY^G#6v6)gg6&s>r7UnyGHV`aw1n(;L-;|YQj}CU%vATn2GLbn|01GZs>|Y=Sf#v+#6}wWgH^YJb+2<|+_F1LaUm?Xl zmh*}$_O@a#oW|JH`a5l5pWQ{^?<~bWma~Td4r#8~YbKR1U?JWXLu>+9K9Vt{vELNR zGAD$7n8YuXY9_HTSem-5Sn4D$2nr?di`+Ot<|O&g0*QJ`U7{n&Tq8hzPbl_pmx}Jg znLbjV6=V9yJ})Tt{^r1C5d2fX3>NTzB}K6b1cLbzf1VWkSk41Vun&fNk4iO_Sa@5SoMsSw zB!QrwfbUB|U^&|fQ0$kAy`80^Zd2`XjZn$xxNwCXR}&>HoG48dR_t^osA8zZo8-VK z2(FP}78h$<4rL$9c~A-VVZ!5aaOX=bgg4A%AP8@mx%Kl;l!Cx=P8YCB6?;RZqGpXB zmpi9y$^}0O_G7%yERbN|T8aO(RCHo~B0ybM>?@{5wGik61UdK#r;r8 z=GPMS)yy1;J>e)&!a_Jl+p?O&_Kyj0smViUYFQBtgp5( zHMX301?UUh!P> zSL|`c-lI}Q%CQjd>u!v-UQRX%=$Wo(j*vjl0B;XPql+W=xzuIF#wo!*jEgo>wp7AG zysigIwmi@IZ}x%f3(LdSBSE{iQa8=~RzhFk7Sd-z7M)c85}+?wF)q67vx8!frPlTW z7OLnAjOcdXI9v(!Q+p`uF%>S5SV&!Ox9k&K33KQ-=HhMTeO&!lHKWamXpvZ&`i89#&~d!V*hBV=pJ*30R6#=jTB&?@ru2h9C#7}mSZ4zO0oaA z6#G~X4{+Fr2Wz%C0I+o^HIQHRg(%VyN zldq9yX)oEf<(cnAWpV%Jvs&^zQlETIp513=MjzLePShhlxG!J%Ovs&y8neG$ia)x9kl0x=SyJQQ_pCW6>7VTK zk4f>jk+~K~;04;)FP6eTH%aVm4|47Upm53E5_^Oi^G|~mzfF%-)&}d^_y0~w|74fH zUy45(GDvW&2S|K66aJw?*_&4QXDR?iv1=vXZ4M|3`sezNN&8z_o)n2jl3o&js1*D+ zOYF`c{`mkD{H$`Pe;+wD#UCwKlQ=kfhm6_ZAjN-}%32`NL~^FYzf=l-mhtV1 z13wcNQ&2MWdtAU`{EIpy{gYk(ekuM_WP$|}lO?qppVuFdw0}^*u6JORpu7B4;^STX z!zgdBeB+;!0VrH@ykc*l1Hx|nPx1G$JSh@%%SR;s8Y%dFC_j+EzdHa0KaYC6#tsMv z{OwZwCt98qiT;v@B>rkC_%}%G7e(;%dk$0Z+uBT{B>$TIlm5x>^><8)KQ_M2mSH^f z^S6=0e!C=MH}>~ZYzq7BR|>~!3%~;BZ-X53FXCM)h4T4ZcJ1$%WB&E+a%=z8>iKCa z&fyx*PuivUWBnig*!cEdGupieY8$RnR z@g`ud6n*t0i9L~|uiBKKp=2w`A0^%oQV>|6uNFsjg>&_`VK#}~Bt0dOucniGo&^5i z08mhJVe=k#Kv8fkc`5!wEKiCAb(|~lua?68Cnfg268x_MQ1Jg&;=SX5qJY1wP3Wu8 zex@cCNVJp;kocEK!GFKRK3D+%a{v_lPf5I&98eVS7p3^yS)LS$JtfCT{E<@d-zl+Q zk-*O=YYKj@avqfcEa3mO`z7s)iUMIK`h`|Kz3k3w4MV3Z-l9pR!g-{~QvqXX*SZO7Um+{93zj z(*Eq8U!^JjW}$ybFjwy-sqwu=eu{suke|eklJ@X3NN|2}<6&`uE0p zDgMqXXMx1Nl5-^f6e;Yt@&9He;NO(-zaZ(Ky|tqS5^W{@B>pHV`0tQdBf!59fWm&R z4Biq46uI#~#UJbcO;rpZ**n_s&y!?IB=A2WF$Mpl5^tFlz=DncEtB?Vw|z@f{4xK~ z|CwCZ_+B+X#osD8xZqx%y9#@qtc51Y_~!)0_8bEL@TP((Tv9CHohKFZ&&n1_|HS$y zr_*eSzf=nTbrSnUD*XQfprHIw68-*0>7ad!bIhNY;%8W}K;lTrP>H>kj{Vjprz(N{ zcCw+c|2c`rEmh1vW&FrOq5NJT*1j#3HACWGCB9O$59)QZf72<|pmXE`Mo``SVl!+4cX*W=a3V_*=`d zwQAqu6n`sGS|9=cDH4CY6z$8DIF(_Dsic1!E2p)@wvFlfHil0lO z1rlu~BP9McQt;c9=N2X4e@$Wv%9kbS?_uSo_+!`i=GtkX#J(Q@|3ea63V?qF00sZE z67LlU6b18>vR@_r6T3g<{_Ye>G=H1Nj^X@m6Yfuoa?HPW_iF9O|4d2zTcz-iHR)U> z@XtGvjXbL@t)8FpVSuE@>%;je{*y3@K;mM_>k|JvDeUKkyUaf%*uOsjMf>ij*gM1l z;h=q2?w0h=F_tGqVvvMY=IB`-v%u(C9*^(r86N!G!@oz8zc|H@%Po+g|I}*y$xHD! z4cLPpp<&o~iI21J&qRqG75=#ufT9@FcyG1?id_HaivtSf_rLoXmLkE9Ol_m*7qCRn zFYX1PC^lE(aryx)us&0?asFD_zxJ0KwZB&Ur8(x$Pw|fl-A4jnULx^tlcIfBNo_myuMnTWB$Ape{6iN_4re^bJ9QU)R`L%&mWlQ~ZUY{UmDbpS%=* zcKuViLGxmlNm)4pM^5)zd>T(zQKNm98=goTjD(+1+ZZ6KUB}p z{Je)GdKc9AM{CO_^5<30Uu*lzeo?J|YJLA$lwb@~X^NjItpyURB`o|#@7nP_ zz35##Uazxv?C3Im1-#RwV*MvS#UC5r4$+RaoBwQ=^iOvAi&Olu^Q+eVCojkRW!onG zlU@6Za?HPWo7~!8n&M|UzygWWC9DGY??^Fz^3G%CGYR9{UH}wbo^SAYJkg8y|LXZq z(2ffw{_mvVw?`sO_2J)ycZpZlOZuk=ECdp3C45oZ?;{0&iDG*_7XDiRC`_KJ*t^pK z;o$mJoZ{!U*8+(ylJh10R4MqMmDuNx;eQi=g8x;C$3-&cpS&FNm(@-Bhg%8@Bo3Bv zY4l5^u%8=Tdo3UPR{&6$%#o$nmx@yS3<(xUFuo0w_*Y56zgS|o>hQk;K*9f_#0#CJ zbm0HBTPN+$E`Mo?Ki0mx$&8+oXnkNhhMgzD{@(yl*k2||zu%aj;*a^C`%`@6pDcxc zN+tFNHvG2(Q1DZ;H`f70?*4bHq<^x@U!3A^WpzuDI976*B>H;>ta94VU$FlZ01Erx zm!#hh%uDez<*`7bK+;FzpDTrbW=QNW9KgQ-fP()%iT9WTiUR+Xo!t$g@w2_=7 z@yANRf49VbVFmvZ01AGV(Y)szP!#YNrTAn0lbeAvBsE@tUh60A--D_NBu_{en&R(lc~T@8o4=Fz{6rZ46e_mY|KUFofP$Z&nR%x> zARO@Lr}$&nU*=fcW7PQlqLp=${)zejP;n23f3g(zTa(_T1op3%Y~=ZMagO=(Qv5Oh za4>Xv|3WG3pDWp%_jAg;q<^x@UzB71wVB-7Uz%h7{2cSI+^`irqQd%icH6f&$NYIY z<}dqqZv9h~WB#@O%B}sS5&w#kjNgT>#GKFqi5(<7&a_!jClZ9qCFC$P*^){l5`^6U zEgY+}r>bJ!Y9ab&K*w+k3G5rn2Cgp$k|0E7Q%AXwYi5m-2UY!zu-{@k-mzO$ zK(_k((~!Z;YLJC{r194?Anv&;^H*xrWH0mkQ6vaAO5nKJf2mM(vPBgWrWnOw(e{QI5K6n{gjPl`kn3CB?5-%sVI_*;t30txyy^BBKGiu=3G zU4L8ow`%=UYyTHl&tGf*=cV{#_wU{0l9wd@z0N_3*WS74v$NX!*&8_{VDgM~_&_ljw zY1n7Ukp6EoL|TOY|9goky8Igw?-Qwb|Iac1%0DOlv#(6BK;jz7VoCI^9-Ap+CH6P; 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%0D&xMz$zj+VSG-*2&}qoT9{y+ z+Au|VwWYW1Y}Q8z`kQ_g3k$s9XvH`$JbwAiPy$2n6iLICE-%rM4c*lu1qYg>vGq`9 z+{9Zg42yA)znydwb6$(1<(ic_utN>&)8jVckZ#8eT{oj0tlsCa>n&t#oF~KB%xA86 z!-$2__|ro#>+0IYwoF?>OAovS1!;JVlp->Ys|l`X@f7B0aR4OR`8;=|Ep`F}H*H$* zFYKV6rD1#m3JHwFaE+dGf|uT-9HSGFV;fqbwr(8a;<(SL96-8a)V^Y?cD1lH&wU=| zHEexlf89(Q;vT)R0Z*AR=<~!uCl;6T&i6Bm6UH_04mEKU@BVjaO+S5a_|QhwYj`r` zBE}OfrY=5cHGb^a^{S1In4+H8G?t|ZYq4~RvzsWKb=d?l_*lrtbVjd{xwBEDk*TC3 zv`w#y4lS`njD|; zNN$KU^;ZpBa!RRy<=Fd((}7SyW?Mr2ma3-F>$f$h$Sva2D`FuhMh~ii6D)ab-uw4R$~;SJt2A))G}m=jKf_1M_fQ$017s2(Az(JTBqA_J}3 z|Gagxq*2GZS+-}KGL<5$YySSmHC+O|YSbg;->AJLFUgbT*QFJ%<7z14)p!D#ZGpJW>=+I<8@f?%vc3XnoLx$+mKKcq=qF zvXNKLCd#_lBf?JEg~?p>LBxZK-oX;knLO(`bf`$@1YP{=5nF7;atq{i|G=z;=U`GZ!D1-W4$RN|hF1?O(+4j6|oXaLU+vhA|lD3Dj zm)2UvH@4*PT|GSb1tFGF9H!D!fQ81_maXKiFbK7Xl!>j7@+bt+ zyNc2#+mdDtg-oy==*o1_Lm@oTi8hG?C588nA9COo)`@1+YZ~y{+wjAL_mNIE9io~M z0qSFC*szQhIZ8oLQ%)eRpk-!BEV_z9Q0T_wSZeBvR?U>dK(dna%%I>r6+^CRL{>Ty zuUZ3^QwQ+@6(f9suF-2K3^pCPWK?~MU4=x8Bx5;Z!^HT2SrjR{KzZTP+5O3LG~4{~ zT#s3fdF#IG2>0B+R*$mw`HP(VNJ6myVQR+^4H_s-tNBo(Rp>(6j`r4(ED@#c#6}dc z1yJqG2=W0eStRgAn#9#yCqFvk5zjNd~lPNDo3buYRwcqhzHq*&g&9%vcJ3L=- z&NX@m#-()^vJhX14G_3;$0~M^LVb7~xmHteFU3G2YXdu6aKH*bT#?=~X^qtyD#Aaj zu~Re87G&?f*A~?@kOJ?(I9mUMYYRQ!-8?gJz^}dm`NtGN!w&(flWU@lD_Wp@0)DU}-6oXG7my>6imTi>g^hysLP92{9(u}t zuo!LTET(;UW_qQME<%B0Tk>On!cipzs=GC~W}28gH!cH}Ji84#v-&srwZ08nEk9r- zW+kU_K$EraU9waSN{J5*O;qWLh9+GHEIZ6!#(-s7mDkspTkzK{X`(~Gd_~12G*eRN zf01L892s;TfDGj3i}m6?!!K!St-O~sNp))-I_u^`BuQmmk@v2Z`%Cn};np&Ae>Fq0 z@p*1KNJoC**c4HquUncVr*%HN=d=q|;(UrwD1PZjC;1xr*Bg$IVl;oi)#D;v8?Mqd za-FWx{#j^z{IpB-u%b=0Y?oP$NWI#v?R?b=8$Mr-J)}Obi+cXF`v1lyWH$^Y{sdp_ z7LMcg6!pR4Llr)oV{|#vB(R!=la?TKiacb9CqeH&VU5iPv03^mHKM3|G--b}FNdn1 z+$ri!*>}WlC@6}qx6x@wX`WBs99>S9+urUakG`;H^1{yp)D2 zN?EsH8dwf|SU{#h^Ezr+t>t^#THBY+%?NI&tT#3DkJiEq*c*odWBHu+KbmIqN=|ws z`vk{`wDGY|*V%A>@0{825KCKj-YUc0Z1sTXLL^1e8vns&|vrIvQw5tO!OgGs5zZbaXv4wYm}FJS^`RBUV3OGa#h zilx6)a%>&W!AknNrfl2RntmZ-PN{$QG=i(NP@g-DC}a?g5yoY|jR%9#K%1|x?OYBq%#`J!@hI|IKm$Twy z7(7857(_u^u$rxQ=({t>vImC{T5p2y(93O(JRF*ZOyf3N4`MW43k3p0wMB!!^=)aoTQJ zarOI%O87l4{v&j4Itp6&aD~ZX;0nnDtStSJZrH8eLvwobIauqbX3%#&qAZ=czy^96OSagj%jzOO!E~H6f^GR!&C0ja|x?)83 zlG|q6JiDos>3-CtiAop!h)NeH?=(q&$VvB~4JB_nZ4!~o)Xka4OKnV8v(Fa)wFYuy z$M=l8S3x-SpP2GaFY5{WV_7GW`$&>|^eorAb3{OPAL)&IQx2$*ymFj2W<7im(@0)X zsRHrl&d5d^J5O9R&&|fQW?440U+f=adk)xqu1b9BlpSBt7GJ!@$Dml>TkY6a^4P+I z_{`Rg^R`ZG_+^|&KYW0_8+&J~-SZqiWeFMbFVW(QGx<_WM}>5ddDV0q~<08SJYLHukVOHR;_kb|;kb?ZfsU`*aL~?nkU?<6`6Y zwW(mc-^w<5n}n?`SWdcgq^GYZP2D@wAmK2+Dvj?n;y<>f$$DgVK7YTfZ1N7{Q_;R6 zHs@N7>`K_`p>0iPjEYST?A1v)Afa};^_0a+4e_5BQCh`bTN^u<#yEbF*MZKl&)t)j z&+s2xzYX&x)6P45JqO?7ID1$u{W*9^v8mGwC(Gk5>SEmmxUvt&Vgudh4G`$m_55QC za%+ZwUmXN@%Y*EdBQ|`t7(1fyh0rBOZ1{pLK5mLHHJrL&dx%{a3-@lPd=@!^PMXzB zz^^J+f5awUX&R5FT)%?!aMoL-hk0zz5u4H_cDBjhi7i@?!Lqust4E+9JC8y)=Du&g z&%xF%$v|zqx+0^ygAH4ifsZ<{0q^rw+4m?qMK7zt;imTy|8#5(igevtB;&2u2cxkr zB9=Nh?1!?0X^++NJCoJX9;x`EgXQ9rOv89Oop6VuwwZQRA|=(aZT z$3!NqCKefiBOq5#$C}$Ko3e2d6%Rla&D;!#sTDPLu5$XhRqWhr#`$1FYTEM8W2~B_ zpCri!tw+TfW!-W#R%chvqs}5@?GLi`<4`rLNdopb2^g@;M{Qhf*y#`OW+UE2_r|X^ zE9vhDuFOtf)5FK2asIIAp5i{H7vJ~ZJSJEFLb26+HB?#_wi1PTy|RbKPENde6|oRr zO)SXzGj`>yi*4?w10>Y(5fs|sLug!om?HIW^U^f%a=b&C;N)yNs+LVUZi5dN;fV4B zU-4fDM1fiuf6gCAY?ug0kAvwW?CIk+;VFuHOB<=K9w(PnBXQWgjdQ!5__TajijHFA zR!n+f&|^;6_?Ljx$~1S+<3kD-Lx>MJijFJt(C;IkAx(28St7(F2Dm+=%)Qt84U;NT636K5GIAryvZ;# z)xqeG<-UsaUazBqHh~H{h4<+i<=2{{{O`Uwacawk;=2j-Eh?pf35%yZcP0UAI=7#K zWm49O2D*6K7HGK|Ehj^NdK!hR8IXfw)el5bGN{LcivlrSmHtY#MJ+ZQ8O1T2d8jIr zY`1-4!?~1n^r9Q*$|n7=h~8%m8Ir9&jFZHv)9Mf6Y$f+mS-t-t3nX4FTesw>^0CD`a^&aQaEc#d1ke==PrONt z(`YfNX&kX#Gf`Cweg2@6mElAu-YvxuoSf-|pD{?y?}T?HkBJtpbP3HOW}Bx_Lx_ES zHUXdUBKge3BCDmbFFak9RBT^S4z-rXk>>NVph9g^e<+}?DOM9_-=`55R5RJ&nV{W9 zjJ0g@1svLU?ktR>i_LZ~n=}AtG4T`1&Gw98Sqoc+f>+gvR{3n>ARK{Zn3BiHl-xvS z$tf~R2G$Y9S-7(HrEK>_TQPh4Jj}7^0GM^3*Jj|WEy_E=2`Ym!pY0n5gLZG2jI)<^ zV2ZGC3aqY)r%cB)u+`(*<0~{+9K{*u!F+a*zS2y!!q#0fse~v<0+ZiYlxXtMlMLcd zX0#Q0OFEHQ5^nY?of^r*5ZSp)INYB!R=jI&W8AQ3Q~KFWLK<`#LK;5~LfT7a^%*Xn z?rhU#7|yR=wRK~|KE|wU&g;BCil^Tp_wjM_g>)#|D0C!*M~zS^}>dhnNivS%A&j$6skxSFvJl9k@x#cF7@AnOAXNW;5)=TKM%QzH6_-)<_)W;I5?I5|6WE@7jwi9Nr+ic>!L=CD*XT z-S{Gg_SjsGSDmE;>VCT0@$WBg2)M@h)l+kb-Z0~P3+y+*q5O?&pz;=v*w@eOFaVDIP^&Nn1@YamjpaB^9 zo0f62cLf+|SIUMBgmA()GM(y>6Lxk$n_~7xV;k%Onm;fr_U(C*pG92c-ql@;J;|x~ z{vfn~>oc>1Y~~rm?X)+-g%)JTXr+{-Cfz^0F3e4V^&2FG28R!Sno#SBy73^U^a+?E z_w0Z=$Ud?PMek6`YG1PV#`o;(ZWbN@jXgXbx_f*|tcM7)>BE&Cn7`%DnTb1-fW1F2 z3*Yc#Z!W}F6KA9QWN$2ozAssqt(4`Xi_(bXA7jB8H==s(i+wha=c-k*5K8R$*nBo= zX8U5x(d2AlL%b6Y-_|>d#IGX3Qz9{jF@3z6GyXQi;F0VkFI&d%> zCw^Z%r=9qH=0px!-k!ET_UiifmE{T6@e^wSw;R=Rzz4KvE1jCTHXM$j)D?JhIJ%wY zb-ZtXCy6GckwZ2ae@@6O+NJ${MD_<}q; zye{z<=}n}Jo)GI8P_XC*i2ZA#0kVFh?D&YtXbAI-F&QOn>-Y?8ea1Juu$0f&qm==m zcSf9L>kNqI8}44+9OnO6{{^U~`O8sFuhgQNK3W>J#!|Y`u#-we27}3aDhgq?8qCEqY8ie0 z7{jXmGN#anXbN4khggi_OfxzY+3R!5H3u^bhGfM}t3b(qT~Z4}&}5bN3Yx2#jiNe^ zyK|t{&if&y3GDq^NT_)q=jR~t6ZiVyvD)h+#?tGENyQbhnkWRqr6J;DfM(>vtY117 z#|2ceRb#O>TZQi|;`P-Z(kqwvb;=3#VpP0%LcDl}UTnfc_~1^`brG&2$Ct+O97m4h zIle}2x?}v6+Eeu623*C93&e}d#EYxx#Y_B^Ss&4h`*Gzf#=Gb}HU-DC*u`S;v2j>B zUPi91fgjU5czT60VZ&nY3>=SlR`XTm)AWu&PN2K%>F!?B-34@aGu^#`yU6tnU1xnl z*Cu|w!mr`8lG^d{MD4Q}wM&%4j0wo0=3I>0kO{auhwd&h-Hp)Q1$6fi?q;$R{5q_e z{$7A9%PC`Z6Ytqt(EU$)0SbcxX*(#FdPWJ65o34hM))j zgH2yUyQI8~ht@q_RmKiav~!oY zIvg8a5v{MtH6S+S4KUw8thRNs^OGF>TeQUP!r7*4s0?#9 z+Pye~)E}_$+BhHsTY5Z!P<|1Z(MtI87!V4dCuI;~bBK@BIAAE?JBi#0Bi4jWl0^XA z>If=*?e5sldZ!uiM>44t9O2Jy_u_cRcT;k2tfj%i#{enc@kwVe)c2 zMb;&HGz-0(DK;Dc#{sG0)KGE%1ABFH!Pq7q>-y^zl~Pq9}x*u;6@ zsF^s*-%<*BL?NKl+Tlhn~ME6O~N{35n;hCDD$9*QX& zfLf`uyg#Q7wH>)gO}OR~)j4)sJ8@zM<(tW5^-HH?&lX2 zRK*LPPAcb)H`BsX6?=JsT}719PF@GPO}4H9C5~LBvZ5Lsg5Ow&BFjouG2son7&|)A zK!W~a(lTmMfTE>eqig-ATx3|6dV|Muwl-At6s`M_Hi!|z<%#hj#X)>rn?=fsPj}LB z&T_2SIFEXmxXK`X(s~&eP2;eG-N!z9M|LxO8jnq$S_@%I2cm)0d`6|JxsFm{Rp!!c zRaZ3PC5gb*^Eo?RbpQ!KQn)(il^M`eqt4kYbL{45;M1hVhPG6(HFKcTd>D_lp_pc{ zvvaAEs0en%GhUj*E2hlrqSI?J>Kj`mOugJ*R0GO;@=aA<(|N%0+G$x;FJi}MssdY4 zF=f)Vb=s^99KWK}9HSv0fT;?pBa0QMpsJ{-;NdFNV?H}I*DM#&AzPZk{N>o1D$a;8 zH)B0MpY2%%F-31seK&tj!m7a6+F%Y0mv4sxfL>E-;al_U zQZ1<)T|)y-l)qUM#%C*VnJ;Yf|ECJ2XC9Plm4XY zbCFdm+esJTk$ACdSxCXlEX==1x_n1-5t~GHAhx%QdtyBBu?6$(ICc)_y2alfu)vNX z-*S5>ohQ^pMTLpTcFafD{K-nY{M>lW6eus&0pfG|bUwx!E%#gVkxjTS$*qZBYx?oO z`8D#Ql;p~wBFAEt^d!_;sxO}O2`_HN*#6uq2enED*Ulq|kwSXi-8O2G1D{30m? z%ahk>D5)n#A%XW7+Fc$mPWp?lH$iwnr;80Y=GXcG^mlk5 zU2AZq?>o(1Y=^tjlEdc785M9bS>n{2CTHNwj+WQ=^0Cx%ds`#}JFqRccT?Z=oYlfT zv>2taynTT7Xw(zCF!aMq0K~@;H35Av4mrXY*YD3=Voxx_Z=@U>c#cl??h+6svqU$` z*odW=0#+w{G&S>21m1TD0!<727S{ilhr}4_?*ZS%7cN4BSlcW^(O%c8|5bRX!uMDWkUcuv~ z1(=_}h>zXu6nhsf!dFnr1}%@djRFpCOgh*>>KMZ_h(wfMBQrT^er=jXe>cqL4Df4| zUz>4_8>C)ZfkDdE2NAPkkm^GHPHa?5G;(Pbu$ouE9LhI_bFgD8O#QpbC4!z*Ztv0q zLG{u0Dz$q|W@!Ks0|jD0tJt--?8V+dFoZ~U&bJ5*&1*g5H{Y`JK;bkpGL69tUWdLl zy~#z5Yh0nrJ=I^Ft&=NFf@9NGlTytiLb2hJy|OB1h8V%lGp3?NiqV$FXHC3p`zmy_ zereC^4_DDZ8lF$3ir~t_lF;a>sx2ASuC~LEfKS9Gd^(9U%yVkuIq_9y+V|!#Od3m; zFTNd9aEBNQ5>V!$c&4hjOh=?dIT4d=HoR@tLy|`e=Zu^p?}#|H10$ad#nyyVz+P^! zBPNK9prr3S&So{B?}8h5QUj(t(@37t1tia!g(OcGTiQTR=Tppi18(6lzc$n3HaOn@ zMD-qG6m(6!hBgqexXmYbcYj3PWE3^Dq1Et_3y?UhBAmx!MaDVutp4Vi6vKJ?14 zb#{NyI)7Y4qe~P>;1^SQ+*URTeqfy)Q%gN#jlsjDRpBMP>iD&ZU!(lmyj0XyVnKYo z-tKg}6UwN0J=JN#M_1O{gOx=IAKh4w1%ZC=KrRi-I77?n8sXP^Txl!J)OYOOs)Xpn z??5Avb-r6KGTZkK*CXqL4>&PSAj+@J;<|#X343XSoxDZpbEQAY&311_WFmG;Y@o8U zIgLb-4c-LOcv18bW-T`Rls0d$SK{L;o2YyZRI=u^y!Ki|xpA_mU43Q;qm>H(G-XV6 zJ!2!~`VN;@BXY$@{5}8~T=)twfLD;Aa_r?TnA_3E zdf8M$*B>QZ_yf8|kJGjO1YK)R@jJMp`!zl*Cx=VkB21r@V<)!Qi;D3L1W(-OOBY?u zLe?L}XJO3oEt$N%)yO2pz_Xt+t~rg2@vUAqcsn%nwR2`)-}3YL?$DrZh;edOmAE`| z0lcM%9b3*+z0|xGW-Q0CxtFui{Bgp0 zHJiNMj`NYvPRhbr$!|~1Vp$KeUEA$EvYYMMVXt@s)Y+*WDC&(JcK6ftYe|#c*kyLA z39SyRF466@-g9v~epRtgwqs$34cvix9!Ju`t?&-I^*Y_E=eOK!)DAmoUnAYyaJsDx zZV0w=2QqN8dE(J_dUQxWT1!7J0Gr8%oT2L!eqDhpL5>N?<|e$gOWwO8?lsZ9GyL9T z#Q%sUJ1^A~;@!d<^Q3J59_Z|c2khK) z=4OjOvCI9v?De(nonF|{6hMS?!T5h{A4L`T+2uXqhe zFJPngVf)BpY#$M>81~8loZ-n=71>H-9g(da0M`b6i9&F%h2fB5oPHOwbFAYX{prt*2gIAG3o3yR>+pD;ezAW0>8%0nkpH#= zypCac*)8u}NUgs=-Mu6~m zl5*{PT<+5d*D#UpOcHly@th`dmM7CS8sUFWrE7Q^zcZ7hf*O7=p2zc&G70m&_XLk8 zD7*SPVyylUQJ+PBE;*#}IP8$(aT2{%Gn+VCc}R>zbf@V}y0cc?+0JvC%Xyhc*XDY< z*374C1HaZR;5_=+Cx_yg^s#}5i8hv44D&bQA#`|PvNw9h{K(A#8Zn|O*`Z3D;W50O{8 zqMU9UMaPs2ZGsbcp3_57uA+A~9y-?Q1PxQFs;K?bD? zyg`xy;~`n>1Mk63B2UncQ`hcWM{nx&BkZSc*MNC*eWPC98)dGm)Ekr-n3#{zv?`7h zH~e?a{ZXU8f`Nl>O!Tdofe15>sd2Q?=621>?g!q=KYHM*{M`j0+(MEW zhOp!9qmtjWQ@f&gVu(vf$76Wv0p~%Blm2#_lhS9VP_{kLoYasLRrdfw*+!upTxtuY zowwpP&*}q#mbzAHY+i(qJ>iXP_t6|$SaiYM-HLn zsm)xh)sbX?F-KJALq1wM=6r!fGYdf z>g*E~)&9gDgAz|ML_S!MJVfTJrYek)eN{F`iLJ(XdFP3Ymvf$C{Hh>j886>Gk@525 z6B#e7IHc4`;gt=-LL(3Zzfkp0NBu?KwA{&4i+;pF)Q0j=C2kUOa`E@Wr#5e$96I5q zG!5yOo?`q%oc0-T$3v+*K`fw}1l|%jE6S9$0xJPk1_va>J?Yer3>5Bg~E2Su+62(g}KzU ze+$YzB_*~Cd?he+oIFGU;UdQ~LG|1hE3v0ifEBErogHr}iX9P03HXnQl(* z&{T1g{t9QUc|~Fe8mk^eGV`Z?TR0wfmgvtt<|}!FB!B#mxrOp>4m^Jf4DV_0ttmL8 zf0HA2m5p<0LTaYWLx6B#Fq{&)5_n4|>fk)9J3(HZD|o9Q^aug7pafsa+oTBW%%>vFd*9pFxtsN$`Z`M4j!y5$~0ga0GX_OLX=) z-6aw|5O4$;AZBI@czt}@dPn8u)8z+`MT%<>95-t>XYgikVcvx4jMfhE=BUHbpToIn zq8aDgwU0CBx3S`}uGXADqX%2aBrhlCs6LdzV|NvK32wstg5z@fJ|L~~rFGV?s$b%G z5Qjd{A$5?r3E+bNlZ3%jgi(RP)1*Fm#<;H$C*rRe%NaIpSg_k(Vl%z>6-yC}!pSqh zy%S^0t5Fl3iBJvKK@I4j^|&G3K{D~PEHr$B#r+Y_ox-IF8_%SrT@?Chlz$ z%VF2{($B}87Rl_uk^2U2J3Gy%6IBh#M(zj;cL`%{EJ8b$;9+mSw*B*t&Nru&%qh$I}Ej= zBsCstsgCzia$9C$e2#a5$4rw)r%+sE(3nY-RN@^f6zR)wu&=sX{g@rX8vT0z7udqg z$o;{3`!&fDerGk}tA@Ir4ZNe@)4(jn=c&|A>X8{5?|8r2zUqBHmLUvLkN%}Ya&gC> z&w)Q*z3Cq{4I#`qaG<;B#0jc}%^H14F3gNxvHVtu&@3goK=aha0h^W=ounDeUPVbd z_yMH9%u~lcz>`lMc#E};CmMvKe^mG3-0Q$cc;r>j3`|u`^#SXOzE+VTq<(jyd0~qE z50SU*Ts~5~y3Ma)EhT-h|BFd@XdRJN@bsBDN)J)`0F~D48iS~>YZw?Ja~qq~?5BC( zekak;IZP&r*NCEyFps7RSh2Z@pMz%v*iQNC`XJ;Vo{g*ZDpLA$u8(xz=4&L3wVsxG zjWrZF(QV>&AKBqH&PIoeEnyLfHH0zV=tz(H6Wv>7!QFm^x4D6RA)Sj8cDQcNi z{RoTlHk)rCZ==~@;+U0T4zgxDr_ZoaMzwko=21s^X2Yb z7$poIbCfI-iEpu{a1F;xMqP(7yVzu5gc;Y+wFx}0G<|j27F=d zey2EN^e=cpVw}EU!X?rKTo26n4Gm<)J7|I$t2&D_a2_^TjKARbRGh&{cP;lxh=%(NKNP`to|d^aw&sDpDdj3+qN>*HKkwGuNVFd{G}Fs{>aVHgzv#4#)_6XG+6 zCDB}%C3rk&U3eePsbcRn(~Pk!h(m?ACXPGm_zj4zRyh7g>nsbA9`4W0kSChe*4>C) zJ0TwdVKcakT#%8IpQl!>%0Pdx&YzI-m>Z?Yc@k{DK-d-|Y`#o5>k45MP+v$pUZM?< zC6Fr&kQ7@r&J3`RBdFgXJayAVTrp25uA3(mH_?{4?-o%i zZWAuNV>-q@ftdbBZL zdrR4kdP?C=_*=w|$(W%|Rb}Ywg70a0Nlo`z zX$HF7?ycbO&TGo&w`%S}*&{I|^fW$E)DUPNx9%pZAYaC2@Exmkplol&=TQj43^mZzsyQbuYm zq;|Gnb5l`<{2gZgwdwPpl>BFz|H**C&FwOBvu?3)lOVO{Cbe*SYAY6#n@glt{6TXQ zS&aO>X8x-5`L9a;8_XXbBsXdG&X_OM@{m*S)M^@f>OUHl*%o4Do`j?_M?+wycAKGg z=i$-sqjo0Q_7E$xVLPZTocU*>b|09(!OVXpeg29i%wNg;t7joUJHf#vGMaHlA8$gM~V$@nwOHBV!WaawYy1bF_T(H zdTN70?I5Yu&(YkhS%&=A%>2*O=Wkd>Zt9u;)Les`OUua3Qu-VJRy`)QJ0`W>^wbtE zC$%P0yHcXL*}5G0+s*ua>GPkJ{HK^dK9AgRioXX&9xX@G^Z9>ztCm${=Niwt$|(baqx}9mEav!}Ufwh=J-kl*GY|co zM!IPQh?l*1|aIG$^AUV%^T&a0m=b!07Fh2CV`3Bvt7&MZBo8=3n`1!1g)Fno}3!C}(Fl|Qv`HR6CT+L?{lGH{p3JWMshi=Z7R{PuT6ztmm& zudq4z-FU*Wn21(q=$*|t8`p_CW}Fk9*?z%nZ&#z1p*r#qKgyQ>k>1F86DPx^TLQVO zdkEn^6}F+k#Hl%I&R_uM&cXs1PY+U2suS20))@2lqF%`-83T)+is*VsbgQ2Mfh=cZr{9FVj z&OrxZ*b*}eqsPW-82MJ$U@xpMTwzgJS#H`TXQ=9OQh!TEWA6kGkX>HTZ!0%%rhK^Z z^mMeO2jH7Gn!9qu?m*n)?+U&xfZc+Z+KRv}H;BTIV{N8w0**#$Yc)gbd^(No7Z|_M zJfO(Qoiu)WKElNf>vd2EE4O>FB?*=M9Y`rEFeWfAFad~;PV2L0h}$0ay<@c}oB?}u z67~T)rw8Y;kdVNzz=*)8z?i_ez=Xiye8CqO0c2mo3Re$@epg)_wW`_#s})9XUY6O( z+bYpg5(_UR3@#!uLs_I=mg<ovrYw ztn=fg3Q&lbk-zw2@|T*<^37@JtR*M$5a~phNS&8TotF_N1jd(3`bxt1YAsldqIi2e zwQRjtUELGjol33&F$vQvo}DPEd1mDQ_WzxL!9v8%j;QZ-j@79gMSd}E0{)WQv-qvr zD^ZV7IeCnR34@!5+iLDI)E)DdbNV*)kDY#uEwOhE@OPJVC7&%1PahNc4gm85| zVdYW6NCV;dM#4)?gmuRWS2hzq6c|0h-@#La&8>uY&JoVKK)5YNc>W^ct4oAyuMkGB z5_VjpB#enut=P%{9lVmPw{xA`&S5sh3CUJtsr`ma`h_=`H`YejdyjDWeZqt7gf|`# z_CF*n?;vb=Oc;Mc7moe$lCVSIg4g_A^@gzNEn!<9VgEbAl>>zP-V>hxK$sXL zoE!YRfX%ZAZ_FlqUQ8IAOSrs*aN9h>`uT*h1%KC3-#Ode^{wXWQtG>UyHVd$^lo@q z%Dll4;nF39l}iZ^E+ag(obc8P!sjaq2UkfcmXo|SnU4O-;Un+Xk_l>cH7r8j!;-6~ zv!EAiNHw&MFi}p}wt;YWCE;0todV}=VM>+2&aM3YW*gzGYQnM|gll&aR_!9(x0|qG z4`D0<7zox9E&zl(n6cban|I)SnWYUsQ`uY_nJcgUof*GV9g&Rx@k?z-Fb-{1P2JCa zsUJfk>Q`KCov!~`9{$K1J9hNfXjr*JL^f&7))xjW;R-mjDL^7F?{k?+4%3vq{N)Nj>|-5Em( z@2}x)^)r=eFPzojo(!{XuzLQVUyb{pWS)a&(daSb#!bShNv^~TQdLKO=4n{#aG%oC zS$`q9y;*Bcprvk~bhOkEWzA(97^=mV!EwJ)0AlC;GMTq{#Hxj%j9k=fmVl1;ro1fm zAN?7cL~H9fk=V8))(OZIb)bqtsrvRh z*=2_yT~<`R-v_?tHL%aEZDubFo&=Bf6GOZA*?vt6=Vty!ZSKzewR-R|6aDx!Fn{*7 zGXS_gsgg+6*6oI8`!k@HO|=<+fjb1zj4}R!uPyY!(yU*ldS%{CcasA! zK0liE=hAoKfq`I&=(&EZG;jF=*Gev5#WJyy%-fN5ji~T@Yj6a3q$vaUYm9+Vic~9) zWm(Z+4b+o-Ld)X%YK<+XCvzaC{udC_dWvbcUrAQR{S246{w7e*Wb0E;35@@> zq@MYE2_WcVdG)~z9pu@Sy09v1vbwbS3%GV*3$2%iMuj>n8Q?M>E<_H|sgk+QYSx3O z`AAqZt9g;tygrC(Hl<+1V)xh~ABL>22g%sBN*~57l;vA|7_`3boeOE46xn}t1zI&uy78>~X=Ywh~?7o+|kyHNku52*jO zlto*)cO3R*!TNsQpB3wSyJl(0b)LMiIE43iweHiyc&)>gvZ8^SrBDBLy*i>ehp|X5 zsI^yrJLK+A>5Yy|@uk%PTHO99VVh{ukeAJ;wf19J;3FBk(O(?#bbg;m~fs z>en@3>67Z)f-uYZ@Q4LhCmIYzb@qe$Dp&=e5B2pKc-_i9^R)vD6_&3G&b#_K!5`02M`{JdWw79R(iyoFhxmN$9} zwLUGOmfR4+Q}SKP{<0|pR!b-EFOQ?eTsW;Qc-h^ewd%Od`j(G@->A-~VEyPDu-@)d zf|al9xThrR%lc+diPo3bE!b2&CsjxJ&35@`22uWMmcREk%6|h)ER0&I-KdqT5*JxW zQQ>%4oePbI+i{XwxrO(+?Ghc-KYjr9mM5UTY10KY}>V{ZHl4*ZJOz#n`B z{MX&Uza{t&1ttVW&aw?La_m3FHfo*sB_50V{=2-QDY-vRAL&!-_i;3W$68*W$gl?`cd+C0rK$8S90-9DjJDaJ7+G=H)%vAAfyz~cuY>8i}j<@oTYwmP7VZ#r&K#)@~3=tcGaIk&4 z1=J?u9<{fam#whm{!v>59r*8Bp{?O_)WHZKT&8jUbC!BB8`p=kao7T{@S;2;IARJ; zdL2Y>czltJ=dQBuF_eX&(?*2X(Mh&f<{d4| zSzWNVsn^~Qz_iHLWG`>OfTxeup(S#HHn0xrF{P-W$cT5FhFgb#*2mD0O$R_Vbl%XB zy)o*@Q--O)jU2V*QikLFrGTL@jD|2O!#{^t*oU$JqC@DK$p$T5i}=ZrX%6P!bOj10 z!_rlV?eOQIx48!*%$qWGdJ$gfn{EXD8eY}bT+Wbn*@!c)x`|#IF=fB&GG(6_r0iAg zRrW7~A4J4P^n^6B#$^DHcy2GVH5egd%ubr7Lf-D`R9R_{bh;UNsynhdh#CV+(nJ9T zcZ{@tgQ}y64?abcKnu@Z&A?zGv&l5|{%`>6@aaXL(onOnXMCT_lfMzS)cosc8!2+= zw(xtn^UPB8vKU$#MrO7*dvN7-uDDeHiU68@!`+|3{eRlDf=NY_#}7H=IE1w1COYwo zeNdT=(Eu!;&lA zR}JCKed8uYx2S_%w1cmxr$ZMN|hQKc8bF*=lk!fuMhRD)* zn=D2jId_W=SNwxL_M(0WN^R9tjgo#;Gjvs?1*ig`9=by!dTo~b10$~+eaYOJNEXMY`T6J56I-CnjS&h6(`~Aqf{Q~Ls*Vy5q!sqWhR9J#E z4yD7-c2IjCKE_Cif$rGD3@lOf#A~jAQ%rBtHypA(T5Rr7{uEo3)d$hJ$}i{%aY}9x zBiraU@ve~5vFq7uIJj?mY7NyBCVi{Jo2Q^eEpfNnCym!DxtGD(^4kJiUP)8CgTuN< z8HO&JOU5DiGgp(|hT8g=3nbAPW3xZRh*{EM@82C`Zq-vvlDFQ0Sd7F*nVU9o#ggMe zc%URX2u@IsIx@@^%}}T>Ka$n!q|U+53^syWh(;Tjb;O?zQEhshF$r;ZF+wDe7vua* zj_?!5%ga7E(#%T=UvEvg_Ld@f0~3B~I=YoEvE2n>85BiyEbWlhB$)ECD;Xw#g-e%) z*P&%#6k?9bJTK3-T`>{;$gH~IrXywXMkLc6*_NEd92>sF>W4c}{Va?QTRO45G1}bG zkmX&@S=I6#s&~w(4(vP~Vj7C5J3hD7TK9i}VWk*>sBGP_D0g&bBy-W*X{&{W z+0#!20RPye35*Ghvql*29Qy0S!_#MWbyKjpt;@Y`@S*asntCL~1|N$y0YaN-*OLbz zb&E`PEfO5#gEaJ4{H(e4w(srd9Juc<<5J>-492f`)iTUd;PoX30x7s(*@YKZ==^BO z>js$(J{2|vh6P3du^^b(aU}3%Y4BTzZXlphFS1zYw(lU zfq-UUR(FPZ<}}2>^S0l?s-xtS!q$yNHd~ees=GB)4(yiK9HK-Hae)akgJ1yaOgBYe zUxR^I0t0xumYzCYZnK_F7D;5#8X`^V!b|RtZ_Cg!;ZCa^B+_JxcCGAj)H?;vP0GMD z?We$byBK3Y?755qU4$`#;TQZJ5f~L16Bz7fdIAuu#PP<%Q;uOxM%~e)=`f7|4y=Gz zm%Do%@^B$03uN}<04eF-gJIVg&%{udY@%FwhvG|+!FUgwZ<3tA!ub@}(Iz3ro!tix zy_A7O-i9+3&C-uYWe`cE*Ox*HDRzUNT0||5zhwELw}fGV5rI*G34y_P{2CJ&?C0-< z!03SF{lJn9_ca+Ae(Ako?3L-Z*BOX~hbMitmigoV%6jtGo4vOUf{UV9RC|yx{84@j zj0%hi3%i-Zq!qhf1Ul0u%c8Jl4VQ zw9z4{H+>k7V0YtoD?jg#5*B45Vmrerp|Aj>%nbFu4{DeIEmWoy6oH>xR7mmi9)}b2 z%DW8jG@V9RFSc;HUiF51|Apk#JQ;G7y{aGA9;~8LG~t6F)GZ+-{3>hp!+ZU{Rkgl9 z;=~_a#EJ$(gb9JsCHx&)N*G-xa5>ra-qIhUS?>;H_=P*LN`JcsGjMPvC^*pE1-8fY|b;L@!B)1EN>2&ceH)H?CGADG-!I)_&3rz*$<1v<_maA z2hL4{j!+qkYh4$M)}6@m4I43p^xv-i(!VPVk#GA)U!55u*B9MsO2pL^hL#pKgcI>N|NkVIRkrdeDE z8x}K~8w|6V-k-Ub#E!kmJz3`7j>FRv)&|q={AotuL&Z3|EL#wmsM^-TlHu;8(ocri zCR^(c_x@>ZGvo6a$5oEQkYo!^Qt!@zZ>ROo)3@8=0Z6eyq!?wxK#C=EC`Ftm6y^Q? zyMieN*jjx%J%4;53^3Z-_bXeH-Xn?ED|x~)nY)A{tP*gTPgR?YP>O}aGGh2|p!d%W z@J02XNYgtgo#o~v+GOa?;u53Q7*%ufCNfHtagLxzH@nWB;nE|~S7Pd4%doXvi;qYO z$*a-3nORpKM!Y}5jl|6XDo7@_L-)O9JuOw%9>y6IGXyLWuaF< z67&Y|rnCvUu5JTv;Y-z21!$TsJAQNYcE!-BwQFG=>DhujdwZ&6Z3 z+M5>_Tls!4E9z4Ly_t0k!sizSI8;EQ6Mj?!D?u&%K)R+ScgO|X3R);r67@*+Ss5A% zt%3C_9h`FHCHm>++1DpD)5)D*ey8r&aa-_^V`fytNAkNvM$u;!L7{40jE;-_aBdN< zQc(n7VFoky$5!h(`m#e3*OtSOVDxsK*R^#eHAm0)RQX{jAZEosnvHJlWIg#zi=fX2 z+VsJd!X1pY+(4^Dp0k3}r&v4kWCBj^yo2uEC7M5Vmc-&+USiHm!6wIslH)Mw7v6EE zd8szR>z8lcN~#YpTy_wlIN3Bhdh0j0mG$dpGsb(!*zlJENoVs{mj(1Joz{IOX|6`w zIt1Rm4h#hc$WZJ97{ZZA5uC%vn-(5ffQjo~(lyT)D{w&ZCK538C{OB|V)QfRY^Y=M zXt4Ak)-waAEa#5l5Hm+65_eRovfFa$_z6_@^suEqQZV7*-$P45FjEWjjMGTR`oyf7 zsX}gMx>quqJO&5TxRgA}r>Mrw9bh0n^Y4LyL@^m)U;?R-!}O739G}^vezF3;TqOhT zr(}J%iag8_9!kIi&y4$4+bdvWn0MF7(G)#AE_RJGew{C+QoNN_^SSk3s!T{9gQ;=f z)Oi4=!vs3Yys_5Q$l1({(n}!DqNkdb&={5wH>}WqU^#N~)$^!-Y#x-J3hj6qi^so9H+_VAABkJbk!3`HV& zP&v6HkULZ^j|48W*Tq(`*0Hsyb%wnxQVTW)bYFE{#09S{)9+&Z?JETD6l`fzlA8$2f0K396%k z+r&`4fx;?;^9CF@Y_ARfncL*ZBm4E60z9Wthx%dYr)!O2HP(!e9)snL<#6L=qJ|DO zp-t3N(?3vG$#-Fsf`N?{?wMmm+<({%%?Vdh4AWpU2~3hjlDV}KW*qvC6)yj0EFip( zT!~=Jn@irRM(+gRqcH=vPIQ*msG_HGH^dlmh}DSJv#F94){k8$Lmq+~wk^+(UNy;sOcLcT(h943}pOW+BSr745y*>Sy-#ll;VRdN8 zInHgN*?%9z<8uh3^9duRgt0{e*GT$0`CU#JUoR;e{yseboDO-(yB_qq6k_bz_S&~yr!IVi@uSg(!tgwxj z2|Po^!w8mFvSejAB&Um7swJ^7p3VU=agmiR{s-VLGLJAE`Ue=LU0aBx{AtI6#rw>4 z{OPND!EKtneCe2~km9b6qc`=vBCAWlo=3NR>6$Zc(oLjW`%V7dT=b~9FCmjbPg^A5 zCTiSc(CRUg%x^d=MpjbNT&PYF3Mtx<<$46pHyb?c9IOe9SGQYWAn1>Qc7n9=!UEov zAmUFVU4KBV-suP>+YVoli*Px)pf~NXYM4UO3lE}d&rX6~d?Ph9T1gm+5yk+~O>Ota zA!=$BAtSm&tR8WGID}J3+=3g@qn{)Uq9oxp{>W#?RqcHL~8x zhblShmpR2L@pFsRsf&0@gF}hdJ9WFk7}gC34s&>S8i_=Jt`Sl91c<{pukc8({;5OV zetNsJkEJ_Os(FS@&`&SMN4*wB|1|2ghUyhXgF?v!hBmW{n9Y)Ibf%~s<5co>T~Ax= zJBD*IeNdW?tZb)2+A&2RZvm5GVGJ#8=}bB*BW3PXQ@{~7R-gMgE9Y~LRdEKUis&|q z%g4z^^)_KK_J^r3@2D+vup;}k(ZMZPT6cE zBc>_+Jr|;(HzJw0go!!-kTCQQIkwO^u3AqAoV(CZt7SQ_g-ujUKrX!A%u1jM)wnjYatMWUu_K- z9=YnXn&Xk=iJTRlN0Q~>S#X`WLQZ3M2@?-Ljo(}6M$T{i+=%(@I0t^Cog&$ngwcMR zUv`k;Nw@gINcCaCg%RpQ=mPa2_>aJ2a2{c#j4-t9A3-06WkqzYf;^qS9q|ghmWm|& zG~Kron^3QzWF+}#(bSPxEA?7W7_J};R-<*9#N9_dYu>L#^i`*dH38F<%wKq zr|_|dlAWkZE@7xxa~VU$-YeEnp*K%SHFOLSC6~2I42`+yvZ%t)h|4a6x!8U(7d;?C zIz$%r;FY6~L){|swDQI)W~Umuuq{`N&NMXYjw@`uT4}H&0*{W2uR~Xd&)3nb!{+Pw zRr1v!932yQ+~#ZOYMs6|+!nlWZI~^1^%^;9CNJ?8!ssb*G-?`RK>e1b4^Qqp7pgho~M_-;VQ=QIydhjoMR^fuGz zP<1{_d?)oq9M@%*G#5iw;O?I`mN;A@;Ws%lXT|4jsf-)xiaueoe&zz*(7Py&1R!QV z+YjNV+>L6z7r;(fz3+l1MtVp-3J5<=ua5JO&=_|Zy2-1XE%AVTFW0nfBa3LFyxR*!!%JM^M_)-hulT26AhKIv17SES@T9;?gt5zn!K;LkYlQLZgwcD1vACq%Crq^e zQw(o%Y^mMiK9lRQdug!afpdxE@RConMD$u=Jrb#N1-tkxBtu-l#Mk_@zX5^NKllD zV640-wY)(XeL@%$7#A3R#y;RWjQ%uTxbZxI%LAXoIC(@&PM?dfMAzry>lpb8cTs{7 zK-;nGv($Qn?WO!#=xryriEeTmOc)xjPO}hsFI>&PVJemyy!D00#plyhV11AK%=y!( z9O!ZTm3$hN-7v3-JSCz zUwX4$Fg}gb1uyBX_LHJol}iMq0jY?<_y_(@X!udmXZ{~Zi3p4e3>QlZAT~96OEJs} zU>%n#cBx}`)B7qg(wJ|01BcODAn8&jV}n9^H<)43@6y-%_B%C1Jh%fu{N$jg}Qw> z)AF?WgzS$Go7#~#?3QppSGWc0kzoWQQg;TdEGYvq}0Hm*{uv`y_eyvPYsY# z{d-Uf7{~PpHek7B>DF@DQ+1L@7krg5T55K{Bil?78@)OuMYmvI2O(H1+WtGnAYacI z?p_#$byGiOa!tScl9))htTf~5qety1E5sepue5M3!QYWjktt*YHsT?~StKzHq=)faV>tgKwhqFj+ z-fULfCscttJ(GjbhBya{hiAWZ5V}u7hxmHIJHB2px0r;g0UbDV_?E+bzU457Z#m4J zL!5R%4yw0j3Ac=jrU$8fE7}yFxN~Z-rEj>U^-a!iVSmLFczB!VXj8V;UX`=m?d7tC zIvUKP^-aF&Xw4Mco_gNulz2T~&Dc6G3ssw!g??N@w#(;H;t)e+vGA?7?Z`Hs{+x!3 ztG?{!=ctksKOg_?bc~t^0hStT(Fv@O%vedPwm5SlqQ9?mwp)x(s1)-FmFs-TBQl>n zT>*56`xSAn5$6G)S!rKDoJ1+b?RvLHRn5s9|2^L9rGXIc9rE;VG`xcSXH9$vc?Zk;uN{#a&rTWFhi32)t=JH*qB@2L4w*WX*ONes@(1BA;oU?rU zsU!%Tv&-<+9fHp(TWeyodQ#F-01q);EXg!Hs4msJ1MoR%S*ie2UA`)N;trqZ#I#BU zrrLfLjwlJTIcoA$x`wGIC7F0KM7@5NB zjCT1@#&)^A@5}K&U}=^=W%}d^tYqu0pR3GVwQdPj&b;ugE0f$Qy_20#9Hr$;@PSs!PYUW-p-^_<4(vrKei{Wt_>KY=WI0RBYh7FyUp6#Gu z1!GnRA25t#&rYb7{qRIOq4-N2@yv%a6Fg@7R)(>LE87pfQ{6|sGee4YR*Q~%r-)RT zd$}BlAC$H^Oc<#rj5iR5j7^`x@i$Gv27cj?^)K;U!+o7j7Rj z^@#IV_&6KOZC!KY`HNp&-Q+Q<4J+5}O{U4GV~c6>F|2B-^IL0L=}+C=;q?!yMi@x@s5ApR4w8 zx!4=|ay-J2P07ujIxR0BZ-c56STDjFx1*A_PWA9$a?_8D)e}ekbRY!{^{fOIZ0{n@ z(h20YZ3N$-4C&Wf+6xd#35+ZEgk~j|8dsmQ3HvtuiBX zQhv(T2wg+rSoFNz0z1^bs>})M+*X)Ww0ET+*>hEy;soyZ?Q&Hn-M#%j-K;`kvFogF zIR+e^$<;2?vWAUL%+rBMbX?wP%Deb^E zwq?7N^N=vwVVCB|`;zUcWw>g4N*S)-j&dSTSVE|iF#e1%(nXkffimr&O`RzPCg_=mLg(#lb5c4RtSx-gZ5E@h5Ws}E-xYq(rI?^lQe z_8%uBvSnf3#JnQB*!$NUcD;+a3F;mfW6>Vg7vTcDWieiWy0#+|k#KxIsoAVRiTFR>!hLadVzPAyc4Cavq1SBL*c-xdAM2a1*22<0pVrOT zJ0^z)1P%&H@Sh_kGV7ny4rg8((IvQy;J#v2OZIqj9#h2STGSA+PwT+S0}N= zr7m5Nm8>e+r`MlFh{nY5f`4$~+jX3v+1Gu|BJ&NWi8;%ECY}S9VN;VxF%>%L*yQ~e z_-fw%OxI;X-^}XLHJELKFJl=e(Ps8=Q%e3)hU32e1vIdVx3aasF zrkn&%Q|BM>h};Zbm9Fl-eC4aQgPE2`)X>?-Xs;jB)dU+E$TGT7TJZoq7~mBLX-CF#c=?(fNEu}X<$&Z)|zGmHMkBB-ndu%BWr=HKkYpI{$Wg{*qqR# zW@xD=sb4D>eenv8ERjkXWvU2cTOk9CG8^h3f!_Ukl$p#Zyi;5whBY*(r^X2Lu+AJ| za%be|13~X7^4Azfj76b`A9a6=9$eHw9rugcE$JK8eaUh>`h+^ZKB9H}Q{;1(@(FLJ zG|0)*mMHsQZCwvagq@u zhB^iCjxhZ<*}0Fh+<1eO9wiJn5hjk46PS5l^6bKlt5EIR;GHO^_&t|n{MX1Gh!9!{Bj*U?=LthG!stawzf2gr0zqLz zdkO|9b*dQ~+Vdx@4K4SPDKu3bY}T8ybU5|RvN=nK($tJLOI#JUt`mlCk|A@Wn=Whp z33CgY4y);eu@y~+bm|1Sh}{w{?hr=WhRa1$%W$|j)iMk&&bP1!##w%_oiOph<{};Y zO(%zLi&H0uY>V?JDTPNYIs914e}eMOt9|a7&0egEX_PD9&8IT08;9i9*os;sZob-f z8U;i;K{Sx3+D>I!_YD2fi`VmdVQ768)*4T0|IFe<5B5nrUDHPEEmu{X7RJ&n815{eq5+mr!#qZ&+l8lDmlY$oTFx*$;?sfWSiNo$uGs1O+vm0 z3GT?JdRTEg?)dK)JG7^hD!z`>A_g~A9pzZv$9h?adsXEQMyiYOEFbdKtyXL3GK4rB zF5<0}+bhwC*M!lxEdP(G+QD%Ba+8Ka*tay*%yXHBr>VCYot)dMW4e>2qj{MVXQZF3 z73!;uPFfCy9R`25&t;kyFTcrAi<%+y@Ow5Sgznx1B!!IhFI3IGkh~WeZr;|IpLI1U zx9Xt;LW2gMjzw=0Y6>O~$GrOC4bGV&V`I!~qez$0YFAl*-6YrXk5sJ4%zptGoJ|-j z{uhvUj*gS7{Us22K9A3O=_l2G7@bN1vH8p~8aas#>Y)`nR}Egw95-Qn;g2*A(2T~H zPVfZj_$fu>3cjC`TZnNd>$@LeKbTOEpEr?*wJDBls~V{ybA|DFQi)Qq&*(hx+2n%~ zt(P+0&r)bf`h--@S4(bSSdn<{g5(V7Zd5DT1V*dd;meu4hN{If>|BkPIVzWt>2Qcl zbD^~G3J*^0ORcH6_0Lrckb zVwnhj1-Z9x!m8s}GxY}A6Omg6^W+0;RWSf#gB|laRh>2a?x5G|$TbSg2)Ugqr+cI^x zP3O?LGPqHa^3`i7St(OuVU`rBVEQy?aOyD(aO^vEYYj;964d@exZI2%dJHix&Z}29 zGwF}xzALV}*R$7GviQ(e7OyY7NzGC$oOP`|o39iGzlPhHpWhGCd8BR)#A*>Y!Le)YS|;oDq{gIIJMwtR-Ssj;*0O5=IH)1@wpU{EMSv|^nBdqL92vz zqE(!Wr%z_Lto^gp`qZDe)-PG>_%8M*bDjRlRo?I!3=Y*a-A49sqzdkpfq5TcxP~xs zfG~Q9Fm#wOUiTA50u_Jw`TM+jM~O!2e+plHkNyOFeI#GO2I}`$V`r!p9oUP{Q0qD} z$KXGkIw1CzMvC;k!0M=p*q{-cHR2f}-oVFzDmzA452z_V)`(pi@g)&E1tr`>#Pfg} zG4xo9UinyxE`JQf_kyzTI1%H38u743Y}AM+h*;K4lxBgwfEuw~BR@(kD{%k|$F1swY6)hmQfZ;UwX#Qzl}KMy%6_Q6hHWV?bRxO?dE(iFj8dKG29y ziMY0vC^3P9fSSj-Po?OEPo?OkPl34eED_rUhR&IYdo zy6BZJr0DV&Qgp=&AU+Y4>NpV}0&2uYjd(&Mwi0o}eWJt#u5C9FpJ>Dv8u1kouizul z9|$_2rnsbAieA+%MX&D$V#PzElszKs1=NUj8ZoL7TZnkAgDBz0gzbPD@qtEssu8=1 zSowq~F9b%O0&&!sv1)b#C9BH`Z2P|9`ITCh$ef{`@}h6A;Na`l1lUqXZJa~I=_XG3 zGt$vH&oT8iQ=1&A512Z@)F(&wJ_`6#=y**h7lEs`^}T@O6I= zc{y$494F2IaaMNQyxe5!OkPcW=t%8mY6(;KC8T_T2YdLtxR>w}pzyM;m%KddwRqXl zONzWPdPi{ZwXv7HT(xm75T}GV8(!JGJYwo1rWU`pQwNy3jH#VS#jqmq#T)*<@|Li! z&){Y2OY-verNzskm!!xmr_F+cudOf1%Y7T?HgT2_=fyj_)E=g;Ve0aJJGJB$Qa3WS z4ym9j@W}vwm%S&f0Tj9He??wqzqWWe&b(`QN3}t4@b&sD^3wIn!g(z8iF56PUFrv> zZeeQqpq;wxHBxslwH>Kwd4U}t`Fm&Z{{rj<6kZx%lb6MBta2_e?-pJ{eJ?op`rtKr zd1v$cQs@(>Y8L5e-JSmiIrlR4tRr2Tq4)uLp9l`V_Pi%AB{t5?52R0==8ZNl ztC-rz)Vi>px{IkNm>Su{^6LcFRPcB8X2Ni#!OP7Li0wj;3;vsdt&$zKc|z3k>Zs zsPt$mh=*W|dozPsph92ywSt2(Rs^#kmmN0F7UJ9`PQ_kZ0!NtofT^b(sTZ00l&LR} zimT}YFYTA#0yos~cSK7C-T~RPXLNe_Qn~k$Rt$RzEGURp2?oV2m()i73BxZWB|#YPk>76x_DjkjVL13N`E?dyxLAJA`IpAlqG4VZ4JFOpM0~6fyENiUBEBglO4B03xr>3wJxIxX5LD~uTcgsd`4~m^Gibw^5OMU_ zwu`BUnOd`y`5OdoTW(M}CsZETRPGZ09r5=Gj;8XGsUMm8euZ$ck}$T4FfK5()~vv` z1ya-93#6tsJhJFrN5sSi!hS%hz$xOXwgpxNZY)3{uZW-6WLMw?Q~Q~Etx}2;*s>KE zfnTf2(yTw9IBsfDA%uFWG|LKzvT;$?Z&br#(1xh?m9nIFA^bH%HI-%=bqaxI40Nz8 z+(tMEr~($bJ_oshg&_C53=uCLFGj9;+ljwaU|3*OU<{CMvr`KSi^ipRyQg(wmX6#z zF(3ZDV}Fa8WFbh#g+M}JWCzJ7b`r+-62=Y?hH53{JYn@s6lDj1S;P9SSPTYBS*?fo ziv83rvLj)15{t6rbz&Jze}UjK`e;W7=t&iq1rCFVb~|J2?|wN>EiZG11DaRH;+CMe zh9xMj{5Fe=3Jl+s-*Fa=>^b@X##^L{mu2PSGko}bS;D#$bQAYUEBKIA{R%BSd$Fk2 z{KcYLi`f*x4x$_s_z+Md?$n6;G-53gmp&%qDS>YQHR4r`cv~ahCt~FjBHj`>?Z*c3E`Y>y9-VeDsqV-Ad5`hR~yE>>d!n_g6ee_>A!e;BPJ=VzJi1(LR^Yia= zC;W)G@G~ZwyyLQ)H&XQKgK%Aj#3tqyvVMhrQRi87ask^`P?RIj*H;eFM&B4MHT?2I zD_P#~@!Qs-ocKp-N2KIm5ia~!&<-}|-j!K@n5-_};@SJ?9Q|saSJo5qed@eoRTgA5 zf(*~rfr(&<1v`XgMY1#_*85AiWHn1OB|OYbJuET2_tQ#z_bDa5l{y{};i`peDB6KC zTndGt*RIJbEL88;pkJZ-!>qxZHLUh9kw38TM4eIHVP?9qb_6BNroKjwQiI}AmII&2 zttB+>GK}M`O+Q&gTwu5vF)VuXrh7Zk}687UlRk9%qDtC61RyM2K_>T25(~B3= zlMSv;mWmHM!-fOTRU4XYE6FByix?w>v3)2agTvLmuu)a#jNzeZ zxmRKdV|+^TuF+g+uM3|!M8xspfd90iJ|J+La}1+?=z=^B9CqDXHaSIWXGm^kJI~5W zh#^{INO!o6xU0fhlEGKHV#--eV?IaS5(7d>ug zOs1$+>=~;}DLx7?^QxRTf`o zvERa0OAo>$EPfW$?0r)NSigf(@LcT1uB_jv;}Pr=HthpH@6WUJxffjcaR+`4@q6|F zf5l$P6PPfv35*Dg3rql_dm#8?evuB|JbJW#9L3ns$hvFv7@cM;+C0N)Vr$m4$pr;P z-+ezeM?dH0p+VPKz67jem_FV{dB$|P>GpR!@Zt)y*bl!RXVy5fLMa5sFYe(KrVPMm4 zkb6#YC)nY84ML8{*6vdB( zY_Kc%7*Gq25Y_^UwPG{z)XTkCo9S_Q|6Xk1=d(pR1P5PFl8Cx%<6I}sV&b$nkdDsT z#ncr{t!cDVXYWJmI;PHzvgDA!YsdI|TN7bFpzyM1A9?w>&*G(#c~|U5-s0oL!Pm?C z$jeh3=Yh~C&hlov)OSqX#MDcU)W!Rex|OM+6D)bHz~wFceMw;GBx$%tPQNKv4}rJ< zv$V!FVEV4R+*2cG$J&EnA&jEH$Y~asa->P?io57QssSctHnrveiVn6)(dP(*7o^7O z`hhImU)7^_%4#^L_=baMCwo*%U1ZfkdoW6kwU081$H-FblE5p3k!ysB8(=c5QqJp$b5{C7$7A0l1#xr4G=U_n~q1te_g)hayy6RhJMr$xGRg_=O9yg_+aB}Xn zqCD(x3JUeqtuMo@-SUN=9mY?|&Ck!9T9h}@T>4DoSjgao`kz`CrDyzY*5}u%w$`>u z->J11LtFM{|3)qRfQt@L_Lf?9i{0>!hE4iftznaXu>hgo;&&;X;(LT;fR44*!oxPX znIu>DKKs|IeLixn7W@k0XBbhETMlPURhN$VzWb)Dczq6HM{wory$H0ZE^CZWa?7!- zuYHo=9LsVrxwp<{vN8fDpA3M>YxBV5PA7X^bzrZuJ~exmDSqZ$e>N?Ivq${cwLT|& z*|t81o1mo!$=axb{2!+jOvba@2w<+~KjUUp{}J;wy0wkY%5JE)E8DmSm0iioo+&|P zqiHExrC+HxDfc#lk2B0=ucikb^snv){pNnqZ3xTC_RT>#PtuiORimaMRY?}g#X0CDz3%$8HQ41=>_EAvS?=>< zl>1)lH#hE*M_Hqdf2g!4Ud>^Q< zoek=hDd<|Yi$s0sTVLZ*wC!$pU2a9~x?JCm(%!#CX=SreTCLQj$&J6@n2ldb{A%LQ znhE@~f`848U+2Joy%qR3-T?p2M?}Gm3x1~?zte$#k@$0o-#G~USAsw2#$VfH^HWy^ z{F>LmZ~FlJdH2~CO96372}-GIo8W7J`1czYKG5fqR`EotdpC5EYt@;TjhN4>xDQ zuAlqEk4Y3|&t-3)`1q*o8wmA&%uW050A^T3vz{uHTwF}>Y!?=v$db6AQhz7A#>2)A z2|RiX^$Ls7BO-K5R&oxR=9$e0C(tnV@Q;(hqQU}H6X&VtKr=yVm?G5n7SGV{!0bKi z8Gaq#`p%-h!{(-^B^5V=XTeRhT@(rpQg>2222AEd86wKRESxaO{_9ux%U@X*uu0Lp zsIxu^a%Xf1wd@qmp}sBn!xa4rv5lwg1rE#|{a`NmfXq#p1iu_K+q9yg3%GE~w5qN< zjZPdE0?7Wy+`@vPb00sQ<%~osTJkk57G`j5oGw%MTyk+Rop?)$qD%}20>cmaI|68K z{!$%cht6b)sBI%hix6X0D$Aua9%Zo(yU(BVsEYOF$~l*!I8>;()uj|xj$2gFm@pj| zrrBwob6|=i_@}K|3>l+`h@tvN_kOfYLde@%>|k!wS%2oj=cy8pDAAC>Fre=O!$B;5 z)?5n|vu+W|7!?>37#A4nAlU?<8s(bP9Hi!D8p-h}UA#Wlg59DF7 zP4g@Mhk4W7PT`03#=FB(=@yQfRhF%L@VAI9R+yVJ9r3t`!W_ozpFaUp@B}$o_Rg&h zZMc`p@j~stBBQ~#y#GJ`+kz>0IEQ4sfsj0WP| zWuvC5&DXQWPg9kZI2M;Cet$j7(^xnwk}J)up-W-3VRiHvXb^DxR}lN}VAnpzGB->x ztqopc6a@TI+(InAiLOd9=HyM9Bm=amaSm12D@Vys4L1MpA@z)lzECmiJS|tYag)6S6ho&@{ zj|R`dOVXo88-Y^O^cmYQ?=RSychQB7C+I{zMt5-cUkrcx@E~<<_q0J);ZipK*9Jkn zphk)NjwM3T+L?GN%|9Ir!|iPrk8YAaOb*HC0rDAI#7Q8uQDD7vQnrzw$yBfQ%+y6|Qc$~N80^26-Z*2z^z@^=jD3#&>8a}Kb~FdO zbrKaw_aIDa%~*pXDvAlvb+EL{ag-jR{1>ev&{ zxRZwwM~)OV@zkS^r>xUe2+}F)9DnLvo%p+{%4&8*Ua2?M{9JbwM$cu0t=xQl{7GIM zlINu7VJ~_Ha{f9;E?-#ZDS9?=WHeVO@?Q~v|Kx+>oSnUs`aG{J+qs5vk4k3G5W-&G z7Olb<6CKnI()S${b$}LP#aImB2NThuJBUV z-&uF}h~bfIgY@O&b5VnM*}uo6m{|Sqp;!~u;;t;N2sPWjivrmrnYKj_*==tzICy)y zn~~(6{-%q5*6B-hx7={!D%7Mecm7A`t|;hk`wyfAtioTQ~HuH~geSI)|S z8fJ-KGrSxgfXFSJF+P8KuHpFetEtA;$zkjmYp=rm$NGhdtTAb+IZY=+Qd^qfV6la* z8L1+SZ`EyjFOl4)-Yu44F7~wN6>gZk?di^%+@2?TlG@Xq#XCEcup=dQky*2>F=PA# z1p4^AAL_M=OPR}CV&!U-7~3O?znA682zV8gV332m82X&OH~!(rKf07NX|glsbceb; ze2Jk&`YZ|aevSV~XD*W6^6@l6ANQ2;^JEh<4i=l*P1;uXWIO!DS3D<}Xs-BdR4*S% zOVE#fP@?ctN+$M?C3MEMf41q+BG7UM5Uj`wwWI36s>G zzAQ5;y>IOudikyei&Wf{Eii`Lk?e|J_K8lZ(svwYZVEfM2t%EO;U2Q{wKS})Z^>Qb z+B-Pei0G}cHp#$rhed>YNjBK0YYENgA&5L&AtAiIG?J#(E&b-HM%h`|&SURME;>jU z3;suJvABHWX`0qW=jzQAqyMOVeZX9vtqB?jbl+=vI+i5Hu*eic6(zc%c5x*Av?qxN zilIQ^x&M&@m`50?Ajg(lfDA3Ue~`GNNgE28$x;r5B6=RrXou3JA52sK&2V;M!T-W4 z(p+a_CM&@6LmpwEV$Od_hNZqm*uy3&!R{zK#4AQYF*B;0AFz5B&j#{dm^Wd%ELxMh z{;mdSs@^fAtV)vJ92+#Ob;Hk6xsHQtHDw&%Nf_QEzxN8PBTO_BM(z-X7X2qwY|^)S zC+=unuaxA3SK8UDSdQ+vD46~IBs~_UC8bqIY{%*L4Rte7muToek|8AD;3X16mRu# z+0PAkvT4)pi+84R$Gbd;9Q?WA;yN7cdU~IS6Rbn{6~+F&B`TwUcJVbj}4y(!M-tIAU*x_hTrKKG8IbIKTmz4!!m zU`{qy8!5*lZ}yF=FoV+!W7foM2mYP1>+++c?;M?*J^Ck-ShWSx2rZ`sXtL(MXN-IQ zXSbhus$evlgOO7uavU+N+-!6G&r_Qwt{9^0!AihnWhhQO6p9yQON&e{m{L$wFvUKE za@2uY%(YP3C1+2mRi~lYFskI?=G5YBbmXbII8?~{V{VRLkANIF?8|JGnyF#f`cBf; z7v27!q0XUwqRu=|+qEFubJ(I*?7?ypE68P~*z$|8aQw$3z+v@QhwRd>BX4ofB-Blx ze9FeXG+^C_-5AeGDN9wn9@PvVWHl2-q)!aH8i_^O<9`qLRv%#xUlzUG3nf}b2v-OrZ34R~kV4#} znKph(PVNtI&@u`#>z=IcjuwFuPQWnAf4>-qnC>2BKDbzSt%>7{u>Q<1#9?uAlNw8y z)a5ybjLTx3p8x-l|0=@pF2Z0vVf-{<=sIEKF=6Z_VR-g`0SwOjFQ}=uH6X}BwzVWw z)vM7|Uym<@AH&HV2RyY0~ng)YkYCf7c(n3N|cjHAI99(Ok&K# zPu;TN@#CWEp;1D9VjCQ-d23lV_OzURVIoznr+J>BI+j74b5);G$R6XH+a*4FW+dHYnkk%Bn2qYL zn@@IM?HX9=BN(5|9=6x8?AR9SerPLUY`b*Oozfc)5XKKmN-g=*o8FYG8(qER8m?ZA zWe!UoBBzJB4K#3t8-$6I!o(#q0i)WOsRepAPEF9eF7wbnx+WVmXrL3u9OEdjucc$v zLuftMUx_PZ%pS((uXXf-;U5&#;GjUN#}(-i0vn=!8`fo$$Y-eFW>zqGpYo4BVKp(2 z`49?^A zL()4U%a2QJyu>wW&O3ym`-HK1{|zv>=)a*+zm{bz9^%O>$Z@QaKVkNLy(xRlh={qy zU(dKPun7$lSw%kMn}pkH!q|TKeT1A#S(EY$a28ki9d1^QH=L&(gO%=SBN(o_Hgier zCOZ5UOWW7ReC0UqNZFlVZbrSMO;XFVgwdC*tGRpU9$fb0M=JF$KK&;C88`8MyKwKW zpmdAxPTPyNgo9nUk9;M9{~a(gpD-RGjIQ|a;56rZX&3lx=S|8(7^LrVar%{GPa~FM z3GLCp$jkZOb)1IAa*RsbsVt<>xHeIgyCa^EANmk z+MCp+D!q}4J{6}2)l-Fyl%sTYeQP%Mj@1z?cAUxkx6%eI)=eH1>8pOe$Iskw<>aX6 z`{4s=g(hg_1(&oRFo@fuP41JauN zkhGkRwAfl5*xhcWTKq6$M&uH;@7HpEehEAlm)~9&7(B+bcbYTI(CrKL49c{T1f3pv9=LeE%84WQx6(YaHU+38HI$$C!Q)TZaALy-B(j`QUv ztN8p0stRv{({1?du59~gbW(bKR~`Eu)(8)>t9QYYr%vIW@_;q?sYAPAxW)TH;vKBH z_w@+FOMxv`7gWny9L~W`QCjhbu*Oy!yPQpSffpLr@j~M@UNj6nr!=1fI*%~a+&$pO z_vk_`Zi0T>oNwA=^K_hm;ukN8v-A}ia1H3{^4@H0b|+8Ghd0Fp9PmL4@S-P#X>0m3(mL<~ zv|f=`=WCWE1NQlSdca=ZX{xZ{ujH(WLxa)o=Ic|b;vhXa01?}6tS+T;{Woeq|noO$`h<_%{tg5a&=giFM&Z1^&s=FsAv!`d~ z%*5fKsaeDAmR zJ$3}4HT^vL8m|4W@3yXc?X}ldMcMVqqOOiQeAhez=vn~!-f96TuayBP8&LzGA7o@X zpH>9k%dLFaXcNXQ#Q#w(*)5vtFxtK7Aoad?q*88XK6^p!SAG(ApQ>ub-n?kJ!3!%F z8@@30Yg!4Q-R1XDfYqp&4Ka5IAx9}XzljVp4sG@~5~jldG#kWIIOJXCc)mx4Ob~N* zpJ72LoHhKGy<8up13XG-=;Dnkra)TkrEbEObL~cE=N@EMXK?hoBm1wjQGpHbJBs5MKch$NqP`KP_Z*|6)#^} z$uo^qZ1R0e>!nMV$QNIprqQw`!9q_=5fztI#ntb~*uehJ|5c*604dIcTp&f1kSMMp z#e0~ed<4>!1>UJkhIO_BJy>Vc*J)W>h^4kf(a~gF(=~IAb100w-Azzm0`+b;)PE_4 zDZ3bHCXI5n)GWX_DT_^{BPi6yVfi5*G%G6*GQXu;!uc0eY3zLvw{j_B&QjQ8XM@>^ zAREL$%ZI2oNfURW+I!Ma5^q`z%Tl7T2Lpvjzf3`zzN`dEZJKtA(ZMki&o0Yl$4DY8 zny;XQ%GFp{RGEx7suEpZJ0&)JaTOW@XSnp$)!2wkJ9kB2zBOmCQ&SH$zG_r8kB94f z4bWf9v8Fi`wPSSy3e9b3qqmf4oQWrj`c|5Y+3f9Bx%%dAnPNTFZss`M4m#_hQ$F#* z5z}4WGr_uZUCOCI7r>aXXTrlzWm z)QGwx4%%QrUGnAj&(vcNg5{V-PHooA?bLMmf!N!ls8h#IOY61`J#Lux=0#zUTen#( z=g}Czrw|0&5ij1gV=k{A+-#}=)V1!!Q10*KI@AU9-C*p2*MC-9i0nwsPyZ4V#r=-Y z{rgVpL3#jm50k1jo5aKG%D^Yzq!~RC z&1WKQ*tmfn5~V7kw>Ha@?GEFsHWS^{%kp&S^>y^Vw2H@%#-?xbG#%?*mYKDAl1|ea zuC`V+vt2a9waIOro7rZb=q8W-kH|}T8`5xtXKIFbv}9E_-)ZwPtLL6;Gv=yUZ9dMg zQ^`D`mW^x;8SPfRz&2l#fQTHK>86Pyb-42BzRYil6mFS>{~kH)eawYB3Ji>)HU({i zS1hm_{p`CGk>2xJ(Drw;6w?S2Ldvcl*f$Q#oX>#X3D~DT>SBQ*tFajA(H>^mhCpP_ zQTK0K00p-`3sJ#}Z;?r^=P<@M1!J^FHNt<(Fpp3lZ`o?*;6FoEhby?xU#&^n*Y@&q6lLx%XfugumbZRvXMC~miy8uZ} zhjNc6>vABo$b7IHGmd%9*~+`t+9R%H(@DzO#w$4k_zS$)-j9I1cusZKw*bAg7JjH- zz1`{?rJ05PB04){xK*|L6hsQmO^k;lYvn4Y`f27R22iSjMQ$cbK+g)$>s(7?0cK`2 zq|o`FEZR6(F$PncPl@e=>SV3frA&wDVUjI3RDobFHJMLFIn&4lEI^7wgYu?F^iNPl zQ8%E4lA0o(k`>qi7e+POz|b&BQ&-PyS7Euj*}Ru4hVFf)ble}7uLn2xo^&|&af7h}?|-&oQ=B+n60J;3OplqUmA<^J&Qa>`TWfsi<+wB_H3h1@haXVi zqO|bv%?lc0R{bdQm;0v)QQrEhG+0b^Rzln0#n4L z5mX)eNq;2E=6m%YSHgY6pRFSIqiWs|DnMW-*SuBaf1KSp{(lj>{jl4)o9xC@6m5UR zA47I3}2iG_6>(BO0jRkvMD$u^Yzqc=3UGond9^z=984fk1D4NBcFX-#&HD7 zmCFVEaS}YEAE|nZI~Uhl8aJH#(Zv{^`-_sX&(srMl_y^*KmL%s{s){Uk(`S2q7#Md zier<$OHi3WrY5idiJqt@(EU+TES*ZLoM9v$VD?CLA!yG+sPaIKh^)$ooKa&S2(g_( zEI*NT&1_G|`YF4&keWp4Ys^~@pd87`Rl2M0aj83`HgV}ygA!7x!YFOSfp@Xw4>Q_IyA2=O0*5V=`A zR81AdlXB(~x;)eA!KL!G3)`uOxIY=rl#D|7b96O-+|R=a$%YX3F&0AITwWNQ)T`VH^RZdDal!#;yq+&+rch%I2?emjc~WtQj@i=r z;?N5NSrjv~JcucpHJT^|$6ujw6h-v*@Mr4Ja!@=~CW=^=*F`lh2F~giLx;CxI$SB$ zLXf+L1%)Z2-812YG(w2C7DBMA%C=y{%^BWHWq;96kMU_R6hkwgB?>RwRL3^aZ4Qz; zkmPFMB;|Z)Q53L$V@srCy8srsl77aVHKawi0 zXyT>X0z-BvV)P{kH8i0YW&|#=*C?FO%<9=yL=8feuNk7W6&ap=uM(rc>}PU{E$a8s zq?&IMLz{8o)hcET!wk>m2t0Dmts-g+qFh&4lw6DoZP(4!giUg!TO$%y{90N33au}| z(QdHDJ?&FgM}QrHcgC6vTDNnDgf=R6U+ zZ-P?Y1I)Um5|7NY#N+cU@id4dbde6S_5m|*tHcgT?3Tn{5c}vNefF{!xIidX&q(5H zNqh%l#~o0X7%TczV&NNBUGj!ipLj#WLAprW_3i?j2&Kd;l6XxLZ-O|b;As`oJO)SLh=4!2w4IrNkOZY>>nj5M5KC zbTG~lO5%MlBG%GHDoDn{8I@QiiRUHpB8VMy zkqVEoBA^ocCGmkIj(|8w7peBV1U3;$8{bRfjwM#Tdx?mXOsSm(ah^~T%a_>3YDugG zvEdacX>-6fLP@+Wi35`O5X8*apbRmV&8x&kNpvo=>P(!kK1&zLr8mI51(kSwnN^>b z#Iqm{(?u%Cx4_;-mDnwby^=TxV&^-SxCCrkCL#@{VhTLbv_hka{2a{E@@LybFE-59 ztbjvTT`OdkDAPIa72*tn^WZ%=GG`r9hmo4*{6b6ZM(P++%PEzlGWKuB?KVQ6v%DaUU>;u9iEa$d(qJ(*RBYoQz$%Ej>t`N9vFv zbquLXNcChxe;@%F4*v>o29J*S&9kz7ooHlTlz{$_m zZ5cvp8n*B@8d3vD%|fc5Qb{Uft_#0w8HWhjmf|$nGL@#;Qke#c*zh~X9J=aFgDw3! z#|KUpIJtScE#pYdMQW`fbqT5YNFAb7lFFEskKfgdeS~aFc{*%aNY`v>NQXpxDPov8 zbhSGjwv6bU2jJv`lU1PGGK;Qh4@|0*h9#+)Mmhzt_;nV zHstjq?;vwzEgC}VI8xJ!ASH*Eq)apB zl_*lYJ79||QT$`=BuzC)&3EgmZlpFKb%auB zLoeg_G5oGA1*RQWZ0XK~EyX)ETOP1P?4HeG4qXjo!j?>($Wr^bp)v;wR)-#sn3wwMyX^GV@n-=k1=M~<99wG zYfIY=ZG~87mo+|bH%Vy2gRqtcU2Zv2yO26&NNqu?52@J~St?^Aqn|M#@e(BAVVUtE zqbc8&vugkB zOwV1mCrUkU8PW~(?@~qQL5fj|I7c^6O}qel1@bL#ml^MgHM!w;2wu&Nvbi~K>VIxlZv6X2|)E{`z1G%ofQ8?4bcwO_xm}ty|2YG}05F_P@ zj`8uKc)K@>3(S-_&o_QuTsiDARl965Eg_SqTRQe(CW>SG$gdB}1GhS`!;9RC4|bt< zUzAO1+99&h-=jEYL)evPmn{p~T(^~Ma;41_FmGOzO=>Q(`R;_y_Qp0_KC(IckuAhC zTVqjc<&S_o*&h}9iHvP7QFTD68oV~gfi&;LE6tChMPF+5HrI+^s&`O&7?L#>MuMB2 zy|2V5q=-`oLK{&A5$=bp7%>q>LM0pF=axAFIicPqv<)Hcr{K_7-zcQXQdH=72u0}? z+9v853$4z+Jy3O5nq-i^x`U)o)LbUHu5mcydd6MO1UvSeahmZZ;~dcY7U)|6y3@WS z^y~n7vl;gQoi6^J2lN~O1`Yy!hk?$cKvyaMJ_+=n0(vTdfl9_|Mh{~R&{qre)B#-$ zoYDw%Hvye3oPGruXyf0VoZby|-{O=z{CfcC83G1|zf|r!uOFfTeVe#-h(^(E;x5X! zV+53vF5IUbi5(HPr;7vzebgCkQL zklKdSo)^eJ$e0sQqztl@XT1=Sj+%ly&RH*~3wP zzcKOm;^z5idht;-wnuMDO%#XT#Rz(7@5qs;uTr-pZHlAUUsGd~zx{__(~ATt$?-NH zz80nKsI{MO+Ds1wB|9v++8y<)xWsRMNRAC9GFf=tQNN5!{Q6H}C>`#oFXFxqNf6!c zs4q!GNJ6q0c1QhI>^Vk7@kQ*fQxdjr-j+n&Qz)b8F@kt~gi0?mYI!Mz-GfknLZM#! zx!7A0MbV`n7ndl$Uy1&s9}u(h2x3;dNJYd;{;*Ch@??sg zC~!wbT4##g6ri^4zodFO%eat^-wQi{ojZZ^yMSqXfrF*M)>FU}mB6k#;Ls&tM)NO~ zhWL)EA~n_R$GFArJVr{YTR@q-44fmBE%wAQCRRwI2gLF#pp3NwrwO^mPJkC#nU|JQ zNFt*amqt+=jS~~s=~bW7HfZd*1so(4Yqo3_Z%d<6#Hr<|pEt`F$;7pT(VOW(`<5-6 zQ#atfXWql5ea>DRziApnpYfq*UMb?{QQE;nAI{mdF_k{L9VQ2cW#V@qk21}^gN2L5 z7ZfMA53w z_u5L;J_|W^B3Qb1@#YXosDT9iJDs^FbzSZe(zPAB&YdM@6EUePH}hKz{JzJ;KU+!s zlhwrcF@K0L!05aK2`)xAqqh$!{(i zMd2F-VRD#tET2MEVPn0Fo(C|QT-~}sekBkGU1-NCz!1a97Z!s9<}=v;d0^oUXzC|ML6zlvKK3rMGqX#952-^ol6nR-CY2enr7# zH;2h!xO^;akx)4dlb(_>pJEd}HAdAG%?<6S4?{7PorzkXv;nPMI%!gj!+3DPF5|oY zsMJ4i{*f|j=4pzQ9M-RVr9U7OUC$9Oy~~Km%43yUw_)YS$15k#g#AcV6;%7DP+*=} zpl=@Ne1mu;r-u5dj3vgsRTM1xAg4Sz%I=$FhElTRJzW>29Vj8IMA#O4>M|MZg?XQl zN*UQLf8L~=(i}Fcx0)I|zNkfC?vBv00cdkBKwHvJTQ}ef9rl`(ZBf+u2Q}VZXQL8m z4GLzHAd2=oh_m~^iX}30XQR~b1et0dSD(-UtwDwNtw5D~`>&ud9q7#b74^`VQ6`Ba z=b~if=qonCt=cM|YPrR-*ZomH(Q9ekGG(WotB>Z_^T5@;^1H-3X{milTjcdjOi6mQvx z?6_oM2WvmlLfZYip_ijABOU65{EO%HP;S;gRQlvl40Q>6;eyY_jxYuoo%=YQ(VvIk z&U~Qj0MJvwSP1kT20D)dy~h|&z%c4_)kQzO5Dk?qF(s%H1bqsi<^uISiQgyDcTM6` zXxc%yAX;704-%vJ$Ul1M)$WiE;iC5@E#badNsb4|NHVw*=<~whpaZqh1zWNnuZ#Lw zvp`h>j-jmt=bF5M;%Z}s*GQGpM9$SKO=L@O3$cj}4LufXO7C*P;3ci$;pzN_s14%kMf9q6l}?H?WIlX3YF(l-2{E@0Wf(GWAL6SC8$?G# zR4C!r=|?+qDBoB!OSg%HB@E3MqvUa@mUg4%R0}FNf>qqrU#BwpbelXL(IUfIw!ACN zqQAW{YKyqh7)8fUa1og8S0xyX2)5%q+tGt+Xd47oh+64`s)F~DeGJr$2JJ{Pl+{J3 z_Vj+d>Pt-@tNJojyY6zk7=QutgQJE{BE_Z+o^G~jgW8NsiI7dc?E|FEeGhu{@k5P^ z4S!$D$LZM#J-++!$~6r1JcmwgUJ#;}w-n2+%QP~l(#h3|g}7`}Hw^`O1JU%qghuBA z;}X!f!kG4J`t8mD1~P%JJfQCoryu3tCxHGloN^ZEImaorzmBwQ+Pxnav-EppyZ0jW zL{v6aS3XY9F6ePLk)98lsXuPjX6h)0&*{!a9vWnG#~|;{Q|O+XZ__Qsx;*&7Q}?}? zKf5m@HXk^3e{l6LoVw>?o)_?PB?usBTQ+aT`|$ESSYtb-TLE*oa;=RPIPSDXF+cgI z?`=m-L+x#Sj2f^ARe}7!CSSLTz3ov+->VBE5N}k->mayAvgloUNCQa-BQ4bZ9rDIc zKTF%t)(itW`Ud(~0&=glIkzczyk9y*i|f7v@PaJ~^4%J&L)WAw_C$jzVXO!m5;|Z- z;7sIJ9PRuUuwocicrt#25{LdtkhFf9A{MVl zMP6?^ZbV#V^B!ZFMbveDL^VB(FDFMp;)#|APuLS*u@aT|^BJx*N7svbj4^dc`zm zq@!!KdS>577w@2oz4<@M0{8C!i!e|E^xpp0gswY4U;n>0S*BItc-<~tsl_zcU4v); zA^trBbUXis9M~)}9!4d|nrD0ps#Za*PU7Jw`Zh~tbfZareD?W=QOZft33N165}o92 z-x>^q?5hq;Yj^$)x!`5=pTX|{qvt9AcK$6=@`1i1KtH3a6u;f)8Lt36tw8U6#>bp8 z_O}QN?zMtUvEDrNKl&G#wohW(?tBVc0#iWWj;{&*nO{e2*R=m%z^-1to!A9ioG!-G zz(6(QS)i{5=)S-y4M5jjpnr%{W`W+fjEg|eGSKP#EiePUN+T?9c(a@i#Cw-$_HDSPZ*HL7iF^c( zcz-GyUus`Vb6{JdF;!E{lVNfCG;NDW_+jg2qk4miwQ*v{c+|#}ZR$ZemR-ooMhdIh zQlri7|F<-U6TS=7Tzm-IC(46td$Z})+VOGh2f#yedx{LJMZGw%OdYMe3JPdhke}^K zqTR#&Z|Uf!r`ooG(7Y6J{V9oco<)`Lc(|gT1G>*c5pP?Nxh++6-?J#*{a}0X;4_jL zsAV~IBSjx5~ z9iAdh?heTHUIY3&Azjvl`YC$yw7Z-(4&m98U4GiF|VdQo-g>fBZgVcK0GPx-x}H-8*ft zQoe&EYNlzExb2l2XUL4e9pqL{|rLIiOFL3I1kV+I~`zc~i_!tPbaF(LG->K1rWMGgx^@q{idzi(Iy>}Q1(8A0jFU=AOi+)bHNHIt#fRp)7or{FrM$zc(vu*csd~8%cjH`J3_riY5fw_9ZVqb z^4h}_(R^zuTZT0=EqWW=(ekUA-=0F~ME&+?(GE*pInwk_N;rZD`Su_|t(oSzAB99W zO*fdk;fra}>N&DuG8ycld&U+wDfH|`th5i-m@|h~h_3=%NRQqkE~Q7)#wAG%o!O)q zdE21dwJ)@@p;e0$86kW%<}qm$T~O!T4|Q_Cq8P}Crc+H5Hf~d6ZD|N1Z-54D^sljT5x4__%a+dyDdxxgUA+fkKT6;ck(W?hpWVk3=?!s7fuSF=&t>Ro(w7yLv zWKIg4^4`i7+yf|&zC+T*gaVc9`60!v8 zKEnLAPDJHIJwOp#QDU{i(iEt#ZP;xxA&4OL%j8IvmqB^pC@KF%P;Is<5O4NG2Q>~W ziCRW_9; z$qbs%aPtiGpa`G;7=@2^m~`i=;ZYl!MLW8UU3u09W+jQ7{nRwJ3g_OS7G6-eryYN!wPpf+z*@v`p#G_vILu2bLM`NIYESvY0Z@|#8bun@>$IU zwH&M}#9PG#b%3=JA}N6!E_x5Etl-MB4#%u5F~y*AZaQLSCke3zN89K_V;3e}x1(WXDUp^1NF5qD@h>`UYPpKA&t$y~XTd6q1j0eEMNJrVdA} zW8rYL^5S9$Yh*nacaFlAJM48om*YM7ZgxehZF>7u{f-wi#nGx4G?4$bx{F$>warP- zMksE$`@;`$7wee*`v}JesNGj$cF<0f;^V!ef1!onisjoFu%E!Lz*8>5NuX~E=$rw1UIP8EIOPq{y$B2} z!4KX$WnWF=sgBe3F2lS{>u|WUt#RYVU%V=#06E`7q;LE02wmyGKnBp4$thXCV7}ASa?m6oOvQg(20KD$wb-GXi4p24nv44&tneM;coZ9L z2?}R8L!Q#Uick}r_^?*II0Y+8*a|mWaUAGA2@IS9`YT`;FE7#?jfT?Fqhvq5lSjS3 zD36vaLSNWhNS%xD-KE|uPzi0$vp}zhu?Fa=1-j~hfqJ0tBBx&h`kR3+FKkpEyN;fw z+s*htr~Z(%Ep@ATDy$U&4PD}BJ8keW?ML~5#w~ZHTs%W-M*5OyaDJtB1UH<_O4Hp! zaL$mEyl{80SbxMVgSQn?3AAx+t^qyQfzEEAuZMr%=JY#2|6QPKkbe*T?qAV^daRqe zA0n(NVqaynIEV!2BQTtg8OMOWaiC{{hi(L>26M#gsYV6rogoMG~(`;!O~HUW3vw4=j44 z5+@}wAc^xJF409gi(~=lCzKNRSF`Fv)vWp`Hh$&41*MH~mXME|IS-ylKTCrPcR}K6 zb@VoI2phB9i{Q}J0SFN07TlRW1J-3keBsPE*F^8^>o`o%Mb$eg2d~n*{>$dFjP|kBmoiwB#MQS}# zUCw_;`imG_w&V90V|LoVlX1D>fh~v6X|~*FiI_>{Gl#Cucwoy8o%5dM^K2^}a%5Z% zoFlExNbNVIoHme*vRw2Yz=jE-|_?k>cM4^y~q8U6B3xhJ+0XaeUTO$m6fR z*(MrmqQ7J&9R{^3xwnSQ@$F}Y2N(|n-F5u?EimwoaE*TnCj1u7Wxgi*Yw91xTx0Z_ zH4A$Bwe!(yzl{5PIs$+`SH2}~-BvnW>#Ml9^*?QmOZd~D*8X)|9L|lQCoS^(z8g3H z{u}B~>A6ZQb++hPM=z7WzGW!-l^8i6{TtPTSmCLsV&T)R=oI1e(d<{OxP2j7{MB#8 zg$vPZ#mwdCUvAvACG?qJG)GAmPrcE<0c!)j)k?!ds1wKNIpGU$^skdPZ>CL`VR^~o zl^5aoZp*Lb#`fQ<$1|<_J!y;$c{-RFy-qHDPVsCf^XZ=(7s&g{_Xu{a^WO_NmS0bf zy^q-B($+{qP~IA$W7Vx;9itVIkn#KLIB~mMaqk2f_FH+nTf_a?4>tb1Zq_04&aeapN^zICL-x4sPW?UV0PH++{GyX_z2{*g|j z{KG%e;SgKaei0Y<{koLYq&S@8B04TcTcRg=A;rG~QZjZziZ%h*KpW1(N~>;+)+RWs zk`LV`J>D#nhVQ7hl8#ua$s4FKt|hC{BBu>K0qS=k+{Y6}& zm_WlW*$vM;ImoBB5v|dHNhW+jCd9^~TZpB|{bSsBe;fDR-vkHmYLiu^=Dpqysr5l5 z+8{*Izf$DaEY(^bui;c+4;i-xof~^U*Bt%D=hUICOG(*C-$+f0TlZ5UJulq!=ijf^ zul)gQ1*$Wj$88`_{)YAiw0w96smLLNXd0f=5-s;sQ_(y`Ow0Eorp|r9?ES!keBe|8 zqWonXTdQ=tzyG`VwXygc`x`ZVbw7d&>X#f}0=9#H}>l27D2;Lqt6GMt?3l__%f3@T4cq zVDQ#UmJFYTcJ~F~U=OhMC2-RDdzym%#r71pMs81SYxI{X8|kpjRU;r~5X9p2-;<&K zEMV&y;6fX)qVIo;=>u0IH+|-6#HLTc^auSg)cpXM_8!{}6yjMNu&`=VDliL}=j9ri5KK_9i&ufO~L z=g>__?^*wW`l6sMQ%YBhogKrz=wI@IK%tscvKaG4|5i-;Xo^7d>5gkWitfKn|D}#D zPTajlGv$UJ3Raef0wgDX7@uSZLmfK}7uFuUs#8u2SL4mlftYJNmk|D?Cg9W+T?cvb zIdx?pJdZVX(+{sJmi%v#b_1=TtR0qC+yGAB0tRlA)pSa#`v%pQGus`JACJ4d-LdNN zxJzk{u=OP^-9g9ailTJKDiv>khU2&5Xa;Shp@R0^pn_J1l+Xv^iZ|eMO>lNcN5FrX z;h?GW9fZYm7lrvNzCqwy3t44;%^SDcyY+jDpo;^DbD$qWp4|gBJ_0(Q02iJDyIufW zW`X0cNYSTXi_6m8 z=j>+O!aZaWq^BJwS)WFHQ1dD3w;~+IyuRp&M_8J@+I3N)1N{wk9io6 z295$VOM%WZVDSauVjHlt9azu-To_^hD6-pcbAWf=<^b=Zj-9>~1xjPu-x0Pk7VV|q zYy6Cb`~HqXqn!V#9A`UpJNoBM{8FEiaDFg4WrHzc|Gj9MkcySt(b_?@rM=2nK@-Bs z+8AnGQ!(7Sa(c-m(Kt{4+a`*8qvf2jb{;!*3t?1#QIH9m)YrXcQ?v^YEhcFfn(vX% zB@nqLVZG@<-u62r%ak$-A*Ph8_oC?>U!$w9;HoBl;*Rd+S$KK#IgOL$ugRHm8=YFd zdwmw&+Lb-`EH-FYURZ6>t{mY*f1&Ra+5j!y_eIkgt|_TMZ#MZ-pGn;n9S)eBVD#to z-DtV98at+y^1U-)^zr6h-N$$5$;T7$ab%8sY|?bTBQe1`H47ix>DI1Z=nt|%yS6+) z<=4S_*mjdkPnC?O#PmU<^}7dj>%FkPcap5{e?`{&?4@d|Zw^SajMBA>RfA|tkf7Iw zHwImIYe?5==yE~V7%}NEZ8QJWpl;ighxh{%#Lt)|{sQyU4x8i;82Am~?*M-xK>QqH zQu8?*+f_1@Rv+f85N!X5c@sB!2Ta@jIu8A7K6>_@5+h-neNU zzx@5h=k!j%wxmD(ymeh7oerCvK*#wf$)oZNZ=~P4PY+kurN|S>!elWVKk`i?4aAmL z>0jrP9j;=s;M3GC8#gKIb)=QP{SE5c&r{bWY)RghL@zCHg29Bshtcv{N&gl(LcbrYF<85TVr?McAJ5xp0ac@xT%4BI!Gn)B%ao8SbFu z`?Jy;>ROaxKzq^Na7f15wR~N+6|1}1yXiR1b?V8&5N9xoX2SG`bwGyN-&$q3Trm{r zq%GpsqiFTTUd=G$fz<}lnozatw+e0D6C0CT*dQbZlhTb#K+$YZJ?p zc`-Z+iqd=~UGxCa6>^Iw}XEp1O7eizE(7p<_c!qY9?|Z3F zF;?M^twbBf??j5#=8bOGddGw!}l~{3XVGBWGFmy*u=#wgu4WNkkJD}-3f31@KclhmVBYD-w*4> z(dlTdx?<<0*#l$y5ADI!G4j9-4}QTOh$9nJx^+`}KN`RF`-s94wkyrp9fO+49kWSb z7%N$zT7RwO*R%(Hv&hI*y4U(ibN6Z!cS{h~{!*=4_Pu}IYMrc&3PaWGEjRVE7P7Ug z^*%RrwX1MDo*Q~w3)iNTa28Mrd#Qw}tugvh+Hy)AS>NE9GR-JgY3_%nsWS~o9rW&_ zXrGGSKp%`%%BkW-a5-7(xm`Ii#lD=}Im%!t+KOqk(1$jJ#vs}n@}1Orv_Z?lwF%>H z^sIrS2&2=@{VSuFkTyulo?AT+-M+mTDo4Q#Md6Q|=~=D1W7V%YuC zPz*+*O&J``Lsf>mpQtyyt5n9jFQfSdT)s^-3_UG8^pGT)LY5+YYWE$#Zu92kq|M^+ zD^t&GE?G-$QO5|m>nPUhVMjUjeaajsW#b>Q&nTjvTA3xF&=kqmxztI7`H#toTjbI9 zYptDuWwd(pDmszQT&1y>2DR15%de;z`zSUvGeU%Zf$sM49A??YE}E=}GkMZ2!=PoI zC|T;8p@Q5#C?_lw7ID%|Uf@T=YjgDtH;ZByzM%Te(=U9m$XbDW_L%CRNtm^W_RSkA zp^0fz3$tBldR%KX<@{-eFw?8I^QJD;P!q2#$=Q+86&tQ}EmH|Nk1MeczMj26BlR_N z_JowAb#&+>efM=4a|5pSt2B{EMVNXg!CJ=CpVE+*M0-E|ED((6q#ZE3`I9LIoK-(`3!GU1*$Vh|g`pUKr-o_l9?xfM}&o`7B#qnmKG$X!~pifQ5s!vef4 z0DZBwOm;0tE32}rRMZ|w^F?4t+p^V=5p?%{HYC94Jh{3*yH~7YUh)@JC@W2w89fn* z=?xO|4AyvAfKQ4!#gRS@^m~-LuFNTDlJJAwU8Qv_7<0-yNO2@X-V|DA+aU%m<6;PA z#9?MyMLDt=+exL0P6{Y1g8PJV%%15%gHb%MC`*9oTh5_#A4(j(NTryxGmXLT1vy9p1UQrCv zT?CJf=hI{4&OUvej%+dBJsFfw&|T3d3G2;4RJqn5?#_rw-Lh%JI=QfDUcRwda|3HP z{yMJz^+0C>icChxJg12)N{1*)^xbhG3Q)njJ5{5`jUV;-Lrn`rnVf_sF5uClpLXXjb~>J&6AV1tlzvYF_{h^P52=x z;YZzDbq;NnQ{a*ltPt7M^0a2lEI^ephC){rSzyo8VwAcR9ll`H5LoI-BO>6s#Py^J z=w}QxqiLmxnO(Sdx3*l;a=3nJ%fbed{m$-2%PsS(A5qMbnx#^RP`YHR`1;n3i5Me zs5;^-BHVt;>hVDW{5&hW4tv};fS&G0pmPDxCwiFw}8%GpqJ5i z2fqV-oYK#~?*o00f&M2z_c*6daQY;t2Y{YAcxUNX^wETbe81yIPu) z!SZWI|6*_{n_uHEcuE-Mwc z3o!3AKk`8ZQ-@^8u~b<@;k6&65oYW9^&|nifb`K>&2F{(^1&EuZymX|S#*j4GMxI8 zz3fUJyRskX%V&qE?979K^%-zzXUYm=@LgZwxj`?RIS;dfBS802R&$K;1kiU9=s5*+ zpXT3{K-XEI_ZQL~`1rnfbj0%yxOka5k!RR}N8Em-Kn<1`d@7QSPe?%~M0Ns0luKfQk2XtKo1}<^RWyUU`X8`CP z1_qvR`ZJ(w66kpj^uOZY^W@%|_>Z53<(-I;-+2f#^)eKdbpR+l5hFi5vP$+4VX}%V zEst4s;}NV;#Kl|lS0m2OcZiI83Fv%};^#>q4rZnY{OjnhJ`ryuDvivhdKng;40XEh zBn_>+$%xiqHCgJH?js+U>d%mko@3vTb-okduyq@0V`0P_j?A;@<}x1THf-ELx9{>B z&W7Sjp?y~TG8Q2!yTP3b9u~`ChQA8vKFhy7jOQ3@fPq?|^8){F0D2o?_J)nylD2GI zHHz`FCpBoEJ%x#XN`ktqV^ac3`38M$C7YXcGmLOuWm9f54l+Ih`bU7Cr$FB{(ESSN zT;Shtfq^BU*ZH3a{o8@=GeB20&~pJ8s0aEl{wH$v`+pAZ<+ah<*hL$>>^mK^?IW@s zLhbN%u-5gWs3KF9xaPdpZ zQ*sV@S_$dQO5v)Ji+Q5sJk|@fn`5n9DUm}G`_1pR*?NpO6sb$5FK+*C8|{Qm{O+&g zV(G7_uana_WociJpH`jhjvo_&n<(N#eu_V)P|8ZBC)yx-{PE$pjy zVqbMR_DSc|z|np}@mDD#_q?)dDYn=0uBMJg)ToRbP&YG-0WsyLtsCgfOnNJS2;1Rt zrZ@++^gNmHu*ImV5!=Pzqtba7NLAl?s9GS@3iaW6)lAX)(3tf#vc7|ri`f00eF0gE z>$D==SBoMXyl>2N8t)I3zo9&tZz)evJ@T{=YU;XcHFdkkj9JH!bspRDyYcElRs*v3 z5*nIE)&=6+hWKuX_g$o`*ot7h|CZ%vT4A%T4%7h92F@v^ukW=Ct^j!KFNV97?Ugrr zm73Um56w{CEz318J*#^6E;{{WsIw8y_%A8uit&1S9#q$%$jKX`0#pN88F)J;JSDc1b(Lrhacl3U$uLK-jnF2(TPHk*rF zvxhFBEw!Pb0`0KO{yZ=_t7V%^R*Lqf7$wf=oAghEh0NeV5Pa95O0Kujz@_Z8I?*i4 zP-#}$PAmI@G*koaSb}&_HcsJ?dzUh zyL(AEJs$18s*DnQ1~CCRGi-TUDuZTfkQ{is(ZbSgJVUo(dbk-}0`@f~w~e35h{Ly0 zZTk?0L~UXvYTIIr6D={(PI_pp#q^6_T36;@-_c8-F-S6)t2JVsmWZEt)%_u)d=)8s zpgo4h_lW7E?c|8(E;slg#z$}pM(aawIO|Sy#AudQbdWIr821|!EbIk@Z4m7=;$y0q z-$$-sC{(t#1c#lf?=^K~Fi#1zNxg1Y@I8)tHxeu6#!x~tu)_V4Et&u**CYJ?VtG^K9-L1TE6m)p|YBrrU|u z<$N1jIK3~fJa1Dy7stBnrQ1+@xMx@lEk0QKN(kw}%s%cJmLOce0pdXrQC4S*;@%Cr zK|sB1-=rsRQdk1Z5N*1N44VrR=s}tn8}B;pGH5MF>JI*Xp~x5~^{)3Qc%Sp1sBV%N zIajny0ZfjL-89V>jFWK7oKF zJBPIxi>}|61AbBy_YQY?&IP- z0DG-BSr)A#|4z^Z$U;dJxqYawhal8n1auY~YYInHesE0bb}bS_TAx-;?8$~oB09Kr z9DyER32SqszEIJLxjtnqp(k|W>RroNV$o_CShd!|X+d{kdAJ+pH+gL9M7*fKYaCQ8 zdU#xEzKe=|4CcCyb8u{HlI+hd0z_$lxZdB&9zq`-I1yTjblhAs#f|}c9kygYRQSrc z2u?$heTy@cnyt5D=U|8yw`xu=DuMS5bO*OML+&swZu>pE7H7z7Z*w=TH-%_%=kD3G zxcYmrv>LJU*1%r77H7~ZGKXwioPm-ka)wB#rw&5>^(2&Q|H~n*_KSxPY%0G&MA*vD zJaxgK#HR8a1jUQm`_{^DxXhKm7lq}!2x|k4KvxroqnYh*;S?`IqYeD1(*`NFC4sSj z;DJ8&hsmQ2YEqER@L!3+E^q}#*w6U%mT9m{G(EI$HTLXrG#k&=&`Pw%X4G+X*j|S% zc?v3=?Ht|?C<<7`9AFjxkrF zsD&0!ADea;=o)}7WfP!Xj)liG!{JTx!BIHI@s%27*;H`)`j zx#PZFUv{mqv~{D1cu_QJZ0N>o+{(*F$wKb~_V^)t{0JV0YfeTPV#k<$J=gDX)LJWM zd5H6e7SH~gq>qG<9?V>TaPJ65dJO0p2RfeteUm`f6sJ!E12dd53-rC>->-rGd7$$x z(7OoqECXHdfdS`#A@rpIT^T@UCeXi=(|0lE0NuHqz8C1($G`Xg7sOTUrDewL@5os9 z0VKrJhT#~o9|_Kb;CT;8UOgr6xNSOek$Q>u@G_<&FnZ%N`^NxTnYwi^^L<20cp zE=b~vBxXD#V(~E$dl{XjD)HDeR(QEEAmUOS)Xu<`t{KgiHstjqua7y>qamb@BX!Y>{25n(<*ka8MJXkq zNy!dC3ZCoNFo$%M1mIDv&Z!1x9Gu>(x<_qDokFU+O-~&{>MT;HDV4lsoNdQ%ZwD~< znqo`lOL)}(QnRJtB_v|MKp}JJYQ;;~;?+5g;LL*4+No>xA$0+%&kU*KNL@i{?saG` zWNhlf?-9nV8;UJ?v#@1kRX>(f1|MruD&3-9WwZAg89)RKPI%vd_W zzXyTU_Y_+KbMPqhwPuU+HRUbF-hc+?NRJATT8h+>A>^N8bUjd{)V*dYIH^(cJ6H-% zYAj+7=@@zqj{>@;NtOvt%|qRG=R9dDN9uwhwE(HrNNpL0{!Yf$NBEuP2lg-y5~?7Qh*# zzC-F#+JB||nd!il9Y9wm(3i!(cLTk9C?_3X`t)7QD#rx3FUN#EZ}`SC?Xg*0ChNXX z4-gJ@ey%vbM6M{O28SfQTOl7jF0$_zUqyP*?fK=^+_<=k8<*k6?Pc03bR7Eg4j@W% z1;Cs`K&Knnag6NZ;(NBt#WziIsp-uvQzri@rk4ZVgp#;#g^34On0N%zYnkF_oC7g} z*v&x9YO#9_=~<^?Kz9|epActJ-*Gx34(6K=IkGxEjgOn&mA3lH)miD`Pp-~i^~mZ& zII_A^9$6j1;nOF{5jA8qR}5L>mg$gc+E|QoupYMje7hskI{0!sNy)f~sMjzyT*B{$ zW=^>b>EF;}BFESC@tqI{Oec%nbUu$eG$D{iK83zKpGHRHwnF7XJ1f2pZ0!MN4*}C2 z0mnvRP*_{8q&vP5*V0K~<21-$gweY*Bdl3_5aG#JP+YzMY+VAbtdLx8O?Ajm6Zs4W zT2m|NjobelQRdTt&h-DL5Fbh66G?myVr>Q}fgQj_#_FB;ou3CRAw*AiYKKER$o9q# zhqhsNPYKnj@-rNEabX9UA=1v%|LE&KI~=knZ>=FMewH@0A2P=X4QJrKGl-iwLgL<6 zIKCE3=P1`S^B2tgGnu-25BOE!2Ry`2%ZL0NLWBJK2EM0Oa5lXi>h)k+5E0Uz8i< zX_KJ3C}nxo;^9Fmax6DC>~hc=tay8X{;!_~_XXvISh-`^dH_^jwu3h*uo5#h_z2V2 z2gy)38A^j3n+vVg*{pRg-=y_(gS}Rr>AMX&jl`fcQ)i&i&k6&Y!pFOzaBGVCKFUui zNk1#wq3J=W;q??5I9yhqFjxXNZ8G^QU5?e@njGC=11-qh364(z$A{6+7$7uN5u4MO z-sezfZ908v*Iv|$gAnFobQ7*7relv)%wCA`umCTkkHv6BTi8PtO{z6i$K_lqNf|M& z&^Fdqzg8BBA*ZeJ!w61lJ7Ss$(PX98tba{oy3PO zhx%r8B=k`i;&m1py&mY2j|N1?KF7usI)j={^G#Jx;WacR*-YhC^({4|S!%uEMWO6N zRGRu}7e*H>&7m;Qv6YVo<2^wZr3Q&5d1Si#90!X~Gd*p8km=?Q#m4yXwpW{N50+-I zYH+{7`moI5{jk4=?I)C-F7;&cHOIBVc|!-+mro5yeNTkl3lra02cY*n^tvu^!Pfx; z^=!#S{@nJ4{gMnpJG3`tp@2;Bx3UpeVT5@QG<3ai(qP5FK?fC};n)iU)%p_WD0kBB zP~sk7RkJ|fE1>f=r_7V`PnBVl-X&bOAwF?kvMds_k5=I;g^r~4Nk7r);j}F|VQUI? zAF)AiDAv_E)~(-4UkM9GYOSMKc;3Ps?;;!M{C{BbPM~ua(4Wn}^Etf`=(zy&H3I{! zK<9H#32@3=PD%goluFw4qMiAd&0EwA+|*6dC)GF(32%obCUEGI|IN#_q(e#T`VawU#)OY2f6h zn>c5{xqXH>ow&>BrmIw4GJwvVFhg&?{G8cPuwtpi&|C(?4;7;%C@hWw0k+>~hOFo>elv4z<@!#<=&w7_SS)*gt5t)Lrq+ zP4{3JOKt2js1|jnNW5#m>gAhdq_V`J-nzeJ8BXUFXoRKTs2)IXM0M?8h<{l zF-(7?BgVlkMYNV0XFPU{PRMcpG90nd{(~5zv5}?WXR&MjB|AOi>c;WCK>S}=cKVn z%u4V^UfxMEG;j>YxsQ`^!Gn_$EoFR__d|(d_N3i-B=&mkMj&y(3oR!MW#lUhE+eyb zie6JG$uw&r^ zng*5FBZ+;II0RzTMNnoL-Hj^ojU+BhV)|(!_RvK#;1WwDl&amQS#{ZIwy_dKKT`^t zKx`wF#OsoHOA_ybILnmsW)S-cC2>{~7bI~7#EceD8X2DvO5))PR(-63RiCOLV!>q) zyBQY=C9zEsyCl&EV!0Q@VaA**D)EIRzLLbZAU4uPs(_3ottzqL4680a!>UWM^wLci zX*jwH>?D+`FH2&(B;Eiqtqqh0#&JSPd?tz0k~jxqemjU=j0=SP_#&&4G>gGXYP)hZ zt+3LuO-y43Q`P|vU9GGnvqY=TX#!^soR(|g$eg`MT|{a|r=I!@sZPuox+s-)#xV9= z$M5_u;1D6(l3xW|DylSFPE=7|FY=BtN9t)rY6nttyOF<;vFWBFWk^b?)}&0b6wF6D zm_s^Ns^C$+&dIHYOmO@?y6q>B+Ktq*TY73EQhSj)MX9s~${6tBcinB^5+QpuUJZ}x z&T6*IvqUTnX7++ZSF_K;7PrnRIt%&Wc<$(0JxCoyYQG`11F6GEU7}Qy+6Sz-3-mG0 z6S6I{XJJdLN3$i(1BsYdIr|k`3Oul-QsmBJ&ZX+iY+T1*wS-OvnBT&Bx0&oz#O_-dJeWU=$smGCc*LE*KO%UY5=L@hSXuC z&hymo0o%gZ_z=JSjG4oVE!j1&#a*M>QdC2E>oDDOF-O)q4^o?u`rr|yj5D5ijNjFS zT$cScuz5yNwd{Yb?td-WQHLq5v@C?3~4lEvu;$3KhxO^!c{`%QFL^i%Nt z`SAuSbgQBF8yuepB|dE+ef~GFFgV9uyBv>^!c;VAs@XxFUw}=X{q}N>G~38IZ7b(2uE}K5azo^cn zZrPeh6?KcLh=RhAd)X16{L{t_$?HkP>%u4&&qJ#c z>8&W^dKPk#1vc{UCZMN4iAWX&Nc9gQk9&y4-UoUf0-cY5UO%UgAg6t^ z5O1zHl-6r*W_Gu)VtYFbdBSR*vgiq*?>VQu0J>&4J;3R+q?r2F=2q%kpSDuBntqi! z*s7~a-zx65qBj-yTT!>gV?^3Lhn)V`K=&IM4#peFSmv@=;A@L?jeBTu_$o^|c9o@^ zx{9#8W6mNFWT<06ldHl<^C@-dsD?&$7tg+(AWP?fnzgBLFSr>m7)D z6RWxkbanvU-9Y~|(DxD;IJchAbN2_NKL+}q@NY)n1pj6Xyu)wj(hp?#Ci-+j>c*cE z=xDDE;gm*x=^K8Uoi@PEcTMeSw z7~O41@i2O?F^AF94Kvmy+Dsz8v5vYYZa6k1t&?9LG0rwDv1{*!9eu2CfTVx=909u9_U*LV+7eCPWL!Am{e&4t98{j z-lEATE2#a|soPLZ!fyB=G60eOG!m&-Q2UG*Z|n8ap3!w%ua5SN`>b5F(LeL-O9p&N z5qBwJ2??%DnJ`WXX&4`zSu)`bC1fEXunP(L!wbEzJ-rUYLk%rGB}F{G11EQf$~X}+ z<5Ohx>H&n*^Av3;0MxX1Ssp7lyfd2jb`vCtgfWU9@hE8sugGLhm zdA5(TxC^yQr}ax0aLIKTd7UMUZqUqQl(DF^sG(uy-nEQMwoH+ShQXE`zpEM5b{7FC z)r@MB#it)5Yd!o}a`-$d1qg`_LYr=DI$ybfM-gAuSHCh>HY)uDP-V53l zuXd#yS3LE|>AnbbUeZ|O8Y`2VMw52MIiO0;!4+Q%Yxc5~R*hAru{ywVwrN+q+Ldlx z3A7`p{~AlV&QfjweK&zlAJEgwzx#mRyZn2A@gC!S#)piLNYAI=iIanl-;1d?j(-w0 zZyn;V{y_{3I=&PW_Z+_xCto^#xglXgg19*A_|2x&#FTaG*M2UXZydjt{}GvQs34V1 zv|`6S$J(`YOkhfKd|WbpPhjnrG^L=O0UI~R@%y=A^qxcf;y2`Y)5p@{iX?7r{#YjN|w<8yKKf#cWW z)I-N_#MC45a@kK&oF8}mQZ!AFj~SEX<#QZ5v^bR}d;G)M=S{w1bia*7PsfUh~t+X?IbMpU}r0fb6rzpUmXx);?8g^(0 zm58}vQs$Y5X*#3A$XEo!XJK?38O}$<2v``?Mn)zWt~YSW!{}oSFuE2<`==?Jci`-H+Q^qo zA>XJ*=f+FAncW~N{ZwwfPaW&FZrQm0M^kMWnj%KZCUMK}K>OkroT{2MIS!L<&r*mJ zwwbMc<9DFOZJ<*aRCd!S8RTQrH;6-zN#&7eMwN7Gv!bl-u|XNV)x~PgyfUbX6}^u& z1@wFaq5}}UW`j69;#l_s?Qby#?=W`}=Cn)GTv1K#7E+racg@895tN3B*N3KIX8B=* z>!7q@&!}!gtjL%`2PNs$z72ZGXQM`XYBZ|pqL7tev2P?PeAcV!C)-@c2rKN)?gd_b(79*u}(WGdrRnvv;n(ix0LlFvt zXt5X-rBahptCs%HJKyiA-EH|hyEFSa&-1?XzVF=UWLGa$@0i}y_NJ~k)yH}7%hVTq zME=pWcX&*#idh@8ACu!loh}V?Wo4%R(J7Awv((OO*R7+_S5>QYp>N#IL*)u(6IW|O zm1rctmid=o)t>(d zU4EbFllRD@wm$M`SdIXFl8^Mh>*0g@!*e_3|86;aqfS-%fo$4=hvZ)p-rxJKC#CGY zeeStecC$wwP9?nlYj&*#96Va4!aumuks;&N@HJFRp>JcuSp>L8J&QvAx!Ox-M@|%XetwajS zn)jx9nY1S)zrFST$K;P&-uH<7tM-GdZOLzk`d?@J`)phG{XL#&U`3CRleYA2gF2qg zXrN~KV7Gpqg}$}*Yb*4PMm=NInz6lly89Mv;rY_H^Y`qeZ^4e*C(6|S(jfbu?4)TA zs{8iG>W)akrV_uuz+9BG!U&eyzM*PA++ zPLu(y(*Wso-9^g>kaZVY=v${2H|mc%i!1bfq!y%2Qj5?GZ=0;QJ*GC1_qWyFL29j- zLf_z>Qin6uYwR?yvEJ12rq(?5j^j>jli znTl-vJ^QSN-k-N!&g%{2|Ec!k&3`0i@pbAe^v&E}x28hh;(B!y`u3~R`mUsO$MmL^ z-c;MI-Z8wXEYHu0dw9=bad(#vFTPdylk#@JJPZ$(vSxt9~}VQ&Y>EIx>|rF>s^ox`$JAs-mjUjeO>UuA8L5( zmys*~I@yK3)hf^UUM**lqozxKR@2HAePpVw>?4P})ZZ>h4a%fl*{NUQntJQ?UD>H$ z^nUQI-G?2Ar>R~VQm6FEt2tFoq3`EI6%_hrA1W&Uer8X0ssVBlp=xkcS@r9#z5n}$ zN1#c&r3H1~nXhW8^Ny)XYagiP8{X9NrjAU#U+JmpliRU_qIC*Iii z;d`*`I-Q-Us&u4E>GCQyHNC0rOLRhu3IjKYS${iNkY}EFS(1~wjbFVa=g@8Zn(F`OHh#7| zCXn0s?ULuKs5x{Szvypu8$UefEZ?S4Ikj)&r~P&CvPS;)MQP5V+xX`u<{Y|>|7BWE zTFBZu-0w>Z*=puXKijwUuk6(SW$T-{UpiBs>Wo{vGfNJ2W~FonPXLDQ^PT`~pOvGY z0Q@s2M?C?Uq567`Z_>WnPXH$Dm(7Py02X_t)DwUa`?J$R+BVe^RQNqA{1X)pn(I|K z4;8v~Dx6ZQ!WZ_dW~d6ss0s~Lp>z~82Q%$=?@dF^d}(L;R_{MFu5;D6?jI)O`kJ28 z(Kop?M?G^muwR}zy#2RaY|lI(&m8t@5_;u@oHKoI4aqsj_vx6N4!)Ph=bSH}wn@Pu z6J<>5J#$!G!)Few2IQz+Ys%uBde0o*Uz(F9e=t@xNfzkV$O0GqBMbaaE%2x3WP#HM z%K~3{Q5HDs6SgKEbxuLxxg)0;Kzq8@ME<=#~TWtwq1>g&rmbb_|%A)4n7CU5+EcTCv7W=JQtb@fKYIC9QD^+Z{Dz^F;n35X z%I9(pJ$3IeXM{_6)7HoM{6NU(T+&%drO3T8^QX<9N%d)szjj zQ>LpFZKvvTt&|s@uT{&ND#fUuGP8EdVy{E>Q`Xc@sqorTKV?VlloeiE{;1cMmj~8r z%O_r2REi8BW)5cBF7HhPGYc~ZGi^6wYNno=)O(2a+@PGp9%6kvLRO0JyLoCw{Y$a( z28uaev2msv_j<+Te!EtILf*nLc`DWLQswr1;R`v3 zKU0u@E;&0#Jt+J71=*7Zznas)gR=F8WXOZE>KAeje^54mjOzA`xw6sKe^6!&&Z+;P z?41#6cv3C;U~q#i8if{Z8Z0gPs=*fh#ug17(m;#Ky%zo5P>U7}Ijlu1Rf`-9r;Nf7 zS>~j%wMRi7(s5~j)LxoAPWP7MyjiQ~vKn4IxCFVhR&{H8yjAs(Z~lu?_qX-x-pjf_ zd$Cqe3Vk1Xb-y;RPTgB-*Zrlc+wkg^2ZM`-<{b85(0)x_bkx0mTr|x)w$`~$+%6~f z!yXLo7^*g>I(dJNkg|2F8}*V}ovB8=HSF+4tZSeVZ#U40KO1Vq?qP>DqUP{Ml&VHJ zsYc8hey9=hFTW=p8UuNjT5G|DzE|b^b+F%szORQLTv(y+d3EYSU*!RjT7b4!twtH@ z5hH3ZU}2pFd|YP%uhm_^k`c8QFtqjp-cSoL)B^4}s=YT2%q+|t%(TA{!_30W z!A#qS7-kk`4rbbZ#MG>T2dGc;7^lh?bKD)p|K{-qLgaq6bG3wA<_px@lci67?SbOR#IjN6vQ>$}y>A@Sn)LZs6wF*;}fAGrTl`n6o za_!Z_Dj$D%|L_YEJ!!<~81h_W^Gdq%OCi zy8ddYuF|oG)%Co$2Og|z$=F)$k|#WBuSm5>KL0vYUM^2mN$X%qw{FS#bxRH}J-FmT z-zZhmQ6*(V(MG75p=Q2dYAwm{9<6Xd7mW|`hN6Q6N))rv445$KWE&bHCX3- z`Js{1@Kb+H?yF@$tGo-j=TwXSrRu@s|E20r|E=o5sCv`_>AKNS)$;j9{YvE%h5FUW zbqp%{P*r3#T+z4*^;%cx`~L8XUO`1WR7DOd>hAk&0`G2p$is6wa(jNRJ&ySF>^4}p7-7~FtadoFw+Jire<|V zPHwO#)@fUzZ+P8S$(dEPXX2bX<0LzRw*xj*c_}|6n8~f*58NW)5cBi-@V&!Ow|4o-7;JwrLG*^r;?iTPhc)bw1ZgMMh7_Y4CHR(rI$n zTWe@i`5$=$RCoDPWch1e|F7lm`uF8eZFu>U{$=@}dn1hH*XfWP=<8J_n_#_4F*K_{;gRwa?<@DCj2V)<+p3~3=V;@bIHd}S-|MS1_HTbdS`q^^Z zci6|8!{@42{cEAIa~di%S`~5*D^%#S>ZBL?#;ZiFtbUUUeZSR9oU9TJl_;&j%)v~1 z&3n_v%)v~X=)GxRW~tfXAFJ+}o0IxHGNgLo z^xMNL{ps*ZcdAOYNoY4_mYT_5eU(4Tihq6i>b!%^lYdEas1Erv1&eYHeP}yov1(ua z!e7-c{Cn-f2Wl6dpbBe-T5|o5d|x(Y@pUfecB^XYyz_!8ZZuf@i^GcVKdkr&Roqg= zr3aX4lQAZkS(rJPX;Zv*rvB!lZ^ryod*olW$mc+*kCKObpHb9H{<>E3pe2WrM|(S> zm)y_?&qEjFH1NT*_;C0gF-(jjmqk+Ux3u`wED?KcCuKaTjpsqT-; zf0{pj@CPF(RYvrH>30Kh`OgSgQr7lcQjz1=(wvatw;wnBTEGZu0Xv`v9C;;^k%%4C z{Zdd8L#mkJH$rYBsn}_4Iql@$1imSHEF0Fu+!{xJ56uo zq??js`lD9B$aTW@xo*(PGP2F~T0je0?W8MdR#QFX1g%D9x|We}PDxnlv6yOZG%D3f zm2v5EGA134M_p;F9dv?jKnlpPxY7^P8yG`cuLrcV+>p`82=vd&%F2z(Q2N8BBv>J7 zW?E7UBz35pmYPGd7i!ZJoq(8w~SUMEctL@WKzSki0q`Mj3-T5wk{jG8#3i(H8RrmOe5XMbTX|*db$~OLn@w> z{}KtcoogY@-!@CtCc9!(buzxdU-E0<;)@?hK5*#k_QdT6zwWa`p9Ho9O1AmS>b-3L z@OrNo{CCPA2W4<9e`Is|`oIhRwZWQZwttKKWCyi$!>?u8GBwf?32AL+SZ&j?mSo*& zvRP>XLta{1RJHKne-YV|6NzL}a%3()NcC!$3t0iFG0l+e)e0^N%<>QMnlsTqFxad3 zx?(G^GFTdT!(aEcGJT4_?#q@%R;L{^tzPe@_+RzE8yMrS`zi~)?63RM0*-&XYB*#DO+YVnN*BMWkZfcBStV5m33|flJU4VF=a?C zIRd%qZb0oMTDmP#+qT-O<>#O)t4GG$FY7=HY9YsOrD<7KW-_YQhAW$sDQRXvR;e6S zY#9q#J9;pw%TJct^Sq<57PO@zUG0^AGgAgUWQQa}ZM11-$PLDlc2KQ-H_b@1kF@1D z+I+emyN_rddHpHgoILSWG+4QAfCp zu%2P&7y&svriBgp%CVZ**-m3i8sW;?b}~#k?#uq$NIHD(}1ghNoJT|jUyxC z$iS+fRbJ|)GBqh9<03Cz#>?dt?^sfcDetK*#g?Be85dpEAhOhE=Pem!)yR3v@#<3V zHT*i1*aej zd3{x_Z|L=*|KPdu;19j!S7r9-agYapxcJ<2@ z{5#8k-E~?0<@rv8jr=a)gm>snsW07hLd6z9R=UQ?^6x(B(_5A;^-;mm%_6;+MmAuIV=#*%?}z zEqkdGbki(Z|4zEp)>aQW>6SW!ipek@ z{4XApb4yYJ-prO&tNAS%U0r^XfGO)z&ONT*37SD!QgiKSH=<=~nbN0>WZaPhwh`8| zR8&hdRhe|@O2A6fgGNj1DAOB5qmdP{{Kg4tWaCNQ@5(Jf$dSX3E90aGWC)w;a_o1~ zY&jT<(_~x8(PbseUJ+Lg;PTN-JdWQNQvGt0=d8<`nahSkVANovfr zGR+Jpq@_6lGvs8sE%zMtu0LN6!nfaE;~jZ_REK5DFGuDcJ&KFvfL!g*uk^Q-{>dtI zW!=l5%EqKtspGw)EDe&E9@K*>rlO9lAWa6`k<~6`WDk>ZOw-e}kjl2@psVuK%Q-lg zG4@KPHdtl#vnw6fr3OQGA4|sGQcD!6>RVFoyYo&Y)}G=(*tJU+`FiH@$K~tL zf$U{9-+re^qWxh1Q=d>;xn{F6G0%<^N(BkF=fW%)u^xBG1+;|Uy#za|INVg zv~OB$39S#kAxD~7{#kN(&02)XoNT$d9DZ6eB}z&SuzZ z7PXs4qk3aab}u7C&(t%t3|Ur41~$V;cV&0ev&?|{wsNG^=}x+vZplTeEtipc$dERf z1N~M@tErP_bkU|m!3^@zkZ}9MLu-{^D<=Hw&l=c2UHD?w`4>1AWObcpQX+n zVwK~{RF_QE7L~1DsrpnYTV5$kjb1Bc;|e%|s=!1!mA>l7^nttYmMAX-S~mzh2r^?w^spMy_a<1qY_D3=IfW z_y!AeVC@EyF)Yl%Vh00U()J~+}v6IsFodR-hWJRPhelLtm%(Uue)x2-umWF zPS!U$t)kM1jDNiZXr(TnY8Bc%g@4b}*r*+d)^>iz{oxjoK$W0XbiW+_a<{ zaMRTTGFhpz|Kq2mC@EJPA~#^ku*<;9@l!3yRYyZv27WdU+R4!5IOk@XO`Hr@9v(O$ zBhyVc)dK{zM7b(7WF=ZsR+ZO-TDIBPO}8^t-O_rw82G7V{R%#q4MMux2xBZFWErHYeu>73lUSpioLIIdhR$ipqk4#v$)vz>E< z+>;+|N)=XybGZ>VA9b&A3e^o-pFYpz-+1FQ-p!hN_Tc>_&!IFq;<+-6(k9hQc^RG^ zu_=#OG7j}`(iksWl|Gm+%Lz(*<=$V#R2?d& zz8$HtGO%Vu3oAJN4_-~L={sd_ey(+=qfX(L|KW$T`pY-Y=tk-u>Cj$XuYlu!L0(z?y(tRS&fskVZWd}!m-*#x zNG`tXe+>v^?UVg9t2)qJdM4YaD_c|Z_5RYp?(`4+fseAQR;uNVpKg2uKk~SIO@G>?WOX>#**WKOyXL| zN|!Ua6HFxJ^c{1#+H}7mo3kw|Q)Ow*tv2$2(UEm0+g7?QSL%-3=@=pXXd}}|b6S`! zZF%^nrR$9hx%SY)j$C;-8S*VpHB5Cf4O;0EZ{o`N-%$t0^r6A4uioy@m&>#K%PxB+ zf0;l3;?AyI2w=Oi1G4UA8ICMmz080qwq*;24bH#hR`IfbsJ#Ad8<6{Bglexg*%HPd zehZwsOR9BAxW=FLu0P8=zRRugz+g?FCQ#-*3tSV(k}o_JoZ_|BlsyP9OXixaT{V@~ zdoR1z)bjRHO`d0Y2M*cnTWU?UV8qHW)D!w9MwT2x<@uFFKFsgA3cYOZ>vy5q`DsmbfG zx#X%^RGHdy6RXxMuga;H>yI4UxUrA_1!UetK4gm>;q%GNcQYN&g=f&oHt=rR=m?)D zg;e>-h44VyxCs7&_Ql{dS)j@{=-#wD1o6qVZz%jt%Adg}1W`UI4UuYmJ?V%@`2*T_ zF?>M?`OaJLXxd!{Z=rn^@bziP_k9h&PTMZ*PeZ@V``~#gcM5pzNy|Wf;tKe7 z+Pw;%NE=A>+U zlsA5Xm(q#Na9%dz+F$T`hGz+u-Ecj_UaRdA}?cNAK(+v666Y!t3 zGZ%ieIpX18;4f&sUAnitb6X%Dy%GM7*1N&?v_xF*1^-OPC&LfQe(8;eY*=bo{-%=; zXL`P@72?J;cp`0fYvjfE(y=?>2U;UP@d^AMom>Ow%6_kwm-r0sONW1kx6}4XSzi73 z9*O+;2zV*2{|>i33h~&j*-nXC8-q?LPQ^+FbzuM4QXt*2l9w@4zq6<_7q8 zT5I3bTi*RAAU}3u*z=Dmw}%V!5l>tIm($k$@aZQa9(^1hLq`U}8Ep}F--7R>!ym)% z(bkV}-bu*Ux5H1+TBw=VzAtI(Q}}y2`VZXfWR#D;-Q3GRiZ)ikH`B>a;X7$FqlK6M zIIW!qJG6ZUeEBJ;-?$JSPrC*120C#ueCnylw}!#}Xy*%famst((@#Tw^2C;2d&bgM zTX+YpcZF|ihy2K`@LP1EJM23hadRNti;n8>GFr=O4?r!fnn*eetdEohk2wOKIQFM|$n~mNrg5%JYRC zP~JWRE>o6k4&z;T1?|wkD`)t8)ywm|@(1XA3vPZ6;&J*!+N3+u;rEb#4V|R#q;2|9 zI{H5H2hut{mUih`bbJN!-=R&~ro$@{-#{nnJ+w_XIv4$ket`TuTBqC7E`1pt{}A~% z(g(1YmV^f-DmJ(te>6y@Ker_o>28|mNZ{Lhg8H(f+Gllua- zeb&$?(xq}D_RIPBe7c;zmd>>izl-id|Bu$_7wJXxYxHJ%KArak>id9>)8Enq>EG#c z+J8RUpZg`sA3=AaPoXvXB6<;h1HGBPm(KeN^*u?)=@IlmdNN&3FQ#+XqWs5nNBRf4 z_-n*>($#d@1?71Q#+9l{uj6Ra?OP@~9pf9DX z=$q;AAE@tsx-9^_d9>hPVlk^7KruWj(y~xkL6#dcZV`-N@ zn~ql_{|efqyV2pl5PyVD($CX2J(iB{L;h@9r!{52MTINpuapkS-1(e-&Lpe^2M7A-;ny zp@W^#{wlgPU679a)95n#61s-Ii7w7SeowlBeu~b^M0_M&LQkcu=%sW)Bjm59%jloz z8hRI9oQ3?1LiDGCZbRo~Bi^1ap*zu4^sRJ34)PzQ%jjq68hSKc+!*=O=?eO7I({KTlWDW9fqC$e&G@ z(aY%?`YXD)1@br174&{OuO;G5uRwoF=o9EF`aHVe2;^Twm(ktn8u~H1xE1mT(-rgt zI_*mrk zq$}vB=)4HxBk2-)DqTe{r3;Qj{%X36{)w)kchSYiBR}J6^rwPuL+8nZUBA!gYfqQZ zo#-n1R=OY``47@%^s{sgJ(?~)5&6^U3i@q2uPx%A(}$}UGWuA$ zhCZ7vJ{kE}&=qtyI`0(3AE8U==jkeXEM0Ia@@La!^m4j}{)#R>4f&hs3VJ`C*ADTf zUC^Hr`UJX)K94Rq9r@SLWpsDChJK7LJ_GrK=?Z!Ro!1`md2|WAg07<1(FIZDZ>7uV z19T1D;#%~l_)O%VL|4!k(s^egemz}6-$Pf?Ptf^iBYzm(o1R3M(hKR8^eWn=zo+v$ zpuQb+4?1`q+CPVGO;^&V(V=rt{t~(ieG@%~?nztpQ}i}^B;Da$)Hjtbp_kGt>D6@3 zdC32X?nv*Vd(auzvp;kjdNd-zn?Cno8Ex_)X*o;#WCccM_16-(0P|4-kmO?AET@2!F0i8 z$e%!$(evmUdIeqF3Hj^j3VJJ@*BS8xbP3(!M)aqOK8Y?UME-?z8GSunL*GLeUyl4I z=n8rm9lHYYN%Ts3A${eQh_9k&(BISfS0TQG9!SfdR`Z_6Y@}P$ov%jzX|zFKLf6nY z(M8uFzbCzjeu@rvL3||Lo1RL4L@%ZDuSNc9TBCoWtLR;H(RIkr=!*W7({1P;*CXDZ z4#(k6bOn7Y-RlO#@25?=gs!4T(gimne+uop30_Pm=#OZN{+^C>MgDfW3+=xd{pm-y zq%Ha+I`3wbzaW)QUrVp06LjUkq}R}U z>2|jvKkF9sZ#aE4y^W62U2aEyCwdNj3!T#q@%!oCbP2tN9!a+=M*b9fIK7zOMt?+i zxdZv%({t$Ubk3cK`)@^mdebfGHFR6LU3cW4PZ!Z$=yJL{U6Me4AG(SjL>JtJcqv^* z&!%hWcj?Y|BmWC}IK7cBr}xqu>8v93uk$@9e>6Rfj?$qXhHF#Ydy!v4 z52Q!Z8|f)@=lhVqn4U&|M2C7J{vF+g-cI+U1Gk|+)96;TO`l5F&==Du_d@+Q(p~BM zXpQbq&!JzUE9oh8=zi3*>%Vi0`3$&{^HkpOy46bbb=~ zXVC-c%ju2u?R4iyk^e9~jUGUU`XK%)-GiP*ucY6l^B+V0m-ImTS9&A8kM7(T`Av$s zz0t?hq5mO%F5QE^nqEoYnJWJ{^83*Jo`7GV3;Mz1=`#9Fx`uwAE`AdE-_RBG7CNs# z;{VVkbn`pVpDMa7UGNn0FQCil>*yN#Zo0Sx`H#~T^iVqQX~ZYeCG-NiivEx;cn103 z(Pi{@x`qziiS`#ii~Lq}1$`==ry+hZT|(bTSJC&;1FBmN^@LjOTm(dpgMpMrtNKZ-7+&!B7Q%jn`k$iIcIpdX;~UO@aAx`ck2uA<+d z3kD;98C^zyM%U2m>Ea>C-$Pf>Sqbzf??uFqp-bqq=qmbhx?m{sZ>P)Xhv^!609`x` z`LEIy^ej4WIO6ZpCG?kc75yt+Far7e=rX#=UFc5@eLP+K67tWbE9k4~ypf3CNte)l z=qmaJx}ewfbH<|nDRftQ30+2iLT{vhq}!FE{2z2*I=u(_vz9)J?l2Dd zXV7El%jn(oEp*rM$bW!dL_b5foPhYtbYJ=ndM&+-?ofvO&*(AqdU`j#hwl0s^0V$m ze-_cl&@Cq-eiq%AzMNi5-%fX!g#3r;G4ud>H~lKz)j<9%dJ+9D-EuPGU($W)U+HRk zUn)KY`AzOae_Z-_y5Cg9&!v6S;H&8p`cArD~1EbaFc4 z-_YCWEp)FLi2p-xq?`9de-bkhZ%bFv7tlqs5WkLIOW#e$XCwYNZPG*O@EpV^(n)#& zZPOpp(YeU~j@Iezbk3WI2YR9Xz3Eo;8v0cF%6Z7Ym|jWWNXJaX@1rZ|{&dIrh`&TH zqNmVZ79hTa{)qmBjxR*~M|utY2VJ}f@$~!ApKbI}bjf1G&!BUbz?acw^euF|rHDU3 zSJ2PUUCI&H>49&-Q|WE=61wX$#6PAt(p7Zdw-Mh#hbrK}1L)5f`Uu+hF5)NA3Hm~M zIDI``PTxc4E=Tz%=zjDtx{{tmceIedkS?WH(be?#bkTdr-$9qt!3WWw-1iZ0P4}Zu zqbuo4=#DFpe-m9w_oS=or|6=U$RA0U(^Ki(4-j8U_oG+SmGn<^#}ARei!P-zdZRzp zbQ`*874qBD<#Z=H_anq_rTftj(v|eHbjOd8KbkJ3r_6jmGrT6$2G`5n=YlVpsVR_bkV2Ce}pclpQm#_LwqdVkDg6e(#z?NpCkV( zx|H5TSJV6HA{+TlA4Y%5=@aPOFAzVE?nhrkSJK_-j$b1GF}joMe>pF~&F7t%%FApd&0oW6(7U5EG+bU%6+T}jKI zqxQCkj+Mw?NSD&9=xX|Vy69Wv@1V=+U=sD^eusE#x*vTST}fX;cl;jtH_@eZPr90Z ziY}@`{z$rp6=>7w<>|C}zTH_*8o5Z_DpqqF;cbjOXzKbtP4 zub`{xZgkNv$bW<`r=O>De?@#O-H)D4SJKPrj+>DG6BKSxcWl z$A3foJldqMp~G7c?@lM_$7q`#Oh>mOe*&%3^Jte|LC3cte;sYoTj}uch##Pnbc?>| zk4>LMN4F#YLRzPPtYbkj1IeqPok6bLfWQR(b1jA|DM+A9kfda|A+R+ z|3H3g+N4jT!@Cf_gig{o(Kg+aj_yYOQ?yQxq+NO{9sd*gOKFo{O^5d&{u7;~chTGE zjK|TRUVD+>hTcfGrxVqPccQE4Tj`>|5Py(fOFv7;_aQ!--b_!Yd+bO2ZF(jBIh|jF z_y*db_tJTPBcA;P`cq0DOSk+7@w4e-`U-j)-HqNxKSFmnfb!4N33@C&oSsd0_8sH> zd!^-c4gD2e>_>bPT|xgvX9f_@?uY*LppT|Y>GpIveJNc@ccrW8o^);y_4lVc(j(|1 z+Mvtnh4e=HL%MSa^?gex>235H`T*T74f)NVME{1;J+KXY z8Lb}!-%Q6Ma4*`TpQ4T95Pylj@$&+=hJ=ZF7#TuJ6+x$`F&_T3J;%G2-D zT`x!cYr6N9@Mb!44ZNS$=q692e_9vBkD~`(2X~+|Z-B3$hu;L>Mz5tGqJ3Qve~xZP zzd{$$GwFf!JG4oEL08eg&;>W6{=aD7EpX#!(4Pc-9Bt9((2-k_e-*9KchC;~C><+8 z{vg_*$I-sq5T8pY==W%g{+f>5j{M(fjsBZ<=w{ENKe2AeKaq|U!{^hx>1*k=cOZTj zJ>yRJe{}Ee@Qd^s`Zc;;0`d9uaQXv!8~rWaRr*vQXXL>FDC*7ep z@*536f5y;9)4S<2>8=kUzmU#-7`~0Jq939Q9zpy$x{Q8>uAyhr#YyD9Ls!sW(0Pv{ z{tI0~|3z2Pjh{z<3i=@bIJ%5JhpwToqKh9x{vC7${V1*VMSKw5`G4>@dKx{K4n2(#-~CXL-e?C2pzLpL>iTGV~5Bh)fO8P}Qzd!O{ zqX*LS>5cRUbmym#|1CX@{+$k$AnqT8_V=KVpjXnT(D_dz{~~%IeFMFbzL)O&4Dz3( zEqVkUc^2`>v_>zc9r|NBrXl|a+MsvRzUL56djb7P&_~i1eL5W(fc#5ojlP+7==aiHt12bZy@5Y(+T=5+M++DBZH9tGu?&$lkP`18jSu}^wD(Y3n+gk-GMHo6ZCEL z4EiD3rJtko2BW@L=qu@&v`)W6SJGe5p&=;$3mv8ZqWjT}hp<2Nar8F&96I+!)OQt~ zpzok((2vq<=|Qw_D9VqcucYVF)9Ck7`NNR^HC;^qMwion(=CT1zuAlEPm(^7UPGTx zpF9Hj*U|&&yJ(00A6@Vg@?WG&>DTDp^n5x#68Rs{bLemBOdavx=^nIyDAxyl1f4eu z`KQqR=!@w5ml3~#w&{E6lF^7iNuN9h9zjo|C)1Hv5MNAd^v84!{XJd!D)P6}9mc}G zVdziqQn&@3KMrn7SJCIE^2a0Ig|4By)59kq-iOXBg9p)FUxUZe8a96R} zM3mn|N9p~vPB$Hn{`HuI{1fPv^m%l?f%rA_K)O41p%?Iy43Gd2|nY z1-+79N9RvP{#JS*eSqFbw-|x`be@L%ljv#mg>>k3#IL7&(D%?Q=_ly?H;_M!9!O83 zH_{8~&eM^o<=`KZ=*-j z9cH1vsdR!~N)M-3(`)IU=)Bn|zl-icXN*LD=Fn~EO1eGWWe&=BqAmJXy2D(=AEf)z z&(e$N(e!S5I^F9{lz*G9qCcne<{`d;E~fX=WpuWV{#4P&(gh~UpG}w1SI{+dH@bK} z@*klq=;!IY1&EKOOX%5j6}_A;Scv?u=rVc}T|@7uix(lk=_vH4fNhZNwj@OX%n5DtZiEP=WmEbUD3@&V2{*Pw9TNLs!zf=#K9qKYcX% zQ%WC6SJUn2qUFfHgf6FVq;oCA@1^_E{pd=17~SzbG^auy^=0^ANlL(a(WA$ zy8`jQ>3(#04Ek3|pFnq9iTv~EQu-RYn(j^)eSrMO=yG~6o%5Q_$l&R)1~xjbTxemUGy39Z=%cTo^PAb%5GO7Ekq>Bgn#U(q_`N9c0;Y&y3R@yqFc z^lfw{-J9)8+JPI`@0Tf28}-JLyU~G>+R}74lotrSz$E zHGL7?_Xp(1X_vl-j{k`GeJVSOR{JPRcK)mtpqpp`8ceQzoN6i|$Dq^i_{6pIQ62Bk0)eJ6gEqosxYlq)wyn>Eif%Z3^ ziF{vYxQGtN;AwQEH-6uGlUc}*48ihy(RK))LC2p*e(TxDPmF>G(6Q^0|0QkC!ru#M zItTgT#rXZ|#kA2E9#6aLusy7!%^CRp-KWn*d3_7w{b~88;!IjyZlJnv29#};FK z^`YYr;P(yBrz4NR+v(&>$iHMB%4-i}d`HkuC;a|$n~u|A6Zy#((4M<#`#9L3ojb9- z^(mi%c#HWcZ*c$bOq-Wt`+6+p%it+=lKz6$GjTjfUx4}|J&<2WTX(|)Xmb;mZ_&mi z^e=rO+p`km(UZ1o;Q4f%`%~s3ZLmF*rhF~d=Z=)0!tyU)jPlOosDC_d z^@m+rYmWA}UxNJjHf&!{(?&MppU?^Jzm1k6KhX{Q^OY%g$M4%7LL0Zim2{*d_RovT zQQlaH@f}YmCSZU3jdqH$y`1tE@_qfdf6(qT@b`4=BJ7VRFJt}Z!;jOkbKnnX>qt2K zHuCjWaB<4b;hA*uEO-xXp86gU@Mt>L1OA3i+yfu=4)gDZyVC}b7xPo`q3B=B zcab0C->-Otj`v6WBicNG@yuL~{CE`cTWRY?Y(H<%2LGPMUfN;) zHrkqtu#*iIka;#>i;ekr!!ZfzBqkq%I72h zS~|?@i^pl5fA3{n$}`d4ZFE#ae>;DK`on#2{VtmEp z(ko~qhV|1-##LRPCVt2I>q)ztv3-3>N2j2^qgNx}J`&sWqqKQ8>idWek3xONtUcPdVYK1IIPAMwj+=W2K`?dGBX+vvz(_?FL5-h3KfPCGZ@{Cxc9i2GK; z18Dm~_ygK-G2ZDm^6ksf->$UdgD2BQKWwknbaFg=)fX&(3OtLB4~4g<+yrj_CGul* zlGg6S@%VjOKOXrfe1&}LX}D*~VR!}|&W9VVO|=Kdr`u`mE*uYyloRkKI@uj=|24`R z-QdS)>vp)Dwr_*~p|v~V&flQCb_YC+P8P#o(a~WT-?rQ5WXP<~&^`;p)ITjWP}!&B(Qc6cWpdm7tc>^tPUy#5(L+r>D(d_X7K;P2zK z{2uvsbNGJRI0}B7j)dV>Rmit^JSe2&bFqECPA6x>duTTZclrV4_4{#ro z#xH#X^7XAaeiYI6M8v1k@$29nbaVt>?7wD}YAhtXCM&S#&~$=%2gY(n{P3~oz1srz|4$@AHe zRQy-um(%eJQU7K-d?|eFX4Id&3;B1@u8f1<=ktw8#gBo1pndX((^R~}Zzyjk;U{Tl zD*9*Bz5wpe!&{JV{(|kZ4;>$b`WK~qEcWk4Taj;_4Bts7rs8@1UdKVEestJKJB5 z_=~hHe^^Y7-)FQQM*ku^ke_Id?fV5fc|5!$6@M1K!$p2%1-9S0bc}yrso74%qfc=E zq?66y@wBrJc4_l$oX>9h1Ld_B5uZxO8gjOQb?yA00Ui+K1gxG!zJ zf%2>9WFy3LtC8;p(4HZ*aW~fADmuaI|BSzopV)-wOLxfW7Hx8WZT&a$lT|n# z-%5w8u>F>%T#f$kqs<4=pTd9GzLwY?#?sb)oS(MQMjFl^oem&BIuGrACgr9VR2<*Frp;5}MnTjUX8a=BUV{1_r7h;aN+(aj`Scsw;PF2r zg!+6vQ2rV^%=@PjI{7T(%W3y2cq1L-_09Qds4ptVakalcL_5u3gHAkx?P(Kj{)6r3 zqIC9eBes{hw0jqy&(JYh&#L~jGLY|{j^o1!I-bPwc{y!-3-6@0AlC1RnJ8~XP~WX| z@>R5NI_>g)&86er(4Mmzp}e^g?e9x#@1gzcQ*k~Y%FjZ+Z#UXgL?@PFe;G%cr(%1q zqV)i_pHs6@J{pAk(Z+flZ|syG#PXZxAU|;zd<*T+@1){SAs%Xse1rFASJAOd>~HgE zvo(CA{P9S&e?_-r`|U<+y#Ja=Ti4?F=+JIA?C!?g3ZRz6aoInjzmg6X%x^be#8FHf{3y^tk58k3WU;$#b;%KllgQ zI01gT1?!)M{o@1L(hxtfCF05J;8C>uEc`pIUxoE~?Gebg+9N)RHg3jv?xdY!tiKyt zF`vh4J>{8LKO5-SjW`~(Y>j*$uh)9g@o%v`ze8(#Q2*(<$k%&d`+tN^PDb3J<$vGx z_RrIgWd6h0o*$+ydLHf4F0GBg`oHujly|ygdC${&JG6flt&;)wP@el zDR)47LV3tH(l9;=T5k$3r|pZeK3&?q1U~L)lsEbO=SJFk2l2sl#T>>BvJU-{u6?&-it;))nn} zjgH(3Z=(}4aD4BS&;E|W_Ar=s7GwNY)4o%1eb(kgiPT0PerQ)Apf2g5- z>#=?xZj16hAMU42I`$6ochmOAXivwJkneUxd!D0>T=)~(cOl&3WaMi{!Nn=-*ni)q zBWJ_AX=5dhZ+D%7^6{3~erM3p*4X}hry?HPhx{Ap@FQq{DQ!Q7`nS{iBAj2_o`&-F zvFKlSIy@Qeotg4DoX>XBZUx5g=5{D=gs}c+(2<`}e-&*m!TLSsbmTie;C}l zoCe?20p*j|VtbfPNBQ^D|DwbEd*lzC!}jrbwu9E^VE?@KT*RZiKh)_+Pb}{r+Un-4y=twho18wm9e8Tz2Ph??#PSQ>`&UXuFV>gzce*yB1 z?~s3A%B_$;i`EWcJb$O-=V5!f@j{e0|A+l&Jna<1E*&d?FDpR4?+Yyd4LW=bjz^6y zLR?RW`_T4z*xzQ+a{KO6*Xu5w_yj)tVw6wve0e)PA9gJU_80w4cfN!A9t?*zvc^2CH4xQln zcVjC5M{I9LU5fe)F8@+msa_HIy@EY?_=63 z!}7z0$Pa&p`}>}BawxX%&r_x$ z#pj7T>DUE`pLiw8C)c6z+sidKv34^VK$NBGxPPzs8kr`;;GqlO)HA_=I6a8s?E89O7_1{8A7oh%mbmUIt@1kQ5 zWB-4+i2W(S@plcaodutE8{*DO*#7RO!w=*7Y#eRye(610=l#-ebcE-JX1Alhgv0Tt zO&(td(6Q^V{m-JEavV>8rZwKL+ z-rE+Veycfr2d$5T2hl#pD`@uz#5dAW#@pP1`t&NqJJHE@7~co!7~3<2cD_M>zD_{g$o{3bvFZRzbXnhj;{}-+G#qllLo#R=C_7%|)-VbWD&hej-%BOAG zXQ6%7sqz-;Yo9>g%A5C{1-qhK~aXfg(kPO!n9Et@fZo%2Gz;GC342MBphHHT0 z7Q91)I|~e7fxwDekl_+!*pQ$r(7~JV`<}mgeXjd{&0u>&GD|_kE+kZ^i1*sAZ@Q@dnw<_`vqgM7oWqXp~{c$P5)kN{m#bz z1dZ2i?2OXY7g%W5*xbe}mZhn_Q=_^6e|-SnOON zKgIrN)mNjR@;$}nU$K|#jhoo?TI=U~r}ABml)oAKIKCxfPX@(n{ZIMU<=UQRVec+o z?>@u!G1{KX^`}1`TEyVX?E61CA*mq3(Pnw~s-%h+7cGLfk*lOJGH1Zp1`MrjDA2Qo8 z>f?I4Cl2zy>v8Pmetgm4%C~WUe=>ISesKW%c)z&x2<2P29&3k99M8sKcYTfTAJ|b} z%li@saN&`v&(HOj9lP*Q!)-L*ZpQ(RXD^I6&*QRw*va#oOW4v(^LLvV&s#g;B=?=|+^(DAzHMCF@rXuN;H7M@@Kfn8S3Uy+lP?>ixn z#nzJAzOG{TOO0Q%$;!8u)_k@Bd)LazrYLSHsrA(W2ZpFVx8Zs66>Mvu_A5_S`QU2J z#~$qLt$5Tl#Z5iswT6?+AFwySwx2rF>CYPVZ#oWk*Y>u{@M?|kYi#BEuG9>bH{VkE zw%F;_{xSvo<5YgTVdjrpIKX_Hb*Acb#b`WhVn6dyZ^K-V%){p6s{b5zT+)2|(J=FE zK{xd?-!;WP=Fg$nb41(sHte4*-@v9gIrA*lXSy!ez@DpePi*J>dJeW;QGfSg2h0Bu z+b(MRZaZ7``*}ZdrQwxYzT4P6P~)9#j`BTo)qXAP<9^gA?B{;eE*yHj^~-$n8VAbC z#plvK<$GWk<(Ff}K`qZsY~uTuRP$8c%<;K2cJn?&ckCUg@?Pv=zI}>=JTEFZU-dbv zYk7NP=M0tKj;-f3KDigr{u-VCkHXFknh)Z!>!F;81242aH;z?#8~1bO;XrF`pEt3; zk=9qiUzP9Y{`@FxdZYDm89O+Cj$EjG4<3MhykC482Vbdtp+(AfpH_R545yIKVT(oc zUzNqmciz_c%*Nh3%72Z0_Z6?dMES1&mUv=f8|=PeDeVHrw#VTs=ejd#QpUr z*wak?YaBYSP9Hk3I=XB7S&3~YG#&x$wQB#!y;AuW&Nmz20Oy;(U>Eb>X6&=8KM#%g zer?a?R;fPwIe8fN?U8q2^CkHo>|dw;7F@0JZsrp^wsO5b9Q%o{!4A$}&tVJCmlKWh zrL?_N3XOAkeEghWcEN7E6uXvcdp(Xl#NS{CE*Uy5hU@diD!(lbFdxTYGx@(^3-k9i z>^!3NlVY9fvvEJA2DbBjX#{rje#jnd;r#Z#k>6DNci#1?FK|WY3+)Xz(EY~^*x6cs zYs7iJRAz(9ySX3o12*AF*nxk?&YN1EXV^PO;f3wOv{?hg~6bDo2`ulh6zNh&%^A_b>(rfz} zh&@Gh{u76-AGLgz-<0nht^6L?QAzuo$8hL8G&BsRbYA7VgR%qrpUc|}uh)F=9(&fw zwyi23{GakSVo&J3+AsBQGy2c=iOt+!_Zo4&5BZ3lIW%82|DEL_z7RXOUuW8`c%YHy z>p|GV`ye;4kN0beht^AY{d@2zZ0G&_vqt=HEnmb=<$L3`{`z9`6rDd_#HL(|NA6O- zeZSUkH|(pV^%ajzHaXvJ<(qk)(i2-wYx#ZHUPJR;sy+1oSKV*uge{BIpCj1ZP5o)U zm;S`a=dt&-^6UMfxXYyNZ#1^`)B3!H9iiI>)*Oh z`2o&{J76F8zb9e$cUr#fM*dCJ_X=CCYQ8SNU-bpI$vv_En!FS{IKMlAecbPOhwaTY zKSdu@KPCevX~opU81Y^_gyHerkiQ9KYt{z(vi^=dkxL`9JI)q5Z4N zpNtpxACF?oSj9`mEADM7FUKD4S0q2Ic(9u0`xe+9tNty=u1#9L+t|eY{I8Fwyo>R! zfbE=53^d}LAIBMS?ngW_%>DCBM^&F0+puGR`ZFK9r^`=qkmE(!V=8aq{>3mH2+cd; z{ozmS;d_iv*u7cvOTFVNZ{qyVh0VO*?>FLWG#`{Xp?qt1mG6yhd*#(eJf*HbBTg#c zRYcoIO>8}`xEtG-tNd~7d7=L2I;HY9-X9r&&AcDGADehS8gW|r<`G(d&9RO9+Z(Yv zx8ljpDBm(x$E&*7+giuJjo5ir^G%}R^V>sH9#$k6RjrT`voi7);%<^)5(H)y+D?ZnVb3DI*T{y!PmG^SK+05`c?SF1; zSudZ(e&(-49N>Kl`&HHF>Y@4QuwkA*XTPSnlk4kxIKcgeU$Bk12M5<{d2So!TWS2$ zTvvULM(W?sILQ9J4!ibieSXA&c=_8KDj)ny+us~)->u`*SE1u#`p|~reyaVo4t6}! z_7jU;m$m%ms5;_nStlIsSP@8SLT zS=gFX=R4Q2^Q@Mq*&XsPY5&-QeKX|bcNKTk*Y?*B+sf3^?@_Go=w#Ro+E>G!C%8?!->6 z7w=;q_u~pYp#AY0uP%n!{#P01eD*$ealTRDp~^cspJD90(eZl(_VPaOIqbQt@%)Uf4K;o>9@8J5Ka9Y(D8)}=a}$*>@kIGHySxmC zuN%ViXMd)AEB+q) zj%fc~iA`taSJ*O@{rkDfyLM=O?!caajvv`xC?3e7?Ykv*Wt3-O`vfiTb?gs~b9g+g zL6(R0KN{Qd8N+-}Qst%cJuS8TzhW!DPw)x5nsdB;rTie@FKxkA`jdb?Syf;2*UC4a z*7EMhzMr)HeEmjo*90y90K@Ux{~uyg7R4*PRlco;`m+|BS84f^zf;`A{#_PZ*}pqu z8|TZju>Y9$->W#l{#xQ+)o0~>%`rH5O8I-Sm*eY4Bkt9FSv5iB&CBJH*ws|q`ybfH z{`LfW6SVy{e6RAs!8(5Jz&3vGCC>-N-JJhAuoW-IPJ9dfh=WVDJrw#+`IZ&(RP11XI)Vc$wZCNlqfLCfDN^d5gW z-&SJ5HmS-@wb3MEVyZN3b0s9-P|CK}U$%NbU9Mt+BhrNg8 zeb}-_?SIBj&NphNQhA$M<2??${OZqPY~uR=Ep}eg^=E|$mA7-h$A{hgzDCur6!-Cb zb2m2g{(Irniu>8#yJ7ohjn8Uq`6S=Rj%(^)q0sMMeHrin6z_^HcolXgDt-?;_`R_# zX=tD4F>SCHFT^JDPh&?#Z69Sq@6~?UK6c5Yv1g=w13PD^{qmvTwF&3DL*FTf{+Q-t z?-rde-p0=A+Pet~^G^!{wy zEXofg)%fql!DMpItSsM4E${!ZwX(+dB(^cXMr338xW4OZxQ*IBiJi2UExYm^#nj#y zY`dfO9$|mzgebhd)y+YFxc@Q-yBV*;*mP3mzsjlnz%rGuf^D}ozf8bhd=5KyD4so+ z%A2^~)e!qQpB;()+%NhKd-(n}S8kS%?^%Dv0e(*|Wu)T%it2A$>}{#>+J_yNbi9tv zqkP{K?SDgXz^DChH+CJC6R<6Go){j#W)_yGs`k(A*wAA`Mz)xRCs$?^Cxwl>oGEmf5MR#f@^ z*b$-f$FPh0kGZ3iALRbScCJ>pJj{D-|<@hwbp*ysvBtu1m2X-jKgZs_T7GM3#l3fQy)_&M>HnYD!}k3iTXt*x*DRy*=Ckw% zdzkOe8FAj{>Qa{e^FG!S?9Qk2#cI)tJFjW}-H&a}6#u52;_ljNuNSryQhYzQa{ZUN zyz)IQ6z_=xA2q(eW8ZLXAH^#u-`raB&0Os4tN1@S{2pU?f9e|AZoeG=|JC^Iz-Hcm zxq(eMStaE=S8M!>V&7c3J+@|3edDm7>&H0k;`#AAZ031P-yJo6o#isgf#BJEbc=R$H8s~6(6S0~2 zR--(=j_uT!qNeKiGGCO!R^n~3qq@fDSM1C!U&Wppj9)G4iMW; z*5?synJX8lr+iB@xwqjNs{aV~@q8*-ee!o`duW2a1+_g4!LCB`3T!VdpTZ`d7ypYr zyVZYN1J&>1eSqQE#{Is%*unAgDh{%LrTCWRPoe(R#m&lzP7Kv*jrHh=QeDLkl$f5&#S65Wqf|t z{x=r~QtSTPP3&5%{o`A^@`E$vH8}82+t+LC<@ebdHB-Ka=jqF_xs}QXu$AZO<(ezs z$?uQO!hU`~=LQaNKR)aC%6Dzj{__(y#mP&sWsiIX+fQk{QnpZe^Cd0+_t;Td^Ur4N znW^)`|FFB4+^D6>d-=VrA=sQ+&h*$|7&>*_C?E&u$kZ2t<*{7t^6Lx1RR*D{2SQJ z_Y?UX%D3cF|HokOzuNxJVdrw~PicNszR50k!Zy~Y$8bHJe?K?!*J^w#byj&N|F0ni z2YCK=8C$j}Ki^NvcSmafbYfpAc^~$UReriI)SpZ7CfKo1`|nI_A1$B7=E-vY(0&r$ zKK#?<0odA8j>op{a@KCjx6hW_VCOh_1NL>1KN@CxD$+e%-erpFmEp_w))_CZDR0LQ z_Q=ohp_a0xhsqD>EVsgrI`VY9_c!?p-Wj?;32)!eaGfM_(w?fXS7?QX<5lsktnv)( z$sr%ahbqed;x>illA(DfynGv0%fH}FpY?l3lW;WsS%RO;QT{dDp|@PBx9Y3Tc=X0& zM=QP@SDZ$9Z0Rd!>Z9@}cFLXb5te^2Zjn^+nO|%Q-G0;hJBSNpkssigNIC7#;rdOc zLmB0AxXm|mN1TvEo{6s%mVMazG0m6pe~H&w6#s;~TZeOCSd4kw-fl1Mp9{^B-z|7Jhd`K8QVg<(D{?_-A|srwyIQhWm31=fnv~ zwLLV&V^hh4@U`6XGW=am`8a-;Tz-RJndHKu<9@ikcw7sgVS62j^W{_ix-WK_p0CyN z>?K|@5AES6_zHf5AL40gG`{IV=ON+t2YfA8z_U}!z3?g4*8)6=@sG!WjLN@&(-6Ol zE%+ribN!TUpxSqA)_nCX_H2=d<3J~!kLG!!~(;*r8)X zLc9E5_W#So9S0RpF<9lz7364aEhqQJffYJmn}EG|IX2mK|K}Vw&(QMZ9-{i)6;!?& zet_HJop>ltQ&IWz@PKG}E8c)l<16?v?vqLRUkzn^a>$ho7uEGoTf8Ak9*8dypNIX# zx8nYh%0G?Q;m1aKoMM>T@Ag^SOF^7&yvDaK&M`s$5l0grgKfl@U~f<5@4?UUU${Uo z#b4qs2X#I6&2aU{Qd#luaS+eLHk*zYSMZs4asp11AZHq(`WC*Gqwy8;?YQ1c#rxpM zSMnrW1h2xI-spaFJhmoLf1lw?qvW(B)&7Iga#5TMe~XK|6z^)dn>+@0#!Ijp@4?Ok z8o#T!z<>HZ+4uMj&OA!}>6oZ^Ib4!>GyLwY;y>dF@8roi-M{i`?C&5S#-1T^#AvlQ zB8j%IBKT8MxeiWllDpsuzda8Iqck`c%E@8@4u*cBfNN^;(hS^LGmPgi1<1jM?4<4aVS54XW&F5A7_hE z`$OW?eno7(B`?J8fSiCWOI3f>@hTt4tLwA&cz=G)Z^QAK0`dZ!9Ph%J3Mzg9$K;S- z;Jx_k32N^VE{xwKYI~`V9S<~L|A?K{HD9d2?v*;e__4X3{MqnCx$#7`=b0@p!oCf9 zpY9}fjFA(uYl|E;N#%nva$D@4rTSK2Z(;cY_UDv8>95Al$xUUZO7y>*wRn-VbeZsAFqu3Razfur>eYn zrCbC%ac%6Gs`_SQ|1|lYk)KoL(@#@<&XRIt!=>bD*xy9w7yGcMvHTp{%-Y^^Ojmu5 z!g3YtEF!nTYlsiUJBiQ7O>!&$cN~NLcpH9(7k|?Bn{I~MpD^+y=+v8TbzN#%uphI#ca?{?Y!@5xX|aad`H7`6kZB_2CDch2wipx9VF= zenp(X@wz2$@J99Z$IbCf?0&1`xfgrislT7GJwfxcWtQ5@^j@xp=W{&lh})4r9A|l{ z{8+pO@4zKrDSjTGZz;dV-eYpv*=pamRUUx1J(uU0IeZ>B$K|^!-VZ0`{MwC8ixj_v-KW&Q;)_(jZL{LTu|K1H z6ubXY{u^vbu6X&yDsN6I55e{&@;>ZHA%Di1Cd-y3Djz*Xu7*1k?}*0|ACAp^lpl-h z;T?DYK946HQvYA$Ko%X3YAt2?Bjmo=lS=*-2Zri+d){zQIny$g_wa_3I)71I={45rbW!2X^7*J8Sqi zIcMlRD13j(wp{)hTk325T*m%tI^TYcgV{Ac1w-dq;qr+mw7t~F{*!W7T!#2WoRauz z{O+*w|HOxm$anCBqw;4QcU0S3-qmW~lU4m4fc^>wX;!DIMH>mz2#H->NamsIpeC*Q*E-{kaLRNl5lu7=HrWS0>i zDaT{`4ms;@D(@LCH^3%52wQh4e%y%P)A*+K(*A3?2DZPF`y26>@*3=VCSStdr*f*T zs?YzATnXEQa&PQ-Aul!J&*k&jx=`Ck_HC-qabNKtu+^e?EDofTKjL6Pxy`;00D7g~0FOz@9mgVwF?8u@0 z^EM7#(z$Y}%!K?+&>*HgA`EW4}kmv67idopSK`(b+yInIbrQTgZC zHBrv}hsryOsJ_P7QdAy={Vw?s_U4vf;`jq{#yHit-zS&GPl$hy?-TzYKC@T(GjYW~ z@>+0_NzW$Qh6Bm|EKHU z-|_yNdLQHxPAsS6|9kwdyqxWT>PtzyiecjI@cuH&ABx}LSe&n{;=AyqjPf;X&!O{? zls>g*eW-XL!@Q4FAN$@Z-rX?gPaCoSvd*7wV8{0w-){~wKFziL_QYnMXKlsyTG}4p zW7BTA#37Y8eXH}a?{Kh)+!I?GYI!CZ`JwZy@cG&rBhLBU4eV*H{=CPIZt8!hmisw&lvAvSUb18P-Re#@O$6C4d5!L5;DDS|gjdGTwid&z_!?0(Id7IX#Gjf^Y+;91jP-XNKj-6MXO5@Xj^7(`kK(nCtGqqA?!S4k>$KvZam+_K-wEZn z;Q35Vy#IsZUGNO@$Kh6Q6kmZK;Dfj?&vOEJVSD8#JE`^pYvlHZSIN5!bAIvx-{<~U z?o%qio&HtDk;FUU=Xey}$^EV+xZZ2E7l+T_>-b_@#Xn*1e!1ppweRdMkH@Azw3(-@*2u<@RTl@9!r+!roqTe?RpNm3QDE?cKxvF^U&D7s?N9UlD9y zwZabX_-A!~vI)B$$=9*@iJbbp@&n8_Rj?(k_LpVYS46&zU6)io{{{McTb_VTDYd_D z!(MzF2ZO3F>LUGpB@f2I8T&HG z8?k+*{2rUWksDs7`~%H*J+LE{;w!K@t$Z5$@CR)FO7VhMR9}Xrs;>#&`=jRXVYt9# zc@bWfM81HN&XeEaK4ay~S5^N`%9qF1q>A^&Gbld`o5m^b#YJAKe`oO|{2brKX|Jih zMzdAkiv3Gez9~+`eX;uoopKz`l&JD=@S)*~XT7fWiu91n?|CeEBEA0<&uqxmj_AZjwV%HM+mQlX6w#Q<(RbQZ#JQQ08 zDSx}+f$}RHyrlDmf&rEHu2=v1VtXlhH#XJN@>jh>er3h`V^=9HzZ-v;t@&U(PCZw? zh%IyE1Z*Xq?XK!y>QuZU4&XL8$Nv-`g0~&fe6twa8mK>evA?i<4O{=w^5?#%_B`B= zYmJ>1wS3dDrG*@i9W^x`rSGe}KaaMbPPqDFt&eedE?$Yd;G=jieu$5aRQXhYtNwbU z@)+zbBp<@Ax*Gq? z4^`fgP2h5hmJVQilxr+udKQ4i$O zxbj1}8FmsMh+V|z;e{8KzXSX6U-%P#i_>tvmi@Wfw;xpb4%jt9_F(q``98Mwkc+-x z`F--w*wRzphW&%&JJ>u-{wApMuCa1U>}#*>c_nt6(=@>fGr$v1NhMl#Z!D#eS66-gyZ`vULSwJJ#dkJiciKDd0%4-&cOe>xPV_xRel0K zKTXb_sP?b%e6c!CB)=10!Sh5Heu|gll0U1yL-;Pw8z16Ee7~ROKegwWsPQa~+f9<2 z<6aZw!G_6?#ZP)Ez6Ve1EnmZh`p6&ggP_)z<&)a?T-5nRM{MJKYbLgEzIF=R2Wk6B z_gUplP2{@R-AvolPV8x}_%m#2Bo{Iz`!YWTJF5Mz*xFuRjvZ~}0Cu&KbA_%`!t*jh;E6U(rl???9=uB!Z-*nMC1WeZ)mgzNJ*Qvds7bCk{}cH=-D)qe#C3n-p5 znaZ2-57<^(@hR9H(E50ct!w0l$yMHB()Ke92Rdo~--z7~`6jj>kqd^tEB?~|<#J!_ z-YcKLX72AKNvVAQV#Uj0#~<=k9N>P$MeLldcw{P;caJ;_yJyK;anLRQjZNF-Dxu#I z`Lg~7%YCqUr+f%|?r47dD)c)V;e5+OIsCgCVcT|UyvAW?0eL?Trk0C@?z4vTeK%Cz zgRQ(@>c@@?iWmP{`R1hB9{SXomD!;~Fj-QrLyW#P3 z|4;jGC+uq`&%w5q8m}Z~<-1xdUJmI@1$ghO=^ZtH&{05K2`MRt8GQ5}fj}GH)^yeS^WSH{5$*T4$^L|uW++nEV zZSfoOhvRge6<>nu-~+fnzJpur)Bc?*o7(G4e@ft9<5b`GxLu4q5D#L!7U2Qp@56Ul z{@XZP50y`nUG3Gw`EduI`tvPr&hKIM!|4iX`i68YvVGx6z__w|EK%?Q?Y~dm95x1Uh~6cBhLBBdwgS@;(2niKFP0%kMvahC!C1K z<0#H&*5WdpubjsAk}7Y`rTU#EqZE(9zFzVgyr1$XuqAXJ7cQT| zqWaEIz8LP-Tjd+$L80s7aDG4h0MEkq&~i@z^v;UXQ1l1vw)c~h7E%2-Lf4()_N(D3!xaC?hyd|%W7vr!gjr<&Yi^@gIE8m-2?t%U1RNqeQVCNF*wM4b^)8xX*?@e zRe8r!<@d)nkGu(+ENbr_4#dmFs;RuYggg*ixu3cLdw1)6GD~&k`)kSLu(OPO0sE|S zff~x+!24$n@r?@FU;E=S73F!@L_7{#DSsQ^Evx*bHr1akS}us|;RZM=lj8jhb3HL1 zC-VLMKKzdF?*lj`@l-X{UP|IoIKcOLjd2#f$9Lj7cn*HV_j`MBlhxY3@8EXhwSFUN zsl9F!wJdg~ zQTbLlmhbU~;xl+LPQ(Xr^A4)-0p3o3($-gd!vcy&<05=N*BblzUT+kRA%8h8$@h8j zxC?%WXXDfj)ZVpLYOge&Mt|Di8GK(q8c*W;`V}~q@AZxwCjTkEe_r*c|5okgxFDCo z4RA|rX`}cEd~LP%ua)=_-@~84cL!;Ec!pnby_4xXwfBnr3it@m58L5HJQ7DaRG$Zb z_(R9LqqrOM&vX18-?wLOsP@`%|Dqx;#P{)?a0~Ls;#a)Sz8V+dd-r3w75)cr;QRLs zjntkipT?&;j_)e>!v#1$nvea16hDO1^pT(82SerTja7dz^qeew{Qd!Z+bcd9&t0bY zCcJ{{m$SG4@mF{+@ytzBzm4mOiuf4qcfu)MYHudK!u^7sxZ_O4@8bL8<+M#z|C-ry zB^<}~{z!bLlj7^Jhx$(%@kLs`=h#O48@uX%Ks*}n<@&!h4pz{7xfNTRY5RJFEsf<; z%~YSMv5x0Gu&<8JPhVkML&ZxpSAONvinqXCJQzFiQv3iP#B=kh{C#`^r}|#?C*UZ2 zDv9!&VMlRYzxBu7lJXpEDWm?L!tM%+zrev_8sCO3)Sjb+yw0#yzKrepRQ^3S=aqA| zRC#9x_0Nt?yl=7;TNi15xqt)st5z!STCaE&?42e%v5DtDYp~s=_zk0c8?Db0tyQ0y z_dk1LTS3KFV@o^vosrM=N4YjC?=Gk9XCk(jm#-VXDVJ!=^8YB0#TK)C0Nc{biP-yt z)=%ShEdK&|J`UJff7sH1@eR!b;p2Od_p>Hpdu`>Pz@}L`pH108`GGldUF=&ZkHVhC z@*eCcp!%NUAn!92_<{bO)$;s=9WCW`_{dE8EPmjYU*SwRd+542ygbpf6t9ZSgXB)Q z9v+Ja;MGR{J3T)+h3$n^J|Z;FgzNVgk?UeJ9*ZqW6hC0Z6IEX#4n%6cs_9UD;rqPd z`FS{Y^Z)u*;*|V;$Vohm-wSzxAMkx^)*q>l{3^KPRXtDcgx}zCcUs z$IE~5Q+`h)PiM84$nR;?#z(l{-y6Rpey6cFx!H|D^Ult#@8hO87w>Zn#)&U9UdwQo8Cw5G zaR#>EfAC(;&ohRO^Wp8OFZmU42hPXa<3o5fwhUDLE3u#R^^^Dp=YOwph8vpSa&}XD zK3oI$xUP72oQwEWJcRExf5Thw1$+bli(BwLXP)kAuL%9AkE_2>d;M?|&d=xI9Q?k< zAGiVexAB0>Dxa)}>feA1<14r!-rrvNzu;r^XA$nk?`!;tk8pne5T9T?()Uz*PV&p* z)PB|98r$%2+!rsy?--w>*h7C_82NWqU)Ek~ZwBY*Rd5-uCw{_1$e)OPTpw)2@91XX2-vzwg3T$-j=Da6bMSTYgl1`9tTe z;qAW(u8$YxVyaA^P(V_D)uQ#=dIL&-+>xaIZ;*7OmB>MJIz;RoholHtK)MY?8>O}%dst!e96eKE=T`Q3Rj7Lqq$YfCx90Oi{@$aAqH zk9-mPPHKHd4OG6fzQ(T+KCw^ZF#yN!mt%2|xDP)eeji`jrTmCNs(-+4xj0U}N4Ddt zndBkZ7F2(K!}bw6pL&cfT#r;AtokjtbUc`Y?VaVvM!dS}uP{XU!SA*HHeyq>;&+TV z=d)#pD&Lo0{h5HR9px)H;F0SLQ@$;WyZ~FKss9(TX{MZMIQ8dO``=;<9)(TEv^?jG z{HAij5h@?JAP>Qowz41l;^a;vmG7!62eEaR+;NoRUcO&EjRTy|r5&yK2+q&SV>iD~ z(#bI2L&V@1&et~Mh2)>dhxk3We{llNNSt9a@$s=p2SrEm+r4{3py;-NTzm*Pu&KXMc&<$N)S)A0Kr*~hBAl%=%&RmWNQ zy^!vP$)ARc@cSTJaYuX!Ps1N^I({#tz&N#6g#WkK7^mU)HwNKEya;FF_d4Qn7UGYM ze7-kN7o+;i@cSHPu>-fk-|;>A7#!qyzX3P7rupy!?#TIA0*<<>c;4}>FY@c*7X02u zZ#)IN@ou~W`&y{JTevFYlWKzMH}n0y6{luATH+jhuRqE#`75!9`R$ldp6~ac;XXLS zM78H&{;Pt`U9>;W#~G?dKm&(bf zs(gUwj}@_FqT(^wG(+}dGtc`h)9C+Ywbv2{d7k(yc1LP?uNnSc&Ocq{J=ry1cgC*5 znt!HXi$&wH1v@WmeLlkG4s!k(s?T{z{sCLs%dz;(T=^h=K2LstbK&$eRbSQlibvx% zL*%x21|Egu@Jj6dm+OBVF;(T?;-oxp$>COeC5hL@MTqynP5P_+bbKCfGvYj-xr|eD zJ!hJw`qNET`Jy;tirfT86CaFA5?_o*IhB79Z@>@me*E=pwHNVP%O8ycHMG9RU`tM| z-}QzED*ghy&ujZ?I!E<6oQluJUY>^@!>&lhKVj=%>TmP8DsO8iuf<~*$bP&jR(^}u z5s#dw^5==y#=D0qzXwh_OrDPG;B7|v1dYdaTz#zalh0TE6L`O{DDFbMIo?EkFuvGB zxQh0Vl;1a3bD_PjyqiAD8Sdzr~Yqj##yK z@wt}2HuiHrYXZ)~@!pHm@cR|l40C)p{i^y*{N6E z`ZEF#d!+W);3ALZQ`pbYO8q%tc$r*$h4LLg$S#~?qPziT;(mc2+lYU_m5Ezcs=lYaRK6b0$Nhpn*p6r8 zl-xgv!>+6vua7v8O|G$b>c3l0dzh3ou?6L=2_sXR=uza=Ucx*W+m)oegy@9+I2l>7|<0k6QrS(w} zTL-BBBk`rsdI>*IScmJ)(e`lxx8nNm1CE-lc)`uIPktl(i0>JH!BI}-FT`E&K^#1+ zzHNrz;2}64$B$(=f%*6Z9zuWK;+=dy zZrQ5#j&MAvk7x7#Mt|%h|5qHr_v1cX9Y4fP58cfCT@dw z;#KdIe+T<7$PvG*{sM30Qn(#%g(u;WIP#hD*I|D-ZSQAskn69A?W*5hM)8u^*F@!e zV}F$5ZfwRovHh{OuXos6SNS=2sD3NQ4+l1<*Y(^+?8?dZh;1u%JjlLNzje*xOoujqPpa278rn&#U=x9QGYieOGayiERBt`To=LAZ%(OAHwzna)mhM zo9oM)vFp723u+Z|x6J2UWl8q1+RjnreNo!#11thunvhZ;4cWbqr_K`POvo z<9^0b?B#w%l0Q}6#Pwz~?8&0`X5hdojkh^o`Sup_2y9s|Cq1lq@Pv*>9Sqa|SnR5$ z{--)Z{z;8zGwf!=U zmIT|OBzYe$hMfvBj2Y(ArWuKg)XT6yV#Dm^Z!FeVfO=V zKZme=t>%Nw7ggR{T<(Rf+tlCv*k;xC^bq^_e*nYrX{4;pMF!kp@d<_?Wsrn0X{nQ@69H3e&D#yZyeY=RP8Osj@(+FE!fZU_>6ch#cyGICHWP0kI?p#;howu ztyBF~aghJ#(B1F~#b;yRD*1q6>bs5I^yifkpP}}${>$>sk!xW8EV(OoO_yh4(`-2o zJKXX^9GoV9#{QXdwgk0jAzlUt=}%+qTBZCSv6JsjVvP7g#aCk=K8}OGD*lfV$4TC+ zeJ9R_{qvPy4V#H~#qLo1B&epLI8(C@H@?{AmEUR)2maXV~^QN9cNr^@RMPnP|L$I2g# z@>ArZiH!Foxh1xf-xmiaDn1Fj$H*(Oh4=yN>8kVNCr17U%`aL0Q+wtvinqe9&hi3m zX({`{4*macQ~Ug1#^)k&JLCDpD9`wO#?E@$e=~hjd){*DZz;T?yj%}oBK{-x6CZ-@ zd>=d$Prz&NcDxU#=V`zGa2$F>Oh1h&@D@ibL()z?3T&bLb9r^)3uI6m|{ z#^LsU!S&c5rs1S~&$t%biTl3TWil;ReOHNFs%ifTVnEBfxKU3rP4oA zJK|}0Fy4#ZxHHcKH)9jW!+302sr7LkyMI^vHBzZR)@t$$9DQ8gh>M(%{Hgnaow2Wq;>)o!m$tvsU#UJz9(f`5=9OPyXJ(bJ zkeYm+?~KNdjf$Ve&Qx;Rua)nZs^ej0Y@Z}|!LF2QZz^`DkblRSCdz)Cj_dKqxH9pS zY1Cdc@jTeqN98Nx&Ogh|a3b!7@4nRa`9y5ud~YWXtW^IWV#jwnpDmVF?VDrdzBo8R z+sgv%8806;^6?#P<@o;@w>q!!%$-j4AGyH#z(K#<5U#=Qnvh_P*Etc@f(`%Ac_NgIp~W{p0^54mIpkeW$T=v79e6?f55(1Ro@mI zIZQr<`wo{M;27e`v#S1!#3Qk%v+^tAOSm~s{*&T;@G*YBati*$@~*?r$0`31j>Z98 zAV%>Ict8(1Q#SQ)11^cL;0Aa;uCv!4bSqz6y7% zsQ4iqNB#|5i03o!@P@L=H|J3Oh04iMxH#XlHo&fQnm_yDm>79F-acMli?0zsj8jig z{5EdWOMZ`M;EXxdpEz6sPvia4hS;54<=xo!tM<=}ILLgIIhX1Sj#s=b_AZfEWB+3L z0d~%pOXODhz&y?W4(yAS=Ns`t%8$p^g7Qo3ZXj2RRDG^*aG2(+q!!iZ9WLj?Hm*-<;owlkJ7XUni_O%x0=w}cY{Per z@<#i4)xL-J@?i`0*T62^3ES~#qdZ=QO*rn0U8bceGJH9IK8qu=%AfF%Pa3au`P83n zd33+9Hojk4?u|#)kmupCrDPvY$S41U)8&=31~$cJ!|s`3l`q>`MgAmdd){?2eN*@a{9 zQk;q9JBPQ_R(|qA;rd*rP3yIOvVXD3wAZG1F+8ZITn)Q$Q_832dMB3nF@6s$4qNy= zmhFZ_Gat{uuV`-{ZpZRG!wKX^Myvi4 zy$T)ba`73dj)bcAFOey!Trt%j0m#n%RT&MVV*t+D{Y~WQ*xOQGfE~nFU^C^nU@Q51jq8jBJ{Q}t7n|&gpEC0CBW$Unc=9^*hvl^xrv0keLx0<1M=g~f zgk5drIYxQbuNT{zDt;P!8p}^`fbA)wuKHu5zCzgcfA-!5N|Gfl4{HNL9E7aZ5t0{{ zBMkz4FgnNX%&h9F=3x))_AZyZT$(!=*kwgcRb^Ip-OhSUJ$kw?D>SwQ!nTgI$I?My zffx{vHA2<}NeD2kMo8iiVB=>T~*jY}pAUGG z?*Hyb>G~hV_uJ=i`8Lz@SDN}f{_is7H|g>IqG`|bf6X(x{|8LZA(wA6{eBXc4|shp zxqO%B`+4Sf#?Lo!`4*4&N4Wf$$NO7czWr7`o?9Qs>;191{4rd9%^m-=8yOrjLiF%>3-1a`_hT zub<}fUEaU%{bt?YEnffsn#;G}qUZZOmmhQcOD-Sq`2II8-{s%m#pOqgzn|pt!)G+S z_xyOqC$IO1arqXPpEKo;)Ad)TKGXB(n%}wn4P1W2^!4MW%=`B}p40suaQVOD@+}_E zb6kGN_*$DX@8{3w@?##)Kj!jnhWArke!%qihdx2~cbDlS;PPYUkDtorhYat}n=<47 zE4X~X_G1JSbDf4*V&gHv|zjvB4&+q%Ve2e$j&r=!a^CLc=-{X147vuW_xO|K8)#36( z9`Bn?nSW1AedZ^hX@2MZ@Hb4Ee}9R~4;a7SZrU^apQQ4;JvjKrm$W_em%04+xcr`X z^zYx!<=0X9Jr554%9}nw$wwc;<*!}p@_)nSzxrjme9Ywz|NaIp|0QmJ%H_}D`fua% z-{SI=%YU8AS5(G$-@)Vg0{;Esi?37T`A1xS@YTBif63)nzDxi93tT?s{@?49^n4Ea z_YdOoOJAzn|F>LznagkC^1q<{ZPQ~rMa`?qlUHjnQox%~3K z(7*o{mzO-g_y6zp_)fY1BQC#&%g=N9?w9EP=3IWn<^P$>OCH~sbNLl6f3x|W`~NX6 z-}(VPo?qwkgYVJh*M71d&%xK|{<>U#IMe0l&F|d*g3EXL_rJ{L+q_<1$>m3kk8k7h zJ9vIS&E+MR-{Vts|1W=r9?zfV@+p^ZoAP(--v?Yi_$WQT&*bt;ynbKE<=b5TMlRp| zm%9BAbNLm<&#!a&9bA6hr|R+C{%{TdBe?t;ZvSaqeg}`|?OZ-(diz52JFmyPOqqZG zaV{S)eh%Kk_~-sUkjt-myB_byaQPjKk1>}YbN_#l%P;ZoU&iG_E`JM`U*Yo4aCyn) z_j;@D{{gSxhjIBXkMGS~ewoKV=kjYlLgVKhrp&*84VMpjegAJRKVbU#6)r#K@@qd$ z_x~Ek*N1cY5%0IRarqS<-)D09WiCJD@{;NIo4Nde$M%!xqO>{zsu!!GXDM|mtXUd zy8b)4{Ig7--^JyJTz-YipUU;$cfkD}>-K#vzk|zff13zs&gjaxUNH@^^Ci z+ju=*;qoEl>yO{n{e39o<0H8IGLL7-iUUKIHxVe{uQo_v-!kYg~RO z!~4LY?(Ze;?-?%NX8f(Ve9Ghfn_T|YRO92HbNRJg{}nF(EZ2YCNW*``_wh20@V(FVcpfqRd?=S6^Lo9J%eNTcvH6|n`#*B|0oxlMnlkI} z@8a?!)*pV9%Lk0l-{A6H*4JJ))#G`{>-phae!%+LTey6{@UFRhoALGerp)@n*PHs> z{s+zPtk3-#mme|yU-yFU|2D7BN18J0Cx4d9cbQ&3lgp1-ANwLMA29trGG)fkPni1L z|GQmqfBgFgnKI+=4P1W2zbB^5zkjx=&-?A2T)xHY_Z_A_kN@XQnf1;8Jmvm*{)b$? z%j^3*mv1rsZn*rI%b#o7^L)R~{LcL0hq!#ev54im=a``UP{~w*}{vI>Eb-4VHfBzIN-{twe$mK^ozb`ap zrjJLa%>3+MbNPVx*LyA)zr24xjLQeS{?Bpw)?3(~!sSQY{&TtfnD_HHaQQa>{$pHz z$oPA=rS9(m+YA0nQ|9&lI4&P>d2Y&V5BOhAeWvHHHNSKDhq(NZ>Fc*mneq9-_ZYvt zf1ly<0gq?S{r`nIHW-Q|9{b zztQk-Gk#CF{D|pgY05mFFEI5Pf8S=xJilMz@&WI!_t`Rh#_tDn`GE2Bkz9Vj;D6-*eD6#B z`_J<4e~ilb;g#Ri@lWr^Wjw#4>i;Q}@$!Dt||*{Qaf!PnF8=cU7GK+e+oXTq-{*mEZlk*gh(i@0H5mP%8h2Qu%#eEW-Pc zQh8J=zr9rco2BxXm&#vVD*r;M{NH^>vH#Oj`F|>vzoS(CV{b3EAC$_URVsf~sr-Ga z{2-&pA1jrAs#O2CbvfH_fB4T8`@dBx2c`0xO65;3mB*#>wJvA6_~6?80j`=KJd4kp@xikR2S`nM@QL^! zrPaY5e2{wW;8XDVRD9lo&s*{NG<@EM57KfTAhp2((uW-&1?<7c|8cz4g3l-5bB<4n z&)-I$Uy9F{;q&GAd<8!5#OHs<=PU8~JNSGRK7SXV{{x@DhtEs+d^J8_gU{c`=O5tn z5Ape0eEtzWUx&}vGXxq}V^evpxquiVvRiIlz-Y z2Y80_08i^2;5nTGJh^l5&j9bA`JV5%z1EgF$Kw8EFq~<+9ddP#H z!v|?050E@4@F~e7+Z-e}&KY;q(3Y`~W^bh|dq< z^TYW32tGfG&yVBt7@wcS=U?OVZ}9mke100ApTXy6@%ex7`8j<4Ej~Yw&oAKfi}?II zd|tul-{bR3`1}Waei@(t7oT6j=Re}}tN8pUe0~j|U&rS+@cB)AehZ)9#^>O*kX!J1 zH+1>(4ISe-I#cDIy4A1EA$#A`yu9ES| z>2r4m$HQcBnT$7!)ybV;p#P*P{nE!T>&8R)+z z(|Np}Ef$+|f|vq;dxlXr0}&^iWHp=4ll5{qPFBgiEzmbh=9~2iAl*3}IMEgK46w;& zI!-sj#L^VV$I#p(3p!V$bhsW|(Lyd4_fKMXn=mi~*bJA;^m@G=Ez{v#kpKnrAQ*hFAX>D6(M1DyH$i8%nXh&$g+;wTqStjgqUSU!y3U&aozg*?7H4 zmM5n%)}iz#j!#GUcntfHF}mK)%DV!{jqil+C5{f)!%4DUq?gHH4BDHH)xU@Hc)N^g z7Xb~yA@JO( zUX|xSYe(dTzq3C+XgSV^X1QITgWy-!om!GPCE`{8b8iO$aOec=1RLhNo{>D6Y>1ro zt{;umX4OPOz$C(Vb`pg-QUDi=x!okAben(-r`SQ04d|Dk*t^FGH9Vdt!xdo6lj+&{ zXt5ex#NZNuINgjEdO?r$SLMZZ!j5_}?6YK^tU#I6k6}nzA1P=ai|Ga)%E0e!lhyTH zVV+h2$kNM~~4+hUvM694oymz`Drqi=INd_mU z&tA?XJa=a4Qb{J~=`fDZr*WLj)8TcZdkqh9Xl|zJi)8}ZT5eVgoYJ%#^&g)-86KNX zaHhwL+4RL^G#p>tAFkr%aJ^RZtLZ6?ojX1(PGPa#fCILi0$4GaLZgHkg=P|yX_}sc zE2hb1fqryorK*O;J|hV@npGLjFa`+oUcp;aWbB+(V4DRk&C=| zPj{SdaXPG~b1=-fZn7+`8fD=S?WEKs78%&Oe>3EyspgCI&ryX7h9}GGPJSYJ6Ti;52qqj^dZle@L51m*d5JGo5cCe&7pi zSuK18hxkc-#jLs@-l>j*&RW%(uL+6FpBhmg(ss!@#%iue4Xf*QRG!|By#_t>s?H$% z4Kg7rt%$5l+E7Uq{u-9wcZS4Ms`#k^tU9gWd(3e2ZC-3{4&C z^$&V6OeK1hqwQn@4x^dQ;rYE4Oel(>Ij3CUX36C+-Gaj6DO%6RiQcN>NWASI{fyBM z(-d5CkquLG-V9;(M8T~)gY1uU5@9zZ9P^KPvbn!lT?qW3vU@UXNey|cB?nwyPEBlO ziPJsC0{hU+3Cw3UBJ%>Zu5KXW^$FLZ171;DUkN>DU~D@MBHw-( z$`~3ma5d)kXs~+6>_)@0Gq7z#$9f!vc`NLbz^;uYX^j8FHlV)F7ex(*+Kn&0cU@sY zE+&fTVEBU-N8!WX7@7o#uhkiQqeBLw z|1y}Jf>GpxcyL_&)stC--Sv|ATo4bGy$nh}>30iQxU!KNNg}gPgHzDzG@d+BKL^Y` zoB?(`olM5t(X=R4S%@qlB>OKejceG?}vVw1`!}egqy?_mSw0iT+ zU^HA^lR^S>%bawp6#AmVeHdClsa0PtU`_LoDZ|5bJ%KC^&1#BW2K!RRq%Bm?%q>J@ zvOfO7=TV@*@b;sXaUv`1>Ymkq^#N+=RiGy`0>cTg=gBciYLRjW!y&S(eKM zde#T1JxfK!?ukT!h8V^JP%BX^=}|GX$V)8SWs(E7n5W&3vH7b0+!J8DnQ374GQ4R% z1P)Qyh{~X>Cu1a*W7E1!EJ4rw0=T~1H0Vy_`+zi1e@!=mI6YT9g5Q7+3UlTBWS*>%LFFp=Z>^TE{JeE zrUS6M;c7L!K7mz7G82VxHLc*k(tnZY8gHjDpX&UhweF3aFzhR*A8e_*#VB2jFILHf z3=weqmG*!s@>;{WKn&1Fi8I}dpJ+ExxPpcj=O#PtPElV@(e+i3&4{Pq-fLl3!{m`0 zu0`CiqV*GhfKQ2#;&BRv{Q_iF0HdwZ1Yoinkw?gNwnY>iII(3#f|Y z^F^AhFRpJ0JPeb|e9g|+D5b-69-e@{v}wxPZ!sOd`L~Aazzn7+kLOK@_KVVJoB-?87ky$>Y_r00}lk4bzMJ^ zb!GbIF)Na}F(n%Ewt?P!(*!+Q6GO*1g)Z|52xg_M4;2ps!gETr=Gv7(-|hhPos3@8 zM-~xXN!C&9gFy55JObUBrgT*C=`|e1YfKEE@TvugMui%dFajrq#aHrY! z&N+5N=LWf^Zx?ODQdyWA?eTi>W$0RFYdN#5OForfU36xr;FCos&+Sz~=Vmn-jt7;A ziMBXp2(tK~bK?ca8a(stWx5sNDoU{6`UsXK(CHRtGauINA+s9LybMob)Y%H}K59|L zfNgI)8xNpo1c%SpX&0xLP%gyg&vmFdRFvuI8{>xdv+- z%^KpmV!a84gl(n>KMR$Njww)`+)`8|43hIa)-dch_2k)+lfEN0RvaUP;d(e)Z0B(j z!xS$}&El%Lsa&xT*+!9caP{4p-`6Rt%hw+4w#-#c$ z>73bFslx&)>(w|r*gB_h#X{mD9~1oE!`Ubv>OegYbgzeyM_}eiHfw~;&DYwzjkA1B zwklrMypsB&NI%pGeS?#WV&Xft%$K^_ zc_VrdFF2p4>p^2DHcTaIxuuZcczjQ}jDWU#GiBmPfOX&^JOmyuldF5%VN9mKaqd~N z`|;UwM7Z!WAU@0dqew0W^( zO4bOC;&mjpq=|}cbO8c54awC|Q}b{Oh+iej?dC#!0iw*Pqve{IESr+*IDIXTK0Tz_P~T^4!Yn)QICxc{AkfDc2zwT=+^S2>3W%s2CS9u?N7o{x zhQ^ew6!&R{U?`8HXIgf#QUSbs?xKjwioE{npfc(dP^q!T6RlgvDo*#Zs>PwO;P^W; zl<@FuIGYV8+xd8){zW$=CMO=2xM(^`Hp2;>ayWle+!Ubla!rONck`hC!f=I9*%3Tx zgBM1dQ5qh4^jdA!^=fup?kzantzEcdyY6A&F}?7xGR}@y!{G4#dNsJeo}SHyUbiBS z#|wm1AT|xB`UVC!L>@0GR~TH&{=v zM$^qY93vcefDIttHaZ+Yj-whOSJyS)^!nEik}Ham#oZ>zBf&xWn@*?tRloYxapkM- zVfCvj_-gbQEqwiHm->Z(kR~>z?I{IF9^u6lnd)3kRs-r)yj_bGz{0=^>w;gNvxNI-cc;5-tL9tl{F1hhv2-Xj6=QBS}@PryOHx!aM{@i;e|&60IFq;dKtHc)^KUVJ@A+ zDqcE=RlIZ#t9a=gR&mfdtd6;JD2y)<#uo_v2136I{8w&FXg?WU+JVId}kuZ-)m`5bcBNFBzoQ>U%(6%GA6~w@9 zN5DZxz(GgAK}WzrN5Fv~MOHB&bKPnW9f3+a0`+wSO6v&J(h(@5Bdl&mSjmpCYMrP` zX&phz=?E*?5u}_>RK;kgQ>Cg-N5H8-86AN#Is#>M1j^_Ll+h6=qa#p8N1%+3KpCB` zFb_e)=ya+z}+;jvxVd1PQn!NWdLI z0)8qDrAg;ik%p>t)fFUfS!xnavfq=ElrRtBe0wTgrAb#fle;&PuBzupSFl=ih4Z{C zoabHPJnstUc~>~kyTWxOIM2JndEOPy^R957cZKu3E1c(D;XLmO=XqB+ z&%45T-W4?Lu5g}rh4Z{CoabHPJnstUc~>~kyTW8X&_Q3YhV=#7=nIywzCbU1fnNFoz4QeOR9~<_ z^#u!5U!b+VV1eojc80!Sf$9r%+7~QPeZfZ27c5YHfyVoSjMNuw8GXS5)fe_hUyzad zf{fG`WTd{ZxB7yN)E8u=z91v@g?-roGEHBQU-|-{>L_8@QvfjJ1gLD*)(P7#P*pkaZ81qv1j zSfEt_H&w+}n2*5f1WqR~I)Tp#Y);^E0+SPXoWSA))C&ww;BNwZlh&+?LV>pltW8?% zD( zvrr(_P$0}uAh1wa-B2K&P*}N8;1!{8#Dv0e5(;Y^3dck!9222H454szgo2nH3dcz( zP*NxyF`*!0hr+QF3S<}x$4)4S=b>;Eg#rzS0uhG-6^8;DhXNgk0wIS2C5HkjhXO5! z0x^dIHHU&K9SW*+D5%n*pn8OYDjf={bSS9Op`f;ef+`&fs&pu*(xIU8gn}v^3957? z1PVlgx)cef@kmgiqQffpj|8E)oR0NRaFzK@^GvStt^Op-7U3BymWRheUy> z>ROG2bLLeE=o8MZNKpGC;Vg>;*(eguw@6U?BH_%71aT!2&cR3!nj=AKjs&$Y5=7}p zQ2Qc5?TZApFA~n=NKpGC;hc^HwJ#FR?nqGkBH=ubg!4QS&htn(&m-YHmqQeUa0vl_ zqPt(1hwOG2`Vh`|(S#sUiEcK5A9RI%*%kI>SJ;%ZygoHXrO8uc z6mam=7?nIVMy1J9V-#@k)EJdKHAaCBg!BCWZ^o#nH;+LFf^+Hj&&}hioEQm8ZzLG$ zBEh*73C^WRa4x+nH=YF@%Ku(m_0EQP-%T*&@B7|ab%m<;ysFwmRhO%pA_c8mP`U-* zmY{|UTKMkzaCPvWDsmN9ep<8OyAs?~f?yyysjAl)B^Ondzy!mvipwkN~y`a$x3cYaF3#z-|b`rc!g5EBu?SflIP|yW~wctk*+(<%Hh!6lG z#D7$`Q1$wdus{xoETv~}Si73|_qLsoz*Nh%QHV*Do74btpvb} z5Q6$NKN;{Moaev$^OIHOl6_kV-*u#V{{PwUrcE;XINv2?%Q>sHt*>0D=Al zP1QHFs>)p?(@{mk+}$Qr9lj9IEW|DgHX&i`lEF+01d()6L7EMOfdtY>1oKoN?7Bb* zq7DQLRUpKT1cHq!5R6oTaIq{9%v6COkp_Z98VC|;AV{Qv5HuMGLDYc|L>&mbGZ2EP z17XJog2gHjOjdzlvkHWr9SBydK(Mz5(uE8m*eetQyFwv|D-=w^p9RF^GGRl{*8SP7qADx;X4s>Jg9C61?hq6k)6!OAGy z(G$Y=g&TSTE`|Gf!u32MYFN0OC%8|A>jHvWBwWlB?&S&B@`PJ?!lgXnPM#12CEUmp zF60R=Q^8{@FmSkpR0)zwMTnZ##r6_w<#l1i{(*jB8l;k$a(;%FOfgqm-qRdy_@quub27*;H5aiZC zaAy9lmmw=u`lP*Y)qNoxwUR@wN=$<0v%8P3s+IYTG;4P=Bj8Mg8A6eJfrUq~}oeV|T6>QQz9# zu7_ud>f;D60>*EC{quJ=NBYgUXYLH1;m7vw44=Ux@h2zrcSwJ)r_t)Q?Al$tuwgTLmEy9AD1|qjWL85GYt-94*%OhfDPoR;dMEB&N8;3Xz(H_Hi*Y80b_Mz z7|ZL@Gw-phFZf;M7$T3Gh|T%+wt0GYc>2yx-Z!Y2_&EOdksh)2RDkM-hhIKJnr14lER%)F3#e2=}YX+%%J;tQ? zHEs7EL0xA1YJ=4PE-jHCMWsYfcvjsAKgz0X#uI5-Eb=0bv1&8V4Y`Q6Oxi*Xh&;7=W88L2VB+!Sry&KJ|(PTVK$9M-= z4W^Is4MsAW*lV5xi+xs{-~8T9oaM&iX;>v+Um1a+rdzEz7S7lbR1Z{Zs@boQ%p=^7 zkj%i=l1RZk&EnL)NL&x;DfD5SR7LMo52-*mpCy*0Wn>CB&es~}B3bYo*x6?Bk{Z|Z zdAeRNQlq#A`4^fuTL|(P-V$cKWV~2jYgQ1h)fdcO{05|2Sk{mN2=@>+!?X2bwJ{99 z8onh-9}lya!rc(JOPs;G3W333nOr5~!FX|*OT~uWdWqG+U`<97W!5}7y)2Gbnd_bl zf?;}=PDg_&C=-QbFj_29Wyu+NSaPVr6v7`kZsLBxUcsNP=gHZ0KJl);T&(@8li`PE z;BEP#8L<6j7wd-A+??(JIA)`!!}b#lS4ljD1~(t)daC!14%pP7_6!R==-Z|c{cWS* zXP}%Vvl)qNWiu-gO@Y~`P=8PlmN4~?B2fueD$~LGdUb&g6q_pojIp~@t*A8*HO85` z()M}HBqGc=`>H|zcYr0uoM?D`uE1Iw-#xo{JzwCkAfG|jI9mdizKFz<@dg%(I~}*` z7)(aYS5qzUS=chGVTgq6NBWO@q?65XxlFH@DIXs}kE{m}_2z4sO0fNfW5q3<2D$Q` z8zM3UjB>g(81T@)EtvU9lEZ|_SWC4QUcI1_R;;3D3@&^}CITd$?Q zBV9@T-b{45;%GZ%JFK&({QA;;5Kxenfp z3RF(l_i*}eMjEl9`KF*GGXc2?U0EZv!9>9;?dJlfGy7B!sAj(EVn8Me!dSZDOFk09+dcD~m&Mfl5Y*<^_VjS};d>s)X*90^o6KnML;s zSQ(T?WEjytrUu~7>1*VTlx<{h(6o7EU*N_!H30>*8VYIS8{2>^ST{EU<-k_^T+P*9 zS97(m)lBpn&`i}S5YY6K`8t`6QY~UQsg*&f`P~qPp4HAk3f7&_Ykrr&V%bP2cPctWB8`ur?ONL_fOF{or>R+n0KsU7-%iPV?$@OrOEU!1` z!?VyQ3?<0xN2u?%*P#I-#5QYKl{r4=a6KGvrk4r6l_b;V<;V?q`%|=XnEO<;7%BqE zst(x^R(nUSP@D6n>+y=&f6+$hCY|*%oo>=(ezrNs=?c;R&_f%CRq#B4=Db+uXgBNz zTBkZ1n-`Alill9qSDi@TJ?_m4);a89n2TUF^B_DuK2mkVa*tZ5XdG}jfQ>~N1;?{X zLe#UgH#PTy%598W2*`m22!8~lPYz;bgNPxDi?y{j>~E+HE!${5MP}NXpYV_!Gt~yt zF^uxq04naWWD->&b)&&>vl))Hd8iaK=UhI902@(eIVi^Il_)?m%u)j~Y!6gmA9dNo zH+4~dRGUn){3nd1NPaVEeE@H>*pAN!5G(4@bUC0%Em(K6SS=A&Iz5{wF-I-R zC?`y1WJc<9$A*EZLkdVmepoXmRiHaD1MOt`T{2DFw}VM9qY3KBj}2V%7F< z`L%l>)9;h(A>9hDQracfBA0fVM4F5dSVS*KcQOEsESP=4?B6eV&tM_AEnwACvZ9ZIlywE!h?Z24wR~;up=HQXtgXfRgh8Ac8IYMXctwGcA#;2N z?O-!xyC0N|7M8oLeP(8?w6`dn<^(gQ?$S@e&>cV=wW3q30ea{d8H7y9C=Ai9W`xS_ zsyVZcys#*(OeBbDVX{Uq#h|n1)wFEFIXRn6$Be>=52}-04a0gmz#$}x`x!MF+S6i` zH9;kYvT)VT0o9 z&WF$#?Z`+R9XTxl>v~mT%|}IFnML^y%!VtIi8?dd`4>V3utvIvudt(Z2uezyDDx9#`q|12uHb^ZVF>(??g!c5RKjO?(PW- z3VW4qI3eRsx+$0&uTjxjE>f~NLOjgn3Qt9Qxd^h=<~U0>_mk<_xwePZI6@%lDO@@? z$=PZckA@pI&(uJpO>4_tTE#B0M`u@41UybQxVxxMkcO_DkT|NnZIHFmys!r>_NF+? z_|p2u*}o2zHc?AAnMoFarBBt8sfO@=Gw7bA;$bo&?}ug_Kzpv=^KMWSk~>J;TpowU zYQ30`7b`e_4Skudd3&MjAo_fT>=PdD-q2jnh^Im%t&d3``%+yIo*2=iVgh5=XpREa!7RPYU-sd=RH^as>@lR8jm1s~PstKu<;UFw<( zL&~np97~YAwzp|$9i`^0;;7iX=RrbRBB{(J-z| zv;J7>g}}MAhxB*yHvVb&JG4u*2f&{R#-J0n5pM z2FbmKI;-J~+?p5V7*)eBCR9PnNkpU2LO0D{F~&yjrxZ*wkaD<)i(BgYt574y`(hY1 z)JwXDXQp$!U^O%x>qVS#$t&ix`Ie zU)PIbirrO=GEW$iW6_O5En=G!zo3 z=xjKf4YPyFW0+_6a5N?>V&zH2$Biy=Tw3>-o5!yr9WZTJ%W$!ma9WIhz!w(fdhAUF z5Q!P+rlA%k3IJO|&_YB3f_vSk@qKmOZoZhVb8oh>TN`V0&_O+0a`OPJVSv{fbE>6r zdDyTc2=jCSPMap#Fd0x_g}N5!=L?Kf%Ag`=xLKN?$PUbaKFVv(2s1^)yj0MHvi1b6 zA~Wn>?t8?4&{42c2#D^8^dAjiCFI?A+P>qqiQqyz|jpE={(S#oSLZw%Whnpdo5X-9Q#$13T05+X^0aT4sxb~XISOSgf zEwX>UncT?djQlx}+?7d+fVOZ$N2)H4oXs+Gb>z)G^akXV}|+y!5}_qdf)zclUwr0Tf+?e!7->1ynGYQK{I8x_1)Q90)~xGcB0H`m#3b6_4G zLmCWt7IYHqer<8e+q>~)JB;^eMl)b{HKUpKJFCOS3rwlyo{SxiJ++G&Hg(RIjCrPw z|0hD&H3b0S2A5&pLDMZ?4}NU>bS1Bgu~b-G&rK^fP;EV-<~Fkfy($H#9avf;aKI@T zDP5vg&NJMq^-z`8#K&jD8-!0qHi#;r_?-fohY-57--Ad7E!G$eF##ZQX`~GY;7fP=1;kKy;j%?cjvf8JG}6|BJkx0U4jk zKGCL)xt~0{97GtjzMoukxwwn@dFRlOnrU!CAVu&Tb8tQN>2TT?CNTX}893|_C! zoHlLNl*$n4K3lcnOR=rm&C**cZRu_7uv-xCT_ngcmkIi8q+uZ+wo<5Hc+|r4&WM4UZpFB`GET`p zsSvyC)n3ydE}*KrIQg|!%h=Ss0>=4hHL%WlAJwZfzBf}wH6~MU*EhgjEp1jUv1Lc)0Qu>O&$L=`0@OH#` zAsz5cg2v4#tt2)F3zMEu}Y=(ZU&Ll*(XulnQePOt|w?&8K%dO^&fmA|>Z{AKi<_J($ z*B7js+UZ2?@p!D71#Q&~E%t#%8O+zlSzTesWD+;ByERVtoS}t`@qB=7NP{m@d1Nxf ze>2ulnOv?os`3l_E&Z^a97cX=zY3!?vB3C%z0V)+2IhloE0$cwKDT1cXaOeo))hzV zv7iMT58kX%t-9(D4RZj(1tGkjjki)!Ku47;}Y5?+Tiu2#=2g8J1>roQwv3U_p6(Cyjg zdTD}7EC?P)tN~%!ybHIveb9@oPWF9+9F5-GCPK(k*ip4w%q{cRo*~JYgmTK=Ydc4s zi)i^G-{z<|6w+at*Gj%hznu%Ysj8cg{usq*_SR#SK_jVDMsgEQhT@m&Y*Ro-K~ z1gEYJD1dd|DxwzQhJp|LLd;GBd>XouwA>sB+BroNYv-|cH&z{~7Dz?!r5TjTcaP5M z4vnwm2`r6|9DsiFq{siB;2LQhZbzU_7Y$eltsu{9F6BVXebR$ZJZ3|G4q8@8WDB&6 z6R-N5Qws`4p4M_$>dhdMTa+WHvPWKWS1GMei9G~|x`|)>R7bmW$aJ`iKZlz8EKv&= zQAg=%n{!P19FM3e>TO;|_Px-h%h?5#O%AoQ=~}s0=?AEtW#gD+En<*+w!bjcSIz#y z{oO!$W(Z30t-kER@?9=~usbF=wsOIxNLQ(7(I?)mV>-lbO1+lq3@t9DZ$7w=c9~sl zR~tOXBlyg)d73Fp16W@q_viGGbZNIKZs`L6uf5}X?U8GdrF#_-GCU*YAgfqppGTsZDu z83|Eh#HzvidWKY^h-A@+fun)mT((!^$~cXM9=e0!IrOP^-aIriFRJ;wlDfI8vEf=> zH+L>J*2>(8xX*W$-RL7KqDy_^MEOo%qbsXB$ozzj-Der%+RPvFb9+ov>igEqlSxQz zhNqvd!yu7Yi_TnX9*P*-@+g zVq_lSM$iaJnc<y|`wEtOxLl*Yq-*uJP#G*_=X%5P@(hd0%Okq!Gb^ z7weViRHZBIACga&6>D{;J8C0bUi*m%x&Ge5X>yw*9wTWocGUAJWT zDqh%iNs(Rc=B~i7@9H{XSP`mOlBL?XRktavYr`|i2x{Shirh?_z3^!=!IOh)JN;IM z?$JmSq(88&(r@$lCj+Vl1SPIkg2xQGGJUCWHTWW1AV7_n-ZM2_v>x+jRWoakn2Ijc zjO}cGqs%yfF~1xmjmdO1R)HC2CN{GCXaay%`vC-;>(Kw0)TK+{eG{*z^#?kOlyO}; z67)^&@pwA}o$x(ki)({MyvQV8tJ`41loYB>uI(|s0R|12(jwS@JaAEf%gLYfLxtcH zDrlInM0+4t=eI9$^49t6yOU`O`(X@WDi12Ipp8al{`g!;??Twb%gb;ZBhpVt*mz~6 z=;{EN2}l`!6+CjQP~!1$PS=WCQD+iUl|2)`8$Mlne}`V?j;*Y9HW-Bm55c?`d{SZh zDZNc(XkU7Q!H3POkP~o71e0wSTQq z`4~DwNIXWQ;kmEX*qsz$5Me?16SPq^W{4-i-^(7mr&&*E=}lyKsi$Dt3uC z6z)ZP3`3`RFFHvJFg(@)1F@n4aBqbAwFpvyXdM?o^HX5izoJeD35wN|S_tw+b3;Eb zOV$(4*F5y46vu$G&|_nnt^p~}3SWZc4u7!+ln2r51V+6TLSaM3JynQmC@qviAFaNB zB7GXF74=|W8e%??uYQ}jhZ5W?vqAF1RL85f4yecgSIDX~Jc+DS6>Vbi5LOIoDr9B# zI}-#}IC0wZkRjRid_4o=Kq+ zy3o9NR9*66r%`=_lSis0uGwAL?It^ z!z`3Uu?|!yc2T^lY~g3uj`#uQ1TV!x_w;&$xy$*3XH%$k$#kX9s$97sw(oI7Yf7%# zzB3AUV(Q2WaA(I5|-XvDEnkXuimv^D(Sb3-by23 z&!abBbJ-(Xr#867&7||2$3K|4g0aunc%Vi(rKm z%|d6GgUmvM^cp6Bak$#f$P`2-aV%NND0-+)p9Y1VFsbSij5U=g}c3EMUkWLB}{AIycnf%lvMdLO}dc; zcyw{UPp-FO`Uc{8-Z{07FrYl| zcjjIxIDKhM_9#hqYMJ_*x=~(ONWfOwJCOdTI(y4m2Y$3EZJ8nxy&Ji?Ux-pe(SClYlvQR5t=NBR0kmh zVb`uml7b#RH@EWewINR$Rn36iPV}QJWpN>Zppwa0yU$1uRZ;I54`ET2#t?KZSbm`@ zM?k&0MMBAg@hVGxpkyMj8|Uml7}ZK}<+1x|WenEDW8Y8%HPCLTftJ8jrPZ6w^$BaB zJbI%l?s@bE?5-Lpk8V#IC_~Fd-P|~k(YB`snt`fSCU(_8^_64X5~KY$O{;f?p?$eG zU=}oBY*C6Es8{7oIKk?K7{sAvukry4mVm?+^w_I@l5*q~!hMvb@~ty;l*MN+m5yC* z%5_)wM5-qszDiuXoIC1wX`GW4PkkM9Xsvvv||Z=f$awREGPS$>Pj-_qU7FZVXy zukGd6dmZ&GKU;$@p=Vy|Y;Do+X(wA-9~L*$SZy2_^jpf7O{q28;1SKBm$Y6r%lFqj zDw77;fqoUJy#sP`XJX?pEcan^Jsm#X&J)@jDD|-Y=(Sd}>S`u%%O9(SUv2uSeW0hTF2Z|^KW=enjI>ga&S3*Kk8&!R|H%4Y z9jYFC(rNTt@Pt`zs!OVV_TM+Y_8;(}!2kx{(T2sd%v)TOS8 zA|G3_zPJ{V9#`^U`$XCICU9&{kZfX#(^B1(%odY(7#1`8Q`OgzaLj9SoG-`Y^X>d1 zi4jYRAdfuCqudRl!lEVR?qz~%-shPcaLj1RW@3yBZqr{6`e*LN^UmJgGdCw!8#YYr z(zm|ss3LX|yLeW${2A03-p5XG@5_%p-Gk3v6MvYnL?B@}t{t~=G51Vz`j)M<#}4M? zbT}KuLw*#x=(6+JOpt@Mf2`;tX>yQZGkNVNfL1=Sl2=i1iZxTSfkVEv{E@!$g*HUUhedUj0>*P+mfFgUsvR zT@TV9?ucn|{>fzQr7j?}P3Oxk+@y;wU;L>b)(E~YSd@|}cLRuN7@4;&z-$b)v7_YKs!@b^HJt>^*OK0MN-cV;^SJYW@iTfMK zDpw@|N~xmKdYyVm>`EC@ei?pJlJgpw7)g$lG!E`oyjuIWaClSh--StiR0=#(1l!E> zGcVLlR1@PrQkk@B{ZI&)A^zsYFAYRa@59NKuG(k5YW&$KC~(`-yK+_!GXHCka&F}wBTs<@g>UqDP2-C%1lyaJ-~ zHnw$kn06i=T&S0;+IIjlHAz~UXMl>CpRaMMs7L{?EnRzk@>4=v^q8vl0gl^uKu$Zx zP~!|BB)~$`9+&yGKL^=hp2{-N&h^zNe;?X;ph&6RUQ;DsooB_&S=Wp_%bn(`m^si~ z6?ud%v}RWWT@@|oT4UGweLx39?N4KyHK_(9z|%QWj=U-82y`nB^+jY!AeZGjvrt7e9;#ZpaE{w`<|QKjSD zA{)Uim$xb&=3XwR zDpD-F7VDa3PSGZYlo_(=GQbE{zC3O>_i&JCp8Ww=(FsW1dzGZY>-BkZne}3rOQYV= zuXySz*(O*|pw%?yC)zI!zB&&9w69c@Jhd*4?2JL#y>RD_ITZ8#-Ut@1;~`NmsXHqu zb1IvJVVu^u=&PNs>h-g$ddVb}^RW8dE9mEM?kZltOtt1?Z8?tvDfhThbi>r-8?sh9 zqeO#)?3B{KoO-;=+BY&~`PNv`C*vmZBAoo@*sgHOx5uh*N@~180uzFJ#4PgZ{9U+3 zc5d?HShAqU{r5hqD~}OhHpt>I1(%Vvcr?DCbskLWOgUPl&MXg%fDF|cxAnWpK%FTk z4b~P43N{^Mh1GlkQWoq+E3^{_t?NFA`q!H%3dqjD*)c*9N`g3wlLZXTqm z@3zL%HC})kZS@s&H|c@;b+lZ`XI!~P1xD7qUdZCPS}*X}8KMl35}vNWm$-%!S=?|I z>Ggau!|iT(-SAXOGSb9W7YU}^FNfO{t~V{BrA4c;6}*}#52w9 zNKj62k+ClEoqL;UI5^G`cgXmnMuKqDyvdC5UWFeGFQ&8n6)TpA%qBAia~7mQ@gX$rKN5v_WIO0v!5~f7^K%RWHOWQ5 zR$ZE}S7y0m@)6{|h33Ji_np~E;8Byr=sb>Gig!ov_K1=HYYB4q%d3XA`Hdk5a=>46 z??dCrt=mN|n6|^A8K%j$(e5tNvA&C>T@&uXq(xSyFoJ{Mwm$~nH}2KoN&wz1WVYh2w*A}+Jh^?;+)_5 zYq=Zp>pVHuNuIEiBZ}O}y659SZFbXV2X<=ODtWFAg=W`=cIlK)srH?!$O@SAcj=Vd zw(k@hM_p*zrBf9GtEsrj2XSoQYieF@m4R|L9a9QvoN0bZPx8HFqYiYK|8N3nqqrBO zw3$>tR|L$Ih@jnm1JxD}Y9{euxmdKgL#rBQUV|YXO*qpkYxy$OCG?m#Uc$pDU7aH+ zntKR1$R$V*99TD?lz4x2t;1UL9tfF6zTZ9Y^tklsT4tQ9bfmkZ9wF_MG0QKy{@A3m zTO^YSZr#ABjXd9k0jq~1LL^>=Lp;Cap`OR`O4W93y>7+!Qyt=DUzVVOSbA8aN_4E{ zG9*<(5-IhhLZb731a`hOw?>-7(G~%`+O``U5?gehCt!AUom@fu<4Y#TSqO#FVw&d# z8?7;E7jIa&ZQ6c-VM?R>3>229hEu*5(V8SM;_Q}0oJ@xBv1`Ds?nxZmU@nI%aMKzv zt>_g{jnTe}&Rk$CtC#MLU;vBgyb)kT73k;ebjC1a&aT)d8{{bmC7=g8BN>7iNL^B* zN@f_lS+24XCx4lm~agxN*S(M%Xbj6vC~!UFneBnhwC#y#@`U^T&mUfr9W0n0g868Fm!R z9yXS8jhT{h%mW2v0tb(VRSXYWYPuuFsOha=_*E%ds^4b1!(~fcC&xu@%dD zicP@)CQg(vZt;}e08yOi4uHxA=@s9av8ha9${L`XLNaqOybgFTr|@WSre+mqtqj8> zpKE%@h}F~C+tV?UMGVu;b-=We>oM*5DWD>UY`TERSa^uF^6ZL@3C}CEG-L{0QP0{= zU(Ed=72wVElS{*3dzPe97KjZY7+ibo5(NAt$b*RgD%}P5p%L|?;_y9|n_q!pdw5+o zDPKTfw0hwA3w1^A?E?tn87#?4WTt-(D_6CQ$XstacB}>7CZ;!3@LwGZlQDwH7RcI3g0i>Pk51^;JdkUQ zpEM&MdnQ`KOL&$}M*~;jC?Z6aSw_c-LZPWD993v`SYcqtq4K>nL6fzBFu%=19$_L@ z!%$DUH1^L;v;NUiHLB?_zE2x@rCmI2@UC#qMr6n0(he|Ls@zpA<8-~T6u1IZ2)Ayd zCsnnA&m9BEbRb(J_p&piAB-mp5qI^I=})cV&~+zD^U^bKL=nc5-7j#DhZT?=#*^z2 z#{44K@b%EMfOq$JvXFOIRV(P-T{R1P_o%vAA?^aUoyEM(&VZ0=Iak^kX9-1F82t{s z*@{QXO}p|^*3*{098jH+zjee$8HrBX1Agn9u~7{X|K5! zv#SZUv_>D=u5}OUv3kI|sx(Ucj%BrnieBZh-YkS7;%KqjoGGo^4GT%@Dl|hUESLb;}tZ^u{tX<-fNr_3@D9ysO{m|M0lr+wBg25!nOZF84s~V?VyzA-UN?;j*8jQ zB@iA85cTHS8Df^8=_XE-@G#5RbTcq|3HZNslTEta0LvQX03Zw7NL^>gKgyOVH@WLh zH1XC(>QNVFOBx0YohVYCDJQjo$hEkw{Sj7vCBf6tOiD@|C+@+1jm&wbk{a2mnk>5Y zKmz*?7;5YY{!Tq0$;sb5YasQqI0YTfoycI z51YQ3;XOR8#%BLc#fcZjqFhnZBXWxPC{l*_`d&lDP5+_xbKrT4n$$wnwM`?1u@vLU zJ(hX1jS*skyh<(EZgvTM2l3v6@7S4hl6%`Jb3!#&Ynx#?+;iN{0b!UT3uR`5Nq;8Zvh?M)%{#yxlX`AF+{)7vJ${7 zS#b0;7=MDYYp1cU6@=f~eixKoK}30!U61BoblzYw@kg-x?;DUo{FBM#`V`4XLg&Q<(i_k?GtkKl=8bHyUpKR8Q7(DevM$a z)`(!s96imm%%Y6iy9a}Tbv0p!ab`{z%emy`=?(^2Q>V``V3!KCQRGZU7WXHTFgYxC zV-$LI8a1f9wrt{YEM@mQEN$M^MQe@Pnu+e_=C{pp=rpRxR+qq>u^6~!wiEISp0yiz z>VkoF>BORab-w05c?JA~c=7OA7pQ<$F?f8DRaiOgMPHuEl^jPeg{shK zwWzC?yad&jg0D=TqawQMpk57zo6T^1KG-Y@!zm2T8o4CNVQ@B>+R+dg{!s)|aQd-J zs4Alu=OqCeu_vu$^zyLF1Z3vYiKuSm1pu7s8p;vHNm>e^hjTF~y4vvc4H=sh7J{m; zP08rZ>=HgP%NwSQEnBVm_&LSg=te{+QqFJT}xoWp+R?$YGB z!ZnvCljEy?$XhUW}$EDb~KCWqYW^0c3E} zRflPjlH53w9BnuA-33}nq6a%*x5Jf({5_q*g&rE4wb$K4)rv~8A?GdKdBQHl<#1{T zxY_MxACl4{zYHl?2RtD`q_2g<;=U&&l_e^3J~t!Ys((S$V^b!r{(E_>-(CYf|56))={7)QkPZ7P42kH@&c}z7-|O( zTa0q!GB1<1wPgR*=9p%Dm}fDR!v^NA=_0ls^JTh%KUi(7V^dqPP#pp5fphazMyo!e zc74DzU>#X72+&K_YHXFO02Z6^xE2uxnR$^$gjaFAEsP$Al9ra)e~hbzxK6`K&dPd~ zk0fF&_zKap#eA^=xtEC>pBtFGIv-XHQpZBM`yx zG9QJRb^MaDu4JK8NL-`D6C9)_nhNJ5z8wo1i3J3YXD+N36&PY`C3jhdua?Nu)U#X0 zj1bwub>zRoG#j(RFqOVYlYi<$^{_bXa)-yX8zV)I2z;*|?(m z%B~LVHm~eD!7k0MD+aqY&#oQp(wy8M+Pc`@-WBH7U#8y%gI8G0ai1oW&1SWxeEJnO zJ5ta0cnD<%rn0&Xr+CvimxX;Vi|g(IK~KRgrI}9Iv$m&+6vO--yQ3KB@2YG|W3Ihm zhwQf}CsKT@@cZV0l`10y9&%_8+F93=T;$!?*muDTv@8z?LFo&gyChkE9_xdHN7CW zppFbIM>+c{71VGa7Nc>c9j2XV=-3v}7JKndDDWq-0Z+ zL%ZRqw7CJ6IpV@sa6BPVZmod<-52BH94l0ajN4(0ut{J?H6M46>=d~>^bdN;W7BqN zZ%3T%)?SH8J~okvECqHXnPY9pcVb0kIck@Jn;f-uP>{K8NsnCISiy~0X(rZ9NvX(I zpTO^(i96ZecAjP#DBUD`)%QjUcr++Qyf{!)h?6xU3(7`6}nwcG>TrMMk$ZY))LxJ*yqII4^sk5s5{@dFagQAs5X z#lI!4UA!4S-Y~PRP@7w=j7C~C#-KHthxpsPwqq%~N|J6=<0H$wjDP`h)KfsbR``nN zk&pOQ0(ZgC4Y+eSR@M7NWf9-U0cOL-#EU}}0!+#Wv_NV%twlZdWh zQ!+fc%)^uvBqIQpj0x6c!V?h0p+@qTxT z@{$MPK;AHMWrau_7CW1%4vp74-Uy1Odt+0wSxSxx@--XeMxkVbycjrOR?vus{2E98 ztBOQ%Yx{YF4BY)`ygAorR{m=Xa9P^hba9`Al%mDD}nB}a3V*@1fsCcLAulVUhW`%a94RCn;UkQi5c z+Z?5!6aVyUx_QfhDMBdc{nb?*6P;4cmFfe3BejET|-vq@GUT9l=c1U;{(- zieaj;des!wSlyVDSZytNGlN2UN%qSMnnR~*H=<1vkimyAVlm_V3#RZCge6&hY?#c{ z_6U<9+VhQZ{a>ZR6eq}58OyNPDkh;rh6*eGp|$d>0OV&}dvASbP;kcN<|Qxnxl{4= zH>&y_PiL}reg+(gsFe+s;GfE3k;eMj)LW~-H?(s$O<)@%nu z3L#U*l#Hbh5YE;(jAwnu;RI4U5X-96egT<}t4vKX?2mQNV(N<^)+s+ivMe`+!u}8^ zqwSd$LYzQU*2aN786Y|}o#-g#eRZO(9LW0E+T_qFM~5`q2r4!?iH0lsJd|3zU8eDB zpPnOD=0nI(nU!8~qDCo@RB}m)&*hg)Cey-ZYaPCW(PR-f1ts3Y@!nCnk(FkLsCv2) z!LLNCnU!9Tv53}9u-U0AUN&j1^U|B?`Vg*#m!Hud_g6YNRZ8!N>@O==yH~_R8ViNt zZg5aTQpvPbZ5ypQ=zMIh1eoq)SPymYX_7v#XRCP`-!4avSOyr2nlP8@A2*4yC^Mq&oqQWxn~ zqevQ+k#0GTq_tQ@5VF{k6hzoAlYO_GZ)k@Bloa){|JGMi?`E1hGlBx3CKVVhi(wEu zOv0ST76Myo(cQAUf0r>7LjZPwAn4HzI!-s&oO3#FIqO{4&E6W@OEQp_5<)jm;Zi!! z6(^_>5r{)pbYUfBTE~l%jf#EW90^_$gw?lBe9hNrje~&ly6PWTaV^}n)fO;RVb`+H zR2Sx%u3U{KLiIA$V6@Dz6@S&YPyGYZvgWoYqD}KsNXQNw%p7Ptj^7+tH)>w68Ml}| zj@!v%yIN1LM$^qY+?=lxWW7M#<;m%@>FK$k{B?^445$N(@c@B7E92)Y{4QN87a=Gk zhT4~9J4($M@}Y*-3^45TQ^`Kt9WIf5d$oYK5c=Ah?xJtbahYhcIYEl_ikX*%Q%hdu zZ<-~AVo1tjHAimvAzT2JtS>%d2gm!FIR8~LNpV#iqt65xW0O`q%Qk+yH*Gm^U;%Xp z@BP*I`dc{ryXVU>@eU+B8J}$-$^CV5DOXTl2 zSqbp|ntp)IGc!RK$9pvyL(wH%zDdA~>0x`ypew>Q2{M|YTY6qI3V<)-Q-o&o45p4% zQ|N1Ru;H+4-_^~}F=w|W7p%VbPo8u&!zK-f-Sy40(mXfHTz;9CPNGq~IOd^E9*J5) zgQRsdupSTD;ov-iR91b+!k_maiCRMAfGxFxW#UFn20c`~fsO`Mf1slQwgQ=cAZTXM zUzcrgkmv43mW*)%!Klp}S4X+D%m`^MKOE&!D%KNL(k;T$a62?h7*Q728n$;M(W4_l zPHS9mNiy?kP>{tM8yMLsr+>$Kn+CSD@``ETY~6~ZDtjF2@Qke&ZYbg@qnbDm7hY#hk z;f_f=%V|H|XnoOUAa2`kPnMY)4uD?O`r7)41`J8kYV6e*K(m`^CU!>*GBP0UK#6ta6bDC6adBmo5LQxGfqHy$*)G2xLi zv4{5u)TZ1d#>PR1B+Mv?T=57`XlQ@o)xLT;pm;fG*TfY)(c)s!ZRHiTk|EyXfGBVc zldIM4qArg^sVk@8(kq!LOq95nk-`bRFIp2C`Cs2Y8z3QnelI0HktmXCBA42Y0`EaNhuyq`Ny-M$+E>niTYRM-!uv=NkMDST{gyvi0h> zsMmWnDY{CUn-tGJC{Y9L?^r`~(28Cu8=@dBsq-Qa*E9-PG8iousR+c)<`hgT@^X%A z5_q3k<)qSA-Ga)MTSygclT!7I7D}YQMiQ6RIpv_9SG*8V37`+C)4%Z*z`~=+8P&Lt z)_%jWbhC?VedFj)X2}ZfmN6V)D}1M1MZ7|$igJp347M)#XFtcovCZo<9D;pAaa%yK zlAgLRw6Ql@gA{%&Yd(&C3=g#*fGX%2I-EDKJyw#>?sy>T8IImXO~cWqAT_8ZRyJe| zdP&%nFj2%Kc2&YGpuT`t3lG(i>#|t3Ykue6eun2mdp9hi@qUA7@W;Yt=&AdK5(p zZ$DSx<|ZC(?+PF5@bURITospj=U zB4D(rJrzlh?3WIGpP&k!n;qsTNrbzDDM*PsfRTj0oxv0Z!JWYrg~Rq>iVQrL_2R`A zLFz0^6e_t5&Jl5h4Ag319}BfIqz>M0V&T!DCEY1oiI02-T5Xi>ynBSyQtZ$FRocb@ z>?mGFhheF<1ovcb;`j*%b zB2(3|w?D&eyBJlVIvY|cMs-g^@`)IY0+R*|w$c?2edPphaAdpvJ`u%~(n4bcm130k zG%?qq8JLLi{mm(?=fe&B{nX7l!Up_{R&kF7uv6}G?p|VM$h)ju`t0}&gj^(zOfGCd z>#jqMY;I?Bsn(n#VsDo0U{KMXByCwD7)k|++x*O4TG9=WN5Uzhq-Ua#v6 zrwD#-6MNbjLCx}T#b8-3!%SZ_K@YIAx#*D^=E+$yHa7rOC!wZ#zVvaX~Vg7qAv1iPxJf6X17vHYVHGJB4!sxOA^z(9;xqAhL1e7 z+YCbO;^U!rNOSjU+wWn1#mASRYV|*Uq#S`U&ZC(z6WVj-!N48fn48P z`Bj9s)uSSA>Cye}Q%tc+gG{K=CxF$BtDsy|#_2 z>9rlGCT{TDJ1KLTJBLErRaJYjK6kG^)tn4Rp0h%P1=d=y<`S4GfpH#sRFlL8zo7w( zo|6Fg+4#W(zI|Ylb}j97(2vjA*Q53|d)~ACYIU>zSF0QKe{V)ET)imr8vpZcluTq#8$t0+nG!lzGMyvkE((0*WwMz*T?LW?u!^)E# z@iiAyJS34Gz>xV%&87wn4aLGg9jiWaKyNn3t0G1SIn=KW&@*yAG6*#;ND)Q|QM44; z(43f>Z*juU0*`|PH-N5|o;5L_z(auVAy3ZvkQ6&NF(M~+whQ_wXo*cgziYcA1S03j zl`DIVL`!dE8S1tc-BYzA(C`mK5;(n_RV7RHGmdH>Y*5`1vbbnLG9NQ>D+>Kl!M}qI zJg~TVEaXU|7UKblFVwhbilO?5b?+f<(?XO-$QddGvguRy5> zP8!~B7=k&u(y|^y%k4Bi5u{8S#bilRa4bZ;)34KUGELpPS5*bwd#FdcDz1q8L0v7j zcr(wre+lXa*(=9>RWAoakeGT+CQgMQ90GSaKrjH}B;>We9MBRpL%5zMsf_ngvR>or z=Ap-ExvB>X?dz&9_(Of61yrEIy$X5+7ycu-@E^fNixFJ=kKo#W1lN8eAc`eH-{UpA zJ}X64a~f$yh?MaVBNPlMCO3nDoRO==(y0Z|#?TtRuq~iTkcQL7#sq1`?Puu6Yl@oK zRO}jrB#OSo-(-As(`TwhPN!VB2NAT-eZBZ3=tTH`GI|;ZX@{mjEN+99WmyZC>zpzMfpCY z@f?X2>AMx`$Z}6lR0$;M5Nk;!Zl;l<(A-ECENg2&NZ!sZrBhv!(jzqiyQO>UQm0uf)Gp{K}06OUXqdN z=P4q(z)ze+MRjL-6U}@EIU8OP?%Yui3NVyT@5*$MV}YAJ)iazxDd9Mmj%$!|IT$M$ zx~#w8LM;>#{C8Xneyl|29U9IS^mrR0%J39qEp(`NYVc|8z|np4^vpvG)r6~xjun!S zl!_0LCSG@JKy787y921%C6k0&cAaO7q#)qaf~N9PIH`o*NXz-c7u? zk2BD{*&!)jZQza6M}WifYCKO4ZgU(@IOa-CMl`Eh2=ZWu$J#py$6tmX9*cX1+CeEb z^$=2yC(1`DwRWzMz1xwS`Se&ys*L1kS zK4rQ#gvoIRnL`rm=(CjP_g3>*JvHLo}*qyfmi8zDJqr5G%vG>y+%h`nwLh^2I_o+$ueP~tpYSI4Mo>-J@Li-krQKHSEWW73(xx;BZCB=JS=-Dq4=)U<|@>s#np$GTaA zq2Mu}(In$F0%8zZLWr_y-7f_0m?LpXf-9X23iTF5pB>K7(mhCe2GkxTJqEQ;NsmG9 zL(+pfOM>`jp;n;c;<8p?d7=@lQ&Tx4hd>3*p+iEhQ)>;YArCozdP}R~bGl=;#odA`&MoGD% zg)I8`Mf^+`k2eR0C&8gk05!mgfAaiuN5k|iosNQtS`UJFfQNAL1TGE)Wnw6_4B~Wp z0q58GVgUULT&^*&OKweC8+}Z}Hgco#8VW*vSdvRqAker&q=L8xGn3YcItcg2I=} zb-d%sa?3rgtcKWXfpn9$3#41MULf79X@Tli$08{Wq_i~rWnpRh_;TlcXEom60$D&? z@4mckoAu3GwOr!7VcS*CTQ)6p!%ocCItNu;ZtMGO#nnn9o7#Lcplg~9JR{_djuzqI z^|hQ#4+E8=L0n&p*%qqdJ=@n`)k4eb;l-WP^LhrFS3F*ClBJ%^w4MW6vw3}RV))%* zJ)g51ge@E7%YX^zjF1YU;fm0bg2Pq|(WV8CHoY*^$a`l7bSNIy9Et>~)pkS!H{qa? z58{kF7nw3iHpldN!+Q%Y5&f38Ea{*%&2TG&pCsoF%eNQxFf_1inx#=^9>=K%kZdBP z*{T)J=73DQ{A#%eoVvjz$Z>!Kg@`OIEuZ%4rLX{k`%(mVQAsb&$yK{jo!x$5?L

      wK=Y>^6NClErQ(i?AR*@kCKOvF8+KNife$siylJ$00=q$r5yZrO|+MJSH#$3Nj zxM@0)kW!_y++B$M3l7BB1oOU>gOS{P_+6j+(r-#aXlQG=mC!cZ-O_IjeOC8XvL-PMo_Ok22#sw&N=~IkWFx zb9%M9OHxOzZn+<_G;?Nu_WJ{0MFL<|bxTg1L|!wIR0Kg11PKrT!3C7MaxD2Es;iH+ zsw?{y%9OEIWr7c?HYFcaWtp+g*e01ch4uB$IAg|kw$(ArkqOGi;yGR%n=i(p9(N(^ z0yvPfPJ{}XDT~WFi&bU%l*Q%Qy2_M=dJ211Ws(g-nLcIY0ru`b+Snprb4nBfv@S`V zC`maRSX8DGB`MF=l|-rPNuoq$f+$s4hA1HfBQw^xocFgX)5jW@XX`3s4fT|-QbEA-m%GM&wK*r_UtDwCAUIdVgT zg`mE!HW#k0I={Y5<0)TOgEKR~xTy^ zIp?veOy{w4L&P-NJ=CBovp{!4yO%yEf(VMyC~dPY}*5#yS2;$&QYsM9u*ayF$= znaU1HdA6=(hpL`rhp0@jLsh1;gMEC54%u8dy6Y==HO?|h6;aia$}Wnhvcxd^L`w7~ zXtrb>cy(H4$n9ps%?I%D(_vXC-3}WbRdiBOU~@YpN2p?(_sKBTAC`WH zoOmAdb&pEK^njPKe+T<%9+A11F{T%>U`5GboL|mPfN@xZg>v=5bd^rv@G#`dww69& z)7+n@HDGHS;XnyX4-z#iMaX?%F7+!#^hzq-_fLyl+&(znn?yBg;$pMmM!tx-QLR&< zo4IAFq8kpa{M5D*)<3e+(YqJeoGp1{(^d*=UtX~}YAmeW!e|)&VX)O}8!G74;fP)$ z)~L0uvopp~VnR=Qw=|BDA!cwCn+lSv3h7`BY>oW3$N~Uc+;MGnYe)g|wdK|j)s$O9 zTqw4Ns8HV;0N^wC>DyrnAbTBZ=f)fX0(e_2iCxE zR&>W0=d{C!Em;{ib_(M@XoWp6pgfqy9TQfIq)?wNB8)>X$yyaEvlSKclWp9tu+GMX z`fORV*Bn{m-N7R=_C@<^IBS@18STMqH=rU=?#G zUDdOS;9wZudS@ds;06Y zqe8hJtHSJhEGxsg-LaA@or2pPxS5v~;O%U*s^G4oX7SL%0BVSWx$!)!7Gy1v4LGfb7lJe}U_jj;qxvC$$;SY6lLK|ix8#^@`Z zNb6)8D9{33)my=f@KfMP4BSAn%_oiu25hn`MY1BfyBqgD+ZO)+>?7N$&@m5L$H87euvj*YV3}dh+0qj$rLaY5CQ4V)n<$+{ zQB*ZClF)99Q88T~-i)7^BqajX9*I>r`8b`Ol(@u_TEsh*Mcms71x`X5L;1cn%=Xs+n3t9DVqK%i8k58JsEh9C=k6aB^$`>9N zB@iS9&sdUVzj#3qSH|m+u&>8xF4DV|p&)c4_3`Ebp|kgINAK5Kj@%Wl>8^RezMP!* zDCsa`CKCy3{Q<;^W_PcmV|Qh&Yxjh_e)Br^XArE#q*P^p22oRAvVUAxvVT%4*gr1S z*&o(hvg}0SGVvKiHpgnBh@`Q5fGrV&l7*GnZ^qijgl5-+S5hL5YU<0mitEa`N=n6C#ig0KBD=Fa!MSJyE*34jTe%zN3x0gNt!+eMw&}_> z1#4l3m$Vgfj1GPZA5O=57ICGOIs!TJV)_5X8zcmTrY~D3-c|PnLQG&z3rFdf*CB-L}ji*iubO z)uqlLYU<0S9@mviJt-ASJucOkI(c-EVg`qNJpE-DD|Q4$PKmHw(gnSGeWo1{0_@?q zD(x+h-$gXbRs}-Ziq{DZb~6) z>MPt7)m6ADE|uIAm1el9a%rW4BghE1hlARxQ;go=^-eJ)nT>b4)ljeeN^h2s^Q_(;Nk5VNZ3I*YO1xYw^4JAq?(0kWdZI8WAiTBv1B8;tiS<7kI6Zz}|hn-I6 z`DrpycgddXYB=Y_DpkB!Vud=!$+HT4E8BcDjzK%)d{o2vb5`h-cjT;6WhGiM(`uFx z!rmlLkTkQ*xSB;%!%sMfek?~v$L<<+#>5RhQ?eqxE^yGtBimLyW~#=!PSp?O(jvLZ z1R+xa@NWHlK0RC@>3D4uqMEz7ux4bs4170zCF**Qnq1p9z}lqKBIPDS4AIjxI5Jaf zkA+AQZ(}QKId5q+WS305c1Hkt^!=QM8ySAnPN((PK!A*_v-xx~n9kt!jJYE)JCvcc z%~n+ygGP1Io7sU0MPzcfxeiV%JRr1tHpfFSld@~xYO@2{@!#k=)$D9K?jsNWa8KM) z4t0m4L$BD>)P;+Rd#OBzO{Mis*?P9R(gD4??SsIBYXnEL(U5xKX0_oWfw^Q7Z31Ch z(y0gDbd5x4`LZyui?b<)?F`!yaPyI4uk4!U>4 zhL4jLl845oSG#A1ik9J;?5?hFYv9p4lo(xEkFZ9R!7Jug@5dP&FK2NYh%h!X^{e!V zEwsFKbxT>rHtR1^Q0fYOgJKsoy3IBC-Hxtv>A)pSmr)dYS!Rhz8OV~7WPW=aH1~T*7Z;7OzJ8h}^ql}b26Ad8aSo>h-5%P+KP zl5fpiu#xMGxU1GIL4gNISn$<)c$SC`oQ?2&GUaIApBp8CV@hdb#5SHs7%WCpyk(mf z9u7yuYX?hH2(K3_HgELwu)z;XyzSziP!1eeCC5^dU~L}Grl%pWz!A^x+(9VHgVUyY zmOaS)dXC^MbR;MCltIFU& z%Be%#&GR8aF^(E==(D29l-_va-dy9GOh1zz8y*<<A>^MpxPl#|B9 zd9doB!JzKcDF(5bCdG26PK)`OFl}EP3e#eJr$z&Vqv*8?@Fv|;^M2lU-<_+Bus1|H zW)wm-816sm?c)D}G1kMo2gM`8y$#&%d5fTa*#5Cq9d%mmvFP3yM%8&fQjCa~fG`o8 z7U!lu!k|rkRjf{ZRIE*XR9ufdJN4fCeE3t1x56-l_vK15Iv+Tp$U7|B;W)o~HweZ0 z-H;2xk2|{%Vs<6*r`XkKfi$d@@#z(t&aT3>FjuRamgCPkrmAHkCK|7ZhY$NZpG{}3 z&=anZ&{k5HuTiYI4`&s(*pm=Y`1lD0T4~Tjx7#c88W0@i2mK=?jXClT;WclY879lS zfj7w|&pg&?OKjZa!S7FoQ~L^&p;F?)m*9~>fEIBA&Ar6b9dgl6KB14E+CVG=#<3W| zYG7t6=P)1#5Vwp{eat*L2=K$7neowH4)mvJb~52Qk^?ugroIo_0S)&Go3hbulG7Xc zh+vH%!-^SZ&OvTqiJKQ^A#NdpWmXs51+&lg%}#urBoB(f2IEX=TLId}yr%pS3iGJU zovK#I&IWyBojB;vNB)u!p9L2u!TT$W(VMYbowN{5{$LB|j`f!H=%gj9PRExMEUf+* zr|6`~aE1b1-3YqfgZ}JtP);aWU~FbF3w{Ug`wEwO97-VatzBDl&br^62CAb56Qq3O zLlwwX;Acmz7P|_xwd@dWuF$O{#6ay9d=0W&Nj2Fm+c~kj9Gea9KPvv}hd4m%t0SHh&e z06%lT3kk`P+86JdR)Dyl$J>u#ajq05;RG20yv4mU^X6i2gv^N1o2!fp46$N;M5yb!mSGTIJHsHMSZ5GlOtY^| zzb{v&-%dy3kmFLH@sq8Qc;k2t60|G1;#@e*^b|W-s(b|Do%336wy29(oQ#BsgL2^T zsq~bpU3z2%8b=)W;pxe^I7T+BLk5Mc4LZ`IPW!9GO>WLx)nPmmSMm;4HlFx@v@jQ(A35!fd3xXpz}~1%^ySENFAfuq|-id9qk6%K5@Or&;rGQ#ek_ zSM?eduWGbUt@f5nd5h!fa@?n^`xKP9tNb5-Py-Ng)rrHE`4U;^`k*{Rdc__d6fcAecf&i`yUaWOllWS z&tv9AXa09cJt>?dOez9+xh6#!=X{W7fgplHYEMjxZCGlO6HI99Jnvsz1peOl>1)Xh$YV~UYLedQGS&8<7IA|f+s#sis&dzD<0bm#_vT~K31xzgt$3+bDc`# zHR`8+)EL36RR`FpqXD$6&pTj}Q}|$>#5xjoV53o0?s6#)(wPY19kEwC4S3PE0{bmg zrVOP_{<$)HF7}TEq=>m=d#kDwdA8F!j3Pr>8Dpq7A3%uc-H`%C>t*fB+T5T)r zA6E@ZLEjsKQsU09gC6~F)dDl*b{fXZY`F2H2N*fa zae0nYQkYsCDyKyCRZfY^HBO1jvz!t_BaTub66A$h!W1jxeYb*pViYyA(h|pdu@SV7 zRQ*^pI3h-JesuMY7#!uu%L7ol1%3F4 z>>NlnRt{BM7PuPalsf|%gp7VBOK zVJj+1eQ=Ds<2R{N+?Afwro1x6g9llCU1_NrcF~(mjIeT56bYDG98wge^`$6c=cp2~lgvuUHNatp8uk7gX3TkeK%V%TJBt%X&y1(r4@O@}5mY4YA!3y!fK#;>Sm zB#E_HsrQq(G1IlqELN=fbWyu1hV93xBPBX{tMbCTBzB@?mgBkAsbo6KE$ekTSz3s8*zTN)G%E; zm+oTrr+AWe%L+|I4Zr{v_#_qiZ1T*#YpQ^I(S9cCE4YigvEh`Bms?H4W#HIqD(a}* zTjwO;nnU8bqAi%j18WA(8sYK+)hdVFX}%&^Rd{$3>cVP-Yskw9uG_i%cpdd3vKtEP z5T6dh)>W~k*M0X$bBaW0*ai}WNVn-iU25*|av)U%DnP0H5&)VIRDkNS2r40iK*=iMP&R%tJ-R-2Zt8J)xLC~Q^gyXQC;3#`VLO5)_rN{?kT&lzERztG z;4rJ|XQ~-d;)5DwHP;=fy;ZTI9aULlm&yG|O)u$1((E|ZelBKB!#hYl3XkF6J|7^f z#b`D#K?AJ4pt%^D3UOqr&qQ@AbDHa-x1w&KZJX-rHa(mh`*?p$;&hRU;LvV%P9a%` zxVIJmFP#m@!1<(vX!Vf*faenp9jMngdED)*v}~6M#qMQVUR?RF&1&+e5Jq`$AO4qi zF7+wrS$a$_LwLNcc-J_PDSmV`Sx%Q;yNg}OmZYqcsfzo!a;BPF{_v)z{g2#={g#f= zgR7%ZF{GC&f^(K-7keDJ*5I8=Ev{kXQAtC$g50&Mtgq?H=o`Ivk0S>ZBHb?=JW!8e zg2eS-Zpci0Q0bJ~3fz>LRABT+jmQnon3RUA>&)dY`z+Fk*XGeG&%fX8p! zj`ZhhmoY{q59iLwf;{#rZ*pszdBz~A*+TL-LD$H?%&IYQfI8t#gkVHV{5;t)&~*8i zGE*-OQQ3GUtQV&~i*kB3wFyzve3@o56Ym@)aU=k1JUdq7GoUNBx%sl~iusDVqt2JF z#^x)kCg*F6JjZ4X>3n&gL3Ru@UsPAxJ`RAHzLR3jY8z=xc{=-Z{E0C#=iq4Y-zQ6a!@1AGUCDj}Eu{`Le@dkIgZ112i_thdC`Yxpea8k|!Uc7V;XlwrL5okwbySPZvcq7h=Nsjlz|Vw$^s z6XdVOGhA+mjp2<5%{VQLy)^1wircvk7X8_R5)xV5vF{+_kHEeuieT=x!`F|P5{T(_ z96&u{N}zfy@`%YW$Rj2P5JyY~pdT@fv9AX;xQDm+MyGV!hUevcT-|_^x4#0Zp@|YZ zWsi&LRJ28Dcw%LoxE5HGImT+-)L+9giX+e3<+9D1Wz&3fFrr&yhv@89*CV%fuihnV zoa`?869nz~ILDeBfo$4r^-aCHeseFy0J8_S6r4cj2)=K)`u5m6Ww=ejDv?1i&lwba z_0*uiE3^Jt>%H>>`J)%BlVu!C;Y!Cb@Ash)_R5sNT4Q3z)nRr7UWXeI$i?6cEdFsu ztk&?_pBs^4t1+p4$739hVPIlcjvhx?=gEVO!9);El6&rvsYlND0VTqC<=2pH=>$4_ zX_CH-#Id91q*L*w+4!%B&rPA9h}B=oG=z6pQo{4W_(wK5)opTJq#j*Nb1y|oQYKko>f|N;C049#vin)BTX+aMI&y5`bn>UjM-@LNeC!a@zJ1Cokc=us*eWMfJZ1l z0*BCkBm`G~1ca*pJn5OIq$q@=#yKING8AOE0L4enA#@5#s0KU=1sM*7f*e9pkU``q z=+6`N6ilIV6ckWm^i(N26ehL!%&rP$-x}D@F)~yI1~zU2uVQ(p;IvOo>E#9cda;kVzbwa z6HVa>LqSBTJ5uX6l%E_-r(--B9Hjz*g^PSXGG~YaYfone7Fi)uwNZd${(0sQ6-Fjt zI4=i?a!zf>T2*-oK!ajz!6|@;5SqK$I+2L%fXcl}CmOmbxtHUR+$$gi_i~5~_cEpt z*+s;tMTUt0;q_pVV zW}cfojyyL7h%7gG2$_dOuz_Th0n96~{=nW|^HlZ|1&R$>K&Yfspc?Q<(s3M;bOJ(< zjzj1R(MQ32u;GJacQV!tD&m0tgjN1fSJ@gry~QWkab-Ybz`9IAwPlZTW-%A$*v;pr zxN;NT>eix-4>L>I>m`oII;$f&k1+AW#%-VDHKV3H3iw?na|EK4l~Kk|tzW8Cf*vp% zUWK4xc72wg79^%uPinK3&^u{1JiZp2xv_uIN|Et{^?aPlo_ECT!PuE9UA(3yg)|ts zy{kHnQdN;c3KZX?7%?=RMqp{L(bnV5EHBQc6P#O->(vR?I94%r3c!(T z=LR92{ZpiC;Bkq<(_52GQ-MuS#;I3MfNP7Ldr1gJE_PLlC{>lku0XL>Bp{T|X_bStP73}S%(RNf$V>Ezv(@2rSAaxusX{ON! z2)Ds9KC{st{>cWI5GdXu4(Vjwr~aRjlrHz)pFr4wzEg2o*Ez%;Fm}%do^J zj)ttJKX1yKEA}Kc2s3ncSmI#h`J(Q~==-}C{x+sy*MK+LUKc?;K5qnO6v0&Irbb$T zMP2pM>=QQ20Ry&O~NojaPoy4I!eUO)?Ca_xh|yDJqc6dN7^& zIBLEh>D_Ctr!lIF{WKAy$Njm#BAK26lm>lP)VUh{~pLKXz*`op`W{+Ys&ggAo zI6Fgnws(0j%0El%cOO092g!_W8)Ry5*@zo*fg%>AQ@6hFhNQNqLGq= zD652nD5PW{8B$Wfc4HLhwuyO{8e*wkYH?{6T+o0X)FOcfwxVM;U zArqDi=`mV%Z3iawaXC3!9D_8;@m|7$H5sP}s)!IJiA6G#z#^GRf(T|3AUZPv%m$NW z8AMPah0pRw3Rce_47QFxbWoi?QrLR_u&|Op7>@~QdvSfcN3*Ath#p>_x? zgM&n`i_a*hw-w2fS0#ulJ~c45DGCtD(h@|lv;+|>EkN`guN;p@XAAqzJ3a7NvUdx& zIlu`A2+@jzW89*fpIqi1++-Q8w_%sYnac1}=8d^+n-D|z;6489cv(P9-4o^gQv*Nb$T%DM@#}EfL`~UHarj8H8-$QI@wg>iXm_*E0|VG`rUhpj6jH!Rgm~x1-cq z{aN>H`q6S&Ub;8BSXRZ@#%)vKL=U^rNc=iT-mo{DYqsa<9Ja(bw!LeM)cUKs{n@}B z4eU0fXT)L)ouqL$)14ie@S&c*qn{KGn=e-sS3J z&|p$RJ49oyX7Qe>wSR;CN3}D=|52Zk*>G2@KBb-QhZMTUSS>ESsTBp;YDl4?4AS zDvccX0gNfk5=2$KHLxa3QuP*Cq_IJQ2-RDF=&H8|GcoS;in3!vH4uXBg}yF)uO^Vm zD@}UchRV>`&C*+;a(C!k_Gzj!@edknZsscUUAou(3(9H)R$Xewd&d0y$0@Es~Jd79Ew zOo$F1*Z64+z7#G`Qvt_$3u9z3S(*yCMqD~gQ#=ZviazNyP4VPtO3?%{U#W&((3HdF zXsW;oItZ8yO%=FCTpCR|9*w2~Mp+XaCPPyk$YFbE-cJQwhNc=Er-OviX{y0B;!(1F2ZX)53vap^Qo@#r*VFe*(`m^@9#?n!|r znx=4hnhH3D4h$wsQvuhAOQ&gyN2e)+QE8gOKNQzIbDtyqqA|| zL75)-aei>HXl-{>_dhe2LWCG^I9rlOA=YqjVd1|7X|z}X{U^=yW@ z&Fw-R6QghFJ0l%ja$~nw&6fvo>DgKt7g{_Sl@Z*2mDf7$#qf?fsN3x@wN*V${C9nf z#a%CU^m*Jn>>rYQ7Q}}kq!_ge61rL^s(R9KBHw*;Q z9gMJ(Olz`=>TpabQfmyCDqvN(MqEqyNx=5(9n$z*+WO|A$Uho8WNxr+ChdjfVbkTQagLY={^7i4{Z{cEo zS~-LXVcy5QibJ?H(v{rIPn}~D!hU%+gl}XCwLx(Y>Zt%=_fM(muvM;BF9Mk>?K%}7 z=OPt)2WyheySojIK#)O_OenMGTPCb?B0jA!;=RCYc`?YNv_i``Hd~0rHl4H>uYDI! znZXf6yq;J~;^tXP9K*EvDeOx8B{XE=j?P!s+}Tg9bR|8zoGtQ?6EPx~X@mNOl+uYR znNAb0L<&`BXae&3Cy*S;FoP0B!=%xx4#l-mT779%ob?{?wYpi4(s;#J#p`d$U&AAO zI@L{wnl93VmFxt!>rTrj_^zo#E=OpVqWssGJ-`Luugruvx*q$ztDz-DYh-P14$eNP4A3ZDj~YJ5ArlT$niGH*d#378?S z3dqT!0O`b40h_TY#Fdy7;wnIrxDqHsTx>JOw$gB0c!vT67+{9DDj+9^0;CgH1#HHq z5LaSSh^qie;!2C^$y*0H=*?vV2-#Npdg0?$`DrrY{sS$S76eJD}fZ^3ZM*eW7B3WaV21exGEqg zhXSM%R|RawrVv+RQi!VnN#aT%ow$+R!07K6mo5(8<-m%ycd|#2%G537oZ0%2kcN0<$xxk}?P zEWPwU1S`@T!$ZV;50x7^J3g`gv^+e7y$EI_yg_SF+5>H9COMVuKzvkJv-H&PJNEw6 z2i#k_bXI&9gzB$2Ma=|bc-jmknz_V-`u6@&>GKM1*dlX{C$*65gi6*hgg5wFRH76n zHr|sgXP4y#vO2rlVLMc&To~G^92l;5(_l67MYdXe)Xxw!R45nBnZ*4mk=Y=RD_ngV zmWO?KwcMd7d-;jR;{7e`rWdszJ6u||Z!4%{SrHff@OWo$G+CBwrdmxo3Knxa&DNrh zJ1XYQWim#@xm!gg1m=4uNec@;)*5rPkQp9MfE94?!<!W4JGI7Q>}b0} z&E`hCYd4y$nQt`2iRu!t8dwa-WR?b`N7@$7N1gMT?46qqnmS!=g zEX@+29L-`Noo3vXG@}ckig6xWq_#7qvSJPc_+U!^b=P(vVCdQ@3wzliqrQJzusmGz zsXBwjJAo(V;^TC75=6cC8qe&PjQ7H9`#C4E!SBd3=Dp+|58MGX5jIR6vl1(KAF_#V z85#X`IGnp@ql?AmnZNy1<9`U2U2}ec9*EHWtU1`saHtXOF-+dsmFIjK4LGd~T>)v- ztiDWr>nR{xSQ?PJOm)C!Y;u_@nB+3mfW$IYfb?Z*0TY{d9nCnPI?5ENhGrbF8C#BK z38oy)QlJdY5}-PoCD-QaXvP86QKmpOG~IjTDVjD3{Z@0I9_V-NbLaKjWlnYb%#3XB2%|nb+Q{f zO%vrbK?i#fOfF=O-yBi7)H?{3?g!Ef0naR5wAt~32^}^4kcByV2`aTM=U; z)C>8js#GH8up6hOVvU(}R6b_V9`oF?+b}mN#{Em!ZRph*#4m?{*!29y*bisfC}^;C zL_Ndl766@@j>=hh?0i=?7RC5o(*WoS2^+XB12qy!O+$rJ_wfuQ+s8F1xsR(*LJLx% z#6GS;>HBzMn078Bx0z{X>Gav5?nY7_yWzYH=>WCrj2WD{8V`U*0r7nIu1uH4!cc3l zCi_BGc%&<76|`pKTpw@NV12djX3?(KUGfH$A80CCS?0SN6$P8C@=2W?9D-Z=W558+JDcjeH?6vcCqEf$JN3O~sxo`|V~(_M443+iCwamlvW# ze<6&&BgWcpU61jhH=dd8s3|11b<{Pe@8W#h;$tNg`@upOmMxyB)lvIvCAyiR*Am5i2#e z5*<`k6tkeIW|sleRK+Z48;n9()UkuZh{K-Apvl47oF)oH#0&PVNk78;lCMHEasGRWP}vG%%gq-df7e+w5%Cp%b>v zTe6x34HZXo2z2Bgj-@`c*>)mpEFA%ladr_r}Yk{&GJ5sXv$=jCSclwAJmT+LxRkIta%l>qp&n{%%Jz zgsy5$?O>=Tw$wztBH@HFIXfdw9FrUCY(ZU3J8fT|EEn{!GX)-Ft_LA{rFxo@Qxq%T z^gyj<3r$pTdibVPCU`I|)0!>JR5O-Q@Y0a(^$%L)3JE%DI$kugKB^=+c9%wDaH-Q^ z3(aU+Oan}={lQ{%Zn6&?E+_b%QbAC>_(qR~Eumo;Xl=~s;cR-EgdEic7=~BM`#Y;Ldlx zPNedtCmoMMf`RSv-}J>MXn(=KtOt(FG>KZ zyZ-)>5_;e^0MelN>^dFhZQwF1v7MYwC({KuT<7QZ78!I1Td%)b--xxwB#->Gp#5 z*?3~gz^XcY9yV3Eb8wm#pNDOQQ|R#-d_s}W!EsGK1E;F;nGg$|mQhegB*p;C#+`vv zX_g;UZ}$0uo-ffKaU;dEMtV@rB2V&|6b(aFjO1}xiT=kKyJhj$+Mv=waWi|XQ4 zH}gP@6LX52Cskm(qI&%!f@3@sLwim@9L^MVkJ&IWh5I4r&9{=eU3pN#+j1_fCW#r5 zw=Po>LlHL_4sq--m->>scy)msUDPyoi)>9>YIOXxP3OYGQ0?`{!F0SlMKUtQ|JC9? zL$}NE%}1wacmz<#$5f%N*Y|#@6p3Bm13D}o^l`*oSLJNZG^n(7?RlzJ=*6{X48-`j zmoi@R{M5qMyQmwlcTrb8wyv_$xkB1)tJiXM6oXDx+XhTrx6R`7I$HxiP2CQCYKbE# zewofN{p^~QzwXD;crd+~PUxZQ62_CsNsUuYKiF^fVhbJZb~Sd{9I&d1)BvYyLl#ie zMQVWCVHMg)4x>;O<7*oD*AyN4Gf?+su2pxv#VPbn^ zl_HhpYV5`cAs)%T8+S>XZR5=(nC8;$>3VRc@7H!WnTF+qNMYA)XVw)65Y0Hjb9zY7 zV2RbBqw)HXf&C@kkj>g6hOqjX85A_v)is~)AZTTQyyF9NOtjr>kp{fG2|uJUTOrAJeby_NbeQ_~fUTIb%;koWcZq1~)Uo0u)zpmiGQHRhD)ZtA*?UmSdoTzO^qyrCpP2U9!( zy-K2B>Rn_w9P}3mavZ~S?#Ii>=PS>Z=6^Ra=eO zu~-nG8}+6QFv1ODYkF!@ZOo<>*|yS~ehGtaA=n4`xNXwsMuSrpT2)T!pi?~}52>jq zb zIY@3^%l1`7P-G$Xnc#LQfB&UMW8sFSEum^aHkWuB*P0xps^ND1Zk(Vm-Kthb32T$p zF6xkG6qZ@Cu|~U;-Ti9yl`RZ4SO61+g&6?1OiC&v!QnR~hpl^r!8RUctcoRgu$iSB zSx^^r=Xk9uLXUWOLzU)`uhb1i7uh0LU@#3ChugK^_%R|U`-Dc9y1;Bh*WZQ4CnCCA z&dTPWF86OhXU|!C2ay3T%qtSSrBmzBRu7mjCvf(k1WnNh|B6S&;m!ApBgow^P`OQF z^=85IqielAw&n0fzU6D5%#^;a!Rx+7wk0&mWDjH-WsALPC48-RKPzft70qf`x!sSL zfaz$Dd+Cj>2J=oHT$S9l;Hk1x1FFgHTJW}*h4jv26^c|1DAykHpsE0W%uNcQD*TbdTR%6k(W}keNcj4>QQnR z*zDX$*v#D6sZ0!JwR4k&&&*97Jf~kCR-c*3M00Xl*e!3ViL{=y>$Z%>6X431$Z4!xpw!%u* zu4}`(uo;uaqtj7MIwW=+ed26ti_yt3g0}HI%F%L+>%a(kcO9$pz^DGWC(g`L(!F0e z$1hLe2cy>)7r~v!3@xemdAk>d2)5CP>nftQv60MJUjut0 zU}b;-3vfvSP+0&-Vw~KDXEo_@g%ysTD-{cL_j+ST1+JsZYX-CP5 z8p)dV;*6fAd+6bK3UM_%rdyE{{}wMJ7kPVKC`~q@5}DbP^nhW1PJK*IYbH($4`E$N z2;!el{e^uaz8jx`e`F_?kiycSUW%`;qHe&ARnkqe0bJXJ8>_6+g4l@bv5T#^0i18f z_2BAuyi>CsUv%;t@;W>6&M{KN&km;J(O`~iM$IYsyJw?Rrf$ZmRgD;FzlqQ;k+}$y z>=Q{F*hQf=L%siCsN6W2jbAwB?ljG6RF*qa;j33CegMGW77!ZI@# zi8ni1R#3W9jI>bn(m>AJpDOK}9jAIHS;_<&<}hP8x;0%fnZR21CBC%bo?RlM(Gxi2 zq4&p#4)ck}TmPoNZlR4Q>P!8Ass>8jc$kjM`N?IQp2XhUpdckWz$V-?xWNaJ%7W3_ zO!?WIWk=YYf0(7*wACNYF5Pn((mAwsfE*1>W7-0Jf3iEtwtJH(S#T&nr=6IBx|qym zktg+#Nx}vKO#|oxxwx~t22=)1Zw-hdrnCBBh|G*)=l$Vu_Z@~8YP|a!`C4PP+R6bj z?^_lwIqxb^W83DtzYUt&jUbyFkASrv4B!1NO{C#^X1=QxfN+?ueqY6wuu9`{f=A#| zO5#M4l6x%1Awd{k3D5>f8ne80ZmU+)J-lF;9x5Ln*4(9` zbB&fKhT!@pv{}PKNB;0y!QA?A!V2^t^0hp~qP$KBz2UN?Zjjn(mm3w5B7_ z7VSsSY9|CE`6eYNg%Zh1tSD*e$>HH*(7~Mtl2#$oF~mB=!)*XfHZ6jk5n4?Owrnd~ z-`Rcrdw`kF9@`EmQkeDhtT*XT5()kU{Ey^}YymtOL3N%o^#Rsrgo+-foa+oG+KI0! zO3q#WvvPu~inJR1MCPFR$;*FcCoK0_J4rnm&nRwBpFukVl$nWCGbBM6r}R7RU*O2R zI~pbY1M86d>U9Y1MD5EoLMCMiOQ$7F<%$Xua)$593?yZGQ&el1Yl;!wqtmFKl&MV( ze?(JkYw@*AnOf`gRlb%f(`&u1sYPXKt+Se1ri|A@1ajCa`Z>Q6!QmALJ?CdjRy%FsPsg|zB<$4NSo`F9;+@ChS3lceXxnxsotHW? zIQeijon0^9KAli%v2@p}h-|m5jq&tow2-^jHZrVN^sN%HtIhM4pZ1|LQG%(?)@lEw z?DgmKa%R$+C8yaMFq~xKSqYHX{A!AlO{}WZ&DUYOQK-X3#?u3^U^yObq_WL_#;dtW zcP;c!I`mH&Xq}S$~(%Bf>jUjU-KwTH!yU}U6U0@S2n~#6*?uxrl z*pt*r^Bb0!v{0Z;T2z=$wJ#8p7793XgaSTkzCfL{G0sNOt|lnUtxS7~GoA&eci8UF^N#yYpR?<5&70Hd|;n`%ikwxBVS^b(2k61vaE#@&9C}_!Ry5 zBn_!ZZ7xAC-CIbkO8g{L03IVRT1&n>9`cqse`AaOgyWJbL7zf`#@b6PEMs2D^oDG- z^iq6ImqRi`3L+D5jhrbe?#}Rl` zpfJ{%NqN*{7J8}!Mh2IJ!76BBa)~evrER6kCZaJ@Qkw8ZRHCp`Qktp98LFzob%m&e zu~b!}@zjogEF5?5XoZHeSy-gtlcXTbTTI-5X4!shnB+*I9Os2;pQqlS6v#HVClh4QQt$J?2tvJ};+HE)AEO2(` z_ye56=1n`d&^PQCbFn(e;iz5hScg>)7~HybyBTs zKgww7Pk}RC7iN&tF?>0u4^c;D+r(O`BsEA}X(|z`5>1lErI~s{l7>1$l2#>Ll7k1M}B7=SBLi5!(OBKs0azR^p+qo{FUgI~q}R;lQ3#u9 za$v(=T8%s1OhJyss=({<*8}&)eD9i!%MUK4CbeV!G?F1_R@kd-3S>=)#Cl-XdecGG zhRVI62ZDR65^aX#(sYJHiJ0N4gmZ6I!e=;?s56Yyoqgqk)LRmQ{iMmP{U( zrbDSpG!n$6nRyiLpEK=m;8QvFk*1-VtcrVTX-IIq5iD{-h%iMQc%4o}dEbe$mAu zLsdOD3??VdhrQ#i6vTtzalXmWuE0EfEvhIHEO?ipj|5H>sAd~VZ4E3Lql3mRfDCjp zx84%s$B?2{$7N=O*+E^Sr;DqYR05JK*i<}@5VV*dP~Z(Zz3StEGMph|s$yFz&NBuj zmwTP_sW>!yEc);hEJ@$v2_3r1-iwazCtVfg%+GJihi2XbiWolvNzy`A|I}U92$fm; zqFk$A)Bfq0wa@YMa*m(MYw-J*Wa!@ z?7VzwM4=NO)u*>l@Fv@Mbii#6Xd3@>ykWsB(RVf zZtvx84m5__><#mPF4V?iL^i{h7g?Mf^j)VtXty2cUBdlnmZYCM9!Fbo2A2Q{m3%%T* z2OFC4VAPD-jt7wKfHB=Us*XI5d)_f$4~1imaVXY7J?Vwq1@&OTlGFg^?I#Uj-mFj$ z7S5*iVB!2$3wAKY4WRy{*M_g2aNugr?LjoKj)0WhB|zlF2N2I2&Fy(eh0O#ur9mF2 zT(`=EoxbWFTWA7Op$2ccrgMIb&Fy(ejT*i|52G~5eSEdl;J{VyIB#%Bg&G{BrgOgk z&Fy(ejT*i|4Lpe6HK16+u0Y=E01BB%vE+KjBdsU9D~J6rV+ zaCj59c^rU4CV>`k<1_p0(5$|Hw7<0&N5AY@Lr3r9tbc|G9bBdx&$%qzmSD5##};5o zkbBtJB)aX}knD#XEscPb z@&6Q0x-|gO9SoaxuA_XX#x6kM`>_8T@dcLHt105uYwfEVPvkAFO>S5F9{(4y7+vbsVXk@0l zZD7OercG1Z&Ai^J?l|4tb5J^T_GN|~xKGvBX2a;_vlZ*;YckK~DrD2FW-Hhc;O$5R zz@T;?2Bqmo+~#knAQMOCDtHpLZo%~^ew(GfOSXcN_f79oU2d#g4O~WEjK=*-(5OTX zH0S}-NnNMvvx%s!c&lM;VWlKm6@7ov+3fWO)3ZyHZM;8z_4=T*OaI?}>c78w8=d#J zdk(ONhrf~X&YbkUwJIF`DGVEj|1CQF@4kf*;MF_&v}68$ztMZ&+u!Z&|Jm9g(1*va ztpnVL`G{w&(2I@T{YCeQ%~aLv4=7|UG}*(0qoIEXuiia2)=bl!MgAms+OCc~m_IW3 zy}`qE0>j?GFhSNgy%FRg#Abg9`(^_TQ3>|iU0AQZRj4whJciY-vq1#CZp?n-K0Rs( zpEur4gd0bbp<%ja*79`Oh2;pM4vh1{bB!lF8-z8KyXSl>A~GoJ(-lzFWPbvZ5icG{+zJR1FO%`XZONE)r}EgMyt0wGHYn> zZ$_{|c5L-}OWff`a-sehSxW7OLwj-arsF53l4l^Xn;qg@%#xKe-_mvj5!3DU4*TPI z*;^pX7O}$B+RrY5szV^Mn;+^B=E-*GB`v6xJLCl@R)IwW>=^N?<9g==)m71W@?RBC zwV7WPQ+7fECqTOa(N?Bd#r1yBm{s(4msWJ7%UlJsThp|)!0+O)Zc|7^2I~{Pa*SA+ zn6>kck;0qJcTcI?=kI&y^xogzV{6oDu?p@Wh})0{cP8f z{r!n`+1ft>cKhesPRDxxd-UpjXA`DAV^=ZTkn2F0FCGv7{#8{}S!_iub^$%973{L2 zR%MEnMZx;26*+^eR^)7~vM88UMNwr*tg_o_W!_XL%PQ*}FY8*G=^gFHHaJl;HoJ+!*wQ8o zjg2(ZW^7O6H8&HNvM5aqY@*a#snq$Z5(tvTP1H@?)Ou;y+1xtzZg74Zix;PcHjbNK z8#I^#}6J%=Zj<{6lXfBmzCqAkUTAz_@8AaW|?U}8^& zfbpxP4v%Xp_D5v!BrXUB1B+XtvZw^IEm7g$ z+NH*1UqiMu{@q8D^6y@O=ij~J&c0U(Ie0-(#6^Kuz(s*qym>)TxOqWPv~9t@u+X?K zk%h*72{sg8(OYQtgN0^)(_3ivD+|rOHUF5+Z9lTm?8g?G{ea!VwXo3aS7w+hz|Qrp zDl!UHBrSyeI_Y%rCc!|9+|Xd=vtg@%`= zC)=u2wa`SxsS?BcrCMmB+Qdo+hevaL%v-6x!jbD1nhHC=ARz1ejXoB2QT@=o-o1>4 zrb3Xh&?Ep}Kv0+4Njw4EcK zed>LL!2{$ldS1~YrH|>=VQrQXsTwEuxE)S5~TWol~8Ai1(Z*N zoK+AnU2_f5&X@!?t5`G(O+083%&cIkByYAal>p9w9v=xPr=& zXYX7*H=mul_vb3f*FqaKXcDZ>LJ}>j z99BR4)}YBktjC(HM;)};LQ{>a*+Nsnwz1Fz=)^)3zA_6<^*y!FRNqqzP4(R??u_TT zg(g&BY3(ZZudq}aeT3_NaZheW$FaWP6ioT%C>xWvIR}8f> zuMx^JZheXSQt5!QUZwo;GR68*Ayup|QK7WHM1|7&5*Ld2n5Zz;*LZI=>q}Ktv%W;7 zm=SJ$NlPo`MYO9}U!uCEG(ELerKbvGY_n3p>47nrGUI_&2q_L~SVtLN3hm)|VKMJOT7SPP{q(q3UX+6+vM?PZxo|oA zL$92ikC1ra56EpYn*0HadbR-IpUcBO(i7}0C*^CT@f%a+A0{71xTC#S&X=c1=Q6`@ zi(}*sLec;C_HX-hBpE;^AD?w+f6mhI?9WX$g8r-wZ#({Nf3bu6RHH!;H>+L^a6POu zIPTASlo4rx54*dAb3Ef_^Z4Ly<6t*rMmK*|_fPT60}1|zdC%N0TRb~ z8h`|pg9&eiHaHdmLuqv&{ef3Y>dokfcS3CxzX zBLUqOmeN?|UtG@YY}%Qjyq?77=GLiyyK#T9jXYx{^R?=BvLYO#6vsA_x*t&H5?qzV zEmpHiv2!8U32Eqy(v4|6N2*h?gGFe4*9PzLsN~~b7%eJld!JG>3~GPz7(3j;?7jPo zwGccd1aynnTh_?*f0liw)L(1C0vHt{q zOFtmxDz4LVJhLDN4I(cTdq)=+J*4)TPbbJWu(<4?FqS6w9yhL)F-r)EI zsRBkMhoUb>OC-IaPmAez`jJF+{{RWs@MAEAXn@$nK=4$h`)`;2m>!z{0@I)*d6Icl7d z$Tw-i(HbS=`2%JHy}*Ca&fJ$^4S|^WfS-hiex9Or`bqfk6Kmi6ocMx?DKTThe~$k! zyc`b!xIE|`K=l9(ZvOr1DKMW69@+cr4+m#wpJq548yioywu(RC=k|8T{M_i;zwxQF zy|J-Xbh_K!t&Q!i&c;Tu(Rtk2>i(hF_%z)~u`Wr;M%Si`S^N>dc(yt|JNx6UTegK~&+N}Tg-y4QA3z*_?-ut9 zT#qacKfEsP7XP|+>x)N^iU-xd!X=9-@E`(?P!Rd^xcEWw=+Uh&CY>+7c>V34wRV;O&?KkgYanC}F!sLJVAeEN52#y0TvL~jO!Lo*L19o`ZZpd~6nW6@tpd)yttwud9+yo(w( z>^C~=JfxX6NpJ5=4nJUqM`vC6ZEzvKogI(($1p*T4-P-XGlbUcoD#!)fp*+qe?r6N z%izL)>E83-(2D=K={~MCDgUmP^?$o{ja7@U=hM;9?J3Wm^(CG4QZ5dExS^@4&d zdrHN%P5eizxS6tp3nHxD@b3R+$vn6)oB4x?zwLf4iETFd)rcJ)_=0wfJ$zyh4PO>R zD24uadNIxH?N+lORnaTb1Qn^$w69Z5Xhh#^(h2VFzuceL1L*@eHiG&3fdDdJ%$`QA z0uL!94#%TAcP3wVHtzo4osDN5BO_?do-&c@9l^yUz%p752A zhK1Mxk`~gngRAX%{n0fXIXYNh@OWW; z-Mtsq*$LKJewnp7+@5uyibi^) z>?l_xsNUskpx{}z#Z1+l^wuWpI%neh1x|8;UERU%T1os1pP1cIE?Pm>E+5*C>?&(rF8_S2t*p;b1WhEwAwjW|46Z7d>R!r#=v=^4fm`n3y^!>fo-uxA)| zkNNn#*s{K#f8Y9*xTy_Y)v@C}BjaiT~K>KIuMT@gG|ok2h}O zKR(@zRG49md_RR}`}7Q^zxxIDp3&iDu{bUZ1S6KkY5$}&zbvN3e2VY=MKK;7Ae7tr zzz6i}*^~^i0Qq=a&dOW%#S4O)^=Fp`(8jkLobaB{;7-GH^9XH2gb6~h&+s7q=)4?X z-lvu)1p@M(nWoXZ?_Ry$-G6rLL1DS{86ZY;g9zVz_Yj9-ez%>*vD}RaPs$sp(S)L# zs4fj@G%;}07>0v@e0sXhl#M25%Y`d6Vx%}O$7jVU%KJy;1H4rfld>G5+Y|zT7@481 z7=?e>&dnbdgX3~=VqjjsB;p_@-ZT=y0v$Sw+v5+Zo717~CE^Sg=%mGLA~31!KB$Z6 zqynSw7l^Jc3tMCcv793kfkrnTos{Fz@pL*wuMwg%d|13Ef=wp{g7^zO{D^Y`U?+%o z0(I^e2N>`O|C<9Iu?^JG1DYPpB;F4k76+FFKoMyJbL_*Thve3WY(c&U``^w6#!xN> z#lwdWi&q8@@qpz7-Xk=E@W#P%Orb^h4GB;C7o$^p{ORE>kO0Un3HqSkXgqX7oE{PZ zsB57*fr7^PZ)bx#KL?gEEc@v0Xn`(OfQBm5BY*)7HDC#XItS$fAw=k8e-hw96Vqz| z7GP~K=J?Wd4I^&bwZUu(ellD=>zh%Y9n*XP#rz@q=KG5AnEnuXL2}IN7;6kr=&PAk zGl>3p;rPZ6f<|Q8AY8O|X3Nv!Y`mNop&d`n6?#o0CZ?`>Uv(V~0aJn1LT%3xdsQq( zr=@8Hp@#y8JdfHXS8QchyQb4U_}gRabYzt@J#SgRi3UtOa`vj^e5$VPWw;U#}Weus)m(} z`qK%msu0~}*_Oz#m|_Pbo5h1_(b?V^B4JLza1pdIUx-Bh5)+`fe`ONvxC+X>wefdo7>fBfa`55-?fyn>8{`3`Yt zqe1Z+;d~Qp)7X8^=*#@rh`-ynXgPmJ?eDwxi|?jDi`T~?RquTK`0?T4{P5v)c7%X| zas=oZY_+pFm3Ccww{JP@_X~9I4mMJZ0DFtM0dW`IzQhJg!pK!LwBaeyZ7?1sPN+3SFc~a|4-`rcdy>R-QC|WzWaW! zcu~B2vG@Mf%O769*el-su=no!{oRKJCf>-a+qY_HOdIPd-YqYNCFI6 zj$)wai(&up;^FZhlaFq%rLVWirHx57!w_mV$14)6bt%Nr6653H>wIx{Cgpy{dTUk5;zOsgW9_$dT#nk1?wM zNV=vB+r*3e25ncM>y`G|SOkNu(e}S-9+jqW3M*$dv^Rm~MXqA;;ID39)rJ1G`fm8eZ`E%lc zm?LrLaXFh5_o&n@pM)R^E19eDftD+;l+SD`XiH#3Xjpvztn)p?d3 zI|}d5I)RoCzL|CI-bbY1Vf+by+WpW_p;eo9;R#T?x8|iH?Yvj-5Mov7q+!q9Rvybu zL4dG*f2G|Z$Lw*lG&Z++XiG`-2jN!Yo8wV455E?L$fx15>2XrAoR&o^(DQknQsgVI zs}}kCzm#I++ed}~T&43P{zR%#XiQO#(lSM8rT}cA_>TW>rfLn@>ALHHi6)*+m306;#Z0~p}TIXs)jv`@uwfdd|~2{>IIVnnC7@Il|dzw`Eg zZ{9_1n9V7d4}1-`Z@qf^4u*Ev#9zGGh4FFUT8v)MHj94`@P9I{U;fv|!-sDlbne?9 z|G4idV+U(1y){*AB{H5-_g~neZ=<3Z9}rpR3&Q5m$J@7H_1F6I3hAB*1@$K!@P;J_ z=MaY<_lgIb^oN-vGV;Q4KQj)DA9wfu6CPM(sWX8Mt_VC3>at({ZhScKPuPe8By5<@ z$%ipt!rf{nL#d4RJA~)$*SKTwk=!y!MpX`FEBA`R04|Wsi5fCoH!SXu|HD!hV>sacHt_>YS!z?8fG9e;}7a`x&rJVK1FzSYYIBqk+SmfvkdO~x8s0YhH1-X$5!$A_6 z)}=ef*98>BCk3(Z^9P&Od$|AoYalB2cHe!!_nvruzuJ3{go7OTFb5-1<`Mk)B}EB1 z=Mv45wQ6J2Fnh+H;UF3Tf<8tR6omZ;(0rnw6`e=)?am7v%Q_DzlQm}kUSXKT5MYJ+ zn0}9eM*iSI;e4bI9ym%=KGp8_!;Q{EB;nbhAeYLmYW=L}!gc+FzrZB$ptvOLzJ`h2 znTs~%o==%yeA!1BJQjt9R;aT2bTX2KFJsX43nmULoK#z8jNs&}}WzTrjtfn8$~<^64DDc-sb=GS;Y1 zs`%`?=rcrg?Y%LpW}g%vXxt=%%nCwC6GyjP(}mB5~3d!{fjIPHzD1C0mX@wXRm ztN=rVqTTUcd}@uyh9qF`t5tpe^yc2%|K0t(6d+Ql-aP0qJ&5@14gClIjLKsIR7gjH zMEWP_O$B@3?b8nfppCnjW8MbGs`Pz7Tb8I;>4zlkDEds%Xo})Bg_^?s10CYDKN*;i zTTJvB2@Q%EQ7YXlzC-+*Y=cB{ww*T=T#>XhHZ4c(w9*hwtRGY_$l#c>9ob@Chk^og zfU$Cu#Yi|mUkP}+o(QajB}5<-mZ$y;y_i$X4iRFD?avC*HbSz({TT~MM>j=wVXSLN z$xtPhlAxp^-=~z8q2U{8xr(NF`_{Te7oF{J+G8To zpOE+|@V9n;Ola018$rV8eAebX7oYz;MfFC)5ipjfpHhm325+S36{V;=bH4szbj%#=MdYtqzIm0rWR@;7rM= zOT2K5`;=leG!lzd98y1}WDRZJNY>n{)4s2NK8@Yj-+ur8uf_L!um1At?Tgp&c)h?a zqgU_Y0mh#G=UuwF^ZouSb3Nt7-fr=?x8MJ-w-1Z|beGuZTn8T?d}ZWWG#7zL|HVr+ zaGTLhx8k^ePL^R@#0GAYd>-c?E-tR=2nz*sU5_|B`Ch9>{N`k7l)E>rEw2u;h3OTL zpRMinlq?f9=gV-qyg7Ha@mq3oHps}92hSjSKBY_zC&yT(eoBEF8oUvxn&8ZRQv|9! zs#XR^=Ob-&l2QQ90wG&J(|L7mDX1KCZ{%PU^0tPD_A1B7->c>A-+(9lRMLvPNdTyy#iql}7u=Rk@*cyI0?sEi_vY(%PwMGo;ozx;7<^Cn8j z`2Pw7#NI>+{bt>u1G}r5L#h|L?qWc@SXl|Inre;kC{~XZlITCPa3O_&E1XN~9h^Cd zcM~r3s|c=D#olKw^**ntSQZX@m9tc9)1w7!VR`%<4Uh3|NociLO$?Mc5ywGf7vtKRmER*NBuW=z;nW`)e-Mg%DS6F;rQ7zXi~qjOYvOL zRR^A*<6QWg3f|D=$j>i^eu3lK?=W=M)SX zj$^T{^d^wwR~X1qNyg9Ht@XLZ_J1y7@f!VWzh7~eJ)?a>Y4@8xzE!fV)d}u%5^Xmp zzTd=&&)!ywW!n7m6i<@Qkd@WuWIZ;KoEmj^<`<`h+s9 z_LcmT4A*DooK&f|!buinOlj9p_vXO)D>ua&!5qtFw&h$$8niazHw}}#5xm!*jO}w2 zyw`YKzLBSC#DUK#lS%}4v}QtV9dWwWf!VZM)%>i5=gLRrma_BaMEjdK(Q1LMvFxOm z1Hy?B*;+2?8S4>ZK98r!y?k<+<|DnP>v`*hz~0E(>zB27C*2(D@`+D%Hc;Ll0Z+X# zQNy7|oEKFKAVT9-7-o;g)V;_OVWB^x!}0Izq&sk1LfwgXaJ-I?z9t_oQx`s0v8~au z;rt4+M%h$bN^onRU{7=0xLbdP?p7t@w)hN$_^MgMZ$$WSOoU&n0KfJ__HR9tc{nmB z0!2SMuP2wQBk&5r!oQNkvqH2_A%t&^(Z7jfv~&#rwVa*ejgJ20#xYFKVcs}~*L)c@ zd;0yn&%W&(!+-t#Ifjo_$FPihUh$kP5nRbVEYW>tr)8Cl4FdY+jBMW$^{+^hZSObx zf+fGK6FkHrx7_C@p>KT0aT>r+@sQVz?|AaycQ10?0a!}7H6`v^hhX1&Rr#})uQyKQ z-Utb>%%xTHK=<$ab!aYIH79bs&)hhXljkmfUy;(+=tKrj-8hkp+UGfcrTGcNaV{~t z<`d7ZY_IkL_nKVE65Z!2zcqq3oRe?7$&>Tn@kv?pCVvLf{KlL7%inssMp?dkK-i5p zxp+A}yTt40$BUx7{cxl6u)EROpts?_{q*^#bP1h3Os_4Vs}u1GK4mYCtIEF$aop-y zy_Ph-aV!5S-O6<5_WdzpTIb7y*>t%WP09k7iHGCwFXxN$ zw0Lpv@6W$2K92B|Cf&E3e^~ZsC8BU~CwV?P7*mw?Q8_7R{Y6>4cvuwswi-px+BeFE z#b{#j;)%{hVG*F!axyRP)8m}CZ&?K6Vw^(weieT&o)>@LDE?Hu{QJF2y2Lty+qXEy zRp`^vKg)UXf6&PX-~J!8dHa_2I+VkQ#VdEimAYMZ@_sd5*RSP#iEG8CKh*1hfGGB2 zd^DY5?5D+H*4c4yLmi{>ByC$q;lr`j5Lr z)v9$|MV^=F`&&zF@}Ln^zjU9VLUEy_L<@t+N0@oXnR^<0pZHK#5MgmF?Scf|Ka_+ zAKn-5UhKViv-^H`Z{LExnjB84d71~ydSIeq2{xmz@xOb;gDv`d|HnTki?}};m&zCb z-XU-d+RXLW&o{{NVVJ`G9!UE2M)9$aET#SVTt`)SREz%ngjR{6g=;m5cAJjDO&H19 zbhZHH$`TVuxAE!1_UUwnmwPc_JGj`O(4Wovm*UwwOaZ2K(H~7{-O*^`fA^}TOt;z1 zMs=q=FDLe0V9!V|+{+zlRbmlLEvS>B=j~hj-@pEGcdyvnefRy|d)f{5tF3chnhXJy zXaMbCCoRX=O5F}h1eq^p$RdCp!)zFPUEeykhY{}F(`o}WzxesA=scotcV6J7s_uiy zbao2nxmWmG3E4THUhebN_ky8g}V2PfrhQko9DV9^v< zw#!qzql~@*$$UXGW{iqf?9Z_fkU`+Zy_bLguhBoAZy3%7^FAnEy#CAUSKs0{H&2m1 zx%F`4DfG#Wou5gcoY{TOu;_U1^hg$=uXto;*bx1F`0CdD09&B{%Hj({7{qY8JQ$ZC zjoaBE*2>;!vgj@R3+7QS@A2F&4wucF;rz^$OO2EbCY~e|!qRjS5mB$LDwPpE+o$UG z;3A&B&^=R^X|Nq^C99&N8BO)m*ZhU@3^~d<8V1KS`_w|Y4LG7JFyz)f)rMZpSHp4`iGmpJ!og+`3F(} z7G)1Ix|}_GrhL4E{q$dUOFM(%j`nnH!FT%Oqw(m#dG2CV_wFV|*auE28_nPjsaDg& zd`0Z{Z-03G+6T~#4(}Ac!RODZq=>{ARt7|B|IDVjz4J#)#b?NbLTwc$FYm6^?7vA`rmfVF*sRP#0ml}3JpX-6KyLv1OEe3)-74WV zY;ni)hkoiD2Ysr!`%;^sXV0d{@YIKz@Z8DS=4m9>riK0dIZbcOUrztMN2OocMOVYO z0~{A;fn=+2`{zS!Qq$O|^K0k771~Do)5@g6=5#nK%Y=lc^#oJGaXJqSdS{DS)Dt%% z&zP9XtDq$%d5*)Q+lIp-Nf+lycfa9di%Xw9i~qvA>d&;%;k#+tj3BPSs@qL02C%!A zDpY7&%b@*|>@Ptw$@LYNvi)(Tg{AOP+!*!HEkTVx<6b;__TTZJjt>mIZr^%zT{8O~ ziIi(Y7HI%h8|NxO{59fi&(b*^z*bvF39vPP2`#`4^;4?>F-0p?EB?wPNL<7?{Wm@h zE6OKJC_3}bbYon9-c1-3Kyg5*p)cLl|u72>s21BW@M8`$C?-R+-=c3?kQiGhOq&Xf-= zWiVY%s)Fs0^g7s(u^*E_F-XyH^*9y z3Rf5IpO<7yze`@mVhZ2ptngHXqh{0LGN|Eb^0kPJ`@lJ;WX8ecjF?;Ev zkc33{Y@8O?gc^Eu4hTkqxt!C+k<})tpcYLnLh6>?-~K+2hkImXW@K%YglMXI232Lo z9vWA+b!pSuq2Gdmlb!BUf+Ng1wmDQrn+aJhQP=AWDpruB>Q{NIJe)Uz7mSr z5llc%MA7#`Q0+f6DCO=Q!;~AzQyO_3*MP3pl1w3@rXes&?S!~0(@ATv2Gu=~2sEsB z?_5IY|EUI?0~CcihPa84h-h-7TN5pP1Mv-rF-2DB=XCduv;F|NLG*iTNShr4S`Dp{ z9_8Adgr*L!1A}b+T`|a`o5dj5TuzA9Q zgef>kdm|n(8cEG~z+Tk4^=55P?btFcALFFK@vHF`n%7yZlVp~t2J%4rZOzs9WR8_-X+6s71<~|mIU56&g zsbB+^5n^Tf@;#U8;Mw8Z7jIO6kd?5xE8!5*Fd#{aX|!3|F7a9tifcG0UQ;+FVUN6E zhn?)ddh_gY_6FlFO&z-0d}@iqO`gGl6r_tz+h-jZWs7Y&IIu_& z*|;eF3JX%kH>73xWrFwyFb3xJkC(Ecfw6Fu^;#1?1j!IXNiC1P#CsC1SXG5VAl+vbDL~eL>$aL5g6alcPzq;D_-#68Rk5^X@EBJnO zRlU06ulql3vkk?j_$qjMhUQpV>OMtFPp_^V(s^0C*aK%PEa$}Wq;ls}A!m4tF4t6< zV~!v_;YRoApXYUdL`4U1AL55)cx{K1qhkYA>s&-#Ro2;flUfqIfh7T0a|D)xUQ}4f z5~niuQ%#(EmqXG;Jj*iS2j#^PZ3>z%4wH?b@}K!v(H(qD&!Dyi9;0TpS6b(h^gjmliseVe%ZatH^k7ybx{@Hz)1O;Zlm@9XR@QiT^fv}!%4{#p5aZb zf&G{NaxY~9DQR=urKYhZNH;JRZaw6xZ0A<$I^Ffke6%83iZ!fot#cH3-+Qg6I}I>x zX`S6l+@G1-{J4J>4mk8zDl4yx>Q<}Z6}94Rf)eeD8zbfbC~MEU+jutLPae zeHl0nWZM#tfmm~5#qXPrnDA#>Ufg~^Go0{vPB-57qod`%{s_(XZ@b500|E|e)3QW6 zd^R)Hc9rI*AgF+xl=3o~N_bli;l6|yr+HyZcm14L3e zFGPq$4rwf6hTBd7NBJWRz!8-Z@(t&(are&9dmzI15DmeO8+vTE*@9Nb{0{eAyhiaH zNo&K83@o%YaQ99{#GbwNLQCF-QEbNOK}SRM7aEgFN9U!RVi)BCDT!Ef5s$^)Ul#RO zgjxMmZ^eRBAmoA)a0}5pp%im!EOVZe24VLn?_N{GMYu3mM3MN4FJjJ~Ka~JGKbxq( zBkdJLHRVkBUe-o~_C@ON2>->m^jV}-S^MxtaoonxeD3a@zfAFV%nvYe&W<5GT%)%m zdY|3bF?3^8D29<9BD)TuhWj#3j9Wk6yc1*NB<{k9B}gw_$u0Fl@`J}{g^0!F?Rs@w z`6*5psTWG*HEIzw1`Hf}9f}_Ugw-LUQ?fEi@fI3eWNVu#@V=M=FW1OximLs~oJg** zuAk6}R4t~Etmn~`VtK9mt<#HP*wHD?dtA%TcxY%Es@Ycx^TK(~J<#KwauQ2&FANca z@R^SLzy%3x-O3HQJOJ~Ta6^{kNq>N>6LJ>W+)kvD9re8UWQ8Ac=%DcPy70rw{rL1F zZbIisdoVa@!C|Pd@{2w|w8Zm%yQ$o}3oekl@O~9cJ(vZa>vYtsbA3%=1-yhv+4}h$ zOrBHTJk=wFc@gM71{2G6li6Ev89X>sm2x;&{?F|(e-=LP-FEj7;gHg~&5MsiapXCpCrTMf4K5QU_kg?i#e4^`kZ{Y#z~j!?*Cx8wkcTTjH@if%F`1ykdgb zMzkoz?W2FWN$u4?nn=?=)kGWHrdcsE(ma7RG@N){a}Wi)R!r?9o&=VL7ff5Ymq zWd??qXmxOdPvCYATvlu)9nW)SEb!yW%_Pp^2u_K=;kkY<2hp~~WJ9{8J!!@2jaf;SZ@r+Az%?r{Ev5>>}4~ zgfH}}PEu!cF>`MZ1@x_GPRe)t9MC}|^An$!Py$Gzd4IxX$~m8BS1HnAC>*S>Vie9w zgHi78o!wv7o$7TF&rDzq!o#=Xm$@#klI`9Zf+QLTF-%$rn+S|Y@>tM$PjP)5_4^5u zE1rPJ!{-1$^cG;`F8pix2K&IO$3VUhC1WDLC5TBwG>(1LkhNjG{P&9FOZ2FkVM0W~ z59wdND1TSIH>%z4)c0SnaHX}rv|DMxmlPa_xrY`9eOo<2zYKZH_)5k|TaHp)s4Cgp z;M_vbEgGV^7|&%SDAk5X^vGqELfR85rpn4oCvKf+A(+XDw-Q!QBVAElHTIRd)C7J8 z{~ot`jmvC|+|;*5w!SU_X z@DwH!VeuxOP0VcNOgZi7cxOtJMYuqe{?KFuxVX#(5eq~fx#QiH$7f9>0U$M1aqr;h zBb2^mLG<{*evpyr5b?mmPNxkr-!@#Cj|crO;*25i0+NTjgV?I!>(-_qy|s}K^%=Bm`TWrowjO7+2CSpcbSar}wH*HDhaALrW9&FsN8Y{T!T@7ACh*tKfdR;-QAXdyelXv5ZVJd* zz*30wiJ7u&`1v1(6cwSf%dU;!co6I$(w=RyiK)2-vQoDeCAtyMg%Vl#mg2UB z5U0Uc`|6f^Rxi(VhS%uh={s?*Z}9NUJqf+0L<~|Wk=wD)@a{~zp4qoEtul{ir>{k+ z&GHj_k|&QwXYl=+i~0Y{FL!`xvcW6Ts`0gfWB^=Zn&OaYq{NLd(~NHgks17V8_Xi! zoW79>4bJDunYLK)`J_?YC!Dy6zs^t29 z21Njo(5|iDs0^F!ljQd(Ci$HPTsLFVdslQq)4|xnV4vk^K&se%qSb<-Z>7yeNP#guzNh6D;5Ry z0UkRd3pRg)>G@F0JjKt&l4GY!l^J@`P`gj(MfkC1O!W<-imufXpwp8f0D{?lg%zpTI9Ir!(>or9m>tiRa% zuV=~rtNj;y`_Fa`lEYUo{`KsD(wU#19qu0Ny?(RzY9B_f67>_vz)~ZL)tTieq{as( zt}gH=^tKjOn|(h2b@P4Y0{^Tf8yg#I$;Gd|^{w|v91U&;dCpvst!YJ2)ylp^^;m<_ zoJ4nc`EWmJ_Qn3E*J8X-JwFIDCrW!4%O(l3f+w4Y84|NYVVSFhg4}}F(_-ij^fpq9+${j_9!v z4U}3*>!JVOgB~r#waD7%swIjQisuC&P8-$EUZJ%+WARPWJ6(6?sY6=G7G~kQmY!P9uG< z#Dhx(1HiM!TGHOYGP+5)S-|Ck^RTv%#%gO#Cg2-=(%7mtG2dFR(W+dmy*tEpYQK+o zCZga%TQE&dmsr9!ceyg6tozi0CJz=_%sw(6(xXN4v}0QsP`=HjPU~(Y&#_LeiyFiG z7sER1hRd$hdPuHRJ4R#`Xb(*fd-u-sq+&+knZ@-H?9o2V%UpbCrpo%~+r5KlFQ4td zq5nw=U81Xl-e=4(`Y9x}doE2F)TL%B#%>%M!9%nGpaIsG%mljc&wqU{o-BT+)!+@< zL8?NL%SyuZzhhmeKg6-58WufUQ^(leZJx;geS~ihaH<|W|CI+ye$#?H*Ax-K+<{*9 zUp)J#i}Uj0C1@Xy)2x~4SrvBAyL4&63z0@R@VlBS_n)6(e zDF9?sghmxKV9~cPi-|;HNu}B9v`(mAUrUgzWJB`Nb4r`aV+v7j7`TU(tu9S(o9*rG;LoPPX^}74Q9~|qRDJ|hmZbC+`FWVVG`6X%p6p*q3VT_ciGHv3OnJ5jbLuM|~Vl zM@Q7I0@rY9)FH^1+8T`nTrvzK$M{Vh=E|dT@HCdEPB~mCMYqerN3^xI;p0G!#=wVZ z?ki9DTt>yrA@yfVjyM(<;76GCKuqiw#E?-@1<9%KN&A4umw(?eV# zKe2O?q>$$LB{2t*aB#Of$YP9XXI1v9kN!GNs4 ztPQ|`44TLk>B<9bVX?4$I1N}pR`FbxfRX34G|}Z~(8p3bl8tJ@)_~X&+~L|ZYPW{p zGaKXX=>TMyyuyFgdcSFg!hKl7^ppRtb!)@Z_Nc~9fXn2t)=!>~KaTON>z-LE8NmB_ ztAloJBCjMFmy9dy8!J#ls)bKUY@#n^UA~904%YBL+r9Nm3@Ulff6%|Q_20GicXXIa z{(D^ODK?m5@bYMLWAkqtk2W{|&UT;n8`tgj&3lE5=+x<|M$Fsz^~{yTKk3?&qRB2B za&+i627k&-6Z5i(MAYP%tj|I?iw&-yc*M?kWrI6SKkjHsNT601z12mpeJq}#K=gI` ztgbG4AS$bi-s+;ay6CMgdaH|`Gigs&7roU*Z*|d|#$a1~ugncdF6PP2TYa>ED>NBnX^1k?tVyUORL@z8mS3olWX;w7-Tf&vlv#i;eFZET zqo1tPP?yh^vvU@Hx@Is+;Ijrjpt2S-Xj4+^9*vMU7bx!6KPkr1F(zqezbFN+(-HZXEy5lb0zaWL9z|MCjZIz#N57ksmjOJvT`Df~@}FFk z<*zMlnam@cSwuGG=Ngf9t%8NI6=EC}AiqhBK& zZGd3|XX6#8s?Q&*O8)~`5p6?kdKhXo)zc;w(dmJULd-%$hvmron6Rr&<_bTDZ4p*$ zfA>YP3FZ)apGPqIp%IrwLFk>_z2gU>h82k0ie@ez3`b{G$9COP9Ebq|s%B$;T-n>@ z6t7q?oW`7Z&{bVw!5H4lKFec4Z<{w@Vssp!M!(xd76Dvac_Tm~D}*D1Eey=kCKqs) ziX)&ZMDKvf0#y0HB8(c@#m^>w0#U(m0;uV*)k^A6_kR+T1Uq(S^rRy~i4pn^+uM1) zw_G=0#~DCP?(bS9hv>_nm)4OC7BN|Hxr2x%Baz_&%1vR;12kE#k?Cv!iAYjKBrE|X zgb_u?Y>*=&Ala8s#P%WBilyrrQ7sv;>mRtqb zfOvj}CHXp6nvb}e6;}n}{M;9W)go-_&asqzAWfmUcPsZwWtvthH;VR$8ODKSFAM_! z?gsQ>X#+Ah-5Ey2S>VWgR{&U>CjX*9Vc4HJ*g4JvPJ>wdHzB7x2W4igDg+Uy*cDMp z)oS$rq=$qf4)!Vz)zc)}+Q7}C;^jKpQi!1fY3!hchn(?xhb1%T6jB1YP?aUF3r%bV zLI1kBv9S+3cz|^#X6peinf%aDH)D?q=t7qHsDUx+Y`hU$VByH#KxIH44Thnkmau4z zezO&+$$l)R?6ZS|R|k(XMyJVX4c9f^{KT*mc9-POh@>NlHt^hkcu>I^)&`g6B5%f{Ai=#2Zwyhu5DhASzzB2SI{}ZZOo5Fp|JSTzVFrvN zE{4FQSoo<|+u{_L5$E>#vws0D#%gDssxnAH9K_kJ8OFr1A?%>od2829jiG!YIS~Ov z+8XZQD8x6;AbEtQ*w{?Ug&Q@-Rm4$pHBjDc%kyTSuH<9fk%ns^n6U*}sW{rEcSEI| zU!O_qnwip^R|VOzNFCe@=C;yxi^PyCt3bKgd-{}L@Uw`Mwx?q;)QtHKu*QKo1Fb32 z#*KUk4x+VA=^IErE%58o#@#z02tZ@tGT#0AUbL6)g*cZG5s5g?#Wbg=60Qb-D>$Cu zjVrm4Fu*5@A|xmz0wx8hQ%r}BG(AhZ*TX%(aS47zTxH;0AP#MGHh_17c)p~PMzjSX zCI;oMLcAEsz$vXbYxQcK(Iv;0N(wOxbl5&Y7Aw`B-eM$!b^8P289%EC+VGd{hyrtk zYHp7yNN8EXb56fN9Q55^Lbg(({`TOXBMy%n@Qm(c7~Q*f0?$AS^gFHN5o{Y=C|oCG z7o|X|6~M_CH$P!d!486FpnXFd3~-K#=0ynd{w_uZe_JRW?%wG#Chuttb;K<()Pco^*eqbpZY3wila$Wono(pq<47oobDYxz1+4jHP2ZgL+ruS&AcKI7 z=EH}{n}6&bCWp^<8MPwWeYO8)XK(*7*?al=g?Q)h$n#gv@#^L4w{PU<&I|lHcxFv3 zZA3ko**mA?V2|WpAs6z76E!zmJ@9ixl7pjx^XTu4<+n)R!5vi|@`{Lv2l!MU(%{q; zt0tXs&F-Tpvj=|d1h~SLQN}(wvl1#rmb_lR8X7}4M~E+O#Y#I1>xg25NZLOvtyZ^& zeOKN56S!m}9)dj$7DVqFfK)$=(ta+iKKkMMwvwv*OIc~zf)CG{ZY>+9eXz10FK1kM z5I^hAmKDN@X-kWd$--IoEWL=APRaLS75@M$-Y5m}U6GJo+zg1vJDa-T@oLKl@pwNA zf%xfE5RZ4ZY!Hw4QnO=J2IBE*w`s*Oa)&|##B~&W9~{Fl_oCv9c}%OAW3XjwU=M%| z?Gb8~Av51#&{whF^G6eUel$ZjqFoHT7}RUSc0*~@7p6@{PJp(KYmgA&irr`rDHbe_(RxSeeBYXBVq(uB2Z-UK6&EL(HaJ5h zPpWYevosV;L54(On2yMW`O-B>g=CW!M2Cg763$Q>!#HpWhGR#qTW{7XZgarw%g=K| z+4i_M&OXs{ z!2_Yi_<+^aaYsT!IXxWQi<)R0(sSNWU{%HJR8^|f)Nx7WT@cnG;0{%He65CJkcq)^ z7JgDR*5mV6mHq$y0rAxw=PW_+iY{F4ma)UzBR&)>g$nPrRPQZ1Se>k^v@ca$C29%i z9kt_Lh55Y0_Mku?Adn$U93Ud$Ru?qzuKpfwd^R49vR|SVherGdmXtlR_Q)1hbnyrY`o7}-{#$$+QRZ^*PI@fallqijZ`GWy?QEBime zQ|l8{@MrMkGEcIwdDBymYD2iK!Rd^b`est8r@3i&m_|%=8=cO&-}F|~PEFXHes7$% z8XuE`t%nC#vM@>;C6&GjbxRvsHv5CXY{3Z^c!`F=0_+eARA3fP|-umhiG^JXE$jkn^^(b9WtQXO5|1moh6PCI+-GfS?3c;Q3t- zjMl8Fs@q`dxscTy2Y~u7KMpaN7YHgg78_DUR9|RXVJwc_OzPdjqYFNAz=?9w21v!X4u|PB+5nVq@y-Pozv* zMB0>A&{bt~4N;N3Np%SH-3n56Vgxc036U1|yV97-A*g<4|BSidv&5Yn7+^@k9kU2PbS;J0T9E!2Hi)|_x1y%>7 z)}poZI?9xRK`c~Z5bxdr*9Ye*aDdQ&?q9rgY!X6CI|^{^93V{(VZqm#%nCkuS>6BN zN4JY1B!kHS!&$zN=kTQ-MzW;ol8dQ|JKhJ+52XkT#8}{Dh<$6EM!AtUI3i$nQ+}D$ zpBml>hljZ6h!GA`6mP_W*oHKs@%Pb4r7FTycEAbb`yh)_+RsaAHS7mk2QlVx_g?ff z6UQv7aj>G`NkYihWea&fl9{rCA+uN7BHO&Z96@(EEP=!O)ug}@AjYMV8elTHqC=b; zJQ7~xv-k$_~fZrUg~}xX zapRxHgET=P7vn+P!pu=ynIdP<3Bv36p1u7yMpZq1Cfnnd$^HAVs(@4_TEXqO^*C{N zyLRtvJVM-KGa^YuY3vgoKq2O88WVJN)%`N85Z4*(LxF=ROb~&j1(>|#gbI+WonPB) zAKqWtO|6kg#*=qr^RKs(%#dbqJnPqf z|LX_PI_1Uu7d=F5S$#D04>y(P2%&@iZLK4>d2#H1G{)y@1L^aJ?yGh++lruT<6@h& z9FvEsFlh%^&U>+G>N{zn$ieF9yhTIhUJ5<;aB#BBs`&F9402ipQYcOVcz!h!?1XQ+jnOGW`c60?qjRd_$d9VB~u z!8rNfGGj(9Dm{I-{d6wrspge{)QypyUc7}Elw!;N5XQoNY#4&=gTH?2`Ri2h3MO0F zqR;2CDK5=%$5TYFOi&VMJ;_d*7Z(J2jT(49W*k8P1f0AsWj4TTCvEcV4s2mi?Hpu2 z_8=!0iKQ14Z6t3|J!Iu5p3>hh+ZU~-++3U_3(+O+-k}d+bY!wtdAmmk)i2&UDR8+A zxVSGm2E2e+8!8&t(rZPL`Idk#&f4dF?63$A*K7*8>Ng};F6C)CddlAT;S?qooQX?j zOI(^;$tf>aR}bv8lCU|Mor$(g2DOr~IrPBO5VI}?=qN`Y$fUlggiVh|w?c~s*h+?i zwt!<;$=7guDX-|p&A=o$N-$04b_)e;d1=}NIT;CIUb^N;q`%{RVcD5oQz6L0 z|0e11+_>GpUzIScFyy#+>TO;;g}wVk;rS53v7oL;ZbYC*+~FW}=N*KW%ThJ|Vz@jBDuvto9NGSUAtVB4~@|8uhU=J4(vfauT9=-l+`a8G=lb`G8;|F!?>|L$)j|BbMe4dhm% zr~=kJu!;fj0z3iUVApFy#C1p{cBS}VLg$G*eQ2A4nV8UKNm7}-X-hJ@?ORTmvsBx+ zm9Ruu`Iiop%^)q&k~4@)P?D|QN?A#l!ZyL!GiVkI&IZQQESpy{jiam)a>L=GU%y5xNXo_$RYqTkGY+d1opw0U^h?Y0o~zJc`8 zjIxRBK}fSjvttA~6%K!Sku|5>za>vm%sr&4u`wA|1&0_RCXw05CFfzp376R#)&VXF zHYQW+opdn#^0=Ha!MvpRVLqZZ;H*2QR0TX(VH5jn$Mmew6UMz-`pzd3GT@buC__JI>5Kc}e!7yCw{d@eiF z)86py8A9WWL&qmZ!8xsU5ZqLA`&bNAr*$mL3iSm<32=xqEvfxIXdyWlVw@nK6gP)y zi4I@?oNkY+@kcj;@HvlD7nXx)c4^PkK(y>)TzNA`Jeez#@$v-;@gXnG?!mjUnB2jq zch>OQeodmjuY@>X;)6x(PM@HKZ(dm5^0MsbTZLhuVhnDgg{Ei`Pf$_M;>c!Y-xm+vObM{4xX zuW2}R{))9{PThRZkF*zd60`=4i`e@(3k<&h;Sv5!jJ+SOhisn4-}lc&40Iha_|qQZ zarpgvtevf#&J!tCH3>Z5JxqV<0;dn~9<$=_O8U^e#>`oaOEx{J!B?{XnC_%Z10R?n zs4Ufhy_7cS-Sc5-#zH0Fs4v4qQ+RmiVJ zc%QIAd>yoGr7inJmpLl3Cm>RTkIXJQn_&X*lRtdHSqr{`-Cch&;NcI;yQhf6lY4H_ z8rFYMh~)^dDscxOzT_t+mbRAcgoN_Y!X8CDsKQf$yP@B_*(IbLS?SQRLj3RAGAZP( zoaA~I-p+E2=iNJ>pWa}WOIX%FX?6Wtev*aTc>P+U@HgcYc)s{q-KzTSCUQ149fK*j zY=+o@Kurr?s+J}B&0e9b)ummbzCxF>RsPccxZY_&+*>a3br)iJ?~;+m{H?R6!Q99^ z5nqJ@44qHydoC1Uo`s0HkA++W`7kd*MeyEBrkHNSv+A^uB$FR6vl*9`6fbBIN3#{z zv3#y$_aDTmwy3MFU2>{*9bE|#f-Pnxnm1TMa3LmAenciZ;`Or4d|PZ z{My}kngqCU`Yp?VGrn)%iX6BQkf@X=a%aZ<`&G$;3qy`ecMA@3pOLOKlna_1W`gi2 z5FC9a(!J}G?g9sShdt#zKhIy!@5S2E<-2nTxrkbmG9lMO;d*Y3tSjQtm)rtIb>7^c zD>n5H#-=WnNnN@~?b~EN%LQdi;XO^LRj@yp(;aaoDjQWqXfr)c81IlEMXGIgQ7$u~ zm!>e3WO#aCCck|hN$E-?UJggCuG(|DOJYpgHI0q9-IA`^%p(8s z?U_ZMW)yf6VnZz?65EZ2nJTa)U(UbMZjXuDdel4p#z!>*@#H-Dw#_0ZIfi_-(lEcd zq0Q6EGV*J;jJ%%6;MXWSOJ!l-sBNUWDn;X;n*rp#R2JF@LcOc)WH!!8xs9YJb#~?W zvxZul})p*HI-*zEE#-t7LvEiZnCg%qQCQ@;RI=?$4Kt7-sIwI zx102dk8a!jZDmmz=BQ#QU;Uj%4EW4YzAKB$-ugFxA)CSV9-QpV7M0oi*s{dL@m9ZO zi^@q?rm2$RNtNAw>$VDum6uDU#m1LRvaC$YO!cMroPb*JfVy9|sQ8=lq>tHIhUj-? zXW4GjMZeXkjfbt|+j6d7qMapRYGr4c3^mbdyJvi(sB9d1|4P#gcS6@{ho^b!BRa zxQR|0)~!Vy>7BS3c*xsY8aMh?7+c0Mu9u97Ekz0$IcC{Ro~Z(>b%re@B3OeWE`fV= ze~nHxYYBY73nn3PpXi%*ERGpl-U4oQWo-H713@j3SDoJ{XquJ8TpmFyW6RLwX~K8Q zV;RSXLx zOmo<-cj&)-((1JawNWeC*+`N@XRb%TQg;(*f@7o4a{^{}IKoYU4WL(^N<@q5HSgYW z4RrD)Y+T7ZFv;&WlLyJ}yXs|rLTgvYH6D%;fKtZ8QTqnCVsnIlsbDHeqdyq58VttRYhoQwzPrzmjA`!( zS~+ZClrVHhVsgZ;MEK7E!+*BA2x-aZ5|lHmy}Wy8Z~yh%H_7XrgPoVp-aI=vbeF2Q z2JWE$X}G?{(2o1N{SLmC*if<@kit6tf8Ih6PK47;hFDICA?Z4h2DHp+f6)G|-y0Q6 z#T|xy--Gta=|~J(U88SKvHfsJY_;qt_>-|P@h*d*W&p~ot>x{9t zkZ4}^v9gJ;hiU@^8^yRXAH>-iLIgU15H$I%HRux-TO%krN6Dz4Y;M3tzI~ir_Q!}Q z+WUT#jC&|N90>qs8p00=sELZ*R;@QA#0UV(#dd(R(CH+=O?`-2Px}3)8)HbvYvPE9KL$-uV)9z!L!$|4&D$u9Hyvr*pk??9ZU`V0JF9_K&==CF&`IX zI2s`S?0CSPtR@GKY1t zOAL2eA7i@*P6jI=+8LJnHq|WbLaMSJ;Gh(rHh@>{DWu17HzY6cRUh?{;XSKqy_7mV zb%yTPR7KjMKwH}1=j*fGmL|jVf`Re4@Z@}yKGJ%Fv%;hCs9tzBj6EE6i8W4n9UmnwgOf&K9Z$cwVR;55htf zaab0s2YZACmSqGn>jKqqFc+%#X=*J8;WRR-^oXFry|Lh3Sm4=lR z=|-apSUAi~tX+R}aA7%bRk9Lhf~sT7^aQGLDo5k`ZT)*%uYOP<-dmfSn^l4R#wl(A zt+Qdf)9+>5cJmE1OwVf#5MuJ`JK!|H#42s@+7?4V=-@k}e&@{!?NNlHC9+y#&+-U- z#abe#^d8RKJszLzlm%^}B|p94`NkP@Q5<<^OT=u@A#)u({=2$zb(P`zU5AM!%oMYb zuUy-p41f9!`cd2G!@wSRVlZT@Y{5=*buk}3{$Sn!F1?e{X@y~Bg}mn$fps(bDi&h2 z*!HA&$rdHnR;o?oXNxqF8xCmw`jZ~sOaH-i<;BOy3jv#aT- zv@|<&xLodcdwcssxYe9LKe;V;55=5k{Ym4(ftrUk(R*Pn!acPb0bJOlEPUA0g?bFP z%^J$}82k*r%7zT+xe_+P(=>pU1pse$ECVhD&2ZmATYIC5?yVl`?6KvNqx|-^6taa6 zrk;)~f1ptf*Mf)n$D8jJDs^({;znDw*=lL&E)U=NBU(Updl4}SFC!NX+?Bd@pv}K^KzKWAkyqZYT>em_y$b7jSJ5u zst^2&A2*N{rAZms#pCUZ2kDDHZ$L%>`NJbCYJVZmz_qz?+34U3JVf8E#+W%)#=TJ+ zH!(=(czOIp$pgkVghUDHKGJKiOb=S)c6Z!0Aqpy}R0*OGP^ntu)ZgB#oP37@IA4U3 z2T=$&`~;jML;)D4$m#l@aAWU4ww9+WQH*&{*cgCJo^HfTJFaKe;wc)-0(ZP^VT_{q zuB1E(A`$t6Xm9}t^7?~}{~cMOxhe<~R8nIKISJ0xL^7hs%imT2msnjrshK`_szZVm z^gv{RHBz?e2s}*w@VDQudA_BZf4TYoz9AT*#-L&T42NbnF+S&~4hS@dR{qx6ggC>K zzIES_&W;x)OgD`06nw`^9FX%zh9wo><-xRYwL*(r3fdfKKXK^cbs>s*x#;M88-x;n ze%hA%9KU*2Ay^=;d~pbYh$<*MVZr!HQ479#UXhQhA=1kUWBJ}2>F_&;!?8m+SUv`> z$5FQr=J?*=tiTIOzJR(~tQhB`IlrYQQw40U$`E0WY|tIyMW&`) z0|!>P+rtYBTqczr@)8;|=yOFRmD^W;m>$ebd3Evh4~7!IeB(X;TV?{9J*3VY^q}5* z@z!ZcaXI8(P1ce9bz^f2I(`M%2M+Cja?X4i0sDNUg2T>OXcEK?GL3Te8FR)e z5JE}CX`kR8S$!xQFL$YGa%Lq|dosj*9J%AQ8ny-99F5>Lmo}8uu9-VUSVuGRr5+Y-&NPTvL~hLIKU-z*proU*3aiF~nb;$--R9xBR$(I24uDH`b$|{sp zU3bw)i|M1w!jpQj&_$ravZm^Vie6J^YBDWhKb@|MXhENJFRYOeG{kr$Z9K2*QnXU4 zB6CoLby$Wj%CKc zvLH{HqFVA>NLv)-<>O|a9i2dI0DM$Y{ILms?N2*WfQD>N`}(Im-EvI@loNS5&R|RQ z7}h+k2FzG`XX6oUK$vK_L0R7Ltk!64Y?w(zNLm>S^XjTg!8q>U!=KtVSi)J*0DndY%@Y8q#rLqG_7h@TkE?kD{bH#fbN77!kGsp`IDu;*<_Ceh!Vyw2O?adj}%>g z{ApX#VSbEBEwvI>)T2`_FGP*~GuU)dl>oRJwNJ+VF^mHtN3WfPHk*b@SqZ>m(!_cr zd+)WIoynp6%^g>TkGLw}x!n?^6}>p+q>813JtX&;qz0;{J91O0CNI_EZHVZj7nQ50 z?W;g0ipf?JY2iSGs$v^AOrV+SIaG zFA)#Y%)a?u;-yUyv;XoR>7pwo;J8xU01X4%Osz8bI(JyzC_;&7(3C8R+d3xoMcJUs zwDTNzKBu(=&@D{DQv+=903&MKurtmOp7xlq9?7h3@_$=U6M`oo%iyqZ6BhJTn^BP` zcCZ?JA?+Oy^6qEo5mDgZV|17dY5Gb-@lP#S*!ILUd5D`!LYuBkf5C400|MFBMcE07 zKhK>C7~m4*W!a2N<-%vKef8R~-OyW~!|_mbNIgU@Tl zRvHMvhKEgV#YQz*Aa}%O(V$hZ=SIc#3#fnWq_YF;bJkK3hzL*^s?%7EAL1nHnnFDp zdE8J_=1LaE&BvTyv9}OIYvtUWt;xLV(VhoL#vTQrp^q})ZN;jIcZG47iI}BW*Rx|* zkAJ=Lp-hp$LLL(U5#&U~Mk-4XL=F*zho24eGlfLBLeZVzD?c0sq#!p=hjL}rBxnIGVFUJYsxET(Sbyc(1ryGs?`5rTNGiZk8p~%&mgL zHgrqn82Ic63Gyrwu`;C!bgK&W2#hog0YW#H!FxHHPU4#mJDDC)@<>X9jZnI zifZoxqEKXo1lKFL@Lb2t25{LL->3nc5>r^1{`%n$k;$(BWuhp$9YPR`v}(3Jw)m#& zPtD@eH+j3bZ1zdG-R2aLI#ZB*FCrxQSJSqYaU9knCo=?_=xkc@!Vnj&-y0Lyo~+E{ zZooWFj!LMYx7GJ&rQ{IB&c`ffa*dUl+{#SO85O`@4X9vcCKp(Ll7#S+p(*YWY0FKR z$ysJRv;E2=1aBypdl5qjrXjW&@b45wlNHSo4|`00_{bwIvzN;aC7xGWF8@2(GjTtW|qu(aPzupE8uIL(BByVTuX#3Qt5^OE7<>F6%z6C?O=>%~` zL%T-S($4nM2)UWs<(|imLmt|Koe92{bHJZ(a92E9&aRdEh;Vc%I%vSi&>P{Ub1b*Q zOUsnzCtca%O$#XvJIOLP_;}Km&=zkus-{@BEnd8rTHwAeqUpRXUcA~LT0fzNcPm>w zvz40JFMVE_w|7mSx8fgBDd6Y`FnL_Ezp~CFhK(3o1|H$}R$ksqwazQhu5Zgaui*&u zN?84%7^9+;>s{1?dnK%PW|X||eFdJ}D`EAQ^X2w(pAwn$y@C*By3A*yPdDhmAJc}< zujC)}%A6+agInK03;ClE+1s4T8+`-|rxX3b^TRZ(H$qShljCu(p*j8FUoVd6l3yax z1QU1fz|mY{7BJ--+D2sad{~*=8Kw^WwR2w!ojil@DvbBVd?-0J@)~T}joBX1nO&(N zY6+8?RMiyW?j4oi`u3cYC8VR~QRN;$Blu?(nQGQHUl5`u{&y`g1#c2Qn0##g(H{y- z5FFfZYynUnEd?q9&^!vhr#d6pso{TF)0Z0np{I=Uk;bH?I>SY?*{QyM@wUqF!$>Z%ol&VLVm@c3M_u1{B#=L}-S#0kWK+H2Xchkx*Td3tji! z_N3T#^liT|YZ!}-UWkPpDG{re>p36tywFO%JlZr;|e3d7PD(u|9X)y|pz6sm~2Eb`PxV%JF&ieM%*28%v_ zs6iU5k@2(Mul&b!A#Qnk>yNNNPZ$`cy}n+&yULsxNf-tn08L#LzwZy2@)j9AN>;3G z@BPx?iP%NO)Thy{nAB-u5z70sSrbf()wrUS$m8|%`;*obFwyqwi4DX5md$=R+K~&b z5K<6(h5V!6&qGRfs*WxgHE?04kX9B{!k(@Fu_;8{-Kv0@ozD#7?VWlJkfPhb`zhsW z7mWB|`2QwJgd4Os9O2GyCoifmz9-?|*?#+d^{vj!TvSOeGg0WQ{3_>7twz284y=5vyXE% zH20BHfYCo``ix+ArvTE66u+b;GhwUdLoWhIX-)@QkzhdfXfhZX;}}2EaqbZe(!}FD zBLG>~h5e8B8bcg~s#AGaTa^F>+z9BflXT;wxaI|zq@^O8nOA4-^T6564uQ)eKn5_& zgw;u;$NDSs`Edvh46L|7`~+Z@;t+hh9$wu0e1}~4N{%zl>}&G-2}WUAV@Gi3aWkuH zlH+7Mi!$l)B8$m!a8eq94>ci}5I%uy1;mKR!r#zw4Z-Df9$Aidj9UMZB2o69qPVn3 zhRwFexz4AzldphQmnP4lnR8U2W1~$aI%3coaK%PU^gD1E+Kzay!NKPdAaPFUve(H>d4`$cplNfW5yqI%R?( z7iww{QY(A4k{QLR(gbQ0SMQfnC2&zD&0mVlXg6d3{H4f|BM+Xx{+i`RDW;l?YT;QL zAX@nf;phhjXY#V7v@Ot3uV-8f?r|Z)A!VIMQjulw)4p=Vn$2Q(smIeKym_x|_#SER zVv>K7@SW#JRBZ%!d*b!f4FUL)P8MF=b*OZCzkjfHX z8oA_);5>V|#0}bCWOuyq;3g?77oVR9ICws10)frJd9dfx8jUkg*%MuM5Z;#^U+rK2 ze1+1g2~3^c$x9Yx0$V(@)M^^wg?&U2NcdU~Q(WyT@b*UoIZkQ{Zo5V)m&eln%+7MW zutyEtY?nhYXOhS+kzCGr9P*@cbB!0pX(wVbp|q|eM0~;#m{UfEA&5U)#%M}1u)65n zOjlb?j*yxNB{N2Q@{>OrE4oWYBDjE&T2S7ilwE@3$2#T z46k%KAnEXZ2YoZ*LcdTpQKtmXZ8+j7(HqBK6cjZ9qQ&!+arIb z9_fjSEWVraJvoQmgL`s5`OTq9WL?do#_7v1ZS}14UWfrc+EdF#UA9$)^JVkYq{obAkD!==e{QE+IVg#?L`>QZBPR6+wAy3U+WHR@JO;)ekentew=_lAF}62_!o`H(z%F(5+0|`ZU|e$E`sN zekzVOaCshL`_lY)Qm#K9VSD{X(MVk8sXKEhdxfJnMt0SKOO$!{&ffm(w{McyI|n;2 zpS^i@aHyFiU+M*G9e;b{ZXFYXkIbO|X$VM2cHAvxKU(O#HfYxXPDld2|74R#>($#g z(N5fml7yZ3ap}HUCfMx8u{D%mPj+7X@?!5PzPXV=b$E8^mg9%bjjgSXN1Iz)G?9PW zZ=8O_+MXwet-(nPxt!yI;RT*TmMpj!N4f(^V8a1E@S?L5?5^q1{1fjSp*7_2AkQZE zB`7SvCpgcvfn#0MM`1&X{-rk2uq%Sj)sw#a1RTVl6c*emeVn{R?sbTnvW^pJonEZY zeyomkyZ)pLD%Mr9rjJPOjMC{Nq>OKjF3(zcW9fDE4LqxAc&hU{s*U=cY-=dMz=2kv z9xFhmd+Q-)LHR7zJKdW2ZK>N(-mKcJajpxJf)k`yq#=tqsP# z9uHvQz1tQqw`8JKAc=m1c(gVc)GjTVv7}%HmN|Gwe6zo^pHx1KHA6WtBak2x=?bQC zSSm$N#Hqvu3V-yYWf2Z1gM|Ehz#ux3gwwyZ27P-D0OtXx%aS_O_YKOslEfC#X7Rs! zI%OdE{L*4=oF*d^i`SM2oRn3atKzurD|R0&*uoi1+qLR~ZJf7*HVR4KC(isloxpq* z``s4kh9CJNl@}f~J&A~^)}PGRZMw?~E_4?G!#;1Tx}&s-`38>==+<=}L}#|pvvnxsEnLv0Wd3uQq-N!W~_e3UU z|C|grah~wJCA`p6VnKY!rld6?TOzvtb^pE8{U&%EENZ=?v{yDY$GsFhEKHB>z2g9g zQgJGO<7f|xEBo@DRD(p!5!RHI!KLBpbAKW#(B+2{dgjCFGTy=e7LMyHjh_kVghp_TUVnCy7VEkh-SP4W#trEl;E zmjg1|t~Px(_G|med95>U4GZ1rQ0t`A6l@qv72*(^PN}n;H4;HnUdQY4i?U=q6IRbU z&-{K+tXeHT($;{fRC2I%q>$bT6*h2rBoB(aCN8yoVbsV1jld$oHTGCx=ASuk$*!VV zT#_k7hHMBo+u8~5f|*WQgS8~7gDrz?z}-89*H1Ov=)l{dj@^unb<2%z&BM3cC`YG& zVH?feJD*P5jZ@y|8|f8Wn;irC?9zw=nE_$4ld#^QTo;#oIotZXUd|rf?Bxub%O&M< zMDbTaPvE4}#aq-QH1rxhyhRUDY!vP&L80Di)VT4sjg#=ycV%aujp{|}TB&L}%2^H& zPY|cG`%C6*+`VIS-H5%02&2=Hv*3>p+Tl${ss_HL`2Dao^260Hgu@&wSr~(|ZQ3cANd0y_r|VWh8Wvsb z9Q0ZPZhaux-fl_#L<#T;G%5k-#48X@aGU#-#15x1t zE)vjk0Mm54U}YdPB;Enhqe6w>Q`zBO^09}|F5vz;{r*QNl}@ObAp40&pZWtlB555$ z4+B0FVsVayvdG>gZcPL5B?w}l>^^uv2KDtN1d85;x9SVajk_T<7Hp4KaY2{~tx5e- zRS9Udnhc<_K!ffI11gMF?m`vTOqyfZIGkWg&6aOBvD^ln-g<(6j@uAYU}ftA2sTkq z1e(5l)!LOoV?EB>{qa!Bxaz1lZ2fyo4T=a$t)@yisr=Owpt2DBI%xsVno9R~t${U? z?ZpYnR%l!H0ea_bhW;^>98V~_6_yn^DU=^r+j;@>&8u)HYNBYvT>tyueo#Q;4s$ek z^!MPAb-MnyAAZ!w+HlFbY)9ASqz_2x)H_FZ|D4u{cfJo6*EeFR%%?T-D6&Jo5mW9@ zYvhMXjo9i~v{wsbpJM*G3_RCQfQDlQf9T zFS`#|%uD*tJ|sCg6vd_RN*GXz5oDT`63q(_;a?T`id{>lvY%A6`Xtc#TNZSrX`Lu^ zKN}2Bh9$?%nfnks)P{8wfU(`@M~pz^N0O70n~h}9NxX!#%{}lOLw;`Do&%Z`lIhz^;dL$R9ChID7 zhaMS-KG{$lOgIKmI%f?P@>}dpUs}lq)aDTPP_^TkVkKh{xj3eQ_%jn!Y*GE4ooNN^ z{AfT(&%kdx))lY>%hMd3g&0(<2`1pqtsEaxL*+V$=SxAHzDV=HlZD&Mc+E^Pl2r&iTO|r z)6DJs6XADv<8`eK0>aWSHx6s#UejC6adeVtD?zR&(PN~Qp%O*b;xP{3V`3Ibv)so-!)~NRD*8A!h|0L+Vo_SmAoSn++ zbH3hAWQgBO;Y_(tQrmh#gaiD}6gWF=KdlX{6uNQUX37js`#zz3u&U4r&CWnZ_?P~Q}(02`MgA~pH zsWmalgX5CKxiu2tG{FZSw^J;j#6U?sH-=YZVbXOn#LhsE$7htah2_?;SVc^&bHc>@ zZHkkuCjY||liCfWJqbpx zqhzzG#Dl-)=+j6xs|D?8k2wk&jiWx$CbcuuLaK2Fj$W);gfDwLhUcBtYQz}=ZG4OLq-B0(*|cVHTyK70P^;F(W+kZ(%T z9t`hqB>(7tqH+yp?QqX>mmD`L-n?X3Zrp4)olL$t6oy%7hvxSurx>~KH)TXQzSG*- zSqp~9;CBNQoo@^~@K!E<|MIiy^oO)9a7(KktlZ$|kfP>s)=Ywc^z3{&5vq|Ck3>RY z!H;?=%m@C(qR2F?=dd+ReF#^skqML%idE= zkr_S7#!Bc&Z|6NF56wDQ;)E<%>kD_<%1`>t>}|f|5C=sVTs!RweRdp6(Nh5G!H{#4 zumevIs66UPdnmrku1xg6Tc;CGycH@Qd3!VU%tzOolaC+!*i6%7AA4)Z-bPeeKZXrO!XAEgAgOKRnn-&OxQQP;q6($mb3OVg4LkI`LM^bjL)@=n z88&D+%QTH@K;tfWQFxQ?{#k1P2}V1e5Vat~_JB_20O>xI9vmH9gr$H|u^6Xrck)3u`niO~;=Y+;g>r2a+6M(35hqJ~B}l!EkVk_?dvR=ET`pYV|!? zAGA86G0RJ6YPMv`=Vy49DEr|x) z;ZGwOh5DrpYrD!FrNknzf6eiGBE|4qVhuaRv5o~&j7Cg$udf%Dc2TeG)?i_2mun!P z0}4BEpH)RNcs3A(B|t@96&&&q#do%KI0o$u=;)%wWn{(eGtZf+if&uX1sUV~3`*JJ z(01Xv72PK8igLVv$=+QjD7ww6s{ky!)REx;W`(>Sg(Og#F$Mxe98HL0|2=LG@Fk7w z;YB(_1LSG~@-RuyhhQvIw0yoPMPNL`3NutSj)qS*?|JKlf>7?scBX7Zn3x_pe`rAA~HAk zV%xD9Y4xQp&=M4tr3++iWEG_0xK>vKPdM;`^;J@BC@fDGYICmOL2Hg>PBX^Blz_8BnfM&{wBYOIW4dmXmvix%Nb9Mve^5!1^->{gZGNo*&1CoyV2 z3o$B8?KLPXO(if7k$KDA%cDdX)j;e`KM5b$C{+MJb${@cpH6vSRQ*}i{) z(ef2)$TvWOf0fm%aPx{np}e3@shi=^nWREatmy6bik;+{!CWWBotcTo7W;|qR<5r& zoXpgp;z)-RQn)f4q2x(R$<;M2q&L}Y8AZQoDUXk{<*>0?Rh~XLGt}p~fGtuBQJ@(Q zP)0IhW*Y@SKCGS7=w7B|PzB=44n$V2a2xhfYPg(pXyK#-zb0KZA2~$DKy-c)JyuRg zpyi5=ENC_~xyjEGEFk||W2UXpe?Ur4iB& zc?ZK8WmJ${csaNBX6q)=&0K>otNUhsr~0+sgF_AkVx6SIm;f)|9=_q_a0F!(Lk0LK zPDBU#!6i4S#_P9uHF4`Tp51Q0$NF#jC>O@WQ=qVh@K^O??D`DO9qbUzX95h|*PHX? zFeop9$L`)acy{>q#T$h)aS!uOv~F*Q)QUL()Mo!%BX@)m^*ARBS@nXBZNqx{6XkqE)jid z_*)yMu2l%n4wXK1^;t>Z4AmgGDO4+ra2LT>#~>qSW+La6_{0Hr^0Pv_vD(2MnmB>B zpK)(Yt%qap*Mlgan*8iyVC7NDdWioTz$9J9QW|tk=qinR_=173C}e+x$+e;xIuA(hPcTws4}qq?IEfGMAK=u(w6BtOqWFQG1XG%C)zWLO3O!9&TO0yX zo}@Z*cFIp~c6>B>BhsLm6UzB@45rAYgK6?-)c+o4Y(57(Ywz5AT^7TS6ArRs?f2(7!D>}pza{`!s#o^ zs^O!uXWF}aRCcOA->+7{-&FAT;{JBsx1GE(wIalU;=UpA(`uat)KzrjNqtDxsAZ7= zq;S`0A7AD;DL+bZZaXyP+WCGX**S)eDCJAgfYv>9daZX<-k8xAgqL8-Vbzxo$v5h6 z-u6T>k&11Bz3c95K#`P(Pb~wUQ+#G+m-1yCBGHom=5I}A@kRh*0X9~HqjKCb1(v2B zkO{WwF?3)8kwUfdGsPh&SB0dAK$$5(gO=GZM&r!VLe>ioe|}L??@$&DR*yfmzfq>c zus25TEOy7LpS7S7Q;C!%o3x&hisdVsJ?9e!9IT2idR~hW>S!j7uUJoqhq2&){BN1J z>FgB`=MXX8^GXO{o6<`(Jc#6)oV`TnjX2xK({v4r(a@aSQfFu`@4@6S%gfru9_Em{ z)JMh>)14@CB5>-bOOysnj9%gzmZex`+$OsSV}-bsYg_>Sv+Fzt-E1h^6gyj}a>c3_ zw_J}=#kTbB9h%G{03uMncsfd)RE?^lYs=Z)*6>igMNhgc>J_Yo=;C%!Odo}@N!@kN zsi&+Yc{`EyR230P3M9MA;qBCWn@ekOop(0gIyKg4MP_M8GZHpR-sfUQL15H;EFeVT zLJv`<88B}x+1r0kVvgwJg`gej#B-ge6gb{_9jt0_r?kZZ)IMFHb5$j72f^(Rtw}x2 z!Hf#CAhlwMc<1G(OJn1PAu675y!Y?ja~;KRTZ*(<9b`oLh%kCR@j+UsB!YcV$aR*I zB$E;-MTs)JY5N=m#}ZJc^d&pztcovvr|!$O5)N^fCL7xtJXU$&P_Sc`qI4PY9=M4T z4&o+ZC|_u(GuEC!TMj1)Xv@Rry;IyRG(;mj)5@rWz*A;<2Uip+#WIWw*fsYMXQN^I zA#I6&stM|BypgC^V#4=^`43=lb=E;FEQ#aA5Ll6#u6DA;zeUAuk>+UmA6!!da?Yq*$77f>;w}IIB<`}q zq2X56iG1FP2%HvN*nB|1Ss#cQ93L9zmEpKHP@)v6D~~EPEyKRRD9*{90X*1 zY_v=_0lRK3xsBSx#YMKsIU1$I@s|TQz0d`zxEyeKdHFB*QYMgc3QneD5l8^ia?1+B zw%Hi)m+jt4YU^b8!%5V(R0ext+wSjVv}i(bxYn7y0V)<$m5mSXRvGV{v`$lh68BOfIW;r!EkTmE7&lL{4Uxc(#s`Kg|CN;0Hc#8D`GjROXgOF3ie!LCz9 z<=i_|yY{OKl{$#=+IvVv4u$}@1g;Q@RFV}SBrmfjFX92;N0_X42Uu8qN2oowSgc^w?C36!_c zlopuyyv^pJ?t4iU8xvT8jv_2lgr1l(@=}U5DI-_orr`* zTK6VEPbE`6&zPq@4td>HDj?D{MdY%0mJ34suqIxZ6pII#&ZJA;px_2mk?>F&%PzSL zOQQo`0~Xqax(R9UXp>1s=NJkN#2%{PQ1V7jGX;nb;MNKik@K7F4`4mQLn}iCHv~TC z6sQWU%mO*g;#DAB<&MH(AWU$U4V}2a&f5{o#E1&xlV|}RK8f9klU(*X7P%r*UCsJS z>Yq<(pUO4dlY#;0-Wzf;mP7iK45^la_-v7+!{KUb#>?>0Tq$IasJ8j|nNs84^fxo?Av&DQk8|_R@aq<$lVT zNod`m`R%$^hGy_-KdGbU0_!tnJ|wzsKKyoF^P$Ck+Rt^)#|W^Sll{7`d8L#%?C-PY zCor1Y{PU>0UkabWd*}9@mhkrZQpS4(uz>ssTTll9I|NyO0e4Q)pkHi3eFefcy!(K) z;veKni9@?KVy39lN8o9|*ZMQ8?OhM75C0%n%J+c+nLb=J05;#qJa>-&d_K%P7k^>v zO@4$uX`CE;uRpcKl(Pm>?!W|J7KTW-|K_61;fp6&I7Sz#--hUZh3_p~n&3wH5xy0N za(Okd@WxZ-<%z{L(h4$jZt+qBp5epP zBZ(sjo%%Ch!U-^Z8~B+op&|@lgulZwn4A|o`}`JCy7(qnPeuD29?IfZ?tM;vZ+n5V zxi^`5KJu*_g!`!_rko(h=R3f2-xtOY(|hvA6+M0SiOsY7A%Ev&H+F9A*TOR^y90=f zFDuUy*`gO{LA3b01JIBI{|qrzj8%7Cs)*{oZhI3 zzr0zvvrbDPFDwv)z3_bm`oh;S_{+YJ?S5Dy27}r6(^@El!>|=&%ZpncmWn_`_}T+R zdQ8LDF?h_pk3nQuAP$qc5`GXNREA|7Rt?}1qZRF;Aw&=@=z^Cf;djktai@DWtBk)4 zjZYZ3BPu*`)#M0F#+vAL@Ew%)Gqe>p7qkcxZz@?o8$4Dtu4Kc?qt{q@Jl68U2bC}< zxyNCRfGx@KBssRj7?Rv$ZL4g7GZ@RwMNLg~WzPzP(q?=Xo3HVf*tk)D+&Ddg`9{Sd zzx!gk62p|*uYvIO_Dluz^;wIj*?NYS9%c8J$-)xO)d-c%<@Z4B@5k5;5K|d!GtMvu!)OS5&3Xa9=D-%=7~mABfmP z8xJO?XD(2zDD2>z#Xf>s(`e6`*@*|d4CSyjI;5H(?_SdauL1!7QO6_JC1SI%n@#hb zTAR4kIkXyNP&l(var4Cpg{3?c$M}~u?Bqq#uYZ6~Bi8QE+6gUKv=3*VHN@lq1fj3^ z-8+AYOpJRarZ8?Igc;Wr8gRkk;9zLbgeS1DfqkM3NudYUISd5=Unc~XqYb0FWMbqG z0WWDn4=INjT~Olt9ma}DTdsF8hVV8*m>=!?O3aG(c@e~i=0P@+CJY&SL+spIvj1f1 zxt}?liAE+{KW=PoZ9LlC+M-|n(|+UhBi8FYIcyD1TJWEb8xbt<6m|!KWIKo&c*3}W z^yV}lY)oFB3;ay6HQ@sKif!O^*^GoVy>TK9DCU;$r2eh|>ZqAu&(d1jfqb0o|FpeX zupgHOa7zb*HydqzWO8x5F$Y(DgV0y?7QBG~R(OIi4~4pE2`6GeiLG2C5QrS(vCQa5 zv)Z#u3DKyOO#AaPk6aTf>%gQVRuZML@Z3rT?uzifH$Uqd?WN6Sm}!53UlL?u!%2e+ zK~fR_LChRREiTHV!$R(urze~Tj|a+ig=W$K7eXHFB>VTSBU#K8kD*g~y#$5CibkSj z`h(g^p1ROi7K;;Q00QtOc7`_&OJXz~3!=c$`05M+0)sl2?DPfEH*n@t;26WZ5+h&2 z<738{IWSkcK7xXpePH!9cv?0f9>EzGha6h}Aahgy6Vi0JK>X799#FoROp1{nq1^kc zVizZvRY6Xnz)-Lxomf$pbkWLCOO=5}1Z1qeeUc9s644Of)nb|cKt@zub z9WGjN(G{hXd!eN{n^4gqTZfW)4;doYVJ+t+tf+VLw#mpg+HkVnd;rCs1LB!>&fQlx z2B`X|SXF1Nh44#q&*ETtvCH+b^8hnCe}=3iIGv}LWXo&Fwz}qg5VIwWwHJQ5gd++a za_F2N;+pd5UNn z8lm3UStn@lY-n&^qhPKFHWyI}Ea;yE1ssA`ew^qbeLB}6jq#S*W~auFijFvUFmoKC zok>h6=4*BHXEMc{h$~N&i`WIc1zL1@-iE}nA7Ai_f#KZBhXRqicWisJM>%uX;!O=X zkS%ad*a_2SC3W1_k~R_|dwCn~U#MduE4$N$6)~tZte!|~G6DVW-fD+G7KiwV7@ur%M0QvCm)zBB>B4vNgmy#kOaq+6X#+? z(8qL796g{Gn_X#daFAZ3^|xp}iya)SW!9WP@HyB*DIsU0{(#rYwl4kPM9!7F$@2{O zV}k=o(T8Q?HUNgmfgXA3xor)6`it}-4>JNNLE-=aZHUMl@;GI$zb%00f?glM|BTkZ z0KiisaWl-@yfJ#7Q}iJL5xf{b783`4Mof~jW{qvPvdSK+6(l>Yq)dNXKy$j@R-vpkadH<*B^rC5PBXco02;82nhqJ zV9BV3@pGa>ZxDrN*oDy4mGdhJmJ7tqy@4dW))}|tRNvOCs72k0-$2s_~^8_W?j#SYUeen&*i-UrrB~{;v1SDc?o>c3D3qq%l z`wF7dTw25)YP%4*vWW%-bK|Z=1wCS1AjSgX&69a!E56+XTI`+@FHk%H(s3JtTR45! z5k&_&cNg`@m#SfRosg8<6x{*UwEebWOsj)J7-1S6;@?jv4NH~%p zg`O79h98JMOu@|Q`C{6%45S|fj*)JQIqnLChy<{ar4<($|1O^=-P zv;tpdrQP+7MsJiFC3xoLF%;OO@(2pywbH;il1xIPr=?F(68A+fc>~)+JW=2a>>zu4 ze2gWMCPWPXaU2Yeg~ViIL`X}XOVSl}`E7NJ;hrwrEAGJCU>HP1oczvM#`?t?c#wQ|5?{wTcb~jkAM-` zS}C@He?@(08|;xzn0I|s>0hkn(5l4nsWqnM#aSx%3}M{cBa=u50Jf0LKt%PgUFSr* z?e~^kDxdG&r*!6~hvW9&Ekz=tZ-o)K+V3q2bKylmX|$F6tYeR@pW+C8M>!D0tRC6B z!i4Qb9xyW#wvCg&Hh|Km3NNx~)EWcKBV7KdBERzpqHyY8jYmk5pzM(|fXy9xaoW)p z6RRc|Dss1e*K+vkq4#guArg2x*0ku5jNDSN=L=}nfu=4fx-xE|IE z8gy4BbjY>CWuv8l%VKJy?r&2;)x!SmS~&mYe?%npI=6plem**u#d0$pfOI{VOvWfQ zcS|(&m>8QIJtmK`$AlCzZ8}{2MX1nk*%b?8xu2cq-vzs@t9F(zf(h%ZLd#lP!KeTW zuW03*@=n9b*2c!ZB1>;dm~NJ5vb=*RX|*J*Gs3UOOqR$F)b9#mfJ(pETXt-8Xp=fA zedigT!o*S_-(iZ)&jj(@iho%UpPf^)JD6YekP!S(gmB+-2;f3XffMqG6NO9hxo#MX zq-kgZC`jSK-+qX(Ac?u3yn?vr82QeeWEM9P7Zn%cl7GGYS6UH;>-Xumk$P$1eNy3* z!`DBnCe1)uTGy)-zZ*m?Krq628-TOPV0vK>F^aKnS5g)XF#K&H8M-}`0+(h zowX4*XQKmWI155NkB|o-ZJo&;vRr)BZEy$K`jTPhKI7!lAL0?$vi0fT9WVa@#n7TNhmj&BaBT%Kuezc-kW zZ812=pWJAp;mL!%SlEFEZF+0|M#BDP5^VH7`y~7Tz1aJI!&!;)7QK5isNz zM}ULm1cv?8_V|2G%@(~~*h(1B7h)YJLB^Py&Kt>lsA=SQ&MTBE2eRJpEL&LKnFMml ztE&|Bay0(`(>M8A-)%=RyC*eU#$L^^S-N299s%Bvm@`kc7lOh9l1VW};g#L^GeB6p zTq|Y?PW1#(Zxnjb>bfrJf{|JaD`mW=gG!Hms!0IfQ5ef6Gc}-2C5Use_4)JrWtkp} z&pMT>H#F(jHRO4IEb=_oaLDp38_)_;5CgPphOJTn67)o|nm2ngU`cjQTZjMv|Wo^U#nJWInxx1?zftfug zvERZ39J&EJ4BgI?N>f4-pjb${HQ11Lzdz#n#m{xEwe~*yOp=f#zzOP(kw_5N^7nJI5IKV-`ZMCrRj4EV^5D+2es)+}{JS;ZC9ZOQ+DyZ(%+LJz5JE9#Gy3MOLGA z$Nrkru$}c`gV5|jI7CgF_i6V=19qt0GBb$RSfDa&z<+n3GfQE3&^d(mNUw|C&M&{1 ze>|DG12wr9Gj-htT+AY&dNGzQSI+BTNRXEUmMcsPQ19ReWs&09?G9`RK68XMoi&e0 z?q%;@W$L&oa81s}++pIwa6l>Y;0uY3oEG`WO=}p@5thwEGYM@63-eQibM&qLo5nxkJ6nD}3clK1 z!9&kevBW&G;Jhb9H*Y;n&=nW-riC2PAMNfe*bL4iELR~z@Xnq3GDWGR zH8~?K`#^w^6~$=05Qt)OI2BuZMj{T#4xT%+aC4LhAB_cAl05{!JY|wvq1XT-eI~tV zkAvaI#>hXewZg`Vs2sL4!u78R$17=dj{XVL#d`Ofo=bEp--DGkH}`e1lzr!Y7p%Ult# z1MH2T_*)V<2fHKv0Nx-P43w5kMT@Y_^sJ8S4&I!gd%s8S=5P$t*b%4ZWc&O~U`M{> zvSvi;fDN`RX?PY3<+*!~r^qxA?4g|+`x9%kf_2S;(Nu#eD4HJBNKn)UpvS@=r+J6N z7^Q;{Z!qc_zRJP0;uGk*<7Ur)f{d*el|DRNoeOOgse34Wh!W4@CK89(UH4+0|3Fx3 z)ibbj(6-x-+A4aqDG{fH%Xc!27dwGbAno;!f z6}&MTi1jGV`Btm}v7wR601N7W;pH?qyd1KOh9mmg4STH%0X ztl5^v8IZOzEHVdA^Zw?+(axc*XfkoPo`2e<8wRv(``LW-!U&F(rMbP|VGOl>aS}qT zM;*%Sj7sMc!2!6Lj2n7(-uqPJzX@-29wVIAq=F3Ina<1}%2ymNEG=$}_!6(rC4z;< zVF`rSr}!ldslX($aa)P8Bc{bWl%~%-g0w|H;v}M{h0N$GEr`UigS32c!7#B~VNW>1 zEI~)JD}1}4_#Dzlq26&3O?9tP9Zt$soY*dgQgeao(3XWkRbQ*Y;R|jh#v|tNv8E6u z$0yB>v%Xh=1ATqqOe(Hz^{$+K_qZ_d(U4dvyeS}7)YHvHlJgH7dP#Y;xu0TvqN|M9 zvOoidSb}!GvQQ|x_MW(zkJep`$6k#GvD~TGd>}&;Ms_rS5@J%oX9D@F%lHe$Pss@; zG&2LDza-$Cw37G_Maud-=XsRu4PvC&q1`kABb) zW$D|u7=j7w+=X(8+e(mcAr*;$Oy5QIhYtgzw19+U&*If47#i@&*V-au)Fhq&9P}7027KlM|Qyg3TJTg27~xqu0*;Am=s2wGGLgVNo-J_N6mY zendejfxC6$ULyoj@**LEO{1o8)XV2wVJm4zG1#hjY5WMene&m5mk8bv2SBzY#Som* zZMJ3k!Zm0z>tsbG4h(8S-{O0qAijI?QO>>r^vJ;t#DH{P0~^%rdUFH;EO1^Lp?ZQ2H6Bwm-D@?%S@{m15yd==c()N&9!4iJD00bXo7 zI63^ukBuZP{|xQ_QtJS@pn@A`qLv4h(x+QHOUy35XZXhdO_IIZc(W+vN#|RmV7tOY zCCOazutcn0l7OV>PoZK8A^~ws<1k&tG4H})m@fDwBAq78IQL=S$q(5zyQOLYsfylQ z4hVIY4#izviXH-oGTcGC0Vj5`o}`swPEGbRA1yxN-+Gg_9)>4DxSHUOT#vU~M6@D0 zq}Cj{#(FuQUaJM85tt+~J_dhiRFs^O?okZK+!OxA&>r(|3p6y1%$NJks+pUcPkwUg zST^rds)JGUEPyCvg|-f0OIQu&o~!)*`7=Pe#KQUf`TLiQ{h{%0iUu3WI$G=w+Ua%? zw*Xe42%+CBtn-XLA!I_b-p+A!PinO$ZgRf~xqdYpagq2* zgSdY0Xa$F#0!$N65%iCXWj!D=wBZF-JHEixFFhy-qUy+s0wW}8pwRyti@Es96I$Lw z%I1N$sdq-ONVOd8X!sfz2Z2N6QkkJb+-mAwSWQz;nz;k*v8R9*llTw&K>&f#LC$w7 z1R{(`lVK!dk)lX5tTf??qRTyoJaLgr;x!X&dsjnW*(5mQ;`@%{qvC{aCk%llTRDE9 z)_q;=Ep(l{niiBXvm0L$@(Lr5V@@2@dWp^x&R7TgTSbv1Vpjq?D3FhI7}Duxj;BAC z#4A`GjZFtiI6x|L*w%e;JM|NhlWnDsKbQr+9Z;kjqQDzY3oiC)+#m%bG_@v|WIiHB z8P;Ikg-jl?`2*)}P!*%=HQh!N)77_r0ej4g-av?Gh{ z5Ot?J7^_@J5DrL{178!F-2qZeiL}w2VBE4mUA4Nr3K_x~awv68*x0D|AiPn!>AY4( z!d(sUdckBqxVV0$%Fzj?Dq;5^ZR~@3D4!9P_cB8cWT6byh6FvcGj?Rr!T?H{C!L6n zZMRA#P!A^Qdz69!UHrB~FflL+4LKU_t@m0R5J=fm~@Zvdqa=+Jt%&J~dtl%Mn3@lFf>p~^Z9kI5 z5))N-ApBTah<-Rc-rro`agy={5Y>sHI8h3lGJ~Ylu`cQWx}~YM&rE64?Eoeh)9nBm z&8FHxd&=!kdx56CKGQxyR5ea_gQwjAU=Jc&PI(q;)G@h*3KO-*@J_(!7r?m~`k77X z5Wqi$jOhmYqxm>pdW;Pcxzc^fI8l0BGR~5o1G{zD40w}zG1xplv*$NHnm;=P^}ky zn^rHtI@$@oOsfySTvJ>vzgS8c$&d>i}R$?7V5CTkj-#SFQ@4pZtF+^;Vi z2P2reW5e&^$?z#LbnC?*IDDV0F~JE-KhLk@g`LuRTt}aWbxc|%<@i1EPK#IF931N% zmhs%Sw4lE-?}got3-p|egzT2qA;TS)_?WmYED`Lf{Y>{_Ot)`@zr!-Q+@qAvzDd;V)C~vz(=CH}xh_&%6C;Vf|?(hMZOO!Q!p%YTd0O53@YDqxA(TGL7=@lW8BhWCI<2c|pAFYd(dh`^=bVr5 zb>0a{ywBOo#QU7>4BzLRlkoMB)`v8nb6Ud2^Uh27zMXSo!uLgICM;8QYQi!KnSyhJ zJ0sFL1u6jY(7?eQV-hkwZ7dY(>67^prFum`uyDvz;YQsofQEs6RSV(jR3*oYxpLGKpmBkdK}isp}C8eJAf=YKQof!^ZDqwokBWp z4cgfiaC8$kFkhCNg=N;GJHpfJlj^@onne>Ig`b_xe{Sx*-rRq)xVygp)6x3=4~L68 z+yB@^M6JD@?Y+(Q{pjHB&ObL1b9Hv+hs}eH{q1*$+i&+^ODv{ju_=i^TfeOQTDrtP3(?Zj(n56k3tVk}ox>YQ^<9zGVMS2YmEA@4yauH? zW-g9)m-nJ-i;8pTc9D&^3kN7T+*MjgtX8kqmLfPvoweZoeSQ(q0v7hhl8hMxuhKe} zYIGR9i1{&;aB!yjL~C7dQrtD$>Uqsr=6w60ppM=jP&tpqfa6uq92n=R$P?vVbje7v zEJQosT2#BN<3e|7?1RV&XXhuae&@W^sz!UI%kG?L1HBnHQi_S?XspoRDqYTrgDbd( z=jW;e(ck4M8mDP*z1c_JF;2!E1MeYHFQ3rcwi)fYm-8@zW}BZs(;JZn0+u>W z?U;}*lSam@0iUNOETc|BX93qo&cn5ZbzNPM3HU}|RaWP#m~XjRsg*7l-XGx3?zP}L zCQ47VB}w~W7)`yn8bMreWyD_TQw`b@SeG+7X}`;Sr8F#I5MEebVJB&y4BLzxmVA_WtJX z=H4N5i^iu*tTMd5*PkThjuZ!MMg%s~cJ5&dKGJe^&;|~TGZCcO*=@hmX1ov2 zWLkqY%@q7S>q=HTG0jc{5{*u=y)Stq|GkE94{@s=ZvDawCBNz9oN9`*J33f@vxy3D&a7GOUcZTY>a7umE0UTK ztHOY31AAGTgCU#!KIpY!XpgqM`z=zpVkmBn0Basu`{Le#(ZIfL+W;0oT~NJ2&B$E5! zd)IBDIiTA_9H7TGaJJc%VZ(4!jMJs7(D{0H=E+h-Tb)*uAd11Pu?VovlL5K9)Ff=d zo)HtrP<5#}FW>|X$)o4xyx3mh~P%mmtX8K%+_FlPA z7!LOF?>zLh4!prqQep?dEJNHe_K+6_!F8LwiVQnOJ1h*-`)J4QNGrrI&XQ^&%pmFY z%iJV0EyZn-MioHrT2nIcD>#q9_!K4dW#~5KS6${tWoBISg zZ0|sBpxR#g1+;Ss+f$hE1m_(M_s}2zM~8j3f%|R7o)Isp*sHdah)?5GBrd7HTXMy* zxHx`1v;NQ%4~fG_?UIIRI5u{78E2Ljuk6B=#wCldxEiGiL<~kQX013w&gsZpZ{+%M zCvmNL?41{pT2=D2rD2Kdlf@Ocd2sLzAQU}%aQpyQU~`j%74j2DP9hp$i-DtnSL?9X zgX6y*Assc25#SU+0n(M~F#=Mpbm}K4N=hL5W{0jn$d7%txLfXk0O?c_GeFC0>;Nnu zHWLfTDxS)c9ru)$g%1I%jir=qr=F|yxy# z2ZP{N^mF;5+&!-&1P_7KZn;}-MO*z3eLTC+XU<0n=lx@?fp*|=C5G7p48z8%yUPhmPGd`YuW`(H+&?bR9=CGZs%mmsj` z)h8G3$#CZ)HYr!snOw) zoGeBuzAJ`>78A9CqTOB_dI(oI(;#Y_*%6o!=XB*9rV^`WBN$xaq}&DF94~cTNomgG zoL+u|rYXNp@yJdQIsuiEi!*R&9Y&7#!CUFIx1|iiF8CkL|aYno)!t9YnbUwFapSwVwU7}O1XD+rfEGpwJ z`WJLq0n#LPiDgw16i5rd#0_B=t{e(x#}Le9;QqKkbFsU5arimPg=K+x!)@lk^Qe{z!C3AXQAnIHK zv(W=XPN@~q0yq*dwm^<#u;IG+O-_7;PYf4_vVw-fImp2|0s||2Xn=nqq6E{GVz*-= z7!pu2+~l~8<&I9lbI;D?Fx^COp8FNkbYY{xM{8scWrw}^Owb($3x8J&*0B^NH^f9e zeUtY52DE2wh`5)cm6(N(O2X~|Vfs&WDy#*9zsN#Z2S9_YpO+bt=;Ep?X(>;lYCg=ss7YJ%Rbf;HonSd?dXIxGI1VlNy=p*Hi zO4ndFMzOW%i?j;FyW7!+Cc<>W)3woReE?m5MnU-r00kbAN1;zS*chZqK)fEO8AGhf z<`v*6V2^|X*m(Gmu=w2-1a}lWajm0y_g7&ZqbvoIYg3jXjLMY>WHA~V;xM1Y6GNt z5UoS>3$CU`fh6r|w@-~SmhkJW1`Ouhk^W;fFti|6rWHeu8-&7%a(R?x6J;+Z=a0g4J=`~3 z|IiDQm4h8xTzl@4fy8fY7alzg+lm)@%_sU}65?eDNg9uI27ZJ9Nq za3qB3Al|5UKWSAx6gIGwDf>Al9fx%s7|P4Z;r1;q>I-Gn*O97VKxvjy^=vQ>qJ^-OA@~uhWma$;M6u0jOrq2bbKEOT$-fs2I9kip|^# zFW@mk61(3bO>+Xfyi~1L56hCW55&COn7N@!8tP=jGpFwgbGv0O-Y(QaQRND@%c(sG zFp%WXV8i;%aL>w;gN8BQ0Xx6qMyXhbzqnE^WtCegNeKYfjLrVXy2e+(9S&6$9iTT5 zBRE?TJD;E_Pn1hWpa^Q(L8ppjKuiQ8JalFi=Vb^k4@itGy?@Ocfrt}v$oYBI8oSi% zTAOkJt+5r<(2_}(+N~pvhNN}(z+m{;fUEvn=4|J{jlnR3_dmCUw4_l2bKpiu17i>l zm-^ibVWUd~ZYNn_t{ZxIQsa|83_1oS6^$excqq!$9i93m0x(0B1@)QJ^Fad#DHHw= z-L88^4I7@gpMm-ePm{<)7rEc4`iRKB^e9WY5CIO`5!Is*;QH+*xEj5G-%?0l#SMU& zqu!|C;P48)Fr+i;74p}%=nfrF{u-73{_n&2zef+F(vEPx4|n@>^V@UtSJ-I2+H%9- zeG;Q9N)f9s7q1wpspI46lBe74zZ8`fSTu-As<1PpuE?8EiOw$2*NK6TZ+S6UV6bV-vyt$v`UVI~{D-Ec@MQ!PrQpw-U5IG#6baMH;Bl3u1=mUGsB6ZqDe9N% zFIN!z{`;4y82ACavXkhPsLAsn!J{25WSek$(&&nfU*+u5pC_*(Hb|O#F5OXeeZ_?; zXD9vB^r_Y91V@b-gT**e$o!}TuHOZIMi@CUCKW4E6-yat_9SJ%8n_vawnFZ-wvU@J zT6A+ruU9GyK(Nr<*I*0|$++!40k!hM;Q>6f)wVw{z{Vz1;35kUUt~89SxXhkEfjE3Yqt7l=l`EO z!MKQ!s6*|y;BFKyBTT$83K;E)&KAh9hkPs&=Fm%Vw4OAN3T%0SSkq^bw(Ch$$bhtv z!uYDb>wiHO1v4et55>MhTu@@u`_zhBl}edbh|W?RT~c9+38BZXi;yjnaEhdB?Uyev zaa1AhX|y2EA~2!-DJ&3aq>iB8m99UjXi4vnwIu?2H!!=^Q%9K60U%d=#`5X(^leVw zgnVKiA=2##IecQ$gMgX&f=ihiX#Lj(JEu9kNF9y}ZsBp=g0s&XSs>JqR|799Z`z4# zIB3*%5e;~N#>S9^!j6uQuD(DV1M@njztn@p<0{87Zp1@1HsaP4=*~elKA!qX(Y}=} zL*KU6!l4C#%#&DYY(6#_iM}wm;K*3dDH_ia_kI8&jrVB6{BI0l$v-(}>C_!SSrmWZ zMmZNrlH7wZ05pSK1&2Xa)d4((CZz{4Rt8VA5O6_hoXais>y_Rx&hdYweFusMw%>32 z)~|CRc7%o_1dO$ar6$7bdAnbja{NiIh~EmzU`au*I=PMRzK1193{|Os;}%#D>L`eA zHR}UI7;}3CVexVDgt?c7X2Q!H+-Av|H0OqR2VJetT9OHD{#HIG}Bq_(!5xsQMuUu~UcgZgu6aDv@v?oCw%2K?qzQ zCxL5nW{!iF##KM?)- zDo@22x0MwOrev+0Z(;HLtn2<54wJg=wLU>Ok0Gd@8CwmRh1i(T+$FW$zE!HPcwV2| z!7h?rnvb*Bdh_sPj`nmj@H%ygTR{1XIJ`!lgZ21D66&UPC^_XMldiKZNVb<&%>p7g z)qk>JRv(L^N;Y@{g~2ssv|1xDUKm_QMy_?7VZ-1GkC-$Vw2=Xh!G#~Qus4VpT=oge zIs)BVPoEOpIR0;vcA7L!be@=c8sSGdl2v(ZDkc*H1iVfbtd*suJ@}m7fmB$MZ)$UY z|Ly*_V1;6*@QCSfV&8E1I8iEyf6T)PU-Lg)4ti%o=%5_@`&0A40}xTqER(~*CNIY( zJ#6*VJax@Y_d?us&6GvWogra^4i(65Xj^G%q{CTZfA~Icxv&IVoz7_*tm3j`XTZwC z+0G;o-fj?pzzfq^jHkxvemYMyxZJ^8UHmqQubFyRZ3PWZyYezUAo4HFfwi@np9ISj z%H9l7@?QnL%U9CiG$`M2Zhi2a0KuUilCf61gz=cvnM~tPD6pQY=tE>-kWWzByp~x_`8iBk)r)6)%_4y&Y(A!2sQ9C4I}Zz(wK3&wSb z;M*$kGh$$hw$!dpG9aQy8#uvMy9TPNcg3;AY&>Rx4(n%-W1BP19X|FvMLbl322(yU z6F>QShDrF|fi!G6($cIr*T|kw)ry-oekL{oSs|4~o`|MIr|Kytv}_hQ)-g*!ml_tx zG8S>!)Vt{b0S+j0&+(5Hw?UM)YW6R3O%~ zF}Cti7Z9|DAth**-#QVS|GfJyRGKx4TcN*r_=Ul?=(!qj3ytskqT-cItp$4bx&KS<&aieD1 zkk|ScZsH@0&k9mkN`19DaYGD@*7#OJrsTO*drRwuXzd{kJF+$#!>XPj>)pzDRXeEq zBwN+ykkX_qI`JkC(MTqXQ=qao;F)ySQ|uJ2Pv-JbfhB95f2rJ2img4J)RZ77d^^A{ zDPQb5g`L?p#XQ(x(cO^dR@cxiF_IID6_A%eV;~31u&MzQC?4L<#cZ;XJGeebdW0ob z7e^l2l*USZU!1!AqvJ>2<7Pkk#7nH)TAapR zz$xdSyylzzhc>u1Kbo-`cWa6nyRY`Q-<~gFeVmO0zOqcSKkv*A!;K5{*y))};jaEr zQzM%AuF`)Bpp{8S!8dvmlcsUP6zZ*w@2td5WD<9fPGdyY$*%(airrctcIP;y#CCVn zon+l%yKnOd2jT9fyFmhrC^B|0uHK0HBf_tpLel5;d*=L!Q902xFVqT7NG>74aM z9iDB=V>*k=>wk|Lm6r>jmum}>9N4u^FV&Wr(@w4fi>D(i%IMYJhIc|;gn_$#CrY@v z;|W`lNo67vnZT5{AhuP~yx=V?zzjanx%7sCv=v4$=NU*Snds`j_or%m!taIfPi z)&6NTZ!xO<6O>Cus1_eDrgLyAOSH52O5D8o)F~+CsYN}J5Y|EH6RjaSXh2MPt14(m zcCke<-mtg)9==lpR##Vl+4~$o6lP=qJkrz;KA6-Pw@t?H(~~%7rXf8OQfb%xrYLQU zzFjW?oC@a$aHXB#%h-L${G894Y>)g1cOdqzh~L+L3rhrij1+Od!+lBv!*C`JChBzD zuP>Vuj*&Ma@a}{%y6l^9E|gk2NvF$X!r$pK<|X2a#DY#)QM8hhXSx%deG{${z=w$= zh%mx6GVpFEnzmeDy6fIik8=nOmh{Yj<#n3lmjC)>$I)u;;NIy;fk&S@wA^5lyU3}`|{31E&EaI1^b;miOs)48Y z?Qia7c<1BSdcR$n+`f6d2NiIKYET(}ECv;lePjW{*6AfLPCGEcMR%Q$2F+Ra@O9q8 zhwpQjJ}gkQ_+c3VHQ7}Qixh7_SZcr)gvHXEfFlueJ%E=JNl3!|(zb$Iq7O!8e2VUA z(RdYn+kFL4U63r_$e00`Ixgqtrhw*vQAGXeCxK@Mqr=IfvrI`mvyuYq3lL@MaabdP zB11i(wCD7IL@a>{P~H`w#nfZ#t7ygJYpRnsbAx=)=$1_Kk;E%A%C2Xif_3Lk@3cBJ zc=VUA^@H=ZEDFv}cj=r=1$2QMFEcpP<42P67l1vq3q+Tq%eY--k{WB3HO~G2?;+nn$=4 z5yDoXAE7}G0hd_f@W-3Iqv(wUk!nQm5T*+{j-7f1Nf8hi0G301f|$MV6+F!|jcu9b`m$ceMZR?ZGB&qYkiAGUWklOd|zxGDf7Ya=pO} z0!Ro0L%ue9mJto8P0iOwRFm91WyA|P@AcZ>E-wdLv5XyMii_r%eXGItfQM~!?}zB^ zR`Rgr%hAVp3eo%D>ZdRWI|b=LfBH`Mxc%Ym7*-*F!$ZdL$vlTPtLH$>#znnbkr%Lop45j;@xSQs$L)jYU~_{BucD2& zI2_x12l%p!WE<;zxW0p5`>I9YP;o(hw(96?qfZtTAdc;l#6aNhYsO z6_iKma_#f_30%l+PS&J7%qg2H2v3J>ZF`umjmN3lmDKNU>NfysoPk^8NveL#m5W!q znw$WVdH9f$Hm4Ila!1vkRkOu9u1MT=_B{ElhH;^bI1jRp6w+m%J={NWzAe^2$~X=O zE$Pur$er<2c+5cD>>pVurua0fkpfIT!r{da{QzAHl$c)l z0BC-6L#C87pX_9Or^CQnJh!nRS0SJZfv~MprIT5@%I}#+PO%XTEK|nSvV$ye$qKpq zmbttBYFP=%e&&24GCjBrjYkn4YnxEpd1YCXiGyopu<4ziYt709svp1U>5Hew7hZ`P zN(-hIY$zUQ`h6lDJ6gTJxCyizT-?GtS~@44^fndeI5oSoVqbS zLCG61${|Z15*trKff(O_)G2m)zPP|?fEiWb9XS=4JCUK{Eb+`M%iPnHek^a-xP&O~ zA^i}*iKPvtaAD0r02e;a!d2_iF1HAQlgpma5h>;k6+>vyTUUEX*g|aGx7#~Ey-rhrc!L9X;&8f3C z!or}JF<8_5DqO1kH)A{ngIH-GE$A61Uqy^f#ih^5ur z5z7O~P!9}Rt!isIxIT0#P_dE^M%Dk~Ki#U#sG@IG`c)}h+w3l88u4?=zR`uwJWp&` z=52PTGDQ*%_$|s5i$|tl@j+S&@BnVLL@K&S0!qfX3i(y0f7_e{VU+upEbA90Jk_Zp z*-Mfvkl2IbcCx!!TsG09-`3*tei}u$!Hsy-+m|VS?fd6i7=8nN$=o~W-WKg`aMoQ> zzZ!Y(GB?rHO00QK&Fuc=6iPJWH!i1OJVtjTo%n&K?#FA@V|9rQc{wGv9%QJ`2NbEf zn>$G?DWZ7u)f4AoX1=8#mkFzP>dl>lpY|;!tw^(fMh>GcbxKvb1xKd*aJ-%*{E=LM z;%_IBiM9O+n}Qog4!QrL19Y4%aFt;F8v)XW~BoI;64`~k`-=*O72^5Lik z-u2vqqh0c_YQgPJ6p62O__%00!ZLo2iHEuY;iJT&g)g#89==Uf^J^WJkO8=x^%YQ{ zHg;tTidNVirm&P>>D-seBOlgFEMZ>V><;9WN;KnlBCn($Xx_2ck&SZhQzfr;@U`yX zlW%1*M_->8?M!0({7jQi6Q|$03CaFM>Fnw!Ugs}+qKF@b=gl7sVQL@b2uUAg8CZ9# zZW3Q%W{d&}M|$|hBkB5=VyrznoJ$a4#HA=Bd?L6&hz$`CjA=NcYMrX2#?}8!h1`tePjeMc4V)1Qu z1rk@;9u_TpyzYKp!6SczUDK)SYmc*w8@G|as_}d2*4?nL18+I;YtfqO$qM18U)!8l z;c*V@Bo;1PFT3X1GKnTqCpzrP4=;P9wb<}8oho;YL#;IjooXwQI@bEMXbhcm)rlWe;>!3!120Ur)M(em*DJUz@%#9y_-93{?F;xB z#ZPp7{qMxe_@~(w^{)~Y{8IYYzDHaR(J?*9)BEVdjN<1(O7r6`gVQ&OY+}{PCcMrm zDY9^Sr(|Jju-tX}3NRAab1*QJBOVscrP7~$5%RQu7 zX)Z#>Swl<~NC#(Uc=O>n&O}rQpeV^!P9z4Is#(qM)kjo4Mcj@)tSq3>L8{$;uZ60x zOlCS;965xU{x1e}=J2%-vK8=s0unMfQC&hG+Y zL;3t|Y46{U=1?03lQjgNhOKipuC3Y@u9G6bhatN}9{Y=ir(KAn)auIUWVO#NtntFS$UyHyi%#B9SDbK>P{{q5!{BhAWq%=d&Tf=v}>jmw|;Xz>xh$R5!k zxQ|pYVuttsf zUOBH-K1BPg%llZeIQbS>bC_WJChXjFX_U{3`-suc54T&d296GGLJ*A@=G`YX#4M2( z#E2UjvzFEi1H`4a6$a|)P9nJG?h?AuD7`!+yj@INB%8)d<#q>A_G-zwXBQG7`Z^JI zDX~J5+ZtJrXkmYq-Sr@SjZ7T>%DjtfjD@(gNCC0&3AA24|(Pf`X?Pr``WEhiqc{@LdMxMd5UQrxJ#qr2yx=0&P?EHKrheQ_O8uhXLt_99~VC=rNXG<%q zHyOLn(g4aKHk6no(jFjs8a=2Eszbi=RfKCXdIXV2Y1kgJXyol1thqR0#ZkJIk5nOC`251VY zl7O-hvuN*fS0NDOdU|Bnr;W1IGX{ruAntm&JHPkuwY!DEh!J-sZhO3}3J`YXVj0e& z=R3hM(#!xeYJvT&11UnNn>X*Mi+!t{=Yr3}PQM0l9V2*xD+UD3K}iu}M?ydwL?nhU zJR>60khV_8rJPFhkw&0P_d%Fi*w3!C8b>}xz9i{6=w~DpFOgJ%I{`$)lWofuA}ct9 z(1R|#6^=C$aAVJT>b+^G6QnB>lB_nJ^HZu@4S+=%q~`r``MYj zRz9rY{RaW?vCi=1C$&nMRtU9Agv=-5c0sD31qekqG9xeHFR2L=J~Sa26Pnl)B1_Q` zs)u)vM3(*DtzXuvf@+jjf^Q?-vfx^gJ2F|TbX;S-$va2NXB2Z8*?qAw;Dts4)Rx`_ zur@`Aw(B2T*kO5!oJEQ#Q`K*Xb14Ku9DRZY|KozSMt=Zp*64fc3^qSxtB<|V?E%QF z6e#=S5Np&8vv-Md zVw|SIvjRfxivU_oY2{LJ*Wyj%w(bj+`+L$rN(fLIDg z^e%_X`P+kS@k3wV-;DmT_x4|VOA*a385x4q4n$56-B8fgg)cE>BIPbP{Yb(`AbHhC z^0^57fwl=T^XR?_5q7{+n!f(}0@@TkI8g!ZJ-sDZc6Vex(n^lNNTTGOgiuMeQQ zJk2FitQb;!5%7xXHUO_{fvULX&48+`7%gUSOqQlX@d3g#5$!-Xb3-KA*zhHXlDmg6 zfo^LIN#2)GN`GERR6s9H1r)^)?dzhWq5~5HgmReO5>z> ziK6DCdl|hdl>dOjtcq&rg{X3%%(^VA^A8Bo`4QogZv+q(QbS(~hrU`;DJBDMf?E1R zNoeyg_07YCTOeZoG!j~)V`5#=2x5&aG4Z9)C8n3YdMYP|H#b1&j16uw28g$=4!SGA zW`e?)KQ9m|BtyJVX7VuPtEEuXI3a`<-Kz@(Y(bWqNDY(W(kFQ(;4LsaBO>U@86yt? zA>urkr`(x0b7C4G!xsitxo|g>R4QU&({ID?fYKPaG)c^uI#Sp-F#KXu<~&YIeD3H! zQHTAq??!X7Fn5W{;}OIFxwz?~P^Jr2R69MbSLzrKY=jyWcmg`F%GzbGQ(jd62UrSe z?M3y{lu(;hM9x&FIe6Hd)nX0XjTlBx+Wm67dnDUHL+ejWA9G%Az#iNj?wr}Un#B~k zYTg3z1$Cw=)BKX1yrdt(%Unba@cgUZSKklaO3 zIYH9#9s(vM^G@uZgn-xfk}Cv&V-b%6?&6v^5AO2RfFZ=AEwrc1U<7@|lxuZp$j)1E z(EMD`6CiDBgg`r_U?t62qbnd)EqOWTp>kyx9Q2z zgqlD8`GlJ1xkAmXe(0LCXSRVa5^ZLiyLoosOE0S~VZM51~8j$$LLvZIq z(n3;oY(Vn9Cr?R{>D^`$5}uw)g`rfR?<#5iLVdbEp3Xox&54WL)1z(7!@CbAM} z1C4YEH!g(1eHp!9i5^B9@8_>*JXnXwGPL~Ncq7ill{O}y=dgynn=(+4d(_t%qSy@>9btyWqlo-*+a-)yEMHt~L1hR`F zUysQ^EO@z?6xctogzJL8=)me%k|^*j)_M$T&ZxpGODhWu%!e?+u+MFNgZSZ16h=a2 z9>B02ei!ppRJ^E_n+Q$aZOH+~!C}V9Pe}6+fk0ypkF!>*sw0h%DFwm&vTY%(vDmV_ zJvw}MbQrx`-(TO|JlxzzOhI z=K&|(t1u2V*YzI)4?ffYA|*-3dP)<3YumqoX)~Y}8j-Z)nrg<92jL>Xa(jJ#)^|DJfsCp4YzW>oOfOmFMV#MR0+!25=MCGfjL*4~Vs{3-k+ zP}6XhM!7#aSbwvL6cnox5*~?mf=M8&8cH>LeFk8)>#A!vuR)BuDiU9MEtSxC8Xv)>%Ivt?R zF1z&=jArSTKcM5(hy{p}c+hGp+KE~xzk!gz{d#O2aSF&0pcC7#b^DE87hM3iodL~Z zGD5`7&OBL)-nQvtg7Q}j+`tYz6Oeugzg>0NY(L8KS(&5|(2y(%@GG2GFkKm#?IUm^ z));{1^b{x-zAQ>haBY@>RbnJB<&a1SfX?p{AhERN)=2irClL}Dl8()9`9^jPjuAUr znlV5o-c(YR#$q|(m@M?M+^LswkZ^nVUagRr%V1#Z-F$Ou#cXVpAsi^cE+``6ulpOKs8@RPhla&nyC)aVw3bYzrgxkx>NNjI9=8D&Y} z3a2dr|ph2BD`b_bpQsT|DJ7j`h$ZuVeeb2)V`hoR)Vd;UoN5LRxyNgS>4 z{N=pH(E*6%QSzq`?U40#4469y$QDEgzxepq9Oexl@=jbJ!63O(q)QMS2l$W)ne}I^RT0EM9(*y-8Q3%49)el=J7m~5rwq9>;z1`mw+{hfGCT&SzN0;9- zloQ4DB6y`vr+oFx?yp>`{mp};okLq7AsqY#R9=CO)wpz|ezCTkk4wG~Qo)=P(KM$d zVBFKo>}2ol;pVr1%K-*gqM^YAd7o;UCNF@_YzCWPxeFmF6Ax~5;?|?isqs}>p!YDNh(7La zT;wXkf?x$u1oe?{3~9zXkbY?lXV5+>0~XA8Qm#U<8+9?1ShD09IxgWIC4Pm3)2lKZ zRA@QlW8yHmX(oRfFeG`h1+{?DXt#OLDqv=7U|j}*j?H#gfCC{z3_67TH~}HcrkDur zH2VB`{ojZ4`yW1kUM}JL=g;$>Kl9hz_b=IoT_Jpx%Q-@GtSoh3qovoMKkG@`EnjW} zJqq3$IG&h?Gx;~z6n9v8ne`)iAt7jLM%u6}#bmiRg#lP0F2e#Bfd6a=uWz$q7Si8q z9g+Qt1_Kw|GnGQ?!HE&84WLBHc%(xKIcZ3!trUl-oZRr8oqqX)w8*vg(jmPRno{)es*Pz$*7ROY@J~VRaaNzV zwBCBzv-dUZ$;yjrVF_l{0?ii$V71Do8 zOxA>b0r&1@l2GXQ4B)Cj>*i__xKZ0UXR=M*4Rji39S1$+USNoLlU)u7_jmuPFJ%HT zh*Z>M0bzKgUGNxyOty0;m7R&$x;m-dW< zC{VjV7&(qOh6imASxMjHln_E7)3qFIe=e+R<_4`r%wGzyB{5}iRKPkwd1tjrxiOgo z2xc$wR#hYgyO;n7b%MQdyu$|y`1O*r z1H!`1&OK22O2R2{1zB9r&yA{4Hr}Wp(ur~uTXEYU5`t11%dVoq*_l{{lJPzR2y}`Q zY`)_lg4z&-VB7rseR%dWtFC!EL|u~}V5Hib_%uybU4!07SJs4j z7(2yahc960Bgw39LkqL|_~x}Rm9u;uOjGb&<*X|!I%R!}7n|f<65Q`ldm=mz1{Gc- z16qTyVMzBTYdew7o``fN%eM`odTwXGxyH7B0)=8+mE3J7q)dqzrsAn0#7+#(M$x*B zp!EpP%|Sxd4cv0CbyNHa!raLG*Z}r6v~D&ux zZ8vmg(pc+yBGD}}TSH}-O(;tU>AR+6I3`AF+*@J!rQc{XHvmH%e-gy8y6L^Y9(oa+Hi3OvEz)y)RXdv?JUXZht za$d9yQzP&(@&+l$X`(AM!d*qnNhxRY6gzAj3p7vNqZ<%tzPn8|qA5K_RUwl5%pTZ4 zhZv~aautZ~UOQD_GHQlOL<(Xw^ZrOI4KmTWf>LYjrDVE{UcQ1QHndmT9Ku#hq-W2C z^SnzC=(qYmrPA^Jmc^Es9#F-fY7f(qW8lRD*pa$Qpvf zjL30_nx@xkMH8=4!^SBW4GBc-4zI_=c{w_j^ku9Ak(v1UjRkd>wlk7~mA=s4Dus?Z5} z)#RNf;cXI}@i4iXC)>N7(`g`&qykYk#BW4`rG#x(pskhVUH3}6e!B{%6y8o_Ks(1D zpqe2_vn_$4FHQQ8D#Y2V#|_q?$%8`gw9|saO9e`2xWek@*2?(0y5bsvk_bj3n96|8Q@H~Mh zhRp7ukG=_;b#nfE)}qqt^6JW5bO!5XvpjQ|~9 zonWeB4FshaO*=&Sfxqb?2ZnJ}nL*h#JEMLESdIbsdJCtDV4qThD)xikqp2ythlZtK zaEqC;Z20*1cibhr`94?SE`WdvEu4w)ZyI_apW4 zC5-rC^I&6t``zL8+dT*x1lY&0NVo?0_GowmLQRMnG*SZasoMT1eAxPB<=4_B{#l5Y zmX;Qx%U_ysx`i_!e0O2A&tYx-vO25?s=BhfsGirLG$#u0-Q_)awF^z&tic7T3*R@$ zKq}CCSKt#5SNItH&{PV5S3IyV@G9+=sHz6U6cQV#hiR*bjwn5YwY)&shFVm+tOI&Wx2P(|1M9_IED*LfPSmr2?*7fOCpikE7NfZ*D;FMB(iKdI@<4)XenU};ouV^%$IwlAQB?Ds*h4MhWeFPxBR zs0Xstq+llW*znO()RHA2=geK&w=auHFX&RW)~KD4L|uqrC%hy+#)J?F%QBdFN*ETr z5VJ&_CRuv94}SS|TS!m3O$1bPRyb_4D-+%y)18@9MqRQ*TOf!raS)|yWaZ6bD4P?D zDjAu2=fL5cA+Md^cWG~VcTQ+f^L$nkmmtv27*~mY&iEU-x9@VrvAEc8Iv=^mf``PEw{}UROdK1#yNrWCmWc<;mBuCWS1@539+Muz7vT&! z;h9-j%k|?<0u%ArJ4u-e;dt88u*CJr;)>gRC*Odyq2V@;A0iv{=o=Nn3i*kh6Avl+ z2Ei}2ep<)Y!;eUhkOFrcBOo?_0yt6YF#=l!mzEP0rOSTw%?>TRp%^_|+%0z)45|t@ zH7yT>gvA0{!)?F4N;E zX*lqBtMh?40)*FxhpUT-^5G?e3Z8v4_`(@sdzCF9rR&7wc_88&SN0v8PzheSBnWt=mzvIL z_|e4(8lc8&!Xc=)KH;Z8PmjW2o$Ys1UGcCmUEoQjO-r#=gc8H_BqK5z`IbUNhuePx zaggM4B|N>-*Ueqk+cAMiic|m8VxE9@mt#C4z`yCE6i9fAj#7>n#?-;D11_XsV9V7! z>wztwIFwbEOrdN}j|&k+#1eDRN`lIgu!97yq%$S%U>2OY7&HRLgbK4c$VoT?9Vg^i z0KdVhgZ4;KoiW)7K>+Da5}rnR!UL`gF?bH`NdW1IK*-G*pGrtgQ`9O|=($J%mv|4o zf$3!}Vwfi_jpOMX)59QE)`mzu$YI2kY*Z4e2M`~YHL!}~$Qf2qYZDRRCxeT@wch%h z6Vi_XdJqr;=6e)#Ib93GO!nVbk=<;E=*Pd4taYyeK28dH-Dz2j2Wq?}CPG9tH zY$J75Sbl5_98fP1!N(#XK>7mZJh)H@ga#K-8DS4s0JR1z>E9 zbRqe9a{VG3N=by`09g^8)RL099QzBsvC?u%E_bP&{#KSgNA1*!kL$m{5ru;dKLmVE zCKa*a{4Sy5ms*@B*>j00mYOiOgimmY14XRgm2B4xA_gDzy`!BSecA2-Qsa~8#N=@% zWZ1XXIwyO-JfA13l~M-e>3pPAX`WM_ zv-_82BMmG*V5x;js?1|i=gb~S9z>;iEL3qDsa8+x=BONJTR`;zjnnCL*Z|YxV>DNs zf^e7|w>_Q)Tp^)pj3^Bd7({YJQYi%ngg7bk?Ft2YVFcPc5EwzN$~O1+-|l~#5OE=9 zH%K2+Q6ffyl!rJ;INt2d!gK#2R3uzLP#sf9oW_?{Wm6KW9Ncd%JL?<5ms8VKn4;oB z6cw7{7Z8B*SFd~<+E#!GL}`f~3%hdP=PgzQVn_B$ry3Y$tGMjgc5X3O?ZgM_^DcIP zFlT$y0n07n5ilo#MZYfaT!t^l&V?n}hl zJV7;Uc+Jy--B}P<5{6`vj11^pU|eX1*v9M(-~moC*p&CbAW}VqR{BE2=Hna+XQM`~ zkm^GXNf0ZMmaXIx;=SW3qcMOMd5*z8f4C6vcofXHK@hU`EU{F6w=32u~=bk3P z{MQPRB8Qvu@O(BBz7rqoDXR?!`QuxthAxa-qMCN0xeZT+^afX4HiTkeU3vw{ubb5! zHcKj-g|L#iW}*i(pp6noVF~piawXWxKs5|PKCe6j`Fx{NIXitkY=sv^P>Ss3ah$J4 zyw3ki;%|9UA2!ARqQf7z52Az34VqF#8*ldx*SGf$@MRYnFxL5SeFwkxH=`dmH+GN^ zgO3iP($>+QRH31awltxUA#go({i-kr;B3@Sj;Xhto%s)(7-QeaNnf%-eN6^1K^;kU+dIDxZVu6%-*21ufE}Oo#NC1pJ{2H$j&ZiX;OM)4Mm_=;^~+jy$SDZV zK>nSd^m4hgm07MYS{&Ev7>hIDsNDSOxJ}NiP6EW-;D=hWt8% zCpjTFn&0*Ca}v&*=p(cR}v61S;w8x%(Sn0^7C6Ac}5|GOg7~X-zw!G4rF9zefwLUScl@RpU5)Y_U4E3 zKcoyTgzV)kT$i+=8E7wQL+xp%wajeIej!dEnrg4`3b7%RDnNc79JGG!2wP*`E-iN*hfgnv{54>BVbrfcZ!(enfi|m}JNV?%z?scM zYg;Wm%E7aevu4l=hUXmWHrh-L>c4Q3o(8dn%Tf4@a6H8Zf56ha{dSoUt}!#Jv2$mZ zdn{Z>$KhZm0u(b1P8u2}Iw%5zi9&hsFTDV|pOajHpv5UIf%PzC4Xjk&BDl-pquygx z9KdI`4hsX~0Cnz3irb(!9Zw;2_oBMzX-u_Xw|w|5!$1<$LkQ~8rlFE+wjRQd!L5gA zB-P3AMF>&IwJSRSnW~rpkEa1>FswZk7P>Pp`;x#VsosXoF^NY~m2Id6Sj3HCn9wXk zW)D6Q0cg)3&hR5AtD=`BL6Hyc7)Q}taUjT9;gLrxx=jdg7Hr+%V;IJD8s0J^I*r`4 z!$hgo;?4^<;4#pUvtX0YnRsqR*F5o#bv-GV8#Xrufa{tc0Bv27JW!jQcz_!`dviUO zsj$7NOno?}sUX!GO<=&qWjQgy5i}$9e&-mzLo^02{1eZ848OA8?Z9EExUKw)B^Au8 zwy1JUZ-y6jR2b{03cw(DEYr<(u9%J2+{>~}*;w^`_5g)UJS)dAKid@6zj)0Yc+FI^ z1s`pzt^-`zll4k7(i`uXgpAnbjo)+LisG4o*JI!8M8IFNwMm13xb-677njJp{)tIX z5cILvpt)&G+nOIVZ!3~F@!1*JX#NeR;&ntqn4S5{AK(Mo-g38HtJKR4XW86csqrc$qvuCUFcV$g~!#?-v<3~?cqSdu$YmZl+JziZ|iB?vhtvy-!OSCe{ zK@_!xNT%fuhK8^}t$qWcSI_fHqS)za{wsNk$>F1uOLK^iEwgyff@FZnuFo}qSsC;;$aTMvk&InoJ!@(bK_Ku=AHTZVG zw)kEDq*1RR^+1INz|0=q<`YC^sY)hBmfvFk2i89#{GnM5vQ+x?>C@6__jIY%Ih*^= zMP#RkHl04!=re-)voqE@f_`=2Y2V`v5Q|E@SQ$t03O@dHqPB`+*DnqS!5YL=% zv+?NN(f+%)2b=J!q0_ibdB7Ue0YO45B5+x`-q7^X>+r;Ci;MM;9m07ie?+x1gAtSw zOyRuOYk#}E9Bjohc9h8un`ico(TsW6Hurvr-fkri+oT-T=V5XGH!wntezg|;wcD$f zPcN6w|C)H1hP8YxcKX;|nU93(tiuAmQTvFU;Epsg4GjNph+R=y-#n)6{`Su1{>zv6 zv3u~(4VdWvt=%b~Axf5&E?H~e5}*!zz4mO->t`!#gI>RQ@szI(BWm32*t#DJ0Id@z zH=&R7L@4lS*xr4&BVjPbhk?7i@$g}^y?3~|zqP)x>1t1GF!4;)k3n1*WWp0WP_r{{ z$`{yHhnv~@wj_aw{go_C0R3+0PJ>V+64rr5e?TO_deaK5MBgK<;P=t$v(=@iO6V`H zK66hI?CCuLE&W;nSiv9B2w&jPevJXj;j1Kx!Py#vo}M#FH)8-)r4xLKqQ6O290Zb- zPa9!V^&9>L?A1QEH#ax=lzlHOK6<1j)&zUn_oYX*#Z_@`jULH&8?LJwp&Fd}tZrEK z#nNL`eJbs&Ev@l;vRBqx&?P{juf8NQ$QqSY7AAEU%>kVK$>x-XFQ4S zAew)Zy}ASxfcp-YJ#gvy_FK;~9oyqt^P{q*01Sntm4$kcsYCXcm3kcuDQsQxnGHl) zO!(|4hLIfkyK2|4z$_Nb7zPSVd(=LbLZ!@VEotS8F31wCjIPwjY+{E4 z;XBKS6^4V^Xq_ogAi*Q+Ju1xNw9;^EUo?9Lw-Qa0GAN(G{)+^k4>TG<-%v+~B^rJ0qYosM=2;kwKp*QqTZcDCIx{B3GTkNCh+yyrEGN zl0+eGC44rsUyv;IdnnaOyrQH}{fPL7nxTQc$C0ZM1{nms1YW^&KLR=E@$eY6u+=|< zmo3QR3PO(p1*-EYJg;-=f$amGk2tA}_k9kS4Kkc#rC)fJ;g!TQ8*Am3aS;G_!BQg0 z67nC=S+)z0m!A7&Ty^N{x`-s^rT#GZqtO(&kir}b4s<{0p0U8)#w2bI#34G~dt@>x zY%)8u+3A2UbZ~?anfG5vQQ{beYEUTbZ(ZVxMW#wTi+{L0(D4&+=kD`4`s}`fDr6^! z^T1pNASK{vt{*8OTt@|^}5{8aA(`1+!&7%P5MFd-r6 zkt86-O$Fo25d8@p79g+$_Cl149Gc$&&L~=0SoJe}5WuKWK}_yAr{Lb*hI7f0-&4#JI~+LE;FMLuXP9aT`<@8wdn^mEDkS)0&i{IXheCSs zPQbp(*9-h`9qviINr6A+T!MQ?1DR4xYeRN2gns)qjXh3A4Vs>Epuc%j?jx`qWRtky zC*_kmQWst!5d?(%Ou*zCL3SvLE&&iAJd?0b2>6E>#($*5WFbquUJVv-^cB|1GZxx6OiWO`%*SJ#A|986B;kIN>usK_GOIEdR4 zAnHS*Du16t_sdHCS|$8J5dpq_^Ci;NPWI~NQ~HWxN_l*`>LRE&hYOc@}vOr zJk?1WPFxk$IRxofPKeYwKNq;rF=lt0`?4WJt29{MkX9Fn!QZ{2BIj}>j`{Uy5Y?t4 zb?AZw!PmIzS@MLM+G6^|(>PHnMV=rQWR^aeBMxPPtp>@JD?^tQ1}R76bi7>(hoUR| zMq9)k)w}7=L_1iap`KFn&(~MDLb=V#+(or=1-4BN`bn z2t1e7^0Q&CSIFGV`P_GpjG;D&hFwhg%5r?fTt*&9jo^lM;(Fk4{NW`cRS53B;rQy+|6>xWG z$s6HH1)sqjVXi>S zU2EY77ojUCFRr4CI+R?c@`4H3RJkA>QL}*1!PF|`C>2;lc*X*y!e>|&QsXd?31rS| z{%@NAAG4>`pG0mKXpQ*0FmmyL6$(+PTcH?5Kz9VX{z#21m(MMy;3%r#h1G^7OASUr zgv_YGXue%2iy1T<_AI!g%mytSIYX<1=?74UjSId(0a!z9Y{`L&=R^Tcvy1SlXkG(v zYXZniL4^~9*w+Eq&|W-pe4~JB_v4X(Tjd+qmRpEceXtyp$%Uez*x4Yx^Lg~3+`1dSl{s9={X{Z7LM2Hvr z+ThxtX~n=PC_U!SSd2j|VI5&&!DqPsqzIZ8!1hEx9F0i(TMti(o2vMa>Y#e5J*Og>Dp*z#e@MA1%Pqes zL#%-$HzbCU0t76I%RGaWgCZ0`VhbcT?}1|WUci#(lau2(XJRM$Ujd-fuUKU|VtaLohDDCp9s+#} z+ISJPq3WU}1UDQ&(7mF7T<0$^+?GN;4HSew66wqY75*;(&TpLb{}>Z-Lj4bcqcdHC7v@zg4VUDDVw>w5q8l^Bp-O2XNBnohJ}QSVEe>d$wBDSLT4k~E#k zx{#oT`CRv_>)^CY>S%uq>tEPVlI zwF(id;(_(L!$`5oKG;SjrR5deB!DL%ZVy`^Jcp4#3*@(;Q2p^dORNf1e=?tJA~A?> zSPq|X5GuA;w~S{h!cVr47(`wR+OZo1-8pgxq4Rj{S0Xfmcr1zs9mEsoz7Vd2OvAO{ z29az*L|`(#XRd`WVo_2KbT@_HB=}1ia4Kam5&JBZ0Z2^s@St1=q3M2{1TRkN?OWGM zB-I<75+zz3zK;6=2aJC(h3RS{VqY|#kFVA3?KN^e*J9-+QC6!1>=FH5>$C{HogLf2 zf)3L;k-H!nqBDbb)KAnu)|rwWGw6 zOZC{9%4uj*CbPtBbg>c>IZxxqHp6g^D18DCgDlx|NN4E2y~B4}i2cOBu9}mQ!KH$m z?OfNMbAkB4K4?<-BO;uz5*f1@_MTvs2w!QlW|FPm!@`Rl>?xXCJ(`mx;n zfNd&wuP)lX7TuM-+5z5OP_aY*(YnI&N3WGvKHF&xVq(J@hv*l;f_i#nT zy7N^OJehU{^jPhBdSjv@uZeh?(EEl;V{-uf8X>QN(G$65zpbR;@2x{q8(zM*u^;rk z&R`7fKz^0knLFCC1TaOhbm?DT-)r25d@GJ8glpw5IrNz$XYaUyo!EfzpPlUAU2m$f2UEC zB5l526$;nM%PCU{s!59K8m3pRV}iYg%8tI2fP*^nj5a&?T+@;yCg8+!Puqwd2N4{e ziLy`(_vkkR`BlH(lMqp{xCp|s4rE5r5u(xj_OS$t{IsJ6Hh}%Im^=_ro|bjFCSl8c zQ`~ow25cG-MLZUq@Z%nmTjKm;YL4Z;<2EHm6*fy4_dQ{lPvwpAG?y?WxH5k50|dZ~ zxi=zjo~>z&Jxr*GEJqHW8Zs44>+?9ZM07fYX z^JlW)YV{4!KW`{tIeNE^862L&KdvkQ($Iro2ZSH*16CTGRrq}?Ty~aLP12=;6`M9^ z1W7;?#s=VFQphawj0wdsh+%g~y=tr%6`ZO#8PbBUo_fVe`ue~ukN>mvj3_C&T%3^(YCz<0{B35U2roOo$-U3_L?37V_^&DWqrM zW3cZ;og(`*G31@)-_^jvK|u3a`T8N?*T}zL-Sh7=Q|8}O5b%cBc?S>(f)F4$_Pzj9 zNd6sg$+6gQwws*+yTJ(O!f1<@!(1vt-k(6(*k-WTK%R_9U5#*t)b3$>Ad`J1%=eYZ z1Q4UNF^I`FHaGbvP0iKRLc!5*1+qyoZMceH(=ZLKN=Vq5otF&y@nrScx57(b{V{lH zOyi!HMo{Z9xaRyS2>9ND(G|nM=Ma4PJhVjGA6moMW++cBIJM`SLvZGN@nAw!7yk7n z`P8U>87UY#6e%PHZh(CV?b?G^8Ys>{Ij$C8&RP`;@&bA=R>;7Ft8oS8irAPr)r_i~ z9c#@vpb8HEY_iexQ9qWkquYps<)G}gKI7v^%t>&x88Jr50#|G!BEHQ%W*6JL9El)I1a_9#jK)=Ln+Mp-6}a zbK8_W!mymL^*+_05nd5C^Enj6?-(|?Y}O`>6~{o($f6L&nE%HLnY}rQ6u-Lkn68hH zzhhpJ1*lEe#1j&PS(3H1U(=$*iWC8~!gFFUiNx|%Q(_;!MoJ8WzK44SQ0)4TuL<{z zDBDo`67|Lz5B9~>T2|+JP0+XIZ1<3SC$N?Nx_QAr2?Y8YDX$Orl-G?2r&5$xp&WiZ z0OZ{fGoq_&f}a=2y5k@azbApvdJ|p>$XF<0kP>c%AnhdO4TMl25oc!_$oeE*n_`j` zv+fOmaAFO|d^TGA&Iqku1P%-0ul8K2v`6AvEfK0Vh5p0LW%#Y87&w-hmZye1Kd^ah zA*Lrpci3xjx957o(bC9DmZBukn_BA%$^uNn01ok$p5M5WwX&cevZDL1(hd&uXxAx0xvHJzI+ZG1T-pOvhv#0phXYa zV(3ilK&cXjnfz{nP5`W+*Kw+KM1$e;=ETTCg2;hDq=&dm?{HgEdAMYGv3#gId@gb? z7juFhStG=HSV30F^>(M;&{%E|nISK3I-BGT#&(m_kPybFqw{cw?{L^nnE=Loaxa^| zKJS-bgFz%|Lm+WBxxS(uL*51CV2Ryd<1iW&`ov|@;w;E;{6$ADhYmt~iPQ^Lt*~yV z|A2>VRQ|=9LUxuB`B<<{3zw;{6+98h0UYTg3CxM0obu~Lf^+)p42wQLLl3V@tBXBW zvRq#VM5UglWC(yKp5}l;A!|4dGZcn!PbMKMOioF%d|gTa2TWKj%INEmiqNT1Fi*e> z)4r8CG%EDpz%uy|3u@m1AkE%{7-sG?S<*`uTt$9HxO>3Y;)IzI)%$}a6R1e~m?4*j z%H1^B2Bsq+^_mrIprQmAup3su_R3S-E8MQt#ZXi0yT;`F-io^j?e+`9fkN)tF@xTd z`Ae>hMAE?Bg`-I#;6|39jfIZ=W*zo07oZz`-UCcDDTPFct`mk~6{{FqSkd+VRrlzx zBAvWUNh?3Eh<=KS7Gdkry6%uM(|h@d1ll!&t28EFj%za zNp=zC82SPz3hjh_vL|UerE;c?pZbPB6~h?DzslB>s);^dNNK%!&jdwE(!ETd+G{w^ z=MtPl2cwxb5mZ;xz1mR$XCSFYd8TC!07_McQir<|G69f(LhjsJ_q^4BI}jq(BEjjU zzmYRA+lhfxg4hsVkL1!`ctpsLr5SqX@sZP&e@UP3ACPpkWHO1}s z0^fXGJ;qa_cb^v|h_T1j_E?3;S3~SwsnxTm+zUeNWsnHt_6r;*vT~k#ZQN1-Z-$#w zQ=aI&+(3x2GdjDC8uU)PG7!IrZd}~EQT(2hkQ?@e3j6%O8eSBQ7XWn^*CF%?3^HS4 zZdo|V#x;Pb06h<()vkv?Rvk=oKrCw_kpRFX8xd%!-^3({ko}Qjhx}f_yEw$)dsX*q z_WDGS3B)V^0`>w5re)}fy54n^egIK{4LU`JDd*6tCj@|4i4k4E1U}(Fhs~DUQvPhM zDjNxBE4T~K)N>NvH#X##EI;`{d$gp+2lHQkU$e=1L*x*1b?y4!;GxqwK*Y4k4~1IX|gWPU)AF=u=9!M%8G z4b9Hvr27+Rz9{-Yct%dG1SfFvXTRY;e42~D@y#oIE#W`BX$ud>FkG&}ZxMg|_R=$E zIubHg2#W__YcNAqa1ZJ%{v9Bicu|W8I=&<-Df0-C?20oR1xr+^z-X3u3lNg?CRXb= z;4Tj%d*GuJW(ABsEYOv?jZwZtJIYTr7xDDNLxl2MUBnxYA!ET=IyVDYCW7a-$oko( zFnm~7Fnog;r5u*14e6RS25cV~YG^}T;P?X^82<>qWiV*S{5bT8@*#%AHf6I%Zvp4G zV;*d2Wo1=R5Uu`E-y@YEz#+vmTgr={aiDj;drEAzrcMWfDu8zCe%^E zmd11i7xU$W`K^Qg%AW%ECueyN{Qt|dJ4t6RC*UcB`&)>eqAJ=N(kS#2dAHkf_5>kq zK`$HSj(~N;X!=A;K7%)DXP5Gl)bg1mZ>aQ1GBf>HqLvH&jY{}GDQ#e~IzsGKVJ#yh zH(CJ4SL;BOn-W@iDHTN->?zcDP}#vG2_(hq;43^Oer0rcLc<(u9C8WiuMrem9BTo^ zPc4yA4CfLQtxMNcJqpK1eE1%^|2lZ4@`EB{`6DsUW`Zbk;=NLlLj3@QXmX()vF z&l!?p03FgJ8PW*a_vg^Q|GnQt_FbB;=PTcDiTKBXcDMD*aKPOc)#T1ovB15+CYPKa z=OIpaxls`S8z){6p{qOa3{Aa+2P7N~ykAsd_O9G1L!S%)tmGOaMgz8j z$&Wp5)mO&yMy^u4zXp&=(9^WS8uA1pl2??}@C{|}Y{tn#77*N@YID_{6G`sZy8TAa zdL?`xIw58jgVDe^E0|j+PK$^@g&O%4kr8B_YiVX3+U_CD`W=sKmor7o>E60;veepv z&n^Pfk^D=kgwIB!*6f~_KmA{ZDDt5|O!O!O$stp}I%S(bm#UJAD!;7!I)5nc_Yd)7 z6+guJ^~&wpbW_mc&cX)}&;}V0 z-n4PNT0JgzIxy6EP|3iDKMut3lRmDLyS?v|Nh)f`9}%dv)tP@)sw`EG+Ypw|K{S02 zTPDs4?k8i41Hc^8t039{EWnlDAQX6yFA4!R2m#*zdyiLXyNLk8jKz^e^?Qb+15}?x z$q-rDkd8&H+%Aup;Y8uKrQnJ-?ZfR~E zcXg@R^anp5XL{qdPyv>@%C~Q_p18_6vl?=L%K=gx@yeeBRqn5R?kNi%f;m2~FLoi5 zgi$~!Z&4z$*Y7k{Jc{>awJsHLVxxB2gGCgDi{*NQA=zQhE~CLTD;QV*K5d|Tl_I|_~HWcCdX*qYQ$-rkb@j~%s(!#(P)0L zw@jnmcO|gipQDdrfZ=nVt0BEKz|7|~qQ^%=Q%XuhLV`nv+$|y`pGhH#WmK_j;BuzUi=VIIO zQBCC`O{Uip7j6l;PcHdqfK^8eh8(cZcNYCJu!Pk40J*4Q8x$L*lWOyDPDaUf!(qQF zU`ypfgjdbryIBihJylj>zwf+cByUDM6!%690i6A!uawURMhwBl4v*0gh^@YLX^>5K z89Ic(n7Q7&nu{f(BSCJs7SJLq4{}2^nF|2}poOpK7*{{}EcaJ26#U8|HxQRg4S1`N z8&)1S2D)?nOs?f-LhFRv0^CWJeF9Q{t&lr8rF+mlS)sNwK>$CkG}i^hJy78ffa0-H zLQu%*fP%iYP@B7hLQ z2t8;V*(OSH4mmVN0JsOSNNWUCgEd0+ba@SV=%YDER8oE{N)xKR!yk_}gyMS)^6>6! zx$`FlFmtct{vxU{tUNpOL-|v;)kG%YZF(q~i~4#SeD`Jj0-+<6i;rQ&V5(~fJ6%ML zsTGPa&`QOTd9Gq)BH+UvXS|wH1RrYQq{%hw6Y$P+@H`(dr~+nLyBWeIoBspT8zc7j zDZF7}>E+XhCbCW?!(uPcodE#Splk<9c5&or!P#u>$#?QE2xyoE@fW2b{1)}wS$@_H zH1T(!2Z3{=XN{pQ1c8?TT-`Kt{r_X{&37BS@;uM~1BiFHm4$XGC{f(EbVdTUJF?PU zPNysF#8hLZ$Dkxi!IVTXL@I&``{DXkdIp0oOb;4^dV!hG_bm6GbM7T6iHgOppff|f zmuFvo`=W}u{}$h^`*dHgofFqDJ9(21*44}(>6nZD$hqcZ=%0Ay5SZ=s-Sf&zJl-!Z zrgJ4k_eaE2L)M~4hB=1B*aRdKE_OaO@<4Ni>_0ImOrey4aIUn#+du7HBGZ!uQ9?$* zR_POgz_7ZC>m!irKMv9^UR@KmLROuZ8HS-qB8?N6FBK2b0K>w5G=hG9w%;ifxGa!} zTLiuN&pdumh$fKcXA?#qYTF4hVfnH3MSK5jZimrk5#EY2Ufx{1IVk zhVkS7b}#BOZO6A5Qkb@kp+mP&nJQTxyP-B^N+j2%<;@8yUyK68DHE#r=7OZm5DEdH zF2aY=*4gAr(b}NkFDGNH^)Ai;n9G;mD!?rHS6}`0FJFB1pN+a{ou`sfQeCo(8R@dJ zmWfbo$D#N;C)49Rw5DHz=((A2&O_wsmx%Lp)#pJazBe0=X9*s+X&pMMrMC&io z8se5Cktn@Ca&mXm%*C6KvWIAO*Ff0KtBxOVr|np_8uABhiTVni}ch3sI~oskjw_*#>@2=+y+SsZb2t z^yb~sfRV*uiTd+Q{KAELg76dxD9Q`Hh@}EW6+V4p$wIhl4Y%}p=k#AI4@pShogkPK zDZp?Fe>D1!U{0O)4nEh@N(mtS6;Xi`U9khPHlO(N09@|y6`E@`cX<#l3JCjRYMeCF zi(6J-%bOY#?X;j(ppbEfA5B|EH&g#v#Zk?Ow^r;{)%71+rQB8m_lOjFDq8eg0`2)w z?kJP33wM(?Wz2#)*`#op9Uk+>{ujYC-)Z#soxmf&v=6RIX8}MOnvfS>Ypg(XcZ5fO z8e`xA$Qg-(FLFDKM(;7J1Lg+)(0JZ+GL40QAGxNudIp~q#yh~0xavB{B9j%}$QO#N zkQ>kh+mS;dm5MGX595FoCi9IM3LiwiHsreVp!i zRvOzn07kv4(V6ITc;?At0~s8WyH(~RbIz`2v&or>Tx8&vR43l)-o^+e-VqWz)F!c; zAnqmda`-$YvU861e2WI|sUuOT-+GnNkp zMCiFiZ@c z9_5k0m=hO*fN&Sm{r#;xJtICWf-f@ySy|J+{_OEsTQ&I8CIW(ac;_aj8zf`%eJ*9dH>Ekda+{hRnS-|dqyurD7*ygNda}TU*pl`Den3;6eq_r`E)|M#r ziV$!%yN#8_TJeEZ;gv?Pt^u0~!8Ty0(Ag0;nehG}EQ;u9ciI2 zCazVt8Yh^I!p~hig>iHD$>L%ySC)M#2#)C}4OR~?9f2J)EZV{#k@AX$McB0f(X54h~@v#epbpzN)6bUxp7?Ul9*S zSrz3O=5T<|A!i-M;84S04e`1cQ4yj6hRj%bfN%Za%t$LdF*dSOb6dedZ~Nyx=SoMGjiGQ;jv0`9_&f_cTNu(wb!*YDJ*pr=Kwyf2NsCBYDa0nn_Q^Hn^ z$%=)Q`ge10--o^7{t@?9Fa!}(RO-!(`KnV}8-As)C%eI@|6%6>0T zlkv@JdNCbe-{#@f7TKu=DAC-R`GV-qCvd}E#mAB`K*`RedDue^XliLX#^oeiv3fXZ z*KH$O)bKs}?4oYn)=kU!x@nv0S^q^Gn3NzkCC23X`s&(<1ivV(!}Hm(JNJ_7<$ylR z%!th<8-pP8E%unUNg2G(rVXcI@KDf(IcXc)Xa(XyE1bKd{`_Mzhcq!|iOZ*p>$N|L zDjkXP)eUjxOhjk@b@9RVKS>bEcm`6X6My=%#k)4v`ISV7-I7b;k0i#K;(T3%MK)Kr z6#HD7YFXL%Agt??9|$yTPIpl5Gy5ZIp?8(<07a0dm`9aQ1?k{bN6DJ<-g8vL=Gpx}?+^1Ck7`@WCw`Z6x=o?76S zc|G$9Zi$#2rV;pbksmjdTOQZk$bl41L}=z$okPGQhhK6AfvP!pAaIY{^lpuTejU`| ze;OZO9Zj*|d*Ng6{J~l582&wG@$R+t-(FEZjX z#G-Aa3wSn@0<0Wu=;;&YH9z0OiMM`fg|L>VRf|`WPOq(sO!Di~>;@5rmgd}e6KFBe zC(r!S@S~A6yqrEmpds$wEfqf)kq19u<@BArUKSa|>IFe3rI0RMu~|jCDpGjgEG<-{ zFv12oT>BJgXa(eTJt34*J$A{aU+1$`e34bs1A=2VZtcT8Jc6@Qg1FJ(w12fj#qh2W~h*fkkop6$_W*5jeB-LehzgrU$ zW1Upoc!S@hX9f{KevY0#X&P}!DH;41M~kbA8*R2^64_pjwYF*j%uvm{XKY=uT<|yk zsPXk)Mn{^N{}fg~SvInngKPTDxd+vZJgX2&EW}&6PhiMTns~OtrHSu)3slM#HIIXu z-Q3hjK1oKmYufC3pQNbfNl-H!ZYValk`N>P&c2(B&)q6s9g(TyHHVY4B3bfuv7F3~ zZ!_{=9G^Sdczkj)mF~r;Sgx)omuB|`B=%oF%ie&4t<(J8*zJ&x{ueS4Tux|C@-wTQ z86RD-RbG{&lcr;~=`I2`zHnh!U{*P>(<@8~G1xN){YD3!XfJ0rw*C&*yt&2H`?BdZ z@|?dpfbBuBKdy^dvU8JINa5L(i+4b!{`+z8{UrE4fBA9AfY#5NiOOqh!QemBY(CR8 zKAXSfpC6qVX)=5L;{sqgQG{A|%@mvBVtVxB6lCz*-(qTJ3jBC{F&SU)ZLWDeQId^c z2m1eNi*V?h@!1Vv^8HGODmu-+d57Ym3pdGMi+rd&AFjkF0ntO{L8W?$fT-fQ?kpj) z885#Hg+t$zg+t%G7vWIF{NA0Y)}P&6dIIX-l6jKD{Yx?iF3s>B9POuj{I2Cg!ZW{W z`H(+;VLrSHeMoxiJ}z>wauOa6+~YoNCiFg$h&4FXtt8^)3_s6^M9jXMy?Y(vHcn{R}=`ug=WiG1{zx&mbiXKD!l68q5N<{STCsHxPkLA%_S-!iPE=cgQF>(OV zI?3v>-$LepeVHq-Tq{qXgg(K}598d6Z?nG%`6=L2R=>=$OFjLb2b0XVZfteUk1GGS zzvI`xJvS!8KcGCYy-ATcBi0iGxuK~6#86&5nxxkc$TH>r2erL&Tuvr?y#!0~(^wHa@`rZacvAQDt@J=#kl3_+j_W%)Y`tl%~61r1Uzs6`%^<`;!WkOz(YMamp zIU^?1N(;lXa+qHT&Q^E|5IKEX+OYo#sK) zEk|hX*$M*E+1PTA=(0K@;Nbf*addWxx=j&p|B-v-ncM$~k$)(XOY31^rOacqCE-B{ z#*TgQ_4Me*0$-b52$SwOKYC_};mhfa1Z4oN=}g%iC5!Wvv1<{d;;2Du$L?|2IWoki z^KcCVd~Sh~?ndfQRY>UI=lp9WR?C0QzeEuT%v%R+^Ahfh3m(mrBA(Rupthf-nUA3Y zGWUbY=I+~|?wunZPA!$3ST1pn+`hfF+-r6_h?g^oC}3M z6UmaD69WP=#(vhsr}IC%LhHiV#l=c-^^%|MqcQ>*tYPs)$qNK0ma!#+!0zD@Q9jef zQWD?ugQd{4;xp7JdQ25FE#mAej_X@Z-r%l$-4tf%ys>xA(wguWW&olhmf)Z|b#T$( zh}+YTi^t2W6CH86rZ|TX2p*MJ%1Om~m9L4DZsR0k%2V4Ac~Cv(+PL5Vx?rMraMW1} zU4GAik(P^`@KJoJNu+O=;}6sPn`O3qjwgl+fpy145MaW6GXQ=bPcQr(^r3!f<@Abx zi|Pz>3W5_LHxIgDP4Gm?>Pwc)%HlSuW@s+ zZHC}$A5>jmc&}Z0C2bSQikg#yONzi&v5`XK zC~hVY+kpuVM~$$ijk{xkT-!jTUwq88${V~z)Ia~YffARba#p~l`6syaP&ky38l@aJ8S#4z{moqlj^6qCdi^N?4f#{Ai_{`usvo8pq>`RS8C z-V){F=;~s6ED*UK%Y0ElDNEYsXQ!k&^4G`yO#pNJ<7T-D!YYb$R3V&ml=HF@=a`ZO z+L5VV80ItS)x`ZQ-bKK^cx}GLus{CMV1oEnX|`#7GbdJ>DG7mn@26uUmAV;d@DX~m zE1|C9_v!jQHg1}P;Ya6JZ;<~Gs(}a`Ci;Scj}8%PSi#FzFDFO0i9 z{!G%TpCJ;2MyV4bwM_Y_%qQ&8hOzdUJs&mG@cUBm7>xEZ2V(caZOYJ_BR7KOJbm($ zy>$_q@v$7T5;k!m8!AixD2>5}zz@r7;*n@WT$lP9HMDt72G42ypB0e zu&){=qvtp4VwFMG+8Vr7eCrRm#5R~#gbS%N4~{lZ-HTZsAWgZ(wkQ4P20bz z8qNCNXOmUbd2asUruSY4)B3P$(fXJc!y0-t{h>(qrIyW2!aql*_>=M~e0i^fV7qHQ za(Djx$Jc2pP1`9T+6Q1W&lBJG*q>x~1do#EWGS+v=~+^OH^|2P%MI(`dRQ}{_D43N z`Qo7ix*V^7T$>ie@6<#8h7bnzKI`vfWIqbD5v|$ERTeL&U`#)abSK{duqo!#NSLpO zKR$i(kH&Y%6QJtj>lymmA|~R(X#oBOxr@!UWgf*ps7ftV=vo35S=+0tdf%kYe>Ue4k=XIo{MM?m zk@!T?A9^2cGmQE{)`i4Ow8n*B5P0#s@dACf)ZSlS9ltyu&#qo)-%p4|cbn}o3+rap zH|ACY8A?C=$R#zQq#WqVNfT-Ni+fe3Neo%@_tVqa$>cQq;+uc`=Ibwi{QBD;Ui{^& z7vKHx{g2;&_u{KR{Po+fzPD%4wLW(=wtq~XU&TMaN}k_(3Vi~sU;c<}GA_S3n=$1HQCo?_OgEYStYrP9;WJ@e!IM`wJw_v)ZS1|ZVu+U9<; zf8w$i5M;E~49clMP*#s~+wt`D;_B*}c9X7EM4zcGMjH0n$sT%`c!0Ky9q9Bww4Xn3 z`jPoxl3w*XUxT_|1YZ~$-G9|zS)077`(5(Clo#bKbM578;>%U^tU1s3(_YtiRW+WP z5HLb({>-Pfx9e6!WB2>Itq{`jy|=ae?1yA|u`eMH7L03hu^@TdN z&CLevUe)~BS1~6P$&YqWRsH$Zee-g9G0`fW=BM|^$t%uf=UMsiDtX!}Iu2^;ZEf|r zotqn4*rq?_6|I#|$m*cZb#NbSlidh3^yHZ2Gk@FI+iUG}uwr%0sJ42>|J=CLfId}g zqW8g)nh7=hg!H4CbbcDwPqDpmihdnF*6AHK<2@`k$D9UID3y;xu?fLCSFN#IG<$qd z^Sk16U3AcIv(*~)+U@S3Kj^iF{Z_X*cv0`hg<7Llr`PHZn$3Q1*zXLxJin-S_E)vJ zhdi#iX1Cqy3`T=qzcuKip&N}ly%+U%sU#`p`knq@*lc!t18TH8t!}?tRgw`8y4_*B zJ?M9b-A<3TTfOFsdaI(%5xrBiKWw(TqX7mRgC657mmJA>BKq_PqkdBImC#NdBB<3H zwuXaFr`>I}o2~X}*c!d4_uLAZQoX~@sNd}MhRyD%(`j}(qrtG-W*He{jv{S)D8zdG zt`>CIYq3!6E_Il=7BenNhVWraEwMZ8X1~eYdp#P}V#X!OU_NYj`kfJa&|a5+d!5dJ ziI+=iI^Eugj#>3#yW8n>hrRyL&OB~(aMth!c(L`o-6va{}3y} z;4IHb(3|5wd^;bw_@UQg5`V&)|9OwG&fV|Yf7e(!n=a&p(^IBO>>DR zS!Mw?r1&vRltN6;Oi=4sL#_?*XRgqBVBJcH!N}L=3QZmc?pf@4G9@-UC53)ui8dp$ zy$xEe-U|;OOyLQZzYv$f`+Nk+@MNQVb#wL-{m(Kxo?K&>JHsOhI@{_8(|Y*MVBRnx z4h4!GZ%fP|uz};+9E+=gL19(=F4SRQ4zjvUPHk3;e*mIx7i=a?ue*?=%Okp|3`f6M ze(XXl4Oi50OpuNCTg?&i71H7dab{zOaa=z~@#As)=+Uw({3`zSCVqSqKYBtIy0nss z18~g~zV2etPG2oAlQCE%Kv$Mrt>r9^D3bJ@r;trwhEQpBy903Vum|j zOLGTqaXY7|VV1eXV00Q`v33zZ5Kz(uCRO>h)%`N7P*7WRc+6B5tLM4wGDDg4Qq4%J z>3Q9s$Etqsz&z&kggG8{4k};v3Kp{NPqf-ksf?h{Vpi@Lpp1{EU@OSFrP{ zS9QMhJ{YxG&3ZeiX!+W3Uyn3vIHBQT8wRHh>jbwkgjR2cN6nP#3}J8%`!FW0PPb2$ zZgbQYuW=OgU`uvd{TAljBMA86aL{TEyD(E#C0nh2yE6cS4!RWPdy^VsN=j|EFufdf z1`z#21_`vOvK(Tj*@nyO_qzRFzdcAw9y9Ooa_PAaw|O{h^#<^9oqn&~ zf%Sy3p{7T3T3}NW_N3oEY`2>IKJ4qT)#{DfZH>EB5+o`n9cm2*9eCcP zq|ON|-U$0ye-5LISak~B2^2otTOr)(Jrisds4Qe#_0@ieyo(#+;rvQ2O!kvsm*A@8 z!wW(BhQVlxt;XJ}kPnr*v?4I|x*k8j+TYt79F8~~%|ZHa&4{sDwK+`9pz1R;C0fsF zb7WT>;fl|*TngnDu>d^5iqWV)Nhou^nu8WOp|l~M(>Ylw>kM0%XfCdac4pb4aEt{9sI!056`93Uh% z!&V#F8ltW4{+n9RcTv2LK&XvmZP@IoG=}eOBr=`K;+;e2Wqn!_3L&=ZGbS3ZE*%{N zN!ySQwULOm1&@gYb;ZKEYAV1Vd6u>L2@&Gg|fYfWYyY1R-&7Yg} zw9|n9?=h9nFHG(;FNjbN5+E4eu!K}olj%<|qxw^_k&TL_2DAL=d|mlg*umViv5HJqaEYW=Y=lKM;5va?;>` zT(?wcSAQyw4g;ncs2$g+c332kNR&aZ3$`7iF6g3o=|DLSh8-`{@C60_hS2+v`XWMm z$UYE&pfzYAVPCwe`5F82tu82ih>E6-j$r^KhuTH8(?Soy*nuc~~B*F!y zfIiiwk-8iL3=zT@!V8jQ2sckJN%NH!D-c%?dEYWJ}`-7lWQfV{q5rYFqw?@!Z%?_%fZhu%=8l@K# zVGiI?Ej>s9G(76S5-{(yZ5XlkaASgyLp@u&kHg5M1(b5i>Z-|Ypd%abV*oa+S z4fAoLS`I%7YrGQO$&cRUTPQBkT*yQs59Y z3P$P5=mJ?>2h7?T4tnrSNxLoaf1oVzDTW0@3{D0eytf%T6_T3G}25lFf{>a<57RD_LKptRadw>$z2M4*wnLuMs`9r#dHhS-Ay{X3e% zt)6+3MZ^e#2dN+uQkD^84~$&k^hd^eBPBZw)wcGEv z4p{>T1Qw5hLxwOOnX4l4Ny@hA{g5RoBBIZVMo&{}xS6-F#@{)s zS?2DunS!2KvZ9D7K|e8jZ^F7IF0&LebOoAVsFKYLe)YICA3k~1Hv4G*Mcl9lMX_)A z{I}qZtYPI5MInk9;)7q!Z!R#K(I>4Mc5Eam7ggs1t;x{-^>l2^E<9m}EQMB#->h2u zuyO$uLV38*h!R1%hb+KSB7Gb5=T|_9%*>SwZ`^&T5erFx!v4zSphbejy#%i}j4lRi zMRZbF%fT5nK?xYZI7uVn7ac%<53uIzBKVa|fXq-MVDk$nF->r<+<|hk&Ll>6+0l~g zH>BGs$GibRrNKAw zR(6F=o_vws@dKbtQA zyfN2eP*_ffM9im5;mA#wt>A5j1!s7B*#WvzxhsOS-wa zAe<&-!gcP=wZNlJN|0NI#*yP7(@y5Gi!k5w>n@%3GWURML_q-B1xQLN*@aT)f0@DfD)pOTqmLH5Qu7)!XJl%pQ1Yb5vt2s%R-sr>CTa@hNykyun?2wLo^ky*r*QB|X zXB^${tCp+I*sSf|@zu#BZgQT~d6hiReH*ULSIP4{8N;jOxdge*GH@@<(ZTpZg53^W z4ux%81*Obm`iU#z~G1^5> z0T)kag$jvdj=kFhBcssCsuG^4whDNSrsm{2@NKS}B2Al} zTG#BYEJ%XoyKQTvwXmmolUMZ~?iKKkJp=*RMZhQHU(z20HBB+G>y$tV(!$CKB{<@P zPJjQlUCR%s2Jf0g9EVZTPyz+S_TuxA3se_wb+8NJaG`id$bnx6&JN8^b=i9B5WPnW z0e@$RejF_b@{v|~6t6Tfze5_9eStLxt|^$LBD-L!XlGF1sHgl+@OQ-H7Gb(!nr| za?DWjRh)my4T~DM-RFSmL^PsRGS+jo0G4W*^E>Y$s(opq6Xh^T@7x7&`aMIZh)nUe zQACb&L7P!m5~E+X=LoId(+*2Tg$<=rf3@MA%o)1VAO+ViNk~*QTnv1qFp*i z$0(aWBxvaAWO3K5EQ*2=dEkgsF`_}Pi*%9I18B3+eJe$hnN|}hfXJ${RW4!|As+F2j)-!A>lL?z_3G=cAaiSZQ>vuO zD=l?&-^~l;{{vPX{Dt@ngBqO7u;F`K%Tc7(-qh{NB@AgEhK#Hdy1iBtQDk*d=qL(nt0O21lTY(O7F_tk|j-qdr33ij=iWH6SK!V~#p7W@ey( zbWAHPwf3WtckOO$r48O(Xb6#VXMnbw!^4=m2CNM{!ZQ~_r3fjB_>=t@A=gB{Y5^7f z=Oj&DR5*m|!jWWIWHa04c=alavVHRTqf?+d(sjhB+@i{&KpFfK**}?m;e@9e9UWS8 z$UPmE4?@TZqrX0~YHYr^mXcN!XS`HihE@0~V%~}!Vw+2ZnN@QT9Pv_(_93XR*+*;8 z7ycU{_T_Nf8g^^SOVbz1kLq zXlsdcf>H}%>UKb$^n^x(lP}vbnL%_ai?SO7=mSh2ISs5YTZ8>UWl_Krq95a;Is)FX z&eDkcV;`=SodB+rxRnTRazDD*OrSnDt6SYXXHtHsTtZ~;s3K6#w3@lCXVo+b@vp-+ z)N*hxIHX#ea5u_JgYb_SxUve7xhDh+XSMAtMDyKlXZse~6jJRsnb0$LQOT^>f z6(I|LDrjus&$`F8u7wf~PHAk)`TrhMQ~z}ySjahPdfPa4hNvKy3rNyII1B=BD)g@x z6zZUrKbuACRr>mzw>E-xvR`CllSNIr-}c4_PFrv7=1hN*sw?3&B`fUZR75VMd@9B1 zWLP1g9G}Xu=m&j_y_~|;An#!IQ=eeh=;3W7$U?wllU5_sahcAnq{S;LWNMg2Au1;X zpdm)(m+kINCa)>NuVOXCr(M7O6 zI~1`f7FaflqtV70+;O?=5Ul|^y(IIyW>>vaMd)e7(F$milPh~30_>)W*LmO(Et9Gr z*H#UG%Jj}Q!@v9fD7sAyo$&mY9XBX=5S_Vag^tPRpqVXd<5k0tIo%tFKOYl!N*|i1 zTrUpIy%8x(sJZE!3DL1yc#ng>^wPZBjBUX&%GIS+UHe&s=e$Ha!Mag18jVqCo44EX z?qu#~rMoUgyK(p7x!sONZ~Nxif)76Au{I6~pA2oVoJ z$_WaW8|_x5k>K*&4DivAxrKYg>5Sy?4s$0zqI!u@ zIe;^Hv6&LM_2767We_*!McA%zqH~#_$ED}zm!2+g6?o&hMfc`aQs-5v4sVh=k4UZ< zgr6@pkXTNM$O{obBOZp;&CTb_3r}hM(qMEEXy9l-MHv*ra(h0sAdvhr#JK*UTtJ3aH z=rWkf!S7an=Sx@;N#z>REhtf3hQ7*&pdB}=u;`5j`gY%698*TsZX?g^`8luDVeM^Q zqp#)AKowr;SB->UHJSL=;waCc=wF=&B)uyC6cZ>S66fd4%dLir=wp%zI@h5+7NCCp ztZ|)^rnac3Aw#Otopco_M4oy|x>q-T-Su-&PxE%G5R6TWV%`St+=d2wqo(vo$!KQ9 z+IWgqEQx}0&dERKbtVZ!$w_>*nvA~~WV@DBD=>iS>64G0o!eTtnA9Cw&gZ(VWw*ZW zPZf#3yc>D%mOq7ZG_3y~lequ7Ff6m&psb6K$xJUjb67#=yBv$wy2sl^x+IIMBCn#M z=d~5bL|}yk#3qyLV5BwQFUdeF>5fSQEBQ`g?K83Q%}i~(wONO2WjIY`Tj9G)sLMBV z#9<;%_`gh3r+f2f*0kuvgf_$c#?&~W>sDhl#yp1v(UK>T*;eUwNVbC=)#bt&_q3YX zx05$Z864#Ss%7+}43d&Vk@WV8$jbzKvlBDCOx?NcsqM%mrHT@s!^jwkmPC+B%cXSI zMZB#TfH41@OrE>NNdCM(D%nuFrgynIdwN$2%|KqYg-l-4#wcjRny$vAwjtCznxNdqxFBC%XrAwbDm zdfAu&tXMd{4wogHXQ0{Ms5bWUAxp2FAk?cee$^>) z_}4_wdUd_rJIP=9@zCg)*ad~j4*S8oZGt?C_*qz#H~n3!S(ZGOse{$QDi)GLo39EB zDqZWJVsI!Z4%6LlhjvCPcZ2~~NMr?+j#j30kA#lW`Kz5`kcN4cuTqJITyWHi~&wA(`q!R#SCkg(FMd(lJ*bW&*eM+vc-|2SIwBjMY=GCynB)B=nn? zu&Z`5@RF)iW%Nm@I{P--F3~!xdNeeh;+)lSJ6ntwUS!ogD8rrnoCbp{YI(&3aqjbi z8ck4Gj~a~!O?nQ_RY>Em9fN}fs#?)90m4s+2L5?`)9-a_)p9&5Y!%tuaCa(t*Q?5# zq#sJRNiX$DL5vY|MHFHC-7c#eP)lA+) z=W!QKI2D-TX&85(O3|7_7izF{7?bwy27vu~a9)0QmlP54xl(QaiO-*Z8*GSP#VA8> zl2PlGrb|D+Z>3jlO?ka`z1L2Z7~u2x+UxgZ9C}qAi{4g@N3YdBhTnBicVqANrT%X3 zCF#_z9slMaS1kvH;t?eysRSyO0L8E0N4B58t3PN%w=c6+FhO2&7Ge>OeXyv);QPG z*Yxw6JhNX+|AB5)PJfLw$0fYCZd%HH6dC(0Zm=#e6OhB2j|1}b3FUmOj|`_X@~y7| zDG-g>WVJ3Qt?5X!31I}bYYlL}U{qLCbM76yYm9Omw z=(?<|%?bory>jJeKGm%*Up}0>u2{VVXJ`$NzL@$uXQVss!6_}x6VWmes^Iq(wjhBF zLEu%Ox&@mhtXoqFg0@NpT8o>A2JsIn$0rYh$8w|jV4wxlLe+DyvQF>tEzzIJHbN`-3Kc;TaGIDaN)A;c$e!NM3zDa)fq6OF3Tas1A4RH1ISB%_Bskob`7fDOUo{@4*9eZ7s zdzQC}_vy%A&8O42yYXpi33uwb4OfZu+ewwPw8XsB$h_3bywuFR)K1z_sh_mKJSlNB zYxU#79-Wll%;!lnM|0DTJ$SWo$T#t%5H+5SsvSi?a>(3|w(jB;>&qJgQR5+kTM35o z1Z&1K22TN{zHwtamcpxfsL&@EZexFqM-_eCpdd@)R?PQVk;1p;ny`7 z6wc~8??+Zm>$IVSC5YSz9XVAD)M?-4IaO)Lm(Q`~*G9u~no;X?Rft_&Cn}-K$GD6~ z)~dE_;M5TO8rH{4zHH1W?XKDJhKCTQQQpciE4bO_wb3U>ncKK}>RmoLeY_Y6J#L)U z@FB#@qc|+8ZHw}6?d{t-UJ-rVi*Q`RGXOt1{0gx6-&g@JMVv42^rI~9k_6D%*aPU9 zCS~J~3k{Bbtu{VVtqnbNad#n-V2fCVif*AOk2ppU(>GYpXbw1m;Ydg{By2_G)<^(H z91sW@ysiTDj)KKwP-3nLUKjYPD1h<03JS=i-z$PVLhV?4Ut;gEWVa@NE{823WFo$)@_LM7W7 zR=^txe?YmA5j{+HV=cfB+ZkQ%umbLNcsk@1wG&xE>Naai}=k^9Iwg9Ckbu?_e6U4TLqLu&eTw@mP>;vBa>U?2$%T(VN?e8DE%H(XXpziGE-guI4fj_nOXtt;xxYdr^1+&a_}Kv)~)z zgF$-s;ws1`@T2EiLaECu*wmz#==8-^Nz;2^ z@gG$zm%#-ihNt4S6Ngk$I=f2K5#Q;k3X2Z}2B@+HuqN^-hq+A_KdHf418yk3C%wrp zT5LePT~Q5IML}GlbeIKB^MpD95p5Y3-t5GGVK&4@Rrpnr0voH0bl0FMIn@BaE>xE7hufD1<-A zZ3ywnA%~}+a(pPBpPJx`sjn+ zb*NxlM0dc|!C$9fAn5~Fdv;D~k0@&*%?^5-<}p%&B~j2Fpn*<2!S|b>qlheKJi^*; zSfrwc?LeYKvLr4ECl{WSDly=QswjdD@ibsYJKS1gumG3)R7s9i*o)#4zyNA}x$%gP zV37J;_K3EL2ZsDd5qH=+=u*-rfQtwuP)*%%-T^ZaeUnQvofdYmR&b=&mo+ca6$$7g`XquQ zLi;M{2s=pNC#X*1!3c^Iydfp9RB#L|Lf(M*o;^eg$YB=gt%DHXm%K1q5Jj#6P>c75 z27rL>B*KQgFftMaRw2p{aD)UJgjfbNzKD)|P*@q%$rTQH4R{cl&BL0)!~kamO@&K= zy`e0e4w>JhJ07&mO@pz4HRfVK*}+)D%~u!Zo+clFbjpG2{s1UhG{RvhH zFU3uVG9;8tylz2Z=qMA%8%hBtksesNHH8&c92$x?6~mq3>xd-l7PKrnUYn-?IzsxyvMURT&1A5J*uJxSIWO*$A)7c&?)WkT)0; zu&u;0%!h-SZmcOudADOMC{&vQ4@nLO3~t2}3W}2*Q-PEqV?dWgVj(*t*m`YocFp9x zBn}K^g{ru8&3Ge%)+Nv6z;JMwgaW#W#qX>wj=)1A5kx}JZ{lebFMrcuAU6#$MihCL z2Sx_uzhUg9-GknXc5gh_!RVO4GztlC0#ebaxU1xfn39w?OU{}=r6DwfsZzioRzJnu z=ERxeoKFa9@D}*jOcoKqFu7Av>jdtC)PmSl3LnlrlvKLpMe8g%Vmq!2949c5!vg8m zO_sbU-WOBLp6LD&_Y1-q1fN`%XfTA?8ZkCD7pNN7=wctkDBZXEpoR(w^h+;Ty5uh#A+V8`4ff^J~ zixUkjCVA?t7l(SA@`%0+zpIJSs(5j%ebKBY?F4`7N&a-<7fPmmehSqzOzl}PG|CWEOcjFreW2vFqTTY5rX2OYq^ za!qtg6cG{LzP#La3EZT5M0JC5<{~N`26~qka%f175Z7yfI;3xFIuDIN*Q#IiDJa7b ze(RT97R5sBpFV4Zl%sw}DJvikTgBdi=MkJk0|EA+eX35FEv{PZ2yK#NL&<{?6hW(? z{9)tE>l9YvfPoJX?1>R0MjbMEute1r2y4+57ez7KkSvq95!#G{oygyV`Pc#Oi|Qg( zm#GMIiu_Aq`znqls_A9n-t(&BN$s}@0B*hv2qHTi9Sg< zlB6TZZws1wh&_PMMHtaQ<}Gqf*EOmMW&lZTh@9jWo^b>s$~g zn5HWi5nX10H)q@HYRWffaa}FSO-#h;)#-k25~GEz97;uVdUdv+ThFMfxK9bKJHO+M4{v# zXZS^<_tF>V$o#)z3)=fHipux&$sUnv2-$>T@A8(cIwrso2FcUw#WJs9s-Iq6TwJ}8 zGq^Vg#sE1?{ijZE7LyZ`>L@3Ki`<8#9o~C`|M#`B3K!qL)##iUAU~f+*V3=$grr7l%SX>hOQHID~Z{xPf*;N7z5(R^2#2L-g`{LBXiCE z)+|h4w}&t!udBlY&N_3J*fP#_mf|9>%CGVW-maBA{IPqs9t$+%4|Bm^I%B-TCGMx< zaLAD?jni7x^mL3k)5_|0L4LG^N39d zP2hBR2V$p7nSDUq_CDn9&$j_2@+@4KV#9!Ziw1Jo%J%IT{hVD`|dV zt|WaUtUC-Md@QU3Y(qtPf|n^T0sJuhP;Y;@BI6{T&r3l^6* z3T&7+66PR#MAeRsg(RKW^z>bZXTKq)eQ8`VqofxyH!9x&j2mp2Bn#v`&~)PVQHRPn zSvmsDJ*CKlbw$`KUeRXGj>}`jhRk14u?ep!5jJ*7u!#sH;bSY>N9Ta56?ucqG^~A> z6cY$zc&;d4-VWG2WLUJWG>+l;^J^W3ZclHbqmJGSWkmL;I}t|866Kt)9!?hxJzX@s z16?%E+T9GQ>N5N4<>csg@pAH-w1cZTv7(%pl?bAosvv1L({tb=Jr}Qz)>>#nu6EhXU@k~LI=epNQscl(jOaV z2drK}JQ&J&!G|Ms&?;eyg+4;M8pQmF2tk5KBq0_F%5);#oKWOM7kIoqYpuHj!Fm9bM$+pV@3x@ z@{NiPNbVp42a*m#gcQ}NF|i7gQiY*bRKT7Qx`|p46Np4*gsZ$#Xv@}+Y8b8}E`}gt zDRFTSF@aU7vHo|MIGzEDHz+xXHLx!vBa|D)#3;q1wVh|NI1h$tLAxy+p_%0rE!CdB7L zhGNL&2N+*K(_#M3C@adN;>4;EY8zV{loK3i5{@xKD9Uct_@&ICan!RI4q{}WblqgS zMtRTv#O>!dbAfF5!_ztv&zvC#GSZ+s(K|3xR{CM&J!8ZxI4UouM;Eu5_je{GsBJbE z5E;5bWYXUN`_B|J4<^~B4pk_@8$}hwH}z-QZ7u!I1eW`48K{>St)w1L9#l?i_5j1uKo|Ekz_v-^9-=FI4vuocQM=s&zglUm<_7@G4s0#6*b# zXh;8Pw?$RVihwac74*6eblpp$@}iMYTW^CJdR!P?h8w6}1k^?x+M}vsq`!7G-Jhaz zP%-Mw6-?-)FnU!`)6Dxnw1eD48f;KNkEZ#0zJ_`gOlO^BY*wv-D&YQEEf*7(BuMIG z165{*{rq_ze#9&*cceZ&8K`c9r7Jd5KLnlVF`USr zY%1Mqg|lg_>J@7k%J72TwyY2_^z zwtw!&>w{c>Zg1a8$j# zJ|wSfIx3XRE{ZZQf--6xWbC?Aw6b1IX%`nF@wmMepj7hxu#P{lKvfS z2QArfMh};$-AmIr&b5nikRkM12Y9zg#JT{1I>MlVg1{JmYw}Xd zU|g<_h$ng;bInx}(>=rx9hpKRg20Chr5Sn`a_!=?Z3G5$zZIr@9mO2}w@4P+q(K{D zGb>FKA`|3ePLmQ>K$wEJr)=>g;yA<-6@N4<;AuP6U)%xmDjdcUE1{7>#e};Y9t*Ok zmN9R|h-9db!6R}m#ZEvWht9Ky29U{nU18h-G9D8v1HlzqDr7ue>@1PGA+jO^w2`?* z6>ubzAF;eR(J!&2SYTr^kFpH&&x-PON*YBPDNv;@M9SE~1qFLTv}R6f=sS3&Jt*iJ z``9LDIevjebHGxaIVs)&c9yt?kL-%&x5Ji4@`gu=;sV*Ez;XbQ7bCqnnNrjp-p2^3 z5KSJUlauc&>x#fqf=F@$YyNTjZTa5eTq`$Ce4g>yRNidV$cUqyOe|_&LPG`FK;DQy zE}MZgO<_-PV(UAZRa9PX`}mBaUPPuRwWU&AkZOd z`+lgOfbtQ!Vrif4TEm3&NviHl_uN;L*xRm2M81KHEg)Q4$arGHB{FJCRgKvuO=E1= zl&cyGg6tQiX#}POw2A1HUj=P)FHPewlQwzZI=~M=ouIIhPqVaL;Au=tCNpRFtoysR4n=%aZ5}7|iquNCYB8?FWvlsGxu}SPUty6X81)c&Ux9 zk^zBEoyO7QA4^}+yOea8Q!#9fpIR*xUbwh<+p4I7G?7pzIH-XSmC#+jRb2)2@Ypt{ z$Li?6pqNMkkH?{6JK-cHl^7mSP)zX*Dyb8+T^N1I8wFQAEGVJu&~?g;s#S%pF$%+i ziHK0?eok$CWgvj~k7x&svZ6dkl@&5xcj^QJkQh67F{6k~2?(=X zkW(1ip`+|!wgushB9p}0P(sp^RQeN7=PR|zR3pYxfd+FS^sHr4*@KR#!r4v}VKc>4aIw=d7 zB+Sgk+-|~ZFNH!uAFlDu3Mt3SaqI5eykfboAKOv!Yemz5jlJn}gp||YJ(qrhApZMh z-#T_utpu}GtK@xbtZGMjic`&4V=4CCEp86$7d78Tx?1mm8U520Vg6;8@a0|IdM@u& z!%5xc^l^5hT#p7w{t$Su2soci@Y|ttg4R!s`1`rU+}V^Aj(K@&l0f0PAhhE9Q-O`>gFPVptoMke~$Q_8xXkvl3HZuMEC6!-4 zlJvT;tq5T*gsb4J83X}Ge(^AuBXAG7wa*#ms=SWDY7(XW$ZI!U=_3#1{IeISy5|kj z>WABts;15Rv&O|m`IL;WXQGL;aLc*ATpHIl42F*O2rCB7h!71D8rd8yiel<2R>BZ} zm?MmGK089&_}Njr3Kqw$&L*p+Y4jFT9)&E#>j9^0NM{0}K*$nl1LK{Ynq^i9dF46} z#m{(g-G|7Q4}E{9b`@L&vrgC!#2%2_f7zCU}ahsNhGq!O(7Z zr*_4$;7cMVLz&Hu&%|)|W;qO)%;KBERv{OMB_eV9NQa@b({SN^WTV-J=fZ3jE*v{o zv$`Cr-BuT=E>T|7N;=*pt5?q6DwwzfR z_7cQ{VJprc?V6E3c(@4tiJL@B5WHvjgYyoNy$SVz!{Kg=D?~w*aO_RdVJPA^3^U=CE*eyk5PyXZZlyEcN(mOG)PqtlyCwR6-oY4 ztn6;E#(-RycLFjgay5D)q$7f1yETjQnMf7<4}}h<%JLT@7$d&7Sa!pz;3+*nVF-7a%ty$3sC^c!> zxUnjD56Viw_ikqv7f-=$M389JQA%>V5Pa_1tQ;3WHHb77H5#u3VuvXt`dtS58X8iO z@%E0I^`o|z{)tD(8B7Eficnc_&^1py+hWU`>zOO@!F->eH3wyzsj0}iV zJX28(D8d|WwA?_VOp%y`S!_c>af>-&`t|^;h(pxO3O0<)T45lOS|c^ysZD5TIS(Vy zHv(})>G&LpdfbM7)(=wwIiWfbgQL<$)4*v)K)h3vEI4`-;~Ijq36K$00d^0xxef8G zA12|?2;?nCE9C9iDuB@;?SPFtw5jlv2KPL!R+>WDa-;w=xZLd4CZ-jl24EDx;OLexc!6f7&20#5{nQCch|UZG z&bS%@3;;EvjCN>})s$fhwiMEsNR35IGo7Pt2yy)=(evQ+)g}@)21KYeIHfrA@6;r` zm%Q#3(Va!r=0nU0xwN*Sf%U_rO|(YR;+P<^sB7Sp4CA#8u5M_vLvS+`gPe41V;~6> zWU_Y`c4K{;5U$FnLTEj?&g1*XUB43tv%X1CHH0-vQt|Ud>w|2=tclg!#=NX=5*dNA zEdexP+&E1*Ct{hlopD*;BJmq!^aOzdSBwUjO@|2D&a-T45#$4#!ffGN@SlVR5_Wz# zWCscZBngDTt3Jg+@RKmys5Z9;W*`sJ#G7*Cx6Sum&84`3Q1rlx52Apa-pf!b5r!wy zZ%^KPVfU`WaPa|LRO94Qx~PR+x0iRnT^G_x^6s~aZ5Xf%0M0&n_9WGWFEHaJiSgwTOZ5RJJFRcvWk*|so&LIgw9cKA=p z&y+f0D}}wKUCE}2+Ycru<^YmT&@VmW>yKztu4J#yo$XP=vY{N%e+C>y4 z5v1JxWwaywqzF>1#5U@9OT&bVRzzhePB=mdkBB`$-1hDC?3Q*}VX^bFV>YQG6;%MN zPs7_N&@BzKa~uW2*%Ft5>`w5S+B@3aMul!^m&~8A8_+unhlQP;LL##?yEoh+8Zk!= zfuF4Epu;6b)CzAS9=8oxazy0bP(Q??!gfnJ>_n*CU-sP!FEiPc7%37jB0lKeDSE!h z{2iN>3MM`FvGKu5HDRat>um|vm@u5VNWMsjS!?3uJy*ol40z6~lPvQxa3IoXt zi6vm6cWxJ%0k)ZNdO%3RoWP;*4-C&XF~s)6fPaQuKoG`A{0SHaOl+rvx3sIII>& zxa^q1y}?YQ#*@JzOu=@ZVC(8CVj$M8N;Zr_Kub-=mj_6}qg7zI2vXo%7^FhUDG?yc z`w$s$)GqpGd6K}&pdiNVR{Yk(#OqPB3cN3QJqE0R1jV(1l+ZHlj45<^r3Qg1=ANN# zAzU7ScMIFKWz$$)#9fFi;Dw2~gXygTKR!rk9W4V%BDe}@*;rv{9>xuGe8*<1IYmuG zwC3v?c{8oh8X<5ThQNuYE1jW5UmAJ!+q)8&ShRzSfbMX)QafNw4t2$n?+D~&s3Gfa z%%G82OWUdBdAqj@Qj)@0!7O!I0GbkUX~4Up59lh+89_W@E^Nr5DhL(Ylq1_R!e zB9>em1rG-TO``nxr0xR9?bMrBg0PSaC5koDJok6ZF|}z@TcVHI@Og!Z-q9+Xj_P!@Dvw+-C9NxXr?q z11=$w@pamr>%ce$VhxSG{?Dc3NC4`0<~q1^av?@0p&-Ywiv$ZK#@x>Afl?|#5hE8z zOw9Fw)B<~OBz6bG2s~H;VGHmF+_)^qcBhQ-ftP-k!-ncc3ysgSk|*wXlLzn}3O23e zhM*$UV*vL%(r(*`p%N*SOU>6&N3l!}&3|Qtb&*}X*%LkG6pYLM&?ysg3??1M*A8<1!HO?+t_N&cz%+idl_=gr@w{8|i(tT}wUDiD@2hOdQ|M%wLI zHBe{<(CuR_1 z9JSEv5Y;q$fAS`779}lG4O~$nKEUZ1m@DV${Ux7at4jWewLZKLGGJB$Q3od051|y| zRxyu~!bO?aX%ZrTWj1EiV*VrrzFV{FDG&Yf zx()zP)D+nl7J5JDlP3qR^s;Xf&WPH;upqJ}+;qd47wR)|a>#8!Bp`H~vEaPFXeM7T znH0+OS{?(K@1o#A=mWWG5zzmy9Rv=T0>7c2B|`|2saFpl|w5nD)yd$G7%WEzP`U)Ic$}|LP{uviR@Sif&Bv=mk9p#TWr2po-*{Sr&2W*1E`J2I7&uJ~J?MwDO@xI- zO^82;=;ynD^jKsRj~IF%Vjt{_yhwgK%10!NkVjZz^84Vmf@eGHE4zM(=!Z}Y@pv)a zKs72wkhm1NAuw`ihWjJ;xLr~>VQeJr9i%=!jEKV$L&IlATq+uMlslkK+7hg z5@F-+E#rvWg@(p4h{3{A!9zfgVR$4Odbf6=a^M`4WD6H`Y>%M#A$4{n9CG~cq$g06 z&V*p92AiZOl8$bdo`m2;zTlXtu~v|o>@MrXx9;> zAaO%BrmeWYpd;Sn9?4c^{>6=ypbRLbEL*!bENUJzEvy%$4Tm^}g*Lu_yQ(M&kAN=} zP=qDs{6N)1CTqYuHY}?EbhK#fO_*$XgK_g|cUO3AVp=m+jF9CRCCL}$`0fqM&W_{7 zu8FvpR009b!=xzjks#^+q@%W9f4>$VeA}MvtsjL_aDXT9j{x(dI9ptQvpj{;etmX4Y+j3Ze^Sf)Zy}> ztpMFTMD7u79Ha~)30Vfn>m2T!wvnmqOmP6+<(VflA92wrX^2SxjMx#l!=Q7+IK3G8 z0O2vm0f$TcvmC`c5?c(}_u0snp-dUL+5C`sCv34T1E@ z4(USptK#kDe0jAb<9CoT+$J4*IX!!6i+?nc6JbW1O=B>Uq2L%!>O=cL2$ujxC zV-rKDNi+PbnaM*szBU4}-zZXt=6`!;cIO^joT zJZG-8)1>uz`kYMQ?PXG6d68tsTwbjT;ZhtC7!J_BNZOd(T*Q;IJR|S)pUx>IIB;Nj zkR`0HLL{N($7F*4dYoTZ-2~Cyl!eoGyot9|zRo=Jf-6|a$!5QM-T{&P=Fi-PQ$dBo z=I7gzaIC1tw>#SomD{PZ8q)2nG90oQ^qNsZ!?#W5L>6r97HWQXw5+F^E9lZKpI*&P z{@v&mdtNHWF7pm5F=y9R*{uKXn?BQ{Ui}ms%$erv#fGbhKFT&24@7S_L>|`9`Xue^ zmFZ(X+o!#TKfZeJKPI!GehsGWe={p`I?MAe`uFY}_(!`W#ZmX~%>o2zMg9AVQQCdd z7{eV=g`}^-w`W6Z0wgTq>8#<8xG$`~=lZRB!iuLIK%uafHHfWRrMKk+;Va%@%T%;t zLG;>9Lw_23v-&g);HBUF$4uz_wB}=t`}h8@8Q|Ml&4WMx-v3>?S=Zjq8aYh%Kl;B~ zn#I0{xBjjFyY6oD{&Kt`srAL|=JIHAEvq3DDqxulzLXr*yjtY1WJ-7^km9@de~V%K zU1Fff={#W1pMCb3x|{!KBKPt&8eK!BIL*%I>FoY|R%hPN zYT?B#qZgl0SB)ftP)Ez!-rHtl@4U8mzR%@I;gCzMTZv*q^kZrV%dFlkSXnnxm8e2c z!9;DoVO^5K8rff&Wut&86*-v0d1xFu4~s^8oXoaw&FD?#x~Dxg=3spGl=AO>SfJ1Y z!cGQs3^1jY(D>S>Q)PxGEbRJNgCEx$i}W;1c5tDWa0L6XYZ z&{pUoa>6Phs^ZI_MfV4DX0TW~6?a;u+8y`ZpVeSa!p$6Xl0HYb&mP^kjXg-1w|P2h zt*OJT`8ml~%=Mu%TJY)bpU*tT@TTsnd%$ROa#Mwa!RYY_j29AH!FjOrR3kxO_7?c1 zPz#RqN(eK5(*R;YZLQ99-;@2WuSd8S`B7n=QgIBgOl&Ux&ZiBNP<;i~^vW!M*3*N< z;Pl^P%4EKtk?~tMc1Npr)Y7%fk6P_W8P@Idr+uuYpT^TC z>-RCY%5&Se@*DDgwpAO)c19b^X6Mh^_El|<SK>0Oo9`3ljy!KEBqT|QLfkYd` z{-sOIxn=ED(LU-~3S!KLO&GdmbaZHJ748C`#me#E!6n@tR_$o>*&r7>4+WK z@nCzPU`<%npmmf^7n>Svx!Ff7#w*$L|xf% zVB&d{G%asa17*+wbJ)IF0fi!LtnW`K~*YaSsYmtl(#IV?rxVJZU_d$Y+YjiBGueE4^yOijzW{j zZ;p~&m@1HsksSuBZDL9iMu?cO3M&azWjisKwb^=OoKVMvOx|sWvI0a;2O1&v5UxY` zpmbbzW)Fqzkdz9WL1SoxBO`u5fDrHlW-&^WZLe%xfk|@r2855|gfmA)8WFA{Ey5`2 zp$g1nEk_Du+y;T9GUXuGGD|}ANvuNqHUoBk`b>Qm4WC;j4gj_ZUV!&xbcO9P87}Od z@*{K=v{+3Pbb>^{7S1{wA}<_51yxmGzFb67zX0X&-N)Jy_c-M`papkp;~oII%$zJd ztPvXts54O*iFZy?o~j-y7ERIrWUStIS(kke2J{N8s)e19oW?ot_IO0)!P&qk)*L5&Y~?|;G!GDB&jwWh9r)L79xC+*BhaCy z17-@;#lk0UzY3m%Xf-$vE=Ut_jyI~3y)a1Z{p?2g;hE}$8yX2iW1*$!me2&8c-9U~ z;?k)bdQdc+DGy;ma77Xa5@=HfN7%g!c5=saq9ke`i+gO}NWZP|U|OcYMi})oBG5p^ zc(~LUF(JOxC1hEN{wI%5B?xx_As!EXW30yA9Dco+nGM`I^5eNTJQo@K zm}0UoC(Ci>lVKMkAFXZishh8LharmhS@v_bH-4snk4SclmCG1u^g(u%JmSM%FzjI5T5QI8oS{|>UF+wA-0WUh7i<}mxy)#BxJa{ctlUHRj!wGj1;x_Hw=FJ`e zTnWmJO(!8_mNi(EREMFu5Z(HtwJ}TT zfl|4JbRs%~x7MLBHgTvJWySs<9Ie~jcpSyiZiGv%0pHf*qQwz)lXjCUbkoT(T33B* zPsO0(p7NdWLrs_5@i>o-6ThAv}>5l0V z8KfnnlA7wt6u|F3|B4Z9hth5H1Du;$DfO9;G3pa30Hy8CBLuKosjg{y&D)?SWL}iE zgF%|5R-Qij^UZR8L##$~R4^=Gz8qWYc{6)}(#Z{X;AVE>3~iS$xjZkgE>504!BBq! zt$TcNb7IaTkcZ2#;5z8aE!ZsGrAn&uAY+FoW6HwCKvw>zn;AC!QhQfXl_MMm-j_<; zQHTZfT}x%UlB+w=o8G{}SMi!|_x#3TCHLr&5g6PWYM)+=QwlYCKAM(aOlLmUhieyG z9WZFz*X48uQIotIuS)MMrlg$58uSQC=ei)*#aB=AD@k!CEpJXp0Y*R>g|mW>7h}#r zR#DJVnL=DXGzU$~r^J%Cxe*bc+Tn=m zt_V5#b&jvU1f~Dxjy>4elSL<<+{9?~0i6_d8^qq40J}NO;{`~9K`Gs%ca9oM+9WV5 zY_op<$^OyTyiLy(*_W|fl-{{Eb)eZCKZ2qQKYj35$%fM{6`F{RLzwEqo^o?MiKXZH zLOd6^mfLENTe?46M*K#{lN@2*O1v1dKPp(oH4KGRH99xpaCu}THg?BVosW6;{Fk1O zc=m8Z&#Plz`0mXMo;~E)L^+#^NkI*NPlLir-k44CC$8(5nj=}m`XL)-1mjW<>v zd%5;C*|9fm=6*TJhMTO8#!>$3ar(14i0oKy8NYvYU@vHN{ESh3B7dM5XzE#Yxxmcd zm8)1_a|N*@yn3^@Vq_(LR0l(4mKwh)Wr+27f_b4vc$;lCf9vuxwY`{!<%Hxb(nx>H ze_6{nM;6gqN_VCGT-9%Ip!66YKEc#&cdeoky}I+AQ{mv+=rO)~dXtP&uc8jZJE$Ln z27|A0yGfgQdtqBa7CLZ+X@Hwnu^z!%xKm##c5?M9j#AJ-JgU1Kp`fxhPa_aAeGEwL z+p5@9H<{ADryET<{r8wkkFRGqCc@nytC(JTLZmF~`A4J$Ckq4x zi<{$@;>VVT=Jo;uN5>#WK%$lz220`!XMvo};nQH>B|pME8Hq;ra(wYxDhl)*-%j2v z5rs_o#Rx^vbX=M4GysGbS653jWGk_7On%)+8=I!5r%%54>Z`y0<%_TW^KU&Ppbd>a zG4D>f=vN3C^GxGw*m}?2hrE5j*;n!Ph8sTY9e8dx=h1tH%@v!Cbp;G>1(VHhxMZrT z#${Tt-%M5U_8lu9D0A7WfQH432J&95Mn%11b3w28Xa6sOq(nj-Br8^e#0IcIr*$Lq zf&XTg{8uZEvI`i>BJMO>2?nZXo7siZ*u&Wx=>_iyI7?L|Aa2OdRUT^ekq7O!3aNyb zX8;oD8JR?-$l!hw1EL+hcZmVF%wP3wLIbWmz)y0L#3Ek1;@I`^*b@})#nI7`!y8^u z;29|Qe3JZp>m>>9ch5ce$E5novnTHNi?c*na66AXIGLu;Ryq{Fy-nQ8Z(qj0uRQ2x zMXbETH=Dn}$J@&&@^>fGYu8TxYE~3;Q}Ftvyoe-+sNm+veAC)f0m@1B+vD5gi_%Xs zplMoM?M-7G8yqbm<{L$@TRR;7OwT9&#e=-eg6}95Xfp%6$xF8YSFg!*2ldDdd954H0sER3W*1LcxVX|-vrcE(a zn_BgUc}uIMX;~5GbcRY`d@+6N@T9Z`Gq9sXP9`G9F&Re2*SFq68Go3YrICkU@s~C( z0hpKb=>;I&00qw7g3LGi^&dEVi|G-~AZz#iAVGbNlzV&x3;YjWQ0|xW1nj|by%de- zPMoep@CdL7o`4j=)2)d+;OZ%0fN$gWRs^Vj;<^VZdh38)A2 z@e8U4-Lqgdo7VA)B{S@NbXITYtA~r=|%d?~CZK*Uu$CIS}Sc7JQk1^HZ?F?p?aN{&e~(Nt?HG#swMq+TO-od57*3MBo|K zWL(9O@M26!qY445@2g^XZe#sr^Ck7yZfw=|*^RB*aC>NN2JUK90*COJKcPSwtf=G~VsT&y~e9Z_=ZE!p74$ zxY2mVuJ-9L-*8gf6m9wz)J)vVv)UfD_A$xT;3Tzd>@ieaTdI0fECWw$`pqbglHkbR zNoX%qsjA7bw{M~1kcHUF7t`O=&sp!1RZjte)<-grlYD8?nLQLdnA5pO?fNZydz)@; zvM?fHj3jfO4}>dMWFOO1^r={l7zPP*C8tRB?goM4rYgZ%MMJ?77w{@USGKL<=bR?g z;ItM&*Uz8Z(jl%%<{i@^YD1qBLjjjUmxOfj|IgmLF1K}Md7kxI5qgJH+0iZtBuLyZ zWy{gBlFoFuQ&w4WbXMfJ93TivFi8LgK#`RF6#Yc=Dm@buT`@h;F;Oot^ZW1H+2;Zf zlt9VOO2{T~&e><**V^m8Y{4XME+Foh#`fjjYq+MuyN%hrvSHwws>x@M2ZOkbI-8py z-7;nWK707Sla3`i?eQd^^{fSZ@*+)9($PS+jZK8n&Gsy>o|7srZJD>2Vom(|!M0Gc#*$zbi`f zty$D&Hwd1NnC@?}Q4IbsLvx;@DPVrz+=jV{um*c+4es;xl)$oor0;c;B?@Xi9``k* z>-}3CXvx@f17gqYbC8um`I_}HteqDw`DyashS8(WPb2i`pYg56!SSDE7?bT0r-^o-ec9Wt?3WX=4W8^)~4#P^hhMD%a# zdqWL+yn%DlWB;+tUm1(%OX%A&g2$hhBL!b_Ew}FrteIau-n>h;8*3SJ>zmg1dPX-c zwVvsXOKoI;eZdXm^(59hJgh3-mmE+XIXRwgQ`CjucZkS3Hap(7EcxgCkvwt!F zeM?0u9v$GHZpPUghNFr$4@o7q?M3s^b` z_Tk?0U02DH?(4Rd@8uRv5A?B}4Q4C$ru|#emmHF)_zHg{UkIWeI z`&|$G$Bo0A+xXl5yJReSR6ZU(Su-X*O2=hnVrEc9Jg1WWv!OB-*(g%-b=)FaQn59I z)T8wS)zjPhCwql;guUL^=N4L7Mb`AM7x-s|$sjk2>wjjkD67HJur8!*SG z5w1}c?o)!n{G*coN>M5OywPF?pManS+pBo}q<<#iSL#t9y=F;2(pfNj?;RA6>hhX7 zDLZ*oykvA~KRj$_x>D+XRtooPy7EDm2g)a_BrTb6vy6baQFrFmz?z(E8Q5Ic${wYY z6iA}gK1zi<;BJf7D%Vn4Nhh&Ed1X6Nm&mcoYpA+56fW)BIuOz@*Q}Esy~A4RwGH5; z$0eJtGSj==EH{Wu=x13km24a}xvqC*6#6Y))Z>pi z(dGayQ^BWWG#JUN!cGoTT((baE{rG&jK*C6;V!rdfZT=!-GuMgY|h*cES{<*8|;p& zc6-ZFC(?r|$Gv0^w6xe}J|_ECjHYtq^2$wSnjm6jYny|J>?D*5FG~jQM(V!oD8fLi z*p5$eBaSpw}w$?hYHJ?eUPuNqWwi}W#iS?z4YGe^sl-5+b}iV3UifP>MnFOoMH zrnvs?(wXP!hL{`4e`qK_mV$h&jp$MmRqNLq|G{ym?k4DPvrojePj2B}4{0~T^@Zn%(mUSr*WLLkSI19h`FFUJd*>`y>skJ} zHz#&&oOxqSf9)-Z{k1oh_18Uz9Q<`Zd%ehB`a{Q+Cdo_gxv>^^_n1`cR^c6?-CG5( zf-~_nUU7G(*vwecx=PLzHa9$Ln`a!^{%MCB(PfuQW2@+&_qY+}kT*Ju)%FDW?EEFRMJgF z*w!j(`bt+j)w3>L?N!hEzt*m%2aqC^{d9)U%7#-`%Q`d0%L12E?6*nJk$1?-#y?hOV`LllRZaR9; zGW+#lANYlkf3#Vnd3tZ3lVsBzr7+d3+TaLL4>isJUKfq>0)kVzY{0d&h5&YDHAHKY zjxoawL<7`du-gyOA~WT3gI2i>;E@~m=0WG4=0;q6Duk<1Yr>+Z(O89M$@u9Wx(7BP z)b#Hk6aUI1p+Lr^N7qLuga&0J&<3}O6u)>;ND%v9jJxj9^d*u77lg1B2Oi{{YFS$wu zT_S9t;<7$*)zIlpxpdzI26_MOqhdG#)9qc;xufO%ATOYpzTbjVDaVy2n7yDo_X8<^yu-JWCs z8v1U-@43CD*)ij;Zj2P@CM*>e<@y$^)D#pjjusaz(TsuG?a}4-E`3b7(qs#{SvT?j z{AAwlsU3CNk2~GZx)aF*(xK`>$(yX7%nj0LjnRy{dfZo#7Fh#b zZsLFnYUEoNr=M6aL~6V6VfhKIM)0*dAz+Be_UbiR{^!ZuMAn}hxVJC-Xb9gQABS-H zwkJMt4PCq-#tb(0-%8*1hQAiw7DYBBOQBX!|*e&{_xENqD#ZqmjtSx6G_=sMrU#X=~MKe ziN>X8|JeS>{Vhbbi$l`;kafHhN3CO-iV^9*oGlnt=mp|$2Ep|$4ap|w2V|K6ulAR^1}!b#>39-Vhlyv{y9q}2 zofrzD=2yYh92|e6nop!IV@ecr7ZR&RsEiY4BdeKl9Zb)ddJAJ31pCA1Q8~wXF!=CQ+5G&TU)hQNZ_&i<~nBCzN+wwr%{uI*yIK+Ht zdq{?Zde#OvIFg8PeWam)vvSe zwxd<=x=P(wT5nXR>F*Y&EGMjv?{pBZ@2>8Ta>Duu-D?lmFHIC~9hzf;bgz<_Yn!LB zxeXtb@_!E>+{yrEYI6J^$+KXzep0F>$6CEIp}|L&;;&3{vbg;l$3XEC|A{SwN1H5P zLoC>Ir0O=N0wxBzD4oF2q2s=s+Pq<8^fH}ajMaioS*Cntih?7)Z4$F>H;!q$3)l5q zJnx!@yRwN%Xt{@jO!Bo(-Q@i%ER?f)9Gstkmtk2kj#%9WtO{07A&3;h^FHU(>HSSObRG3BU?lgFlP_s?>OufjJMXKy--_DVdibVT}|E|d>z^gw4A4=?PNzD zD$f_=xrEYB%yNHWp9dctLFFQfQx!p=0-VR>zCxUPFOW^xqU=3PupKIJZx*`wB93nV zkI6HFYqG7LJyxn)ePZvLX%u%m>Ts9~KZklR501L$`k=8>)kdkEpZ{B`Dh%fj<0&~` zY_NVt^Z8?&G&CItqWsF_aTC1&f=B-D?QIbFF@4qJT0E?>>~(hh*?G{5<)ERp>59OcXF;njazvMQs?#bP`i1)WApVf)NF0rFh3gVA5=-&bGOE9Al5 zOGbZbaM7?A2L}h{)-!HB6m-UGbgo>^mpp`FLuyEud7PpKm`XKK#{U zK$xw{VHL>`+nG=GP)zn4v|6)8H5qx#7x39mu%pL*9{>1j?*fHSSBfn(q4(0=HZ z?nKCE4dM-WOzi>qjY6Y!->9psR9cX!#$b@!EBXNF;@k0@o`p2w;Eb0f3tuKr0dJuA zsQ~-W{IEd%^e+JQpa12V*7K_=nUfO^vn+k`j7iPWCt2#lCtWVS`qaHTnvG9SC!-0y z0@-X!E!%UC$6Z#<}qKoJ32wBdDd|MJV4GdEw!^m{7tu(WBTm%hm0W$93v z)*6p|c#CEjTlf(HZ)Tu}&61@|u;xSPt?!21*CUhtlh8qVM_3&mO@j~YBa zfDUuqNC{4`tNVqkJsv7?BJLGGoGxY;0Y|JqYlHW1fc4ukNAM8looz6OW-V0jHOKDs z^$EG$rNWKmv57@ACK*irQG{qPw|YlJb6hxmi-<KR9&uDxl8RGzs*%z zA8nGX(j|JyT8zqgB*xsTCf}b7p{U*D_o#0p-iT4P>7kRGAk{G@<8_uEJ^Q{oEZq7u zt;Dx>w&!VS-HAq&uW+K{ES?j=R2tjFy=glvURLwKP5*DFXV&XUZHdwbX+|uNR#V?b z)`2`9lIraQ*_7D0^YQ75oS)LZ3@7KLVF%mFU~rZgPSIKmK7Bn&T-CAS$El0bT(Poi zgQ~-hQ1UG!P&?%Z25IKV%FeT9w)jC{RlHfaQ(Ifgq$9QEe@TA9o>;UBN8;*GGKS|B zB82>ck+IAfAM<}L{g{_{1It|V8JoW+ag0g(K!2&$p0m23p|buMUB@p;g|cdHeVEA}2E4H@hb9qQLk>-+C{U1`H|4f|Y@8PcA^d)mv0vbX)4gQu z(zS2H4#=|vAAl9kSe->M5YPB6$Zaus^}2}NuEB1oxTj>c#@P8$suS1>$vu2vtardlDk=UxgKHs#*rL2o>~0(NDi~Sc#q#JmisN^xU40aLtDRUBv-WG zT8HIE9~;e~bT@`E$QF#@%-Gr4Zoqh{F;G;=^lF#*Dc$hueT&IZUFFLA#%83BPAp+Q7d^BPt_C#Q!dS?689d{V)dI@~#oBi^`k*)=|xedJG-0)ejCjtwyKOYd4ymcDvthbzANJ z;Hc&!MdpeV%&^y|zWSiwYY!SNwrsmaZ+xK1Lh(+s(`Yf^PP5+Z_vwDOP3=BzWv+Oo z)<5XfH8<^Uui0)l>VrnJ-fHkdEN6^H+dg1s8vSOIs9&8XfplLKEb_JzV~fvZPt6OIt$fpcA5BoVO>>n zc%pS}9JKpP23vvIX%AYhLATeT;z7P*TfB47Y&5%_X1`HyP;;Z(ZFe-#%(BL{H+#)a zzs+LxT8(~p(5o|%xphj5V~^Zy59<8iX9WELYc?1#D?SL7nZ~rZ_I#(=X4CXF&fcIw zf3!;FeQ#-~^$wf9+vzpB&CY;z?H<*9G}Bz|`0B8Q`|L~5BAac{W3?!rS<|#L*dMd8 z+Rb)j(5GK6z1>_TRBR}B8lcbevGv;R zZoS18VBwprd%OwL;?&+5Fe$CNhS(o;yMRTX;_((wi>vSLE)WfB#9z6~CTusGoh$&Q z#hDo>g$AHgX8^51qupxM+w?sK&$PJaz25ABe(LP+W~Z*rqcX1&#Csp^Af zAH>)iXp6_2_E;cMvR&D}neA%I9SaB5x(7@GHFd$}Leh;vy)mF0g=$T4qH4Cfy?VC? zP}1u*uQkk!mlv;fRC~RvcYrb$tH%*W?J*puozX$p*P+Fh5_EQ2Jq*MyJJ|Z37#e ziaLklb1v$8CwiaC!TIWd?TSQ_RFBo`t&(bT5V2e2om;Xx*JWYks*q}fdl5E$zk7wWZ`sS zTSj;*EsB3ZugQ)Y^g66KXoBMwv{hPb1zcEKl5 zT3u+4dcVyI`rS9u>fHj2rp(9yA`upJ@^X|m=xCdxxZdJG^`}f)60(7b>DAf#^)|!~ z7`r1BTr62@9Sj%?LkCzJgVq2Vquq~yJF1ienwjhmnr#43h=$GF1N1zkrbTN5+V28C zoclm0rJEpAX5J4xvkgaVLk-Yx!`$`==VE^Q-IpTO$Ic(0pHJqi$tZ}bytvFd2*@ko zR`y>7w5{D|J~4Vof5~^2$#mkW!UF$s zGKA$%3&Ntv-R>e;!V70|<>k9**!aS(LR?UlkAZ27)9dvPh5UfUR}((A2TdK z2xZbVzTcKt?;iBWwMK6ZuYR{233C4az-+qTte#On^PKv{yGR2A2o|b+jZO_4)zL)O zaV8!H4P=cX*TCs6<#1LxxI%{XHyAY=JBvmlGlC19FoJAsSKkq8WoQu83uHQQrUjDi z$;Amx!&HbvY67Qo0PJl(4GgP({ z%+6Oxx)qxWTj<3bq^gAUn_Ljr3h&~}#c*U@j{i#q2@MhW!KuF`?DD_=&;Rp({lEYE zZ~ss7fBcXC@86Pt{U6DH|JVQiKmYZA{oDWbfB$P@rG&e+vUb8V4=DjbWFgs=(l)tO zc`$c0p?l}2?4`vW-1DbFN*hYBCW8f9p^5@tSLM=5cSodFLi`BP)v2$VQ8h&nmMHq zOu=cw_6#7_`n}#j4Ck7{kf-pQFl{Y3Akm@CW{y`1eeNSqk<_l&8gyVfTfH8f?7D6s zmV!O5BY#0egU|-{2aYTy1v68JG_c=?xrgyaV8{9mIs@^go^H$(9vmS3X(9W89Bp*z zJ>sku11v3E+!k=j%|Pa=;-6T+Cc;i5ujq7<76X1Hx6q+5A}E@#cO;b?Ap1jjhKwXTv``o>dVr9o zivXYjyTe4o#N>o1p)fOyNUKfBCfo)a52jI4i+Iwa!br+kV1~&ShT%qb){V$`KWk<- z;9!7|I-C}AL{^PX^TwK*dk08ikq%NF(v41!Ua*_i6ecbKCEceF3nIpbKF4B$&}UfT z{0ET977jGay!MlKYFLXDJ^+pKRhos%m~Q;=zuk$=$k*d*j9Cp^#xNweup25_?7Jap3DrMls?Bw$N5*i^x;6QG?##0;ajk;cqXZ&7paWRHi8J@p_(BQct8|*=q zu_Ra*!O@t0U9gJDU$BjAd~ez}{UG)U?vW!@z$d0sKLwfZdr?H9;z>1;Vmdr`)3Xsn zOb?JTz})Oi5&406o79-Y15zMjJeR{F1gpz!I3DjV>@|y75@wXW^akFp@CwCsw!nNc z^QhLB$rkX=BG4*U_31T};l@Sb&eBy8icE{m6lg6K;Uwdzk@N@AR2MA?_`Qn&(bEVi zJtk7DT+Nvx)7KN%4{fA8+SENqW*N`+n$L-xX=8Xx8;*kGYc-r26OW3Bo+o7t>SER0*(*Lnpo|V#9Wxhig$4KyL9+bZ;|A^wIZPW;! zoF!H@lfopBo7j7s8mrNIZ=z+@hL`L`y<+4c+85AKs?D}7*7%BMRneovF}$f^70flP zI0!UsP<&yGF6D(5nHKXikZu_-Le>a|$IB=pGnlT7Pms5Vq}r9^>dH$IHKA#hnKV{} z-te0$s!uQcoIOTWFP#{w$LUMaRQX2CdPRX3bYGubeY$n09ibQ83h1Xk!aj8CthNa& z(%FJo#=dSRW%*f%bn0)Hbk#Lgix8eKo$FLU7b?1Ecdy5J{*^M^p=Yit_hryX2-gB;zO?xcAw|GVWKCH+wiWa2CZz1BfjofDot$;5YzM-v;a>{@bx;ks^ zm!to)MD{jveH^HqVE_78B-%&5X+N+VudurNB;LyhQT4Dw+a|T>Rt{3OLfjNb}0wkB*#Z z{wrrXc=V6S1;-A7i!MHI6vDL{v2Z#0cpHftOAPXeKq<CF1KjKhWo)+R+#I7xd;HS=)>$0@;i3*XyAymW6t6f$OH9wYjT7QY@ z8V#lg)nU41zCM)l4JwjpnN;#@rvYnIEY~`aaR@>^tlUtx8|yNOdHojf^ja1aHz8=L(>>w_ButY-}a*}#6hlfW#JMK~&A zw<5H2I^7TZQ25YH>4=qL0G|+N78K(c- zQQxf`OGWNMkmXv|S5f?w6Qrn6xj@O)Qq#AaWKHOuL`t5Xb99@N2n5$?5{I%2L!kP4580x5{kJY$)S_xF#(N{bYs*ldTkL4- zALh@7Gt^u;tnP~It6JGYaOowN(R+JdFPJ+WqLaau-cp(wZS?#GXORBiE`^#$*YnvF zKV9XN1fSb9i$@cre4Z5YkcAt5gxKs|1sX&lk-rav!tJrSZR%nT;h%m{amM=1UR(hQ}6>SLb;h8+3rw1eM*nb(Oh4zY{ zdto>d<0b65jELhvZX_ndbOR%%?HP_g4RL+Pf@+WzP7Vgi@lEn#HJ-m7Pm`|?l0VI` ztQ;@y$C5DJG=9&FCte)6jbrfm^4!sej?_QRU3v=8KlU;bcYT#DULfD8-=J{Bd`;wE zSX&|0YRP4Ue_n2sJ&z^FT;uT@=r*rj(yHa*Q}-Mq%2rIRg#Rk4l};OUQO-};q~WjI z>GkDWi-{wCC|+cxN=>=bM)i6Re<4Iy$c>Oo$&nISQVYv7Hy4?*HB7sZbIFDcXO3K>#DM{dpN{4@E5_a6j2w_Q4GlBTUS=Lh9C@=D14Iqsu!!J`Yqyawyp#L^6@R$19SrW0Gj z24Y79sfYySHG|~0tSn~#h;Na9;h2TR8Zu2x74iHlE4zo^F}}O_+2Xo|aUQMEg>Gpd zYCYC-fbAMqbNFRpBZ)gMZZYNc7N)C-(|7DicB*M;9G=EY6E-J%-sf2c1f7bH0NEb2 zv_I~$Y1?I+_KF@fGVWStd*V#qM`VeR7wboPL1G)r)=OoK@#eh3*9r-zW`e?O3?^Ta z_{>NJ9Ble4Tp*FdH}Kpro8wf?So5+_%XfIcGgqyf~_Kn4@7Ac-B4@_71*5Q|g z9VpV+9v*?LB-=w~o|#1tRkv|ErN5ZGB6Gz}4990gu8~wWKf(^Zz#kA396a%GYiF$e zNPmzij^`fL4se2Giy(r>^}Hv0*;K}Dir)=buH-t-e#31OEdraiDQIP1mu$YPJ>>dL z7J?}NU&vPuZ$?akbJOAAP*ryk>VpCB1!kh_>{A>SBl&*lFkpKn+i9>CVrrm}m;;!q z(oQ#$k!OpuHtY!IuLS^LYOVjdNJaVC;&R=?oRxV4yD-28J;yvcRqAAmB?YtURkhOCyQ-dgB2g{g1=#v`05-s<1$c3w_0WYdmncNo zJ7s6aRgIm6>mS&%g%Y6NZ^;InU65PXu+8NpUuPfo1xFZ;8MQ*a6eNI5>52&Kz~nc9 z4SCEGCt+RH*kBK^{>CJj(*{F(4AQOYEwdqgPn7RTuD7yf#?oItzUZ17`90u^?~#YP z*QiUofoZpbJaCd0At)4=+cvuwn|yR=Sll<+m;B5+?w;Ni;5eRvbo3@o;75Th7y)MY zxfuyS#5V6klo-!Tlz#LV;Fs}uX1+u5o>YyPmp3@1fKL2BQ6Vb&LS_O(arx{6`#50Z z(5xwBQPDlEE8ah#FK97Y9du%#Efi=R-|K(~$7iB5Fn8)U8c6;%=-ImB74WTgDFa5S z1u7N9;gHRGc4L?^Q|?d(r39kvb=(q!Y!%TWw?ZElN*mp~fGK;d4&jAw32qDx@E_|- zsEru?hW%*Wbs>{5oL8_9dAQ>;QC3z#0oY$u2=?VDV!b%SWM!Y(Hlb?8?2!AjREl5> zwA4VW99J^iCN2)nm1hT(rZmEA3l68;!usL_VuAi^vQGHrptFX;kg31=;#~v=D zd$0Do#CU-!gE$oKFj{`3{X$^xL8FRd5jq9|0MaZyu^w=p=&i*9Xz1(EArK=E@ zNvTjg0;YAK&0df}0rVPCEjVw8n*lu_(}QwM(8J-TFDj2ihCM^n3KKgew{<);iOisS znUAn#u$KT{_`TgmSYP^EZg>-geO zkeu!6In+N0j7j0_X@fY*xdsJ&fs>Xk1`KNt)C*~=^S!WjN(@lMe#V&B?=aCM5Ew8m z&{Tx8lGO!FBDH4bI~1qS;`cZh6^)7I1erm^tSOG)Sr>*xj0k}uWNrrU%&DE5?@&7= zxTt6%!oW;%3@fS(wBVZJ!i|b+BdZtTbBH_<1(=JJBLbqVzyg4wSs#K&fIgVU*y<)r zQ^R=~EFf_JTNiGC{i1M5ndJ$6x64BVAZV5aqD`hc;JnQ8d{{{B-i_gfiP4!y|DiQO zW+1{gryb|4Rm4N4MejLs17d>o9#On#^x=Rc@p z&S5$OA1!u<)rc&WGR4^m9qc6Wo`cR1iA5YTpo7WJ?1WG|5*PIy9ujT|%)w7VYYtGg zw08uhI*^YS>c{CR`7q%SqmU)Jn0*L|^?At;_6D@TMmK!S)V9!Q`M zBMj;j1Cs8rAo+mo0X;*k;g)RntsGK8+a*OgkJwMhXhh*F3KbQzBcqv5Yn63d7aXLR zNuo&!KnRisBK)j}qEL0v3K9l#N{f$S`V~?xo$*kVy^1JA2cpD9NO8H+VU$v%p(sJB zpy4PB#{)wOx1iu-a%=S{C={hua371OUNBqiLRl!XLp)g-iVEe!gF-_g>4W#eE13XN z+Du;LxdoFLm>Ozhdx3dj%h>EK+En~6k269!ItWn0G;s6+_t48o)Ih=RPzvQ(AN-h! zt|hlzjv}Z|Ge6N%`h81mFvuv$p+Neg86dzUU5wVp7iE1|8{%aV+6vAX@Z>%4d9*&h zC>>=WItunt8>l-D=kb6&_EGtFBezCCO1@4F9u%hos00dORCJ*VSsiriH$()ACWg ztaoI$)xR&4V+5yNTz#*xC?Pd7aYLrF>2&;x;KFalM#-lbB|bMQD&6zh4e`ru3g&EP zHo{vFXkDf&mt(>=of1uCh1tfw?9$~Lyu7)XuV(TamJJDUOHedsQ=dM3uo$14mTeAZ zW8C8UXZrH7l#Q5Jw!E$}c7*_oj=gmwEH%I1*Se7$<{#JvINJR|=Qz#k9J&9)Dq$sM z;j#j5lB!dKJbdu8-HT3txNw-%U9Pg1H{Nm0J-*Ie&kNTV9)-I%=4TiF*mHM&%DwZ` zS^gdFpe!jDP5fxMWb z$~e5ut}4dXmk3x{KQpOC$vF8LIWFn1_e_<}^^w2hjliz=Ilpz=`JXu5>?fa0KP%;@ z15(e>-+BBkq*PGRZlU&dyX;W|54PFm@%i`>*(fu)C~s-$){NX%pHNE`3WwKjuGQz; zYX4kZV~K|OTArrYdp;(%s=CoF#&LaCy*`Ttud*(T3x*lJIm6Si(j~?Ab*<0O>%E-g zVO+9eFDO!3NtQ%NzqpaoqNN3VMK(LBlr5Rb@!Nch*-5vW)$ljUM7Tg2hdX+nfQ_R?6Q!zirYDMh!(wimpbe~Gt1&sPM zH!E6;*G_;IXOY!=KWES4uo9`6n@UApNrNVBdL2UbFrdR+4I9!$+MStA8>qL<=-6>@ z4+GBCZGFr*=fzi2V~Z)EJU@%9lI@FyvFmmizuT}kXW3z>Of(s~%r<1-+paNZxobSx zX~?~r!;We~pIociWl(|KO6ryx@F$S2^@-|zr^4Tz?4rrgOM?Rc`n7moIWZl#mx z3#@B7o-be4wCr0t+qO*y%@>X8&Dow7h~p@lcUAtAhNRh55iMA;NERf2+7|YycYemg zsQ1a1#!5V9)J?77Lz zy`lLvPjdIune`-@&QXQp0K^X8>fVHj)&Cz@PR}I)J%`q#?}eN<)hsf2{0eS`o9^UE1Ia}2dUQ_ z5{A_NL^9!QC1;h{B`hWgr>3!@3Y=_(2`-yKu}N{@geDqWJLKU1z^G8c6#jO8%Qy z@0v_WttpaOMuOok2$2~i7*3jD@XS|7M@P1ojeX<2xvm0?VM7I^c(5W6-b^cS5uzLF zf$M|$<$8ctpOIMWgVSn!o?sWjpIEz))YF8FsK?$N+~(=@yZ|Bm zm0)GEe2w#7nvsBHuO<(2hVqWM5s|&ZVpjKtpS7n8jMP*_w@D5hlr@raxt_R)aPop*-GJ*Os*K0vN=2N zX*PI?bwdYZrl8otx4v_xrzJ^KHj?#o`+~!L_UpaKd8)XTjQ^6!nTuh%8nf;MVY2L7 zgYn0>aUEL6f>@Z?bQU9VexsZ$yjV(+#{_ko7lnfyuRGzL^n2m0QC9c;DS|^!ts!-X-C`f%2~? zNp)Zc6kH%jRVt^&P5@Yw*W8jTb1LQdUaSC^4D=yPOg?puD5D2Wz&%I*EY9GlWNg~ zTfGWO%nvNjV-BX{)GuGCBx@4%Wv)beCK}w8aoCS}8N={Al|_#Xdc-GSls%8DlFn&o^CdVKtkS0C|qho#U zB$!JXn#Ar@Rns@4UXYDJ87v1G3m%d=R%s$WA zJ6&d<=Cighy={T3ed%LA`_^*L^GVlFbI(t6@4w1Df0cWF5n^b&2DxFfEoirES8)v< zYssZkF7z0d;+BlDE_TS?w$!{rA&kA(2XteWDdLr!I@lpyjM9rI`O`X=w>>>2&53y3 z+{aKHpYO6WQ93@7uZDTn7?virseEMR5kY)aMW-d=DVF@*qu4Ot6kJZLD>;p0tB#?& z4QH$9sB)-`?a)JEtfMiDmHP)~;mVtfn+|m!d4t-}1yeI!$4`XX37LQ`J9G#W8m+%5))%6z3W_0R- zskwuXAf6S}i-#UA2=XUr)sOtX$`)=PV5yGTJx*M*na6LD{|85X2VdF3C=_HsCr?Ox zgz)4gNp-uPo43%9uHpw{+>GxCNnhF6iX%J7&YMk-OSB2a`^9WrUS9mink#~E+zX$f zc7@ZC!W76|5zjCjgt5hM$cH-~U?|Nl!h~Dla&Zd77Zc9`MchrTy*0v6TE^k(2`+w= z#XKAfb_~>0i}OrrnTshfvyzeHCWYgk9MvLMgG_1qoyMxQzZ6sVC}aO;($Qk~uB=ri zH8)NMn9Ac7fc<_W-W)!0t>&YFUp3w~xNIs7J^O^9$P|ywLek=R5-=#Gi8nDx=!2X% zQ$uyrX)Pk@ZCCO;z|Bn=blD^F1tN0FfWE{ao7Sz+(m1Be*OS6bB(o>a{dl9MrP+J5 z%^o5agZvrr7{ZM(H`!57q~ywcaAnFsS5+Y!#k);jVUHdmK$!wItIO$OI0!V#s*kl+ zQk?w0;ZRUns}yli;b`g+lFu!}u{NlU_J$8ew-Fpy*NSp1>ooD0`IW$}BNtr-0U>?6 zT)>%DI+ISkDfG&M8~Z{rIvBE?)^rxvwd34k#wDRu6nI{TG{D9&0&Y!FYg!W5Izh19 zrCA_mUNLf_^D!-{xcux2VspudR4#`^XW*3fxGOD*=>suBpcRzA9`9piQde3|zeH(C zx!1El*cNiUA}O z)1=r{M7hw4^c1h*I=GHVKX^Y=tEU{}S_yTb@HHX^3B-aw8qUY+-AV0n0oXvPm8Z*RqC zVJ$NqG>sDujlec?z$V-T1J}tecSgV6)7II;0t~P_4#UJlVd;CMD@|8Dw9d$(;Y3J6 zgy3`wXo5;o!Xm8`#EpX-$d%X#xH5{Gv%bP%)uT$~#;6D~xEGSgwh1p};>{Gt#Hur5 z$YVuO1BMl7$Ry_ss~&1))!FR|(#5=+5dnN6+`_7dqH-6^t~%izG^f4;ig4DbT~<%0 zhk;F0tmjqr)$}khhVCSuMLg8VkslJLtn$3tGLSOU;=^#PJGeKL1;oK#<+Qq7)?QUd zqetOLH{=8lYdjo{_FhpUdZ<2BU8vF=(c~*vPpV5B&9qqLo(uz@4$nt8vKdJ)#&^$qbN&YltH^6XVoi34bwHW}}f( z&~1AwcK1?FFmvn{+C4=Ogn=mcysX0BU=w)gEJ*#IFUE68ADpB`P$IE|Ao;^0Hcb+}QTt-Wl5%AyYq2wCTdwG*4-HV9$*z@FP z*OYlnGeM924C%EERTYNwhw)Sa%FS>X&F7D;{arc^v@v++G_7||?3+{83+AnQTx(V< z6D;Mcei79t&2ZdD&2m^q71*o<*rjFHi_43P0JTLRT{`Xg({oIBUk|VT+menFg?urV zEiTg`?^OfIM)mH!V*?DJEzo#If2n_8{bOB{={+tQTr}*3LY?c@Gj2WOmNxN+Mhf?h z5tf%1mBT86bhZ|s>Y;evHy8%b7829C4IH3<>hA=ck(StO-gxW@OZVonKl4J}Im{9^ zoyQ%=GS&BZ<5(8`{-S_YEG$fc0#G!6Yqeqk+VB!k05CLamB}8kaNK;fH>!O;Uo8&i zKiIzhW%7Ll|EA?I8cmw%Sm>_qd^i~Sc08wLB3P$@VyxAlva8v=Pqm-_%n!SlpK34v z`Cp!CO23+-&`rR#*+cFb-Pci;9)`K|?%{)PE?1?mK6S5-X5-V-$!J2afU#X2W;z0l zdcmJ%Q7iFr<~HP3n|jTb&1^|qUx11qM(1RAsEnkG%|BX>S3iJ^UaekN&~Qxm8hJ|0 z?n~`RJSf-T=;G#!cDmbG`7+b*DMJ_KhA|a&B_GZ-(eK7?95&(kB3I7nxF|GZP7I(# z&DF}DPQI_=9Ner<9=UHhVzmzqXgL^S2>j>&?ee_%&%@E|Sm```=Dp-g=gVOf!&%*N zDC}e92W{aJZA9ggO!WH*bL|i3N@1@|E)Nej$we^zsar!E>!Gj!q4L(^)uoK1l1q0E z0jg0uqI2fs#bkzIDkgMdLuBO zv^>Y!<$B_1kl`Goy)ip!Ha$miE~_s__nA>}o7K&zrq|i2nMJePmYbX3yW<``YToD+8XL%&SLI8V(IWjTD^vCA%F+L)4ea#7Itj09j`{_Ke4WR zVD;NoJwxU&M(9{INHEoQqw5Nv$fgWIFiR2a+`4)-_a>T?@IlfR9ARw84sRi68qK|V z61^FBH#TImDt2R!mOLL1&)h7|j>+@+hRw-dAuP&rvRpwv-X!Gx8J#%-b9izx5%Xw_ z9%tAOnbjAP*n9IRxxzQhbSkOfASipJv9|sbWnoy2sZNF&)?RXWEXyNrTM@c1V{_#EVX5IGN$fWj6YyHu^b8#>Rlzg)hyA3Tt9c@*f?-_9)nI*QGZZxXh88 z7uoA7=qP_@RJe0exHEt4-{PoTPXiDe=VF}vV7`7hKA(-e%d8_|hdGMXvrXfmtNsIZk$6@tz2l7;xMM91RcoL;Z09b9XXA<;$$s)l++&d`f+`eNpbJkh zSB;i3Tme7Pv-#`jD)fWKC#%XQG9uege*f}^Z;zgQ?db|*!m(Ohj(uzSw>aHyc`FQP z2Z?w6(SGC-@CGViV*=i^&k-4?@%D>!TzOeWSyDFD>>1ODi+yC997^jfBdA&@>!qJt zKc!kkm*h+bZ0Yl%3_9U6PC&pWu(N3oRxR+*Tw3yWt0j2BAJ#7HrvuM_;W^H*MYTmny7w?@`cX*It zq~S@;w7O>9LYX|U+Ej%kRil#E>4$ltP}nUG6}jpSG`Qa-uI?tdl6?AetxUc@%yoP6 zeOQgMNFf$?{cOK9)!NgO&wIiBf?Ls3mC&-M8OHHM!%+Em-{4(Aipwj}B5QHj??PAH z$W)y^$M6XLfYP;N74_3@iR@fvWEz?&q#sd@g*l;wV7@Y()G}Db!ID`LHWp^h*|1lp z{e07DWqX;*v>{@Q;+9ljRiMT7&yCCisd-jduj-{M>jRSfoUV>~To|5%X~XRJ5fryU zG(qZW7Qk$Ow5x`~;HesBk0*}~)BbBp%!~(Rs~eHsq?5mYT=#%yk%_iEHBwD}cHQhd zmMj0%9`k-eEr72Msm+zI6;=zJkr@|d%TfA0vGBL4YQ7%6x`fsFu)3O(Mw74aQCV~D z1{oyPv3P4MZH*^T0}v=|TM+BJr~Y7fmqCE(smmZB^>`t#d})MOT|#) zaEzS2=gMS%`e=jDwo*aJRHFw-Y?0g=Eyklvh^1Q<837Cf;&WZgt7Y6?-=(N3quMCh zU+=BkuxgF3W3|TDMYYD)A4IKDGQRuMbb8?Vn_?poi0oMq!A?)wjiBSutm70#ca==pl?mWto)@DOAQ=S@|%1mQV zLaD$qy8|IZn%%!?2=TqdmmI>XU^g;(1Rw8cMEb*j{@*Tn@8`40a)~5A4&bMDz8$YF z7uX$0^$#}n*1@Nf1t~X(T#Pn%aS_ZCTrF7GqB34gj!hT}^zS;2&UDzD&yTvS$)2iq zfv2vnKIdYrhc)BgZyxIM$G_+4kFqGXUjDWD;hlKG4Xsx~1>#unnmI1^jc_R#Bk)$}eD*SH7} zQshIauN@USw_7RR0x5&WG`eD_DkQ?)oRcGOD$N^CMR0}@<0qd;6g&UHdk3N+!z8B= zd8GHxqrX?~rU=)FLQ2te$_t-&bfq9<4vzQGI^6Gi73m)*F4*Oyqm z8qyNW*=uh@m#Lk+Kn7kiH@TpH>fKaiW$oy@uM>O{3@zL^Jfq@#ar;K`?wQ=T9FV(1 zQNVt=gf`|HGvCi})B6k~lRAexX3W=m>@(%WZQyUikod5UNe0Vd4z>CI=`NVN5#TmAD z9C75`@5+mbt>M!=vukVS!z7PV1Tf-!UK>Xe@jEsswb`TQm9jssHd|j=0eg=fz>nsc z4MJ@*CTLQ>;Fg%BFUE`M>=MhrWO{jVjPoYblOc0>+gieVtLdcTjK{tCt~IXxg{A$4 z?bIk(Ro^L=SQH((z8}&PlBh5>aJFX3*1*|WRU3#%jjC&{`o7N#5SvD08WxPIZG`?E z#tj?bK=PsaDFDmyOdGvXJ^PilZ}=&DIraGhUFlpDuw1OywFHs^Aojs z{fDmST&vqyo8dM3Q=a2fk+ezW72E$Qw5Eqqi@Kz$qBYwK{StLUBBpdxs|T{D zEqgl}AE)jevJRSntI^wPM9to4MGig8>uxl7Q$Qxo1f?K!nu7HZxq~#-1ha5;xzgzj zN5fK313z!7S$)V3GjHHjS(0XjS@gkXc`2&T*KuX~?9rC)>Z8_{x&0N5CA(s3=zyqd zy|5grBG;C1jb^8^tS-IyRmJoX;^Q$vE=oRWG!R=j6xUnTx5}SR$Qa^i#D9r4;_bdl z-%^+%!gA#P5uC+`1S)-tID8FqhUY#+zS;Pe(t?vPome&T-`n1*t(%R_ZLNDNKO5SO zZ>!OXqX9=NobEwA$gO1&#G*I#5NH#PM%YaUsM`03%i2xN1BZJ-8_6FhSfrL5HWqBc zG3(w7nB1SJABbOivART9up&R#svz38E{3>GiBb%*agz<~f<)K|47}V>f^pCOxEk@@ z%2a6G1w9*dhCsjWT%?V1z-8FFg}~O*xldX0asMp--b_A+l9>p?=5C;@LmBhKBse#} z6zaVunGy=2l$OvV7|6suKqHWTc(-D%6!|1zF-PX-zWiI}od$d#&(q4pj`|YucfQl1 zMQK+;i^lAnJFs1kOlQxC;T$@;Caq1}OfJ52_EF|)tLd+aaS~l;S&V|SZ6>=zyhR%D zO3RhhFK7EC>(K$yNviBin@o*i?Te+_C))#qH*8OsH{owmPo6QgCVaK&fEdsf>`RSz z@PrL%=x_~vJv-4Bm-FzYjT->3tn#lXuW+tU6#SaB??%;xFRm$11x7v~K6^34!`3R8 zOiUeEWx@!o#A!kF5qm>W=Jh*ba>*%AH49`6ct8?#VD%VpFcpGf8>U(KaC4>uJRVNY zJq$+jztb6cn#7l5YeaBilvq(S%!%BmYlMq;iZRJB^1L6Mvh~O$tcFdjM>K4t1!wxx z>@W>4sq=T1!TTLV=_w4P5jj#*W;ghYEGx;1X8dp8ky9jOS$)4|6EMX26$tM(2>hPq z6myvwSZ&_5oipCIo@;EH)Dqr_R<-oBO@FhRx247QtJ_p>cwunjZC1T`+RgEa3-3ka zu5+wlF81^)_t>)??&a%cNVFDUWSGsb&j+#^9AL&+}nzc;K zkDon0t2OiL=+)c9)Ag=#s!^X@k95nlzj|A(RDQ(v2 zt-k)am#3oLO8SD9D?hDj{ti9J>S0vQl#!pA)+0n+-?Sx)b6PT=>R;Tu_nXJu9Bwmz zYv!&vZ@IZ(w!+SQTP^NQS3Jcu#=&_FuF>QMnT&fg#y@+4evdr!Kuvl5+ogKNJB6cC zM5$#`Z$U9^Vj})fOzgFvU~X_?*@__5m$8n^ml2V4M(;58DomRaETcT!K$&o)uP06B z5FLJ(XTI-)G*O~> zuoWqiIVDqU;YF*oN63O8Q8Y+I_j;*_HbQ#o`$jGe*4QN4N-+$>*U-6HR`u5O-GI+~W7ioZW*kOJ)4qSBy zsz$wz1{Nm8Z!JA9n-Gtt*-@&_%RL?IirVrh?}1ry?zWZC0()0nN%SLoVVJF({M zF(jZ_i~Rhg{V{XHieDg_jErS^(+K>TZ|RZ85O<>g!Or%bekE`i=P5k^wt!fapxJs4u+SKMTf8sa0Hd(WIL_VGEL@$khOo#z8B^n_1M`S`8 zAgh_pZ>)ff>r8$xt^JGDcrGiDuMd(x&6ckx zg#4XRxC5@;ERyjYnj?CA3@mrKbhz1F<|Hzn`k@heiM(a$@A%ifqTXNUGH#7>&qMMS z-(K!{2-WVMmuB>`vs1;W3$Sqfh&$JtlM$ zsD(@!d;G5Jo#+Tz8(#0X%LoS%DoAbphE08a)zKkcvQQ8_2Ci;)H&_VwJ89Ac#ru(4ty$VUYhtc?l@(vzlb_=B7-^^3euzunbK08w%W>p(QT4#Nom2zU=k&J$ds1uIsYLWOUO213M=OU z#2OhhNJ-aika&vBS!9w>Vu+AsBa|f9*juV)OeE?dEd<}BIxz`Zj%q%SLbf#cm@GMD zS0Qx>sh`Mfqf{$thKOuw@@0`Urbz}1CWEn&@uaQ1D6!bnr$|#z=qX7@NMS<8k2cs> z$xO0(>$G$+7grbj%rN6+G&?4rG|EI?W0soqhSW03`Bf)F7KwT^OY!@r%#B)n4UF|zAme4!M4 z>>QIYDi)#nykBn_AemN39y1^f62(btryZBwG-+wJ80kOA@t~$w`%ip|mu~ z`g%&HLv|q2$tdFzspY6UGacIKWH)=e$I;Fj(qZy4b=!xag6-O1ZXpOUaV5`QOsQ4bXI*lsZa*h*=Q7NNANscbUQ@N(AFI zNCrb%AE26MvdADUO~ND~o@{??F%tJEi({LFml>m#v@{?DCSqhDXtrY?LnRUzW%pxR znlUQf7x|LNL&V7fA_L;G2-2jNDuc{is!5-Q>g^uMelU+B6C-ee*^KfR^~l2IX$ozM zv@mosd6tx!5DcU|j~x(oZ%tt($02JU8KwYuG6^w(K$ZSZSAkqVoC=_6e zac0~P@=%dqiL8xHr625(+DI9>Vhu-Hn%q?NI!P}{JERekL$X0ur`$9PkV#8swvw6V z4ylbe&I#36C*NXjnpJqJoqc-Zx3*@=@%^o&Ban#$r?g1&Q zL|)+Ts6>ZsT{3B92cDM3yosbtBrPMm8QJ2<5v8q__7F$|-pRH9^11N)On3G5hS z-n8xxDU!&9MY_5+dCp*C>AkWk#gm$r28eV3^`J>~oRWM=d0VrS8keRd**=whiWHk7 zcR1m*$5vXpM~W~Vg-W7EamX0fn$j`hU=l`cvD1BGDG>pH7vQ5@l_W_cof~u4&Fsgt zaQA?WQp)nl@k4ekI?wLSFN^jJ8MEG!Ws{Q%gW+g_9C!_q*ybiW)a@o( zYr%|=YmFqi%7vDhXa@Na!+u%2>OUaN%>N(aDU~CIf#H3hSnoQy#5(r}cGEQb% zS!%OnURrtpF-+oNl2LM&k*7_kglLZp4H*~jk-C9@`%kfXrLP){}6T*>6a>d-rgwmu8Zi4Pv_!fy~h0g#4`kh&sP@0ut&bqJ! z9DZz9&MJ=h+{%Q~tPDAT;57$ICNb*5~%gM~kF3a&nX=xUi zQ@0Q95-CiAVA0)Ri}C{kU?QPt1Hz4YV&9u=u}o{sp`~?GnjD;z?vV_dFXpwc#I8PX zW;k6*_b9M7**ZCK;LS;psU)8|PBM5)NWheC9RSN5Q{>x(eCu_)$}z5O9PhQbE1Xe~ zRvc^E{Fq6ze}rK&J=Y3v0Zb`o262(+l8mivNH!W&7g#JyJEeueEg&FPPWc5{QBES# zUgs8FT`ZEn_H6vW9*eZqTG5 z?I@e85+ml8F)9tc#5QGn04CsEj(kzenPm*6Sw@&U%8?Zkd<8+vvNOueGKSJDBZ)O3 zth&(Pq>=8yC`zW0UB;+*ei_Bk=9iHvEIr&d$g0ScNESuUa243MpHtS&R#M~3L7zK0 zErUinMrC8<5x4bkmujLv-jHzScy^A7rewTpHAYWTjYHO$+US!6b!$GG)JA!oORfqG znz@6#?{%a;dR?kNG9^6Ghp6-zld|0u$VclVin^@p>EMsk)dA zv7}L8{A*0p<>~q)gr0E{WUPS6_|)&CJi?jw6~RAn1Mu4HKRx;Lv+w@IYx-BPwn_C( zCiZ~oP<9>N)n-c?-{U(gnzZb;g>~v84uP>xl%tYcDZ_qw`WnYos}kd#SV5KWsMumZ zQf&qQ)7{txe<=S^n;0%2u{-Ihh^p?O%<6B*dzjvmBx_^bo;Bt;P8H;ZW$x0S0`Ag> znWT|&P1UiNtGE|J+ChJ@w@sC)-^Z=%|d(HlXr9XHd7vt2)HS$L|*v z(MQ#e_J)P~#(o;gHLhOe)zxBY>l9eLUsg*b437O0BU>K!qcL9^9;R+$W!AmX$V~@= zN^BF$U)YEZ%mss3S3@kBxeBZ;cDdKJ4io<9P|lN4<9bOWrQhbP2*T=&%1@<^>l8!M zh%5(z7zA|-iWXYmaN>=a>@=lK#?eAkc(S|-`tKje6n0E%Wovr@5VChGWDP=J2X701 zE0H5g5@Z8P&<;V_BSq6OcKx3m6ISh$WBhZ9=3Z)jt7e!E=x3yvh}DpwNI?PHODo-7 zw!$`qy+NUfoR$CKE#Zs|alFe`q}zfmkz@&e0O^O)*&E&!Ylq!U8BR2<0U}NW#^U(V zy&}%iV0PL{&9JBgB!aMGXta_ft9xnClu3KRPbfr;55z>(E8re|0-V*C=AZ1rDLo+?r991)1CtCo4F|s6yo=gk7 zYDSt`R2~S@5ttxhLv+O-*wtMwjP$A~coB%9G;bo!LnPT~GG)7M1%X-@eH#2cD}#Om zVO_t?X72BDENo>Xu|orakQ?4!IwnLwJ4Kk1VI#6b5{r%ry*M%~1ot~`q=s@tqI4vo zY+!W4b(AX7IPDZGgJO_TBiTl!kJf<`0NIn!GQLu~1N}bop9X4j6jKZXEf+FU=_q&F zi1ZK$2Z0b|Oz55@Dwk%Vwaby+7!VgmFg+RQAZXlmD^O#|QbCd;l+d-xtb{qEnVmu;U@wF( z!L*P)niU$89CoL`0?tRlCz}`K!f5ufN9gmDW_C-#Kyjnr6TCykqT*utIb3$#4Ej7^ z5oHY&5~5n{a!`MZPTDE#C7B8)fLo2G}VHBNMt#ry+AeGjAXRFbxO)Zd)PN8~R?E z$zc45PL40m=N%I+r+(vHn`*+$% z6WbRww!)WMeJM<#1MY!Rf~5o?(I~@QfMXy6x2BXNI9o?4MIx@$4qo(7PPCMjMOlk) z)Bss=RLOg~B|%(;lgrAbFfEo~s#g?DsqA*bDV4PJ9ZVfyhdC9-UC5B?1LRe*EGbB+ z%q!(mDm~f?F|ybguyBxosRZv*BO7jbTfS@Smipqo#7)Rj4h~YgR}9|P6y??}0gJE^ za7402!NwAuIFb=jV%s8??VG~tzs~WCAr++l0JA~NqVK0E%=w^{Fu*`IB0CXGc(5O1 zP}`E1TMvp2h^ZM|5?cZuNwOcXG%an5X0~pL6BDW&$pxH}kh3PzS`F>KDH(%d4+M*W zGXm|ju^DOIh1A@7RBS&s9I_bfvLH4k*T6Q5`P-JL-MS?L`#{k$3@{KBG;@f&K`^x~ zUAuKl%pOt(jR`x7K2|;b7SnkjElGBY?HiU@SVy;Uia}ThUqw^f(zja=3gWW^%OQ(x zVh5rI=z#|Z8r8PM=UtkThz{xlt0-BN8e1W8?K^J@0ViYuhBpKG7GZ6O+k;s5ZH0nc zkBL12|6w>0Ot#p9kfLoc(zfL89U4-UM%lB%^SeM|K2v zgtk?L#XyJQ&S45ma+o4yiN>7*Nutb!b#CmuB{6#(wMclt5NHr!l`#?(EwOW23USKu%qX%Z|yhaq|r1kG?DjA`dB zu~Jx!Lq|aE4PdI^Ga+8?h3b=F7B+*)L;43mVSWm?BbeGb3WV%~F*NQ%a_$AI17VV9 z-$P3{4uOsk(IeZCZXTVM6wEuP#TXMR28dad8S-R+bb%qbkCwo*=ra)q!2pPMhD4Vu z8BOh+6k|_t4$1?sgPglB$1=p}p!A*7Vv;=?n{~)o$UpIhE%clB(Gp&_2!^E@Kwz(9 z1MeF7vg~|Jy#qWGa08R&Il2_M0=co>3w;T;(|&_2hY3X}1^ox)?)>0@-$0KlNhfp1 zS%trqabMc`u_0V8CkFN%emD?f_@v3Ue&-0*l6@v*0Cubpe{gKZ@p((#tq7;B1XVJ1 zfb27_H*5=53;Al}PGsLEp`rQ_2@Z9c6&~tNPJpOe1(SZ=^q-RQdgQD%H(5CP8ggCI z?P9ldcXLzN#;pr)rI+<7X~|h)Zt-%)72MXBsnJ#Lz6x^|bzY=-@LqT2-F$ibS#RBX z7oT-%(mz@A;^klrhHu|>&dxUEx^mZ}yu~o~d^sCpjQjF;n*I*cSz=TiEP0Qc%s@5R z`tr+MdyB`a;Y+To6&^gQ@wfTNIOV$mI{%Qr``f2<29}G_;c`A6O@`;*aDRF5y0qv! z-{f$_%q00cz7bfMZ|G0^ja(HU)Lw$?uk0P<#G1&7zE%Ibr2ft=-r>bdw%&rS;cP*k zEP}GH89c>KPdC)d;P|*f@%$Bl>_9m%3`T7rE zfAvK|khUMmA!qX^fByOB$%}8k`}XNk^7U6wkDk3aO1R}x+EMb=Vn}p-TxJ$zO}eUlKtEq&4W=$PD{(Hx{h zJ&}8ly7hWCSAGHNW2je?^K&JhCjH;(AN=X)+oylzg(qKp_4QXT|5;uC@~fB6 zj$XV-zWnCfD$8x8|IiSMl2Hf=}yK< zZ{V`h;ng+|xr#Tu&3+iZ;kBXis19-ddc9iB|MBoJT8cwfRJl^8uiUROE_vE)N6&tr zeDh^?vsHKXIehrwKTJ-iC*xC$*mGF*hxys7A6`>{r}8OWUw}}m=knV6-Ee;J`gf`_ z+&F)AJ~@`L{erlL<{^%cyZWdq{hz%2<5w?|7e`NDe)Y|>%t8b5z-yc0??ai$hN#*xXc>2%Jk6sY`(AKo0o;`dp9bSx=^C8JJhGq(W(0+aR z05Zv)?>lPAc6-w#?Cm9AJ$rfd?UzrU9^Lye-3V!CS39%{SSp2Ta4ggeel;cYiISEu z%WSQs3!(jI=vL*O@u9p`^!Kxi;biIxD81;Pl@qcDS8vHXM6>=bvV%Vhbx&u)%f`F# z)p%hrTfu{nHb9)T4j#tO(==kJ<}HF%2A4jyyKw&w%W9<-qn%t8{=I=5K!U9 z@_3huh8#$_4g|+I zzQH8a^<-zo#CB!vIeW>DElXeh+vU!GHe6jU7;{MTtJ67~)%O-~(8A|I;#-rj#DPSn zYMsy_i>jQ4Ln>hdgOxv98t9wLRVn~|5yn!>I$ao`Y_9eaVE67Ym4s5%>h3Y)G&x=(%ioTrG>=b*Ymy zDU&FfaigAbOpAsJYiOeDiqfK_oGPuiHN_mgM!mE1^VyY2$D|OxkYWHF?S&rXx~IfX zv&&b|1<-J#@d9dSN|r&6cpFe4{~kOFMCmxWP)-G(pbD37Xx-__WF!EmFVQO0fM4A2 z+R+*$q=WX;NT`f3jhKYmRk$mTN(ujbDBj;JYC4}9ypTgm`yM@gvV5fi7D??AMID*l z>vyN%TzlHPS8|-{R%R_T7llX*nWy=%3p{jLwfL1rQ_yWSGG3VI)<|1I3<*=6iR&%T zQc&TF>~q9ruzSutHGh^rEtLHG5f{|Ep~od%N^3EJ8x*h`lHzgl%eBcrP0VZCFM`gJ z67G4MIC9nIqr-pd*dL!Ju=3-@)no|^5{fFJ92X-ZCm2}G#Xm^fjD8iUlniE!&|(HB zCL57rlGOK2$`8{{9OwYOTm*^r9YZ3R^mZcQ9*%f}?)-MnLDPL4@k(uZKE5FHC85II zS>T&DpyM_I`yhF|8J2AB!e~DfewJ6M<0(q|YH00^V@6!iB@Z+q{Y9*ipq1A(0<~>&bnARTCvT_Jy<(gOyFh zV~;72kGqAj0c0@@?q3W*97bkKBSlu&6;aj3bB!~9MP76`QfHhCe zGnXaRr0cmpw0(PYkl0kfR+I^^{$?^XOXybxa)Vk-fFjlQagR=!3wyRa1LNp+IW8@X zcWvUWzXBMa8juS@Ul8sGfOC>q`Q&c`Wy`Jj-SAg1sV2GIX?7yV_d$q66L~k>`RY>C z%f8NKNy&*xW3uPv(W>Nl>+x#J-&P#&;L+;C2gMZMU^rkuWaGPWV4l(lUIMrjC7#^y zzsUbuliXa|C0u84^#uDU?CFIm}~TyIK_Ws!!bGf8v` z&Wl4iI^FG%7owPsGcrdZnqEz&mxeMOj!au12_)(q%E*Mq^D)9VQm`w*JqzT8b`KvI zq3=>+=Aj{?6tnST@<=UDKN-;X7euKI^pj0Fn#-dup@qj3hPm=@lI$hCxf||E_z5d( zqj#u(h)iBi;gII9ED`(>!0i6D6C%a!Zf2jQ-MKRoa4$YdM5VbU6E+<#4&qmUw*Z7KwlnQwV!`_Hxi zk^SCMAoGp_PZ3ELc|=GggMNst5FrssL38K_FY<=+sT$#_$3wX)!~t4$6N*?{#d}G4 zekFJ)C)@cTh$v!REF~g_L^$bU17YIQfm?>aGmI`D_;s2B+~6KzqERK$c13jYII-!! zUm&vOlznB4lX48gI58(_jJaBDbny!aEX-|_NYwmN5?Ud=6aJS3XHuCzvfRjt(VRlD zn{oG*+0J$W|1T5-6ifi1-+DA~FC`S8JFhK&P< zyd9V!j%tZA{nm@;eHqlw!S`mwBK4H=6VfJ9=dB0nc@~oW)rW&<1C^#(qf_*B0k-Me zgY=p@22mYW!jnY{4zUOy)GcY8DW~{}$ zl^~0w2LKFRO*I&P+6I=%lgWPa#Uya=lLb(?fXqKZ+5d)i-VlZT$>bksM&Ptqgf9=deyb9m&sGf7mv&;h&Q4eVnx0*GVV71tp2fW?Cfl-FR9M7ydI1m6BK*%9G}#P!V5sGjY`i7 zL$WnwI#E`GZP<50wk@Tdu;ch5%tWl}B6NseGo2&!UU4SOFnkTZ6WJO>iTPpNgEfe7 z^M_#Sj6cf|Je8I70s@ZR`(y|rNq^7Ni{t3Q@npVx`OX&@4vo{Kt+L zLWV;^$Yju}f5W_<+BW&j>N$n5aH}kyjG8VGEvZHvtCGqUMAsbq^Xdku$XlW@N?ey;O-1qQ|kp5P99wvBT!E0QQ1I1;iLDlPJnjVk? zHn0%l2#5d?a7|bw&!#gBxAB~U9+@x3O9(bgOvONQDOU_Z3Cxmt3T5a)(ZHqylg^>& zj|>gtkH*IV520F|0qnuI6^I~bw}{?Q z@Iy`#NYmxy`1~gE6fRm!TWvTC%~lg+{Yd8fN8;JR2UgoDQO2dD8W8!%!_gUzPn?B| z@#t~^=Q0{g`)7_o9U8WtAE$>uPHPPT$Eh5^^eP?Mq#U4@mtNhO)~$B1#TP^@FGnK~ z6?}`G3mAV6t+{o^x`cx|r^|A@`cdubMN&XHW2>)D6J_Z$j~nKM_5uuXCI3Hr@4DQ^ zm8}V$zoQ7fLzYh*8KMXZ07Xi&B}GWGT%{_J;)ryWJsvd!1d?KkxD_%%k+%CK=85`M zx+f<3#PmeR#Cd_4?_2wlJ8uAz0Iy2AN+c4w?`yC9?R9mE!Bwu;^D(?`fKEg`b^BWM z0D?!sC+d6fQ$`6%G;Vj8Xc1K%IDle<@W&z&6eZ%_JKGShgp(i=cdz)uh3n=R@=ot3 z`0RZ?sR2SbR~P(*}vR$Y6s5-s;Lc} zD!TCQEel>ZW`oyNxePknuZc4Pr>nF55}PlLZY2s!5x7lW0WG?X*~*Oqw@O@ZcGwM; z`y}puXIBJnQ#9pN0B{$3ND5sIX}}GKQHuP(po9$ zG$Qvaf$MS(rYixOXIBF>w-~L1!6$MMXFkA`_#QJ$XiQ1DnKG)i_d<8C7z;Y6KLP=F z&k5qZ0IF&GzPjpQW5Gv0f-M=2OA!|;v8vKm8O1F`s|ANyENX%JsdomkiRj0S&_xL= z4eq2vr^m~l6-!EQW?$Mq$kh_}Nzv~tPih$7*<5V4k?*_CVokif;Gve^Wm z!eg`PJ(eJrAonKkI*2Y7K)O&-yc{R91%b+1Nns`A1X}{rzbaEBB_R(k10@wvJTQpT zy-X7)Or!qPwdC#{d!?aRp-`%DO&NM@8zjsRy@WOohXo)ctQK^>_=aGxClD>}F#Lki z=hIV*kq>J2dM-fdZw(@$`x3GM*|@~e_j?b)Sh@~73mL%@dpDyYoiN$~JNrJeU zR`N+|pq zog9Ui?DG-0HD$|O9ZH@YOnYu9oPQ^Y{N(`l1&C88&LatvG~6{Lb_^~7IO6W!K~NdU zmY{IaQY{wD8YW-|i5`f7Vbtr_=F;%gF*Oidrzh%^LF{a>eTnp;jO8$L>Hcsas=fAD zM$~}`M>vFUsyYqD@|W`oBRzdA5()4oW|i1#24w=4WA8~vw5vHvviI@=4bU<}(Y!)v zMysRer7jO9D3`80|1ss5fqxHR(}P87i^2B+EPrt2umNS@3Q_<+^6FIX9uHWfYvO1& zsXEd!A-J5|K0uPP1jYSb32l3HXM?dQ$OrfB(-Dp~+O0w}e5G3lT2g!Ac2N>i9b>zg za2E3S?E7}(JD5I>#z9Z{RzX6Q6HD$g%<`;w7SxcqW~coc4wi3r_cnH`BsGdy1<_>@ zMKZRybG)t+@1*Y$0MC|=5W->%kS^B^=nA2brpn)L9ExLO5mW9a(!QD6J5sSlMg81E zpmz00ysF@XDW49+7t;o%s7qmbQ_=gf)~rV?q~kK_Ju2mt@1?Uh@ib?r2|wmAv^61a zRWb;XeQ`(BM>6A{{Ap#k%V1I9)}X;@ugvwArGXm*77yz0WEEX*++@pT(?uz-r=C@&K-9W1Xr zJ-{J~tx$JGe70!}&aSOkcu>_2DS5v4CoY_0ttLHKIpxfV`S_|T=^p7>b`szACW8P{ z`aTXk8}0!EO= z#8iBO6XCCu0nj7?P2PcUCC)9>J<;)<%y-5CaTyjcjw77o`<6Hv$k3JR+nLfITd+az zFnCm?UEeZ{iJ>HfH;@4u11{@V_~V6#WNHz0IK}0A{StBpg&&phE;} zGIovXEW_qj2b`Kfm%9l!nKfFnn*7n~iAs-F_#qxD13K8r=q^9!``&>v8(UtdEvr)@ zSUDzTO8_pnxFYna3O{@)9wQo>BIZX70?V_Zv*qK~SAkX(T45Q9*fT2m>U{o4) z6QAMGU|?A!+t$W8rlPxdassi56&EsdbDtAT)?ivn2>F===6QqqXMXbnf7bASd@~Z5 zITC*jRp7J751*e~?5KJ|4-AF?r;{B(jWI#}9veh#GX>1WbFS`+F%l#k)1DSUZJiDj z!eU9TRIfx$lOBw4VVwZ>cEEUT-tj68BuD7wa|`(fbv!+O057^`9p6}n8($sM(L#Xg zxZ6h}M`4qKNina$q|>lm4r&|WeyJ-1NqzBg(^NuyRS_pR{sBvkKRAD77_w{HV7M*b zpJ|KxBaVFY)qrP!smieJWNXUq?TCyyas!rARBuCqAn${+7;rnvnV-rCK=YOrSgGbbtYEMNR)EIY zK_Z*0VFrjYkjD*#5eQ4T0$w1@!`ayQ^V;uu9aT|!{X{v6fjm;2M7Rjq$()Qt1q@O* zIpw(vh_yIsxN=6)aX08S;UwlNQ^-Vqlo0CJPam8t8Q zy^qmwku8nIK1xZ8C@Nx@4hn_>KpqPc&NM_wm|Mm2n%pz~ChlwP`Y!u=Q~BSJ?Z;vm z0RdW=65pyk53!a30+cL8Y3_MVNiu|H<3H6mg()C_&)i%A@gkj?2{lskouV!&cA~np zE=K?z!f!VVz1HOGo2^4S~5h)^S5R);~&awUbUl zGzK+NfbCdJDqA5Pt*=RmNDF(9G(6BRD`!$%177ep!fz<9U^NEsBPB+Y(rYEcRwyc* z^dfb138R=fAfz7iAiKYSsQGa5J2*rBxf7U8%RKw(V+2Cy8G!UcGU)8uNGZ5f<}b41 z$gg|IzIN39@_*3Z!rGnd>zZ&%$!=%lU3Q$ITCUEojgKpPVvhU3>-^fp2QgNB;H|B# z!RxB?>oy*3!+wzhc&E%yZkeC5Wqwj+G9ep3NS`i?`mQa#;bhm821nE0ezUvJe1K=( zy-tSSu80CZBz??5hI_Py@Lq#rKp3w{D?j58MJKOGCqMtEZBBSWma}cm85{EgcK#^kG6_Hxg5>8{z~Zz<%8($Pad|YxvxhLsX5%K0eJl3ors9l zMnqNGd|gIq^6^2r*k{vA$a|<(;TnEYx_x=>{v(JO8h^Y?iEeS9okAkRRq+-?sm+U% zv#=+SJ<=OrrgKj&Uq*G83RlYu8x7gAvRlBwr0}+LB+}ax6BSb8)q3dl8X^Xq^4Ao( zEh9fCLp`UIT+B)vOPO=B{w+uFaLKaE4x(VohH$rNyfHoksa?s(a;`_y1oB;ZA%JB0zfmV1pG@th;8U~H$eUo}tysK_FCjcWf zA-1lR6F`!CX{;c}c%qc;#1(`uw~Qc9_;{I|zz7#-P6$Ey$xoL2_ zA^M4zclvoS12`6)6H06;P*I&Mgo$1BOm03P7uqHx;U?r*1aWZ!GT3`E0q2xX6~IDs zfED#fzSp{Z1~9ZieW4Ji2Q*~&iO@Mo_hm_5q@rqb=2T2;!SfK!ZhQ}GoEAcO3?WD6 zC*pqog@D@vDHGekbTsjMsy5tMt+K>Sb+{w!h82)D)k8sR+Je88wW10Bw(Q)Tmmh(K zgf$!(l1}b8jSrXUNuban^4a*kUNB-PP1Cet*h(Ty!7ky()Xm2ixUl+X6v&Ore8lf0 zL8qx-c8-3V+vabzJ~kBZU}{r2Ce`}SM00L&T}vYKp5DPK_Z8JkeGicjVU;b0GNweb zi?YdLMOI>0nbGdhRhtrQcsJlk{9p0$N_PskiW!aTpMZ4T(AcP*vMIKAMVX+=nWJ6- zq%y>6X!SU`PjvFJd>s=tG+ho%RI53zee#Z?%SG#QzyEjdzyAAw{U87SKmX(Z`oI6(Q#Y#R%EtArCbLcHJ%}0Z1kBEr+Oy4zkcq#I zJ&R0drW|nIkG0WJ@JAm9Lb;QUdjcgB$C2!lY`{r|nx>6fm@ z)Tq26%qMA|A7-3_d?nj=+2hnL(8eR0bYThtu`dxKTzMxwL{#i2c6^*_0Cj!xO)TQR zkKNnueZtO*j*Q$_ru_BmpFX^N{fh{IicC60Y}CgpdW@9b6c1k2j~SU+I3|z60%gUr z%ose3-f!V4<&4J1<*^euRasvzEk5EUi!X3iu zz^%^P>Q8L5Hz~N9v&E9P!4xYI_huOJ_o-4DYBTTJ6qVIA~pw9G3HgjXB3jRnPr>X72 zmy%nCPcH6=N(eqOhm!%Tq4p-gxyTbrr*%<>6c4Stchso`^vLdJelGlYYFZ~Mw7u^5 z3m^g!uZLun%Mh}|Uts;x{RN5-snq@~JfxN>TX|zc7W87!`6dv{sPyt@Ws@|CgQhwC zqLN}D>Jme0!`i$vr+~T1JR(5#ImPesC z^501ukHK08Pt<>jonabH{Js#X77sxKwA`H6PZIecO2q!$YQs|$g!>d*zzM_f^eJ7& zcO!qqKosxRysaVbkpB2$?o2V!7=Yym$*ai$Vzu4-$#i6mSY*c6|8<)EfgTQ7RxT)tD9 zL;ZG#ri2+tW|lSqUcUF~kF$-q#8k_MKT%-|L|TTjO8E-1n+#Oi^qTPYqcP5gpgBh@vesg+E|#mI!O()_6@~sD4R?(n#AVLAW|KgQpY9{ZeX~g{%zv~q@Jsf zQ_pPU)M|dCNLg(jTM>T|)m*}l^{GnrDBoz7Asjq*(r>Du07VsSs;~f3!FoXq6S<}~ z8J8f(iAIGoQ8!tIl9eDW(}SW4REPpqff~X(kH{rK>&xeQhwi4+c*I(-X6;!K$LEqw z+va75Nvjlz8LraTEW)vPNa5b#iJq)OPsXj3-a zR8sKdd9TP8ptVGW8EC|EnI6bP{3AAJCL)wIEL{rw6uzmsDEVdq^DM*lz~W_Xp}Y<{ z3>+u-FtK<5Lo{R3zptJh3NO*U5w@g)6XBX}Vuvu`31g)IHQ~K{W|ULG6LH*AuuCW9 zO;wL!>xpxgaq#haEtgr{U{FJ5{rUG#zyxHt9&zSG3`C*ZPUzRn-c6}BJ#`;z# zFp}Oq)_jhp|49Z4bVsxoS7WUCXhI}#QTY$MUIEII$biKqISm6K=`H;m&?c}Q-G((9 z(_94+1@tQfq=aS&$M+la+Y+vMzbU&7$Upq=WKd34UG13+G>TYX$T28T7_BH1z##-D zoCQE>H!MVQ-ea@oL*RY4VE?$M3rdZ70Oao`0QiT;SI-2myJ&1IZ3oRn9k--ua-Y>9;I)rDSyj$TvT zL}?R2Rc9F1s2=p9l@++`bfZ>%%?pX3kRSxpQm znB7c}6NeT#LgZ>BjkOJ2fTa|N=sQBy=1>TphMHS~+IR0_tnthuRazm6doIn614$kJ!aQR}iS@9D7f+#8h%>H60 zWTBsoKRTt(RKgg~)ZzdKq|Xojw2%hbHod<;%wPk)hs}5xw9q4e}Qqz2Gm|NCbaTkqGYIx$H&0=oKyzL*P=@+&Y_!+X9srB=cJx zF~ICK#|J%UyQO@=JIiN#e{FY$2N2_XZ;avpk6IBR=qL%G?90aN#U7c6lsu?SU@{4L zpZ-J3;ngRu>Bt0*fgx@x$Rbo=fD#-nW7VoufZjwHEF|=ZhitSH3d11wZh(x0h_aI!iN*1M@VR#sYN2VqVgq#o$?M#nW+=+*n`%A};YH$l;HMsfPapyeQ-u&- zVq&~!g7Osnn%mTKlBO8|EK5gs?-WGqOy(!`YAcxQal-WTK9(+?E*#v3)bM91T?R*H zeqv>U=RH-y_*VFxHm+Hb+d>TaLV^bU4$N>~X@OtIDf!51Flf;udBdd6 zN5-NDpW-&VPD!`B?j={@Ou4J7OlB&7+3)~yIZi~1D$;!fHTMUdGY`=&I>-8aZg;zq zi9iG*!?E9&)hCm9aH3M3iJ1|yCme*s-X!u#`Y&l!0_y@wXb+G!KnUspIc4;kseQ<& z)Ak|=>!mi<2MPnh21ED@Ko2N{J|UsQC*YF`jj#+w%w?J23R?cG8MfhGhFrrWZl0tR zFR;kA;2CGS`Mcx%-F5yR9WDQ;nT4?5wLeWjV3!NAa~k#DT_DEp=|Ns2kexFH3!`j= z|3Y}McP_wwmj&4GE(KucjPK&^P-cCvKg1@L-)e|$mcotmbR`&jCgbxzA;DaN@p(Ig zix)4v%Wd}p_l9ooI`O{j6fDg9lqgsj;Eu}xcd`s{=W-Zec90jp$k(3%?%a9?X!hGx z3mJBX0a8N_rWz`OUU=suxYwZu7#3ebSL?^Rmb-Uc6C|xoJvP@T?=!a_4dA_c-}CzX z#GDusqpdC)68VzvfBqA`T|65`GPn${7e*BVu|_%_hPr&J*w)O4F*5lu`cQzt*BgFRRs%Uj|Htx@X!?D8-sD-qmb;H`~&bDmjnc^ z0a2-}14;6A4Bjb;<+0kVMLfzRmzV#|tCn>65r4*?;LgA)}bKqC-E zLPp|-!U^pWb=ULkd!9K0HRn<~8TH zjL7*ce;z;A>3h(B&2qVMBuEOy?MMO(M<7JeZ#HmrKt&-DB8{ICQk}y_gF{EUjF9Ca zc!eyV{z(YQ$0Q$C8>4f&j{UPIZ9Y83^zBjY!m4A_z5HbC z&?kwCqN{OadVbAkee}M{T^aKdK_`A%NYSP#?>PyIBv|)g-*+m80YsyDajT@P^N+4g z)#p4<{Rs-dlh&~^$yTm39Qi9eqASTOe>@mYpp^0kll}qnuV8u-hbT%bEf-c>$@gno zF=RyZN^t%M#`FisshQE3lrolaR1i)AZO@HRrBo2cOj)}lqnokvqQ2f(HO4ir+CwE6 zQ6-h@#u5!j_gj`P%Nldc?J;jK_Vh$1p9Shmp7!!*rO#I z+Fu_(tQ})&#g0=tz=J9Ro2#P?V}bEFB|n8W{b<-FiPH)|>)vsu@_oj$qPHH^`XG6n zQ3y7s-bZP{5ja={^Xg3`ihCTXu!)Gsk$2HysL^?189z*v)CtrPjj??&oFKs2b11#} z5*T|scH@NmFFJs)8JAx}72~XlS`LTsZlZ|*QBz`}&4=WXgB4Ba;LmxYAk0Q%wXW=} zUHfb8E#sajDa&oFHp;5bi3Z-BE;{R-eDg?rZi)id@D!apcegUNynf31T~i z!a*9vBt8Q18mdnGz6cVh8^!7xLQ_afEt0BkbHj0|SAJeeTM==2SQR%6lyFPB>D*dE z;$rb}lW(l&p>mxbcB#-R7=TyP&YT3^Ubc2T&!YCH?dZVV{V%hoaZ{x0lX-@Ckr}J=)qh63As|r&61Xblxdjx5yuM0 zTOxjw#)m45OZxst)+eK*ODtvvv8gs1AJP4!`JA;F92*Ndv8a0CF`aDU{5Dg7H7;dC z2L}pkMCaZJwF#S$j13!yPw|bAM6DL(J*sIKP|UdEQ^!1DKUcAfP9y zKtN9t-V&D)6i6(;$U+n&v4ohq9L9ECA%ZVj8?WFZ&hNjR!QA#>*aJ8f-d4osUKWMv zPkCRN#k0RtoWVXPuYZ@C0)OHzdpRj&hlvyczR7>IPkJu2jY1-W5MfaGUtC z{57qEDOWJQpBZhsyo?DmjUkbP?wa8!hcrC`c0R#h)l9H~<0HapR|f3Sm3;nfc7ydinGAPg^LKl9H(#P|PGHQG#@clbiXOj{QuJ zOLIpqyK1Y5$0@gd1^xKuTpaul1l9Qir&KyEI4fYh9rCVq?d=w`e9Z4zqj|7x4Yi%o7a19-*0=b-*4}|e7n7iKel#$ z+Iz`|FW=$Qhd162Z(hH9`9U7-dbJ;a+Lj`_00ye2CG`x^huVFFP-;V30Z)QnaIhah z5q|g1?=Z_yetM!LF%(|Y53yGf6(NMjg72-U-<&o5ZEqljoR`%XD_;1CUrjA6GV5~f zC5m*MW~cOV0PRS-m$)|1H8#229-21%sYAiL#m$^S_A{k_u|<4hE)kMv&Tdky)q}ab z6t|Nye|bdbnpH*75M(d5gYlV}CaX&`bkGirb}fD~NYgO7-gO+`S4)^(rXUtK(92AD64!9r64N z5w*=?$J+sst)`Gvyca0B&MiIXwsf}Dtg3KXnbM`)O!by3kIiPXPX^|R?&w@QDHsg< z0WzW|_@u3JIgNlzE;^PoGqx%7Wpnq&=E`;;W2d~?)f#u(W!0bOX@Rz~4_6>S#_bzF zD-XGjC(t4c$17eZ)7FBm3%89&=NgCF0oui%t6Q0KU7Dc2!!}bqzIJAOvl~Bf%48bX zE0CYMPJfA%t9GHLZ5mrilA=r20*bX2C3Z%pp6OdSl@x5Q5g0!)BQrQQ%xDIx((b^v zSTbjeZn_UM)hPrXbHT!1&{5VbMY$gN^9`i954M65eaMZo zOeIO{t#cM3TE7=<1@YEioYw&r3A?rBk9A)3(6TEhKnE#bLrB)Dq=OE?xlvl6Q zSIaa=h|`(WI`_R@A5k#QyjR1{(Q$h)Jn?ET-~IXB+gBcJY(C@msoc+Aym;a5zW?dN z>o?xJx3Ax9@4oTyh#GO&mf=lgRF~i}2;+vNn#05B3#`+=_s)hB7@Q5@Rbg*D(4a?D z4d@shbwE6Q-v?)8~>;cA$>4ga%GMX~l?ADtPum6GqFJHZV z_jd0WrVRP}+dp{ke@qhF%Cdf-R{2+4346?1`3($5+lQxXN8cnLT3*X9L=A&=m63$VUFijg zQPP8gbAa`1kCCJM6FUB{!6CNKn|-Qg-oAVD;rVlX*xLR3YiMBpbu?~&>bK3?aAJ|# zz9TE0`gQB^w67mGTGPIM`gD_D>jO=BP{hg`3?2Fx4sJ<5M$uHDvUo8&ImK zvar6dA3X5hZtuPM@Z-zZZ%pZ>btWFEQd5bBN(Q_vN?L(=yawasBgXF5v7iT&>%+1> z{7!;%ts*y%N|>_hg~$Gv0kjUQ-tlA{!8lB9ueaM!>v7Az+5I|3is0|Qv(S29>xccpiBkB2 z-xR(L-B=LS?q$kQNz6jg7+8US=qCB6qksPHJ4N}4dDussy69QrEp$$c)nC+3*E;(n z_zm1gm_AH_o{4+$6x@r)e$NaV?-lkj(Fb&*+z)|-RADrwY!@oN=z|O@z8HjG z4`0;MgG=Rt+_7rwfj?-GKlY+}G=TN(=~~Ype2R{0W{aj7fXIt)I>%Jw>j^W=_B52z$8%Rm^z z6_>#}yp`J6r;2+#0{&7xQ-fJ~QM;$cbKf+0`#_n#YE=xTVld8iu{=BBR=grebLp&S zdAQ{(XFlelEhlk>aF&2@{;tJkHwNVx61Y`JCpoAJ?da>B6$4(G2+Ax>#c<%~wiLv3 z+ZfJmLq3M$6@Og@E2_{>c`bO*aPp7!tTu=qEX- z3jJIg*^gP6is4iY=j%W}hT<#qbG7K_vg_uvSvK9v zu;*UHmU|NA4v8aX_sz(!k&TUGGL1YFCv#(u*!r2p%3qYUay(_oyG%R2csM0d2MbnK z&9kMt2qXx)u6FYEbAE_PA6l)n=YzHnxp%&OOS$6}8MY+EoyvwT9_SMH`0_M5u|ol; z?WX!JY{6d@&Pc|{6s}$B&M?pMjcoSfa)X%k{;3bYuU{K$YuokaN1Pa(6FO|&;DcRF zjWSigHfFQVk&f(DSh{l@>Z;&%Jl5SImE82IXmzyDbF^<&oBn+PnpabEV>i7y|J80N zzs1S6uMYxHqd3=xDY$iB#7qE@LXUfgPLYixhb$U-fxm54zcx)xGgOVa@_JROOhS6M0=|Fmf<)8#c zcdwEWxD2A;?}~3AdW1SkD!$;q1ify}04gatDB@10Bfylu z3F%wVvUC`FFxn3x?|)AkAi0e2ipXVzct7GysEfa(Oa7MJ=-cnS z-f(yvdcEKn;mnZC6OqzP%GuC6LP*Ll;~`?H@ud02gbcXYa4y%Fg~l_S&|{q=BG%Vr zw!ecgVIg7(BKBT)(uwLSoEgq;Mx0c^Or03x6$qR=2H`aRT6WUOHf|7F94VS24c&Is zm>ud2H0Xu^tk5G)zN-otSauIqx8FH~uU3rjJdE$!B2kya-8e+*cD}RB3>Lr*Y=FXL}JWia6DcM^v^xzBI{YsHx~YJo%=HFmuLQ zp;1N$zy9H?=iYu53|#^YoqM*wB4&4O0ce7&-8clA=zRr2gM)o06}l-&1+4blL$Z-^ za7y{-?$(a?=7+Z?r;JL>A%Ubl?@ykjXR^VhnT=`!lRoAn=_8(W02o5^zES%C52L^j zDbrVJpTe^+^80I^_jbSw!=IoL?jrY7G=!ZOlmW}unfgsmE43K|Ahptvt!8-t{QX(`bX`!6143U(`NfFhB=KnSd2DvsHc1JTL4YzV!aQ^UgD0NJc!41^B(+heaP!b(`-vcAeMcOTbKP zk;f{-$!IhjBaWjci>}2easqj;*8p3z7(%(dN^*^e!=8~%ud%u zKl*Rn>!j%BL~`E7V7>p)W}0HS{Pn&&(q! z1^LOKdkFhnA5+Myo5hI`cs;cPksnCCu0}gRioho;_&;U_$f!7GJ< zYmwU3+BAcQJ%3ibzt*Mc-lPw&9BM+s(Qwek<{7H5#3lwKWJlwl74I#}F&j#%kUSqA zfWl6M{8YK5YtCRIONb?RUNkPdrjaS`w`f*2rAnoQ$xU4s&#N6)eceJ2IEPy=kn$<+ zs+own`?-aw;j~^*S97vG>BW7m2cU>xONby%T&G&e0#gF=ptX$x@cylczF5uSG8>sFW=Rs3|2i1Y>Lp%mg+bbIkz>p(%n1C zOAVMHp3U^K=cehpX~eQWg5zVy8D&K?*C8KQ>fo-Ra|NAoX{n%d1)T$4-zwWXCKZe}Fe39pAh# z`)eYs>jKDc1ARR#Z#hv=f{vmyO?@jVcW~klDk(Sh3l&tqM5rEH+shWL#N3BU;grda zU5UAIbt)_7PEps*k#jrto3~lsogHQ+!#t-9(*fk!S-s8j@9bzR8agKpRiHaIL7-tq zWm*Cwg|sb3Wm_3(&tNLerA-iry5V{~ptODrib3hSKF^_@3rBQi;wh(K$@AD+BS%HW05+JZ%ol zPh}x~=5;F+-}PL_v)e2?&lzVW_&lfJ)4}FB)0)2X-6retoZ(hfbWSQ-rucl9ccFqH zAQj@S5O;;R2M9cR$u5}{;x=fTzbnLDA#UYsr_U-n{}v`SD^^D7X6;Y*%s8ZRQ-}Hzo&1(`;g;|9XpqTcST3)ml355XhYrXOYUvhSIg8T?i56_(o)it zeNq*AwhBF4#k0EHtPQCxSlxeba6*ad{`+;^e^Yz+W=0DukK0pu51_D*0^BAaJsBP8a*iG4mFq#2quGS-&N^c6~X^o12PIFOyKKyP0)-tMWJb<1nc5H$_M7$b;3) zID&5DVHgcZqbh%s=lu-ZqC~)$%E_{FvMdhidC@vSn!0@*PL?UT%FWSY=ApY1$oJN0 zSCaE^OZ1q@K~;)O?+Pptmv_so_S$dPUzJ&HIS*t|n z1>^SMxZ94}iXUeV+Z@~Y(J&Z9Va9`FaOk%&(|&mW?w#M25<1thz{}*9(`%Fa_Wq&~ z;gaK#k<+vCOo$s;vn?BGMR5SbpX_79>F|XrF;Fkkpvi<-(SN z3A^9D?PxDK&gyi}KizX!U~=MaJ^D)yx1y7LI=N}gtvbf-F>cQ9%OLdIaE?!7i4|E{ zvPxZ|E9%0`_=A&u37xT~Vwqp?b^855IP4)Aw|6)hbRt+`ta@L39M~o_bu{#ly2R7* z0$w9;UaNM0P2E`Qb?@c7KfinX%B$D)u6;cmosEM}N0HZfy4HBK)@n2!dk1IUt6}Hp zxIGx2c)R}ilYh2c5oC8bIq3PG<5cDM+8m4vP@CQT_ITVr^X}QK$g&mV=Q&c!@&W)g z5&1H_b4Vfe&>#?<^dj8;6BQQ?I3IzW_?>7tp5L+0zMr^{6X6e|Q*l0!TZ&F!(eG@S#2E_+I3P5s$#V zBjk1F2d{JFcaB5v=xj8Mj{MLMz4pOy5_w;ae0(0$h^;;7dLFW$wdpJsw)>+V-tvZr zo)>)?-n|3K80`Kg!wDXCgD@Hg2NND)_;}Plp&_968)KCuddOJko^1p*9r?kSb;W}~ z4+q0Ron__N-aEosKkW5}UsxwQkG`TyBY%t-qSIcfJi zpp=GfPVh(B_6nNA8exUAZOK%AK;yyK?}I+Vc-AnG!K8nHK4NYCp8A@}*!*_qNH08d zT_F7lX2w*%VJ2&UMQHbe=#0gagn$Qn!ln7QgApif)E>9{0B~c|g%6l>+Dp!+X~vw@ z6{^-v3cUROYnom4wqHEokRH6BM7dpfg-x&r$^q?4S4Q=dc5gzu!@?!03iaTn%+K3H zKt@vuCM%vO5{hK7OK!=Yf8zJ@>tcyC>7vT9{~3VD@9G}B1Cg@N>9e`lTFJaVR9+7bPD1IWV!bewrvq%8-8Iv%E{sq9_8R5NnX!q=xF8XeuwV^lM z{Kmj}pbbLh#IffdLoOLskOQa+%&CGLON1Oa5Qt$Fo>h=!P}g{39CQTRb%%a9_%_06 zDGjF(iNSxsUk6A)q_B%rI|H1|y9X+WQb82Y0~-9QAxeRa!95_p5}Y#6OjKZHS=PNP zckleQJ^ts!ufOug?GhM-SFXc$C*svP^4gxhv6Qc$Ot~xvjiMqqS zC&?Kh)R)7gw3{j>YhFAbTY1ns_>fkQuGC1j)u^H1cR=B>V3j-=pByH!3Kp6 z+w~^uB8N@9efQ3<$Uxp$ zYkst=f=;minflzKXwl=56Y4TkXN1{*R*| z8u(}4PxT8{J!AyV`W%zJH-h3qv=}%a@h5Zz0fg|PgV1`8yLa$@FxFow#e&_dvN89F z43?#SVCPgXR8yi1uzCPWsOC`v=yB*n+dB-#Q0Qm?VZ@xk5rvwExEJ=|hxr8bA__*> z{)m{Ye20qq6N|)MHOQ>a$RZTtDb-^&nFKr|AODcc@_1M=$^y|BIr^$3(bUi#Jn2At zj|>9|P1YB`*E5zP?m-4cR8e(y_L4Qlst~1n;;=d2{P(Cm^4C0XkB^1}9v7ow7zQ{Y z%xW5vgengUi?SeDyc5WgS_{@toE0jig{BKdCAK^KK3`2TpPGl|!%iEg7V6h$BNa*d zz~gB*93#ki8?s}uhCpYy({O+UPy$LP&2U(k(n9jA6u^t(01CZ;y1OBs^jVVy)uFQB z*)!z?C6^Tah{#8Sb|I<)iBA$!d(9=IY7!=ts-m({)a$k8XkyC(_bpR62e@xR(NzI= zPI2O=0`6+}C$-_&M2<=kf>q=dY4}zUPW2b~8!M7J*QF5ZOugAq zbvkdV>cMK7(oy+d`l;UBTx+7vbvD&nd#qa5m#EdP7fsY>S6YgUn0ERfcUpGLWjRpyn$J+H$5ySRw@I^uJNy+;ga1GGp zOheOteA9E3tmg6;>srkdt8xV(`CZ|Fgg)08US6`BeEvk0yBNxh%_!npq+;qS#q{i( zJQAPQ>AIqbt+c-8;oMxtn{pz%|poT<}1ca<|~A zNYf_;{^x-HNtwMZo`wHvwYRjCgKf0KZ~|I4izaI>2UaE7=RDe<>ViOSG~MR#?i~pA zaQ&zEg1RLL9y16R?8iHTbzeQk!$58UZ}PZ2;K!AR0Kg@*0$^SWorS|3cn&jVn(Hb9 zW#Cr-l$yG3@F|Gq6P`a=gL4l&dp0lLvqvoSkfl-!rf@)=3x8?Yq|T|^0ksMyWd?9M zbwewxYDQK8sF~nso)@6t9r$%Ego5O`$Vp%-kfK$)(jIFgXK@rz;!G&JmMb|&V-`a3 z%yrP4fjUWCGcSk&VG%K15{Qz<&{jesu8^S_1_no#>|!-JxcBf-L=q@|(55mKN>#oo zNBu|y!eYaaW5jUO29t(ugECyWd&lL-N>9Q$m<)~w!!P&)U&F_Ok`1gTPDnM`i9$xf z$-1cE)TRci0Y`rFC4lJZ%W!;*%e^>UbfVswjOXq4&QE*Z&dU!kx8Cf%`LL_q5-5b_ z&k})>qH}QuXc(l#@v24)QWEkp6wP%~aOtPL+-}Au|Ed9=nC)s{{DPDz`21T$x8&cG++(j3!hXM&zr_ z5~dDHRA!-C8%6>~+;x*>VmZ@M7Nl`1DjC;27;MHn(0;)EOmDT(5Rng*ZzH!+#+maR zI4u+v$pzrF#q1k!E^AP!j0yAvfSVaMGsCzct{`00v>~o4(U-U@2^5!*vSSkjq$}s?nfXenHhZe`4Tw4)wULK+DK%3zbQ$Om6&7CHR_0@U=7~M{u=O4$u)v z*saPNoJhYkoKprefO|{Cb!1v|iPf4{iPI7p4C9D6Hd2mjlKOS2G+p?nXQ>=~tMr+* zOt|yQiEZUzy$RyTR%ow6d-kF|2ih~RkOBh77#7It4TFy85FJKB07IwH!^vhmWf@mu zisG_vF8pN;B2lnNRrw2Gp6YaIkXKM^1@K~QCx2A{@5TW-5gMIl;VDE8)1 z&6#~jgqiAqs2ku2HDRK93JJA5!Z2sgBnA}*f~#95%9|4{<lD=p*u zTFl{!C#!;xHzP2d10jDbtW&Z!iT<^qZxzK4ggjEcNLNs!JJj*Tt2jS{)Y zb}eBAM^O>$aVpYL>ey5IMj1w>p$1@2q4co0Ca7F+F|uk7RTte-$WB+h`3>me@y6XO z>SwWN(Wxa@z^DYo1-jyR{qkgt@v<90DdsoRrqjoHhM$7>zl@!Um{@xxV2m$P9bZgGkDZ?4uu#w9EQSE$jnXJHvnb!Ds z1wL*f_^<}xRu*#}FrqE2OdYqwh6-F5^COQ-g5#rxnZ2lbVQ9;`+Yr*<5iu6UH~S~oe{D!Wb3u+A`y z+=3X1@2#kGg-kCvb*QG2D<1FYU?Og!D>!Tp4|)vO{1_`W!=Wcrb$inwW~7b<*3&2CaVE=zAy##vnd zL3B`(X5HSgbU89jmo7({>C)whFCnA+ld@EVk!27A*gG z!Sc;V^Hz{KOb-xs^T14=JuMBOGDo91E2GcZCl;icm&WFuJeZX}*xjg$`;$xVO!D+i zc4Z(EmC+EPU41h@qtsZvQOk@~&)8_)JjmjKyy=+M1Kj`nZ3LNhpoMKeuHE+FeAPeR zxX9|8;7f5=m)`qU>q2|Kez9}EzP^5ule510_#&%+bYYVN=sMUKlsl15)bEAQSN-qt)d(*&aI*xHqNc096rviVsXlBE>4;yv2yM} z7AMYy~ayivuX&m^hy-Oq@>^Cr(tcC~=~SMTrwtEJ~cHVo~Bm6^jz*lf{V>y;ztypDs*V zPZuXGRIw;&p^8OG3so#iTBu@C(n1xBlGfA3NejJLn6#RW#VLbzEJ_-zV^P{*9g7kN z>sXXJSjVE|fjSn3eeB2L1kzl>;-Zd)7Z-Ibytt@i;l)KA3omYS35$z4-))E#5{aAcq)|W7;sAJ(tMI8%I>iQBU75!LzQXeg0LQu!T6Y^*Y z6M}v$G$Eq-jD=>g{ggK0DyvAO(%Jn_Yh6Vd{v{mmf4Nez%uIUYi_$aDCPR#u9F-{b3&|Q^M~g zan`C6hdey?zaae`6p_c1aWo7A6-#{XJWuM}-_}lj^a0#{!HDTPcpJlf*%Udk=idsl zdn+%>Rivvybo8Fz?lSSrw&(YJ>8fPJk*qhP@emGl$epDzMqCXXa#0U`qKh!Rx^`SNp+GoD&3b_+AC-M0|LE{3Qr|6O8U}dfr*qtie2GNl3HY;*fUyQUg}0 zRoBF}ki|de-I(baqeU~B>F1V)HG!D-&1{ z4x?_@XBkz=3J2jm_Zs@G6*B<$nd!Xy%ye8oQ?;S@2><7gquM}r*f+Zp@@~uQ?Cocb zZ&tn7!HC0%%beCqNtG4Wau{ zmu@aw>CeiA6j|A&5ZqT;DpS20+QSv~8~gb!ngI70!Fd*LUUa1LzC}*0Z_NvEX5+w? zHMOiut(r6c!C}oCJbT{obRehSb>nz<>_?OFK(8k}^;lm0>x_Z?Ll;@m{Y8ekTJRh^ zAb9qCP;cV*g9q8#&8Xz}11#An8n2D^6*2xAd`z~Rc>AI0RHiq7HxZ0#laMS{C-h@x zBIGI>fu`t&Hyj7iQD1k-Gu@V%_|x-d&P<#%of+daOc^4-b|#S@rT9tn0r%uTK~0&l z)@w4p`x%_k)wxisAOA zwiPpGRBHb=6ziW@iO=BI6-O$-{0XOctgpacYSGs-@nI>%gBV_3AQs+9fh@Hqs7XyD&Tqj%26QT1a*wSchya^Vznbb-%&B!>^b$Xpn~{CSQ(mt= zo?Xr^H0V$$q}quHfP`Ge8gVUWCmJYpV|JqzlB;1_R@4T|&ZB=TSvTar4VuEc1^zZh34-#!HgD{d-PK}-n$|T&$;pZz8N$Y;` z*Gz%5^RAV1sbM<8akW7>CM#v0A{qQ$PZQtP^di40g*$8?gn1%Db|cLXwOlSyhxG9R zuJ&8LL~@1>vNJ(s3S43pDk;v<9`i%5regduALywMeeJJ}wYBYf^P`r`Q{r~*Ic0HH zPc=1azh4{qmzbCt=@*x7Ar7gcI?u%;561|E=8y_>L6nLj5rE~WubBMhj_WO`ca#2=^TGzqh zSvwTiuaAa9D6N0~wFb6*|6%K+`9^Dkd(J5zjPZf$VD-*1K8bqpp%HoWWc>d7tfH9f z5{#cc?>(@pS+?%X6F=&f`6qtt8LU3CuT7-;@mjIT4PsOS*hT@*tYl$(R-3}A2hZ7q zd-u4=T7Q`)hG6`0HT|sjF`?RIm8qWdQ3Q^I3CR|AR+I1XC;L)PNpxk7qIe5s*?8(@ z4Ixy6o_ByGvxa5zne(;Z3;lTdQbQx_=wCCG(31+TJ<}$wSFlHW8UgKBnWRU43zj4?R zKk^4k?4HkXV}-o><7)f9dswP+w1%6zN1UL~_Czxk@b`=!74etPMV%sfets4hjNmqv zV!7t4G|M*6b9z?5hph>gDazlOT}WPklJxhZTe6BOOd2SH834Kg!wSU-@|TqHK{h% z)N61vdo=jBR#{wo>@MNCaBUu=0Q@ucDmln8_VaWzLH2*vHg=aUwRCyCNDy{%o@1cj z$V1%6A0M<0&nTP})X4vqX~wkUj~SmIEX~^Ge0`D@ZrUCEpCTb-bT<=HYdm|t^Xz#O z7l&e5%-=KR)b)P4T&B6!SYJad-zPMIdNu4E9k&O=6K~fafAY`n-kHl?VV;+8TXN(w z@z@rS&3Gdtl`+!n`9#_06Vn*vVB{y7tznC3(asn-74T5;CcASkL!x4&?jQj25!D}& z&(!X*SF0XurheGl|M2GJyId)a?Q@>EKHiw+B{6Q@RzZDuZ_%n zz%E(a(YtjcYuj*)JSCC0GtHEqt3BG+h-V*mvm=nj&Mg!fHd8-w}n|vJ13>4-0}jg2tyjJh>%5Z$bfe2 zT;jvt5YiSnA84Nj{XI)W^(impPE*kQxKeA@qx%_gRM5l?X(iINb0R1OhVge&Dt7@V zW~;7*-0%P}7FA?FQ}nc)W=A*CoTZ)kg$3~AlxfW>v#<^Ewsjs$xQ;#iWx38{fv9ij zLIyD`vsBVDSi82x6YAA|^T$K_LQuy?@!GaNhA}I3@8!EczkB-%e`!Se8sc!tljpUb ztTh`rSeuQDKUnpVgkHDiI8HMum(+Q&;$<@~S&uD{b%8N3HGPM3>D8(pTl^UNlGWszi;#k=!kuGmTWjApm#X!bbGm!&I*wj>w={s*oRi5~x0v-N(@B`@>jf>k zDjIiLU#z~Z@ZR*m{PtdUyFoM@h&Sn%_@d!^yPzcGV}+qV|N6x<8+ye%?6p5xWB*Wl z4j^fm7y1!%F783og{Qs|u*0UJ*Mqv?qgMmB_~lnf&zMQJ4OzT@+U`W~VRcxe1st%^XlGEPmiII@qO&$0{Ep-Z)&UMfC4 z%RD`8;87t4Ipeq3E(td|i%)s$pH&QgkOK1h{6uV49 zyVDecJWG9!(qbE3taI7WF=l zi>$+kG#fyi4#<>G>_}G3Gc;yKclj_H5`^{`IbFUDJthjT@bki9vZmqZxo4ZCdtTs- zm{pBOR5CukG+8JD4ha=q8FDVEWYjvueIvPSo1E;-)UNqZ7P4|t$Rb4Ja0!}5wB;xl zjqx`mi1=TD@i)aB_w2Ct?YOjhQnZsfMHSORdr3L1A=lDqJXP=&HmS{7DOH_{_*~a2%ZKIAyG-4YOQmL>&?`rxg#5Q!Uw^@umt010|w+ z(}tooAr{vgaBAn%9lrnx6GZbS_7{R8j)PNPT6ed0yf;6*-Fy3f+k5|`)<{8*#U1n~ zFBt`=1D~`BOl+X^^7>GB1z?Yqf{)qWI4Xhcl7amWxKUl4lC`zGv<_P>EN%s&h;pE~A_stVpv2N@Z}$fdg?ApetCzU#q2Y~)iKE)gO{F^BiY(s51&oJ%O+hQ-yP zbdbXBKkvNr%ok!Z@+T_tIWV%5X9oK?M(*zPIT79A;LTypjJ^4umf;C6^AnM1o@L4d6$xZj0%10ovA5M zZT*A7f9W`oG`k~Hk13uoJ|-;KyJi3%{6l~2Lu~7LTkXN5-9ytM3>ma1z@KEr5YXsb zXN2FbV%mYPIrWSzN2l0c&yN>2Jsh`dzo0iE=B(F6Kl*+Gn9Bq@+d(g+*O!EW&8bd)eb!d^++RLb0?)QXr8<(HYj*NLQgc zQC0QYuw$Y~7Aw?8P*iA7K)w_`X6BKU5>V;lg?&=9DWr5kTpUn!EDrZ%3nGxIdR>in z;5A!MR`7q!%+%;x)ToPR;IG@OTOVzBRkf{C0ajPMzt*Mc-lPv76&j?3qv4>7-7{2Q z30uGjIp+8W7L+VE9)3v|lIOz%P}qr(pDLI1MZ~R*ONb?RUNqj(rjaQQ+-O!dCA4Zn zQ0Z{5<@~l|46FLOg&uGYw_d=sDekJ7h`IZ@g{k4RUQkzaLelEReXR$eh+s>IAWdAS z8gYY_U0>q^vFbz5`&rWiMa?U44b+5*1JuM)PCx0JcQWP)rk0L*1GN+zBTU2Hs-}5- zFpbBhhTKM|jFvS}ckF%ilT#zQh+m>02+%$p&WQ5XJv;urfDe(#H~Q z=?Xf)S>syA03+Ks4Zm@y%`gpQ<>^wVD07ZtoDNg))%*9QOc4Qnwq{A+`!AkDEc1Ljny~b0P-kNGAI3mY2 z_7`e}G@k97m8FTG>d?<%O=33?dci+IG7hMn8J?mu$AEqt))v-s-nACc0ynXn z&$q~kDF|i`FlLj8!ktwQ{fxV+VKGU>mCE&#@o@R|?|QrQZ~XA>&y1e?`Pp-M`?HOn zt6s>I;Ny-q3~&PI{wgjHX6Ko*`o6%A&Yu{P+JMuq9(qu^U}jE}mL{R$Nh==GnRJc? zk7C)tp9YaRTJ+eB;ym6>&eYz+PRvYf{+rx=bH1u}T;9u?JvcomSllTK>nv)tmN3}} z==b8T3Ok}-p^+Bk<#++ZbL>U*gmcXM{UJP>@0~W1wKjm07|6=iOKDf|SI5_~V=kPW|SNY^tK{OEW7M zL}xX{>I%x?asDxZKJ&k2`aJD|AbUP8X}k!7(!*$Su;%?`=e4=TkNr`v%{wM8rtr@Z zL5Y3417Qj7ne%wx%=M0O(b*p%R80r4a~@2O=}JO+`@kUjDS)0wKmnAO)=yUltNm3h zmwJml^8!{un=SCdUu3wY=#kUlEKsg!u&lf- zMO`;X;O3~SZh+t|D>O^`3|Y*OBX?7m1H4gQdbY6e%eI}=LTkW4CG@;PsbrB7!l`7z z^9rewwJcRwm8`n7&Bu}ao zd~~d>Sh-UJKc;SEBF$(%B$%a1RY8poh-Dr8(bpjU85?Zz(;V&L5(GHRm-(Y>(!b`L z3ra;YBXR01ca6*&wcG~tzgRh0Nwu8_Z;~93-Ti6N9bWsY?qs9bN?{JRk7>J*yIG9!Cl_*@x76eYo}j3FT8|7tF0ArDBHu+vm$j$@iq z(BwHCKQTg7M0@o<_-z=TQOCgV)u{(vg;7D`S%EAS-^k^uAT;%iyO=fEbxe_M``&3s zBSt(*UGq}L6Xv5arqG{Vy9o*4aDWQ!#cSb9-`p0*oLM_8Z60xoTZU3HOpBhROX_P3 z!(u~4Xbc@n$6V61Q|5AMz>zLf;&z!TdEV<~s+y(VFH?1wzF;a+s;nZ+6_M1cZ z^VMDal0V9>S#NS(Ff*qwdx&-qMXSD$&NOw9jdlvgQ&YEjt>1AKJ#0JPkB)EvdlL8m zj8Q|TPal--%&Z zR6#V&jzH5}P<5vN%{4D=Y%+(DiPu80lx7~%5@V7wQ|*4CT6fs$<|y2?HPU=S3r%5I z47;VdFo@Hx({uQ)^(<`>{tiP?$z&oCSE$Ap^H*cH5LbwIS3CrBQXySnVj$VdX$oxK zRY6;nUNqAMnR8CG{z3C6P0;d@a=~ALUOjW-Td? z*C6z2%1Q{PEN$9z7|R($aPu*?N-`Z&H91R4I-H1~q$JZpk^N|D+vW?P(M0|ir6kiC zQbE_?bK{yD>0udasO*yFGDdP?*QGXNuiLGnWu?uog z)RBe3KT@GK&g-J6Wei(Q?W5Q%a8iU{kN0zCk>Twp`n*-RqQc4$D(^1s)=`@=na36O zg1jnQy2q_4J^tmo^muN4LUVPCB;3hAJ#UngWlj`8qX@8GmW6m`dVptzlKM?YpP(PL z%%j4Z7gs{4Q6zjb%_AEV#UQ!hdxfbjlFKn0;{_bwr&JR06Ibn^U$W7rf3OE` zolWz+9;|T8v0{92VMVQ_wKbqmJ=qA#mOsc@A$L#^2PJ)UTe*Wo`K?yzL8^E#{WzqX z_R~GjAQfFX0F9IWnlhDu$(NGeoR%nbFK=uWgp=+Dk0fY}@C2%4vtO6`~5^-w{v8F^)j z)@2c0AIr<|fkt<)rxP?gHq8rK<~hk|oVg~4$jqnpgFba<`SV9do0adeTFMdH<(?Ps z*z2wz2c2NXv**aT_NHdURjjer)Xefx{!FDvnlEPAcahcy`got=pa7oX=<34(8>8`l zG0Y-wx5;r6*w_$GKj+zPXD%JgwcE}LA`X}Uu~ti)A8`QPz2x1N=~#Zq0d{QKZp%5= zow?>R%bDWNr`>IdSx)S>yv`x)Kzfp=g&ion+wz*6-;Lf~KMp#v+h)&^*=?yAnPa!* zb-SD=z1l?%q>Mut6o@@i`%bH+KKcEA&ESD~{qcc~)di0KCKRkmBbY)^2bRJAzAD%H zIV3rS;ua&w!Gsp+wu~6Zz5+OCh@{Yxbc(u*-d8OID>|b~esdMI0XW9XdjTtGRPAZE zd@aRG*`8OiFy%O{lwegX%*%^~nc9O(@&Bl_U^cerrH1LOv|zt!d(N$|LJOAKyEilR z!5ko}*mK69D59PujEbKC>S&;BU$Q`|xaM1{4$IMnIodf#J(lxgr8N6qkrF6L3y+nr z&^g1Yo>yt6oE6GCG==JtSGpJ#s52F9ylkguMI;V0OWGMts_69HK^&r>q1k@c$V5v; zd%BEK)ujr}sj^F#Dxtbd6+O2KFICTyRe7n}m!;B6RV#x(k7_Sfj|_f!D!x?hr3Y`W z-1-n%0Vys&CU36##t5(i#@P)JXII3@OIjTv~mXxTPhjl^il?w5kv1DPDzyuB^aBSKW(Uo25i}m|aB{y*o_y?(^pHmYyvm8Z$dKO*JxG z$tGJXS2bd0SVCSWR3rK7wMr4Gk4e&}{>h)I>551eO_R6MSD6*jH2?YIgZ%ne@*ise z&-(Qa(Z3QWXNqK;y_?%9kg|jg%YON=1`RW)GkF;3CNs$5+UZwi8H_S)J}#5i4I15D zMnP67U@HY|2ESge0ydAl)uN9eCI9h!TqdQDcDYQZeXvW*Winru<1*J# zubg3pKLjTM63HQAIumhp+he93J_+JfYgjwuk2xpoirsSuNY9(*)w2RdX5Yt>G#_Rk zXV;!&Hq~c5TM19cd80!L8O66khm2AqPSMTG5og|4BhEZN&xkXXlo_!c&$%NeFwM@U z)4{|spW1+2CujEfvG8J?{{$UVH=W=0~WWmN$mjmo!p2=O&?Pcu$J*GK-c zelS4drNi;C4{vOwWA;KM$0mMy|I^;iPkY|Z%MUNN-t4{ku&dpkQ;AzszQs4zsv;52 zWDXcrZ2WMY%_@|c$GrZ-Izs75OJ4!ZXA}7f5Zp#ACEmwpZ31D^!|D>&*;s$h^EA$} zm~c!hl+q;;ZB;)yvjJE~2a`i2{SI}UR&^`3*Mxri*sm!`B629|yTbv(HXb|LE3MnP z5Z~a?+5r^CL`_Vhb}63U@IZH78&$wOSsA6zVX}}Yc{X>+xns-PrAS&S7!Y30Od(xr zPCJE6u@Z&~>9XgxR7h8}WK)H7^`+JI*e!YP$LV^{ZLE;4Xvx+J>FP_5KO;%2%oWI! zjJ8Mfr+m4Kyoi4;IAAVFwBMG{xyDULtA`|iru{pqGqNYszH693j=Zq3f##h~z4Ln% zFC3y5mJ)DM%8WJJr+eh;G?vJ`t{tC}A$?J=I<0VoH~}P+4ds?Z6V@F{8dZl-s$?=6 zaNZ~wt5^jLF6?<`v*tw%w$=uSe={>f^$iRjtR?|hn;-nJGY&>kfJ7JwkYqykh9+9k z{Gov5%~stSYS<d3>Ir!6o$%luaA!404*2Rz~Li)r1+QW@1`& zU{_3kT_XFQuTS&Gb0$sX2kHf#oSbB$^;#M_X+5a%`}-^Em!t0BJ6h?G`ZKE~K`S_9 z#E{{nHC3gUP`DMVpUzG2XASh_EGGtbh4={1RVi0W_xhXJCuy0A*q3#laW<=^n;!c^U?z5+pqNKJFmT$@BaMm z?JL|JN4+-PXoHB>EB#3?3ZkPiLV!X@-5GZMwYztm$k>bhIW4aC91`H`q(~2`NjDj{ zW``*_83tX?-mnS>a+)K`9t|Nw4l_=HgG0o&nT&n+n}WdMfJq`i$j!#(%5V+bPbA=K z>TQHtlra0C9r{dE(iw(E>P#SZ)E@iY{n2pP^Y{-+Dei7shY(?wJUCChiStBVWmgIJ zqVdExASUl^oHqq;C%dRcz3gai3EOoeKUJ&n;yJqW%?I3+e?V#-xOwf!;!Jh+vJ0gG zE;@yO)GIhW^de05#wzxrrUbVGN-jz-Ce@QEPG_g62o3=Z;%G<*(F#?a&r{=4Qc_lH z`Z%TH_6Tv%PlE7}a&TWsLTaF7PgjmVOoL{wVPfpe;?KW;(+r|^fBS*!PyAuaFgHBPdv~t z`CfF?9`K(?75)++BhLY1NR4_yWJ{$>$4!Ufq|aK0ETk7^#m?kPZ}_QIvmfu9oxS3H zISM*Q>^ypi8$SgLZII^ybcEaQ_dNfflXj1_4krBre@vOfkMG{^gy7--oCMf>wnum; z9QICpGgRAW9rDUdg-jOA0vq_zyF4?qd*d^uJGC z^Lz5heZz@O0U=jxZQBi}a`LZsFT{}VLES}oi(i({7-mF5vYalRG@4)XXD&hQPJ0Yx zv9IRYEUBKq6ceKeH1z#$?Ve&}x`R|G4-aeXEp7QNHy5Utc_1-;6HmLCr121|nUo=B zW^e3w=A1ayJu}-RyL9D+bN96w7aG#6%?1gWGj$>xvQ^X)KaO_}2?vDldnUE8WUJplSja>M$*ZR+!~&UdPb#O*q+vVc9Ju{mx~E0iH<$q@Ht zzderjN72~ZcfYVAWWMf*i<7ULFAP6XGPk4E;~5W4C|$G9 z7HXoO5F3purf4=rm!ThOetWBOWu zE-{;?2g>rKwinM^CgZ^;2=g$;n$!BKOP6^17o>7WXn-=(LQPeAPw$vm>cgp;qm11aMjo13JVOQ|F_aZ7o?|i>%DuB< z)=XDAXK)=T*ZWM7Oy>(%PlJW2r-dEp)gBp!DX(xI;{z%2*gcer&k4rhMT;QG}>h@n0WN8#GY=IF>xg<(hKr0QUY2&YbKqD#mQ!DVN%e1>6b z-pdM{o=)pZ+(Pa!q?VpPs5MsedWzZ8G{0;Qe|_nJJGcWX@wd%#55KrigWwRZa|7fz zkD!xa7xs>L=;4DhE)Kn;v(Yd*@}b!A+6P0Z%Dx==_&i1?I1iCHJP*lbp_!J1B1m)_ zdeobF(U;-fJ5)LbgUN7$hur`=j{vGRDBbY!sC~i|sNQe>c!;nHG3-V0F(%(Mf|`!} zV9dHKGUl;77!K+zE0?*wBisOoz25K(>tyHAS9EFQk3p7D6>5v&6?Ur*J!p^GLyoaF zn1IWhA)?+o^RQx6zp#hC>Ymf|eMRgm$l-bz&8i7KPmau;d<;UL9G*VZl~xp^w|dBi zZBA~bW!o!gj+()|wqz1=172(pD;aFBB!pxZJH_T)W zun29acF#x}NkYH_J>k;)TXEPNK||N4E2lB_{s4+#{^Hp*jrBGas@6@0;r#xqu7|7M z_KW8m(u4Pth(4p4F1*4f*aPK&cBLz$`boPtA>EDBRE2u*Qs(FFAp{Ag5=>S+Q6v;e z5S-kSJs=YUFH*I!M4EI_<=Fr1cOt*5d+-iK%09C&r_b$knt+QtZEKmk&*tiMu1bqgAh2$3ID^>`n&hocuAl2z5TzfN+_Daw7`0!GRn!<-!D)O;nH&s27Jv1qsFa zZ8?w-2R82*>b_xsyaroi2#ATjyF))5d>aAHNW&?_Xz)42F9+E83R_CGGr;4#d!T}l z6@>IWp!E+HiGGC;(kOdUS}FH{_-b*a10hq$gNW^BV5AsZ&c?dLQ$T2Mng)eynP2QJ z_`9A50GhbsT+m9)r!MY>;d&4n&Ai)MBM43e=PE^E;VN!q zjfVfAkxOaT?jrY2vcBro(TU_0gN>-`Q*433XBi52c73?IzP9mXb}yc+tv4R|^~Soh z_~qMbt*y6OGe`Vrt+~05wp@K>`L>#CkI~~Z#GuvunT%1FrtUup+ zfmzPg*5gM{7H;b?XhV9O#|tfmw$@sYo-W+h#@eGz>G6!VHlA?37MnxR{Kf`rncCL+ z+M_1vaIv;PhZ{0GGunFETzp(;OQ?B$YFm%+*5if8H6Qod0Bw|@LrYT)&1cg4{sIq{ z&8s_DHkW&_kVQ0SvZLIC<|j}6d$K;8+2at~Xe``T zYwal*)$DO?3Oifu5NoYHTAzJ5qerd953v@^UN>frYh$hXh)2R=<07MaJhQFMwZ~7% zs1|K&qcIzFJOP_-EupQ})7f*_Tx&H+hl?I8O&m|N>G08$#hLwl90jWiC^`4Iu9De5 zzB*?A_}Vc0jpZ@>Cu>choLR&O79;5G(L%Qf94xKbOc4jmQ=+%U4wgr2kLF;C>u8H8 zXR)@{*B-ZKA1s?|P1(?kw$*BF&Rj1rD%?;PWx%bqCpa=@(FT}Db8!aTTH9EkhYlY# zxL%7LEV%bPna%7$8=%(3+iJ`KIzX)rf~m#E)m(e>c=jCPzV>+WIpnI(-piY7PaiE~ zTpM%nD1gNE#lci_?a^F3ia>qt12dK&B_uf5rB7!A97rF^9L|MpH6PCdI@Z_ae!UQ% z2k6+GXIvXgxuwr@WdgN6z99hT$<+azC(8vm01fB8&2BCa;9&cR*loJVsI}I7%G=S# zLWKW#ZF7!;h4VrNycn1QoOm>QLj!U>Ap>5lEeM0=y0J9Z7Y9?1);2c(fA-$KIc_US z^ZolOLf-*%Bd(XCL<;XHOk0ld^!85lM%y#t_Uz8aM2CZ-iV|(A$YvE~X}tU0?=KTT z6{?6LK|Ua;TA3Yfv5G*RgM)MOJ)gYY!o(r%mYAkYrLNZArr;!pIX((qNzAc{ZyPJa zt71+^?YU50P}gr8tH9B&zpvyliux#tp)0?df`dYeTl==>G6O>QKE#}yK0<8bdtQC6 zKAsL!TXn^alat=AxYjc|_5V$sI380PJ3m>;GdhS0`J56kj);aoXxRYf2@%zI=iM28t7TpuegZfEug zF}M0u1*gv%5?*1qs?UXY42jW-hIa`?mcF4?=dI$9&>WAojC}6xpm7P^0pBx=8D&+K|pspEj>hBGZfkKtwh=syxcT zFy7D)xoM&1#Z-Co|9k80ip;zqNd-=U!-*SkTKh!P#Ua)@)Hp0^<-yVbfb#&~2 z6t=03EzI<=?&z^7=3Srxv|mkGw?k{q$%K zEHj-6o<8|+AW?k(jm_8eg6i=$E(59vP%}EBzdOG=dt)`0)^!26stXX%sIxQ>ENY*h zK5-nZ#Sv{B;2FSNSfc?ulAjmM!OzY{Y1IHG+I-OiF!8!-(%>0fw6vF-@e1aI^(yYP zZ0+bcu7i{lzVjDYh$_%bQQ@6|C!gnx^l~$0ilC`P|s^_2>EG&70}QJ)FPU2GJvW zcmSxh{_Xsi>8~>|9M-@6APKi0+R_|(mzP6)`di{lQlBV%%>#K%t>#K%n>#J(! ztuI)Srti)-@SOkm+c~XAywB$J93V7j@Hnr>`miFz?aXiT_nUll}Aj zV-4{2Q?`%X7hV21jRa&oAk^^`6j# zv*fiy_z$n54z=_{)LJjQ>4wL%KmS&fw%_^j+do9lZCC#Na_{<+9@xWmSt#|hI}z^) zTkjeB;*N8$+pQVsmeCt1L8H7mYCZ1{z+Tiw)bb73<*lu__aQp?U;rDu^ESnmo6G7z z_nJFeHI+tabJ?1gxk7xgt6H;L?&WHRixuH!qV;;!_gvdAf31(ALk%>CrDI<}|K#GQ z83-`dkgbWgCVj^@!VB5@HeRkV7<+8{@vgwl+gQGg)}FA7T5O>C_4$W*zhPE(iTyNB z%Pg8-n+@7}(Jk8Tb#Impgp~T%wH?tAY|(x>J80a>TVIC30-N~Rcmd7`YtO>7*~{j) ze1ETAie?VW6=@!%naA=EFH_r1H&Sg$mfR{}5i@M-l`g?}6zFJ4=#H+y~c1Qr`|YOx`w7R{nJ*RS8c1Q4$Oe6n%- z$;Ry`(dGs5#ud$T)C4QLn?b8tH;d5^VYmjNv`%MxUwx=Lw*S{3fBNB1-~Od}qievx zwl2%{t5JzRRGk@m+*Yk|eVeVcw{!p+^`?8P$69(9nz!+hoj6qufCdn{fcXAA^2PM*X0#0Z<~GB z`m=ppJ}wV1{dlq0K02>ixXkp&T2d^3saM19Z*dZzPs`6{f4<{r`JG2wdf4tUT5;#m zmcF%nj5fLRXiKl!Jw_|%IoJ#%__alR(I@j;p~yZV!T z;+jvh^Y;c{**L>#bz_KWQ;I>Xle8__x{K+XICR0!q^=A8g&Da9z4J0&hlX63>F9q* zHvW(_f5$FZxJ0V_|42#`!#MGt|zh4_Nx=ShO1-0I-&6p0(jK*zw|x<+xKbL5nu*e zc6HYoU{+hM{{0L*=ow()P4yv^kcC7aX}z#?Mm(+vU}ClQv$P^xVzsUQZ0zUd8Q`hb zclz=S@Tlv5>3s&a@6)a`zznwR>aH`uthQYJ`x$u9Gr+>X2nzd8lzv`^fSLIr@Oj;= z32p4?ixn|b54KKIL>t)Tv^5X3XPY#B7_jI!>s4R)gn zyCs8hd*G|`-q*1k5;>r57f*xz+Ku`2Ni8z#Yc|3?i=S&_qyF>a=O!Xje^I?uE@~n| z%eUNEva6TTKn5EyY6>!H7lxq)TH^h^IeWyqkaN+?>f$h@>)0x)vG|z_*S|1#L^FLe zvDX|3$M2@Ejb)f6R~8dX12>P(SBszhKQQ*Mw%gR*l&q$MIxpZoR7%vrWqBS^BGI4H zYFUT%GpzUbwRNoB5UT-K@6j+5ZhepTs;l={_-{kK$0SZ!e=BCt-Xl8#08d~wWlAOJ zA+Z&=0r(=#iCTGfgaa~YgxKy`!OqgB#H*$_iQ+8v*$yb0S606HZHDLrD2bNN&6tlZ4SR09w)%u5d;#EizA+|smCu{UdCe71bdFQ6)?ZW2Pb{ zm}v}$x0BSH(&*cEHDFtHNO&=~sDRbmd-f6$J>Yu;_a!MATo?G@dqh!Gq0nnP!eAFB z5%sOA+%N@qf;MUOwwmq$>`yYuE4V@F;R?8YJSn^aSR!NqFmX!JHYJ=AA>anXN$esq zkq8}34&wy)37e8giT4IRN2#xp=^zm+KERxi_+?UfO}h4UZ0{(|c9Km^ZWA}K6O%D6 zZn6Q{l!0TX2~7iJyM%5C#RQ3?Q!>b`G_T6lwS!~&)+Bb=4UJ8q>`bC0rqory*4NIcof#aA$=*W zw29HRZifdA2P z%iDND!wJlS17M7wVVu7iU^oTdcl{*eLM-CE7{HB;&m}BM#{<$%GP@Mv8o75sqy*R) znh^+1DpO!iDO#5AckM7^Qn1|4@Op3e&Vxfgmm=orc(&f2k4{49MM;uW;APrTHU$8) zasWmJQz(nl?ImrM&}nm%o(|imJzYt!IQrX}N4ocdulUADy4q^MHlsoe-K@M+ysWa} z?Bbyq75(+_4}qR6s~lL+DK0}@<$$|P;xKbaaBV{Xe+G=XOD>Hkz+FmQauuSXHmAXs ziGwpKL0cX`G7LAT;nr!ecXyxdv^u0CafMaf1k*J}n=}l(#L^5BYkN)Jb<+nV!_c0PiCiRYluad2cv^G%&$35NFVd8d z$`ezVqO1|32})=TPdG#0(MNp7nhRPHSX1*+(bM_YQk7xq0 z_wBjeCN~qHVht-3AS?*DnL!|#0C#Fv@_)SvfRrxEO0^ZaL;lM0CW$)2Rwv>4cn3&ci#`%NjAlyD27lRtcAeD`k|ng1+sbNDZyJvwa5ktSrUCC=h|J-yLABJ)BTNrMlBf12q40gl3FBtt_Yx|Sxm~i zObK6TSeBUy8|^4_ZZc{*hKK~P~Kbzu9b9CL}iCh(5Ru>Ny4#fy&j|}7X%W`r)P3DWd zi{^?Mu}8fYi9}RO;Kn^8>mhLF6d7wqj>ab@L;{E#8;jTYgr^b2o=Kt+f-M&l>nwW* zHqps%NWG_IuW%FR^JO?I4!6oj=w$~~VMz-Gkg>+kFlq??Tk`p7kR~e+bS|}84BZZA zucI0b5FI2@G|LEk>Bo;%LJ4szT=PU?<51L7^?U3q6FWCM)u2U~cp0z85G-3h!Og16 z`vZz-h;n!n!QL!kbsR>D{I3RFoxevk18|k%!H)+>T;$lWB|H^ftADphc$#~^T5Yql zwQxAaWM!)I&-xFNd1Ep{-0gJYP2$*#;d)bAZJCkw=qfvfoJP{c63>*lu9OzE#EmCa z{<@@;M!zeiKfhigf?$*-^i>)lf<-6lEXut7sH~JC^2;F#42WgMoJo5+@pzZ!W~(HA zZ+_n?%rdM1>Ot*zYlN(>Eek0PQ3;`u;lTG2GHpie07|SlqIb}4N9Q-I0ar4S+V2(VNt396l#QN|&0&}^1S&jB1z@&!5<%UYG&fTzA0X<5o{Z}5)m9o$00aUimkoS+h*?a#Gc3$ng?7cvMLxBBjY8Xlca|`QQ_D zwS4075?R2ed6^DUJsw#I+Cn(m=?2p(r+nP3E{zZ#EZ}!zXI&yCj%*A=wW8PFlt$+` zs{vPPNDnQ;UUy+f4DIH|{}po4dd zB}7#r>y23qEz4xsC@Oj>=MU$c%oLaxFi!(?X~XM8W?wph57Xe8%G}Lf?c{KlY1ZUP zf*K)nJfd zuh7bI2;^Sl_az9^>|0GSN)N4aVxpJ)P=rxfGe=nqG4(cyStMDVA);tVP^PMVzK46o zO;$!g4}srE`Ju7y@B|wGx}JfLGD*OS#6@|F5+9`E)dA3D;Yv%+WPn%zJBuWOQupAK zewC9UI6!%UXDwEh`Mb5*qaM|q4U`MO#?8hb#_^sb|50uRfObT=!Av1bS5=g86(Mhi zz&?x*b8sC?owrX&5M;V@wrWMqrr~zV4kp1X#OQgh{SS9cO zr%|GqCZ;Xbo#a;MDp^VmG1OhrZq14UWYCqG%I{u*D08IT@6IQydsqGLpJ7kj$JK60 z2Vsw>d<&mXY#4O|;94Qe@Iv{S)U;azQKZJ+Tm_oyfLcT@xz}K5gy8N8?sA=#nzP(` z_?ZsUo87FG&5=yk)qtOgcAFx{%0aYS+%$EJPyzlDGO;KJ+q>)5mJFg@G6Rs-%GX1tOWgGpr#C;2Nt*YOChTUg- zM73Am+a{JZ3=i~ZQHd}cK)PmS`asiP0#LePcN@c91nf6x0)oc8<`S(CRphB=A=QuFhXk;Gr|{SKWTL{Gg0pLGn>Xi6U2*v~c+>XW-1xVQYZe6}JN_B#I#v z+{z;=Qev4BG>$vDf;;6q$^obNJM@QOAve(%JnR;P3%w$Qc% zDD|jdw>*o3JbPmfbry%ke`r7FwIC8m76 zybF_hf_qv|2ce>j(>*{jx@ksnQl(pJIyasNf>@Nc%c}$-vVgs)SDO;6lo7SRlc*S- z->e2)sUeSQ2222lsmf*~hdFYCepaPNqcx+K#74NV2W++dV4J-l_NB+QK?9i-39urn zrc63Qf-}+%P48^yPBD%D1hd4m%SCxld;%adUv6zmILS#ZA|dY%&$c=ZBZ>RffUD!z zCm7JO0xFwfDC`>ulm1Q-o-DRd-f11ci&QDWkST!hWM}OpV+GK4uL2y`jxw%+#DMzQ zTe8NTO~`k)z@A8{v{vVwW+-V#kl+gm!UJ?AaqRjMi&B0za}9(zzM-Ts1-1Z*T=Cs< zi&lqpB(AVB0vu`88Wa%->k0;7u+z!Tj+kTwe1=_2AJqs*0W3jrQ#lt(sc8;FbaKY0 zOeU#s1dVkOPD%*2>j}4<$tWS?kFBZA2)!sI|9XHLH!}jVbckv>I^BVOGeBh;pv!c> zbS$l77l!3XhT4#RN3W2P_m;; zw@Wj6wG)W{ga{5GK{6#g)hMZ+#vo1!$lrp^WcGwe$%I>8;fD35v^u3Dv4yr3V67`X z)w0tHAb-ECGO86TApQ)SnDz*Wu2O=A^@L!(Vk#ocRMrK@0g7b^jB#_<#gUu%8WSWE zz=j&2Fw`_|N{t7IDUg_qpdJG6jx1m_Zs&m-gQxeU72OTuzle);5b#ojPM%iYXia^@ z84c+90GXNyFY+`U2TPj~dub9|cLw;oNQq)Wa9LAbcws2UnzFyyT=9Kd_%+a#GBPPc@gWKC-ufda_o_DmS;(bnw1xM`%14SES{4x}R0! zQ#dPtE*-#OdqD3$d^!U(Rj_jd+SdRMTkPd*g80;xK3HcK76bIQMmI*?GFX3E5iP87 zX*vu>3Gq(4KFf0|foLE#S7&+V=>Ys*XTt1CxUi!;_2!+OE)&Y=3)t|T42Ts^97*k8 z4Y*>$aaHkiV`gs6s68{V(TRuQ!bnpJ64((c?<55a*E4%UW}c*Yf;xJ)9>lAK9qCQB z4Y-7EHSUJ04Y+kg9y)+U08X&JiUF7WZovK2!U{EO3Cz1;a=EOIQ}>4z?pSk(TTSHi zY#^`<0C|Yng|&pJ=16K&@*G=lcB4{4dB9+f^@ zcMf@gfGYsag+bwgdF?Qh7zSvjnin_PYof=X!uM1hD-^m2w2SkEtbu38_|G8 zmx?rsq6@0WUo;V9!6llVxCg2Cne3E7P>N&lLiJFNO$m&XAu8lD0x(|N*jsXJd$vjD zc(Jn9Z5StT)8AFxz&Zd_I_eF(lme|G8a$D91JbyghMH8w zJrLSHwS%J_K~lQVaR;b@XO*a$#qgC?Tnqua#}!unA~GtV#%aujgGfDs)@PdzH@X)e zRL>BMc->(^L%W=)?49g@(emYO)MD4I+X2%(WSjdL3cJ4bguC#gxW1pLzGZ6+2n+=|^@?n)yW4 zxV%fzouky=q)Lf{Lz!@)$4JrLXv|m)kO;?qH$BNbZ`#4JQXf#uvmxxTA_7L(^c(RA znifbgr>+H}Dx-4`f?vd=Rhf{QX~>=g&&-qNdlOn6(UBznYQPmwcwFx!8W&JO1LTUq z-i;0pBQ?}uz&uHnX50G&t`>HrH@Wgo8t{H%1tc(xPTegkIdjG;72S<=;m@xH(W5GF z2GI^Iiz8~MQnG|{_BukF8q`Z9q6abR3 z(Yrl|dbb$$BP(y{yV)?n)p+%-#h<$0$;+WiZHTUe?Dx_<^0Ln5`XqHDG9jc{!5$p| zX_f3f6iSc3_CA86OS9F0pOIcdgE?Y7>MkX0os{&I)olzw>EM7~CD@xQuNHQ6y#ynf zG|Z~*sZ%dO@2)DoB)#PKM9ic0uqgpS@Hh2+YX%LN6N9*?=u|~SQ09u@RS~iA%)s6! zV2fUY!??=A>&+B931MUx7NWFg>#a?6Q{s3tC$E+6O=6b5B1_yLZ)Si35h#(kL7wwnHg2cvrkEPnueVU@eFC-+GOS;63GcuZI|=b@ z*b31AkRq@N;Ax!U_s-uP{dBJe{Cw0BK$Xa!md!_83K_xpA)q~JIY9Ra_Li8ZM3y;q zg2ilL7oLgx{i!j3+%6AMaq9Q{2lc?bddGT(ty{A40pNcgg!5;Grz8`nPD? zGy!|c>S6A}-B*0%5!8|hGp16x>{mYMY_>vY6-0~yk&&g zi;RjH+NazNHn&p=ur!I1u1a?~0b2-BkBK$`q29JyCn2lhA-~95MD}K{os@EV?HVq` z&hhET_Lk&qu0hliYNA7O^Hqj#Ef=X{H3yo;@68n#mO}tPag=HO<(UMkena9K9VU4; z%~?X@JzTNH-6|MH7iOygS6cXEt0iQ$8cw^qOF`41zXf)XyiJqCoVLA=E7yB-trqqX z)DmiLnXx_hb+yE>zbJ@uexi6w=q2;JTC%2=(5HviXZpS+3Wtk>II?^KfcFLC!!09? znNculphtH~Cmj6*tOi{1vPTx(guY03&|f6+iPS($WlD%{s)IcCC5morwUngSJGqcM z)j|ihq_-mMO4C%)0jy^9PNI9YXGUVvs{vP=^sxmlDQM9Edkc+cLeaX%hqTR=n(9P{ zd$z=G2wZ$(M8>uW+!Tawa=Jx706Eyd3*2>qo007^NZN#fF%HpHEupN<8YHq4G=Ymx zGsUH8OtCki6>E7!UiR1mm$E{NF=frk!b8BcdJeYKXpf*y4$9U5wlbJc$;2=b?!Xi~ z1+Jk-k#g)ld`;qllg+{L*w=3D(#Mv#)&P2lga8AiC8gc?jV5scBb_8PwYbaX8lB&) z3MfcY2d|h2+ZGd-rj1sQB}&{V{axg4WGVv9M|Y4R_$$cOfRGPCDb5hO#$bE%piZ_3 z!VzAR-$kw=dE5ovBLUcfd^cI-Vw#p2bx|@UjO7dmnC|z(g9vm#=%4c5B8Wn#gv2Yp zv|UQjsxaZbdz#_n%3MI;*;|Po{IC=x%d*y$Ok{2eG>+}AU9fUFpAlVl%qZ`0D^F;* z2I@?INVN~=r*AqCgWjB9y*`@;A74Cu;(Vz7e7yd7AM~N?-$a{VT{d4FO@Ep%juz8k zzxwyt(^l~~|99Hw51}EodXXn43&ffY1R99V0FHg=dA^DH>tFfsa zERIgkrf;g}+?!pUoh=`er$6w?RkNswO#gm$bjBO^cN?5dPZs?tG!Gk`oL;i3^)gko zf`H*_Q=36~Xr9>z3l`_CSu`}}QrnRiDYrv479eOhhQ!yx#P=LybdXP<)L;(R_o zh1oPiH(TOm)3I|gyL$hc1IApYXVqe| zXu^ z=wf=wWNg=6vRvmuHOc02j8?@8{8k&0+gabUQ#(B8%;CLJ3O*cNo*uCd^JmMm==b?F zX9pKYmz;FY|FOU4H|Nv&?7u96%WAi5yi3S3$c!_4bmr9lJiCDF)HJ4-&BlWLa8jG| z6hw!?x0jbkpWqnh^JmVuLPvakAqxNz;~ z&nLl(2(MwaQ|zU_J^e791@qIlv(uB)!>U7F+BOro()$=DZ`g7Qb=`-<`kVT`$;KS92uvHA2C! zrwd~VV;5Lf>#@hUdQs8`VcD^wQw1)ZV zn~IsX?pac@*>Nwsd-?+UCob(fwr2n#SdVd59;V@5FZ^9Qzd~~>60jgQszXBX7wBV%?LS7;9o168&`%E z&DNC_dAJ!qbtdY6_jy)VVp2jVv7taUuSS3RVuTP(F|M}I-bV)=2%;FL?@buUfz@z!6_ZN$EK5o1X z+pjNLud5PVPoLD{2YvDK0^!@p+C|0SjjcJ8gyHi5&E^RHgN#F-#5SlMxr2k?+rRwt zU;g}0{8k^qKb~KF^0OXp4G*z?Q%l{Le6Jlju;VAe3ct@nItu2|ml zzWJuIS@wh12pZFc=bL{pj%|O~{N5L;{fFq~)`xgS*j{PgsJyjjlZbn}a! zH@~=ci`hiBK#kq?{$lf%#runEbZ7I`Rn?*fm)E{IUMGFE{;?US9(Xx!{ka*S`FQ&F z{n0vs_YJ}2<1bFp=s6(QX2KulC=PsIJ#4d>KfOQtWx7|HphT70OWT;4`^%S#0ojzZ z?Kdy9{oV3qw!UPbYI)dZr;EL>E?)#>HviWjfBNB1-~Q!A(2UGwi_5ENGv?-9$IV@T z{{F|_H`UL4+pX1tRR4XsU;P{G2mg9GMMrE@aXVtRNi4!J`}$S-zyJ4p{u|V4_HXrF zT(GuUu+I8T*uCvF+xSa+<<|eSd{Jjp?1%bIHOpjFD;ZS}gKcl$A+lZtv*+*YZ&#~1 zpDunno6g=Y-uauD%@3mI$lKZlapUE@g6_*fNGQ{RE>>l-ghJ#@9m zt-Ufozgz^r{qQ3AV|@kOwzXg8|L`j6*ea(iLEL)cH@GYaz3rrhnhtcv-(3M1Om+@!f*&mfh_dV)pOG z%$q;i6Ew@%d}seQ3)cE>dNyy4UF+Lzbl+bosB~R<{eFA6e)rpzwzf;= z-5$$+H`&1j?cIFC>^3jkxbud2weMU_YIDytv$MC+2*Tz&`*-7QY<#o_Iep%Ki>(W) z?Pxq;-eymBkMWJxa^AGhyWZyM=DXN<+kT`sz6)=;C+vP}9dGN}=~cs-j>EQkGh%h%T)p@YfV zq1|M%H$KM=&tMy>>m}YW#qO7BH{3K%*Kxgpy&1WUgKAeZ+l_B;V00ZcR{ZV{>tfTc zZGJkA_AN7QAKiMI?XPZ`e7mX5Ot)wnWK6qujK1 zp@p{A#ZYy_&nA18H`pXwH`&M4F`Qjja(=#c`cAL&kPU)H^!4TRXj7E7mf9|{3IDvt zPFfe39{bCy(?0~ieEsTY(_?>Gs?T=s#)uo|q2&6_iFKOeNUhUPE)q4{;~No=1rHuknn^v8M={oBUgx#Hf2O|{i|*B83Y2z!O? zlXd#HSFgghc-vSn8{+Eu#;UD}^m@|uKX(0W|NZmX{@(04D;Xa&Hukln@xyd}bykO* zmdD|}5onfw4@%cx23)`fneXp$IOc!9+?(NTtt~Knc~SG$+Dp=`{Z>P#qRM{{xYSJj z*4sV(+j6X~KX#8^F9=Tk_eadPn(Jb3`E2j^XRBwM&0q2u%zmF4Eb0BBS~D|7_pT1! zUp;&2)atKSr_q85CK2Z^!ll%1y+>OvhM#`C*lRnue!6mMdk1aW7^yFU_sbb9f2qf` z6XnYv9o#}OTFv(sn)O1h1ZZpOKGqE*-Er!+5;x9#@9O!TC%)#fV6N|vKEl$@_Tqi} za60o3ywui<#RYm8{^@{J-F44F+r!bKWOr`4q3!r+-Ldrsu5l+IUW|2j2yemuH)KY` zUx_CYRJI_ z-1S71Az#^#?=Gj)gNt*VMg+>wK3PDS*XQ2*hka@F%UEYnv(>=U=Jj1{VuX$T8aEm% z6Kk$a?C)F|`<01Z$*)OsOi`zEOg>9boIj$WV z(o`vf@?&*>1Ns+)aXlV5zJ~NP$04zZ@GiE zZN6J*uja-~Th#0m;6$Fbh;DIh-pl=Z1AVxNF%IJu!HnP6pS+3*jIf2tMt-ziTx7em zi;3V<<6NiulG2k}>*6Y{+&)n^QYV>T-wn6!;%n6M2~DLu`Q2&fbSdyp9kg%T#cOk0 zr)Hl3->#qOI_=%r#gy(m)+UbV*SzAppE)b0LnT?}%_inkkyJIrFlIiLv?|=X(QaeK z*QiZ05O1nYY_tvO;l7)AZEil(B{e=##9Y>y^Siu>NzHk*T}<`C?(E{!z{46S6X2dw z%A=b-%_r20t-Ik?R(y?GM5S%Li*4L8t{e1R-)R@OvEsG4*}PZO{1eq{+fK;tuwpv0 zJr*mb_-}VMv1`7|tz}v%>v-Tl!W5z1L{2eBzHa;#9{@yG^0kq(*0^ zrB)0K8z`yUY2U_**XCxRZYyehn#6`?^nbxF&htmx#d)ziySM}-9X~?7i%S&BNn>#x zl;HG*bgtX78*b&ro74=fv^gQ=A$|H91pAH?a@#Imn;SE2QR5Tc7$)^d*Ja^#;5C>;nq#ONezf3U_F|VKo<1F(@p!f zO$_bT+?aQZntjURiLv=k+Pkxf|N0`pw#!EJ6E9w$&9Yh6#4HF<-|7nfSbOlBxI&W@ zu>8E?Dz28Fo0>Sn@_jS`gO9ja8MX;QdS6OnUrx`hm|TiGP* zkJhw>3>}wfb`C^9&&vL8GRYx`JSG=~w=;2Cl5vq=Lt}zL7Utu!=M&FEC$Pin+HyQKfq<289?>=Xg zy3*0y!@H80C^HakdiV6{o(mZ@H4Ae|4zoN-(MTmK);K9L^j3)qb0O#$_6Cyb zUKez18fX9n&13|YinNI*i7qB^ALDWF-}GebLjf6F!PN_Io3)8)x6uDZjVmaM(FH~E z`3nlPxRO(jaWSBvC`(|;Yah_Pd1_3~2t9&Tt0cE$AJM8v6BK4zV@~K~iZadck4f?d z{5dcn{h5U0ngX^6Xtt<*Lj@pJZ{jG-1?3x1e1e4{i=RwlgD(`Rq!a8CaW3HUMkyT| zw7HnLA!WkNSVi0bfn4Sxo>XaNP~K1pumxl?C@3jD*Ur`R5tAL&~o|4>4wHQ}Sl%tD@@^cpx zSfl+8@2VTTH-G2^jZB zQ}3pN{psFUZFO;)mMP5)prQ#I!X%Y}{~1b&$k0s2Dt4-Z*ipCxaw3l=6-yt??SxJT zCVZ3Gd~XFFGIX-1X|rZd5b`CMD90yEA%ij3l~ebAD4I|bl_cv#aywPd2ul~&$S|9b zn7YkIIQX!5vKDBfDzSLP4S4$fdkuFpM7q=B9rQK=y?9VJ675M4T)4 z5Dh3NvV`8ty}RXfpH%{Q=-ta~)Z4ey7BZ%K=b7ww;?QUGwo$!TsAPC(Tvb)<2^d*e zlDR9&2};!_d9JFw*hr)htLis0YeM0aXd+pTQC6zbCZ=f(AuZ7h4?pA`5vG&mCuRvv zkqgm#g`7ZxS2;X$i9$k#+7pp^H9-UH{X`?ni7~+wYI`F7{Ot++Lva-X7*I}-({qRR zqgxm}J-RupB=un_#nY-& z1H+G9q*BQa>4Yc;S`9-#%BXJ@d2?Y(fvh*vUKTgJI+qYb*86iy7E(eAVt?M;eI`vk z2@}|bw7QmrF^T)<3y&-(lJV_{(t3)Ub)%4>QZ>|bTJenJq4Ru>9snC4>Ov_ivegXB(lle-6_Z{sE`uUKSg(BIgySp zC(<2|6YxF~J7DPp$_ccEC9uT39Y)mm;x4M@*2j(6tpef74}s7K)vPI8G%51NQiBwG zg4vjMX!@=~COO1;rJE&|>x)y)IGG@B$OMZm2CZ@UjO?~LNGk9kl63K2h2mT+Yl>WI zQD&i9#A%esyt%}X4Mp!2yqRpWM9gd>nS*>*)su!j5#pKdEz3ui6XO?SN1uH)HhL)- zhl8iZPEao!kQhZyOkk<1-w9rbX9d+;vuJ&CZXG2ey_V>)EGS~qIXvtu z2vC9oSx$wDCd>);CH&763nB5rQp}?;?!7J}3ySgUv7^tvAp5}t1(}7a;|YVJD5!W6 zIlKvqh@>u&3=F6=zp5@MCMjizBAN6s&(X?^jKbkjGpP>u(qn~;`-~`lU^W6PJD?DQ z6lOQJ1TIAS6)FB0kPxoIBQZdlxi2)x;b3vYi$B6V3$q>9rD3&t*EUYg;B1KrF z9voB1u=ffQhkMUvWN9&eQFip%S7kq#v_Mx?*}O>$a`DvcDWt^&5O`H~AuX~AEfF|s ziap?F1)d|34AO$!sl;ZZuz6hI@S#L>KN;4nxvy1e!E7j1Ay-=!I`p)#Vo!}J;tr#_ z($_{|ibUx%ys`FB3K`&)N_e!W&m(sdVq|I*2Qn#kfg_VB>VW~AsHdnqB;4C8X8sMLFlyZ$0t4i3FE3N%4sLmTHtq@+ z1qLOOvU!2Ppa@X)alnR%C8r(|QrHl&9P;KoWTCKVWHK7PLk#xH`rqb?0*o*5N@Qg+ zIZ4DA$)<}7lR>gmXjy>+D6=6sgB%1|mLhgyG9o@#chvZ0+R+ZwX+Kch0)z)Ofyv_A zB^U&vAIVgDGD~%LRGu4h4kuP&^vuTclT|X7?n51v`8hOsL4+wuR5Gxk1ei%9qcD0y zID$iqWYD1#grEs_fhVHSoI`pux zE)IIg4mKmOm;4*ow1~$q)sA1x41fGH;>+pTYp5BF_pE^wbcyxk=>G3Jxr3 z@}82SNSxGw%gNiBYz z{CS&SjR&d6yD z8Edivc+gyiZX&m(U^2#?MOiV43P<)($XLrun3?DzK~?}TDc5WpT&V$44PfL<@`+lz zhOHD%GP%eZ$z=^4Jc~e(7UZ0zG3Ie^&(Ah-fiuhc+DJ}5-cwG7R`1NUkxh&73%BE4 zDBOOKLJNYpXyLMHK@W92tQr{;7jc18UL%9Jz?ts;1WjDf)uDo=QW!LC;;8FWoAANQ zNM#BgXj_?0!P!3uB5h8Oo*QPJAqJX7-Z$Q0!vB5Y!e{ z^pqyIIFBqQq(~ivR|YVnnxAD&mIzz)7CR#gjPWbD<6WrWei(rIS=oK<#^2cPo+lf`k7FrQ+G$5Cu1$dnXT>p)Q8>NX7d(`?qZBeEoy8?s z_#zpqU4d5UU%D}}z!<-jJN{xzxk>o*X79A=Rs;qZ)s(PmrUANHngXaQk^%pSu;&25 z3%YUQ>gT8_EE;8;R)cQLD`ZfAahH(ntj#8~ETlrkKr&800jgSbu0tk@(@<^FeK4Us zfd+r77EKIhlJ5A6 zE$KGpOl8qn7{E+pKI;O5rUp9pN179PFm9%}q)1l>#@nl(6;2OsJ87bkZedu=T+Xku zTI?+L0&^h%YHWxyG=C_COo?fL4^j*;T2mLM z%0rQ3g+@4l_`=q<4mv&Uosk8`_*LEUms`~h3fcfgdTggb1V%_V3sLT1VPs^5Sg=qN z801*tVTX(;Fu-q@Bj>nXGO|?E*?1RK(i3W;tQ zs&4D4qnQR~VUR+h8Y&hpoRFp>&IW;jzUh5BMS(%}m-qM)1qQ`Z9%fd^SY0ihI#huH zQ=Cv>pyGE}A}sPt<@6K6L`mefU@tbbg_~tK*vV#7qXzita}tV7(NJbXE^HZ5t1gp4 zCdZChrp*Sw7FL`%8#>S<+WY5PjVv(6FYAuK+_G*CtXdss0)qip&~0YGgQQ91*#bH_#HsfJ{kLxHGZ>%wIVQf~=r5 zg_p-Ev!Qg5On?Cb)wpelf3h};Q;X~!%E<;WF36uI0NRyS%xqdwjQo16)%c)F^j*qu{5q-ffyUVA5!?mN()qvApizxdw@Env3aCL zj6S7X774A#$Pw0J7X?rZ)aV*VRQR(sP>-!aiADxp9)>8F>sg1@iHvA;Ko^C=OcaeL zT1lEA464o_giZk}iD(lL!B5zK0~jb7eb>$R(@PQGCc08kmEAfPs8BjcI{?y9m`pBY zKVmKsH}eJ_y=F6_z~HgQPL0ZEpM7SONq9WP?lBs$dmdO%WMGI~9S6elT(Ip&fY5vG}up-YHCi-c@k)Mr~4 z!K}c!K{E$e!38Qvn=m5pOq&ahl`PGxH#T=Se@EC*&Xx~QdYBP)Q&Fc##ijUOhd<{% zp$GO4l#eSX#?FY!XFHG(WxeyN9uNb{3E;hB;Da=4fE*A@s8p>1Dvl9_Ub3YzcsvPJ z4;O_D!Vd^6Ig<&!E(C2{WMu@DX)~ah~l=NG)=zuU6(@#Lk5XCU!hImJnIx9>DGIZsYdlTAh=uPO>O|RLED<{TIgvw_- zkOvh4kXfDrfeon+)OOjhZ13qtHR(Z0|;p(W9Gj?1_n=6lUxi3 z_Xr6As|ieC$727x)Dr9xO6stX)Nb5n+AqM&9Gqzy-d@5O<-wRpr0C;}>Chc(~> zY)TXqR+Lv`9}7=Jhzx0?1{fTlg$&-__)gu+i<^-;L&{+|OuhEqxN>6b{HJ`j1L;qA zmqBtfUOb?j$Vu&!4FSUpf~l&N5uj{X4YyhuVNk6AX{+uld>W|mu4D>+Cls=rOcrv9 zCHED_9+w71ERYd+P;?ki=%HpSEKq?TPFP=&3h|OjSYhu8b3sKZV}d~16F~()3lB(1 za{&-D_C68<6>FG|oh8aF>;Rw*-D@h$h20y{39NsFeq1pTjUCR;XVFeXvy&3M;9&(t zL3J{D_L?PN`WWE(YGDP=e0n@Pl)qvL;F_h%!((p>8B^gwDp$kMID6nhGMUUW+bQ%= z86qj1hV9Z*J?z#pF(|BXMf9*(|3|WobSgB3QX4k_mWMB2EfnF2NILhvBLZ_~E+NYz z=9RJSVzaBY{eqmp{`BnZUQaZ#oEW>I1#)5s8d@X~jzW()4`@)(uR1H$00lmpn4$sI zpfC?F2Sl9>8d;f7!W)y#BBn!Bxz6Wl1HAL0hWkoNWrG+eekz2#55zAB#8AYvEXQuWjU~*uqIl#tTonbb-$R)s^#f=%RahU8FRrZQGM|iR|l(BW{8x9R9)63 zL6K6XqQ>|w0z}6xOU2OUGI39}OeMO5?KrZa7`uN3f?@~yR~R!MI5!q9X5JN*HK$~5 z#$ca{@`gw&JT+G#R z*`kP1zB!wJ0!kK;RI`ASmF?G6EH*yLGfP zAFsmU5fnh2c=Dz~CM7C>tJqkx!r{R+f*pGNSRn%}UpXO0udxs7%>a`R#Ym~8)PNEw zchM-x%mGH&c%mS;tX?1i$4Y-FHZgr7#U-ZYZAn(Oy(#6PF!t#X;FTQ$8AFd^^O^3| zi^{n2VszZ6e6|a?PY)w6aA*$DNi_wxk}3#I>^O&xX(^_WA*P*&lT@_`z_vCt1rs|8 zhsMkyh~v&)g$!#mAyj22Rv}~TgTAh!zXsMyo3vET@&RZ9bdth-P`%htw}E2PtIRUb z3)9F@yAH4K%M`cF2CavpXva@fW|Q*5LlO#^O)vC}Ewk|@HY_y>%LAo{Gvy@}y%0gh zl^5ec(gTk z<)Ksy1GFU*JSGMOj|l={JQ~(oc-osd8}ilqmk?~> z(HY(wuyV~bP*^=l5mY>>tiGh7VnhnMCNN4QJNw2S>2nhp#fxBUOpjt0Fg+q`@;G$L zEPIK2Wf?i}sG%S!4V`F+0yMXCIXW|4mCW`xjr4{7h4iQlX z`!bTDK}-W8$f(PSlB_I8=vU+fwc2>roQ&f5L28j;<86}_GT3bti8;cEz?*UWr0gNL zO?n;sZYE@Ccp*i+s-TGAsw!J0w*r^|6$u?dLDIKpAqdNh=mWk*&E6$qpQE3MaSx*8 znE9@B32&MW0|bAC5zvdwxN>4_Opjt0Fg$i3GJnSy5BfWFym1V%=j0ijjl>)+8kQ>S|_c=auJSEgb;VEfl0bkD_7fDK_pd)3HtI-#rS z$dq1@RM=nIj4LKa$Mh(60@EXnvKnA%FoS}90HP4*Lu1VxGoZ`KZr8|oT$@88lCkdE zCGbxiG{vBp;L<3-27!#i;jMmvI$-k?D`YH!W6hsMGSo+72=vDW?Pg`RZ8BicnT#YT zqLOSX(N6;gI&OG1NfmJu7lcfBB0+!}6@b1(al;c$O0rb7xfq5NKqVp->-?Hkof~_t z%gBOaY)}sb#SVaaP&quQvPlON6o#u%g*}8;CstWe(oII>@CXXDW-6F8d4r>vOnWb3 zQUpZ_wkE3z|_2#gGMF8lPj7Ob_~F_6{QyDIXYl}ps$f7#n`AG zNQxam_3#qBVPh23;3f2gP;oSn6eyTgeV`z4Oj3j-V#$&sr_-1By3`~E+7$H+B8vw? zkuJ@dn&WD49K@4UNinfjMlxaq`BP<>fNrd5z1C|dNlUkr1jcGL5lt?2@xsZd;%4;6yP!)n5NhIjw~(4hW0>O>;SaK!$=D( zX1QIRg}vy=rv^MY^*MT(D!WKbC2LYfA14pX7^3NAia`L#HX-c+Dz;O4l(<7)nN7~x z)Wrk64t}>1ZrQd41refm!{R0&YIvJNWi})Y0A;3j&@(m<1eppR4jJ5G6^gL-P+;g! zRC9^h(A%_v5mshH0u-)lH&S|uosk8`*bpBGj2(dZco=~ZMFpK5HP;UUgI=pLr$=BI zHm53g3|%%O69rDMm=^_D+vF}8Q&dHVOg#q z%$h|ikiq9jDzevZ_M$Sfs2CgM15vR9ARiASDgd>|qo9fkh**k_MutNlmljFW$RH~4 z?#oHH^lc4uT1|+e!ho_9J=ox-z*JL-v!Ol=Fnbl`+Vp2i@MLrj99ndEaQP>2R&ug1 zb3F9Xi;%TggNO_43hNG|F=$IU1inU%O{2t6TT%7;lj0u}!mkdwtymWHY^HOi z?EJw`Li<8*c}*48!VF3mXi2CNBrbo&`vN=FOt~f=3hTCVfovMe$JZR=GaH@(*eBXBwv> zG4M~MX_Ba{p0%N*UCoq1aV64CKPetcVfA=RV6R?y){7B!Gik~-aD;kzNT9;X6!`f? z&&@&Zm{6W|rpO&!V+9($Ma~p57G%P^tCFz@2!*M=kwQik7?bg9wNsr*z=bg#}2RrJT@YK2o$+)k<+dsLDC7m>1OK0ofpDBHX2=CQ$2u7;-X-F+l4#>d4)~QOFRqz^U$DD1{7}2*euvp#(93 z7u!H6R5VpOK3IcVkvEfZW+8i#HyebCMa)#0#nuDFK%-(gba+uk`w@2-Sstahy7(J2 z%3*q2kK@XT(QCISJ5jrBSU{9^4J#=?4#9&h@@5LXaApn=-p1X7`XbK8)LoiSSWHim z6hKn}wC)^8;mx?g>3b?eo5NLUCoNZ!lRAq(!c&+ONkPslwGArZ9}Q|Lm+sGnwmF}8 z+4RU#xpPT>A@Iy3ON+uKOi;DCHC3D$x!)p>$9BZ?r1wlBEt#_2zUwlwq!_z+8%ePP z#oN>X58oCKXDC{b$eS_TX<><{SThFDn^AWMkQ5WTPrB)@Ns7|Sob8M#k^<%dkN4Rq zWY7@FT$D3-4Rkc6b#mW>-_4w>I7JoNRs@cPZ}E<(wHo=M#go+T6+{?t?RxI55PP9~ z4HOm60~S`sAZCH<6H6W)`wLi5?PYra=MKKTk!8i$<=e=L9Vp*^5LrR`l0gb+OdhL( z2JjWEp_INv3)L9?P@4@g&M=vHQj8)ifbFI$kb40XCQr&e9lQP5D`c!YC(_uoUWE*f zqZGJzQF%6`TEJnwXhh(MR@Vp0tcsvS#t=A$g@;^~*&vQN4Dv(4Tj8v-I3|vqfQTp> zO9$*?TO5-*f&?T~kN12;$5hqGpbQySf^J)I>}l7)iB+6YM1y%I_o^!_n$)bA%ptY(nm(3T zI&#izf~SN=a;l5N5;AF+E!q}1*vKdLn2x~AV2Ndn7}OXvPlR^hm2`qb%cvN1By$m{ z7~XM0>|c8#_Ahf$TU(i*86@c5X3n^hV)Wwe$xak+Ka8ZX2)CMkgu;sHjpCt73Tvw& z_jAQ>JOPQqj;kUmNGT>I#gUm6Nr6X!6gE%IQpn&wim5OZ6J#hY24is|V|@S%H3_NL z*LWH61|ri{4>&=}Di^tled2VWxR(1_uEKpZo*sfI3A#^}s_CSI1P$Zlc zU^&}iP-X-04W2%)KIuhfT!Aru^)|M}E>v$njL0D30NSR;>k%0gYz*LyB0>#9lsFp; z3e(wD&7Z|Nf#b&2O@-HU^r@titG5XXgJ0`%P*7W8i(y2=PgU|*bpveLY!rppGaIYk z?yb3QaU+pKgv9U{E^yc4FhOFtGTw_Tc29;3EvkGx%|f68#v)0pG!QrlDNp`t@w5NO z6rjuL{OWA+BKT>(I9g1f{o(18fS>NUm=FXvz#$2R$yh={HoT#+1_{kz5PKAac zJ(3xdw0abUC`uDnmkR1@TP7k+Lwh1CP9+p0I>oS#L4x)ecP|}fTsbj%RbIIhRe3m@ zhp)9DN!{wAG~Nv09>HLl#hcG2vy?$?HkQCagAG%86-j~0>#0K`8B$G2N|CJ!G-s?o zZy}*b3LXj%v>b=Y%%7EcjbJbSu|?}8k%_2~S~!OC3`}FiAkLwV#FMW?_no z(SM{Lv47QHh@Wp#=DziWw}g&@5bfWZb!1sFc3mE_Vi)T29zs?C@M;|rH114Yo>erd z<^XkhpdMAISA8tq?HmZu2Jc?1$chR0K4B!M&eE-u?lZEiFpoO*xMmJ0vVsgGFn&~c z4Qee2QdMwS0vE!yfy$b?;iiyrkpoPOriGix0p{G{kCfS1l5+t@xi}lj1T8zzi~>Ob zV*iniS2#qYLI%VdGHClpsYVtUV;AQkFm|Ch??D6x@oD;_$O3~iBIDK?01~hks`#Q- zi}YE?Q&t2<0p@1}g0{$_NuV{rE7LX=PH(0TSQ6%%P{@FyOWOut8-*!i>JYgfpa3nV z-G^rYD6=V9Vl;Y{w-%)Skfbc8AyMQ`9#ZIrfx@EU^dtZ{)`mg`4I#yzw1*PHIi4J0 z2fU~B8d+Y9U7Lrz*oE4>2ay*Xd%47cBs&V5WIF}b3=gzi6KEd8P&(H(htn3oMa*G0>{qF3_lyv||#Z z?O057V^TpD*)o;@Zn}Z+L}*`zGiAp>pkfWmYll`Zc{8%47`rqNNwEv1c@H8fz&ViH z4IwE&Gfju6v?w#IZ_Um@?WL842XPh4BBPNTz5v~_pq7?EE(cZ-IM&+$^lUMVi}S}` zlv)aa*QSVR#BC3ynFO>6qKr*rX6W*0;90JcA+8K8c>j9kEuPQ>4xIq?B0iKA3X(so zlVST+#+x87tojj`q3_~eWJVSkW0&S3Fm|Cd??D7cl$IE(8jCjA540GS45kHU^8me% z5P`7=EH#0VP$KLqtHSB=h|>yh@VGN%U>j7npXDU!59C`E6? zO<7E?$_v_JG~wJ{EU{Z$WaO(-79)a(VAYb!l#9MKiptGlXc~|7M3F&@^%h*?ii*)| z^U7Hg?!7s`dVMwx<}ZSyX_dwt?APhcPoMl&B{bL9e5~`E>yL|{H$O+~UsT!Ad}sf& z#OUga;8hUT7k=b-hJAlJyPO`K?Zx|h^MjD;oF)nC*-Nj#9X=(BM9KnCQ&m*pn71-E zjSQZ7q@i3Xh>JC3a~nHUL>jQ6In_ixhZZ6Mk02{7Cxe6K9h8hXAx}oxCd3d^4 zal@8pvIrifHMWTrGKN|Vgrx1hUS!6V7vq=aVO#7(Y2HH^7t|A2BR7rJlVx6p@DP!V z#d6u*YMh%~Ts&-YX&yW}tNlNU#IQQpgra2*n<6pr@l!b_Bc$TEp@z+K@D(17HwrK= z*>OWrI1MH6Vrr}&_hSMMiSaXq)n^pUQ(A5+y->(zsIO#3i9DLj)r4F_bFc|>A%UCb z_Ks)Si_vctJH>?3{m)TeMB$5I^qPw({Bnyf(0(TalycJw68LjjUf@DPITOiPT~dzX z%2`t^3?j=U84nQW26HiW_m3iWh*~-r2@j=^p;u-MgBB~laAxC*syK)u1bI_8Z_3cGv zMEkCDk(8&;Ga?3zl8|v$- zoKuYq&e>e;hF@ zxrGri8{*xbW~}ftE3(7D)n`-SJ9L{;nGM{Pw%T$bi0So7q~;kDnE`m1Rqo5{x4tRj z_*9xhrkB@mY>_c`@r8+uFSYuDradV|!)g?x2yp~hmd6}X_3EG4(UF9+#nL;y8e^0TM`qes)9 zTSYI23NOhwjUX>F(93~t6`u%S5}K1*+d{eD1V9{dF4m?nq&F^ay4SjlEi1+@zc5+x zrPg1NZdX#Dq{<3xUMq9b$XLouiZfa!L)l=Jm86&#gz+;VjqI#xvI5>rOEi%TTquhx zrBz|_Xq!=i`;gfvWRMjF8%N|rRt-|*Rcp~+jHp}WSTzWJ-egT2sBgM=c!$znD)g4w zSTPdaAxt??$e1S+n8Dr({g&DAP}D+t;0t^!wUup#g zIwCzDMJ5fHP?pB%L#oIKCzEgh5UK)bKTo~UL`DQtbTkG{WLO~I(~=Z2sGJn6dm{yf z*ONMmHR(0X3Yi?6DS@HEK@?tZWoeo20|IY_n3dd=54ISGRl`E%p>VTNY%ZgCK!!H) zky_+}b&I(puMK4KyVQh?EHXwf#6V#gMlU2}9-Qb>CU`m1TPA*IOF zGc|}oeOqj~jj%<9*XNd*q-WotNh86WgDAWnXvmVJE!8}*UJ8kv3j}T@3A9*V-d0U8{I^py5!h@wm5n!>E~+!@XN0-7s;?;^Ly>fr#(&FrYYiAhM)*)$bJ zKtGGz9;+wy0;itNpl;U`o0xn*-+jGYO5IejGDxudEUWh-j(9YBc5Sp1*|lIO3}5eU zeoo4NHN76)6}Z510P`S(j4Hm&Gr(ChrBe|YIWbI>Mb*kMxkL52d4UzV12jlzz5&Q+ za)$~N>IrQ`!>Z;7k|l2Hin zK&IEyj4drj&!|OO>_A5C!$=F#LWanw#f5~vDocyRfI?+5lMKI&@ef5>7?L7wG*rvN zgUq^t0&Au14e0=?w3yJc+r8p~w6JP0QXJ&Gq=~gb1a(m3XOuMJ{P7sGAiE6y+aV8n zGR1DE&BR)VQE#U`5#gbXX09rqryK+fV8@aX7UqDrWMv%YA?`(HY;iGqQZ3?Q2a;+Z zMqF6eHnnmB&I%6Q0DRs%lj%|iSwZ11+glSC#y@1>K?Aiilp|)N7#G&k70zlomI|Lo z{Y?R)Qyo!Q7ddFcCcJtnoZY69L`tW7WO zx@B#s*+T)7lWJMw$l5N6voWB!y!d=Y#^_14h>RUbs(l!dLF*e7BF!{FWTZr5bw?6e zRwTrVvwg)K$v`6vQvc6jq&e2bNifJ5=TM_%U%6d;2g&faRq4 z(*4q2crP*|TN&dg)kZs!RQoU@0}C231XX0Pw}JB!$>1yna1Gs6n+@&-c&j@G6p=yg zRD#4%%>zU8DG5Jt*c2uWTO?-g&2IT!HrEr{`sc?HZ8lem~qct-4 z+o-W-Hj05j!v-M#pAj^CGCb2h}Zp#*`Tx&hay);x9N;3J-%^KY?e8W&&8zn&4q~laVn%2_W%Z>h3rs zio`$+0$E$A5`#Daj+87oKz|ys)~wlPO6_3(~D13Qh@HN~>Py^)1X2Qmtm zFVW>`c4o6t*fXWNSb*MvNg-n$n3Bp3rEqzGGe`k)J4Yd7J))zkhg&a3v?Wx4X+gsX z@4%#xK@+Et!_ylSGH_Rr%^kt4m*1xQp)BBl5opSRK!r5nCNEr|V(!9-(m&?XYhgx~ z7vpC~#b0iA6pmwH)Jj zO|sb2dlg1s0v!i(p$Jw%)dR&TU>EXG3Zu`-0J07teK|-sPs9_2@XF=vC^~{;+j>Qa zz!h1WXu{4hm0lr(>oKG=m6~J)lp39>`a_9tS>gh9g?=S=7=%FgK|z89W%TyKdyyGg zWIRH46gI|}n;k`yJj!}BZjaCaYl3`FjZA6KoQZF6#X8`)g;D9rG77iHpN*R5o!}HQ zw7w2gcoD}<5gMj0((g=kQoyG1M-`qLsR#{g*BOOg%+X8j+{)3w?yj27YHS*aKN^>a z+}_Hf>FaB6suyG2@=#{-AWw}=Px@pMb(8VDaO)y=5cbMP>ZZc`rlyhkXp=GNPWaus zi1m7=F=Ym)DjGfDo$N-yJ2T&papeOTDt^w*EVjkZ!K)yuQ`#0kM|}ENf7-I4VD)gb z0h(bLMA`t&Fu?YV06EeG4`EbuZrLeO1W!U^%Q~4}kr*6(^Gd1K3bUapkDS&-c9b>3 zQJrLHohTYIU=q#tXbY}3m9vU*me@57MGO+GPU!V$55)DdSPW|^dd91$QV8BjmX%Au=j(A4C+bYAR3bfrZ&cnqZ%3JQgT3YZ8oq) zQdk{EP7@g%{VK6v6B#ff3IJh53YnM^i~*9H3_3oqymks(L?533No9@-g$yb%K5{vF zP8~9)sT|2LJPwLn>`nDzi(Aw%c5xIM?tw(cTC~O$Y_4E`S^ON7NU<=|^o*QM-1mvb zmKUQ(zL6KZ5cz%(c@Y8sHh{bc0dP_y8SL(o*tc5whmKd031kOWlNWfBQC%UUFnW?z zh6=*?TZ9|>zGO=(`MEex(Cq`JRs;fYA_Ee-Rw?_5Tf7URdglii4WKZLZv zIZ?_5a>RB?)5as9nFYjl>3Q$T-U^#X=oADnRcS%$IgMgGCsC0WsEJ?(`jJ&a7$9F1 zkwJcG{Mll=891mSnT(bk1@Y9r9Hg5kT0n(tAkP#Q*cl!B;H`;!*#v39Yyg45>&HVW zWDtRAMt4QXD3K6s9a7QUWKzhG{FTR{IOSqXrCg)9+wA+J>B6sk;L^UjkLWV|6W)O&j&E|6hr4KaC1K&Is0J3oHX5FXjA9Ap^z; zjhMXDP2=^{%#^N-D@+<~kq)i0$RN#^bYZ#hj#_v$4H=f4s5hk;?gSDPX`o5~@g+q8 z?%K;&zr~kC>k@1XxuOH0T5xmSh*M@`yBNu1B10o%U?u&l2}c$gj}XVe%=mKS7%_c| z)SOs^hGi4#LW8h;+)SYO)t>D%{5d-b2@FP=UL?B{6u)9GyS(;}Ef*T0HeUm59XuwSFW zKYj9BMWCj464vE&&F7}LuK65q{5{$DoUVVKZ_t}>(3@}2n{UvYZ_um2>>0!UXU|=I zLET?7Q1$i8{d!&YgYTnP@QCkYuHj$KIcN0n@UZzit8H{nPxfpL-=EG{#yu7>n89`H zReJR*3wXnJ-EZ}Mty_*h-h9i(ZT4|F>-AT=czrg@X4&3+IdZ+4cF+ImJ#yYm)^2M} z*goDka;}bC+;`+c9eHx!ktgcN<$Xsk)sds{zC%Z$Ja%;Nu_Jlx_}*j3^4Q6}$4=z2 z(|eDd%407%;9Z#tArz6HMfYZCk<8Jed$Y7i=4sKrnOY=swdme#Et2_Kd~d!M%X}@q zH(!foz82q`uf;N7i|@_XVwtbS_vUM{%-7<3^R-y!Yw^ALS}gOm_}+XimibzIZ@w1G zd@a5=UyDV)_RWn$pM=;aUDm)h@o8fC!~jpaZc4WBLBGi?p{|?E3E3&B?XE3$gIP<{ z%Zu|fjLN+e@L5iyS98P`)A#sTb{v| zq0|_Ioe%Kp)$$gbT<;(2}b>b~|> zjz7QqB=xu9qkHNhj4kUS(LMDL#+LPv=$?8AW6OF-bWc5mv1L6Zx~Cq(*rFct&5c8E zkxSc$H72-4F6EXRu~ovHbju}1+CjO5kJ054|K3L^E%|&)y4)g^mbcgoXx(NXZxBkw zqlt#%(L_V>c%q?rMA5+5k0u%z`_V)LV?UZ`VC+W|4UGL5qG9{6#)`IxhT=Y=fsuAl zG$0RLH1IF6%m|z5&Njs0i#a;Js(ArqL$hkakmr$s2t%!)_-8t7XMAPCxw{foJJ@y`2 z_u0prI9K$T%!9!nk9jco<1r5ge>~>F;E%#QwvTJObQ|-C?!`P9YZsXZ{Gw8O_$5m2 z!#tKT))o`Ejd?8ZvG>rr&pzJ7JZKR2c;}bFAMgA!_~V^l27i?EyM0{SDBI4jcb>g# zbT(qFT|B=W+3NiAOGFj)-G%=$PoZ@VZ#%!sd+a^5?z4|KonL@1AMXe;_~RWR27ie;b9iint_8waI*~goXkR$(f zzM4DaWZg4ICvqGqY4Cd`>D(}`E!VbP><}iMv-P{W({yaC-P^^%H`sqwb)f%Nm)PyE z7X_YN{Pgy6N`~tt*-PoOrU<9C`{vk@O{HSJpX!awy=T+;oEwwQ8!hcO1njeEbohr= zm0w5dmDhf9>1Kgq>#n)w1Q&pVJ4GtlTYu|#;}yjsyNRN`aVl^tia<8DDPG?VPBF@E zoMQq-P$`zc$Zd-DxApX7rwC-<1S3n0hNIojVkC?+?qqp#K8-uz%5KHEQu4(zvx z-@<1v_vVp(+n%&7!}8+TFOHjwC+7cVK>8muVCHVr7pYtvX#wu@Jk z{bDF@x!4wLb#Y>pxj1Q4UW$X0jiWg|n@_7__S@>T&Fv&^+d!(7kYuP7$TO}pIQFQ72?<6swUid#-1P3>=@Xs-(J{?2n_f9p6m=}w%RhdT;) z+1;x>*c7(c;$gGSfv=nYy&MWifExI}p1z$0i|Kq}#D9EuI`*?&R6Xov9X>c*UJUPGQ?tiR~)_$<~_55IQbb2;@Q;oehyE;2t4$6!U+5_@=`uD4& zvtV)F8n`*8ACAthrgbI$esDHDS=^0uP|I<9D4s4jzicS44O%_y`e2u*Z{PJAty#K& zM`4O{{(FbPo<8}3shnO;-%n?Y`Dzha?)mW%U=X~K_tT^KRdq!$e|LU$_9l4EA1>dV z&W_Gr1YVNEfTBvjyK?ULJi4etma7pYm{TrXPd1 zj1`<*p1%)5Bm6y$L#n6yzJ2@YfBJISc3$pV`}=p*-u5w$=JP9dYxC0lo%_@N%hjTD zg8#%>`H}6+Yjpw_2YwHS;c|1UFIT*tRQ=DNPl6Q@*3$QgZxNemwf7JPv(uB)ziX9ygT~98Fuzf zJ;(Ip(eVO$Z%-FE+w$r(v;eKAfMv3C?HJ>PXtN7*pAMxgY#6t=3`xrIinUUoeSU@AOsphv4+eex1VAX1vka@!8dzsp&L} zMv}suj)KMI)lzNz$9nB;oS^Zh`0wq#zrNQ7+n{TQfP;xLgZJiaFWzsMZJW{VRaY2< zlfO@ZpKK`)>JK#nRQ>G>d^Na>mS6G&%TM(wX+K8icu1+ZERB$2Qw&414np6 zd<=ua2V<<2o`|YhJyvtHdAQY@R(6YveO9$Hy14!I5&8py*ROQ7_rf&`KggWlbrTO& z<%q}Q>qcd7Z$MZ0`A-mB&3>7k|H?1?8Qagan!_MK#jO^5?Iw(sqsxmk^ocj8CnsE7 zsqobRFZ>3#FMd6RG5vae`3u_q@%bg%(&FrsKhK}P|JQ%}G5FVSfB5#hKmGWpAO2cP zoCh!(s(ns~JU zR{#0kbm(R8{IG+j!}tI4G90 zPaAiV8?T?E=`vrS{>&B&V^xu6wj9kiZIGpUwE2g|JlOnxsVg*pK%MDpwAAL)M_c(? z_h`OqEQk8@hx*SDe|>&_W^Z%)apRU|FKv81U%YuUZSHT#H9B+ic@qT`jM8;7b3Rsk zW=*?mrrB1ymftOxrptQjQk2(7Kag@-J;YKG-$#W$n$@SFQI!3Kw2YgN7Jk^YAi}@Z zE9CEj3wSrCg4f^nGl)KP5I$`Po5*RV;{QHxUZee`8P@;(>MKUatisay@Z#e#G}f|D zs>`pNWXl1X*H+)t8KYTVY&-U4vuEo&uC46Z{vZ(`dcD^($?|;#oMF-8ADH_)lxntCL~t*Ba42u8}}6*R!`jwMeYyR>wo^;8q*y z);TtUh*e!@kuCZ+Pq9Uz#|-V2-(h-frhm`*wMlH9U@y#7aYS7UEWf^_qFX1??bwAhLGPw62rCYTkpn1 zu5YRKdT}m50Z_vmDbU0#Jv@Z2z4X#G2B&H6_WOK_XYNf+VL26lb^Su5TjsKw%9{J3=RDjT z{*f1Hmc^O_V&&>txgr{CX1@u7`F^Nl8h=-7TLwD6tM0D|)Pc_B2v48<9=)vBr}_uK z$zPfn=$a?Zj?T7-YhzKZQL6k}w})JR_>x@^<`wwvZk`A;e3wVnT#p{Yto;yGCIYJb zfDQ?hy1inf(b%!kM#P%{jPK=O*cway)a+?A0U{o>3whssQ<>u9hfS~f6PViayHB+a zQu_%$Hu`M+`P0T{S7Fz0`DE{T{j0``t%;c4w0=RO`TYOwz58<$*|`OpUvU-xL!U_< z#u!^jvYr7`MF^8{?!Zt$k~5V%MG0HR0$WlfnMda2{_S^t`_bxd^;)uoACR$=!E&qj zqj!J%d#vxV!wqA68-z&;&tzO`@fU>rLMZ1zLr;Cv%?g=$th?#o&QW)>Qk&x z&^J=^*H$g_jMoT65CRf`aI}_Lwa^O(p#HqO7 z3%HF93uU4IIfvVPX1LAgl8EKFN&D zn`f+9y^z9-&9Y_+7|ho=k7z8PA7}4po?x~MI>R#wi?cx*{-N$hK{_^m6Wk_w z;KHbKRp?@D&;|YpaAM#l*_U7v+uqYw_PNmpm$76Q2wj{GufdBsVsH&!z+>wIGZlE@ zt@bi};;{w74`!bYYZRyt&CrwAq$`W8K8ro^vak8>Gc|~ zFm9FkxyCCDn9(T9&5d&lnK%!;!r~>;U3vjHg{O`Xw=o(589@wn+=m936eeKk0hq+W z={}f{1#1L&eXf;%2;o}Zx|Q+amyUs)B4`%OFLFH>Y;g^R7!8~&6k?QLC9NO|=uP)+SvJgmtOl|hR&c%)WY5WHL+pjN#UNSx9;zfc<{Miqbg^f<; z4^jqbWglcYmJ|B7U-3cKM07Ge-QC2%(b*p!#IIil9s7%m1WiLM;WQp2n+%ClE*>;2 z_g1oL+5y}!&aZ-5x=1K*WSF908J+e*(&UUeJYk_AXaE!_gAUp(uW=!_VmUnSw~7mu zkxL0mJm38(1v{ccJ}}H!L@dDhGDOr$ikqNFg%1K@>Z=P2iNx#rnva2~W^WQW*vExE zxI_g5`21fdr%;NKhHSxq7GhGS7NZyc<8xe)g8((!bY+)f-N%;d@Ar_4X&F)ZFGh#C zx%ggSy9WzDH|vnsX7@y7%pSS6`FR^YxgYjYU<>`iR8uZ5|xn1>WkC*zgg5M(O4HiciP<1aoee zk?S_rxsQ}{gysU*&IA3uKm_=Ql@sIUiBr=U%#|y)VL=M7fQHN?ri@W|QNDJyjqIRS zw!>N5Y1mlGFhdv~D7a9w!{Uw%qRE6_p_)d>`f|Z+k(&g54t7uBVBS**Ys>c)b(byA z6VDRfRyxgu%##PY%~vhHsbbh&zDmXl5pS75q_al7S(6}ITn6t7!EKqav(*FJGK~!* z4r0wB+A`m)9?h0%9J;-Rg-x_&ey(9*qxm8)0&B_CJ276NvCQrz*Sa_~e*K>fUPe%I zG1$s!Q+dSD6s7zXQ?9}lC_p|i?cgvbiDfR8=ZSgokW(3=;h*&ir`J+V(li&0>)V@t zg|ZrT7o1fVUy>UjA1RO>rtnR6u>4nLs=A*_MrS=ka8?$%C_pg3`u>cC`!ns6SAmy{PPT#G?8});3OOab?RxisC}gd&mJ#b30>> z1pVLtVH5iNJ{z&z=(v*o!E8FP53H%ZPER@q{&?y1Z?kuB10E}8#5nuSJzZk|-nG9| z^DOj|f5e^5!(22A#zZ3YAq=eA-JCtO!|u!76%D;CGhq!glg(SCg^-ucv@`1+Z~nYZ zpG-}V{iDIo6ka=+6DIL&q*W>oE-Kd{FyHCJ7it4}^9SWFoJr$`jf``6dh(_}S-~>$ z)eJKa1N9^|eK0^8YisCiE{(=NZec(tKD3ErYIk8gXTrF_mee>5qd|wq0^_Bvn5R>; zT3pePUMLx2lS(T9x)GM7+ZgxDSOKeBCf0%dxK3F3%HN{o2sdVy&etLUDrlIA(eX#* z2D_PG=hL`_jsIL5BHyAR0+*1-3n1B?KWJ^kB2y z@F8p5md2I#|pP#$wSq8~RZr@qo#U|-Hv zrZvT4rR}_4xR56YGqIQRu~x7dZA|~sLvZ$!LJf%$#?BFbMrIU0C3~Y z5M7Gu=Li7zCH(#upug3{o>u_RIanIr{+ieSyDr%c@^hv^w*2*V-MxI7@uV0 z$lFIPZxrS`?pZ`OHgQ^~{~t)ns#YO-Yri;S^E(tnDct{^E3Xg38u4DJseF(iAmwv1 zI6*1#=@iTaF%zh!k4}^E1ois{sCs40>E=etZTgc500*#>IR~!&x0(C>;pp`3k#VDw z-h0##cO$zxo`HkF4#sW6ksT3`sYs5KBBldZ2)LhQo*;cJS8`?2hYE+;p&atf4I`q( z+T{s4!pQ@68U==li;^h^>T&X^)fvPpxfNRC;<@y4=BQZe^bD_Nn3nOSrd2;~L~p8G zcwYXYo#l|ccE4V~IUeo5585ISQeTNZc^xbMr4E_F{2uSLc~`uU>BZksNVT7xjEhZh zX`$a?G~DL1iI&~`iUpb*7YfGR$NZmhobYWrZZrk+cjHB#^zxTZPVu)N0b}}wvK_PO z0od*2R(ACwn43-yZmq%W^*&1OZ{B*1XeazXHpD#^_#K;AEI4inqvvdkmCRCGi~R7L zw}5D)Sm&Q!y?*id;g56%#a0ML@~xgcdsW)VFN^;-PIqc|ARY6Lj`G_|pWcIpqiA!%FoP7V-T~?U(JswHdDE zHbv~PF!OlXo*=sN;x+pn7c6sa9;BGLSWHBSUqRTQ=fdP{!h!q7`{M7CI~UvA!=0_Y z-Dzu^N~1VV{B6PzH)Kgl%&vG_p{1Btw&i?MMVd)xg^r_SkJs#X;cXQDvv;#(=(8|m(@o^5y6x1wU^pV$9uyZilvL1aGJuf#r$vNL;offVekmG-Hd z$^FuqSYKo|_SFK1T3}BL%*l3GDRZ*^>_-e|UmLr8pCfd;n*AwR4*Qy1UU-BOCTp9v z$YkYnh-WZ@Dtn1yisdiU8mMu~wyR)o)?HyE+TECK;i_PCC4J2K-MzU4wfnL^C0l#R zOtV`zOf!3F$sCKfmT&#s42|l@6O<5E;ZosG$E$N}H*-Va)eoRLgz>_c3!07j(NA$! z!OTt;G_yFOovDJ8A+?MMKpLFyD?>f+aBE(C_y+GxW~2>lMKX=-&-wPTlb8ym@@`?* z3>No(p+oER4UQBlu6AaL>=v8wyBr(H++h0X`@O-vFv8&jA6c32G1FPCuO)yUBwjopT?UX zr<2zoT_>A6*)GilN{3B9gvYRFcWk$|Q{GK&J~!D>Smin6&ycJeV6pp^Ch1d{$~w8T z;uLeJ0y;eDeG2D+*&pF>Fr*co;J;wa0gA0uAMY%@y{tDk(-Rt!`?J_YrhyGr-U2t3 zLl#(X``>xi+Y`eqry>6=X>;{xu$VitUCeHQ7_IR zeiJs*{G7*t$n6&q(Am4iE4AXKNxXoCwBh>6R(Pj3Hv)x*{!QVHfZmn`9c)eR>~C#u z&E7?+W_nP4>yx`yySJX8G~KUOU<)zXqO;udM%_R;9aJ>+e+Ky9unyHQU>3v1Z5%sdi|y zGQ84_R;$!n+2h^wqczxQwMw&I&72CKZ??;ub($^OtSoK8O|o9WIqacCV7?nyM6KdyM0CbyIJYh zT9A$D{%%CcNcT6T%2s8!-lQ9~M#&Q5xZURMC|zoqTBC%iIRockU=58LAnNw6B?M}&n>lIseXxTY8e7#QjNe6tkPTSQeLxFx-WrVr6SEC#S{8^wjT>gMRH%llwFt8fsDK*GD zu3>Wd16x<^SISjtm3B?+bIMl0*Sq5RD7|RL&gBn?v?Jz8=>^KLVC|G#>XmkblMAI5 zSmiY?wy6ApL=Z2X(u$S{LjEX;Kq;kxN%AG3gwm+A>U>Ekoiw3Cu7Ph5L9$H#v?}#7 zE$k}ogPH~MTrl)st6q@=>Q*|nD&T(;`A!10mr5YId6q~Zx_jE#;v5;N)9nKbp~{hw zI^93rN)wO+m3VXm8NACmAD7J9O~EBIB$rUVxnzcAp(HG2wUAu90tukJwpLMCjFQY9 zVIwHX-0g^YQhKQaJLP<{M(G8%j8NRUWeAA}#E*eZHOg@z^6H${x z{mLvuB4U?IW^?T>Q1g{aW{Xw3Xvti=BFSv_PNVJUq6W#lTiIQP-R)8WYXPy~5_XqT zSldY;CMkERO^I`RA%SQ+Y9n+i?GA5Or>v-3Y1R1i)hRDBiOxA+oiZc58wIsfZe%_Z zF1fg-16#Dgu}QA!m~%y_{FGjx3Bc#Hu-AjltTdf=Rjena7uc}7f&o%`sZ|1aQi{ZJ z3<8>funsu}0o#`bWdOUN^iqeYF*wF3;I#Wf_lUAvG# zq8;6@hPrdjgKLgxCv0u5IU44{B@ovf4Xqaz6y@yDkd!7z=>-~+3i_h-0u4!E2}*Np ziDs!0S);OB;}#0X$Qs!1T;rtV0xx`>lNqI!CeBIVPe+Lbx|}di zD6ME+j*^Pj<)|b=9Fcc;mnzegP+FFxKsN&AE0F8T(qozq9%W&nfRSY>i7iu8Bi)tn+vcu8Bi)6ju5!r5B42 z;Szn9(hD?Bp?N4xyeTw5rHQxHd!Y1U8YkBrcLlw41Tw%eTwr-!E}1!o3yrhN-#gdD zp>Ya&;hH$Sfy!c_^a4#)tqY|WV1PC8e3ZXHQxzH@$8fc#%DvUlRMmKtUZAN8NQh&& zzzzjW#xY#P>WG8`j^V;@sl07eyMp6VSW-NW44|X%lX6WQ8m@|yqx1sdxy$KHY2xh` z=Yw1mhmI@uE!V`G79>d(K`M?Ak0iDcl}61sEJ@x~=+;*BfQ#GBoE*Ci&H zChj(=*2J5XQgj?ylWHs=Orha6sRROLf}>E4nz9r`Zws$_gYp#6CNje5c$BHY%iZFc zG!4pCpot5Nj9Wx#CxT(QMP%47mk8V?FQ)km z%ZAd6#Q|~+iCaX_{DsECEh1plA}&u^L@lvzl||GM>!K{8uJGA#-vlfMk-5Nq6R;Qr z2G4yHW-)LKms>=z7(`~XvWR3f4COBtrN9voZV>?!7wf_;BE(3FeZ(yyi*MljmPfU~ z(hxeevWVI$T7>GkODqk_U!cUOI9$qKn%M8+{Zak`9A0ITQhGs^0^zfv>I>pP1)@%+ zQ49DL=eksRfsG;@pHz3zHVU{hry2X+I-xTfW9*6r& zg?F5*5u_Y36)3Y zTjKPhEZVlpf1vsc!mb1#eC6Dtg{3I?F8A>*u@tHP0!vZ(1}VL? zpoj2%%YA%SGm6Ux?&Gs4Zcf)!eSzaZh)qf@(2^0u&9oP)yuij3g_x);(l#b#Ev8y? zW07jQ@KFd(O$o&k@%WNZIze>0h+Cv=rHj};2CRx&n9!jhzQc^XMZ2_`T#N2GShONfqa`+74H!JLFk52RshSThy6xZ_6w!SxweOJeN9QRv zUKf@dZ6|h~Wzx}hV(VG;Ali;fblZDe)8U8&+WB(HrP04lN-wa4MVvTCBw*1B&9O=8 zMO(s6N-t&!b4{m7=>_csdsm1=L-<-1BGE+IXeRR%BGD2VzTC%dHA1-0O(7C(kx!x! ziB4G#UqEsdBGILyXm3v;5~}h8MbUhU!(G~lMBNDLKm10fh5qz{_A8{W$ zEOrs&!Vw9x*!g=`h=lTCavwV^cHti6K6Y5_BDRUw$b!W#_&i4>kYXj)ll#~aeyZ}- zsJ@H%Qz73KBGDD=$q@-y`U27Ah{O_0pUNXx`YOtq>btFq;=3G?fInHiKdSGx5mn9Q zA4MckykCUkP-$dYi$%kux)bQkET<2Ky};0(wg zV-|Fq?gLC#Sl(^QF%1ah$U>X$1o+s6ue?nW0@QhiMn<2Ha!lkKV&}1TEy^-ckrc2h zZAW<~wycOA=77y<wuI1RgH(Z#klbcB0lMN0cmvfNOFbQ33)Y_5(+hfPjd7%Mm3*K)7Y95T%wV zIiV1xwul1eF)oNb5c^#rN-A4`BT7I()Ob`L0RgEAWP!>fARr}%`5!nKi zUJw;8qQ5z!WKr>AJW4M(gHl9)^B5N(E+W>5BT7JA6!Jy&U5&VKL8Kd8 zLf9NbTsoAbQJMi!tF#@}!~qD2_HL05-p~NTYNk= z4M>!*G!&B96jAsJNol8AN!Vdb%DK(NGn=SZT(@NmtaBZ)w;1Tw^tL?Boy=8MWB zv=dnk3Q24Vi$NiYEfFiGkVJ*dDkQNZkPL++b`{b_W7Cl3Bi=iYO+#G=;i*taqSYs27A=oW z17arl3y)1R#Ek0&97#09j7KeSBoT<2I30x}i9pPRO{ENS*cH(n zJT?s{Z;IF$jwAwU6S1K@HVxTGVjpoN(PEqUdsj%J%8uo+X*l;xAl4j71kxttCPxy1 zw5j(%=>;)SVjpoN5vQF950yd^m42p>L{&e8BZz@OFr@{yN0Apab3Wbz+7>JoN+@XN(uK>LQQQUgx3ic4a; zMcYZvJT|RMN!nuCcm>X`;C$vQXNX`&us*AoK-&r4XR%wfonn5_0BJkH{kqWLX*)_U zU`PU?elSOA4QH-zS|kmshb+!XSxEHH&Us~9DPJgZn0g*>-~<*ktCwnC68e*tnS zG*yK>cg1Nn3VBv1qI2XK$fZEcIPz?9*Bm+F$g{=lafFp4&p>Wp*4e=E7r1A*u z1VX@LSRwO8y=H^qBU$a71ai>Z)j72~3i=eDSeqmbv0$SP9Evsa)9l27#)Af>`% z#F1wpr6T5%BhMBi$mJ$So~^DC-wzylHl&ox2aY@&QYyxy@(4(&LUgJA0;E)&dZ&=* zjzX-dzKgRqga?ktumUL+{EQ>dKuQIF;W4b1&B^rwjyzlaB~E7?c?QBNV%9nG41`s9 z;1u$#_=`fGJ7WJS!WA5_S5M|g}l@(i3oM9OgF8HldnyBv95 zg6LBH1!w%JtW}D?0MZp6evUjNvPxx{Q+pZ#n?l}F_JZ=eBGrpp)5zr&k;_zh0jS}? zi30E$!%svCu)u;9^dhies<{|~Vp((Llca=#W^TilH=}d{9K`95<;^HtL9;Rlc{%dz zGbswAT_y5duU6A1_=I+$OYO?aKI|6)zEg7 z1tJ$4Cl=9mRKG%DM(8cH9VHr&oT#e9{S{~@a1idVKs$j;aDRnyT^{Me{S}5=^GH_i zuQ1%2M@n#ih2hp*U+qwOK|A5W?ofKMh+8opr5B61<@PC$ln36uroY0@US^R|%3q;! z#+AQ9)rD66ik3*DQT__02P%I>TU60f{)&#kYm~pDqx>p@Ub-SDSNSWtBIki(x$;w~R5fP0%QXbU=h5gO_6~M`b-Ac z)7)PHd|mlfD1QOIF7O)eudp0^E?2m}0{FV%FTCak@O5$0nDSS&g}tx*73zdX<*(?f z+6z?QHGG}(LGG^rzOLF){l!kS{BwT=aCt$W z%3q=MI_0nEh#FViUjbZR_%*q|0#zi1-oyPBz~x1)BOWPlH4V65llv=B(?Iw`mA^vm zBOWP_j9}qs~J0_4@ZFD zgUVmg5&W9h+=#eT0f`Q-0II&AnR7RlHxuZKuhm`Nj4Ce}%{hO{n^9d?dk`q8Xb%FV z6f_f)q=W)M$|*|a&qk45G;{o~ycxxH0Y}j+0C}#Ar{=;=f-=qEt?yA*AH*j1i_g9!tjA7l##N!PcBFG*EYI(!9sAmDohc$%10;L`K zRF%I1@t3q=m2yCHNQ)7xln2^j#mWmrxgbOeTmgKlln+9th!}n0=>p#s%AoLcnRwvd z0O9FE!!{33tyL-IV2cPlTzI-{Z*hQ7c)HL~)b8M(E@Sw7ZMu|h&`x<+DA^dx=bCGm zQVrS(3xa3nn8$-7r^?gSXoz`IW&=-&(6p4Nt0^=|<>_jQ$SmdQQng8xr>iBZ)+$d| zTX^!6r^|RS)37^&UOM8OC*|o~5*NH^~3g2zL|+Hg-7JRTy4oO`;= zQ^NNV_jH-3M6{#)1?@y`HurRyr-U<5o|yxWhp=?Gr^_-5I3lS$T}^=yDoFVH|JhtDJr>iS!(r`}~JS<}0a!(gLB?2kpo-VW#k%QdR zWgZqz*WA-(9v0D#YB2Dy2&9&Ky5M0^>q0eOv=fmK%F|`qB%>GQ>1qqaTzR?>xy8q$ z@(7s^qGlQQbip$t_yqTK!80QwHn^wDG9NfH#XVi{%m@UHd%Das!{s~obeU&{(>2e` zfoDe0weobS3`JgD9yMtM!mB)8s-_V4bRlCxR2k=@n`?f=5S0+;C5qodC`~5Zu#+3=N^ba8DOJ zIwJCdd%7$`gY!@2=~9ta%G1>m87Rur)fKhzxTgy>cZA)+JzekwiOgH>>4GOn=r7#U zWe1;ezNbyJU>5`d8)SkA=6JnlJe?dlw%CHg8T|-22a!(fkIF-2~fV+;6f85gr z+)hLWaZeXcfLHCP{(?*r5lP13UZ8!-lSk2BJ7tRVGY<9|NNDz0!8Z^*NF3`mjL`TJ zX-DY=?R*KD{E2dLJgV;+v}kl)J{~0(zl@vz1QcqX?5BIa zm*C?K>4valGK8l~3EC!d6X}R5ro_%{dLv=7WBf?2yrB+Z~`mmYDdJwDCepQcjeAisN;gp zxO3Iicn&Ue=PI0YBFKb0SFOAl=hxi1Y6mfJ`sB`4v=i@vJ6GY<6RHz;uEIGd_MdXD zwnP~;uvG&JR0ZHu5JWQ>4?p%daPwZQsMu>KzOeS}( z!Wp=ha}~}&kq*I~t5yb$^LbwO&<>gqPD^So!5JvhqLp*CEx<|TTKH%PdkO z8_qzHPR5<9a0Ut|H+QbW87R_!xpNiiT>`A<&Q(kA;_sb1SK&kyL08JTs>w1qYLeAO)UAi1am427#v$0!bAI z%W%vWxdtLAi9^0P(?MZV6t_cJB@u+70N<{74?K+!AgZ%-3T~e0M5Ou)+PQTh?S#ur zIxQ)^U_2)u$as{$0J3%RfwZIi1?}AaBkhE9wdVFwc{_pg8qm*tvXoi?54*gS^3eoX zS9f4mc{73VDtD(K73J;}l;Xqs$|orZW!GW5<;?`0G@MVMyqO@AQZMOp=W4So*YQf7 ztGk!rT;07g=V~42!a2jq4%{)1LpZvdI#-nnXsQ5pk#QXmx)p4F-jHrQC|3v=;0@{4 zgO-F4Ufz&yzBXVwUQMvZ^*{toLBHZ7(hZ3Gd1UGEhI9);0})4*qi8>KSaooQuy{tU z3tB3?aISJ?5RptUj_5OTZ4g=ktasj!QVztXa#(SF(BwN$o`Gy+tBQsKNWob^ydnH& z)boTAxT2wOmDIRS2pKPIj~Z7BP3&@zx5l+XG*qj~WA4yUltmVS0AO{ZTux0XhVmpv z9(HHUm3yvaAV5RK2dcokhVpf*$h)R|kCf-sz*jj6AOiu^sR=R=K$QWLfdDPx_^MTf zlGqk$X)+L?Eh?+YENgYNhYSShNbkDfIjV@h3wDnNqjHK?O09CbVp zz#Pe(&Uhezb|mva063BbkjDc7;7AtpTvoKNsT063Bb(98n?&`t!Z zs6c?GIH6qy0<>`cC<~-gfdHy*rwRmUi)s@p5I_c2Q0vLEO1TQe0|DTG7Jw=b1h9Ia zTpsa20CPZdLxu+em;;(SmUtil9MA%w=79ikKnwoDv#c!}l{E)C%(0C0Er@^g;q8=&ou6X(wPibHFf9vch|-?mdw4 zDE?v&c9WZ=9kr*IIM@Yb=L0#&cvOEeu;26^(oR6VH76fPJ1UQW*gHMByqzGIx?@Y_ z%>=dhQ!vV#31aclbLGv1S4p|%1*s_4yr7hZ2;mTf(hw0Uf=+zh{_-UeWK!zUKSLlu z8FgK0AVA|10s$IV76{X<6a=DPS#G01=TvTa9<2|FkgbhBG}wptvz-4VeE1%!`CH@s%e zD#9U25wwDaBAKSfjTTcXILL%@Onh-v+N20tu?PYcC7~1xflK17OFmU~P-Kl;E(ipW zaT$Vgc4Y_&q;~ri$0)_6tG}gx^F4t*Fcs8MM+;?@JpZt5+yLq^w@i6=fu4^@^^jwDXa((r^P2~)Shl`6`he04 zf+a+ts0vzX2`8@#T9LsL6qiNmZehu)pp}lOI-`PClxoO>R;=VV4_@FwD+rblS>ils z1;G+R-twRo1WSmZMjo_+US4hSR2LYOTa55^(2 z!ldJHaI4Z5!8{xdMv#T5s>6d;0C)@ilLxJ!q`9!JdC&@Qa-nB&B)Bc*5f56iY6K#9 zh{_`bX9#CIhk@--TrU4q&`L+>gB%1#aE8DfI0lU13?Uyl1dL=r0pxK6*s2)tpj#fa zf`l^6xC^Q-c!;#4{KZ@;A~-`h z3##rYtn%>`;tLp&t_!bTVGbD4PIwBuokQj+90mT!8PbmGFPLYUyFOOSpsq;^1~I&c zj3Qc7SN?2*P@1BkhoFIo+w6 z0P`}edi$<}nZjQ?we1A#e>-_So%Lq@+hO?GJM)5+_P`$zpZo1eCm z9W&9ZO`o!{o!pwebN`^zTYh52j*G2Zs2k{Q+0|RakBcgf9!XribsZ(CYF*$NX7 zwTqYbM-m_;Xw~PgSDNi^2e#FY9ow*KYrL-x%8%O2*JzCl#cIp)(s!!6tB-B@unp5n z*J+K3R}t=SB`O-L4-Kt-x4jEM;f$>({n_bc7&Zv#`rm%j+}%2_pPL)6dV}N5m)l9b zbqRc>^&8_WHJsz+tmV~71pwJDPWf~`jn!L2b3hWWw@6oSjUCddcTl2YExrPXXBRO) zM&xVlZJe!W2fMG;8X6TV)fSGD>%~7Be<)#Mg)oN z&NcOwCW2QHXJ`9s4ZczXkchlhllp74w%oavRZv-HZ|^|Mt98xazh>)Z2eqBn<12_y z*oB5^OTLD_(uAS~T-p}xOsy}IuXHYfuXKK6d<6%w`Dm8a_qI6#yER`Se)ZPiEA>v- zdk9ub3|Fu?Eg6 zgP&rp%?;N9sx886yCz@hB3=E=8pi@(*==@OXP)>a>MOg|i{~r5)!!IjLClMfhFzUA zfW}d_%16Sl-Wt2J-3gIa>$J9_%x-_Jf!mtsZPi@@;97j83PT8mRo2pWPzwvECZaz5 zI(r*-P#2s5#aY(kD|P@w*S!1dv^I^SY4v2*X}p8S?f|*{8u?1&68K8vH^o;_PpjGp zCA?PO+ZMRlF^C^2R;#zR@PJm!mt9$_b-QY(aIU4V)R5%eXgKG@8hm9Jfj*rUaHTal z1Av{b)0I}&SL%rIt3nd2x3_UV8PIA}SXqZNSZzmC;aXE&L2?YDc@UR+#-T4!U1?nc zTWS5q*a~<`%U9xEeQP8BybZsj3sGCWHN2Kk7<_fX)myug3S9usYpD!y2UXz(Ga_Go zYeQuKssvqYz1GO`K$;u^(ayNRa@GXAU?^zhkexGu6uHJb=0k(oyB=EsD{eKL&V{zd z&^5%?BirlDPko7O1zFn{Zw6tI-;y2FsI;-UeRjj@n;Y5=MDMz-QPXU9*XlH^rEws+ z30Y><^)L=ld3(3(Hdu4@$3}ieyKVJ8*5M30h`I-A>mZKR$3|!~Oekb|>{M58ykmji z2)r>FC@|Ak^68H)R`Hk_FD#{_atfJL77!XM4 zw!X0R>aDFdBLay{dai+x;3(}{y@j(`)=^mCD2CF2aNV`^72phT6FV38nvxrm-vE-< z%8O>oPOXhIP1oTJI~GxiltWwbGi~miwqvFEkW^g9iv|3-}Mj@8~@%d{N2d^{i)F3r$T?93jKX5^!KUQU%vb0@xPX3Fx=ZK zja2VXPmgDa<#o%}+?*Dpa}gqGF~y;a(ir(%7p-Molt$<0x@a%!qBJic*F|Sp7o`~q zxh}fPx~P>VL*)9XRhM>B8e*UCq_(t^((wL#C-tSBl%^KsJK0^@NoiU`zLUn%PL>mD zZ7HRemwL+2WGStd=N;ucSxT+tNl*DsmeOna$#D5jmQrkajpAG(mZ$jTJ6TGx<@tsAPL@(^d2(aElcf|}o>`giWGTg#r*-B# zSxT|xIi>kdmQrkaLTj!QIB6xtJ}B+OQeuGO-5my5YBKIqHC)_{0^6=0+EphV%SLkT zEEmQ)YvkJ5Ro4#go`o5@cJkY1qy4 zX}Fs)jr`4N&$b5hp6fOZ^jyzPBkkF4p7-o-M$hDCTd?`fyVf&q-p%%$W`!L_buy@S zMEwuhF~9rI{h3bo?>gII3iDxFIl71^MO;D)RVsdm-|#oM-!KdP?<#_!td=G4cniE} zSUEggLs0hblIlfFrr75xo_>O<>LgGkV1AenA)9hC%uyd9}g$}eli^$&OY`g{d>vh(P^^Z zvn;lQ!E`nmyg8lqlff+M4G->)CdtX@U~u^P<_4~u4iEa1WOmd~X8p;@G#MSbKmPpe zugRbL!~UdqoIFR$DQxE-2mAfuw4d}auw9uR^$(Ia=*1fUVB^0G<0n6i&@Bw^(JOnFXy2m&gjqzYd=ns$jad3Q`yy+*W)BfS<@%GIPtV;6tC$IkU(_ddD z51;*<{QcpJ7Z0Dk`uQGiHFBRN{SSTL(col!Jizl}c_+Q$>@((lbK~jb7mxme1`q%E zE+`}f){U7>fhWD zYkcTl(xjgp^k+yGoVw?InCyds&hUmn54{g)*0WUM-t6Ya(QG#U!`-`?S8*3_6mN4d zd>cOXr=HjzKl@Yi(+_!KJ5M0y_k%-F+@Vh#25o|?z8=4S`}*kS#_w@`FzlCpH5l$6 zpC0s+?|S3P(Ra9ae&P7-@!$=RoBm|jJ1%^AJixWLg^Sa1e}B+BK0F=n8+n1Nqr;mU zchBp`y0ZV0SATi(GI{y<(W@svJxd<_1VVZ8>?J-teg4;15AEW^AMw|V$H|`_KLU-o zOD~hnKRv{^KRcxT=Eksh(w~ld`ydOqY_E;39kvxj`8kc`r>z#@&Yk4R zvsaH_{P6J6;|qVx@PruW+(`N6#-rZx{wcVU@v*=)Qx0U;PDUsF;cWVczW;I9pF)Rze>$0srUSpdpF9Iu7|VG0^f4CZV0zIvy#(TRbpy<>C=eg!dtU3>tMG&*sUaoySu1E0-5 zqVN4F*2hM$C$Y5xv$H?{78cAd$Hfa@y$mbp)+BsRHt~G%zWWcV_mY40C!+*wQm2Pu;$h_uK!uu+5=#YJ4q1JJ3GF=WG>dL@NnYr1*mh7iy6u8Mg92#+8;)P zgYd=!p>6Jim(3vZd&jS92d_;~eNJwn+Je6hNIQkM11NpVQ)c`tF5Y&8$#2amHQp0f z%&dkLrVyKd*?^8qU+fMJ^mlXPKYuqq`{~tV@QI*HMrjE8$<#kcl8;AF`JqdCUgtEv z(PI?)xORy}1GXGMUasP&=V_B;P;9ZvNhHN~j`EiTSEFQ6NjtIPKM+c@G)= zpS88!dkLf)x362~Yw~qIt1lzpzXb{O&5&>;f%VCFNe`Dq%2DoIlX6;#<_kG-)R?{* zQjR3BJ}EEh;gU!>Dood;d|sp+75Hz4lp_hOPs&SrxCl~y_^18{Q(B%3$H2kHJ>ZOH z+D$W7sk6Qc7O7oZW{sYQCF=TE(+WMEl|GU^u}wcd`PXUe^$z+U&P+&iOU{}ZnPhA> z*;6#wV!QL!U!MY)hSeYK3g9K4L7s)$fDb#{b^svpQy&1!KdP0=vz^*6z{~Ld95V6^ z)1hA)Tkx-w0#DqlTZ0CD!vYm9=`GUXyzO~Y;`9ClNce03sQkA}jkax`<3HOrv#mhC9zP3|IG*#=B11iM=cKFi zrtJDoK!o>4(X6u}gha=i%;3yPZ5gNi*&5)-$R>z^QA=*iG5 z!T*A8;Ucw0@XjKB2r)huoWKD;_Fchp zhQEG>;aqG~W-24Z*kt0Q4q%mIY~RW~3#11k$)?`>pP1f*aH@%$x2*(hLkozwXTb(G zKYW?|`T37Y`~p!|{#n3lPEKHHAP^^hXK@0-xgO)Mtjp=?cs!cSwq1YlE?N|qo1^av z0bMT6DJ}vYdnbmMxv&h2jtikaSO$yvLhuW^zyyE;x%drecs8r};0Gf_A>pYox8u** z75IA}?ol^!_&M$I+@M(DQ;U6o@^la%;6?wiKj|afBT1h2hNrz_3_V3uAJ)c?WiJd~ z((f}*`29Ab)3Blzp=N;@2D;&<`FkxsPIhWPV>VL@(LBIBPWmTrTvQ*Nuok~Lvp6sK zWyfRd_v2{t-hJyP+&li*`#en?CHbihN5dVb)$Wg`vr6*t*ka+{9w9g*CE1kJj1-K< zW(bK(QY8xkCcdlV9)cWAGlTp01OYD}5z#hEOaob1N23%3=|Pd3p4j_|jS?4UVJ`w)rV{K;TmEH~(fAA0A1-4UlmBpg8J5SNL~0Ocn`K!S=dD6KnvAB?odxX5hPif4 zT)QT&T@&B3Yohj*_$DmSE9Wz*e#%IQIYTHkZg!jvZ=Qri}$pt9QeK=|2cR>j$iRoL2 z@;gBR;G&ZV=aUqATJ#cq1yeDX((2Q=fF~>K^1OAw=fnvWsJdXtUWcn(hpSw>qAoXF zrLYCh1xvbC3e)hLOO2$tRtox6(^))I}>r##J3!k;D@AFm}FQ9g*C7hy0c#q^Q zl)%*E5ek?hiKs|$hy5N(R`#d4q=gIYKJB`&@}EAJ&!F_SZ|DtKc@!kG9bE96y1-%L z8%u{MeI@CJJlpN({RV$}GaVhH##Sa<5$SUCJUL{WS&m%5{2#g0bbm+yJan=w@#Uuk zXYA`xgCR>iCDn=D3z=Up+5H!h(0j%uj|Ovo&C6CjQw#d?7d^@mBVU)M=&N6x)5>2! z@)XJrWaf#_^8`{H&Nv*PWQSEu$H^2^SGU6@Alo0w{!xGb{WLlHJhuAYQ)Gem-q?8! zACLO(dbUR8{0tCbN&Dn;Q@!$cjB3o#JA)Y`f2dwKQ2@KRh=> ztM6io)P4~3bkrYAtgpH5hhWv1KkJfEU*T|!!_o2a=%e*%lgC`G!g_xKZxqT}1=snO zO>4&vdq7sOd&bQnvWnA3M87*$oE=Z~v(NetDZ1&{%Pr*#3_h^rW~-_n zN4E!=C!nSkfe=Q~@1byYs7mDC$_y(LyKlz!L%f6@a_`-_8hJtht%L>l>BQx=>0V$F zEHozYWkJ*CeGDD?(PQvR`TYIVq3@ucV$;olRp9y~Ho+@9CL~M+3Glt+Q=>b)xI9&% zAKbL?^W+fa>SHHZtocIO`nppCP|82EC69433J#7~=m;158_AO{x}5awRGYr9D3ByS zf=F%7)`KlycFsit=95l)IrE%d0RkH||8urUzp&GL!mNvvUf2eF*7I)8JneOt+S+|J z%7Ig`7FB~RVFh6oDojllmHm17wKpJG>^y~;lMb)Ish#40)io6))a=EPZBA=A81<*a@2&8LlcEKsCu$au>~G-Y z^-|Gw`$UVqv+jXw;dd?kP|N5r`ArKyVxnE51UyeO?tgldf1dVt{)kfoSFq7c)jI6$ z&rHw9iG)c|FQcjs71_DndN((M@(w-R^CK%#cL20LmYH$o+P>~w_nVK2(d@Cku7U+Bq6{p-Al60-x+e)QB zU=-K7Jq%lAM%~FuXyLiWa}n)PXb%yBz_&axMh&F1~RXDH#^ZPsqXXui1- z4XxycEw(R_iW+kL$pqG7_U@Y-|3IKmwNm>feJl8IKdJpUzQJYtuA2Gsep2_}RQ*@; z@9rn9@QwQ_)8EIQ(@Ajj(i8|jN>tt4_yN;+_Q2+w^y1?cZgWN4P%F&WJ2RyQMc}4K zy~!9=*r3=Py4q`ijNB1)GgltYS;YRJy|2w8dgs_E3bt8AzS$DEuIw1Mu{UncleMF0 z-EimcM}yfAXVm_6=Yoxv1>7ZYNcUE8f~yDfz90)YoEHjblgkd}FtHR4D}onE363}KA&P_Ka4-R`8Kg_J_MypB0B*4l)E7iq zy3j>Bp9NgvY!hPhnIl7Z0q)67)-9WimegLP`uAp{blk^3R;t+;&sN0D9Bi6_ZSzF4 ziRs|VKFSQC#$Y`37;JEgqJziB@vz1u!yzzGgB;V(uVQ7BG?|5N*6zEEz}^_qc+YIDxW)=C+O=y9;W9r?YfA_I){8cs8`t z+D}n>(y1YV>l&UKOGko_qRglrn!6392EZ>SV>=D3X)rsat zh^MJa_!J#DZL&lqdYQuXsXW!H zDSWkGuv4+?;q`J`KFfnK3Ha@%1C$-Mr^~cSd$QS~EdV5(os3#${=oUH$&4w(8(|&c zjCSL!7$$PBZP%~kQ~qSrsj01)R}e|&BZc~l61MvTcO*R+Z+?+*A*`d*pUs#HKembE z$KIz3RA>l*D+4Hc4<^Fg`XG@AR$em3q9hS=q(M z*3AtZ8d0e=U8mNk{{;SK9l1Vns*3NkaA(!`nYq(<4^~ri6LsIadHOqBl^QzQwV~>j zwjVvH=i5&o)X-o0=7T%-qm?{{Hg3mjVf`U6#;{IM<=S4HE4)1Gy?3rv`xd9g?n87y z`0PV<$9*Wc?9PKXov1hcd@`Pm(6;b4j9#}huglv2hFIpTfGBHc-XJ&H@)m$|ibdi` z{?P<^R;xy!Cf2?#PYEiN3~~Ifso z5kl@TSm?k|*a5ztFW!d1Qju#sIwS3)Xw1vOvkF~sRFklmBy`|p=PY`+al(V-=izk# z(5Y?gocy0_T60YxGLx;KNBHFk>URjI$;$JvN?RGxpLrjC3Ae(=U+~nqsh!zQUc(y` zMRkmz@lS(c?F)0n*w0*HSi>2Ub`WgwbcE~B9Jjdhf36K|)47)!*ypAJm{5MYnj&CW zZn{-X^LJKuPy@U>7HYyd>I25K&SbvVu2R0|(zfThhH0V9Y-r8p0O%Jc_D_e`f`2Y; zuLVDh4Trla!5=jQ%TCNh;Xuefwxqw1A{UJecvzo(E$Pb^v4t`kK zEii|T-GRQgcRY$6wNbp|SGYJgSdBWoIk{Fsyp50@)MVGH0D{?#)=#FRP$$4TT{QZ>y4)fO@ zRf?s$hc$^eFX|scugF>btLhfH#-?V3w{bp^ot`6xZm%^9FE`SEuQdy!`DG}=GI59^ zGneWOKQw;*)yco}HKQ|*@%^L0&J@NEq>4TGstEcbkInkYFH{tIDF~mhlWcC-D0zJT zz{&`F=3yWJQwfLxdc)W?psF00?H{)=;2Gl6Oe7b^bCS#rwj>xXj0RSY1vcGtHMG1a zyqrqgiwUTh)NC?9D64tKrNNsrx-2Zcx`2w^IBc64!hh>c2Ch&N?zS56JF({f zfpD9Lt6?qYRBUe|D5cls-R7lfydISe>08iR1EptH*?q)P&%3$srN$PygnU2L5JX7W zKI%#wC*$Lu0lC=3^CYTVqTkJp?|>l|?*9&XMrdV8T2rKa9oPvApOe7}0?d&k{QDwbimQQ-`5ew@^c9E-rgP1$ZhAt$M zU1i$g60^Q~*as~M(ctYzm1Jqm9#e7d!IR@uMpt=&DZAM48gOJ4ou$;;&B z<43Qa{PZk&^wYCf51%}Hi4RW^+xyTiKKv1Xy?C7b>G7iE}e zsaG(1tWsy`8kae|qcA(bcX7c{(rPmJB>7#sz$bCZ7hX#2Wk23g_6poAUXS~q`unfX zHkowfVdyu#5T_z$V%&Uzm%2G*h~Vp4k<})ZF2wn+OqM{uo~2oFRu{B3{s3%BaH+yT z@6a3zTL0@=f&;>X=8!(cNj+YoJ+HLwN*GVvzVR98K=BdM$t{v!e1PzW4r^sJ0?Q($`g!T%f zrz?;je116{n{WL%Tza_Bl|J+evzR|+Zsq5f^}{c!s`03wg#}o!R_PrjWT9Srm_ytu z_9`-mu`H^Q7dDZi!WNbyn+m%bCOgSIP=UkyJGPQJY*Mh#EvMZba8S7J(8=rR7x>X8OSh_xWI8aQ^Sw0%0j87b%vvD7Ku9A1pz3#&^Mm?NbMavT#rt4 z=~{;BGsOkINP5jB=zh`6k)&JLHkUN-5=~@*5zV=7oFskLt0kw>h-f-jK~c2L)jppO zaif#__8utR{}DO;QXUZdiG&*2>;>*7;-FaPEd?puznt0M+<-&l_o!!Vbt`Xf{Ot4hi52!`_=w=y=lMNS92*7 zv;B|YrMn0H!^+Wkd?90EYpDHSYgTLN{s)J^{%_W6jqCk?AuprQ>D{dj#L0!XkB|0O z^2i;EYUS7dkUZ^uPU?;Aq+YGU`2b9iw2FI>I5vGM!CAr3V-M0~*y zumAG=`Rk3}6_!h{vS4OpaEibhWBDj$ieI$qJQ>r=k_s*P|WUa-0+Py?atv| z?e?#;3Tj-T&D+`0=J57C+`4d~pSzOW!`*wsdk3>Ul*PLD@3cz{bSs&?-gdu__tOvk z*V{o4Jea-4fP1so_m21Xa0h<%@w<-?=yFyG)3|MY<6i6AVLb;phZZ3Yw|@=&oa|Na z*#ivjp#a?G!2Y_Aw)X~i?%e+MZODlG6*6O#hv^Q?Jk!)gqO7=j0 zQm)ByWrlfKHmP5ywo`o>ced(EvNs@v9rJA8k6eYfQly*p1f*;fg!yCCOg@oG!o%P4Bk)AZ&w5U>l|E>5do!NMTFT-t)p}T%> z+0=~#A2e!Z^sP-dh~c@bxcit-9L6;{J17T2Cz>aaQB}4cWWFyGWPb102Ts zBQgI6uzts0BL6?>4Tcvb0G8_ije1f42NZD4|1XdK$G2Z50L%c`T}c8wMeK221kA6P zp%ATr!$qZk-_lQqZ?ks5bnz}QN2rRh4AQ>73l_|GnM>nokTLVSf}AVdRdV%Y|71T1 zC?BqTI_$Mf8! z@b4Q`GPRyIVxY0j;Wg%F?9qvXPsz3ovo)SglH0#J`Irp0hvQ@~`83z#?EsjdpAW}( z>s}Wg9BzJsiu<5_`&awD*S`1faOwU|ytadjP<@|{4(_{AA0%5QiT9Gt`K7Jf2}VzC zo#WBl<2~H8^T0k0-uI6ON2Aff<|jNy(4Z6h1_CVqSa>&A^JH=0{KKVJzvTJmLLw#i zuU|@}{67%jzjAMF2SL(LX1{10M#15BKJ_iWwQh$?0%=_~vwie?Pw89=bEk;t)yC zW$>a8n}kQE#t|~w+&avS>4r|Wa0;(CXAFm#_WK8}*|fH8qfP4&Nq#=|4X#e>+nAKW zCmvA3bb=fUX3e&pPKw-DI~{H7(BQqj?;If_vhF)KRy3yE9C5|0pbVFdWcBrP+2XGI zdj3xNzc<3n6y9^sRCi?OCp*GPMkgqBl$VC+@$4Xs#;4Px z*Kd0Ju-2a%{Oa95+ldq)xH+L~Yw!=8mKvsb0}pf~d)Yl7?k}7B@jgJ3zAL@54~Y7>Zn#T*PQxY^U)%XV-0d}=DrJ`T z8gKs!XhQ7tv42;=3wjJEqXSqRj$cl7$H|>NM|yY-l|1i&3hl_vI~Nn+A~$Sw$HNvl z$rx|X4|~gjD1cWyG2KEXncji_490SVeD;)}00_O?9-iC-AtrB~Bf6d(rCR^-y|-Dv zGHB!Wy`#bvCj{Nj3UdbnlshFqn z(v1b#=)Mm`UtwFdq9i8h7kGW2> zclw*`dvug;!h(D(dHl#;h9|vGps7Z+YVSQr>lt9wy#z;lL$AWss7hfnLX(R8$~Moo zg`40&k|8v#wAVmn+XrKexqWcFjX;DC$9p@qe%%d@cYou?>||`S#ZA4*w(Sac*EnuN zt2<$vb+A2f@+{n5*neOV)_(D-b+~xd<}qHljEUPwnd^ydNW2sG>-N3DAoHDF3)|k! zuly_YY0&aq)>?;A*5b?f_$Gb!oZN9c%pS&HLK&Uf7XF&L>w^I{*5LMD&BcoNJvg}M zIEMS`-E9l#7;L_?n_SEbT)_6Q`Tj9*hN0}$jGx@!15W{I;G^d!HoF`j2}_Ulh)fPV z#WPjUP8}o3D_79A&<$Yow9S9*r5~|v_~N)1ifP~7t5xrXp~F+e2f7pQfq*bv#A=iE zZF||ov7B+5J9aeu2hZoqrWX_t?7RobV1Ik?;T~%1A$rPqxOEmMiT9Q`9-Y3y#GdT1 z_hz~o`1$SRzHOtXu>`jzYWD{F8Fgx4!mNKhMKn}0_>lj?90`7{{OH(_?i5bk=QX$g z0_S&ng6UqEUHXNarL(qD@wI(qJ7#b%$qO$GpO;-AVY`;6Ye6uw}q{ zq^se+yn%L(NHM`0dXArsL^cN>@CvcO_}58GbY>z2xBB(7FLDAV5O)wLxG&$$J9Km8 zsXd4sKYG{)`Hf(b2tU*DfzKJ=Kf*D1)Aye@Ar)7T^{H&7v}aEsy0kO~HP7jXis(lhN;fK|mtH zGE9B&+W{NGc(AGEJp^+2$=Kt9ax=|MIdrr2D;4S9y=Ux>O^~vuM377{7kq1rW3#sy zSWcjD>ngk7oNdHj0m#Fxp<}pbd=DjU>}AaR$-fQUzRC+JaB5q_lq&~?cK$lt&AAuy zi&L0P&GYZWMqlZb{E~ki?w!~xfd-8@LZ>UbK*9`zpYuuo-W}h=dkLJ-z1+cJ0tz?= zqpCahGJF6i@%FD}J%LT&EDDA|>-(spW}$+^OuV3{xJ*Q$*FVD@#l5t zWBWJ-`hco&m?mEx)i?+)-Z)Ni*&~L>_RElb@0O^j4SOZnbD3z*c}lQM~{&7)yw&}iuy}kFhf1N9+3z{Dm4#Xc=goV39;(mP!d435Ol<>RI z)4xCucWP4M9h%~if*UsmD=C(jr&l_`yr3*DsELjtE^41&pj0mElydztik?%|yzwjSCmT5YprLg)>A5B9c~IyI+{#h_dXyz8*KfSN4)cRndjzC z>ejYR_SWy&zd+mPc{pICx82qH!c{c)qin{`U2DklnwS42-x~106TWsj3w%mNZP$A7 zEPK;+h|hQIg8}%r5xNIbGesivnB)-`FuX6E@!3Yj6N2JR1hno)i z$2e#P`_ze5r|H~)ML)*>5j^5z`XhLEV|=izn4z_LZYxf)3IsRP+ zRk!NiX4Sq2%x1E%y72aFMHjDHi-lX!cN`M-<@EdOwLl6kmzVb|Oyl40+1jhKjp{MpC6&+E9HEY2sx?BFo9^Pj-4aQyp4qv#e zr+9a2V~h{*fPR`|Zy?P1tvAS0fn_T-e=K`Kb7Ek!HwG}K%>0Vr(#IpKVCoIB@X`au zGd{)b^8u4Pj%v0BTVaL592|uBkHHvb3Mj!|wL=x{)mw9WA)C&yi7x{b2nUV7@!;5v zQQz8(?+_;(1i}foKmxma9S;w)^}xaLj-T-D2hQaB)eY^)&<5TdpWYcCU`rzTE=~$- z;^%3!nqED(smAyG-5tDo_YTvinwy^C>4L#yzv3$mm+XqoFLQ?{o0Je7pm}h7KYd@x z*kXQNt9uUo_!W6M^DFlFZu!zTXA5MdceY@Q`@sNG`n@pz#lIYDFsCnvv}F*9FzJXI zdkPCX1_OtIZ1Wfb4E_-kHQXS95@7b*JGgr{ziF$HQ0!Zu$d?Hq3vnsG<@m7}{}qDe zi{U}%^0$uvLQ#lXyBPmlZ(iqrpUc`W>%_-@g|~k`@n6qh7UI9s3z_&YL|Wt$05OAw zL^Y@P;mP%I39F zPMbZzoQ=ihU5kxn_J2r&Sw=Cdf4^k^*P88x{I7PSdL942`bw|fBHjPt?U%8D6;gmh zYS(x3?H%hvfJ$TTWLg>`do4tjVb5VEDrt3q?QC{VOej*I7Z?5%kw(< z;?W9;!)bj8|3n$K>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "alglibinternal.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + + + + +static void tsort_tagsortfastirec(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Integer */ ae_vector* bufb, + ae_int_t i1, + ae_int_t i2, + ae_state *_state); +static void tsort_tagsortfastrrec(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Real */ ae_vector* bufb, + ae_int_t i1, + ae_int_t i2, + ae_state *_state); +static void tsort_tagsortfastrec(/* Real */ ae_vector* a, + /* Real */ ae_vector* bufa, + ae_int_t i1, + ae_int_t i2, + ae_state *_state); + + + + + + + + + + + + + + + + + + + + +static void hsschur_internalauxschur(ae_bool wantt, + ae_bool wantz, + ae_int_t n, + ae_int_t ilo, + ae_int_t ihi, + /* Real */ ae_matrix* h, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + ae_int_t iloz, + ae_int_t ihiz, + /* Real */ ae_matrix* z, + /* Real */ ae_vector* work, + /* Real */ ae_vector* workv3, + /* Real */ ae_vector* workc1, + /* Real */ ae_vector* works1, + ae_int_t* info, + ae_state *_state); +static void hsschur_aux2x2schur(double* a, + double* b, + double* c, + double* d, + double* rt1r, + double* rt1i, + double* rt2r, + double* rt2i, + double* cs, + double* sn, + ae_state *_state); +static double hsschur_extschursign(double a, double b, ae_state *_state); +static ae_int_t hsschur_extschursigntoone(double b, ae_state *_state); + + + + +static ae_bool safesolve_cbasicsolveandupdate(ae_complex alpha, + ae_complex beta, + double lnmax, + double bnorm, + double maxgrowth, + double* xnorm, + ae_complex* x, + ae_state *_state); + + +static ae_bool hpccores_hpcpreparechunkedgradientx(/* Real */ ae_vector* weights, + ae_int_t wcount, + /* Real */ ae_vector* hpcbuf, + ae_state *_state); +static ae_bool hpccores_hpcfinalizechunkedgradientx(/* Real */ ae_vector* buf, + ae_int_t wcount, + /* Real */ ae_vector* grad, + ae_state *_state); + + +static void xblas_xsum(/* Real */ ae_vector* w, + double mx, + ae_int_t n, + double* r, + double* rerr, + ae_state *_state); +static double xblas_xfastpow(double r, ae_int_t n, ae_state *_state); + + +static double linmin_ftol = 0.001; +static double linmin_xtol = 100*ae_machineepsilon; +static ae_int_t linmin_maxfev = 20; +static double linmin_stpmin = 1.0E-50; +static double linmin_defstpmax = 1.0E+50; +static double linmin_armijofactor = 1.3; +static void linmin_mcstep(double* stx, + double* fx, + double* dx, + double* sty, + double* fy, + double* dy, + double* stp, + double fp, + double dp, + ae_bool* brackt, + double stmin, + double stmax, + ae_int_t* info, + ae_state *_state); + + +static ae_bool ntheory_isprime(ae_int_t n, ae_state *_state); +static ae_int_t ntheory_modmul(ae_int_t a, + ae_int_t b, + ae_int_t n, + ae_state *_state); +static ae_int_t ntheory_modexp(ae_int_t a, + ae_int_t b, + ae_int_t n, + ae_state *_state); + + +static ae_int_t ftbase_coltype = 0; +static ae_int_t ftbase_coloperandscnt = 1; +static ae_int_t ftbase_coloperandsize = 2; +static ae_int_t ftbase_colmicrovectorsize = 3; +static ae_int_t ftbase_colparam0 = 4; +static ae_int_t ftbase_colparam1 = 5; +static ae_int_t ftbase_colparam2 = 6; +static ae_int_t ftbase_colparam3 = 7; +static ae_int_t ftbase_colscnt = 8; +static ae_int_t ftbase_opend = 0; +static ae_int_t ftbase_opcomplexreffft = 1; +static ae_int_t ftbase_opbluesteinsfft = 2; +static ae_int_t ftbase_opcomplexcodeletfft = 3; +static ae_int_t ftbase_opcomplexcodelettwfft = 4; +static ae_int_t ftbase_opradersfft = 5; +static ae_int_t ftbase_opcomplextranspose = -1; +static ae_int_t ftbase_opcomplexfftfactors = -2; +static ae_int_t ftbase_opstart = -3; +static ae_int_t ftbase_opjmp = -4; +static ae_int_t ftbase_opparallelcall = -5; +static ae_int_t ftbase_maxradix = 6; +static ae_int_t ftbase_updatetw = 16; +static ae_int_t ftbase_recursivethreshold = 1024; +static ae_int_t ftbase_raderthreshold = 19; +static ae_int_t ftbase_ftbasecodeletrecommended = 5; +static double ftbase_ftbaseinefficiencyfactor = 1.3; +static ae_int_t ftbase_ftbasemaxsmoothfactor = 5; +static void ftbase_ftdeterminespacerequirements(ae_int_t n, + ae_int_t* precrsize, + ae_int_t* precisize, + ae_state *_state); +static void ftbase_ftcomplexfftplanrec(ae_int_t n, + ae_int_t k, + ae_bool childplan, + ae_bool topmostplan, + ae_int_t* rowptr, + ae_int_t* bluesteinsize, + ae_int_t* precrptr, + ae_int_t* preciptr, + fasttransformplan* plan, + ae_state *_state); +static void ftbase_ftpushentry(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_state *_state); +static void ftbase_ftpushentry2(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_int_t eparam1, + ae_state *_state); +static void ftbase_ftpushentry4(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_int_t eparam1, + ae_int_t eparam2, + ae_int_t eparam3, + ae_state *_state); +static void ftbase_ftapplysubplan(fasttransformplan* plan, + ae_int_t subplan, + /* Real */ ae_vector* a, + ae_int_t abase, + ae_int_t aoffset, + /* Real */ ae_vector* buf, + ae_int_t repcnt, + ae_state *_state); +static void ftbase_ftapplycomplexreffft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + /* Real */ ae_vector* buf, + ae_state *_state); +static void ftbase_ftapplycomplexcodeletfft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + ae_state *_state); +static void ftbase_ftapplycomplexcodelettwfft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + ae_state *_state); +static void ftbase_ftprecomputebluesteinsfft(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* precr, + ae_int_t offs, + ae_state *_state); +static void ftbase_ftbluesteinsfft(fasttransformplan* plan, + /* Real */ ae_vector* a, + ae_int_t abase, + ae_int_t aoffset, + ae_int_t operandscnt, + ae_int_t n, + ae_int_t m, + ae_int_t precoffs, + ae_int_t subplan, + /* Real */ ae_vector* bufa, + /* Real */ ae_vector* bufb, + /* Real */ ae_vector* bufc, + /* Real */ ae_vector* bufd, + ae_state *_state); +static void ftbase_ftprecomputeradersfft(ae_int_t n, + ae_int_t rq, + ae_int_t riq, + /* Real */ ae_vector* precr, + ae_int_t offs, + ae_state *_state); +static void ftbase_ftradersfft(fasttransformplan* plan, + /* Real */ ae_vector* a, + ae_int_t abase, + ae_int_t aoffset, + ae_int_t operandscnt, + ae_int_t n, + ae_int_t subplan, + ae_int_t rq, + ae_int_t riq, + ae_int_t precoffs, + /* Real */ ae_vector* buf, + ae_state *_state); +static void ftbase_ftfactorize(ae_int_t n, + ae_bool isroot, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state); +static ae_int_t ftbase_ftoptimisticestimate(ae_int_t n, ae_state *_state); +static void ftbase_ffttwcalc(/* Real */ ae_vector* a, + ae_int_t aoffset, + ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static void ftbase_internalcomplexlintranspose(/* Real */ ae_vector* a, + ae_int_t m, + ae_int_t n, + ae_int_t astart, + /* Real */ ae_vector* buf, + ae_state *_state); +static void ftbase_ffticltrec(/* Real */ ae_vector* a, + ae_int_t astart, + ae_int_t astride, + /* Real */ ae_vector* b, + ae_int_t bstart, + ae_int_t bstride, + ae_int_t m, + ae_int_t n, + ae_state *_state); +static void ftbase_fftirltrec(/* Real */ ae_vector* a, + ae_int_t astart, + ae_int_t astride, + /* Real */ ae_vector* b, + ae_int_t bstart, + ae_int_t bstride, + ae_int_t m, + ae_int_t n, + ae_state *_state); +static void ftbase_ftbasefindsmoothrec(ae_int_t n, + ae_int_t seed, + ae_int_t leastfactor, + ae_int_t* best, + ae_state *_state); + + + + + + + + + +/************************************************************************* +This function is used to set error flags during unit tests. When COND +parameter is True, FLAG variable is set to True. When COND is False, +FLAG is unchanged. + +The purpose of this function is to have single point where failures of +unit tests can be detected. + +This function returns value of COND. +*************************************************************************/ +ae_bool seterrorflag(ae_bool* flag, ae_bool cond, ae_state *_state) +{ + ae_bool result; + + + if( cond ) + { + *flag = ae_true; + } + result = cond; + return result; +} + + +/************************************************************************* +Internally calls SetErrorFlag() with condition: + + Abs(Val-RefVal)>Tol*Max(Abs(RefVal),S) + +This function is used to test relative error in Val against RefVal, with +relative error being replaced by absolute when scale of RefVal is less +than S. + +This function returns value of COND. +*************************************************************************/ +ae_bool seterrorflagdiff(ae_bool* flag, + double val, + double refval, + double tol, + double s, + ae_state *_state) +{ + ae_bool result; + + + result = seterrorflag(flag, ae_fp_greater(ae_fabs(val-refval, _state),tol*ae_maxreal(ae_fabs(refval, _state), s, _state)), _state); + return result; +} + + +/************************************************************************* +The function "touches" integer - it is used to avoid compiler messages +about unused variables (in rare cases when we do NOT want to remove these +variables). + + -- ALGLIB -- + Copyright 17.09.2012 by Bochkanov Sergey +*************************************************************************/ +void touchint(ae_int_t* a, ae_state *_state) +{ + + +} + + +/************************************************************************* +The function "touches" real - it is used to avoid compiler messages +about unused variables (in rare cases when we do NOT want to remove these +variables). + + -- ALGLIB -- + Copyright 17.09.2012 by Bochkanov Sergey +*************************************************************************/ +void touchreal(double* a, ae_state *_state) +{ + + +} + + +/************************************************************************* +The function convert integer value to real value. + + -- ALGLIB -- + Copyright 17.09.2012 by Bochkanov Sergey +*************************************************************************/ +double inttoreal(ae_int_t a, ae_state *_state) +{ + double result; + + + result = a; + return result; +} + + +/************************************************************************* +The function calculates binary logarithm. + +NOTE: it costs twice as much as Ln(x) + + -- ALGLIB -- + Copyright 17.09.2012 by Bochkanov Sergey +*************************************************************************/ +double log2(double x, ae_state *_state) +{ + double result; + + + result = ae_log(x, _state)/ae_log(2, _state); + return result; +} + + +/************************************************************************* +This function compares two numbers for approximate equality, with tolerance +to errors as large as max(|a|,|b|)*tol. + + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool approxequalrel(double a, double b, double tol, ae_state *_state) +{ + ae_bool result; + + + result = ae_fp_less_eq(ae_fabs(a-b, _state),ae_maxreal(ae_fabs(a, _state), ae_fabs(b, _state), _state)*tol); + return result; +} + + +/************************************************************************* +This function generates 1-dimensional general interpolation task with +moderate Lipshitz constant (close to 1.0) + +If N=1 then suborutine generates only one point at the middle of [A,B] + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void taskgenint1d(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + double h; + + ae_vector_clear(x); + ae_vector_clear(y); + + ae_assert(n>=1, "TaskGenInterpolationEqdist1D: N<1!", _state); + ae_vector_set_length(x, n, _state); + ae_vector_set_length(y, n, _state); + if( n>1 ) + { + x->ptr.p_double[0] = a; + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + h = (b-a)/(n-1); + for(i=1; i<=n-1; i++) + { + if( i!=n-1 ) + { + x->ptr.p_double[i] = a+(i+0.2*(2*ae_randomreal(_state)-1))*h; + } + else + { + x->ptr.p_double[i] = b; + } + y->ptr.p_double[i] = y->ptr.p_double[i-1]+(2*ae_randomreal(_state)-1)*(x->ptr.p_double[i]-x->ptr.p_double[i-1]); + } + } + else + { + x->ptr.p_double[0] = 0.5*(a+b); + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + } +} + + +/************************************************************************* +This function generates 1-dimensional equidistant interpolation task with +moderate Lipshitz constant (close to 1.0) + +If N=1 then suborutine generates only one point at the middle of [A,B] + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void taskgenint1dequidist(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + double h; + + ae_vector_clear(x); + ae_vector_clear(y); + + ae_assert(n>=1, "TaskGenInterpolationEqdist1D: N<1!", _state); + ae_vector_set_length(x, n, _state); + ae_vector_set_length(y, n, _state); + if( n>1 ) + { + x->ptr.p_double[0] = a; + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + h = (b-a)/(n-1); + for(i=1; i<=n-1; i++) + { + x->ptr.p_double[i] = a+i*h; + y->ptr.p_double[i] = y->ptr.p_double[i-1]+(2*ae_randomreal(_state)-1)*h; + } + } + else + { + x->ptr.p_double[0] = 0.5*(a+b); + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + } +} + + +/************************************************************************* +This function generates 1-dimensional Chebyshev-1 interpolation task with +moderate Lipshitz constant (close to 1.0) + +If N=1 then suborutine generates only one point at the middle of [A,B] + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void taskgenint1dcheb1(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(x); + ae_vector_clear(y); + + ae_assert(n>=1, "TaskGenInterpolation1DCheb1: N<1!", _state); + ae_vector_set_length(x, n, _state); + ae_vector_set_length(y, n, _state); + if( n>1 ) + { + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = 0.5*(b+a)+0.5*(b-a)*ae_cos(ae_pi*(2*i+1)/(2*n), _state); + if( i==0 ) + { + y->ptr.p_double[i] = 2*ae_randomreal(_state)-1; + } + else + { + y->ptr.p_double[i] = y->ptr.p_double[i-1]+(2*ae_randomreal(_state)-1)*(x->ptr.p_double[i]-x->ptr.p_double[i-1]); + } + } + } + else + { + x->ptr.p_double[0] = 0.5*(a+b); + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + } +} + + +/************************************************************************* +This function generates 1-dimensional Chebyshev-2 interpolation task with +moderate Lipshitz constant (close to 1.0) + +If N=1 then suborutine generates only one point at the middle of [A,B] + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void taskgenint1dcheb2(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(x); + ae_vector_clear(y); + + ae_assert(n>=1, "TaskGenInterpolation1DCheb2: N<1!", _state); + ae_vector_set_length(x, n, _state); + ae_vector_set_length(y, n, _state); + if( n>1 ) + { + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = 0.5*(b+a)+0.5*(b-a)*ae_cos(ae_pi*i/(n-1), _state); + if( i==0 ) + { + y->ptr.p_double[i] = 2*ae_randomreal(_state)-1; + } + else + { + y->ptr.p_double[i] = y->ptr.p_double[i-1]+(2*ae_randomreal(_state)-1)*(x->ptr.p_double[i]-x->ptr.p_double[i-1]); + } + } + } + else + { + x->ptr.p_double[0] = 0.5*(a+b); + y->ptr.p_double[0] = 2*ae_randomreal(_state)-1; + } +} + + +/************************************************************************* +This function checks that all values from X[] are distinct. It does more +than just usual floating point comparison: +* first, it calculates max(X) and min(X) +* second, it maps X[] from [min,max] to [1,2] +* only at this stage actual comparison is done + +The meaning of such check is to ensure that all values are "distinct enough" +and will not cause interpolation subroutine to fail. + +NOTE: + X[] must be sorted by ascending (subroutine ASSERT's it) + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool aredistinct(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double a; + double b; + ae_int_t i; + ae_bool nonsorted; + ae_bool result; + + + ae_assert(n>=1, "APSERVAreDistinct: internal error (N<1)", _state); + if( n==1 ) + { + + /* + * everything is alright, it is up to caller to decide whether it + * can interpolate something with just one point + */ + result = ae_true; + return result; + } + a = x->ptr.p_double[0]; + b = x->ptr.p_double[0]; + nonsorted = ae_false; + for(i=1; i<=n-1; i++) + { + a = ae_minreal(a, x->ptr.p_double[i], _state); + b = ae_maxreal(b, x->ptr.p_double[i], _state); + nonsorted = nonsorted||ae_fp_greater_eq(x->ptr.p_double[i-1],x->ptr.p_double[i]); + } + ae_assert(!nonsorted, "APSERVAreDistinct: internal error (not sorted)", _state); + for(i=1; i<=n-1; i++) + { + if( ae_fp_eq((x->ptr.p_double[i]-a)/(b-a)+1,(x->ptr.p_double[i-1]-a)/(b-a)+1) ) + { + result = ae_false; + return result; + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that two boolean values are the same (both are True +or both are False). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool aresameboolean(ae_bool v1, ae_bool v2, ae_state *_state) +{ + ae_bool result; + + + result = (v1&&v2)||(!v1&&!v2); + return result; +} + + +/************************************************************************* +If Length(X)cntcntcnt0&&n>0 ) + { + if( x->rowscolsrows; + n2 = x->cols; + ae_swap_matrices(x, &oldx); + ae_matrix_set_length(x, m, n, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( iptr.pp_double[i][j] = oldx.ptr.pp_double[i][j]; + } + else + { + x->ptr.pp_double[i][j] = 0.0; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Resizes X and: +* preserves old contents of X +* fills new elements by zeros + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void imatrixresize(/* Integer */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix oldx; + ae_int_t i; + ae_int_t j; + ae_int_t m2; + ae_int_t n2; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init(&oldx, 0, 0, DT_INT, _state, ae_true); + + m2 = x->rows; + n2 = x->cols; + ae_swap_matrices(x, &oldx); + ae_matrix_set_length(x, m, n, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( iptr.pp_int[i][j] = oldx.ptr.pp_int[i][j]; + } + else + { + x->ptr.pp_int[i][j] = 0; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function checks that length(X) is at least N and first N values from +X[] are finite + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool isfinitevector(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteVector: internal error (N<0)", _state); + if( n==0 ) + { + result = ae_true; + return result; + } + if( x->cntptr.p_double[i], _state) ) + { + result = ae_false; + return result; + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that first N values from X[] are finite + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool isfinitecvector(/* Complex */ ae_vector* z, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteCVector: internal error (N<0)", _state); + for(i=0; i<=n-1; i++) + { + if( !ae_isfinite(z->ptr.p_complex[i].x, _state)||!ae_isfinite(z->ptr.p_complex[i].y, _state) ) + { + result = ae_false; + return result; + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that size of X is at least MxN and values from +X[0..M-1,0..N-1] are finite. + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool apservisfinitematrix(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteMatrix: internal error (N<0)", _state); + ae_assert(m>=0, "APSERVIsFiniteMatrix: internal error (M<0)", _state); + if( m==0||n==0 ) + { + result = ae_true; + return result; + } + if( x->rowscolsptr.pp_double[i][j], _state) ) + { + result = ae_false; + return result; + } + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that all values from X[0..M-1,0..N-1] are finite + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool apservisfinitecmatrix(/* Complex */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteCMatrix: internal error (N<0)", _state); + ae_assert(m>=0, "APSERVIsFiniteCMatrix: internal error (M<0)", _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( !ae_isfinite(x->ptr.pp_complex[i][j].x, _state)||!ae_isfinite(x->ptr.pp_complex[i][j].y, _state) ) + { + result = ae_false; + return result; + } + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that size of X is at least NxN and all values from +upper/lower triangle of X[0..N-1,0..N-1] are finite + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool isfinitertrmatrix(/* Real */ ae_matrix* x, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j1; + ae_int_t j2; + ae_int_t j; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteRTRMatrix: internal error (N<0)", _state); + if( n==0 ) + { + result = ae_true; + return result; + } + if( x->rowscolsptr.pp_double[i][j], _state) ) + { + result = ae_false; + return result; + } + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that all values from upper/lower triangle of +X[0..N-1,0..N-1] are finite + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool apservisfinitectrmatrix(/* Complex */ ae_matrix* x, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j1; + ae_int_t j2; + ae_int_t j; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteCTRMatrix: internal error (N<0)", _state); + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + if( !ae_isfinite(x->ptr.pp_complex[i][j].x, _state)||!ae_isfinite(x->ptr.pp_complex[i][j].y, _state) ) + { + result = ae_false; + return result; + } + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function checks that all values from X[0..M-1,0..N-1] are finite or +NaN's. + + -- ALGLIB -- + Copyright 18.06.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool apservisfiniteornanmatrix(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_bool result; + + + ae_assert(n>=0, "APSERVIsFiniteOrNaNMatrix: internal error (N<0)", _state); + ae_assert(m>=0, "APSERVIsFiniteOrNaNMatrix: internal error (M<0)", _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( !(ae_isfinite(x->ptr.pp_double[i][j], _state)||ae_isnan(x->ptr.pp_double[i][j], _state)) ) + { + result = ae_false; + return result; + } + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +Safe sqrt(x^2+y^2) + + -- ALGLIB -- + Copyright by Bochkanov Sergey +*************************************************************************/ +double safepythag2(double x, double y, ae_state *_state) +{ + double w; + double xabs; + double yabs; + double z; + double result; + + + xabs = ae_fabs(x, _state); + yabs = ae_fabs(y, _state); + w = ae_maxreal(xabs, yabs, _state); + z = ae_minreal(xabs, yabs, _state); + if( ae_fp_eq(z,0) ) + { + result = w; + } + else + { + result = w*ae_sqrt(1+ae_sqr(z/w, _state), _state); + } + return result; +} + + +/************************************************************************* +Safe sqrt(x^2+y^2) + + -- ALGLIB -- + Copyright by Bochkanov Sergey +*************************************************************************/ +double safepythag3(double x, double y, double z, ae_state *_state) +{ + double w; + double result; + + + w = ae_maxreal(ae_fabs(x, _state), ae_maxreal(ae_fabs(y, _state), ae_fabs(z, _state), _state), _state); + if( ae_fp_eq(w,0) ) + { + result = 0; + return result; + } + x = x/w; + y = y/w; + z = z/w; + result = w*ae_sqrt(ae_sqr(x, _state)+ae_sqr(y, _state)+ae_sqr(z, _state), _state); + return result; +} + + +/************************************************************************* +Safe division. + +This function attempts to calculate R=X/Y without overflow. + +It returns: +* +1, if abs(X/Y)>=MaxRealNumber or undefined - overflow-like situation + (no overlfow is generated, R is either NAN, PosINF, NegINF) +* 0, if MinRealNumber0 + (R contains result, may be zero) +* -1, if 00 + */ + if( ae_fp_eq(y,0) ) + { + result = 1; + if( ae_fp_eq(x,0) ) + { + *r = _state->v_nan; + } + if( ae_fp_greater(x,0) ) + { + *r = _state->v_posinf; + } + if( ae_fp_less(x,0) ) + { + *r = _state->v_neginf; + } + return result; + } + if( ae_fp_eq(x,0) ) + { + *r = 0; + result = 0; + return result; + } + + /* + * make Y>0 + */ + if( ae_fp_less(y,0) ) + { + x = -x; + y = -y; + } + + /* + * + */ + if( ae_fp_greater_eq(y,1) ) + { + *r = x/y; + if( ae_fp_less_eq(ae_fabs(*r, _state),ae_minrealnumber) ) + { + result = -1; + *r = 0; + } + else + { + result = 0; + } + } + else + { + if( ae_fp_greater_eq(ae_fabs(x, _state),ae_maxrealnumber*y) ) + { + if( ae_fp_greater(x,0) ) + { + *r = _state->v_posinf; + } + else + { + *r = _state->v_neginf; + } + result = 1; + } + else + { + *r = x/y; + result = 0; + } + } + return result; +} + + +/************************************************************************* +This function calculates "safe" min(X/Y,V) for positive finite X, Y, V. +No overflow is generated in any case. + + -- ALGLIB -- + Copyright by Bochkanov Sergey +*************************************************************************/ +double safeminposrv(double x, double y, double v, ae_state *_state) +{ + double r; + double result; + + + if( ae_fp_greater_eq(y,1) ) + { + + /* + * Y>=1, we can safely divide by Y + */ + r = x/y; + result = v; + if( ae_fp_greater(v,r) ) + { + result = r; + } + else + { + result = v; + } + } + else + { + + /* + * Y<1, we can safely multiply by Y + */ + if( ae_fp_less(x,v*y) ) + { + result = x/y; + } + else + { + result = v; + } + } + return result; +} + + +/************************************************************************* +This function makes periodic mapping of X to [A,B]. + +It accepts X, A, B (A>B). It returns T which lies in [A,B] and integer K, +such that X = T + K*(B-A). + +NOTES: +* K is represented as real value, although actually it is integer +* T is guaranteed to be in [A,B] +* T replaces X + + -- ALGLIB -- + Copyright by Bochkanov Sergey +*************************************************************************/ +void apperiodicmap(double* x, + double a, + double b, + double* k, + ae_state *_state) +{ + + *k = 0; + + ae_assert(ae_fp_less(a,b), "APPeriodicMap: internal error!", _state); + *k = ae_ifloor((*x-a)/(b-a), _state); + *x = *x-*k*(b-a); + while(ae_fp_less(*x,a)) + { + *x = *x+(b-a); + *k = *k-1; + } + while(ae_fp_greater(*x,b)) + { + *x = *x-(b-a); + *k = *k+1; + } + *x = ae_maxreal(*x, a, _state); + *x = ae_minreal(*x, b, _state); +} + + +/************************************************************************* +Returns random normal number using low-quality system-provided generator + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +double randomnormal(ae_state *_state) +{ + double u; + double v; + double s; + double result; + + + for(;;) + { + u = 2*ae_randomreal(_state)-1; + v = 2*ae_randomreal(_state)-1; + s = ae_sqr(u, _state)+ae_sqr(v, _state); + if( ae_fp_greater(s,0)&&ae_fp_less(s,1) ) + { + + /* + * two Sqrt's instead of one to + * avoid overflow when S is too small + */ + s = ae_sqrt(-2*ae_log(s, _state), _state)/ae_sqrt(s, _state); + result = u*s; + return result; + } + } + return result; +} + + +/************************************************************************* +Generates random unit vector using low-quality system-provided generator. +Reallocates array if its size is too short. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void randomunit(ae_int_t n, /* Real */ ae_vector* x, ae_state *_state) +{ + ae_int_t i; + double v; + double vv; + + + ae_assert(n>0, "RandomUnit: N<=0", _state); + if( x->cntptr.p_double[i] = vv; + v = v+vv*vv; + } + } + while(ae_fp_less_eq(v,0)); + v = 1/ae_sqrt(v, _state); + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = x->ptr.p_double[i]*v; + } +} + + +/************************************************************************* +This function is used to increment value of integer variable +*************************************************************************/ +void inc(ae_int_t* v, ae_state *_state) +{ + + + *v = *v+1; +} + + +/************************************************************************* +This function is used to decrement value of integer variable +*************************************************************************/ +void dec(ae_int_t* v, ae_state *_state) +{ + + + *v = *v-1; +} + + +/************************************************************************* +This function performs two operations: +1. decrements value of integer variable, if it is positive +2. explicitly sets variable to zero if it is non-positive +It is used by some algorithms to decrease value of internal counters. +*************************************************************************/ +void countdown(ae_int_t* v, ae_state *_state) +{ + + + if( *v>0 ) + { + *v = *v-1; + } + else + { + *v = 0; + } +} + + +/************************************************************************* +'bounds' value: maps X to [B1,B2] + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +double boundval(double x, double b1, double b2, ae_state *_state) +{ + double result; + + + if( ae_fp_less_eq(x,b1) ) + { + result = b1; + return result; + } + if( ae_fp_greater_eq(x,b2) ) + { + result = b2; + return result; + } + result = x; + return result; +} + + +/************************************************************************* +Allocation of serializer: complex value +*************************************************************************/ +void alloccomplex(ae_serializer* s, ae_complex v, ae_state *_state) +{ + + + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); +} + + +/************************************************************************* +Serialization: complex value +*************************************************************************/ +void serializecomplex(ae_serializer* s, ae_complex v, ae_state *_state) +{ + + + ae_serializer_serialize_double(s, v.x, _state); + ae_serializer_serialize_double(s, v.y, _state); +} + + +/************************************************************************* +Unserialization: complex value +*************************************************************************/ +ae_complex unserializecomplex(ae_serializer* s, ae_state *_state) +{ + ae_complex result; + + + ae_serializer_unserialize_double(s, &result.x, _state); + ae_serializer_unserialize_double(s, &result.y, _state); + return result; +} + + +/************************************************************************* +Allocation of serializer: real array +*************************************************************************/ +void allocrealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + + + if( n<0 ) + { + n = v->cnt; + } + ae_serializer_alloc_entry(s); + for(i=0; i<=n-1; i++) + { + ae_serializer_alloc_entry(s); + } +} + + +/************************************************************************* +Serialization: complex value +*************************************************************************/ +void serializerealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + + + if( n<0 ) + { + n = v->cnt; + } + ae_serializer_serialize_int(s, n, _state); + for(i=0; i<=n-1; i++) + { + ae_serializer_serialize_double(s, v->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +Unserialization: complex value +*************************************************************************/ +void unserializerealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double t; + + ae_vector_clear(v); + + ae_serializer_unserialize_int(s, &n, _state); + if( n==0 ) + { + return; + } + ae_vector_set_length(v, n, _state); + for(i=0; i<=n-1; i++) + { + ae_serializer_unserialize_double(s, &t, _state); + v->ptr.p_double[i] = t; + } +} + + +/************************************************************************* +Allocation of serializer: Integer array +*************************************************************************/ +void allocintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + + + if( n<0 ) + { + n = v->cnt; + } + ae_serializer_alloc_entry(s); + for(i=0; i<=n-1; i++) + { + ae_serializer_alloc_entry(s); + } +} + + +/************************************************************************* +Serialization: Integer array +*************************************************************************/ +void serializeintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + + + if( n<0 ) + { + n = v->cnt; + } + ae_serializer_serialize_int(s, n, _state); + for(i=0; i<=n-1; i++) + { + ae_serializer_serialize_int(s, v->ptr.p_int[i], _state); + } +} + + +/************************************************************************* +Unserialization: complex value +*************************************************************************/ +void unserializeintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t t; + + ae_vector_clear(v); + + ae_serializer_unserialize_int(s, &n, _state); + if( n==0 ) + { + return; + } + ae_vector_set_length(v, n, _state); + for(i=0; i<=n-1; i++) + { + ae_serializer_unserialize_int(s, &t, _state); + v->ptr.p_int[i] = t; + } +} + + +/************************************************************************* +Allocation of serializer: real matrix +*************************************************************************/ +void allocrealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_int_t n0, + ae_int_t n1, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + if( n0<0 ) + { + n0 = v->rows; + } + if( n1<0 ) + { + n1 = v->cols; + } + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + for(i=0; i<=n0-1; i++) + { + for(j=0; j<=n1-1; j++) + { + ae_serializer_alloc_entry(s); + } + } +} + + +/************************************************************************* +Serialization: complex value +*************************************************************************/ +void serializerealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_int_t n0, + ae_int_t n1, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + if( n0<0 ) + { + n0 = v->rows; + } + if( n1<0 ) + { + n1 = v->cols; + } + ae_serializer_serialize_int(s, n0, _state); + ae_serializer_serialize_int(s, n1, _state); + for(i=0; i<=n0-1; i++) + { + for(j=0; j<=n1-1; j++) + { + ae_serializer_serialize_double(s, v->ptr.pp_double[i][j], _state); + } + } +} + + +/************************************************************************* +Unserialization: complex value +*************************************************************************/ +void unserializerealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n0; + ae_int_t n1; + double t; + + ae_matrix_clear(v); + + ae_serializer_unserialize_int(s, &n0, _state); + ae_serializer_unserialize_int(s, &n1, _state); + if( n0==0||n1==0 ) + { + return; + } + ae_matrix_set_length(v, n0, n1, _state); + for(i=0; i<=n0-1; i++) + { + for(j=0; j<=n1-1; j++) + { + ae_serializer_unserialize_double(s, &t, _state); + v->ptr.pp_double[i][j] = t; + } + } +} + + +/************************************************************************* +Copy integer array +*************************************************************************/ +void copyintegerarray(/* Integer */ ae_vector* src, + /* Integer */ ae_vector* dst, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(dst); + + if( src->cnt>0 ) + { + ae_vector_set_length(dst, src->cnt, _state); + for(i=0; i<=src->cnt-1; i++) + { + dst->ptr.p_int[i] = src->ptr.p_int[i]; + } + } +} + + +/************************************************************************* +Copy real array +*************************************************************************/ +void copyrealarray(/* Real */ ae_vector* src, + /* Real */ ae_vector* dst, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(dst); + + if( src->cnt>0 ) + { + ae_vector_set_length(dst, src->cnt, _state); + for(i=0; i<=src->cnt-1; i++) + { + dst->ptr.p_double[i] = src->ptr.p_double[i]; + } + } +} + + +/************************************************************************* +Copy real matrix +*************************************************************************/ +void copyrealmatrix(/* Real */ ae_matrix* src, + /* Real */ ae_matrix* dst, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(dst); + + if( src->rows>0&&src->cols>0 ) + { + ae_matrix_set_length(dst, src->rows, src->cols, _state); + for(i=0; i<=src->rows-1; i++) + { + for(j=0; j<=src->cols-1; j++) + { + dst->ptr.pp_double[i][j] = src->ptr.pp_double[i][j]; + } + } + } +} + + +/************************************************************************* +This function searches integer array. Elements in this array are actually +records, each NRec elements wide. Each record has unique header - NHeader +integer values, which identify it. Records are lexicographically sorted by +header. + +Records are identified by their index, not offset (offset = NRec*index). + +This function searches A (records with indices [I0,I1)) for a record with +header B. It returns index of this record (not offset!), or -1 on failure. + + -- ALGLIB -- + Copyright 28.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t recsearch(/* Integer */ ae_vector* a, + ae_int_t nrec, + ae_int_t nheader, + ae_int_t i0, + ae_int_t i1, + /* Integer */ ae_vector* b, + ae_state *_state) +{ + ae_int_t mididx; + ae_int_t cflag; + ae_int_t k; + ae_int_t offs; + ae_int_t result; + + + result = -1; + for(;;) + { + if( i0>=i1 ) + { + break; + } + mididx = (i0+i1)/2; + offs = nrec*mididx; + cflag = 0; + for(k=0; k<=nheader-1; k++) + { + if( a->ptr.p_int[offs+k]ptr.p_int[k] ) + { + cflag = -1; + break; + } + if( a->ptr.p_int[offs+k]>b->ptr.p_int[k] ) + { + cflag = 1; + break; + } + } + if( cflag==0 ) + { + result = mididx; + return result; + } + if( cflag<0 ) + { + i0 = mididx+1; + } + else + { + i1 = mididx; + } + } + return result; +} + + +/************************************************************************* +This function is used in parallel functions for recurrent division of large +task into two smaller tasks. + +It has following properties: +* it works only for TaskSize>=2 (assertion is thrown otherwise) +* for TaskSize=2, it returns Task0=1, Task1=1 +* in case TaskSize is odd, Task0=TaskSize-1, Task1=1 +* in case TaskSize is even, Task0 and Task1 are approximately TaskSize/2 + and both Task0 and Task1 are even, Task0>=Task1 + + -- ALGLIB -- + Copyright 07.04.2013 by Bochkanov Sergey +*************************************************************************/ +void splitlengtheven(ae_int_t tasksize, + ae_int_t* task0, + ae_int_t* task1, + ae_state *_state) +{ + + *task0 = 0; + *task1 = 0; + + ae_assert(tasksize>=2, "SplitLengthEven: TaskSize<2", _state); + if( tasksize==2 ) + { + *task0 = 1; + *task1 = 1; + return; + } + if( tasksize%2==0 ) + { + + /* + * Even division + */ + *task0 = tasksize/2; + *task1 = tasksize/2; + if( *task0%2!=0 ) + { + *task0 = *task0+1; + *task1 = *task1-1; + } + } + else + { + + /* + * Odd task size, split trailing odd part from it. + */ + *task0 = tasksize-1; + *task1 = 1; + } + ae_assert(*task0>=1, "SplitLengthEven: internal error", _state); + ae_assert(*task1>=1, "SplitLengthEven: internal error", _state); +} + + +/************************************************************************* +This function is used in parallel functions for recurrent division of large +task into two smaller tasks. + +It has following properties: +* it works only for TaskSize>=2 and ChunkSize>=2 + (assertion is thrown otherwise) +* Task0+Task1=TaskSize, Task0>0, Task1>0 +* Task0 and Task1 are close to each other +* in case TaskSize>ChunkSize, Task0 is always divisible by ChunkSize + + -- ALGLIB -- + Copyright 07.04.2013 by Bochkanov Sergey +*************************************************************************/ +void splitlength(ae_int_t tasksize, + ae_int_t chunksize, + ae_int_t* task0, + ae_int_t* task1, + ae_state *_state) +{ + + *task0 = 0; + *task1 = 0; + + ae_assert(chunksize>=2, "SplitLength: ChunkSize<2", _state); + ae_assert(tasksize>=2, "SplitLength: TaskSize<2", _state); + *task0 = tasksize/2; + if( *task0>chunksize&&*task0%chunksize!=0 ) + { + *task0 = *task0-*task0%chunksize; + } + *task1 = tasksize-(*task0); + ae_assert(*task0>=1, "SplitLength: internal error", _state); + ae_assert(*task1>=1, "SplitLength: internal error", _state); +} + + +ae_bool _apbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + apbuffers *p = (apbuffers*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->ia0, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ia1, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ia2, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ia3, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ra0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ra1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ra2, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ra3, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _apbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + apbuffers *dst = (apbuffers*)_dst; + apbuffers *src = (apbuffers*)_src; + if( !ae_vector_init_copy(&dst->ia0, &src->ia0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ia1, &src->ia1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ia2, &src->ia2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ia3, &src->ia3, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ra0, &src->ra0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ra1, &src->ra1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ra2, &src->ra2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ra3, &src->ra3, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _apbuffers_clear(void* _p) +{ + apbuffers *p = (apbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->ia0); + ae_vector_clear(&p->ia1); + ae_vector_clear(&p->ia2); + ae_vector_clear(&p->ia3); + ae_vector_clear(&p->ra0); + ae_vector_clear(&p->ra1); + ae_vector_clear(&p->ra2); + ae_vector_clear(&p->ra3); +} + + +void _apbuffers_destroy(void* _p) +{ + apbuffers *p = (apbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->ia0); + ae_vector_destroy(&p->ia1); + ae_vector_destroy(&p->ia2); + ae_vector_destroy(&p->ia3); + ae_vector_destroy(&p->ra0); + ae_vector_destroy(&p->ra1); + ae_vector_destroy(&p->ra2); + ae_vector_destroy(&p->ra3); +} + + +ae_bool _sboolean_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sboolean *p = (sboolean*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _sboolean_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sboolean *dst = (sboolean*)_dst; + sboolean *src = (sboolean*)_src; + dst->val = src->val; + return ae_true; +} + + +void _sboolean_clear(void* _p) +{ + sboolean *p = (sboolean*)_p; + ae_touch_ptr((void*)p); +} + + +void _sboolean_destroy(void* _p) +{ + sboolean *p = (sboolean*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _sbooleanarray_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sbooleanarray *p = (sbooleanarray*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->val, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _sbooleanarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sbooleanarray *dst = (sbooleanarray*)_dst; + sbooleanarray *src = (sbooleanarray*)_src; + if( !ae_vector_init_copy(&dst->val, &src->val, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _sbooleanarray_clear(void* _p) +{ + sbooleanarray *p = (sbooleanarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->val); +} + + +void _sbooleanarray_destroy(void* _p) +{ + sbooleanarray *p = (sbooleanarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->val); +} + + +ae_bool _sinteger_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sinteger *p = (sinteger*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _sinteger_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sinteger *dst = (sinteger*)_dst; + sinteger *src = (sinteger*)_src; + dst->val = src->val; + return ae_true; +} + + +void _sinteger_clear(void* _p) +{ + sinteger *p = (sinteger*)_p; + ae_touch_ptr((void*)p); +} + + +void _sinteger_destroy(void* _p) +{ + sinteger *p = (sinteger*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _sintegerarray_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sintegerarray *p = (sintegerarray*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->val, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _sintegerarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sintegerarray *dst = (sintegerarray*)_dst; + sintegerarray *src = (sintegerarray*)_src; + if( !ae_vector_init_copy(&dst->val, &src->val, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _sintegerarray_clear(void* _p) +{ + sintegerarray *p = (sintegerarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->val); +} + + +void _sintegerarray_destroy(void* _p) +{ + sintegerarray *p = (sintegerarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->val); +} + + +ae_bool _sreal_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sreal *p = (sreal*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _sreal_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sreal *dst = (sreal*)_dst; + sreal *src = (sreal*)_src; + dst->val = src->val; + return ae_true; +} + + +void _sreal_clear(void* _p) +{ + sreal *p = (sreal*)_p; + ae_touch_ptr((void*)p); +} + + +void _sreal_destroy(void* _p) +{ + sreal *p = (sreal*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _srealarray_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + srealarray *p = (srealarray*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->val, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _srealarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + srealarray *dst = (srealarray*)_dst; + srealarray *src = (srealarray*)_src; + if( !ae_vector_init_copy(&dst->val, &src->val, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _srealarray_clear(void* _p) +{ + srealarray *p = (srealarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->val); +} + + +void _srealarray_destroy(void* _p) +{ + srealarray *p = (srealarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->val); +} + + +ae_bool _scomplex_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + scomplex *p = (scomplex*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _scomplex_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + scomplex *dst = (scomplex*)_dst; + scomplex *src = (scomplex*)_src; + dst->val = src->val; + return ae_true; +} + + +void _scomplex_clear(void* _p) +{ + scomplex *p = (scomplex*)_p; + ae_touch_ptr((void*)p); +} + + +void _scomplex_destroy(void* _p) +{ + scomplex *p = (scomplex*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _scomplexarray_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + scomplexarray *p = (scomplexarray*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->val, 0, DT_COMPLEX, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _scomplexarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + scomplexarray *dst = (scomplexarray*)_dst; + scomplexarray *src = (scomplexarray*)_src; + if( !ae_vector_init_copy(&dst->val, &src->val, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _scomplexarray_clear(void* _p) +{ + scomplexarray *p = (scomplexarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->val); +} + + +void _scomplexarray_destroy(void* _p) +{ + scomplexarray *p = (scomplexarray*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->val); +} + + + + +ae_int_t getrdfserializationcode(ae_state *_state) +{ + ae_int_t result; + + + result = 1; + return result; +} + + +ae_int_t getkdtreeserializationcode(ae_state *_state) +{ + ae_int_t result; + + + result = 2; + return result; +} + + +ae_int_t getmlpserializationcode(ae_state *_state) +{ + ae_int_t result; + + + result = 3; + return result; +} + + +ae_int_t getmlpeserializationcode(ae_state *_state) +{ + ae_int_t result; + + + result = 4; + return result; +} + + +ae_int_t getrbfserializationcode(ae_state *_state) +{ + ae_int_t result; + + + result = 5; + return result; +} + + + + +/************************************************************************* +This function sorts array of real keys by ascending. + +Its results are: +* sorted array A +* permutation tables P1, P2 + +Algorithm outputs permutation tables using two formats: +* as usual permutation of [0..N-1]. If P1[i]=j, then sorted A[i] contains + value which was moved there from J-th position. +* as a sequence of pairwise permutations. Sorted A[] may be obtained by + swaping A[i] and A[P2[i]] for all i from 0 to N-1. + +INPUT PARAMETERS: + A - unsorted array + N - array size + +OUPUT PARAMETERS: + A - sorted array + P1, P2 - permutation tables, array[N] + +NOTES: + this function assumes that A[] is finite; it doesn't checks that + condition. All other conditions (size of input arrays, etc.) are not + checked too. + + -- ALGLIB -- + Copyright 14.05.2008 by Bochkanov Sergey +*************************************************************************/ +void tagsort(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + ae_state *_state) +{ + ae_frame _frame_block; + apbuffers buf; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(p1); + ae_vector_clear(p2); + _apbuffers_init(&buf, _state, ae_true); + + tagsortbuf(a, n, p1, p2, &buf, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Buffered variant of TagSort, which accepts preallocated output arrays as +well as special structure for buffered allocations. If arrays are too +short, they are reallocated. If they are large enough, no memory +allocation is done. + +It is intended to be used in the performance-critical parts of code, where +additional allocations can lead to severe performance degradation + + -- ALGLIB -- + Copyright 14.05.2008 by Bochkanov Sergey +*************************************************************************/ +void tagsortbuf(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + apbuffers* buf, + ae_state *_state) +{ + ae_int_t i; + ae_int_t lv; + ae_int_t lp; + ae_int_t rv; + ae_int_t rp; + + + + /* + * Special cases + */ + if( n<=0 ) + { + return; + } + if( n==1 ) + { + ivectorsetlengthatleast(p1, 1, _state); + ivectorsetlengthatleast(p2, 1, _state); + p1->ptr.p_int[0] = 0; + p2->ptr.p_int[0] = 0; + return; + } + + /* + * General case, N>1: prepare permutations table P1 + */ + ivectorsetlengthatleast(p1, n, _state); + for(i=0; i<=n-1; i++) + { + p1->ptr.p_int[i] = i; + } + + /* + * General case, N>1: sort, update P1 + */ + rvectorsetlengthatleast(&buf->ra0, n, _state); + ivectorsetlengthatleast(&buf->ia0, n, _state); + tagsortfasti(a, p1, &buf->ra0, &buf->ia0, n, _state); + + /* + * General case, N>1: fill permutations table P2 + * + * To fill P2 we maintain two arrays: + * * PV (Buf.IA0), Position(Value). PV[i] contains position of I-th key at the moment + * * VP (Buf.IA1), Value(Position). VP[i] contains key which has position I at the moment + * + * At each step we making permutation of two items: + * Left, which is given by position/value pair LP/LV + * and Right, which is given by RP/RV + * and updating PV[] and VP[] correspondingly. + */ + ivectorsetlengthatleast(&buf->ia0, n, _state); + ivectorsetlengthatleast(&buf->ia1, n, _state); + ivectorsetlengthatleast(p2, n, _state); + for(i=0; i<=n-1; i++) + { + buf->ia0.ptr.p_int[i] = i; + buf->ia1.ptr.p_int[i] = i; + } + for(i=0; i<=n-1; i++) + { + + /* + * calculate LP, LV, RP, RV + */ + lp = i; + lv = buf->ia1.ptr.p_int[lp]; + rv = p1->ptr.p_int[i]; + rp = buf->ia0.ptr.p_int[rv]; + + /* + * Fill P2 + */ + p2->ptr.p_int[i] = rp; + + /* + * update PV and VP + */ + buf->ia1.ptr.p_int[lp] = rv; + buf->ia1.ptr.p_int[rp] = lv; + buf->ia0.ptr.p_int[lv] = rp; + buf->ia0.ptr.p_int[rv] = lp; + } +} + + +/************************************************************************* +Same as TagSort, but optimized for real keys and integer labels. + +A is sorted, and same permutations are applied to B. + +NOTES: +1. this function assumes that A[] is finite; it doesn't checks that + condition. All other conditions (size of input arrays, etc.) are not + checked too. +2. this function uses two buffers, BufA and BufB, each is N elements large. + They may be preallocated (which will save some time) or not, in which + case function will automatically allocate memory. + + -- ALGLIB -- + Copyright 11.12.2008 by Bochkanov Sergey +*************************************************************************/ +void tagsortfasti(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Integer */ ae_vector* bufb, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_bool isascending; + ae_bool isdescending; + double tmpr; + ae_int_t tmpi; + + + + /* + * Special case + */ + if( n<=1 ) + { + return; + } + + /* + * Test for already sorted set + */ + isascending = ae_true; + isdescending = ae_true; + for(i=1; i<=n-1; i++) + { + isascending = isascending&&a->ptr.p_double[i]>=a->ptr.p_double[i-1]; + isdescending = isdescending&&a->ptr.p_double[i]<=a->ptr.p_double[i-1]; + } + if( isascending ) + { + return; + } + if( isdescending ) + { + for(i=0; i<=n-1; i++) + { + j = n-1-i; + if( j<=i ) + { + break; + } + tmpr = a->ptr.p_double[i]; + a->ptr.p_double[i] = a->ptr.p_double[j]; + a->ptr.p_double[j] = tmpr; + tmpi = b->ptr.p_int[i]; + b->ptr.p_int[i] = b->ptr.p_int[j]; + b->ptr.p_int[j] = tmpi; + } + return; + } + + /* + * General case + */ + if( bufa->cntcntptr.p_double[i]>=a->ptr.p_double[i-1]; + isdescending = isdescending&&a->ptr.p_double[i]<=a->ptr.p_double[i-1]; + } + if( isascending ) + { + return; + } + if( isdescending ) + { + for(i=0; i<=n-1; i++) + { + j = n-1-i; + if( j<=i ) + { + break; + } + tmpr = a->ptr.p_double[i]; + a->ptr.p_double[i] = a->ptr.p_double[j]; + a->ptr.p_double[j] = tmpr; + tmpr = b->ptr.p_double[i]; + b->ptr.p_double[i] = b->ptr.p_double[j]; + b->ptr.p_double[j] = tmpr; + } + return; + } + + /* + * General case + */ + if( bufa->cntcntptr.p_double[i]>=a->ptr.p_double[i-1]; + isdescending = isdescending&&a->ptr.p_double[i]<=a->ptr.p_double[i-1]; + } + if( isascending ) + { + return; + } + if( isdescending ) + { + for(i=0; i<=n-1; i++) + { + j = n-1-i; + if( j<=i ) + { + break; + } + tmpr = a->ptr.p_double[i]; + a->ptr.p_double[i] = a->ptr.p_double[j]; + a->ptr.p_double[j] = tmpr; + } + return; + } + + /* + * General case + */ + if( bufa->cnt1: sort, update B + */ + i = 2; + do + { + t = i; + while(t!=1) + { + k = t/2; + if( a->ptr.p_int[offset+k-1]>=a->ptr.p_int[offset+t-1] ) + { + t = 1; + } + else + { + tmp = a->ptr.p_int[offset+k-1]; + a->ptr.p_int[offset+k-1] = a->ptr.p_int[offset+t-1]; + a->ptr.p_int[offset+t-1] = tmp; + tmpr = b->ptr.p_double[offset+k-1]; + b->ptr.p_double[offset+k-1] = b->ptr.p_double[offset+t-1]; + b->ptr.p_double[offset+t-1] = tmpr; + t = k; + } + } + i = i+1; + } + while(i<=n); + i = n-1; + do + { + tmp = a->ptr.p_int[offset+i]; + a->ptr.p_int[offset+i] = a->ptr.p_int[offset+0]; + a->ptr.p_int[offset+0] = tmp; + tmpr = b->ptr.p_double[offset+i]; + b->ptr.p_double[offset+i] = b->ptr.p_double[offset+0]; + b->ptr.p_double[offset+0] = tmpr; + t = 1; + while(t!=0) + { + k = 2*t; + if( k>i ) + { + t = 0; + } + else + { + if( kptr.p_int[offset+k]>a->ptr.p_int[offset+k-1] ) + { + k = k+1; + } + } + if( a->ptr.p_int[offset+t-1]>=a->ptr.p_int[offset+k-1] ) + { + t = 0; + } + else + { + tmp = a->ptr.p_int[offset+k-1]; + a->ptr.p_int[offset+k-1] = a->ptr.p_int[offset+t-1]; + a->ptr.p_int[offset+t-1] = tmp; + tmpr = b->ptr.p_double[offset+k-1]; + b->ptr.p_double[offset+k-1] = b->ptr.p_double[offset+t-1]; + b->ptr.p_double[offset+t-1] = tmpr; + t = k; + } + } + } + i = i-1; + } + while(i>=1); +} + + +/************************************************************************* +Heap operations: adds element to the heap + +PARAMETERS: + A - heap itself, must be at least array[0..N] + B - array of integer tags, which are updated according to + permutations in the heap + N - size of the heap (without new element). + updated on output + VA - value of the element being added + VB - value of the tag + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void tagheappushi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t* n, + double va, + ae_int_t vb, + ae_state *_state) +{ + ae_int_t j; + ae_int_t k; + double v; + + + if( *n<0 ) + { + return; + } + + /* + * N=0 is a special case + */ + if( *n==0 ) + { + a->ptr.p_double[0] = va; + b->ptr.p_int[0] = vb; + *n = *n+1; + return; + } + + /* + * add current point to the heap + * (add to the bottom, then move up) + * + * we don't write point to the heap + * until its final position is determined + * (it allow us to reduce number of array access operations) + */ + j = *n; + *n = *n+1; + while(j>0) + { + k = (j-1)/2; + v = a->ptr.p_double[k]; + if( ae_fp_less(v,va) ) + { + + /* + * swap with higher element + */ + a->ptr.p_double[j] = v; + b->ptr.p_int[j] = b->ptr.p_int[k]; + j = k; + } + else + { + + /* + * element in its place. terminate. + */ + break; + } + } + a->ptr.p_double[j] = va; + b->ptr.p_int[j] = vb; +} + + +/************************************************************************* +Heap operations: replaces top element with new element +(which is moved down) + +PARAMETERS: + A - heap itself, must be at least array[0..N-1] + B - array of integer tags, which are updated according to + permutations in the heap + N - size of the heap + VA - value of the element which replaces top element + VB - value of the tag + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void tagheapreplacetopi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t n, + double va, + ae_int_t vb, + ae_state *_state) +{ + ae_int_t j; + ae_int_t k1; + ae_int_t k2; + double v; + double v1; + double v2; + + + if( n<1 ) + { + return; + } + + /* + * N=1 is a special case + */ + if( n==1 ) + { + a->ptr.p_double[0] = va; + b->ptr.p_int[0] = vb; + return; + } + + /* + * move down through heap: + * * J - current element + * * K1 - first child (always exists) + * * K2 - second child (may not exists) + * + * we don't write point to the heap + * until its final position is determined + * (it allow us to reduce number of array access operations) + */ + j = 0; + k1 = 1; + k2 = 2; + while(k1=n ) + { + + /* + * only one child. + * + * swap and terminate (because this child + * have no siblings due to heap structure) + */ + v = a->ptr.p_double[k1]; + if( ae_fp_greater(v,va) ) + { + a->ptr.p_double[j] = v; + b->ptr.p_int[j] = b->ptr.p_int[k1]; + j = k1; + } + break; + } + else + { + + /* + * two childs + */ + v1 = a->ptr.p_double[k1]; + v2 = a->ptr.p_double[k2]; + if( ae_fp_greater(v1,v2) ) + { + if( ae_fp_less(va,v1) ) + { + a->ptr.p_double[j] = v1; + b->ptr.p_int[j] = b->ptr.p_int[k1]; + j = k1; + } + else + { + break; + } + } + else + { + if( ae_fp_less(va,v2) ) + { + a->ptr.p_double[j] = v2; + b->ptr.p_int[j] = b->ptr.p_int[k2]; + j = k2; + } + else + { + break; + } + } + k1 = 2*j+1; + k2 = 2*j+2; + } + } + a->ptr.p_double[j] = va; + b->ptr.p_int[j] = vb; +} + + +/************************************************************************* +Heap operations: pops top element from the heap + +PARAMETERS: + A - heap itself, must be at least array[0..N-1] + B - array of integer tags, which are updated according to + permutations in the heap + N - size of the heap, N>=1 + +On output top element is moved to A[N-1], B[N-1], heap is reordered, N is +decreased by 1. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void tagheappopi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t* n, + ae_state *_state) +{ + double va; + ae_int_t vb; + + + if( *n<1 ) + { + return; + } + + /* + * N=1 is a special case + */ + if( *n==1 ) + { + *n = 0; + return; + } + + /* + * swap top element and last element, + * then reorder heap + */ + va = a->ptr.p_double[*n-1]; + vb = b->ptr.p_int[*n-1]; + a->ptr.p_double[*n-1] = a->ptr.p_double[0]; + b->ptr.p_int[*n-1] = b->ptr.p_int[0]; + *n = *n-1; + tagheapreplacetopi(a, b, *n, va, vb, _state); +} + + +/************************************************************************* +Search first element less than T in sorted array. + +PARAMETERS: + A - sorted array by ascending from 0 to N-1 + N - number of elements in array + T - the desired element + +RESULT: + The very first element's index, which isn't less than T. +In the case when there aren't such elements, returns N. +*************************************************************************/ +ae_int_t lowerbound(/* Real */ ae_vector* a, + ae_int_t n, + double t, + ae_state *_state) +{ + ae_int_t l; + ae_int_t half; + ae_int_t first; + ae_int_t middle; + ae_int_t result; + + + l = n; + first = 0; + while(l>0) + { + half = l/2; + middle = first+half; + if( ae_fp_less(a->ptr.p_double[middle],t) ) + { + first = middle+1; + l = l-half-1; + } + else + { + l = half; + } + } + result = first; + return result; +} + + +/************************************************************************* +Search first element more than T in sorted array. + +PARAMETERS: + A - sorted array by ascending from 0 to N-1 + N - number of elements in array + T - the desired element + + RESULT: + The very first element's index, which more than T. +In the case when there aren't such elements, returns N. +*************************************************************************/ +ae_int_t upperbound(/* Real */ ae_vector* a, + ae_int_t n, + double t, + ae_state *_state) +{ + ae_int_t l; + ae_int_t half; + ae_int_t first; + ae_int_t middle; + ae_int_t result; + + + l = n; + first = 0; + while(l>0) + { + half = l/2; + middle = first+half; + if( ae_fp_less(t,a->ptr.p_double[middle]) ) + { + l = half; + } + else + { + first = middle+1; + l = l-half-1; + } + } + result = first; + return result; +} + + +/************************************************************************* +Internal TagSortFastI: sorts A[I1...I2] (both bounds are included), +applies same permutations to B. + + -- ALGLIB -- + Copyright 06.09.2010 by Bochkanov Sergey +*************************************************************************/ +static void tsort_tagsortfastirec(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Integer */ ae_vector* bufb, + ae_int_t i1, + ae_int_t i2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t cntless; + ae_int_t cnteq; + ae_int_t cntgreater; + double tmpr; + ae_int_t tmpi; + double v0; + double v1; + double v2; + double vp; + + + + /* + * Fast exit + */ + if( i2<=i1 ) + { + return; + } + + /* + * Non-recursive sort for small arrays + */ + if( i2-i1<=16 ) + { + for(j=i1+1; j<=i2; j++) + { + + /* + * Search elements [I1..J-1] for place to insert Jth element. + * + * This code stops immediately if we can leave A[J] at J-th position + * (all elements have same value of A[J] larger than any of them) + */ + tmpr = a->ptr.p_double[j]; + tmpi = j; + for(k=j-1; k>=i1; k--) + { + if( a->ptr.p_double[k]<=tmpr ) + { + break; + } + tmpi = k; + } + k = tmpi; + + /* + * Insert Jth element into Kth position + */ + if( k!=j ) + { + tmpr = a->ptr.p_double[j]; + tmpi = b->ptr.p_int[j]; + for(i=j-1; i>=k; i--) + { + a->ptr.p_double[i+1] = a->ptr.p_double[i]; + b->ptr.p_int[i+1] = b->ptr.p_int[i]; + } + a->ptr.p_double[k] = tmpr; + b->ptr.p_int[k] = tmpi; + } + } + return; + } + + /* + * Quicksort: choose pivot + * Here we assume that I2-I1>=2 + */ + v0 = a->ptr.p_double[i1]; + v1 = a->ptr.p_double[i1+(i2-i1)/2]; + v2 = a->ptr.p_double[i2]; + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + if( v1>v2 ) + { + tmpr = v2; + v2 = v1; + v1 = tmpr; + } + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + vp = v1; + + /* + * now pass through A/B and: + * * move elements that are LESS than VP to the left of A/B + * * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order) + * * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order + * * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order) + * * move elements from the left of BufA/BufB to the end of A/B + */ + cntless = 0; + cnteq = 0; + cntgreater = 0; + for(i=i1; i<=i2; i++) + { + v0 = a->ptr.p_double[i]; + if( v0ptr.p_double[k] = v0; + b->ptr.p_int[k] = b->ptr.p_int[i]; + } + cntless = cntless+1; + continue; + } + if( v0==vp ) + { + + /* + * EQUAL + */ + k = i2-cnteq; + bufa->ptr.p_double[k] = v0; + bufb->ptr.p_int[k] = b->ptr.p_int[i]; + cnteq = cnteq+1; + continue; + } + + /* + * GREATER + */ + k = i1+cntgreater; + bufa->ptr.p_double[k] = v0; + bufb->ptr.p_int[k] = b->ptr.p_int[i]; + cntgreater = cntgreater+1; + } + for(i=0; i<=cnteq-1; i++) + { + j = i1+cntless+cnteq-1-i; + k = i2+i-(cnteq-1); + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + b->ptr.p_int[j] = bufb->ptr.p_int[k]; + } + for(i=0; i<=cntgreater-1; i++) + { + j = i1+cntless+cnteq+i; + k = i1+i; + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + b->ptr.p_int[j] = bufb->ptr.p_int[k]; + } + + /* + * Sort left and right parts of the array (ignoring middle part) + */ + tsort_tagsortfastirec(a, b, bufa, bufb, i1, i1+cntless-1, _state); + tsort_tagsortfastirec(a, b, bufa, bufb, i1+cntless+cnteq, i2, _state); +} + + +/************************************************************************* +Internal TagSortFastR: sorts A[I1...I2] (both bounds are included), +applies same permutations to B. + + -- ALGLIB -- + Copyright 06.09.2010 by Bochkanov Sergey +*************************************************************************/ +static void tsort_tagsortfastrrec(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Real */ ae_vector* bufb, + ae_int_t i1, + ae_int_t i2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + double tmpr; + double tmpr2; + ae_int_t tmpi; + ae_int_t cntless; + ae_int_t cnteq; + ae_int_t cntgreater; + double v0; + double v1; + double v2; + double vp; + + + + /* + * Fast exit + */ + if( i2<=i1 ) + { + return; + } + + /* + * Non-recursive sort for small arrays + */ + if( i2-i1<=16 ) + { + for(j=i1+1; j<=i2; j++) + { + + /* + * Search elements [I1..J-1] for place to insert Jth element. + * + * This code stops immediatly if we can leave A[J] at J-th position + * (all elements have same value of A[J] larger than any of them) + */ + tmpr = a->ptr.p_double[j]; + tmpi = j; + for(k=j-1; k>=i1; k--) + { + if( a->ptr.p_double[k]<=tmpr ) + { + break; + } + tmpi = k; + } + k = tmpi; + + /* + * Insert Jth element into Kth position + */ + if( k!=j ) + { + tmpr = a->ptr.p_double[j]; + tmpr2 = b->ptr.p_double[j]; + for(i=j-1; i>=k; i--) + { + a->ptr.p_double[i+1] = a->ptr.p_double[i]; + b->ptr.p_double[i+1] = b->ptr.p_double[i]; + } + a->ptr.p_double[k] = tmpr; + b->ptr.p_double[k] = tmpr2; + } + } + return; + } + + /* + * Quicksort: choose pivot + * Here we assume that I2-I1>=16 + */ + v0 = a->ptr.p_double[i1]; + v1 = a->ptr.p_double[i1+(i2-i1)/2]; + v2 = a->ptr.p_double[i2]; + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + if( v1>v2 ) + { + tmpr = v2; + v2 = v1; + v1 = tmpr; + } + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + vp = v1; + + /* + * now pass through A/B and: + * * move elements that are LESS than VP to the left of A/B + * * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order) + * * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order + * * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order) + * * move elements from the left of BufA/BufB to the end of A/B + */ + cntless = 0; + cnteq = 0; + cntgreater = 0; + for(i=i1; i<=i2; i++) + { + v0 = a->ptr.p_double[i]; + if( v0ptr.p_double[k] = v0; + b->ptr.p_double[k] = b->ptr.p_double[i]; + } + cntless = cntless+1; + continue; + } + if( v0==vp ) + { + + /* + * EQUAL + */ + k = i2-cnteq; + bufa->ptr.p_double[k] = v0; + bufb->ptr.p_double[k] = b->ptr.p_double[i]; + cnteq = cnteq+1; + continue; + } + + /* + * GREATER + */ + k = i1+cntgreater; + bufa->ptr.p_double[k] = v0; + bufb->ptr.p_double[k] = b->ptr.p_double[i]; + cntgreater = cntgreater+1; + } + for(i=0; i<=cnteq-1; i++) + { + j = i1+cntless+cnteq-1-i; + k = i2+i-(cnteq-1); + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + b->ptr.p_double[j] = bufb->ptr.p_double[k]; + } + for(i=0; i<=cntgreater-1; i++) + { + j = i1+cntless+cnteq+i; + k = i1+i; + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + b->ptr.p_double[j] = bufb->ptr.p_double[k]; + } + + /* + * Sort left and right parts of the array (ignoring middle part) + */ + tsort_tagsortfastrrec(a, b, bufa, bufb, i1, i1+cntless-1, _state); + tsort_tagsortfastrrec(a, b, bufa, bufb, i1+cntless+cnteq, i2, _state); +} + + +/************************************************************************* +Internal TagSortFastI: sorts A[I1...I2] (both bounds are included), +applies same permutations to B. + + -- ALGLIB -- + Copyright 06.09.2010 by Bochkanov Sergey +*************************************************************************/ +static void tsort_tagsortfastrec(/* Real */ ae_vector* a, + /* Real */ ae_vector* bufa, + ae_int_t i1, + ae_int_t i2, + ae_state *_state) +{ + ae_int_t cntless; + ae_int_t cnteq; + ae_int_t cntgreater; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double tmpr; + ae_int_t tmpi; + double v0; + double v1; + double v2; + double vp; + + + + /* + * Fast exit + */ + if( i2<=i1 ) + { + return; + } + + /* + * Non-recursive sort for small arrays + */ + if( i2-i1<=16 ) + { + for(j=i1+1; j<=i2; j++) + { + + /* + * Search elements [I1..J-1] for place to insert Jth element. + * + * This code stops immediatly if we can leave A[J] at J-th position + * (all elements have same value of A[J] larger than any of them) + */ + tmpr = a->ptr.p_double[j]; + tmpi = j; + for(k=j-1; k>=i1; k--) + { + if( a->ptr.p_double[k]<=tmpr ) + { + break; + } + tmpi = k; + } + k = tmpi; + + /* + * Insert Jth element into Kth position + */ + if( k!=j ) + { + tmpr = a->ptr.p_double[j]; + for(i=j-1; i>=k; i--) + { + a->ptr.p_double[i+1] = a->ptr.p_double[i]; + } + a->ptr.p_double[k] = tmpr; + } + } + return; + } + + /* + * Quicksort: choose pivot + * Here we assume that I2-I1>=16 + */ + v0 = a->ptr.p_double[i1]; + v1 = a->ptr.p_double[i1+(i2-i1)/2]; + v2 = a->ptr.p_double[i2]; + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + if( v1>v2 ) + { + tmpr = v2; + v2 = v1; + v1 = tmpr; + } + if( v0>v1 ) + { + tmpr = v1; + v1 = v0; + v0 = tmpr; + } + vp = v1; + + /* + * now pass through A/B and: + * * move elements that are LESS than VP to the left of A/B + * * move elements that are EQUAL to VP to the right of BufA/BufB (in the reverse order) + * * move elements that are GREATER than VP to the left of BufA/BufB (in the normal order + * * move elements from the tail of BufA/BufB to the middle of A/B (restoring normal order) + * * move elements from the left of BufA/BufB to the end of A/B + */ + cntless = 0; + cnteq = 0; + cntgreater = 0; + for(i=i1; i<=i2; i++) + { + v0 = a->ptr.p_double[i]; + if( v0ptr.p_double[k] = v0; + } + cntless = cntless+1; + continue; + } + if( v0==vp ) + { + + /* + * EQUAL + */ + k = i2-cnteq; + bufa->ptr.p_double[k] = v0; + cnteq = cnteq+1; + continue; + } + + /* + * GREATER + */ + k = i1+cntgreater; + bufa->ptr.p_double[k] = v0; + cntgreater = cntgreater+1; + } + for(i=0; i<=cnteq-1; i++) + { + j = i1+cntless+cnteq-1-i; + k = i2+i-(cnteq-1); + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + } + for(i=0; i<=cntgreater-1; i++) + { + j = i1+cntless+cnteq+i; + k = i1+i; + a->ptr.p_double[j] = bufa->ptr.p_double[k]; + } + + /* + * Sort left and right parts of the array (ignoring middle part) + */ + tsort_tagsortfastrec(a, bufa, i1, i1+cntless-1, _state); + tsort_tagsortfastrec(a, bufa, i1+cntless+cnteq, i2, _state); +} + + + + +/************************************************************************* +Internal ranking subroutine. + +INPUT PARAMETERS: + X - array to rank + N - array size + IsCentered- whether ranks are centered or not: + * True - ranks are centered in such way that their + sum is zero + * False - ranks are not centered + Buf - temporary buffers + +NOTE: when IsCentered is True and all X[] are equal, this function fills + X by zeros (exact zeros are used, not sum which is only approximately + equal to zero). +*************************************************************************/ +void rankx(/* Real */ ae_vector* x, + ae_int_t n, + ae_bool iscentered, + apbuffers* buf, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + double tmp; + double voffs; + + + + /* + * Prepare + */ + if( n<1 ) + { + return; + } + if( n==1 ) + { + x->ptr.p_double[0] = 0; + return; + } + if( buf->ra1.cntra1, n, _state); + } + if( buf->ia1.cntia1, n, _state); + } + for(i=0; i<=n-1; i++) + { + buf->ra1.ptr.p_double[i] = x->ptr.p_double[i]; + buf->ia1.ptr.p_int[i] = i; + } + tagsortfasti(&buf->ra1, &buf->ia1, &buf->ra2, &buf->ia2, n, _state); + + /* + * Special test for all values being equal + */ + if( ae_fp_eq(buf->ra1.ptr.p_double[0],buf->ra1.ptr.p_double[n-1]) ) + { + if( iscentered ) + { + tmp = 0.0; + } + else + { + tmp = (double)(n-1)/(double)2; + } + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = tmp; + } + return; + } + + /* + * compute tied ranks + */ + i = 0; + while(i<=n-1) + { + j = i+1; + while(j<=n-1) + { + if( ae_fp_neq(buf->ra1.ptr.p_double[j],buf->ra1.ptr.p_double[i]) ) + { + break; + } + j = j+1; + } + for(k=i; k<=j-1; k++) + { + buf->ra1.ptr.p_double[k] = (double)(i+j-1)/(double)2; + } + i = j; + } + + /* + * back to x + */ + if( iscentered ) + { + voffs = (double)(n-1)/(double)2; + } + else + { + voffs = 0.0; + } + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[buf->ia1.ptr.p_int[i]] = buf->ra1.ptr.p_double[i]-voffs; + } +} + + + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixrank1f(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_vector* u, + ae_int_t iu, + /* Complex */ ae_vector* v, + ae_int_t iv, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_cmatrixrank1f(m, n, a, ia, ja, u, iu, v, iv); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixrank1f(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_vector* u, + ae_int_t iu, + /* Real */ ae_vector* v, + ae_int_t iv, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixrank1f(m, n, a, ia, ja, u, iu, v, iv); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixmvf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Complex */ ae_vector* x, + ae_int_t ix, + /* Complex */ ae_vector* y, + ae_int_t iy, + ae_state *_state) +{ + ae_bool result; + + + result = ae_false; + return result; +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixmvf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Real */ ae_vector* x, + ae_int_t ix, + /* Real */ ae_vector* y, + ae_int_t iy, + ae_state *_state) +{ + ae_bool result; + + + result = ae_false; + return result; +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_cmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_cmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_cmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc); +#endif +} + + +/************************************************************************* +Fast kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_ABLAS + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_cmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc); +#endif +} + + +/************************************************************************* +CMatrixGEMM kernel, basecase code for CMatrixGEMM. + +This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where: +* C is MxN general matrix +* op1(A) is MxK matrix +* op2(B) is KxN matrix +* "op" may be identity transformation, transposition, conjugate transposition + +Additional info: +* multiplication result replaces C. If Beta=0, C elements are not used in + calculations (not multiplied by zero - just not referenced) +* if Alpha=0, A is not used (not multiplied by zero - just not referenced) +* if both Beta and Alpha are zero, C is filled by zeros. + +IMPORTANT: + +This function does NOT preallocate output matrix C, it MUST be preallocated +by caller prior to calling this function. In case C does not have enough +space to store result, exception will be generated. + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + OpTypeA - transformation type: + * 0 - no transformation + * 1 - transposition + * 2 - conjugate transposition + B - matrix + IB - submatrix offset + JB - submatrix offset + OpTypeB - transformation type: + * 0 - no transformation + * 1 - transposition + * 2 - conjugate transposition + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void cmatrixgemmk(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_complex v; + ae_complex v00; + ae_complex v01; + ae_complex v10; + ae_complex v11; + double v00x; + double v00y; + double v01x; + double v01y; + double v10x; + double v10y; + double v11x; + double v11y; + double a0x; + double a0y; + double a1x; + double a1y; + double b0x; + double b0y; + double b1x; + double b1y; + ae_int_t idxa0; + ae_int_t idxa1; + ae_int_t idxb0; + ae_int_t idxb1; + ae_int_t i0; + ae_int_t i1; + ae_int_t ik; + ae_int_t j0; + ae_int_t j1; + ae_int_t jk; + ae_int_t t; + ae_int_t offsa; + ae_int_t offsb; + + + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * Try optimized code + */ + if( cmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state) ) + { + return; + } + + /* + * if K=0, then C=Beta*C + */ + if( k==0 ) + { + if( ae_c_neq_d(beta,1) ) + { + if( ae_c_neq_d(beta,0) ) + { + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + c->ptr.pp_complex[ic+i][jc+j] = ae_c_mul(beta,c->ptr.pp_complex[ic+i][jc+j]); + } + } + } + else + { + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + c->ptr.pp_complex[ic+i][jc+j] = ae_complex_from_d(0); + } + } + } + } + return; + } + + /* + * This phase is not really necessary, but compiler complains + * about "possibly uninitialized variables" + */ + a0x = 0; + a0y = 0; + a1x = 0; + a1y = 0; + b0x = 0; + b0y = 0; + b1x = 0; + b1y = 0; + + /* + * General case + */ + i = 0; + while(iptr.pp_complex[idxa0][offsa].x; + a0y = a->ptr.pp_complex[idxa0][offsa].y; + a1x = a->ptr.pp_complex[idxa1][offsa].x; + a1y = a->ptr.pp_complex[idxa1][offsa].y; + } + if( optypea==1 ) + { + a0x = a->ptr.pp_complex[offsa][idxa0].x; + a0y = a->ptr.pp_complex[offsa][idxa0].y; + a1x = a->ptr.pp_complex[offsa][idxa1].x; + a1y = a->ptr.pp_complex[offsa][idxa1].y; + } + if( optypea==2 ) + { + a0x = a->ptr.pp_complex[offsa][idxa0].x; + a0y = -a->ptr.pp_complex[offsa][idxa0].y; + a1x = a->ptr.pp_complex[offsa][idxa1].x; + a1y = -a->ptr.pp_complex[offsa][idxa1].y; + } + if( optypeb==0 ) + { + b0x = b->ptr.pp_complex[offsb][idxb0].x; + b0y = b->ptr.pp_complex[offsb][idxb0].y; + b1x = b->ptr.pp_complex[offsb][idxb1].x; + b1y = b->ptr.pp_complex[offsb][idxb1].y; + } + if( optypeb==1 ) + { + b0x = b->ptr.pp_complex[idxb0][offsb].x; + b0y = b->ptr.pp_complex[idxb0][offsb].y; + b1x = b->ptr.pp_complex[idxb1][offsb].x; + b1y = b->ptr.pp_complex[idxb1][offsb].y; + } + if( optypeb==2 ) + { + b0x = b->ptr.pp_complex[idxb0][offsb].x; + b0y = -b->ptr.pp_complex[idxb0][offsb].y; + b1x = b->ptr.pp_complex[idxb1][offsb].x; + b1y = -b->ptr.pp_complex[idxb1][offsb].y; + } + v00x = v00x+a0x*b0x-a0y*b0y; + v00y = v00y+a0x*b0y+a0y*b0x; + v01x = v01x+a0x*b1x-a0y*b1y; + v01y = v01y+a0x*b1y+a0y*b1x; + v10x = v10x+a1x*b0x-a1y*b0y; + v10y = v10y+a1x*b0y+a1y*b0x; + v11x = v11x+a1x*b1x-a1y*b1y; + v11y = v11y+a1x*b1y+a1y*b1x; + offsa = offsa+1; + offsb = offsb+1; + } + v00.x = v00x; + v00.y = v00y; + v10.x = v10x; + v10.y = v10y; + v01.x = v01x; + v01.y = v01y; + v11.x = v11x; + v11.y = v11y; + if( ae_c_eq_d(beta,0) ) + { + c->ptr.pp_complex[ic+i+0][jc+j+0] = ae_c_mul(alpha,v00); + c->ptr.pp_complex[ic+i+0][jc+j+1] = ae_c_mul(alpha,v01); + c->ptr.pp_complex[ic+i+1][jc+j+0] = ae_c_mul(alpha,v10); + c->ptr.pp_complex[ic+i+1][jc+j+1] = ae_c_mul(alpha,v11); + } + else + { + c->ptr.pp_complex[ic+i+0][jc+j+0] = ae_c_add(ae_c_mul(beta,c->ptr.pp_complex[ic+i+0][jc+j+0]),ae_c_mul(alpha,v00)); + c->ptr.pp_complex[ic+i+0][jc+j+1] = ae_c_add(ae_c_mul(beta,c->ptr.pp_complex[ic+i+0][jc+j+1]),ae_c_mul(alpha,v01)); + c->ptr.pp_complex[ic+i+1][jc+j+0] = ae_c_add(ae_c_mul(beta,c->ptr.pp_complex[ic+i+1][jc+j+0]),ae_c_mul(alpha,v10)); + c->ptr.pp_complex[ic+i+1][jc+j+1] = ae_c_add(ae_c_mul(beta,c->ptr.pp_complex[ic+i+1][jc+j+1]),ae_c_mul(alpha,v11)); + } + } + else + { + + /* + * Determine submatrix [I0..I1]x[J0..J1] to process + */ + i0 = i; + i1 = ae_minint(i+1, m-1, _state); + j0 = j; + j1 = ae_minint(j+1, n-1, _state); + + /* + * Process submatrix + */ + for(ik=i0; ik<=i1; ik++) + { + for(jk=j0; jk<=j1; jk++) + { + if( k==0||ae_c_eq_d(alpha,0) ) + { + v = ae_complex_from_d(0); + } + else + { + v = ae_complex_from_d(0.0); + if( optypea==0&&optypeb==0 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia+ik][ja], 1, "N", &b->ptr.pp_complex[ib][jb+jk], b->stride, "N", ae_v_len(ja,ja+k-1)); + } + if( optypea==0&&optypeb==1 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia+ik][ja], 1, "N", &b->ptr.pp_complex[ib+jk][jb], 1, "N", ae_v_len(ja,ja+k-1)); + } + if( optypea==0&&optypeb==2 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia+ik][ja], 1, "N", &b->ptr.pp_complex[ib+jk][jb], 1, "Conj", ae_v_len(ja,ja+k-1)); + } + if( optypea==1&&optypeb==0 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "N", &b->ptr.pp_complex[ib][jb+jk], b->stride, "N", ae_v_len(ia,ia+k-1)); + } + if( optypea==1&&optypeb==1 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "N", &b->ptr.pp_complex[ib+jk][jb], 1, "N", ae_v_len(ia,ia+k-1)); + } + if( optypea==1&&optypeb==2 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "N", &b->ptr.pp_complex[ib+jk][jb], 1, "Conj", ae_v_len(ia,ia+k-1)); + } + if( optypea==2&&optypeb==0 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "Conj", &b->ptr.pp_complex[ib][jb+jk], b->stride, "N", ae_v_len(ia,ia+k-1)); + } + if( optypea==2&&optypeb==1 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "Conj", &b->ptr.pp_complex[ib+jk][jb], 1, "N", ae_v_len(ia,ia+k-1)); + } + if( optypea==2&&optypeb==2 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia][ja+ik], a->stride, "Conj", &b->ptr.pp_complex[ib+jk][jb], 1, "Conj", ae_v_len(ia,ia+k-1)); + } + } + if( ae_c_eq_d(beta,0) ) + { + c->ptr.pp_complex[ic+ik][jc+jk] = ae_c_mul(alpha,v); + } + else + { + c->ptr.pp_complex[ic+ik][jc+jk] = ae_c_add(ae_c_mul(beta,c->ptr.pp_complex[ic+ik][jc+jk]),ae_c_mul(alpha,v)); + } + } + } + } + j = j+2; + } + i = i+2; + } +} + + +/************************************************************************* +RMatrixGEMM kernel, basecase code for RMatrixGEMM. + +This subroutine calculates C = alpha*op1(A)*op2(B) +beta*C where: +* C is MxN general matrix +* op1(A) is MxK matrix +* op2(B) is KxN matrix +* "op" may be identity transformation, transposition + +Additional info: +* multiplication result replaces C. If Beta=0, C elements are not used in + calculations (not multiplied by zero - just not referenced) +* if Alpha=0, A is not used (not multiplied by zero - just not referenced) +* if both Beta and Alpha are zero, C is filled by zeros. + +IMPORTANT: + +This function does NOT preallocate output matrix C, it MUST be preallocated +by caller prior to calling this function. In case C does not have enough +space to store result, exception will be generated. + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + OpTypeA - transformation type: + * 0 - no transformation + * 1 - transposition + B - matrix + IB - submatrix offset + JB - submatrix offset + OpTypeB - transformation type: + * 0 - no transformation + * 1 - transposition + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void rmatrixgemmk(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * Try optimized code + */ + if( rmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state) ) + { + return; + } + + /* + * if K=0, then C=Beta*C + */ + if( k==0||ae_fp_eq(alpha,0) ) + { + if( ae_fp_neq(beta,1) ) + { + if( ae_fp_neq(beta,0) ) + { + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + c->ptr.pp_double[ic+i][jc+j] = beta*c->ptr.pp_double[ic+i][jc+j]; + } + } + } + else + { + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + c->ptr.pp_double[ic+i][jc+j] = 0; + } + } + } + } + return; + } + + /* + * Call specialized code. + * + * NOTE: specialized code was moved to separate function because of strange + * issues with instructions cache on some systems; Having too long + * functions significantly slows down internal loop of the algorithm. + */ + if( optypea==0&&optypeb==0 ) + { + rmatrixgemmk44v00(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _state); + } + if( optypea==0&&optypeb!=0 ) + { + rmatrixgemmk44v01(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _state); + } + if( optypea!=0&&optypeb==0 ) + { + rmatrixgemmk44v10(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _state); + } + if( optypea!=0&&optypeb!=0 ) + { + rmatrixgemmk44v11(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _state); + } +} + + +/************************************************************************* +RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation +with OpTypeA=0 and OpTypeB=0. + +Additional info: +* this function requires that Alpha<>0 (assertion is thrown otherwise) + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + B - matrix + IB - submatrix offset + JB - submatrix offset + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void rmatrixgemmk44v00(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + double v00; + double v01; + double v02; + double v03; + double v10; + double v11; + double v12; + double v13; + double v20; + double v21; + double v22; + double v23; + double v30; + double v31; + double v32; + double v33; + double a0; + double a1; + double a2; + double a3; + double b0; + double b1; + double b2; + double b3; + ae_int_t idxa0; + ae_int_t idxa1; + ae_int_t idxa2; + ae_int_t idxa3; + ae_int_t idxb0; + ae_int_t idxb1; + ae_int_t idxb2; + ae_int_t idxb3; + ae_int_t i0; + ae_int_t i1; + ae_int_t ik; + ae_int_t j0; + ae_int_t j1; + ae_int_t jk; + ae_int_t t; + ae_int_t offsa; + ae_int_t offsb; + + + ae_assert(ae_fp_neq(alpha,0), "RMatrixGEMMK44V00: internal error (Alpha=0)", _state); + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * A*B + */ + i = 0; + while(iptr.pp_double[idxa0][offsa]; + a1 = a->ptr.pp_double[idxa1][offsa]; + b0 = b->ptr.pp_double[offsb][idxb0]; + b1 = b->ptr.pp_double[offsb][idxb1]; + v00 = v00+a0*b0; + v01 = v01+a0*b1; + v10 = v10+a1*b0; + v11 = v11+a1*b1; + a2 = a->ptr.pp_double[idxa2][offsa]; + a3 = a->ptr.pp_double[idxa3][offsa]; + v20 = v20+a2*b0; + v21 = v21+a2*b1; + v30 = v30+a3*b0; + v31 = v31+a3*b1; + b2 = b->ptr.pp_double[offsb][idxb2]; + b3 = b->ptr.pp_double[offsb][idxb3]; + v22 = v22+a2*b2; + v23 = v23+a2*b3; + v32 = v32+a3*b2; + v33 = v33+a3*b3; + v02 = v02+a0*b2; + v03 = v03+a0*b3; + v12 = v12+a1*b2; + v13 = v13+a1*b3; + offsa = offsa+1; + offsb = offsb+1; + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+i+0][jc+j+0] = alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = alpha*v33; + } + else + { + c->ptr.pp_double[ic+i+0][jc+j+0] = beta*c->ptr.pp_double[ic+i+0][jc+j+0]+alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = beta*c->ptr.pp_double[ic+i+0][jc+j+1]+alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = beta*c->ptr.pp_double[ic+i+0][jc+j+2]+alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = beta*c->ptr.pp_double[ic+i+0][jc+j+3]+alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = beta*c->ptr.pp_double[ic+i+1][jc+j+0]+alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = beta*c->ptr.pp_double[ic+i+1][jc+j+1]+alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = beta*c->ptr.pp_double[ic+i+1][jc+j+2]+alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = beta*c->ptr.pp_double[ic+i+1][jc+j+3]+alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = beta*c->ptr.pp_double[ic+i+2][jc+j+0]+alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = beta*c->ptr.pp_double[ic+i+2][jc+j+1]+alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = beta*c->ptr.pp_double[ic+i+2][jc+j+2]+alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = beta*c->ptr.pp_double[ic+i+2][jc+j+3]+alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = beta*c->ptr.pp_double[ic+i+3][jc+j+0]+alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = beta*c->ptr.pp_double[ic+i+3][jc+j+1]+alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = beta*c->ptr.pp_double[ic+i+3][jc+j+2]+alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = beta*c->ptr.pp_double[ic+i+3][jc+j+3]+alpha*v33; + } + } + else + { + + /* + * Determine submatrix [I0..I1]x[J0..J1] to process + */ + i0 = i; + i1 = ae_minint(i+3, m-1, _state); + j0 = j; + j1 = ae_minint(j+3, n-1, _state); + + /* + * Process submatrix + */ + for(ik=i0; ik<=i1; ik++) + { + for(jk=j0; jk<=j1; jk++) + { + if( k==0||ae_fp_eq(alpha,0) ) + { + v = 0; + } + else + { + v = ae_v_dotproduct(&a->ptr.pp_double[ia+ik][ja], 1, &b->ptr.pp_double[ib][jb+jk], b->stride, ae_v_len(ja,ja+k-1)); + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+ik][jc+jk] = alpha*v; + } + else + { + c->ptr.pp_double[ic+ik][jc+jk] = beta*c->ptr.pp_double[ic+ik][jc+jk]+alpha*v; + } + } + } + } + j = j+4; + } + i = i+4; + } +} + + +/************************************************************************* +RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation +with OpTypeA=0 and OpTypeB=1. + +Additional info: +* this function requires that Alpha<>0 (assertion is thrown otherwise) + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + B - matrix + IB - submatrix offset + JB - submatrix offset + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void rmatrixgemmk44v01(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + double v00; + double v01; + double v02; + double v03; + double v10; + double v11; + double v12; + double v13; + double v20; + double v21; + double v22; + double v23; + double v30; + double v31; + double v32; + double v33; + double a0; + double a1; + double a2; + double a3; + double b0; + double b1; + double b2; + double b3; + ae_int_t idxa0; + ae_int_t idxa1; + ae_int_t idxa2; + ae_int_t idxa3; + ae_int_t idxb0; + ae_int_t idxb1; + ae_int_t idxb2; + ae_int_t idxb3; + ae_int_t i0; + ae_int_t i1; + ae_int_t ik; + ae_int_t j0; + ae_int_t j1; + ae_int_t jk; + ae_int_t t; + ae_int_t offsa; + ae_int_t offsb; + + + ae_assert(ae_fp_neq(alpha,0), "RMatrixGEMMK44V00: internal error (Alpha=0)", _state); + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * A*B' + */ + i = 0; + while(iptr.pp_double[idxa0][offsa]; + a1 = a->ptr.pp_double[idxa1][offsa]; + b0 = b->ptr.pp_double[idxb0][offsb]; + b1 = b->ptr.pp_double[idxb1][offsb]; + v00 = v00+a0*b0; + v01 = v01+a0*b1; + v10 = v10+a1*b0; + v11 = v11+a1*b1; + a2 = a->ptr.pp_double[idxa2][offsa]; + a3 = a->ptr.pp_double[idxa3][offsa]; + v20 = v20+a2*b0; + v21 = v21+a2*b1; + v30 = v30+a3*b0; + v31 = v31+a3*b1; + b2 = b->ptr.pp_double[idxb2][offsb]; + b3 = b->ptr.pp_double[idxb3][offsb]; + v22 = v22+a2*b2; + v23 = v23+a2*b3; + v32 = v32+a3*b2; + v33 = v33+a3*b3; + v02 = v02+a0*b2; + v03 = v03+a0*b3; + v12 = v12+a1*b2; + v13 = v13+a1*b3; + offsa = offsa+1; + offsb = offsb+1; + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+i+0][jc+j+0] = alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = alpha*v33; + } + else + { + c->ptr.pp_double[ic+i+0][jc+j+0] = beta*c->ptr.pp_double[ic+i+0][jc+j+0]+alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = beta*c->ptr.pp_double[ic+i+0][jc+j+1]+alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = beta*c->ptr.pp_double[ic+i+0][jc+j+2]+alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = beta*c->ptr.pp_double[ic+i+0][jc+j+3]+alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = beta*c->ptr.pp_double[ic+i+1][jc+j+0]+alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = beta*c->ptr.pp_double[ic+i+1][jc+j+1]+alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = beta*c->ptr.pp_double[ic+i+1][jc+j+2]+alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = beta*c->ptr.pp_double[ic+i+1][jc+j+3]+alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = beta*c->ptr.pp_double[ic+i+2][jc+j+0]+alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = beta*c->ptr.pp_double[ic+i+2][jc+j+1]+alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = beta*c->ptr.pp_double[ic+i+2][jc+j+2]+alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = beta*c->ptr.pp_double[ic+i+2][jc+j+3]+alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = beta*c->ptr.pp_double[ic+i+3][jc+j+0]+alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = beta*c->ptr.pp_double[ic+i+3][jc+j+1]+alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = beta*c->ptr.pp_double[ic+i+3][jc+j+2]+alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = beta*c->ptr.pp_double[ic+i+3][jc+j+3]+alpha*v33; + } + } + else + { + + /* + * Determine submatrix [I0..I1]x[J0..J1] to process + */ + i0 = i; + i1 = ae_minint(i+3, m-1, _state); + j0 = j; + j1 = ae_minint(j+3, n-1, _state); + + /* + * Process submatrix + */ + for(ik=i0; ik<=i1; ik++) + { + for(jk=j0; jk<=j1; jk++) + { + if( k==0||ae_fp_eq(alpha,0) ) + { + v = 0; + } + else + { + v = ae_v_dotproduct(&a->ptr.pp_double[ia+ik][ja], 1, &b->ptr.pp_double[ib+jk][jb], 1, ae_v_len(ja,ja+k-1)); + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+ik][jc+jk] = alpha*v; + } + else + { + c->ptr.pp_double[ic+ik][jc+jk] = beta*c->ptr.pp_double[ic+ik][jc+jk]+alpha*v; + } + } + } + } + j = j+4; + } + i = i+4; + } +} + + +/************************************************************************* +RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation +with OpTypeA=1 and OpTypeB=0. + +Additional info: +* this function requires that Alpha<>0 (assertion is thrown otherwise) + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + B - matrix + IB - submatrix offset + JB - submatrix offset + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void rmatrixgemmk44v10(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + double v00; + double v01; + double v02; + double v03; + double v10; + double v11; + double v12; + double v13; + double v20; + double v21; + double v22; + double v23; + double v30; + double v31; + double v32; + double v33; + double a0; + double a1; + double a2; + double a3; + double b0; + double b1; + double b2; + double b3; + ae_int_t idxa0; + ae_int_t idxa1; + ae_int_t idxa2; + ae_int_t idxa3; + ae_int_t idxb0; + ae_int_t idxb1; + ae_int_t idxb2; + ae_int_t idxb3; + ae_int_t i0; + ae_int_t i1; + ae_int_t ik; + ae_int_t j0; + ae_int_t j1; + ae_int_t jk; + ae_int_t t; + ae_int_t offsa; + ae_int_t offsb; + + + ae_assert(ae_fp_neq(alpha,0), "RMatrixGEMMK44V00: internal error (Alpha=0)", _state); + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * A'*B + */ + i = 0; + while(iptr.pp_double[offsa][idxa0]; + a1 = a->ptr.pp_double[offsa][idxa1]; + b0 = b->ptr.pp_double[offsb][idxb0]; + b1 = b->ptr.pp_double[offsb][idxb1]; + v00 = v00+a0*b0; + v01 = v01+a0*b1; + v10 = v10+a1*b0; + v11 = v11+a1*b1; + a2 = a->ptr.pp_double[offsa][idxa2]; + a3 = a->ptr.pp_double[offsa][idxa3]; + v20 = v20+a2*b0; + v21 = v21+a2*b1; + v30 = v30+a3*b0; + v31 = v31+a3*b1; + b2 = b->ptr.pp_double[offsb][idxb2]; + b3 = b->ptr.pp_double[offsb][idxb3]; + v22 = v22+a2*b2; + v23 = v23+a2*b3; + v32 = v32+a3*b2; + v33 = v33+a3*b3; + v02 = v02+a0*b2; + v03 = v03+a0*b3; + v12 = v12+a1*b2; + v13 = v13+a1*b3; + offsa = offsa+1; + offsb = offsb+1; + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+i+0][jc+j+0] = alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = alpha*v33; + } + else + { + c->ptr.pp_double[ic+i+0][jc+j+0] = beta*c->ptr.pp_double[ic+i+0][jc+j+0]+alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = beta*c->ptr.pp_double[ic+i+0][jc+j+1]+alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = beta*c->ptr.pp_double[ic+i+0][jc+j+2]+alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = beta*c->ptr.pp_double[ic+i+0][jc+j+3]+alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = beta*c->ptr.pp_double[ic+i+1][jc+j+0]+alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = beta*c->ptr.pp_double[ic+i+1][jc+j+1]+alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = beta*c->ptr.pp_double[ic+i+1][jc+j+2]+alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = beta*c->ptr.pp_double[ic+i+1][jc+j+3]+alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = beta*c->ptr.pp_double[ic+i+2][jc+j+0]+alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = beta*c->ptr.pp_double[ic+i+2][jc+j+1]+alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = beta*c->ptr.pp_double[ic+i+2][jc+j+2]+alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = beta*c->ptr.pp_double[ic+i+2][jc+j+3]+alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = beta*c->ptr.pp_double[ic+i+3][jc+j+0]+alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = beta*c->ptr.pp_double[ic+i+3][jc+j+1]+alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = beta*c->ptr.pp_double[ic+i+3][jc+j+2]+alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = beta*c->ptr.pp_double[ic+i+3][jc+j+3]+alpha*v33; + } + } + else + { + + /* + * Determine submatrix [I0..I1]x[J0..J1] to process + */ + i0 = i; + i1 = ae_minint(i+3, m-1, _state); + j0 = j; + j1 = ae_minint(j+3, n-1, _state); + + /* + * Process submatrix + */ + for(ik=i0; ik<=i1; ik++) + { + for(jk=j0; jk<=j1; jk++) + { + if( k==0||ae_fp_eq(alpha,0) ) + { + v = 0; + } + else + { + v = 0.0; + v = ae_v_dotproduct(&a->ptr.pp_double[ia][ja+ik], a->stride, &b->ptr.pp_double[ib][jb+jk], b->stride, ae_v_len(ia,ia+k-1)); + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+ik][jc+jk] = alpha*v; + } + else + { + c->ptr.pp_double[ic+ik][jc+jk] = beta*c->ptr.pp_double[ic+ik][jc+jk]+alpha*v; + } + } + } + } + j = j+4; + } + i = i+4; + } +} + + +/************************************************************************* +RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation +with OpTypeA=1 and OpTypeB=1. + +Additional info: +* this function requires that Alpha<>0 (assertion is thrown otherwise) + +INPUT PARAMETERS + M - matrix size, M>0 + N - matrix size, N>0 + K - matrix size, K>0 + Alpha - coefficient + A - matrix + IA - submatrix offset + JA - submatrix offset + B - matrix + IB - submatrix offset + JB - submatrix offset + Beta - coefficient + C - PREALLOCATED output matrix + IC - submatrix offset + JC - submatrix offset + + -- ALGLIB routine -- + 27.03.2013 + Bochkanov Sergey +*************************************************************************/ +void rmatrixgemmk44v11(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + double v00; + double v01; + double v02; + double v03; + double v10; + double v11; + double v12; + double v13; + double v20; + double v21; + double v22; + double v23; + double v30; + double v31; + double v32; + double v33; + double a0; + double a1; + double a2; + double a3; + double b0; + double b1; + double b2; + double b3; + ae_int_t idxa0; + ae_int_t idxa1; + ae_int_t idxa2; + ae_int_t idxa3; + ae_int_t idxb0; + ae_int_t idxb1; + ae_int_t idxb2; + ae_int_t idxb3; + ae_int_t i0; + ae_int_t i1; + ae_int_t ik; + ae_int_t j0; + ae_int_t j1; + ae_int_t jk; + ae_int_t t; + ae_int_t offsa; + ae_int_t offsb; + + + ae_assert(ae_fp_neq(alpha,0), "RMatrixGEMMK44V00: internal error (Alpha=0)", _state); + + /* + * if matrix size is zero + */ + if( m==0||n==0 ) + { + return; + } + + /* + * A'*B' + */ + i = 0; + while(iptr.pp_double[offsa][idxa0]; + a1 = a->ptr.pp_double[offsa][idxa1]; + b0 = b->ptr.pp_double[idxb0][offsb]; + b1 = b->ptr.pp_double[idxb1][offsb]; + v00 = v00+a0*b0; + v01 = v01+a0*b1; + v10 = v10+a1*b0; + v11 = v11+a1*b1; + a2 = a->ptr.pp_double[offsa][idxa2]; + a3 = a->ptr.pp_double[offsa][idxa3]; + v20 = v20+a2*b0; + v21 = v21+a2*b1; + v30 = v30+a3*b0; + v31 = v31+a3*b1; + b2 = b->ptr.pp_double[idxb2][offsb]; + b3 = b->ptr.pp_double[idxb3][offsb]; + v22 = v22+a2*b2; + v23 = v23+a2*b3; + v32 = v32+a3*b2; + v33 = v33+a3*b3; + v02 = v02+a0*b2; + v03 = v03+a0*b3; + v12 = v12+a1*b2; + v13 = v13+a1*b3; + offsa = offsa+1; + offsb = offsb+1; + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+i+0][jc+j+0] = alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = alpha*v33; + } + else + { + c->ptr.pp_double[ic+i+0][jc+j+0] = beta*c->ptr.pp_double[ic+i+0][jc+j+0]+alpha*v00; + c->ptr.pp_double[ic+i+0][jc+j+1] = beta*c->ptr.pp_double[ic+i+0][jc+j+1]+alpha*v01; + c->ptr.pp_double[ic+i+0][jc+j+2] = beta*c->ptr.pp_double[ic+i+0][jc+j+2]+alpha*v02; + c->ptr.pp_double[ic+i+0][jc+j+3] = beta*c->ptr.pp_double[ic+i+0][jc+j+3]+alpha*v03; + c->ptr.pp_double[ic+i+1][jc+j+0] = beta*c->ptr.pp_double[ic+i+1][jc+j+0]+alpha*v10; + c->ptr.pp_double[ic+i+1][jc+j+1] = beta*c->ptr.pp_double[ic+i+1][jc+j+1]+alpha*v11; + c->ptr.pp_double[ic+i+1][jc+j+2] = beta*c->ptr.pp_double[ic+i+1][jc+j+2]+alpha*v12; + c->ptr.pp_double[ic+i+1][jc+j+3] = beta*c->ptr.pp_double[ic+i+1][jc+j+3]+alpha*v13; + c->ptr.pp_double[ic+i+2][jc+j+0] = beta*c->ptr.pp_double[ic+i+2][jc+j+0]+alpha*v20; + c->ptr.pp_double[ic+i+2][jc+j+1] = beta*c->ptr.pp_double[ic+i+2][jc+j+1]+alpha*v21; + c->ptr.pp_double[ic+i+2][jc+j+2] = beta*c->ptr.pp_double[ic+i+2][jc+j+2]+alpha*v22; + c->ptr.pp_double[ic+i+2][jc+j+3] = beta*c->ptr.pp_double[ic+i+2][jc+j+3]+alpha*v23; + c->ptr.pp_double[ic+i+3][jc+j+0] = beta*c->ptr.pp_double[ic+i+3][jc+j+0]+alpha*v30; + c->ptr.pp_double[ic+i+3][jc+j+1] = beta*c->ptr.pp_double[ic+i+3][jc+j+1]+alpha*v31; + c->ptr.pp_double[ic+i+3][jc+j+2] = beta*c->ptr.pp_double[ic+i+3][jc+j+2]+alpha*v32; + c->ptr.pp_double[ic+i+3][jc+j+3] = beta*c->ptr.pp_double[ic+i+3][jc+j+3]+alpha*v33; + } + } + else + { + + /* + * Determine submatrix [I0..I1]x[J0..J1] to process + */ + i0 = i; + i1 = ae_minint(i+3, m-1, _state); + j0 = j; + j1 = ae_minint(j+3, n-1, _state); + + /* + * Process submatrix + */ + for(ik=i0; ik<=i1; ik++) + { + for(jk=j0; jk<=j1; jk++) + { + if( k==0||ae_fp_eq(alpha,0) ) + { + v = 0; + } + else + { + v = 0.0; + v = ae_v_dotproduct(&a->ptr.pp_double[ia][ja+ik], a->stride, &b->ptr.pp_double[ib+jk][jb], 1, ae_v_len(ia,ia+k-1)); + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+ik][jc+jk] = alpha*v; + } + else + { + c->ptr.pp_double[ic+ik][jc+jk] = beta*c->ptr.pp_double[ic+ik][jc+jk]+alpha*v; + } + } + } + } + j = j+4; + } + i = i+4; + } +} + + + + +/************************************************************************* +MKL-based kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixsyrkmkl(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_MKL + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixsyrkmkl(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper); +#endif +} + + +/************************************************************************* +MKL-based kernel + + -- ALGLIB routine -- + 19.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixgemmmkl(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_MKL + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_rmatrixgemmmkl(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc); +#endif +} + + + + +double vectornorm2(/* Real */ ae_vector* x, + ae_int_t i1, + ae_int_t i2, + ae_state *_state) +{ + ae_int_t n; + ae_int_t ix; + double absxi; + double scl; + double ssq; + double result; + + + n = i2-i1+1; + if( n<1 ) + { + result = 0; + return result; + } + if( n==1 ) + { + result = ae_fabs(x->ptr.p_double[i1], _state); + return result; + } + scl = 0; + ssq = 1; + for(ix=i1; ix<=i2; ix++) + { + if( ae_fp_neq(x->ptr.p_double[ix],0) ) + { + absxi = ae_fabs(x->ptr.p_double[ix], _state); + if( ae_fp_less(scl,absxi) ) + { + ssq = 1+ssq*ae_sqr(scl/absxi, _state); + scl = absxi; + } + else + { + ssq = ssq+ae_sqr(absxi/scl, _state); + } + } + } + result = scl*ae_sqrt(ssq, _state); + return result; +} + + +ae_int_t vectoridxabsmax(/* Real */ ae_vector* x, + ae_int_t i1, + ae_int_t i2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t result; + + + result = i1; + for(i=i1+1; i<=i2; i++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[i], _state),ae_fabs(x->ptr.p_double[result], _state)) ) + { + result = i; + } + } + return result; +} + + +ae_int_t columnidxabsmax(/* Real */ ae_matrix* x, + ae_int_t i1, + ae_int_t i2, + ae_int_t j, + ae_state *_state) +{ + ae_int_t i; + ae_int_t result; + + + result = i1; + for(i=i1+1; i<=i2; i++) + { + if( ae_fp_greater(ae_fabs(x->ptr.pp_double[i][j], _state),ae_fabs(x->ptr.pp_double[result][j], _state)) ) + { + result = i; + } + } + return result; +} + + +ae_int_t rowidxabsmax(/* Real */ ae_matrix* x, + ae_int_t j1, + ae_int_t j2, + ae_int_t i, + ae_state *_state) +{ + ae_int_t j; + ae_int_t result; + + + result = j1; + for(j=j1+1; j<=j2; j++) + { + if( ae_fp_greater(ae_fabs(x->ptr.pp_double[i][j], _state),ae_fabs(x->ptr.pp_double[i][result], _state)) ) + { + result = j; + } + } + return result; +} + + +double upperhessenberg1norm(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double result; + + + ae_assert(i2-i1==j2-j1, "UpperHessenberg1Norm: I2-I1<>J2-J1!", _state); + for(j=j1; j<=j2; j++) + { + work->ptr.p_double[j] = 0; + } + for(i=i1; i<=i2; i++) + { + for(j=ae_maxint(j1, j1+i-i1-1, _state); j<=j2; j++) + { + work->ptr.p_double[j] = work->ptr.p_double[j]+ae_fabs(a->ptr.pp_double[i][j], _state); + } + } + result = 0; + for(j=j1; j<=j2; j++) + { + result = ae_maxreal(result, work->ptr.p_double[j], _state); + } + return result; +} + + +void copymatrix(/* Real */ ae_matrix* a, + ae_int_t is1, + ae_int_t is2, + ae_int_t js1, + ae_int_t js2, + /* Real */ ae_matrix* b, + ae_int_t id1, + ae_int_t id2, + ae_int_t jd1, + ae_int_t jd2, + ae_state *_state) +{ + ae_int_t isrc; + ae_int_t idst; + + + if( is1>is2||js1>js2 ) + { + return; + } + ae_assert(is2-is1==id2-id1, "CopyMatrix: different sizes!", _state); + ae_assert(js2-js1==jd2-jd1, "CopyMatrix: different sizes!", _state); + for(isrc=is1; isrc<=is2; isrc++) + { + idst = isrc-is1+id1; + ae_v_move(&b->ptr.pp_double[idst][jd1], 1, &a->ptr.pp_double[isrc][js1], 1, ae_v_len(jd1,jd2)); + } +} + + +void inplacetranspose(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t ips; + ae_int_t jps; + ae_int_t l; + + + if( i1>i2||j1>j2 ) + { + return; + } + ae_assert(i1-i2==j1-j2, "InplaceTranspose error: incorrect array size!", _state); + for(i=i1; i<=i2-1; i++) + { + j = j1+i-i1; + ips = i+1; + jps = j1+ips-i1; + l = i2-i; + ae_v_move(&work->ptr.p_double[1], 1, &a->ptr.pp_double[ips][j], a->stride, ae_v_len(1,l)); + ae_v_move(&a->ptr.pp_double[ips][j], a->stride, &a->ptr.pp_double[i][jps], 1, ae_v_len(ips,i2)); + ae_v_move(&a->ptr.pp_double[i][jps], 1, &work->ptr.p_double[1], 1, ae_v_len(jps,j2)); + } +} + + +void copyandtranspose(/* Real */ ae_matrix* a, + ae_int_t is1, + ae_int_t is2, + ae_int_t js1, + ae_int_t js2, + /* Real */ ae_matrix* b, + ae_int_t id1, + ae_int_t id2, + ae_int_t jd1, + ae_int_t jd2, + ae_state *_state) +{ + ae_int_t isrc; + ae_int_t jdst; + + + if( is1>is2||js1>js2 ) + { + return; + } + ae_assert(is2-is1==jd2-jd1, "CopyAndTranspose: different sizes!", _state); + ae_assert(js2-js1==id2-id1, "CopyAndTranspose: different sizes!", _state); + for(isrc=is1; isrc<=is2; isrc++) + { + jdst = isrc-is1+jd1; + ae_v_move(&b->ptr.pp_double[id1][jdst], b->stride, &a->ptr.pp_double[isrc][js1], 1, ae_v_len(id1,id2)); + } +} + + +void matrixvectormultiply(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + ae_bool trans, + /* Real */ ae_vector* x, + ae_int_t ix1, + ae_int_t ix2, + double alpha, + /* Real */ ae_vector* y, + ae_int_t iy1, + ae_int_t iy2, + double beta, + ae_state *_state) +{ + ae_int_t i; + double v; + + + if( !trans ) + { + + /* + * y := alpha*A*x + beta*y; + */ + if( i1>i2||j1>j2 ) + { + return; + } + ae_assert(j2-j1==ix2-ix1, "MatrixVectorMultiply: A and X dont match!", _state); + ae_assert(i2-i1==iy2-iy1, "MatrixVectorMultiply: A and Y dont match!", _state); + + /* + * beta*y + */ + if( ae_fp_eq(beta,0) ) + { + for(i=iy1; i<=iy2; i++) + { + y->ptr.p_double[i] = 0; + } + } + else + { + ae_v_muld(&y->ptr.p_double[iy1], 1, ae_v_len(iy1,iy2), beta); + } + + /* + * alpha*A*x + */ + for(i=i1; i<=i2; i++) + { + v = ae_v_dotproduct(&a->ptr.pp_double[i][j1], 1, &x->ptr.p_double[ix1], 1, ae_v_len(j1,j2)); + y->ptr.p_double[iy1+i-i1] = y->ptr.p_double[iy1+i-i1]+alpha*v; + } + } + else + { + + /* + * y := alpha*A'*x + beta*y; + */ + if( i1>i2||j1>j2 ) + { + return; + } + ae_assert(i2-i1==ix2-ix1, "MatrixVectorMultiply: A and X dont match!", _state); + ae_assert(j2-j1==iy2-iy1, "MatrixVectorMultiply: A and Y dont match!", _state); + + /* + * beta*y + */ + if( ae_fp_eq(beta,0) ) + { + for(i=iy1; i<=iy2; i++) + { + y->ptr.p_double[i] = 0; + } + } + else + { + ae_v_muld(&y->ptr.p_double[iy1], 1, ae_v_len(iy1,iy2), beta); + } + + /* + * alpha*A'*x + */ + for(i=i1; i<=i2; i++) + { + v = alpha*x->ptr.p_double[ix1+i-i1]; + ae_v_addd(&y->ptr.p_double[iy1], 1, &a->ptr.pp_double[i][j1], 1, ae_v_len(iy1,iy2), v); + } + } +} + + +double pythag2(double x, double y, ae_state *_state) +{ + double w; + double xabs; + double yabs; + double z; + double result; + + + xabs = ae_fabs(x, _state); + yabs = ae_fabs(y, _state); + w = ae_maxreal(xabs, yabs, _state); + z = ae_minreal(xabs, yabs, _state); + if( ae_fp_eq(z,0) ) + { + result = w; + } + else + { + result = w*ae_sqrt(1+ae_sqr(z/w, _state), _state); + } + return result; +} + + +void matrixmatrixmultiply(/* Real */ ae_matrix* a, + ae_int_t ai1, + ae_int_t ai2, + ae_int_t aj1, + ae_int_t aj2, + ae_bool transa, + /* Real */ ae_matrix* b, + ae_int_t bi1, + ae_int_t bi2, + ae_int_t bj1, + ae_int_t bj2, + ae_bool transb, + double alpha, + /* Real */ ae_matrix* c, + ae_int_t ci1, + ae_int_t ci2, + ae_int_t cj1, + ae_int_t cj2, + double beta, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t arows; + ae_int_t acols; + ae_int_t brows; + ae_int_t bcols; + ae_int_t crows; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t l; + ae_int_t r; + double v; + + + + /* + * Setup + */ + if( !transa ) + { + arows = ai2-ai1+1; + acols = aj2-aj1+1; + } + else + { + arows = aj2-aj1+1; + acols = ai2-ai1+1; + } + if( !transb ) + { + brows = bi2-bi1+1; + bcols = bj2-bj1+1; + } + else + { + brows = bj2-bj1+1; + bcols = bi2-bi1+1; + } + ae_assert(acols==brows, "MatrixMatrixMultiply: incorrect matrix sizes!", _state); + if( ((arows<=0||acols<=0)||brows<=0)||bcols<=0 ) + { + return; + } + crows = arows; + + /* + * Test WORK + */ + i = ae_maxint(arows, acols, _state); + i = ae_maxint(brows, i, _state); + i = ae_maxint(i, bcols, _state); + work->ptr.p_double[1] = 0; + work->ptr.p_double[i] = 0; + + /* + * Prepare C + */ + if( ae_fp_eq(beta,0) ) + { + for(i=ci1; i<=ci2; i++) + { + for(j=cj1; j<=cj2; j++) + { + c->ptr.pp_double[i][j] = 0; + } + } + } + else + { + for(i=ci1; i<=ci2; i++) + { + ae_v_muld(&c->ptr.pp_double[i][cj1], 1, ae_v_len(cj1,cj2), beta); + } + } + + /* + * A*B + */ + if( !transa&&!transb ) + { + for(l=ai1; l<=ai2; l++) + { + for(r=bi1; r<=bi2; r++) + { + v = alpha*a->ptr.pp_double[l][aj1+r-bi1]; + k = ci1+l-ai1; + ae_v_addd(&c->ptr.pp_double[k][cj1], 1, &b->ptr.pp_double[r][bj1], 1, ae_v_len(cj1,cj2), v); + } + } + return; + } + + /* + * A*B' + */ + if( !transa&&transb ) + { + if( arows*acolsptr.pp_double[l][aj1], 1, &b->ptr.pp_double[r][bj1], 1, ae_v_len(aj1,aj2)); + c->ptr.pp_double[ci1+l-ai1][cj1+r-bi1] = c->ptr.pp_double[ci1+l-ai1][cj1+r-bi1]+alpha*v; + } + } + return; + } + else + { + for(l=ai1; l<=ai2; l++) + { + for(r=bi1; r<=bi2; r++) + { + v = ae_v_dotproduct(&a->ptr.pp_double[l][aj1], 1, &b->ptr.pp_double[r][bj1], 1, ae_v_len(aj1,aj2)); + c->ptr.pp_double[ci1+l-ai1][cj1+r-bi1] = c->ptr.pp_double[ci1+l-ai1][cj1+r-bi1]+alpha*v; + } + } + return; + } + } + + /* + * A'*B + */ + if( transa&&!transb ) + { + for(l=aj1; l<=aj2; l++) + { + for(r=bi1; r<=bi2; r++) + { + v = alpha*a->ptr.pp_double[ai1+r-bi1][l]; + k = ci1+l-aj1; + ae_v_addd(&c->ptr.pp_double[k][cj1], 1, &b->ptr.pp_double[r][bj1], 1, ae_v_len(cj1,cj2), v); + } + } + return; + } + + /* + * A'*B' + */ + if( transa&&transb ) + { + if( arows*acolsptr.p_double[i] = 0.0; + } + for(l=ai1; l<=ai2; l++) + { + v = alpha*b->ptr.pp_double[r][bj1+l-ai1]; + ae_v_addd(&work->ptr.p_double[1], 1, &a->ptr.pp_double[l][aj1], 1, ae_v_len(1,crows), v); + } + ae_v_add(&c->ptr.pp_double[ci1][k], c->stride, &work->ptr.p_double[1], 1, ae_v_len(ci1,ci2)); + } + return; + } + else + { + for(l=aj1; l<=aj2; l++) + { + k = ai2-ai1+1; + ae_v_move(&work->ptr.p_double[1], 1, &a->ptr.pp_double[ai1][l], a->stride, ae_v_len(1,k)); + for(r=bi1; r<=bi2; r++) + { + v = ae_v_dotproduct(&work->ptr.p_double[1], 1, &b->ptr.pp_double[r][bj1], 1, ae_v_len(1,k)); + c->ptr.pp_double[ci1+l-aj1][cj1+r-bi1] = c->ptr.pp_double[ci1+l-aj1][cj1+r-bi1]+alpha*v; + } + } + return; + } + } +} + + + + +void hermitianmatrixvectormultiply(/* Complex */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Complex */ ae_vector* x, + ae_complex alpha, + /* Complex */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ba1; + ae_int_t by1; + ae_int_t by2; + ae_int_t bx1; + ae_int_t bx2; + ae_int_t n; + ae_complex v; + + + n = i2-i1+1; + if( n<=0 ) + { + return; + } + + /* + * Let A = L + D + U, where + * L is strictly lower triangular (main diagonal is zero) + * D is diagonal + * U is strictly upper triangular (main diagonal is zero) + * + * A*x = L*x + D*x + U*x + * + * Calculate D*x first + */ + for(i=i1; i<=i2; i++) + { + y->ptr.p_complex[i-i1+1] = ae_c_mul(a->ptr.pp_complex[i][i],x->ptr.p_complex[i-i1+1]); + } + + /* + * Add L*x + U*x + */ + if( isupper ) + { + for(i=i1; i<=i2-1; i++) + { + + /* + * Add L*x to the result + */ + v = x->ptr.p_complex[i-i1+1]; + by1 = i-i1+2; + by2 = n; + ba1 = i+1; + ae_v_caddc(&y->ptr.p_complex[by1], 1, &a->ptr.pp_complex[i][ba1], 1, "Conj", ae_v_len(by1,by2), v); + + /* + * Add U*x to the result + */ + bx1 = i-i1+2; + bx2 = n; + ba1 = i+1; + v = ae_v_cdotproduct(&x->ptr.p_complex[bx1], 1, "N", &a->ptr.pp_complex[i][ba1], 1, "N", ae_v_len(bx1,bx2)); + y->ptr.p_complex[i-i1+1] = ae_c_add(y->ptr.p_complex[i-i1+1],v); + } + } + else + { + for(i=i1+1; i<=i2; i++) + { + + /* + * Add L*x to the result + */ + bx1 = 1; + bx2 = i-i1; + ba1 = i1; + v = ae_v_cdotproduct(&x->ptr.p_complex[bx1], 1, "N", &a->ptr.pp_complex[i][ba1], 1, "N", ae_v_len(bx1,bx2)); + y->ptr.p_complex[i-i1+1] = ae_c_add(y->ptr.p_complex[i-i1+1],v); + + /* + * Add U*x to the result + */ + v = x->ptr.p_complex[i-i1+1]; + by1 = 1; + by2 = i-i1; + ba1 = i1; + ae_v_caddc(&y->ptr.p_complex[by1], 1, &a->ptr.pp_complex[i][ba1], 1, "Conj", ae_v_len(by1,by2), v); + } + } + ae_v_cmulc(&y->ptr.p_complex[1], 1, ae_v_len(1,n), alpha); +} + + +void hermitianrank2update(/* Complex */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Complex */ ae_vector* x, + /* Complex */ ae_vector* y, + /* Complex */ ae_vector* t, + ae_complex alpha, + ae_state *_state) +{ + ae_int_t i; + ae_int_t tp1; + ae_int_t tp2; + ae_complex v; + + + if( isupper ) + { + for(i=i1; i<=i2; i++) + { + tp1 = i+1-i1; + tp2 = i2-i1+1; + v = ae_c_mul(alpha,x->ptr.p_complex[i+1-i1]); + ae_v_cmovec(&t->ptr.p_complex[tp1], 1, &y->ptr.p_complex[tp1], 1, "Conj", ae_v_len(tp1,tp2), v); + v = ae_c_mul(ae_c_conj(alpha, _state),y->ptr.p_complex[i+1-i1]); + ae_v_caddc(&t->ptr.p_complex[tp1], 1, &x->ptr.p_complex[tp1], 1, "Conj", ae_v_len(tp1,tp2), v); + ae_v_cadd(&a->ptr.pp_complex[i][i], 1, &t->ptr.p_complex[tp1], 1, "N", ae_v_len(i,i2)); + } + } + else + { + for(i=i1; i<=i2; i++) + { + tp1 = 1; + tp2 = i+1-i1; + v = ae_c_mul(alpha,x->ptr.p_complex[i+1-i1]); + ae_v_cmovec(&t->ptr.p_complex[tp1], 1, &y->ptr.p_complex[tp1], 1, "Conj", ae_v_len(tp1,tp2), v); + v = ae_c_mul(ae_c_conj(alpha, _state),y->ptr.p_complex[i+1-i1]); + ae_v_caddc(&t->ptr.p_complex[tp1], 1, &x->ptr.p_complex[tp1], 1, "Conj", ae_v_len(tp1,tp2), v); + ae_v_cadd(&a->ptr.pp_complex[i][i1], 1, &t->ptr.p_complex[tp1], 1, "N", ae_v_len(i1,i)); + } + } +} + + + + +/************************************************************************* +Generation of an elementary reflection transformation + +The subroutine generates elementary reflection H of order N, so that, for +a given X, the following equality holds true: + + ( X(1) ) ( Beta ) +H * ( .. ) = ( 0 ) + ( X(n) ) ( 0 ) + +where + ( V(1) ) +H = 1 - Tau * ( .. ) * ( V(1), ..., V(n) ) + ( V(n) ) + +where the first component of vector V equals 1. + +Input parameters: + X - vector. Array whose index ranges within [1..N]. + N - reflection order. + +Output parameters: + X - components from 2 to N are replaced with vector V. + The first component is replaced with parameter Beta. + Tau - scalar value Tau. If X is a null vector, Tau equals 0, + otherwise 1 <= Tau <= 2. + +This subroutine is the modification of the DLARFG subroutines from +the LAPACK library. + +MODIFICATIONS: + 24.12.2005 sign(Alpha) was replaced with an analogous to the Fortran SIGN code. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void generatereflection(/* Real */ ae_vector* x, + ae_int_t n, + double* tau, + ae_state *_state) +{ + ae_int_t j; + double alpha; + double xnorm; + double v; + double beta; + double mx; + double s; + + *tau = 0; + + if( n<=1 ) + { + *tau = 0; + return; + } + + /* + * Scale if needed (to avoid overflow/underflow during intermediate + * calculations). + */ + mx = 0; + for(j=1; j<=n; j++) + { + mx = ae_maxreal(ae_fabs(x->ptr.p_double[j], _state), mx, _state); + } + s = 1; + if( ae_fp_neq(mx,0) ) + { + if( ae_fp_less_eq(mx,ae_minrealnumber/ae_machineepsilon) ) + { + s = ae_minrealnumber/ae_machineepsilon; + v = 1/s; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), v); + mx = mx*v; + } + else + { + if( ae_fp_greater_eq(mx,ae_maxrealnumber*ae_machineepsilon) ) + { + s = ae_maxrealnumber*ae_machineepsilon; + v = 1/s; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), v); + mx = mx*v; + } + } + } + + /* + * XNORM = DNRM2( N-1, X, INCX ) + */ + alpha = x->ptr.p_double[1]; + xnorm = 0; + if( ae_fp_neq(mx,0) ) + { + for(j=2; j<=n; j++) + { + xnorm = xnorm+ae_sqr(x->ptr.p_double[j]/mx, _state); + } + xnorm = ae_sqrt(xnorm, _state)*mx; + } + if( ae_fp_eq(xnorm,0) ) + { + + /* + * H = I + */ + *tau = 0; + x->ptr.p_double[1] = x->ptr.p_double[1]*s; + return; + } + + /* + * general case + */ + mx = ae_maxreal(ae_fabs(alpha, _state), ae_fabs(xnorm, _state), _state); + beta = -mx*ae_sqrt(ae_sqr(alpha/mx, _state)+ae_sqr(xnorm/mx, _state), _state); + if( ae_fp_less(alpha,0) ) + { + beta = -beta; + } + *tau = (beta-alpha)/beta; + v = 1/(alpha-beta); + ae_v_muld(&x->ptr.p_double[2], 1, ae_v_len(2,n), v); + x->ptr.p_double[1] = beta; + + /* + * Scale back outputs + */ + x->ptr.p_double[1] = x->ptr.p_double[1]*s; +} + + +/************************************************************************* +Application of an elementary reflection to a rectangular matrix of size MxN + +The algorithm pre-multiplies the matrix by an elementary reflection transformation +which is given by column V and scalar Tau (see the description of the +GenerateReflection procedure). Not the whole matrix but only a part of it +is transformed (rows from M1 to M2, columns from N1 to N2). Only the elements +of this submatrix are changed. + +Input parameters: + C - matrix to be transformed. + Tau - scalar defining the transformation. + V - column defining the transformation. + Array whose index ranges within [1..M2-M1+1]. + M1, M2 - range of rows to be transformed. + N1, N2 - range of columns to be transformed. + WORK - working array whose indexes goes from N1 to N2. + +Output parameters: + C - the result of multiplying the input matrix C by the + transformation matrix which is given by Tau and V. + If N1>N2 or M1>M2, C is not modified. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void applyreflectionfromtheleft(/* Real */ ae_matrix* c, + double tau, + /* Real */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* work, + ae_state *_state) +{ + double t; + ae_int_t i; + + + if( (ae_fp_eq(tau,0)||n1>n2)||m1>m2 ) + { + return; + } + + /* + * w := C' * v + */ + for(i=n1; i<=n2; i++) + { + work->ptr.p_double[i] = 0; + } + for(i=m1; i<=m2; i++) + { + t = v->ptr.p_double[i+1-m1]; + ae_v_addd(&work->ptr.p_double[n1], 1, &c->ptr.pp_double[i][n1], 1, ae_v_len(n1,n2), t); + } + + /* + * C := C - tau * v * w' + */ + for(i=m1; i<=m2; i++) + { + t = v->ptr.p_double[i-m1+1]*tau; + ae_v_subd(&c->ptr.pp_double[i][n1], 1, &work->ptr.p_double[n1], 1, ae_v_len(n1,n2), t); + } +} + + +/************************************************************************* +Application of an elementary reflection to a rectangular matrix of size MxN + +The algorithm post-multiplies the matrix by an elementary reflection transformation +which is given by column V and scalar Tau (see the description of the +GenerateReflection procedure). Not the whole matrix but only a part of it +is transformed (rows from M1 to M2, columns from N1 to N2). Only the +elements of this submatrix are changed. + +Input parameters: + C - matrix to be transformed. + Tau - scalar defining the transformation. + V - column defining the transformation. + Array whose index ranges within [1..N2-N1+1]. + M1, M2 - range of rows to be transformed. + N1, N2 - range of columns to be transformed. + WORK - working array whose indexes goes from M1 to M2. + +Output parameters: + C - the result of multiplying the input matrix C by the + transformation matrix which is given by Tau and V. + If N1>N2 or M1>M2, C is not modified. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void applyreflectionfromtheright(/* Real */ ae_matrix* c, + double tau, + /* Real */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* work, + ae_state *_state) +{ + double t; + ae_int_t i; + ae_int_t vm; + + + if( (ae_fp_eq(tau,0)||n1>n2)||m1>m2 ) + { + return; + } + vm = n2-n1+1; + for(i=m1; i<=m2; i++) + { + t = ae_v_dotproduct(&c->ptr.pp_double[i][n1], 1, &v->ptr.p_double[1], 1, ae_v_len(n1,n2)); + t = t*tau; + ae_v_subd(&c->ptr.pp_double[i][n1], 1, &v->ptr.p_double[1], 1, ae_v_len(n1,n2), t); + } + + /* + * This line is necessary to avoid spurious compiler warnings + */ + touchint(&vm, _state); +} + + + + +/************************************************************************* +Generation of an elementary complex reflection transformation + +The subroutine generates elementary complex reflection H of order N, so +that, for a given X, the following equality holds true: + + ( X(1) ) ( Beta ) +H' * ( .. ) = ( 0 ), H'*H = I, Beta is a real number + ( X(n) ) ( 0 ) + +where + + ( V(1) ) +H = 1 - Tau * ( .. ) * ( conj(V(1)), ..., conj(V(n)) ) + ( V(n) ) + +where the first component of vector V equals 1. + +Input parameters: + X - vector. Array with elements [1..N]. + N - reflection order. + +Output parameters: + X - components from 2 to N are replaced by vector V. + The first component is replaced with parameter Beta. + Tau - scalar value Tau. + +This subroutine is the modification of CLARFG subroutines from the LAPACK +library. It has similar functionality except for the fact that it doesn’t +handle errors when intermediate results cause an overflow. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void complexgeneratereflection(/* Complex */ ae_vector* x, + ae_int_t n, + ae_complex* tau, + ae_state *_state) +{ + ae_int_t j; + ae_complex alpha; + double alphi; + double alphr; + double beta; + double xnorm; + double mx; + ae_complex t; + double s; + ae_complex v; + + tau->x = 0; + tau->y = 0; + + if( n<=0 ) + { + *tau = ae_complex_from_d(0); + return; + } + + /* + * Scale if needed (to avoid overflow/underflow during intermediate + * calculations). + */ + mx = 0; + for(j=1; j<=n; j++) + { + mx = ae_maxreal(ae_c_abs(x->ptr.p_complex[j], _state), mx, _state); + } + s = 1; + if( ae_fp_neq(mx,0) ) + { + if( ae_fp_less(mx,1) ) + { + s = ae_sqrt(ae_minrealnumber, _state); + v = ae_complex_from_d(1/s); + ae_v_cmulc(&x->ptr.p_complex[1], 1, ae_v_len(1,n), v); + } + else + { + s = ae_sqrt(ae_maxrealnumber, _state); + v = ae_complex_from_d(1/s); + ae_v_cmulc(&x->ptr.p_complex[1], 1, ae_v_len(1,n), v); + } + } + + /* + * calculate + */ + alpha = x->ptr.p_complex[1]; + mx = 0; + for(j=2; j<=n; j++) + { + mx = ae_maxreal(ae_c_abs(x->ptr.p_complex[j], _state), mx, _state); + } + xnorm = 0; + if( ae_fp_neq(mx,0) ) + { + for(j=2; j<=n; j++) + { + t = ae_c_div_d(x->ptr.p_complex[j],mx); + xnorm = xnorm+ae_c_mul(t,ae_c_conj(t, _state)).x; + } + xnorm = ae_sqrt(xnorm, _state)*mx; + } + alphr = alpha.x; + alphi = alpha.y; + if( ae_fp_eq(xnorm,0)&&ae_fp_eq(alphi,0) ) + { + *tau = ae_complex_from_d(0); + x->ptr.p_complex[1] = ae_c_mul_d(x->ptr.p_complex[1],s); + return; + } + mx = ae_maxreal(ae_fabs(alphr, _state), ae_fabs(alphi, _state), _state); + mx = ae_maxreal(mx, ae_fabs(xnorm, _state), _state); + beta = -mx*ae_sqrt(ae_sqr(alphr/mx, _state)+ae_sqr(alphi/mx, _state)+ae_sqr(xnorm/mx, _state), _state); + if( ae_fp_less(alphr,0) ) + { + beta = -beta; + } + tau->x = (beta-alphr)/beta; + tau->y = -alphi/beta; + alpha = ae_c_d_div(1,ae_c_sub_d(alpha,beta)); + if( n>1 ) + { + ae_v_cmulc(&x->ptr.p_complex[2], 1, ae_v_len(2,n), alpha); + } + alpha = ae_complex_from_d(beta); + x->ptr.p_complex[1] = alpha; + + /* + * Scale back + */ + x->ptr.p_complex[1] = ae_c_mul_d(x->ptr.p_complex[1],s); +} + + +/************************************************************************* +Application of an elementary reflection to a rectangular matrix of size MxN + +The algorithm pre-multiplies the matrix by an elementary reflection +transformation which is given by column V and scalar Tau (see the +description of the GenerateReflection). Not the whole matrix but only a +part of it is transformed (rows from M1 to M2, columns from N1 to N2). Only +the elements of this submatrix are changed. + +Note: the matrix is multiplied by H, not by H'. If it is required to +multiply the matrix by H', it is necessary to pass Conj(Tau) instead of Tau. + +Input parameters: + C - matrix to be transformed. + Tau - scalar defining transformation. + V - column defining transformation. + Array whose index ranges within [1..M2-M1+1] + M1, M2 - range of rows to be transformed. + N1, N2 - range of columns to be transformed. + WORK - working array whose index goes from N1 to N2. + +Output parameters: + C - the result of multiplying the input matrix C by the + transformation matrix which is given by Tau and V. + If N1>N2 or M1>M2, C is not modified. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void complexapplyreflectionfromtheleft(/* Complex */ ae_matrix* c, + ae_complex tau, + /* Complex */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Complex */ ae_vector* work, + ae_state *_state) +{ + ae_complex t; + ae_int_t i; + + + if( (ae_c_eq_d(tau,0)||n1>n2)||m1>m2 ) + { + return; + } + + /* + * w := C^T * conj(v) + */ + for(i=n1; i<=n2; i++) + { + work->ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=m1; i<=m2; i++) + { + t = ae_c_conj(v->ptr.p_complex[i+1-m1], _state); + ae_v_caddc(&work->ptr.p_complex[n1], 1, &c->ptr.pp_complex[i][n1], 1, "N", ae_v_len(n1,n2), t); + } + + /* + * C := C - tau * v * w^T + */ + for(i=m1; i<=m2; i++) + { + t = ae_c_mul(v->ptr.p_complex[i-m1+1],tau); + ae_v_csubc(&c->ptr.pp_complex[i][n1], 1, &work->ptr.p_complex[n1], 1, "N", ae_v_len(n1,n2), t); + } +} + + +/************************************************************************* +Application of an elementary reflection to a rectangular matrix of size MxN + +The algorithm post-multiplies the matrix by an elementary reflection +transformation which is given by column V and scalar Tau (see the +description of the GenerateReflection). Not the whole matrix but only a +part of it is transformed (rows from M1 to M2, columns from N1 to N2). +Only the elements of this submatrix are changed. + +Input parameters: + C - matrix to be transformed. + Tau - scalar defining transformation. + V - column defining transformation. + Array whose index ranges within [1..N2-N1+1] + M1, M2 - range of rows to be transformed. + N1, N2 - range of columns to be transformed. + WORK - working array whose index goes from M1 to M2. + +Output parameters: + C - the result of multiplying the input matrix C by the + transformation matrix which is given by Tau and V. + If N1>N2 or M1>M2, C is not modified. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void complexapplyreflectionfromtheright(/* Complex */ ae_matrix* c, + ae_complex tau, + /* Complex */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Complex */ ae_vector* work, + ae_state *_state) +{ + ae_complex t; + ae_int_t i; + ae_int_t vm; + + + if( (ae_c_eq_d(tau,0)||n1>n2)||m1>m2 ) + { + return; + } + + /* + * w := C * v + */ + vm = n2-n1+1; + for(i=m1; i<=m2; i++) + { + t = ae_v_cdotproduct(&c->ptr.pp_complex[i][n1], 1, "N", &v->ptr.p_complex[1], 1, "N", ae_v_len(n1,n2)); + work->ptr.p_complex[i] = t; + } + + /* + * C := C - w * conj(v^T) + */ + ae_v_cmove(&v->ptr.p_complex[1], 1, &v->ptr.p_complex[1], 1, "Conj", ae_v_len(1,vm)); + for(i=m1; i<=m2; i++) + { + t = ae_c_mul(work->ptr.p_complex[i],tau); + ae_v_csubc(&c->ptr.pp_complex[i][n1], 1, &v->ptr.p_complex[1], 1, "N", ae_v_len(n1,n2), t); + } + ae_v_cmove(&v->ptr.p_complex[1], 1, &v->ptr.p_complex[1], 1, "Conj", ae_v_len(1,vm)); +} + + + + +void symmetricmatrixvectormultiply(/* Real */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* x, + double alpha, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ba1; + ae_int_t ba2; + ae_int_t by1; + ae_int_t by2; + ae_int_t bx1; + ae_int_t bx2; + ae_int_t n; + double v; + + + n = i2-i1+1; + if( n<=0 ) + { + return; + } + + /* + * Let A = L + D + U, where + * L is strictly lower triangular (main diagonal is zero) + * D is diagonal + * U is strictly upper triangular (main diagonal is zero) + * + * A*x = L*x + D*x + U*x + * + * Calculate D*x first + */ + for(i=i1; i<=i2; i++) + { + y->ptr.p_double[i-i1+1] = a->ptr.pp_double[i][i]*x->ptr.p_double[i-i1+1]; + } + + /* + * Add L*x + U*x + */ + if( isupper ) + { + for(i=i1; i<=i2-1; i++) + { + + /* + * Add L*x to the result + */ + v = x->ptr.p_double[i-i1+1]; + by1 = i-i1+2; + by2 = n; + ba1 = i+1; + ba2 = i2; + ae_v_addd(&y->ptr.p_double[by1], 1, &a->ptr.pp_double[i][ba1], 1, ae_v_len(by1,by2), v); + + /* + * Add U*x to the result + */ + bx1 = i-i1+2; + bx2 = n; + ba1 = i+1; + ba2 = i2; + v = ae_v_dotproduct(&x->ptr.p_double[bx1], 1, &a->ptr.pp_double[i][ba1], 1, ae_v_len(bx1,bx2)); + y->ptr.p_double[i-i1+1] = y->ptr.p_double[i-i1+1]+v; + } + } + else + { + for(i=i1+1; i<=i2; i++) + { + + /* + * Add L*x to the result + */ + bx1 = 1; + bx2 = i-i1; + ba1 = i1; + ba2 = i-1; + v = ae_v_dotproduct(&x->ptr.p_double[bx1], 1, &a->ptr.pp_double[i][ba1], 1, ae_v_len(bx1,bx2)); + y->ptr.p_double[i-i1+1] = y->ptr.p_double[i-i1+1]+v; + + /* + * Add U*x to the result + */ + v = x->ptr.p_double[i-i1+1]; + by1 = 1; + by2 = i-i1; + ba1 = i1; + ba2 = i-1; + ae_v_addd(&y->ptr.p_double[by1], 1, &a->ptr.pp_double[i][ba1], 1, ae_v_len(by1,by2), v); + } + } + ae_v_muld(&y->ptr.p_double[1], 1, ae_v_len(1,n), alpha); + touchint(&ba2, _state); +} + + +void symmetricrank2update(/* Real */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* t, + double alpha, + ae_state *_state) +{ + ae_int_t i; + ae_int_t tp1; + ae_int_t tp2; + double v; + + + if( isupper ) + { + for(i=i1; i<=i2; i++) + { + tp1 = i+1-i1; + tp2 = i2-i1+1; + v = x->ptr.p_double[i+1-i1]; + ae_v_moved(&t->ptr.p_double[tp1], 1, &y->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), v); + v = y->ptr.p_double[i+1-i1]; + ae_v_addd(&t->ptr.p_double[tp1], 1, &x->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), v); + ae_v_muld(&t->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), alpha); + ae_v_add(&a->ptr.pp_double[i][i], 1, &t->ptr.p_double[tp1], 1, ae_v_len(i,i2)); + } + } + else + { + for(i=i1; i<=i2; i++) + { + tp1 = 1; + tp2 = i+1-i1; + v = x->ptr.p_double[i+1-i1]; + ae_v_moved(&t->ptr.p_double[tp1], 1, &y->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), v); + v = y->ptr.p_double[i+1-i1]; + ae_v_addd(&t->ptr.p_double[tp1], 1, &x->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), v); + ae_v_muld(&t->ptr.p_double[tp1], 1, ae_v_len(tp1,tp2), alpha); + ae_v_add(&a->ptr.pp_double[i][i1], 1, &t->ptr.p_double[tp1], 1, ae_v_len(i1,i)); + } + } +} + + + + +/************************************************************************* +Application of a sequence of elementary rotations to a matrix + +The algorithm pre-multiplies the matrix by a sequence of rotation +transformations which is given by arrays C and S. Depending on the value +of the IsForward parameter either 1 and 2, 3 and 4 and so on (if IsForward=true) +rows are rotated, or the rows N and N-1, N-2 and N-3 and so on, are rotated. + +Not the whole matrix but only a part of it is transformed (rows from M1 to +M2, columns from N1 to N2). Only the elements of this submatrix are changed. + +Input parameters: + IsForward - the sequence of the rotation application. + M1,M2 - the range of rows to be transformed. + N1, N2 - the range of columns to be transformed. + C,S - transformation coefficients. + Array whose index ranges within [1..M2-M1]. + A - processed matrix. + WORK - working array whose index ranges within [N1..N2]. + +Output parameters: + A - transformed matrix. + +Utility subroutine. +*************************************************************************/ +void applyrotationsfromtheleft(ae_bool isforward, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* c, + /* Real */ ae_vector* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t j; + ae_int_t jp1; + double ctemp; + double stemp; + double temp; + + + if( m1>m2||n1>n2 ) + { + return; + } + + /* + * Form P * A + */ + if( isforward ) + { + if( n1!=n2 ) + { + + /* + * Common case: N1<>N2 + */ + for(j=m1; j<=m2-1; j++) + { + ctemp = c->ptr.p_double[j-m1+1]; + stemp = s->ptr.p_double[j-m1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + jp1 = j+1; + ae_v_moved(&work->ptr.p_double[n1], 1, &a->ptr.pp_double[jp1][n1], 1, ae_v_len(n1,n2), ctemp); + ae_v_subd(&work->ptr.p_double[n1], 1, &a->ptr.pp_double[j][n1], 1, ae_v_len(n1,n2), stemp); + ae_v_muld(&a->ptr.pp_double[j][n1], 1, ae_v_len(n1,n2), ctemp); + ae_v_addd(&a->ptr.pp_double[j][n1], 1, &a->ptr.pp_double[jp1][n1], 1, ae_v_len(n1,n2), stemp); + ae_v_move(&a->ptr.pp_double[jp1][n1], 1, &work->ptr.p_double[n1], 1, ae_v_len(n1,n2)); + } + } + } + else + { + + /* + * Special case: N1=N2 + */ + for(j=m1; j<=m2-1; j++) + { + ctemp = c->ptr.p_double[j-m1+1]; + stemp = s->ptr.p_double[j-m1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + temp = a->ptr.pp_double[j+1][n1]; + a->ptr.pp_double[j+1][n1] = ctemp*temp-stemp*a->ptr.pp_double[j][n1]; + a->ptr.pp_double[j][n1] = stemp*temp+ctemp*a->ptr.pp_double[j][n1]; + } + } + } + } + else + { + if( n1!=n2 ) + { + + /* + * Common case: N1<>N2 + */ + for(j=m2-1; j>=m1; j--) + { + ctemp = c->ptr.p_double[j-m1+1]; + stemp = s->ptr.p_double[j-m1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + jp1 = j+1; + ae_v_moved(&work->ptr.p_double[n1], 1, &a->ptr.pp_double[jp1][n1], 1, ae_v_len(n1,n2), ctemp); + ae_v_subd(&work->ptr.p_double[n1], 1, &a->ptr.pp_double[j][n1], 1, ae_v_len(n1,n2), stemp); + ae_v_muld(&a->ptr.pp_double[j][n1], 1, ae_v_len(n1,n2), ctemp); + ae_v_addd(&a->ptr.pp_double[j][n1], 1, &a->ptr.pp_double[jp1][n1], 1, ae_v_len(n1,n2), stemp); + ae_v_move(&a->ptr.pp_double[jp1][n1], 1, &work->ptr.p_double[n1], 1, ae_v_len(n1,n2)); + } + } + } + else + { + + /* + * Special case: N1=N2 + */ + for(j=m2-1; j>=m1; j--) + { + ctemp = c->ptr.p_double[j-m1+1]; + stemp = s->ptr.p_double[j-m1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + temp = a->ptr.pp_double[j+1][n1]; + a->ptr.pp_double[j+1][n1] = ctemp*temp-stemp*a->ptr.pp_double[j][n1]; + a->ptr.pp_double[j][n1] = stemp*temp+ctemp*a->ptr.pp_double[j][n1]; + } + } + } + } +} + + +/************************************************************************* +Application of a sequence of elementary rotations to a matrix + +The algorithm post-multiplies the matrix by a sequence of rotation +transformations which is given by arrays C and S. Depending on the value +of the IsForward parameter either 1 and 2, 3 and 4 and so on (if IsForward=true) +rows are rotated, or the rows N and N-1, N-2 and N-3 and so on are rotated. + +Not the whole matrix but only a part of it is transformed (rows from M1 +to M2, columns from N1 to N2). Only the elements of this submatrix are changed. + +Input parameters: + IsForward - the sequence of the rotation application. + M1,M2 - the range of rows to be transformed. + N1, N2 - the range of columns to be transformed. + C,S - transformation coefficients. + Array whose index ranges within [1..N2-N1]. + A - processed matrix. + WORK - working array whose index ranges within [M1..M2]. + +Output parameters: + A - transformed matrix. + +Utility subroutine. +*************************************************************************/ +void applyrotationsfromtheright(ae_bool isforward, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* c, + /* Real */ ae_vector* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t j; + ae_int_t jp1; + double ctemp; + double stemp; + double temp; + + + + /* + * Form A * P' + */ + if( isforward ) + { + if( m1!=m2 ) + { + + /* + * Common case: M1<>M2 + */ + for(j=n1; j<=n2-1; j++) + { + ctemp = c->ptr.p_double[j-n1+1]; + stemp = s->ptr.p_double[j-n1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + jp1 = j+1; + ae_v_moved(&work->ptr.p_double[m1], 1, &a->ptr.pp_double[m1][jp1], a->stride, ae_v_len(m1,m2), ctemp); + ae_v_subd(&work->ptr.p_double[m1], 1, &a->ptr.pp_double[m1][j], a->stride, ae_v_len(m1,m2), stemp); + ae_v_muld(&a->ptr.pp_double[m1][j], a->stride, ae_v_len(m1,m2), ctemp); + ae_v_addd(&a->ptr.pp_double[m1][j], a->stride, &a->ptr.pp_double[m1][jp1], a->stride, ae_v_len(m1,m2), stemp); + ae_v_move(&a->ptr.pp_double[m1][jp1], a->stride, &work->ptr.p_double[m1], 1, ae_v_len(m1,m2)); + } + } + } + else + { + + /* + * Special case: M1=M2 + */ + for(j=n1; j<=n2-1; j++) + { + ctemp = c->ptr.p_double[j-n1+1]; + stemp = s->ptr.p_double[j-n1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + temp = a->ptr.pp_double[m1][j+1]; + a->ptr.pp_double[m1][j+1] = ctemp*temp-stemp*a->ptr.pp_double[m1][j]; + a->ptr.pp_double[m1][j] = stemp*temp+ctemp*a->ptr.pp_double[m1][j]; + } + } + } + } + else + { + if( m1!=m2 ) + { + + /* + * Common case: M1<>M2 + */ + for(j=n2-1; j>=n1; j--) + { + ctemp = c->ptr.p_double[j-n1+1]; + stemp = s->ptr.p_double[j-n1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + jp1 = j+1; + ae_v_moved(&work->ptr.p_double[m1], 1, &a->ptr.pp_double[m1][jp1], a->stride, ae_v_len(m1,m2), ctemp); + ae_v_subd(&work->ptr.p_double[m1], 1, &a->ptr.pp_double[m1][j], a->stride, ae_v_len(m1,m2), stemp); + ae_v_muld(&a->ptr.pp_double[m1][j], a->stride, ae_v_len(m1,m2), ctemp); + ae_v_addd(&a->ptr.pp_double[m1][j], a->stride, &a->ptr.pp_double[m1][jp1], a->stride, ae_v_len(m1,m2), stemp); + ae_v_move(&a->ptr.pp_double[m1][jp1], a->stride, &work->ptr.p_double[m1], 1, ae_v_len(m1,m2)); + } + } + } + else + { + + /* + * Special case: M1=M2 + */ + for(j=n2-1; j>=n1; j--) + { + ctemp = c->ptr.p_double[j-n1+1]; + stemp = s->ptr.p_double[j-n1+1]; + if( ae_fp_neq(ctemp,1)||ae_fp_neq(stemp,0) ) + { + temp = a->ptr.pp_double[m1][j+1]; + a->ptr.pp_double[m1][j+1] = ctemp*temp-stemp*a->ptr.pp_double[m1][j]; + a->ptr.pp_double[m1][j] = stemp*temp+ctemp*a->ptr.pp_double[m1][j]; + } + } + } + } +} + + +/************************************************************************* +The subroutine generates the elementary rotation, so that: + +[ CS SN ] . [ F ] = [ R ] +[ -SN CS ] [ G ] [ 0 ] + +CS**2 + SN**2 = 1 +*************************************************************************/ +void generaterotation(double f, + double g, + double* cs, + double* sn, + double* r, + ae_state *_state) +{ + double f1; + double g1; + + *cs = 0; + *sn = 0; + *r = 0; + + if( ae_fp_eq(g,0) ) + { + *cs = 1; + *sn = 0; + *r = f; + } + else + { + if( ae_fp_eq(f,0) ) + { + *cs = 0; + *sn = 1; + *r = g; + } + else + { + f1 = f; + g1 = g; + if( ae_fp_greater(ae_fabs(f1, _state),ae_fabs(g1, _state)) ) + { + *r = ae_fabs(f1, _state)*ae_sqrt(1+ae_sqr(g1/f1, _state), _state); + } + else + { + *r = ae_fabs(g1, _state)*ae_sqrt(1+ae_sqr(f1/g1, _state), _state); + } + *cs = f1/(*r); + *sn = g1/(*r); + if( ae_fp_greater(ae_fabs(f, _state),ae_fabs(g, _state))&&ae_fp_less(*cs,0) ) + { + *cs = -*cs; + *sn = -*sn; + *r = -*r; + } + } + } +} + + + + +/************************************************************************* +Subroutine performing the Schur decomposition of a matrix in upper +Hessenberg form using the QR algorithm with multiple shifts. + +The source matrix H is represented as S'*H*S = T, where H - matrix in +upper Hessenberg form, S - orthogonal matrix (Schur vectors), T - upper +quasi-triangular matrix (with blocks of sizes 1x1 and 2x2 on the main +diagonal). + +Input parameters: + H - matrix to be decomposed. + Array whose indexes range within [1..N, 1..N]. + N - size of H, N>=0. + + +Output parameters: + H – contains the matrix T. + Array whose indexes range within [1..N, 1..N]. + All elements below the blocks on the main diagonal are equal + to 0. + S - contains Schur vectors. + Array whose indexes range within [1..N, 1..N]. + +Note 1: + The block structure of matrix T could be easily recognized: since all + the elements below the blocks are zeros, the elements a[i+1,i] which + are equal to 0 show the block border. + +Note 2: + the algorithm performance depends on the value of the internal + parameter NS of InternalSchurDecomposition subroutine which defines + the number of shifts in the QR algorithm (analog of the block width + in block matrix algorithms in linear algebra). If you require maximum + performance on your machine, it is recommended to adjust this + parameter manually. + +Result: + True, if the algorithm has converged and the parameters H and S contain + the result. + False, if the algorithm has not converged. + +Algorithm implemented on the basis of subroutine DHSEQR (LAPACK 3.0 library). +*************************************************************************/ +ae_bool upperhessenbergschurdecomposition(/* Real */ ae_matrix* h, + ae_int_t n, + /* Real */ ae_matrix* s, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector wi; + ae_vector wr; + ae_int_t info; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(s); + ae_vector_init(&wi, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wr, 0, DT_REAL, _state, ae_true); + + internalschurdecomposition(h, n, 1, 2, &wr, &wi, s, &info, _state); + result = info==0; + ae_frame_leave(_state); + return result; +} + + +void internalschurdecomposition(/* Real */ ae_matrix* h, + ae_int_t n, + ae_int_t tneeded, + ae_int_t zneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* z, + ae_int_t* info, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_int_t i; + ae_int_t i1; + ae_int_t i2; + ae_int_t ierr; + ae_int_t ii; + ae_int_t itemp; + ae_int_t itn; + ae_int_t its; + ae_int_t j; + ae_int_t k; + ae_int_t l; + ae_int_t maxb; + ae_int_t nr; + ae_int_t ns; + ae_int_t nv; + double absw; + double smlnum; + double tau; + double temp; + double tst1; + double ulp; + double unfl; + ae_matrix s; + ae_vector v; + ae_vector vv; + ae_vector workc1; + ae_vector works1; + ae_vector workv3; + ae_vector tmpwr; + ae_vector tmpwi; + ae_bool initz; + ae_bool wantt; + ae_bool wantz; + double cnst; + ae_bool failflag; + ae_int_t p1; + ae_int_t p2; + double vt; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(wr); + ae_vector_clear(wi); + *info = 0; + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&s, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&vv, 0, DT_REAL, _state, ae_true); + ae_vector_init(&workc1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&works1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&workv3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpwr, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpwi, 0, DT_REAL, _state, ae_true); + + + /* + * Set the order of the multi-shift QR algorithm to be used. + * If you want to tune algorithm, change this values + */ + ns = 12; + maxb = 50; + + /* + * Now 2 < NS <= MAXB < NH. + */ + maxb = ae_maxint(3, maxb, _state); + ns = ae_minint(maxb, ns, _state); + + /* + * Initialize + */ + cnst = 1.5; + ae_vector_set_length(&work, ae_maxint(n, 1, _state)+1, _state); + ae_matrix_set_length(&s, ns+1, ns+1, _state); + ae_vector_set_length(&v, ns+1+1, _state); + ae_vector_set_length(&vv, ns+1+1, _state); + ae_vector_set_length(wr, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(wi, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&workc1, 1+1, _state); + ae_vector_set_length(&works1, 1+1, _state); + ae_vector_set_length(&workv3, 3+1, _state); + ae_vector_set_length(&tmpwr, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&tmpwi, ae_maxint(n, 1, _state)+1, _state); + ae_assert(n>=0, "InternalSchurDecomposition: incorrect N!", _state); + ae_assert(tneeded==0||tneeded==1, "InternalSchurDecomposition: incorrect TNeeded!", _state); + ae_assert((zneeded==0||zneeded==1)||zneeded==2, "InternalSchurDecomposition: incorrect ZNeeded!", _state); + wantt = tneeded==1; + initz = zneeded==2; + wantz = zneeded!=0; + *info = 0; + + /* + * Initialize Z, if necessary + */ + if( initz ) + { + ae_matrix_set_length(z, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + for(j=1; j<=n; j++) + { + if( i==j ) + { + z->ptr.pp_double[i][j] = 1; + } + else + { + z->ptr.pp_double[i][j] = 0; + } + } + } + } + + /* + * Quick return if possible + */ + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + wr->ptr.p_double[1] = h->ptr.pp_double[1][1]; + wi->ptr.p_double[1] = 0; + ae_frame_leave(_state); + return; + } + + /* + * Set rows and columns 1 to N to zero below the first + * subdiagonal. + */ + for(j=1; j<=n-2; j++) + { + for(i=j+2; i<=n; i++) + { + h->ptr.pp_double[i][j] = 0; + } + } + + /* + * Test if N is sufficiently small + */ + if( (ns<=2||ns>n)||maxb>=n ) + { + + /* + * Use the standard double-shift algorithm + */ + hsschur_internalauxschur(wantt, wantz, n, 1, n, h, wr, wi, 1, n, z, &work, &workv3, &workc1, &works1, info, _state); + + /* + * fill entries under diagonal blocks of T with zeros + */ + if( wantt ) + { + j = 1; + while(j<=n) + { + if( ae_fp_eq(wi->ptr.p_double[j],0) ) + { + for(i=j+1; i<=n; i++) + { + h->ptr.pp_double[i][j] = 0; + } + j = j+1; + } + else + { + for(i=j+2; i<=n; i++) + { + h->ptr.pp_double[i][j] = 0; + h->ptr.pp_double[i][j+1] = 0; + } + j = j+2; + } + } + } + ae_frame_leave(_state); + return; + } + unfl = ae_minrealnumber; + ulp = 2*ae_machineepsilon; + smlnum = unfl*(n/ulp); + + /* + * I1 and I2 are the indices of the first row and last column of H + * to which transformations must be applied. If eigenvalues only are + * being computed, I1 and I2 are set inside the main loop. + */ + i1 = 1; + i2 = n; + + /* + * ITN is the total number of multiple-shift QR iterations allowed. + */ + itn = 30*n; + + /* + * The main loop begins here. I is the loop index and decreases from + * IHI to ILO in steps of at most MAXB. Each iteration of the loop + * works with the active submatrix in rows and columns L to I. + * Eigenvalues I+1 to IHI have already converged. Either L = ILO or + * H(L,L-1) is negligible so that the matrix splits. + */ + i = n; + for(;;) + { + l = 1; + if( i<1 ) + { + + /* + * fill entries under diagonal blocks of T with zeros + */ + if( wantt ) + { + j = 1; + while(j<=n) + { + if( ae_fp_eq(wi->ptr.p_double[j],0) ) + { + for(i=j+1; i<=n; i++) + { + h->ptr.pp_double[i][j] = 0; + } + j = j+1; + } + else + { + for(i=j+2; i<=n; i++) + { + h->ptr.pp_double[i][j] = 0; + h->ptr.pp_double[i][j+1] = 0; + } + j = j+2; + } + } + } + + /* + * Exit + */ + ae_frame_leave(_state); + return; + } + + /* + * Perform multiple-shift QR iterations on rows and columns ILO to I + * until a submatrix of order at most MAXB splits off at the bottom + * because a subdiagonal element has become negligible. + */ + failflag = ae_true; + for(its=0; its<=itn; its++) + { + + /* + * Look for a single small subdiagonal element. + */ + for(k=i; k>=l+1; k--) + { + tst1 = ae_fabs(h->ptr.pp_double[k-1][k-1], _state)+ae_fabs(h->ptr.pp_double[k][k], _state); + if( ae_fp_eq(tst1,0) ) + { + tst1 = upperhessenberg1norm(h, l, i, l, i, &work, _state); + } + if( ae_fp_less_eq(ae_fabs(h->ptr.pp_double[k][k-1], _state),ae_maxreal(ulp*tst1, smlnum, _state)) ) + { + break; + } + } + l = k; + if( l>1 ) + { + + /* + * H(L,L-1) is negligible. + */ + h->ptr.pp_double[l][l-1] = 0; + } + + /* + * Exit from loop if a submatrix of order <= MAXB has split off. + */ + if( l>=i-maxb+1 ) + { + failflag = ae_false; + break; + } + + /* + * Now the active submatrix is in rows and columns L to I. If + * eigenvalues only are being computed, only the active submatrix + * need be transformed. + */ + if( its==20||its==30 ) + { + + /* + * Exceptional shifts. + */ + for(ii=i-ns+1; ii<=i; ii++) + { + wr->ptr.p_double[ii] = cnst*(ae_fabs(h->ptr.pp_double[ii][ii-1], _state)+ae_fabs(h->ptr.pp_double[ii][ii], _state)); + wi->ptr.p_double[ii] = 0; + } + } + else + { + + /* + * Use eigenvalues of trailing submatrix of order NS as shifts. + */ + copymatrix(h, i-ns+1, i, i-ns+1, i, &s, 1, ns, 1, ns, _state); + hsschur_internalauxschur(ae_false, ae_false, ns, 1, ns, &s, &tmpwr, &tmpwi, 1, ns, z, &work, &workv3, &workc1, &works1, &ierr, _state); + for(p1=1; p1<=ns; p1++) + { + wr->ptr.p_double[i-ns+p1] = tmpwr.ptr.p_double[p1]; + wi->ptr.p_double[i-ns+p1] = tmpwi.ptr.p_double[p1]; + } + if( ierr>0 ) + { + + /* + * If DLAHQR failed to compute all NS eigenvalues, use the + * unconverged diagonal elements as the remaining shifts. + */ + for(ii=1; ii<=ierr; ii++) + { + wr->ptr.p_double[i-ns+ii] = s.ptr.pp_double[ii][ii]; + wi->ptr.p_double[i-ns+ii] = 0; + } + } + } + + /* + * Form the first column of (G-w(1)) (G-w(2)) . . . (G-w(ns)) + * where G is the Hessenberg submatrix H(L:I,L:I) and w is + * the vector of shifts (stored in WR and WI). The result is + * stored in the local array V. + */ + v.ptr.p_double[1] = 1; + for(ii=2; ii<=ns+1; ii++) + { + v.ptr.p_double[ii] = 0; + } + nv = 1; + for(j=i-ns+1; j<=i; j++) + { + if( ae_fp_greater_eq(wi->ptr.p_double[j],0) ) + { + if( ae_fp_eq(wi->ptr.p_double[j],0) ) + { + + /* + * real shift + */ + p1 = nv+1; + ae_v_move(&vv.ptr.p_double[1], 1, &v.ptr.p_double[1], 1, ae_v_len(1,p1)); + matrixvectormultiply(h, l, l+nv, l, l+nv-1, ae_false, &vv, 1, nv, 1.0, &v, 1, nv+1, -wr->ptr.p_double[j], _state); + nv = nv+1; + } + else + { + if( ae_fp_greater(wi->ptr.p_double[j],0) ) + { + + /* + * complex conjugate pair of shifts + */ + p1 = nv+1; + ae_v_move(&vv.ptr.p_double[1], 1, &v.ptr.p_double[1], 1, ae_v_len(1,p1)); + matrixvectormultiply(h, l, l+nv, l, l+nv-1, ae_false, &v, 1, nv, 1.0, &vv, 1, nv+1, -2*wr->ptr.p_double[j], _state); + itemp = vectoridxabsmax(&vv, 1, nv+1, _state); + temp = 1/ae_maxreal(ae_fabs(vv.ptr.p_double[itemp], _state), smlnum, _state); + p1 = nv+1; + ae_v_muld(&vv.ptr.p_double[1], 1, ae_v_len(1,p1), temp); + absw = pythag2(wr->ptr.p_double[j], wi->ptr.p_double[j], _state); + temp = temp*absw*absw; + matrixvectormultiply(h, l, l+nv+1, l, l+nv, ae_false, &vv, 1, nv+1, 1.0, &v, 1, nv+2, temp, _state); + nv = nv+2; + } + } + + /* + * Scale V(1:NV) so that max(abs(V(i))) = 1. If V is zero, + * reset it to the unit vector. + */ + itemp = vectoridxabsmax(&v, 1, nv, _state); + temp = ae_fabs(v.ptr.p_double[itemp], _state); + if( ae_fp_eq(temp,0) ) + { + v.ptr.p_double[1] = 1; + for(ii=2; ii<=nv; ii++) + { + v.ptr.p_double[ii] = 0; + } + } + else + { + temp = ae_maxreal(temp, smlnum, _state); + vt = 1/temp; + ae_v_muld(&v.ptr.p_double[1], 1, ae_v_len(1,nv), vt); + } + } + } + + /* + * Multiple-shift QR step + */ + for(k=l; k<=i-1; k++) + { + + /* + * The first iteration of this loop determines a reflection G + * from the vector V and applies it from left and right to H, + * thus creating a nonzero bulge below the subdiagonal. + * + * Each subsequent iteration determines a reflection G to + * restore the Hessenberg form in the (K-1)th column, and thus + * chases the bulge one step toward the bottom of the active + * submatrix. NR is the order of G. + */ + nr = ae_minint(ns+1, i-k+1, _state); + if( k>l ) + { + p1 = k-1; + p2 = k+nr-1; + ae_v_move(&v.ptr.p_double[1], 1, &h->ptr.pp_double[k][p1], h->stride, ae_v_len(1,nr)); + touchint(&p2, _state); + } + generatereflection(&v, nr, &tau, _state); + if( k>l ) + { + h->ptr.pp_double[k][k-1] = v.ptr.p_double[1]; + for(ii=k+1; ii<=i; ii++) + { + h->ptr.pp_double[ii][k-1] = 0; + } + } + v.ptr.p_double[1] = 1; + + /* + * Apply G from the left to transform the rows of the matrix in + * columns K to I2. + */ + applyreflectionfromtheleft(h, tau, &v, k, k+nr-1, k, i2, &work, _state); + + /* + * Apply G from the right to transform the columns of the + * matrix in rows I1 to min(K+NR,I). + */ + applyreflectionfromtheright(h, tau, &v, i1, ae_minint(k+nr, i, _state), k, k+nr-1, &work, _state); + if( wantz ) + { + + /* + * Accumulate transformations in the matrix Z + */ + applyreflectionfromtheright(z, tau, &v, 1, n, k, k+nr-1, &work, _state); + } + } + } + + /* + * Failure to converge in remaining number of iterations + */ + if( failflag ) + { + *info = i; + ae_frame_leave(_state); + return; + } + + /* + * A submatrix of order <= MAXB in rows and columns L to I has split + * off. Use the double-shift QR algorithm to handle it. + */ + hsschur_internalauxschur(wantt, wantz, n, l, i, h, wr, wi, 1, n, z, &work, &workv3, &workc1, &works1, info, _state); + if( *info>0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Decrement number of remaining iterations, and return to start of + * the main loop with a new value of I. + */ + itn = itn-its; + i = l-1; + } + ae_frame_leave(_state); +} + + +static void hsschur_internalauxschur(ae_bool wantt, + ae_bool wantz, + ae_int_t n, + ae_int_t ilo, + ae_int_t ihi, + /* Real */ ae_matrix* h, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + ae_int_t iloz, + ae_int_t ihiz, + /* Real */ ae_matrix* z, + /* Real */ ae_vector* work, + /* Real */ ae_vector* workv3, + /* Real */ ae_vector* workc1, + /* Real */ ae_vector* works1, + ae_int_t* info, + ae_state *_state) +{ + ae_int_t i; + ae_int_t i1; + ae_int_t i2; + ae_int_t itn; + ae_int_t its; + ae_int_t j; + ae_int_t k; + ae_int_t l; + ae_int_t m; + ae_int_t nh; + ae_int_t nr; + ae_int_t nz; + double ave; + double cs; + double disc; + double h00; + double h10; + double h11; + double h12; + double h21; + double h22; + double h33; + double h33s; + double h43h34; + double h44; + double h44s; + double s; + double smlnum; + double sn; + double sum; + double t1; + double t2; + double t3; + double tst1; + double unfl; + double v1; + double v2; + double v3; + ae_bool failflag; + double dat1; + double dat2; + ae_int_t p1; + double him1im1; + double him1i; + double hiim1; + double hii; + double wrim1; + double wri; + double wiim1; + double wii; + double ulp; + + *info = 0; + + *info = 0; + dat1 = 0.75; + dat2 = -0.4375; + ulp = ae_machineepsilon; + + /* + * Quick return if possible + */ + if( n==0 ) + { + return; + } + if( ilo==ihi ) + { + wr->ptr.p_double[ilo] = h->ptr.pp_double[ilo][ilo]; + wi->ptr.p_double[ilo] = 0; + return; + } + nh = ihi-ilo+1; + nz = ihiz-iloz+1; + + /* + * Set machine-dependent constants for the stopping criterion. + * If norm(H) <= sqrt(MaxRealNumber), overflow should not occur. + */ + unfl = ae_minrealnumber; + smlnum = unfl*(nh/ulp); + + /* + * I1 and I2 are the indices of the first row and last column of H + * to which transformations must be applied. If eigenvalues only are + * being computed, I1 and I2 are set inside the main loop. + */ + i1 = 1; + i2 = n; + + /* + * ITN is the total number of QR iterations allowed. + */ + itn = 30*nh; + + /* + * The main loop begins here. I is the loop index and decreases from + * IHI to ILO in steps of 1 or 2. Each iteration of the loop works + * with the active submatrix in rows and columns L to I. + * Eigenvalues I+1 to IHI have already converged. Either L = ILO or + * H(L,L-1) is negligible so that the matrix splits. + */ + i = ihi; + for(;;) + { + l = ilo; + if( i=l+1; k--) + { + tst1 = ae_fabs(h->ptr.pp_double[k-1][k-1], _state)+ae_fabs(h->ptr.pp_double[k][k], _state); + if( ae_fp_eq(tst1,0) ) + { + tst1 = upperhessenberg1norm(h, l, i, l, i, work, _state); + } + if( ae_fp_less_eq(ae_fabs(h->ptr.pp_double[k][k-1], _state),ae_maxreal(ulp*tst1, smlnum, _state)) ) + { + break; + } + } + l = k; + if( l>ilo ) + { + + /* + * H(L,L-1) is negligible + */ + h->ptr.pp_double[l][l-1] = 0; + } + + /* + * Exit from loop if a submatrix of order 1 or 2 has split off. + */ + if( l>=i-1 ) + { + failflag = ae_false; + break; + } + + /* + * Now the active submatrix is in rows and columns L to I. If + * eigenvalues only are being computed, only the active submatrix + * need be transformed. + */ + if( its==10||its==20 ) + { + + /* + * Exceptional shift. + */ + s = ae_fabs(h->ptr.pp_double[i][i-1], _state)+ae_fabs(h->ptr.pp_double[i-1][i-2], _state); + h44 = dat1*s+h->ptr.pp_double[i][i]; + h33 = h44; + h43h34 = dat2*s*s; + } + else + { + + /* + * Prepare to use Francis' double shift + * (i.e. 2nd degree generalized Rayleigh quotient) + */ + h44 = h->ptr.pp_double[i][i]; + h33 = h->ptr.pp_double[i-1][i-1]; + h43h34 = h->ptr.pp_double[i][i-1]*h->ptr.pp_double[i-1][i]; + s = h->ptr.pp_double[i-1][i-2]*h->ptr.pp_double[i-1][i-2]; + disc = (h33-h44)*0.5; + disc = disc*disc+h43h34; + if( ae_fp_greater(disc,0) ) + { + + /* + * Real roots: use Wilkinson's shift twice + */ + disc = ae_sqrt(disc, _state); + ave = 0.5*(h33+h44); + if( ae_fp_greater(ae_fabs(h33, _state)-ae_fabs(h44, _state),0) ) + { + h33 = h33*h44-h43h34; + h44 = h33/(hsschur_extschursign(disc, ave, _state)+ave); + } + else + { + h44 = hsschur_extschursign(disc, ave, _state)+ave; + } + h33 = h44; + h43h34 = 0; + } + } + + /* + * Look for two consecutive small subdiagonal elements. + */ + for(m=i-2; m>=l; m--) + { + + /* + * Determine the effect of starting the double-shift QR + * iteration at row M, and see if this would make H(M,M-1) + * negligible. + */ + h11 = h->ptr.pp_double[m][m]; + h22 = h->ptr.pp_double[m+1][m+1]; + h21 = h->ptr.pp_double[m+1][m]; + h12 = h->ptr.pp_double[m][m+1]; + h44s = h44-h11; + h33s = h33-h11; + v1 = (h33s*h44s-h43h34)/h21+h12; + v2 = h22-h11-h33s-h44s; + v3 = h->ptr.pp_double[m+2][m+1]; + s = ae_fabs(v1, _state)+ae_fabs(v2, _state)+ae_fabs(v3, _state); + v1 = v1/s; + v2 = v2/s; + v3 = v3/s; + workv3->ptr.p_double[1] = v1; + workv3->ptr.p_double[2] = v2; + workv3->ptr.p_double[3] = v3; + if( m==l ) + { + break; + } + h00 = h->ptr.pp_double[m-1][m-1]; + h10 = h->ptr.pp_double[m][m-1]; + tst1 = ae_fabs(v1, _state)*(ae_fabs(h00, _state)+ae_fabs(h11, _state)+ae_fabs(h22, _state)); + if( ae_fp_less_eq(ae_fabs(h10, _state)*(ae_fabs(v2, _state)+ae_fabs(v3, _state)),ulp*tst1) ) + { + break; + } + } + + /* + * Double-shift QR step + */ + for(k=m; k<=i-1; k++) + { + + /* + * The first iteration of this loop determines a reflection G + * from the vector V and applies it from left and right to H, + * thus creating a nonzero bulge below the subdiagonal. + * + * Each subsequent iteration determines a reflection G to + * restore the Hessenberg form in the (K-1)th column, and thus + * chases the bulge one step toward the bottom of the active + * submatrix. NR is the order of G. + */ + nr = ae_minint(3, i-k+1, _state); + if( k>m ) + { + for(p1=1; p1<=nr; p1++) + { + workv3->ptr.p_double[p1] = h->ptr.pp_double[k+p1-1][k-1]; + } + } + generatereflection(workv3, nr, &t1, _state); + if( k>m ) + { + h->ptr.pp_double[k][k-1] = workv3->ptr.p_double[1]; + h->ptr.pp_double[k+1][k-1] = 0; + if( kptr.pp_double[k+2][k-1] = 0; + } + } + else + { + if( m>l ) + { + h->ptr.pp_double[k][k-1] = -h->ptr.pp_double[k][k-1]; + } + } + v2 = workv3->ptr.p_double[2]; + t2 = t1*v2; + if( nr==3 ) + { + v3 = workv3->ptr.p_double[3]; + t3 = t1*v3; + + /* + * Apply G from the left to transform the rows of the matrix + * in columns K to I2. + */ + for(j=k; j<=i2; j++) + { + sum = h->ptr.pp_double[k][j]+v2*h->ptr.pp_double[k+1][j]+v3*h->ptr.pp_double[k+2][j]; + h->ptr.pp_double[k][j] = h->ptr.pp_double[k][j]-sum*t1; + h->ptr.pp_double[k+1][j] = h->ptr.pp_double[k+1][j]-sum*t2; + h->ptr.pp_double[k+2][j] = h->ptr.pp_double[k+2][j]-sum*t3; + } + + /* + * Apply G from the right to transform the columns of the + * matrix in rows I1 to min(K+3,I). + */ + for(j=i1; j<=ae_minint(k+3, i, _state); j++) + { + sum = h->ptr.pp_double[j][k]+v2*h->ptr.pp_double[j][k+1]+v3*h->ptr.pp_double[j][k+2]; + h->ptr.pp_double[j][k] = h->ptr.pp_double[j][k]-sum*t1; + h->ptr.pp_double[j][k+1] = h->ptr.pp_double[j][k+1]-sum*t2; + h->ptr.pp_double[j][k+2] = h->ptr.pp_double[j][k+2]-sum*t3; + } + if( wantz ) + { + + /* + * Accumulate transformations in the matrix Z + */ + for(j=iloz; j<=ihiz; j++) + { + sum = z->ptr.pp_double[j][k]+v2*z->ptr.pp_double[j][k+1]+v3*z->ptr.pp_double[j][k+2]; + z->ptr.pp_double[j][k] = z->ptr.pp_double[j][k]-sum*t1; + z->ptr.pp_double[j][k+1] = z->ptr.pp_double[j][k+1]-sum*t2; + z->ptr.pp_double[j][k+2] = z->ptr.pp_double[j][k+2]-sum*t3; + } + } + } + else + { + if( nr==2 ) + { + + /* + * Apply G from the left to transform the rows of the matrix + * in columns K to I2. + */ + for(j=k; j<=i2; j++) + { + sum = h->ptr.pp_double[k][j]+v2*h->ptr.pp_double[k+1][j]; + h->ptr.pp_double[k][j] = h->ptr.pp_double[k][j]-sum*t1; + h->ptr.pp_double[k+1][j] = h->ptr.pp_double[k+1][j]-sum*t2; + } + + /* + * Apply G from the right to transform the columns of the + * matrix in rows I1 to min(K+3,I). + */ + for(j=i1; j<=i; j++) + { + sum = h->ptr.pp_double[j][k]+v2*h->ptr.pp_double[j][k+1]; + h->ptr.pp_double[j][k] = h->ptr.pp_double[j][k]-sum*t1; + h->ptr.pp_double[j][k+1] = h->ptr.pp_double[j][k+1]-sum*t2; + } + if( wantz ) + { + + /* + * Accumulate transformations in the matrix Z + */ + for(j=iloz; j<=ihiz; j++) + { + sum = z->ptr.pp_double[j][k]+v2*z->ptr.pp_double[j][k+1]; + z->ptr.pp_double[j][k] = z->ptr.pp_double[j][k]-sum*t1; + z->ptr.pp_double[j][k+1] = z->ptr.pp_double[j][k+1]-sum*t2; + } + } + } + } + } + } + if( failflag ) + { + + /* + * Failure to converge in remaining number of iterations + */ + *info = i; + return; + } + if( l==i ) + { + + /* + * H(I,I-1) is negligible: one eigenvalue has converged. + */ + wr->ptr.p_double[i] = h->ptr.pp_double[i][i]; + wi->ptr.p_double[i] = 0; + } + else + { + if( l==i-1 ) + { + + /* + * H(I-1,I-2) is negligible: a pair of eigenvalues have converged. + * + * Transform the 2-by-2 submatrix to standard Schur form, + * and compute and store the eigenvalues. + */ + him1im1 = h->ptr.pp_double[i-1][i-1]; + him1i = h->ptr.pp_double[i-1][i]; + hiim1 = h->ptr.pp_double[i][i-1]; + hii = h->ptr.pp_double[i][i]; + hsschur_aux2x2schur(&him1im1, &him1i, &hiim1, &hii, &wrim1, &wiim1, &wri, &wii, &cs, &sn, _state); + wr->ptr.p_double[i-1] = wrim1; + wi->ptr.p_double[i-1] = wiim1; + wr->ptr.p_double[i] = wri; + wi->ptr.p_double[i] = wii; + h->ptr.pp_double[i-1][i-1] = him1im1; + h->ptr.pp_double[i-1][i] = him1i; + h->ptr.pp_double[i][i-1] = hiim1; + h->ptr.pp_double[i][i] = hii; + if( wantt ) + { + + /* + * Apply the transformation to the rest of H. + */ + if( i2>i ) + { + workc1->ptr.p_double[1] = cs; + works1->ptr.p_double[1] = sn; + applyrotationsfromtheleft(ae_true, i-1, i, i+1, i2, workc1, works1, h, work, _state); + } + workc1->ptr.p_double[1] = cs; + works1->ptr.p_double[1] = sn; + applyrotationsfromtheright(ae_true, i1, i-2, i-1, i, workc1, works1, h, work, _state); + } + if( wantz ) + { + + /* + * Apply the transformation to Z. + */ + workc1->ptr.p_double[1] = cs; + works1->ptr.p_double[1] = sn; + applyrotationsfromtheright(ae_true, iloz, iloz+nz-1, i-1, i, workc1, works1, z, work, _state); + } + } + } + + /* + * Decrement number of remaining iterations, and return to start of + * the main loop with new value of I. + */ + itn = itn-its; + i = l-1; + } +} + + +static void hsschur_aux2x2schur(double* a, + double* b, + double* c, + double* d, + double* rt1r, + double* rt1i, + double* rt2r, + double* rt2i, + double* cs, + double* sn, + ae_state *_state) +{ + double multpl; + double aa; + double bb; + double bcmax; + double bcmis; + double cc; + double cs1; + double dd; + double eps; + double p; + double sab; + double sac; + double scl; + double sigma; + double sn1; + double tau; + double temp; + double z; + + *rt1r = 0; + *rt1i = 0; + *rt2r = 0; + *rt2i = 0; + *cs = 0; + *sn = 0; + + multpl = 4.0; + eps = ae_machineepsilon; + if( ae_fp_eq(*c,0) ) + { + *cs = 1; + *sn = 0; + } + else + { + if( ae_fp_eq(*b,0) ) + { + + /* + * Swap rows and columns + */ + *cs = 0; + *sn = 1; + temp = *d; + *d = *a; + *a = temp; + *b = -*c; + *c = 0; + } + else + { + if( ae_fp_eq(*a-(*d),0)&&hsschur_extschursigntoone(*b, _state)!=hsschur_extschursigntoone(*c, _state) ) + { + *cs = 1; + *sn = 0; + } + else + { + temp = *a-(*d); + p = 0.5*temp; + bcmax = ae_maxreal(ae_fabs(*b, _state), ae_fabs(*c, _state), _state); + bcmis = ae_minreal(ae_fabs(*b, _state), ae_fabs(*c, _state), _state)*hsschur_extschursigntoone(*b, _state)*hsschur_extschursigntoone(*c, _state); + scl = ae_maxreal(ae_fabs(p, _state), bcmax, _state); + z = p/scl*p+bcmax/scl*bcmis; + + /* + * If Z is of the order of the machine accuracy, postpone the + * decision on the nature of eigenvalues + */ + if( ae_fp_greater_eq(z,multpl*eps) ) + { + + /* + * Real eigenvalues. Compute A and D. + */ + z = p+hsschur_extschursign(ae_sqrt(scl, _state)*ae_sqrt(z, _state), p, _state); + *a = *d+z; + *d = *d-bcmax/z*bcmis; + + /* + * Compute B and the rotation matrix + */ + tau = pythag2(*c, z, _state); + *cs = z/tau; + *sn = *c/tau; + *b = *b-(*c); + *c = 0; + } + else + { + + /* + * Complex eigenvalues, or real (almost) equal eigenvalues. + * Make diagonal elements equal. + */ + sigma = *b+(*c); + tau = pythag2(sigma, temp, _state); + *cs = ae_sqrt(0.5*(1+ae_fabs(sigma, _state)/tau), _state); + *sn = -p/(tau*(*cs))*hsschur_extschursign(1, sigma, _state); + + /* + * Compute [ AA BB ] = [ A B ] [ CS -SN ] + * [ CC DD ] [ C D ] [ SN CS ] + */ + aa = *a*(*cs)+*b*(*sn); + bb = -*a*(*sn)+*b*(*cs); + cc = *c*(*cs)+*d*(*sn); + dd = -*c*(*sn)+*d*(*cs); + + /* + * Compute [ A B ] = [ CS SN ] [ AA BB ] + * [ C D ] [-SN CS ] [ CC DD ] + */ + *a = aa*(*cs)+cc*(*sn); + *b = bb*(*cs)+dd*(*sn); + *c = -aa*(*sn)+cc*(*cs); + *d = -bb*(*sn)+dd*(*cs); + temp = 0.5*(*a+(*d)); + *a = temp; + *d = temp; + if( ae_fp_neq(*c,0) ) + { + if( ae_fp_neq(*b,0) ) + { + if( hsschur_extschursigntoone(*b, _state)==hsschur_extschursigntoone(*c, _state) ) + { + + /* + * Real eigenvalues: reduce to upper triangular form + */ + sab = ae_sqrt(ae_fabs(*b, _state), _state); + sac = ae_sqrt(ae_fabs(*c, _state), _state); + p = hsschur_extschursign(sab*sac, *c, _state); + tau = 1/ae_sqrt(ae_fabs(*b+(*c), _state), _state); + *a = temp+p; + *d = temp-p; + *b = *b-(*c); + *c = 0; + cs1 = sab*tau; + sn1 = sac*tau; + temp = *cs*cs1-*sn*sn1; + *sn = *cs*sn1+*sn*cs1; + *cs = temp; + } + } + else + { + *b = -*c; + *c = 0; + temp = *cs; + *cs = -*sn; + *sn = temp; + } + } + } + } + } + } + + /* + * Store eigenvalues in (RT1R,RT1I) and (RT2R,RT2I). + */ + *rt1r = *a; + *rt2r = *d; + if( ae_fp_eq(*c,0) ) + { + *rt1i = 0; + *rt2i = 0; + } + else + { + *rt1i = ae_sqrt(ae_fabs(*b, _state), _state)*ae_sqrt(ae_fabs(*c, _state), _state); + *rt2i = -*rt1i; + } +} + + +static double hsschur_extschursign(double a, double b, ae_state *_state) +{ + double result; + + + if( ae_fp_greater_eq(b,0) ) + { + result = ae_fabs(a, _state); + } + else + { + result = -ae_fabs(a, _state); + } + return result; +} + + +static ae_int_t hsschur_extschursigntoone(double b, ae_state *_state) +{ + ae_int_t result; + + + if( ae_fp_greater_eq(b,0) ) + { + result = 1; + } + else + { + result = -1; + } + return result; +} + + + + +/************************************************************************* +Utility subroutine performing the "safe" solution of system of linear +equations with triangular coefficient matrices. + +The subroutine uses scaling and solves the scaled system A*x=s*b (where s +is a scalar value) instead of A*x=b, choosing s so that x can be +represented by a floating-point number. The closer the system gets to a +singular, the less s is. If the system is singular, s=0 and x contains the +non-trivial solution of equation A*x=0. + +The feature of an algorithm is that it could not cause an overflow or a +division by zero regardless of the matrix used as the input. + +The algorithm can solve systems of equations with upper/lower triangular +matrices, with/without unit diagonal, and systems of type A*x=b or A'*x=b +(where A' is a transposed matrix A). + +Input parameters: + A - system matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + X - right-hand member of a system. + Array whose index ranges within [0..N-1]. + IsUpper - matrix type. If it is True, the system matrix is the upper + triangular and is located in the corresponding part of + matrix A. + Trans - problem type. If it is True, the problem to be solved is + A'*x=b, otherwise it is A*x=b. + Isunit - matrix type. If it is True, the system matrix has a unit + diagonal (the elements on the main diagonal are not used + in the calculation process), otherwise the matrix is considered + to be a general triangular matrix. + +Output parameters: + X - solution. Array whose index ranges within [0..N-1]. + S - scaling factor. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + June 30, 1992 +*************************************************************************/ +void rmatrixtrsafesolve(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* x, + double* s, + ae_bool isupper, + ae_bool istrans, + ae_bool isunit, + ae_state *_state) +{ + ae_frame _frame_block; + ae_bool normin; + ae_vector cnorm; + ae_matrix a1; + ae_vector x1; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *s = 0; + ae_vector_init(&cnorm, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a1, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x1, 0, DT_REAL, _state, ae_true); + + + /* + * From 0-based to 1-based + */ + normin = ae_false; + ae_matrix_set_length(&a1, n+1, n+1, _state); + ae_vector_set_length(&x1, n+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&a1.ptr.pp_double[i][1], 1, &a->ptr.pp_double[i-1][0], 1, ae_v_len(1,n)); + } + ae_v_move(&x1.ptr.p_double[1], 1, &x->ptr.p_double[0], 1, ae_v_len(1,n)); + + /* + * Solve 1-based + */ + safesolvetriangular(&a1, n, &x1, s, isupper, istrans, isunit, normin, &cnorm, _state); + + /* + * From 1-based to 0-based + */ + ae_v_move(&x->ptr.p_double[0], 1, &x1.ptr.p_double[1], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Obsolete 1-based subroutine. +See RMatrixTRSafeSolve for 0-based replacement. +*************************************************************************/ +void safesolvetriangular(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* x, + double* s, + ae_bool isupper, + ae_bool istrans, + ae_bool isunit, + ae_bool normin, + /* Real */ ae_vector* cnorm, + ae_state *_state) +{ + ae_int_t i; + ae_int_t imax; + ae_int_t j; + ae_int_t jfirst; + ae_int_t jinc; + ae_int_t jlast; + ae_int_t jm1; + ae_int_t jp1; + ae_int_t ip1; + ae_int_t im1; + ae_int_t k; + ae_int_t flg; + double v; + double vd; + double bignum; + double grow; + double rec; + double smlnum; + double sumj; + double tjj; + double tjjs; + double tmax; + double tscal; + double uscal; + double xbnd; + double xj; + double xmax; + ae_bool notran; + ae_bool upper; + ae_bool nounit; + + *s = 0; + + upper = isupper; + notran = !istrans; + nounit = !isunit; + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + tjjs = 0; + + /* + * Quick return if possible + */ + if( n==0 ) + { + return; + } + + /* + * Determine machine dependent parameters to control overflow. + */ + smlnum = ae_minrealnumber/(ae_machineepsilon*2); + bignum = 1/smlnum; + *s = 1; + if( !normin ) + { + ae_vector_set_length(cnorm, n+1, _state); + + /* + * Compute the 1-norm of each column, not including the diagonal. + */ + if( upper ) + { + + /* + * A is upper triangular. + */ + for(j=1; j<=n; j++) + { + v = 0; + for(k=1; k<=j-1; k++) + { + v = v+ae_fabs(a->ptr.pp_double[k][j], _state); + } + cnorm->ptr.p_double[j] = v; + } + } + else + { + + /* + * A is lower triangular. + */ + for(j=1; j<=n-1; j++) + { + v = 0; + for(k=j+1; k<=n; k++) + { + v = v+ae_fabs(a->ptr.pp_double[k][j], _state); + } + cnorm->ptr.p_double[j] = v; + } + cnorm->ptr.p_double[n] = 0; + } + } + + /* + * Scale the column norms by TSCAL if the maximum element in CNORM is + * greater than BIGNUM. + */ + imax = 1; + for(k=2; k<=n; k++) + { + if( ae_fp_greater(cnorm->ptr.p_double[k],cnorm->ptr.p_double[imax]) ) + { + imax = k; + } + } + tmax = cnorm->ptr.p_double[imax]; + if( ae_fp_less_eq(tmax,bignum) ) + { + tscal = 1; + } + else + { + tscal = 1/(smlnum*tmax); + ae_v_muld(&cnorm->ptr.p_double[1], 1, ae_v_len(1,n), tscal); + } + + /* + * Compute a bound on the computed solution vector to see if the + * Level 2 BLAS routine DTRSV can be used. + */ + j = 1; + for(k=2; k<=n; k++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[k], _state),ae_fabs(x->ptr.p_double[j], _state)) ) + { + j = k; + } + } + xmax = ae_fabs(x->ptr.p_double[j], _state); + xbnd = xmax; + if( notran ) + { + + /* + * Compute the growth in A * x = b. + */ + if( upper ) + { + jfirst = n; + jlast = 1; + jinc = -1; + } + else + { + jfirst = 1; + jlast = n; + jinc = 1; + } + if( ae_fp_neq(tscal,1) ) + { + grow = 0; + } + else + { + if( nounit ) + { + + /* + * A is non-unit triangular. + * + * Compute GROW = 1/G(j) and XBND = 1/M(j). + * Initially, G(0) = max{x(i), i=1,...,n}. + */ + grow = 1/ae_maxreal(xbnd, smlnum, _state); + xbnd = grow; + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Exit the loop if the growth factor is too small. + */ + if( ae_fp_less_eq(grow,smlnum) ) + { + break; + } + + /* + * M(j) = G(j-1) / abs(A(j,j)) + */ + tjj = ae_fabs(a->ptr.pp_double[j][j], _state); + xbnd = ae_minreal(xbnd, ae_minreal(1, tjj, _state)*grow, _state); + if( ae_fp_greater_eq(tjj+cnorm->ptr.p_double[j],smlnum) ) + { + + /* + * G(j) = G(j-1)*( 1 + CNORM(j) / abs(A(j,j)) ) + */ + grow = grow*(tjj/(tjj+cnorm->ptr.p_double[j])); + } + else + { + + /* + * G(j) could overflow, set GROW to 0. + */ + grow = 0; + } + if( j==jlast ) + { + grow = xbnd; + } + j = j+jinc; + } + } + else + { + + /* + * A is unit triangular. + * + * Compute GROW = 1/G(j), where G(0) = max{x(i), i=1,...,n}. + */ + grow = ae_minreal(1, 1/ae_maxreal(xbnd, smlnum, _state), _state); + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Exit the loop if the growth factor is too small. + */ + if( ae_fp_less_eq(grow,smlnum) ) + { + break; + } + + /* + * G(j) = G(j-1)*( 1 + CNORM(j) ) + */ + grow = grow*(1/(1+cnorm->ptr.p_double[j])); + j = j+jinc; + } + } + } + } + else + { + + /* + * Compute the growth in A' * x = b. + */ + if( upper ) + { + jfirst = 1; + jlast = n; + jinc = 1; + } + else + { + jfirst = n; + jlast = 1; + jinc = -1; + } + if( ae_fp_neq(tscal,1) ) + { + grow = 0; + } + else + { + if( nounit ) + { + + /* + * A is non-unit triangular. + * + * Compute GROW = 1/G(j) and XBND = 1/M(j). + * Initially, M(0) = max{x(i), i=1,...,n}. + */ + grow = 1/ae_maxreal(xbnd, smlnum, _state); + xbnd = grow; + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Exit the loop if the growth factor is too small. + */ + if( ae_fp_less_eq(grow,smlnum) ) + { + break; + } + + /* + * G(j) = max( G(j-1), M(j-1)*( 1 + CNORM(j) ) ) + */ + xj = 1+cnorm->ptr.p_double[j]; + grow = ae_minreal(grow, xbnd/xj, _state); + + /* + * M(j) = M(j-1)*( 1 + CNORM(j) ) / abs(A(j,j)) + */ + tjj = ae_fabs(a->ptr.pp_double[j][j], _state); + if( ae_fp_greater(xj,tjj) ) + { + xbnd = xbnd*(tjj/xj); + } + if( j==jlast ) + { + grow = ae_minreal(grow, xbnd, _state); + } + j = j+jinc; + } + } + else + { + + /* + * A is unit triangular. + * + * Compute GROW = 1/G(j), where G(0) = max{x(i), i=1,...,n}. + */ + grow = ae_minreal(1, 1/ae_maxreal(xbnd, smlnum, _state), _state); + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Exit the loop if the growth factor is too small. + */ + if( ae_fp_less_eq(grow,smlnum) ) + { + break; + } + + /* + * G(j) = ( 1 + CNORM(j) )*G(j-1) + */ + xj = 1+cnorm->ptr.p_double[j]; + grow = grow/xj; + j = j+jinc; + } + } + } + } + if( ae_fp_greater(grow*tscal,smlnum) ) + { + + /* + * Use the Level 2 BLAS solve if the reciprocal of the bound on + * elements of X is not too small. + */ + if( (upper&¬ran)||(!upper&&!notran) ) + { + if( nounit ) + { + vd = a->ptr.pp_double[n][n]; + } + else + { + vd = 1; + } + x->ptr.p_double[n] = x->ptr.p_double[n]/vd; + for(i=n-1; i>=1; i--) + { + ip1 = i+1; + if( upper ) + { + v = ae_v_dotproduct(&a->ptr.pp_double[i][ip1], 1, &x->ptr.p_double[ip1], 1, ae_v_len(ip1,n)); + } + else + { + v = ae_v_dotproduct(&a->ptr.pp_double[ip1][i], a->stride, &x->ptr.p_double[ip1], 1, ae_v_len(ip1,n)); + } + if( nounit ) + { + vd = a->ptr.pp_double[i][i]; + } + else + { + vd = 1; + } + x->ptr.p_double[i] = (x->ptr.p_double[i]-v)/vd; + } + } + else + { + if( nounit ) + { + vd = a->ptr.pp_double[1][1]; + } + else + { + vd = 1; + } + x->ptr.p_double[1] = x->ptr.p_double[1]/vd; + for(i=2; i<=n; i++) + { + im1 = i-1; + if( upper ) + { + v = ae_v_dotproduct(&a->ptr.pp_double[1][i], a->stride, &x->ptr.p_double[1], 1, ae_v_len(1,im1)); + } + else + { + v = ae_v_dotproduct(&a->ptr.pp_double[i][1], 1, &x->ptr.p_double[1], 1, ae_v_len(1,im1)); + } + if( nounit ) + { + vd = a->ptr.pp_double[i][i]; + } + else + { + vd = 1; + } + x->ptr.p_double[i] = (x->ptr.p_double[i]-v)/vd; + } + } + } + else + { + + /* + * Use a Level 1 BLAS solve, scaling intermediate results. + */ + if( ae_fp_greater(xmax,bignum) ) + { + + /* + * Scale X so that its components are less than or equal to + * BIGNUM in absolute value. + */ + *s = bignum/xmax; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), *s); + xmax = bignum; + } + if( notran ) + { + + /* + * Solve A * x = b + */ + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Compute x(j) = b(j) / A(j,j), scaling x if necessary. + */ + xj = ae_fabs(x->ptr.p_double[j], _state); + flg = 0; + if( nounit ) + { + tjjs = a->ptr.pp_double[j][j]*tscal; + } + else + { + tjjs = tscal; + if( ae_fp_eq(tscal,1) ) + { + flg = 100; + } + } + if( flg!=100 ) + { + tjj = ae_fabs(tjjs, _state); + if( ae_fp_greater(tjj,smlnum) ) + { + + /* + * abs(A(j,j)) > SMLNUM: + */ + if( ae_fp_less(tjj,1) ) + { + if( ae_fp_greater(xj,tjj*bignum) ) + { + + /* + * Scale x by 1/b(j). + */ + rec = 1/xj; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + xmax = xmax*rec; + } + } + x->ptr.p_double[j] = x->ptr.p_double[j]/tjjs; + xj = ae_fabs(x->ptr.p_double[j], _state); + } + else + { + if( ae_fp_greater(tjj,0) ) + { + + /* + * 0 < abs(A(j,j)) <= SMLNUM: + */ + if( ae_fp_greater(xj,tjj*bignum) ) + { + + /* + * Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM + * to avoid overflow when dividing by A(j,j). + */ + rec = tjj*bignum/xj; + if( ae_fp_greater(cnorm->ptr.p_double[j],1) ) + { + + /* + * Scale by 1/CNORM(j) to avoid overflow when + * multiplying x(j) times column j. + */ + rec = rec/cnorm->ptr.p_double[j]; + } + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + xmax = xmax*rec; + } + x->ptr.p_double[j] = x->ptr.p_double[j]/tjjs; + xj = ae_fabs(x->ptr.p_double[j], _state); + } + else + { + + /* + * A(j,j) = 0: Set x(1:n) = 0, x(j) = 1, and + * scale = 0, and compute a solution to A*x = 0. + */ + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = 0; + } + x->ptr.p_double[j] = 1; + xj = 1; + *s = 0; + xmax = 0; + } + } + } + + /* + * Scale x if necessary to avoid overflow when adding a + * multiple of column j of A. + */ + if( ae_fp_greater(xj,1) ) + { + rec = 1/xj; + if( ae_fp_greater(cnorm->ptr.p_double[j],(bignum-xmax)*rec) ) + { + + /* + * Scale x by 1/(2*abs(x(j))). + */ + rec = rec*0.5; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + } + } + else + { + if( ae_fp_greater(xj*cnorm->ptr.p_double[j],bignum-xmax) ) + { + + /* + * Scale x by 1/2. + */ + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), 0.5); + *s = *s*0.5; + } + } + if( upper ) + { + if( j>1 ) + { + + /* + * Compute the update + * x(1:j-1) := x(1:j-1) - x(j) * A(1:j-1,j) + */ + v = x->ptr.p_double[j]*tscal; + jm1 = j-1; + ae_v_subd(&x->ptr.p_double[1], 1, &a->ptr.pp_double[1][j], a->stride, ae_v_len(1,jm1), v); + i = 1; + for(k=2; k<=j-1; k++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[k], _state),ae_fabs(x->ptr.p_double[i], _state)) ) + { + i = k; + } + } + xmax = ae_fabs(x->ptr.p_double[i], _state); + } + } + else + { + if( jptr.p_double[j]*tscal; + ae_v_subd(&x->ptr.p_double[jp1], 1, &a->ptr.pp_double[jp1][j], a->stride, ae_v_len(jp1,n), v); + i = j+1; + for(k=j+2; k<=n; k++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[k], _state),ae_fabs(x->ptr.p_double[i], _state)) ) + { + i = k; + } + } + xmax = ae_fabs(x->ptr.p_double[i], _state); + } + } + j = j+jinc; + } + } + else + { + + /* + * Solve A' * x = b + */ + j = jfirst; + while((jinc>0&&j<=jlast)||(jinc<0&&j>=jlast)) + { + + /* + * Compute x(j) = b(j) - sum A(k,j)*x(k). + * k<>j + */ + xj = ae_fabs(x->ptr.p_double[j], _state); + uscal = tscal; + rec = 1/ae_maxreal(xmax, 1, _state); + if( ae_fp_greater(cnorm->ptr.p_double[j],(bignum-xj)*rec) ) + { + + /* + * If x(j) could overflow, scale x by 1/(2*XMAX). + */ + rec = rec*0.5; + if( nounit ) + { + tjjs = a->ptr.pp_double[j][j]*tscal; + } + else + { + tjjs = tscal; + } + tjj = ae_fabs(tjjs, _state); + if( ae_fp_greater(tjj,1) ) + { + + /* + * Divide by A(j,j) when scaling x if A(j,j) > 1. + */ + rec = ae_minreal(1, rec*tjj, _state); + uscal = uscal/tjjs; + } + if( ae_fp_less(rec,1) ) + { + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + xmax = xmax*rec; + } + } + sumj = 0; + if( ae_fp_eq(uscal,1) ) + { + + /* + * If the scaling needed for A in the dot product is 1, + * call DDOT to perform the dot product. + */ + if( upper ) + { + if( j>1 ) + { + jm1 = j-1; + sumj = ae_v_dotproduct(&a->ptr.pp_double[1][j], a->stride, &x->ptr.p_double[1], 1, ae_v_len(1,jm1)); + } + else + { + sumj = 0; + } + } + else + { + if( jptr.pp_double[jp1][j], a->stride, &x->ptr.p_double[jp1], 1, ae_v_len(jp1,n)); + } + } + } + else + { + + /* + * Otherwise, use in-line code for the dot product. + */ + if( upper ) + { + for(i=1; i<=j-1; i++) + { + v = a->ptr.pp_double[i][j]*uscal; + sumj = sumj+v*x->ptr.p_double[i]; + } + } + else + { + if( jptr.pp_double[i][j]*uscal; + sumj = sumj+v*x->ptr.p_double[i]; + } + } + } + } + if( ae_fp_eq(uscal,tscal) ) + { + + /* + * Compute x(j) := ( x(j) - sumj ) / A(j,j) if 1/A(j,j) + * was not used to scale the dotproduct. + */ + x->ptr.p_double[j] = x->ptr.p_double[j]-sumj; + xj = ae_fabs(x->ptr.p_double[j], _state); + flg = 0; + if( nounit ) + { + tjjs = a->ptr.pp_double[j][j]*tscal; + } + else + { + tjjs = tscal; + if( ae_fp_eq(tscal,1) ) + { + flg = 150; + } + } + + /* + * Compute x(j) = x(j) / A(j,j), scaling if necessary. + */ + if( flg!=150 ) + { + tjj = ae_fabs(tjjs, _state); + if( ae_fp_greater(tjj,smlnum) ) + { + + /* + * abs(A(j,j)) > SMLNUM: + */ + if( ae_fp_less(tjj,1) ) + { + if( ae_fp_greater(xj,tjj*bignum) ) + { + + /* + * Scale X by 1/abs(x(j)). + */ + rec = 1/xj; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + xmax = xmax*rec; + } + } + x->ptr.p_double[j] = x->ptr.p_double[j]/tjjs; + } + else + { + if( ae_fp_greater(tjj,0) ) + { + + /* + * 0 < abs(A(j,j)) <= SMLNUM: + */ + if( ae_fp_greater(xj,tjj*bignum) ) + { + + /* + * Scale x by (1/abs(x(j)))*abs(A(j,j))*BIGNUM. + */ + rec = tjj*bignum/xj; + ae_v_muld(&x->ptr.p_double[1], 1, ae_v_len(1,n), rec); + *s = *s*rec; + xmax = xmax*rec; + } + x->ptr.p_double[j] = x->ptr.p_double[j]/tjjs; + } + else + { + + /* + * A(j,j) = 0: Set x(1:n) = 0, x(j) = 1, and + * scale = 0, and compute a solution to A'*x = 0. + */ + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = 0; + } + x->ptr.p_double[j] = 1; + *s = 0; + xmax = 0; + } + } + } + } + else + { + + /* + * Compute x(j) := x(j) / A(j,j) - sumj if the dot + * product has already been divided by 1/A(j,j). + */ + x->ptr.p_double[j] = x->ptr.p_double[j]/tjjs-sumj; + } + xmax = ae_maxreal(xmax, ae_fabs(x->ptr.p_double[j], _state), _state); + j = j+jinc; + } + } + *s = *s/tscal; + } + + /* + * Scale the column norms by 1/TSCAL for return. + */ + if( ae_fp_neq(tscal,1) ) + { + v = 1/tscal; + ae_v_muld(&cnorm->ptr.p_double[1], 1, ae_v_len(1,n), v); + } +} + + + + +/************************************************************************* +Real implementation of CMatrixScaledTRSafeSolve + + -- ALGLIB routine -- + 21.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixscaledtrsafesolve(/* Real */ ae_matrix* a, + double sa, + ae_int_t n, + /* Real */ ae_vector* x, + ae_bool isupper, + ae_int_t trans, + ae_bool isunit, + double maxgrowth, + ae_state *_state) +{ + ae_frame _frame_block; + double lnmax; + double nrmb; + double nrmx; + ae_int_t i; + ae_complex alpha; + ae_complex beta; + double vr; + ae_complex cx; + ae_vector tmp; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "RMatrixTRSafeSolve: incorrect N!", _state); + ae_assert(trans==0||trans==1, "RMatrixTRSafeSolve: incorrect Trans!", _state); + result = ae_true; + lnmax = ae_log(ae_maxrealnumber, _state); + + /* + * Quick return if possible + */ + if( n<=0 ) + { + ae_frame_leave(_state); + return result; + } + + /* + * Load norms: right part and X + */ + nrmb = 0; + for(i=0; i<=n-1; i++) + { + nrmb = ae_maxreal(nrmb, ae_fabs(x->ptr.p_double[i], _state), _state); + } + nrmx = 0; + + /* + * Solve + */ + ae_vector_set_length(&tmp, n, _state); + result = ae_true; + if( isupper&&trans==0 ) + { + + /* + * U*x = b + */ + for(i=n-1; i>=0; i--) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_complex_from_d(a->ptr.pp_double[i][i]*sa); + } + if( iptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sa); + vr = ae_v_dotproduct(&tmp.ptr.p_double[i+1], 1, &x->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1)); + beta = ae_complex_from_d(x->ptr.p_double[i]-vr); + } + else + { + beta = ae_complex_from_d(x->ptr.p_double[i]); + } + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &cx, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_double[i] = cx.x; + } + ae_frame_leave(_state); + return result; + } + if( !isupper&&trans==0 ) + { + + /* + * L*x = b + */ + for(i=0; i<=n-1; i++) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_complex_from_d(a->ptr.pp_double[i][i]*sa); + } + if( i>0 ) + { + ae_v_moved(&tmp.ptr.p_double[0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sa); + vr = ae_v_dotproduct(&tmp.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,i-1)); + beta = ae_complex_from_d(x->ptr.p_double[i]-vr); + } + else + { + beta = ae_complex_from_d(x->ptr.p_double[i]); + } + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &cx, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_double[i] = cx.x; + } + ae_frame_leave(_state); + return result; + } + if( isupper&&trans==1 ) + { + + /* + * U^T*x = b + */ + for(i=0; i<=n-1; i++) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_complex_from_d(a->ptr.pp_double[i][i]*sa); + } + beta = ae_complex_from_d(x->ptr.p_double[i]); + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &cx, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_double[i] = cx.x; + + /* + * update the rest of right part + */ + if( iptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sa); + ae_v_subd(&x->ptr.p_double[i+1], 1, &tmp.ptr.p_double[i+1], 1, ae_v_len(i+1,n-1), vr); + } + } + ae_frame_leave(_state); + return result; + } + if( !isupper&&trans==1 ) + { + + /* + * L^T*x = b + */ + for(i=n-1; i>=0; i--) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_complex_from_d(a->ptr.pp_double[i][i]*sa); + } + beta = ae_complex_from_d(x->ptr.p_double[i]); + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &cx, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_double[i] = cx.x; + + /* + * update the rest of right part + */ + if( i>0 ) + { + vr = cx.x; + ae_v_moved(&tmp.ptr.p_double[0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sa); + ae_v_subd(&x->ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,i-1), vr); + } + } + ae_frame_leave(_state); + return result; + } + result = ae_false; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Internal subroutine for safe solution of + + SA*op(A)=b + +where A is NxN upper/lower triangular/unitriangular matrix, op(A) is +either identity transform, transposition or Hermitian transposition, SA is +a scaling factor such that max(|SA*A[i,j]|) is close to 1.0 in magnutude. + +This subroutine limits relative growth of solution (in inf-norm) by +MaxGrowth, returning False if growth exceeds MaxGrowth. Degenerate or +near-degenerate matrices are handled correctly (False is returned) as long +as MaxGrowth is significantly less than MaxRealNumber/norm(b). + + -- ALGLIB routine -- + 21.01.2010 + Bochkanov Sergey +*************************************************************************/ +ae_bool cmatrixscaledtrsafesolve(/* Complex */ ae_matrix* a, + double sa, + ae_int_t n, + /* Complex */ ae_vector* x, + ae_bool isupper, + ae_int_t trans, + ae_bool isunit, + double maxgrowth, + ae_state *_state) +{ + ae_frame _frame_block; + double lnmax; + double nrmb; + double nrmx; + ae_int_t i; + ae_complex alpha; + ae_complex beta; + ae_complex vc; + ae_vector tmp; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0, "CMatrixTRSafeSolve: incorrect N!", _state); + ae_assert((trans==0||trans==1)||trans==2, "CMatrixTRSafeSolve: incorrect Trans!", _state); + result = ae_true; + lnmax = ae_log(ae_maxrealnumber, _state); + + /* + * Quick return if possible + */ + if( n<=0 ) + { + ae_frame_leave(_state); + return result; + } + + /* + * Load norms: right part and X + */ + nrmb = 0; + for(i=0; i<=n-1; i++) + { + nrmb = ae_maxreal(nrmb, ae_c_abs(x->ptr.p_complex[i], _state), _state); + } + nrmx = 0; + + /* + * Solve + */ + ae_vector_set_length(&tmp, n, _state); + result = ae_true; + if( isupper&&trans==0 ) + { + + /* + * U*x = b + */ + for(i=n-1; i>=0; i--) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(a->ptr.pp_complex[i][i],sa); + } + if( iptr.pp_complex[i][i+1], 1, "N", ae_v_len(i+1,n-1), sa); + vc = ae_v_cdotproduct(&tmp.ptr.p_complex[i+1], 1, "N", &x->ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1)); + beta = ae_c_sub(x->ptr.p_complex[i],vc); + } + else + { + beta = x->ptr.p_complex[i]; + } + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + } + ae_frame_leave(_state); + return result; + } + if( !isupper&&trans==0 ) + { + + /* + * L*x = b + */ + for(i=0; i<=n-1; i++) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(a->ptr.pp_complex[i][i],sa); + } + if( i>0 ) + { + ae_v_cmoved(&tmp.ptr.p_complex[0], 1, &a->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,i-1), sa); + vc = ae_v_cdotproduct(&tmp.ptr.p_complex[0], 1, "N", &x->ptr.p_complex[0], 1, "N", ae_v_len(0,i-1)); + beta = ae_c_sub(x->ptr.p_complex[i],vc); + } + else + { + beta = x->ptr.p_complex[i]; + } + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + } + ae_frame_leave(_state); + return result; + } + if( isupper&&trans==1 ) + { + + /* + * U^T*x = b + */ + for(i=0; i<=n-1; i++) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(a->ptr.pp_complex[i][i],sa); + } + beta = x->ptr.p_complex[i]; + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + + /* + * update the rest of right part + */ + if( iptr.pp_complex[i][i+1], 1, "N", ae_v_len(i+1,n-1), sa); + ae_v_csubc(&x->ptr.p_complex[i+1], 1, &tmp.ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1), vc); + } + } + ae_frame_leave(_state); + return result; + } + if( !isupper&&trans==1 ) + { + + /* + * L^T*x = b + */ + for(i=n-1; i>=0; i--) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(a->ptr.pp_complex[i][i],sa); + } + beta = x->ptr.p_complex[i]; + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + + /* + * update the rest of right part + */ + if( i>0 ) + { + ae_v_cmoved(&tmp.ptr.p_complex[0], 1, &a->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,i-1), sa); + ae_v_csubc(&x->ptr.p_complex[0], 1, &tmp.ptr.p_complex[0], 1, "N", ae_v_len(0,i-1), vc); + } + } + ae_frame_leave(_state); + return result; + } + if( isupper&&trans==2 ) + { + + /* + * U^H*x = b + */ + for(i=0; i<=n-1; i++) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(ae_c_conj(a->ptr.pp_complex[i][i], _state),sa); + } + beta = x->ptr.p_complex[i]; + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + + /* + * update the rest of right part + */ + if( iptr.pp_complex[i][i+1], 1, "Conj", ae_v_len(i+1,n-1), sa); + ae_v_csubc(&x->ptr.p_complex[i+1], 1, &tmp.ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1), vc); + } + } + ae_frame_leave(_state); + return result; + } + if( !isupper&&trans==2 ) + { + + /* + * L^T*x = b + */ + for(i=n-1; i>=0; i--) + { + + /* + * Task is reduced to alpha*x[i] = beta + */ + if( isunit ) + { + alpha = ae_complex_from_d(sa); + } + else + { + alpha = ae_c_mul_d(ae_c_conj(a->ptr.pp_complex[i][i], _state),sa); + } + beta = x->ptr.p_complex[i]; + + /* + * solve alpha*x[i] = beta + */ + result = safesolve_cbasicsolveandupdate(alpha, beta, lnmax, nrmb, maxgrowth, &nrmx, &vc, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + x->ptr.p_complex[i] = vc; + + /* + * update the rest of right part + */ + if( i>0 ) + { + ae_v_cmoved(&tmp.ptr.p_complex[0], 1, &a->ptr.pp_complex[i][0], 1, "Conj", ae_v_len(0,i-1), sa); + ae_v_csubc(&x->ptr.p_complex[0], 1, &tmp.ptr.p_complex[0], 1, "N", ae_v_len(0,i-1), vc); + } + } + ae_frame_leave(_state); + return result; + } + result = ae_false; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +complex basic solver-updater for reduced linear system + + alpha*x[i] = beta + +solves this equation and updates it in overlfow-safe manner (keeping track +of relative growth of solution). + +Parameters: + Alpha - alpha + Beta - beta + LnMax - precomputed Ln(MaxRealNumber) + BNorm - inf-norm of b (right part of original system) + MaxGrowth- maximum growth of norm(x) relative to norm(b) + XNorm - inf-norm of other components of X (which are already processed) + it is updated by CBasicSolveAndUpdate. + X - solution + + -- ALGLIB routine -- + 26.01.2009 + Bochkanov Sergey +*************************************************************************/ +static ae_bool safesolve_cbasicsolveandupdate(ae_complex alpha, + ae_complex beta, + double lnmax, + double bnorm, + double maxgrowth, + double* xnorm, + ae_complex* x, + ae_state *_state) +{ + double v; + ae_bool result; + + x->x = 0; + x->y = 0; + + result = ae_false; + if( ae_c_eq_d(alpha,0) ) + { + return result; + } + if( ae_c_neq_d(beta,0) ) + { + + /* + * alpha*x[i]=beta + */ + v = ae_log(ae_c_abs(beta, _state), _state)-ae_log(ae_c_abs(alpha, _state), _state); + if( ae_fp_greater(v,lnmax) ) + { + return result; + } + *x = ae_c_div(beta,alpha); + } + else + { + + /* + * alpha*x[i]=0 + */ + *x = ae_complex_from_d(0); + } + + /* + * update NrmX, test growth limit + */ + *xnorm = ae_maxreal(*xnorm, ae_c_abs(*x, _state), _state); + if( ae_fp_greater(*xnorm,maxgrowth*bnorm) ) + { + return result; + } + result = ae_true; + return result; +} + + + + +/************************************************************************* +Prepares HPC compuations of chunked gradient with HPCChunkedGradient(). +You have to call this function before calling HPCChunkedGradient() for +a new set of weights. You have to call it only once, see example below: + +HOW TO PROCESS DATASET WITH THIS FUNCTION: + Grad:=0 + HPCPrepareChunkedGradient(Weights, WCount, NTotal, NOut, Buf) + foreach chunk-of-dataset do + HPCChunkedGradient(...) + HPCFinalizeChunkedGradient(Buf, Grad) + +*************************************************************************/ +void hpcpreparechunkedgradient(/* Real */ ae_vector* weights, + ae_int_t wcount, + ae_int_t ntotal, + ae_int_t nin, + ae_int_t nout, + mlpbuffers* buf, + ae_state *_state) +{ + ae_int_t i; + ae_int_t batch4size; + ae_int_t chunksize; + + + chunksize = 4; + batch4size = 3*chunksize*ntotal+chunksize*(2*nout+1); + if( buf->xy.rowsxy.colsxy, chunksize, nin+nout, _state); + } + if( buf->xy2.rowsxy2.colsxy2, chunksize, nin+nout, _state); + } + if( buf->xyrow.cntxyrow, nin+nout, _state); + } + if( buf->x.cntx, nin, _state); + } + if( buf->y.cnty, nout, _state); + } + if( buf->desiredy.cntdesiredy, nout, _state); + } + if( buf->batch4buf.cntbatch4buf, batch4size, _state); + } + if( buf->hpcbuf.cnthpcbuf, wcount, _state); + } + if( buf->g.cntg, wcount, _state); + } + if( !hpccores_hpcpreparechunkedgradientx(weights, wcount, &buf->hpcbuf, _state) ) + { + for(i=0; i<=wcount-1; i++) + { + buf->hpcbuf.ptr.p_double[i] = 0.0; + } + } + buf->wcount = wcount; + buf->ntotal = ntotal; + buf->nin = nin; + buf->nout = nout; + buf->chunksize = chunksize; +} + + +/************************************************************************* +Finalizes HPC compuations of chunked gradient with HPCChunkedGradient(). +You have to call this function after calling HPCChunkedGradient() for +a new set of weights. You have to call it only once, see example below: + +HOW TO PROCESS DATASET WITH THIS FUNCTION: + Grad:=0 + HPCPrepareChunkedGradient(Weights, WCount, NTotal, NOut, Buf) + foreach chunk-of-dataset do + HPCChunkedGradient(...) + HPCFinalizeChunkedGradient(Buf, Grad) + +*************************************************************************/ +void hpcfinalizechunkedgradient(mlpbuffers* buf, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_int_t i; + + + if( !hpccores_hpcfinalizechunkedgradientx(&buf->hpcbuf, buf->wcount, grad, _state) ) + { + for(i=0; i<=buf->wcount-1; i++) + { + grad->ptr.p_double[i] = grad->ptr.p_double[i]+buf->hpcbuf.ptr.p_double[i]; + } + } +} + + +/************************************************************************* +Fast kernel for chunked gradient. + +*************************************************************************/ +ae_bool hpcchunkedgradient(/* Real */ ae_vector* weights, + /* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + double* e, + ae_bool naturalerrorfunc, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SSE2 + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_hpcchunkedgradient(weights, structinfo, columnmeans, columnsigmas, xy, cstart, csize, batch4buf, hpcbuf, e, naturalerrorfunc); +#endif +} + + +/************************************************************************* +Fast kernel for chunked processing. + +*************************************************************************/ +ae_bool hpcchunkedprocess(/* Real */ ae_vector* weights, + /* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SSE2 + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_hpcchunkedprocess(weights, structinfo, columnmeans, columnsigmas, xy, cstart, csize, batch4buf, hpcbuf); +#endif +} + + +/************************************************************************* +Stub function. + + -- ALGLIB routine -- + 14.06.2013 + Bochkanov Sergey +*************************************************************************/ +static ae_bool hpccores_hpcpreparechunkedgradientx(/* Real */ ae_vector* weights, + ae_int_t wcount, + /* Real */ ae_vector* hpcbuf, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SSE2 + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_hpcpreparechunkedgradientx(weights, wcount, hpcbuf); +#endif +} + + +/************************************************************************* +Stub function. + + -- ALGLIB routine -- + 14.06.2013 + Bochkanov Sergey +*************************************************************************/ +static ae_bool hpccores_hpcfinalizechunkedgradientx(/* Real */ ae_vector* buf, + ae_int_t wcount, + /* Real */ ae_vector* grad, + ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SSE2 + ae_bool result; + + + result = ae_false; + return result; +#else + return _ialglib_i_hpcfinalizechunkedgradientx(buf, wcount, grad); +#endif +} + + +ae_bool _mlpbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpbuffers *p = (mlpbuffers*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->batch4buf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hpcbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->xy2, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xyrow, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->desiredy, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mlpbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpbuffers *dst = (mlpbuffers*)_dst; + mlpbuffers *src = (mlpbuffers*)_src; + dst->chunksize = src->chunksize; + dst->ntotal = src->ntotal; + dst->nin = src->nin; + dst->nout = src->nout; + dst->wcount = src->wcount; + if( !ae_vector_init_copy(&dst->batch4buf, &src->batch4buf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->hpcbuf, &src->hpcbuf, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->xy2, &src->xy2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xyrow, &src->xyrow, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->desiredy, &src->desiredy, _state, make_automatic) ) + return ae_false; + dst->e = src->e; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp0, &src->tmp0, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mlpbuffers_clear(void* _p) +{ + mlpbuffers *p = (mlpbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->batch4buf); + ae_vector_clear(&p->hpcbuf); + ae_matrix_clear(&p->xy); + ae_matrix_clear(&p->xy2); + ae_vector_clear(&p->xyrow); + ae_vector_clear(&p->x); + ae_vector_clear(&p->y); + ae_vector_clear(&p->desiredy); + ae_vector_clear(&p->g); + ae_vector_clear(&p->tmp0); +} + + +void _mlpbuffers_destroy(void* _p) +{ + mlpbuffers *p = (mlpbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->batch4buf); + ae_vector_destroy(&p->hpcbuf); + ae_matrix_destroy(&p->xy); + ae_matrix_destroy(&p->xy2); + ae_vector_destroy(&p->xyrow); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->y); + ae_vector_destroy(&p->desiredy); + ae_vector_destroy(&p->g); + ae_vector_destroy(&p->tmp0); +} + + + + +/************************************************************************* +More precise dot-product. Absolute error of subroutine result is about +1 ulp of max(MX,V), where: + MX = max( |a[i]*b[i]| ) + V = |(a,b)| + +INPUT PARAMETERS + A - array[0..N-1], vector 1 + B - array[0..N-1], vector 2 + N - vectors length, N<2^29. + Temp - array[0..N-1], pre-allocated temporary storage + +OUTPUT PARAMETERS + R - (A,B) + RErr - estimate of error. This estimate accounts for both errors + during calculation of (A,B) and errors introduced by + rounding of A and B to fit in double (about 1 ulp). + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +void xdot(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* temp, + double* r, + double* rerr, + ae_state *_state) +{ + ae_int_t i; + double mx; + double v; + + *r = 0; + *rerr = 0; + + + /* + * special cases: + * * N=0 + */ + if( n==0 ) + { + *r = 0; + *rerr = 0; + return; + } + mx = 0; + for(i=0; i<=n-1; i++) + { + v = a->ptr.p_double[i]*b->ptr.p_double[i]; + temp->ptr.p_double[i] = v; + mx = ae_maxreal(mx, ae_fabs(v, _state), _state); + } + if( ae_fp_eq(mx,0) ) + { + *r = 0; + *rerr = 0; + return; + } + xblas_xsum(temp, mx, n, r, rerr, _state); +} + + +/************************************************************************* +More precise complex dot-product. Absolute error of subroutine result is +about 1 ulp of max(MX,V), where: + MX = max( |a[i]*b[i]| ) + V = |(a,b)| + +INPUT PARAMETERS + A - array[0..N-1], vector 1 + B - array[0..N-1], vector 2 + N - vectors length, N<2^29. + Temp - array[0..2*N-1], pre-allocated temporary storage + +OUTPUT PARAMETERS + R - (A,B) + RErr - estimate of error. This estimate accounts for both errors + during calculation of (A,B) and errors introduced by + rounding of A and B to fit in double (about 1 ulp). + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void xcdot(/* Complex */ ae_vector* a, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* temp, + ae_complex* r, + double* rerr, + ae_state *_state) +{ + ae_int_t i; + double mx; + double v; + double rerrx; + double rerry; + + r->x = 0; + r->y = 0; + *rerr = 0; + + + /* + * special cases: + * * N=0 + */ + if( n==0 ) + { + *r = ae_complex_from_d(0); + *rerr = 0; + return; + } + + /* + * calculate real part + */ + mx = 0; + for(i=0; i<=n-1; i++) + { + v = a->ptr.p_complex[i].x*b->ptr.p_complex[i].x; + temp->ptr.p_double[2*i+0] = v; + mx = ae_maxreal(mx, ae_fabs(v, _state), _state); + v = -a->ptr.p_complex[i].y*b->ptr.p_complex[i].y; + temp->ptr.p_double[2*i+1] = v; + mx = ae_maxreal(mx, ae_fabs(v, _state), _state); + } + if( ae_fp_eq(mx,0) ) + { + r->x = 0; + rerrx = 0; + } + else + { + xblas_xsum(temp, mx, 2*n, &r->x, &rerrx, _state); + } + + /* + * calculate imaginary part + */ + mx = 0; + for(i=0; i<=n-1; i++) + { + v = a->ptr.p_complex[i].x*b->ptr.p_complex[i].y; + temp->ptr.p_double[2*i+0] = v; + mx = ae_maxreal(mx, ae_fabs(v, _state), _state); + v = a->ptr.p_complex[i].y*b->ptr.p_complex[i].x; + temp->ptr.p_double[2*i+1] = v; + mx = ae_maxreal(mx, ae_fabs(v, _state), _state); + } + if( ae_fp_eq(mx,0) ) + { + r->y = 0; + rerry = 0; + } + else + { + xblas_xsum(temp, mx, 2*n, &r->y, &rerry, _state); + } + + /* + * total error + */ + if( ae_fp_eq(rerrx,0)&&ae_fp_eq(rerry,0) ) + { + *rerr = 0; + } + else + { + *rerr = ae_maxreal(rerrx, rerry, _state)*ae_sqrt(1+ae_sqr(ae_minreal(rerrx, rerry, _state)/ae_maxreal(rerrx, rerry, _state), _state), _state); + } +} + + +/************************************************************************* +Internal subroutine for extra-precise calculation of SUM(w[i]). + +INPUT PARAMETERS: + W - array[0..N-1], values to be added + W is modified during calculations. + MX - max(W[i]) + N - array size + +OUTPUT PARAMETERS: + R - SUM(w[i]) + RErr- error estimate for R + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +static void xblas_xsum(/* Real */ ae_vector* w, + double mx, + ae_int_t n, + double* r, + double* rerr, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t ks; + double v; + double s; + double ln2; + double chunk; + double invchunk; + ae_bool allzeros; + + *r = 0; + *rerr = 0; + + + /* + * special cases: + * * N=0 + * * N is too large to use integer arithmetics + */ + if( n==0 ) + { + *r = 0; + *rerr = 0; + return; + } + if( ae_fp_eq(mx,0) ) + { + *r = 0; + *rerr = 0; + return; + } + ae_assert(n<536870912, "XDot: N is too large!", _state); + + /* + * Prepare + */ + ln2 = ae_log(2, _state); + *rerr = mx*ae_machineepsilon; + + /* + * 1. find S such that 0.5<=S*MX<1 + * 2. multiply W by S, so task is normalized in some sense + * 3. S:=1/S so we can obtain original vector multiplying by S + */ + k = ae_round(ae_log(mx, _state)/ln2, _state); + s = xblas_xfastpow(2, -k, _state); + while(ae_fp_greater_eq(s*mx,1)) + { + s = 0.5*s; + } + while(ae_fp_less(s*mx,0.5)) + { + s = 2*s; + } + ae_v_muld(&w->ptr.p_double[0], 1, ae_v_len(0,n-1), s); + s = 1/s; + + /* + * find Chunk=2^M such that N*Chunk<2^29 + * + * we have chosen upper limit (2^29) with enough space left + * to tolerate possible problems with rounding and N's close + * to the limit, so we don't want to be very strict here. + */ + k = ae_trunc(ae_log((double)536870912/(double)n, _state)/ln2, _state); + chunk = xblas_xfastpow(2, k, _state); + if( ae_fp_less(chunk,2) ) + { + chunk = 2; + } + invchunk = 1/chunk; + + /* + * calculate result + */ + *r = 0; + ae_v_muld(&w->ptr.p_double[0], 1, ae_v_len(0,n-1), chunk); + for(;;) + { + s = s*invchunk; + allzeros = ae_true; + ks = 0; + for(i=0; i<=n-1; i++) + { + v = w->ptr.p_double[i]; + k = ae_trunc(v, _state); + if( ae_fp_neq(v,k) ) + { + allzeros = ae_false; + } + w->ptr.p_double[i] = chunk*(v-k); + ks = ks+k; + } + *r = *r+s*ks; + v = ae_fabs(*r, _state); + if( allzeros||ae_fp_eq(s*n+mx,mx) ) + { + break; + } + } + + /* + * correct error + */ + *rerr = ae_maxreal(*rerr, ae_fabs(*r, _state)*ae_machineepsilon, _state); +} + + +/************************************************************************* +Fast Pow + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +static double xblas_xfastpow(double r, ae_int_t n, ae_state *_state) +{ + double result; + + + result = 0; + if( n>0 ) + { + if( n%2==0 ) + { + result = ae_sqr(xblas_xfastpow(r, n/2, _state), _state); + } + else + { + result = r*xblas_xfastpow(r, n-1, _state); + } + return result; + } + if( n==0 ) + { + result = 1; + } + if( n<0 ) + { + result = xblas_xfastpow(1/r, -n, _state); + } + return result; +} + + + + +/************************************************************************* +Normalizes direction/step pair: makes |D|=1, scales Stp. +If |D|=0, it returns, leavind D/Stp unchanged. + + -- ALGLIB -- + Copyright 01.04.2010 by Bochkanov Sergey +*************************************************************************/ +void linminnormalized(/* Real */ ae_vector* d, + double* stp, + ae_int_t n, + ae_state *_state) +{ + double mx; + double s; + ae_int_t i; + + + + /* + * first, scale D to avoid underflow/overflow durng squaring + */ + mx = 0; + for(i=0; i<=n-1; i++) + { + mx = ae_maxreal(mx, ae_fabs(d->ptr.p_double[i], _state), _state); + } + if( ae_fp_eq(mx,0) ) + { + return; + } + s = 1/mx; + ae_v_muld(&d->ptr.p_double[0], 1, ae_v_len(0,n-1), s); + *stp = *stp/s; + + /* + * normalize D + */ + s = ae_v_dotproduct(&d->ptr.p_double[0], 1, &d->ptr.p_double[0], 1, ae_v_len(0,n-1)); + s = 1/ae_sqrt(s, _state); + ae_v_muld(&d->ptr.p_double[0], 1, ae_v_len(0,n-1), s); + *stp = *stp/s; +} + + +/************************************************************************* +THE PURPOSE OF MCSRCH IS TO FIND A STEP WHICH SATISFIES A SUFFICIENT +DECREASE CONDITION AND A CURVATURE CONDITION. + +AT EACH STAGE THE SUBROUTINE UPDATES AN INTERVAL OF UNCERTAINTY WITH +ENDPOINTS STX AND STY. THE INTERVAL OF UNCERTAINTY IS INITIALLY CHOSEN +SO THAT IT CONTAINS A MINIMIZER OF THE MODIFIED FUNCTION + + F(X+STP*S) - F(X) - FTOL*STP*(GRADF(X)'S). + +IF A STEP IS OBTAINED FOR WHICH THE MODIFIED FUNCTION HAS A NONPOSITIVE +FUNCTION VALUE AND NONNEGATIVE DERIVATIVE, THEN THE INTERVAL OF +UNCERTAINTY IS CHOSEN SO THAT IT CONTAINS A MINIMIZER OF F(X+STP*S). + +THE ALGORITHM IS DESIGNED TO FIND A STEP WHICH SATISFIES THE SUFFICIENT +DECREASE CONDITION + + F(X+STP*S) .LE. F(X) + FTOL*STP*(GRADF(X)'S), + +AND THE CURVATURE CONDITION + + ABS(GRADF(X+STP*S)'S)) .LE. GTOL*ABS(GRADF(X)'S). + +IF FTOL IS LESS THAN GTOL AND IF, FOR EXAMPLE, THE FUNCTION IS BOUNDED +BELOW, THEN THERE IS ALWAYS A STEP WHICH SATISFIES BOTH CONDITIONS. +IF NO STEP CAN BE FOUND WHICH SATISFIES BOTH CONDITIONS, THEN THE +ALGORITHM USUALLY STOPS WHEN ROUNDING ERRORS PREVENT FURTHER PROGRESS. +IN THIS CASE STP ONLY SATISFIES THE SUFFICIENT DECREASE CONDITION. + + +:::::::::::::IMPORTANT NOTES::::::::::::: + +NOTE 1: + +This routine guarantees that it will stop at the last point where function +value was calculated. It won't make several additional function evaluations +after finding good point. So if you store function evaluations requested by +this routine, you can be sure that last one is the point where we've stopped. + +NOTE 2: + +when 0xtrapf = 4.0; + zero = 0; + if( ae_fp_eq(stpmax,0) ) + { + stpmax = linmin_defstpmax; + } + if( ae_fp_less(*stp,linmin_stpmin) ) + { + *stp = linmin_stpmin; + } + if( ae_fp_greater(*stp,stpmax) ) + { + *stp = stpmax; + } + + /* + * Main cycle + */ + for(;;) + { + if( *stage==0 ) + { + + /* + * NEXT + */ + *stage = 2; + continue; + } + if( *stage==2 ) + { + state->infoc = 1; + *info = 0; + + /* + * CHECK THE INPUT PARAMETERS FOR ERRORS. + */ + if( ae_fp_less(stpmax,linmin_stpmin)&&ae_fp_greater(stpmax,0) ) + { + *info = 5; + *stp = 0.0; + return; + } + if( ((((((n<=0||ae_fp_less_eq(*stp,0))||ae_fp_less(linmin_ftol,0))||ae_fp_less(gtol,zero))||ae_fp_less(linmin_xtol,zero))||ae_fp_less(linmin_stpmin,zero))||ae_fp_less(stpmax,linmin_stpmin))||linmin_maxfev<=0 ) + { + *stage = 0; + return; + } + + /* + * COMPUTE THE INITIAL GRADIENT IN THE SEARCH DIRECTION + * AND CHECK THAT S IS A DESCENT DIRECTION. + */ + v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->dginit = v; + if( ae_fp_greater_eq(state->dginit,0) ) + { + *stage = 0; + return; + } + + /* + * INITIALIZE LOCAL VARIABLES. + */ + state->brackt = ae_false; + state->stage1 = ae_true; + *nfev = 0; + state->finit = *f; + state->dgtest = linmin_ftol*state->dginit; + state->width = stpmax-linmin_stpmin; + state->width1 = state->width/p5; + ae_v_move(&wa->ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * THE VARIABLES STX, FX, DGX CONTAIN THE VALUES OF THE STEP, + * FUNCTION, AND DIRECTIONAL DERIVATIVE AT THE BEST STEP. + * THE VARIABLES STY, FY, DGY CONTAIN THE VALUE OF THE STEP, + * FUNCTION, AND DERIVATIVE AT THE OTHER ENDPOINT OF + * THE INTERVAL OF UNCERTAINTY. + * THE VARIABLES STP, F, DG CONTAIN THE VALUES OF THE STEP, + * FUNCTION, AND DERIVATIVE AT THE CURRENT STEP. + */ + state->stx = 0; + state->fx = state->finit; + state->dgx = state->dginit; + state->sty = 0; + state->fy = state->finit; + state->dgy = state->dginit; + + /* + * NEXT + */ + *stage = 3; + continue; + } + if( *stage==3 ) + { + + /* + * START OF ITERATION. + * + * SET THE MINIMUM AND MAXIMUM STEPS TO CORRESPOND + * TO THE PRESENT INTERVAL OF UNCERTAINTY. + */ + if( state->brackt ) + { + if( ae_fp_less(state->stx,state->sty) ) + { + state->stmin = state->stx; + state->stmax = state->sty; + } + else + { + state->stmin = state->sty; + state->stmax = state->stx; + } + } + else + { + state->stmin = state->stx; + state->stmax = *stp+state->xtrapf*(*stp-state->stx); + } + + /* + * FORCE THE STEP TO BE WITHIN THE BOUNDS STPMAX AND STPMIN. + */ + if( ae_fp_greater(*stp,stpmax) ) + { + *stp = stpmax; + } + if( ae_fp_less(*stp,linmin_stpmin) ) + { + *stp = linmin_stpmin; + } + + /* + * IF AN UNUSUAL TERMINATION IS TO OCCUR THEN LET + * STP BE THE LOWEST POINT OBTAINED SO FAR. + */ + if( (((state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||*nfev>=linmin_maxfev-1)||state->infoc==0)||(state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,linmin_xtol*state->stmax)) ) + { + *stp = state->stx; + } + + /* + * EVALUATE THE FUNCTION AND GRADIENT AT STP + * AND COMPUTE THE DIRECTIONAL DERIVATIVE. + */ + ae_v_move(&x->ptr.p_double[0], 1, &wa->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&x->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1), *stp); + + /* + * NEXT + */ + *stage = 4; + return; + } + if( *stage==4 ) + { + *info = 0; + *nfev = *nfev+1; + v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->dg = v; + state->ftest1 = state->finit+*stp*state->dgtest; + + /* + * TEST FOR CONVERGENCE. + */ + if( (state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||state->infoc==0 ) + { + *info = 6; + } + if( (ae_fp_eq(*stp,stpmax)&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_less_eq(state->dg,state->dgtest) ) + { + *info = 5; + } + if( ae_fp_eq(*stp,linmin_stpmin)&&(ae_fp_greater(*f,state->ftest1)||ae_fp_greater_eq(state->dg,state->dgtest)) ) + { + *info = 4; + } + if( *nfev>=linmin_maxfev ) + { + *info = 3; + } + if( state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,linmin_xtol*state->stmax) ) + { + *info = 2; + } + if( ae_fp_less_eq(*f,state->ftest1)&&ae_fp_less_eq(ae_fabs(state->dg, _state),-gtol*state->dginit) ) + { + *info = 1; + } + + /* + * CHECK FOR TERMINATION. + */ + if( *info!=0 ) + { + *stage = 0; + return; + } + + /* + * IN THE FIRST STAGE WE SEEK A STEP FOR WHICH THE MODIFIED + * FUNCTION HAS A NONPOSITIVE VALUE AND NONNEGATIVE DERIVATIVE. + */ + if( (state->stage1&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_greater_eq(state->dg,ae_minreal(linmin_ftol, gtol, _state)*state->dginit) ) + { + state->stage1 = ae_false; + } + + /* + * A MODIFIED FUNCTION IS USED TO PREDICT THE STEP ONLY IF + * WE HAVE NOT OBTAINED A STEP FOR WHICH THE MODIFIED + * FUNCTION HAS A NONPOSITIVE FUNCTION VALUE AND NONNEGATIVE + * DERIVATIVE, AND IF A LOWER FUNCTION VALUE HAS BEEN + * OBTAINED BUT THE DECREASE IS NOT SUFFICIENT. + */ + if( (state->stage1&&ae_fp_less_eq(*f,state->fx))&&ae_fp_greater(*f,state->ftest1) ) + { + + /* + * DEFINE THE MODIFIED FUNCTION AND DERIVATIVE VALUES. + */ + state->fm = *f-*stp*state->dgtest; + state->fxm = state->fx-state->stx*state->dgtest; + state->fym = state->fy-state->sty*state->dgtest; + state->dgm = state->dg-state->dgtest; + state->dgxm = state->dgx-state->dgtest; + state->dgym = state->dgy-state->dgtest; + + /* + * CALL CSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY + * AND TO COMPUTE THE NEW STEP. + */ + linmin_mcstep(&state->stx, &state->fxm, &state->dgxm, &state->sty, &state->fym, &state->dgym, stp, state->fm, state->dgm, &state->brackt, state->stmin, state->stmax, &state->infoc, _state); + + /* + * RESET THE FUNCTION AND GRADIENT VALUES FOR F. + */ + state->fx = state->fxm+state->stx*state->dgtest; + state->fy = state->fym+state->sty*state->dgtest; + state->dgx = state->dgxm+state->dgtest; + state->dgy = state->dgym+state->dgtest; + } + else + { + + /* + * CALL MCSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY + * AND TO COMPUTE THE NEW STEP. + */ + linmin_mcstep(&state->stx, &state->fx, &state->dgx, &state->sty, &state->fy, &state->dgy, stp, *f, state->dg, &state->brackt, state->stmin, state->stmax, &state->infoc, _state); + } + + /* + * FORCE A SUFFICIENT DECREASE IN THE SIZE OF THE + * INTERVAL OF UNCERTAINTY. + */ + if( state->brackt ) + { + if( ae_fp_greater_eq(ae_fabs(state->sty-state->stx, _state),p66*state->width1) ) + { + *stp = state->stx+p5*(state->sty-state->stx); + } + state->width1 = state->width; + state->width = ae_fabs(state->sty-state->stx, _state); + } + + /* + * NEXT. + */ + *stage = 3; + continue; + } + } +} + + +/************************************************************************* +These functions perform Armijo line search using at most FMAX function +evaluations. It doesn't enforce some kind of " sufficient decrease" +criterion - it just tries different Armijo steps and returns optimum found +so far. + +Optimization is done using F-rcomm interface: +* ArmijoCreate initializes State structure + (reusing previously allocated buffers) +* ArmijoIteration is subsequently called +* ArmijoResults returns results + +INPUT PARAMETERS: + N - problem size + X - array[N], starting point + F - F(X+S*STP) + S - step direction, S>0 + STP - step length + STPMAX - maximum value for STP or zero (if no limit is imposed) + FMAX - maximum number of function evaluations + State - optimization state + + -- ALGLIB -- + Copyright 05.10.2010 by Bochkanov Sergey +*************************************************************************/ +void armijocreate(ae_int_t n, + /* Real */ ae_vector* x, + double f, + /* Real */ ae_vector* s, + double stp, + double stpmax, + ae_int_t fmax, + armijostate* state, + ae_state *_state) +{ + + + if( state->x.cntx, n, _state); + } + if( state->xbase.cntxbase, n, _state); + } + if( state->s.cnts, n, _state); + } + state->stpmax = stpmax; + state->fmax = fmax; + state->stplen = stp; + state->fcur = f; + state->n = n; + ae_v_move(&state->xbase.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->s.ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_vector_set_length(&state->rstate.ia, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 0+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +This is rcomm-based search function + + -- ALGLIB -- + Copyright 05.10.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool armijoiteration(armijostate* state, ae_state *_state) +{ + double v; + ae_int_t n; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + v = state->rstate.ra.ptr.p_double[0]; + } + else + { + n = -983; + v = -989; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + + /* + * Routine body + */ + if( (ae_fp_less_eq(state->stplen,0)||ae_fp_less(state->stpmax,0))||state->fmax<2 ) + { + state->info = 0; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->stplen,linmin_stpmin) ) + { + state->info = 4; + result = ae_false; + return result; + } + n = state->n; + state->nfev = 0; + + /* + * We always need F + */ + state->needf = ae_true; + + /* + * Bound StpLen + */ + if( ae_fp_greater(state->stplen,state->stpmax)&&ae_fp_neq(state->stpmax,0) ) + { + state->stplen = state->stpmax; + } + + /* + * Increase length + */ + v = state->stplen*linmin_armijofactor; + if( ae_fp_greater(v,state->stpmax)&&ae_fp_neq(state->stpmax,0) ) + { + v = state->stpmax; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->s.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->nfev = state->nfev+1; + if( ae_fp_greater_eq(state->f,state->fcur) ) + { + goto lbl_4; + } + state->stplen = v; + state->fcur = state->f; +lbl_6: + if( ae_false ) + { + goto lbl_7; + } + + /* + * test stopping conditions + */ + if( state->nfev>=state->fmax ) + { + state->info = 3; + result = ae_false; + return result; + } + if( ae_fp_greater_eq(state->stplen,state->stpmax) ) + { + state->info = 5; + result = ae_false; + return result; + } + + /* + * evaluate F + */ + v = state->stplen*linmin_armijofactor; + if( ae_fp_greater(v,state->stpmax)&&ae_fp_neq(state->stpmax,0) ) + { + v = state->stpmax; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->s.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->nfev = state->nfev+1; + + /* + * make decision + */ + if( ae_fp_less(state->f,state->fcur) ) + { + state->stplen = v; + state->fcur = state->f; + } + else + { + state->info = 1; + result = ae_false; + return result; + } + goto lbl_6; +lbl_7: +lbl_4: + + /* + * Decrease length + */ + v = state->stplen/linmin_armijofactor; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->s.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->nfev = state->nfev+1; + if( ae_fp_greater_eq(state->f,state->fcur) ) + { + goto lbl_8; + } + state->stplen = state->stplen/linmin_armijofactor; + state->fcur = state->f; +lbl_10: + if( ae_false ) + { + goto lbl_11; + } + + /* + * test stopping conditions + */ + if( state->nfev>=state->fmax ) + { + state->info = 3; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->stplen,linmin_stpmin) ) + { + state->info = 4; + result = ae_false; + return result; + } + + /* + * evaluate F + */ + v = state->stplen/linmin_armijofactor; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->s.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->nfev = state->nfev+1; + + /* + * make decision + */ + if( ae_fp_less(state->f,state->fcur) ) + { + state->stplen = state->stplen/linmin_armijofactor; + state->fcur = state->f; + } + else + { + state->info = 1; + result = ae_false; + return result; + } + goto lbl_10; +lbl_11: +lbl_8: + + /* + * Nothing to be done + */ + state->info = 1; + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ra.ptr.p_double[0] = v; + return result; +} + + +/************************************************************************* +Results of Armijo search + +OUTPUT PARAMETERS: + INFO - on output it is set to one of the return codes: + * 0 improper input params + * 1 optimum step is found with at most FMAX evaluations + * 3 FMAX evaluations were used, + X contains optimum found so far + * 4 step is at lower bound STPMIN + * 5 step is at upper bound + STP - step length (in case of failure it is still returned) + F - function value (in case of failure it is still returned) + + -- ALGLIB -- + Copyright 05.10.2010 by Bochkanov Sergey +*************************************************************************/ +void armijoresults(armijostate* state, + ae_int_t* info, + double* stp, + double* f, + ae_state *_state) +{ + + + *info = state->info; + *stp = state->stplen; + *f = state->fcur; +} + + +static void linmin_mcstep(double* stx, + double* fx, + double* dx, + double* sty, + double* fy, + double* dy, + double* stp, + double fp, + double dp, + ae_bool* brackt, + double stmin, + double stmax, + ae_int_t* info, + ae_state *_state) +{ + ae_bool bound; + double gamma; + double p; + double q; + double r; + double s; + double sgnd; + double stpc; + double stpf; + double stpq; + double theta; + + + *info = 0; + + /* + * CHECK THE INPUT PARAMETERS FOR ERRORS. + */ + if( ((*brackt&&(ae_fp_less_eq(*stp,ae_minreal(*stx, *sty, _state))||ae_fp_greater_eq(*stp,ae_maxreal(*stx, *sty, _state))))||ae_fp_greater_eq(*dx*(*stp-(*stx)),0))||ae_fp_less(stmax,stmin) ) + { + return; + } + + /* + * DETERMINE IF THE DERIVATIVES HAVE OPPOSITE SIGN. + */ + sgnd = dp*(*dx/ae_fabs(*dx, _state)); + + /* + * FIRST CASE. A HIGHER FUNCTION VALUE. + * THE MINIMUM IS BRACKETED. IF THE CUBIC STEP IS CLOSER + * TO STX THAN THE QUADRATIC STEP, THE CUBIC STEP IS TAKEN, + * ELSE THE AVERAGE OF THE CUBIC AND QUADRATIC STEPS IS TAKEN. + */ + if( ae_fp_greater(fp,*fx) ) + { + *info = 1; + bound = ae_true; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state); + if( ae_fp_less(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-(*dx)+theta; + q = gamma-(*dx)+gamma+dp; + r = p/q; + stpc = *stx+r*(*stp-(*stx)); + stpq = *stx+*dx/((*fx-fp)/(*stp-(*stx))+(*dx))/2*(*stp-(*stx)); + if( ae_fp_less(ae_fabs(stpc-(*stx), _state),ae_fabs(stpq-(*stx), _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpc+(stpq-stpc)/2; + } + *brackt = ae_true; + } + else + { + if( ae_fp_less(sgnd,0) ) + { + + /* + * SECOND CASE. A LOWER FUNCTION VALUE AND DERIVATIVES OF + * OPPOSITE SIGN. THE MINIMUM IS BRACKETED. IF THE CUBIC + * STEP IS CLOSER TO STX THAN THE QUADRATIC (SECANT) STEP, + * THE CUBIC STEP IS TAKEN, ELSE THE QUADRATIC STEP IS TAKEN. + */ + *info = 2; + bound = ae_false; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state); + if( ae_fp_greater(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma-dp+gamma+(*dx); + r = p/q; + stpc = *stp+r*(*stx-(*stp)); + stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp)); + if( ae_fp_greater(ae_fabs(stpc-(*stp), _state),ae_fabs(stpq-(*stp), _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + *brackt = ae_true; + } + else + { + if( ae_fp_less(ae_fabs(dp, _state),ae_fabs(*dx, _state)) ) + { + + /* + * THIRD CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE + * SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DECREASES. + * THE CUBIC STEP IS ONLY USED IF THE CUBIC TENDS TO INFINITY + * IN THE DIRECTION OF THE STEP OR IF THE MINIMUM OF THE CUBIC + * IS BEYOND STP. OTHERWISE THE CUBIC STEP IS DEFINED TO BE + * EITHER STPMIN OR STPMAX. THE QUADRATIC (SECANT) STEP IS ALSO + * COMPUTED AND IF THE MINIMUM IS BRACKETED THEN THE THE STEP + * CLOSEST TO STX IS TAKEN, ELSE THE STEP FARTHEST AWAY IS TAKEN. + */ + *info = 3; + bound = ae_true; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + + /* + * THE CASE GAMMA = 0 ONLY ARISES IF THE CUBIC DOES NOT TEND + * TO INFINITY IN THE DIRECTION OF THE STEP. + */ + gamma = s*ae_sqrt(ae_maxreal(0, ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state), _state); + if( ae_fp_greater(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma+(*dx-dp)+gamma; + r = p/q; + if( ae_fp_less(r,0)&&ae_fp_neq(gamma,0) ) + { + stpc = *stp+r*(*stx-(*stp)); + } + else + { + if( ae_fp_greater(*stp,*stx) ) + { + stpc = stmax; + } + else + { + stpc = stmin; + } + } + stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp)); + if( *brackt ) + { + if( ae_fp_less(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + else + { + if( ae_fp_greater(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + } + else + { + + /* + * FOURTH CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE + * SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DOES + * NOT DECREASE. IF THE MINIMUM IS NOT BRACKETED, THE STEP + * IS EITHER STPMIN OR STPMAX, ELSE THE CUBIC STEP IS TAKEN. + */ + *info = 4; + bound = ae_false; + if( *brackt ) + { + theta = 3*(fp-(*fy))/(*sty-(*stp))+(*dy)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dy, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dy/s*(dp/s), _state); + if( ae_fp_greater(*stp,*sty) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma-dp+gamma+(*dy); + r = p/q; + stpc = *stp+r*(*sty-(*stp)); + stpf = stpc; + } + else + { + if( ae_fp_greater(*stp,*stx) ) + { + stpf = stmax; + } + else + { + stpf = stmin; + } + } + } + } + } + + /* + * UPDATE THE INTERVAL OF UNCERTAINTY. THIS UPDATE DOES NOT + * DEPEND ON THE NEW STEP OR THE CASE ANALYSIS ABOVE. + */ + if( ae_fp_greater(fp,*fx) ) + { + *sty = *stp; + *fy = fp; + *dy = dp; + } + else + { + if( ae_fp_less(sgnd,0.0) ) + { + *sty = *stx; + *fy = *fx; + *dy = *dx; + } + *stx = *stp; + *fx = fp; + *dx = dp; + } + + /* + * COMPUTE THE NEW STEP AND SAFEGUARD IT. + */ + stpf = ae_minreal(stmax, stpf, _state); + stpf = ae_maxreal(stmin, stpf, _state); + *stp = stpf; + if( *brackt&&bound ) + { + if( ae_fp_greater(*sty,*stx) ) + { + *stp = ae_minreal(*stx+0.66*(*sty-(*stx)), *stp, _state); + } + else + { + *stp = ae_maxreal(*stx+0.66*(*sty-(*stx)), *stp, _state); + } + } +} + + +ae_bool _linminstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + linminstate *p = (linminstate*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _linminstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + linminstate *dst = (linminstate*)_dst; + linminstate *src = (linminstate*)_src; + dst->brackt = src->brackt; + dst->stage1 = src->stage1; + dst->infoc = src->infoc; + dst->dg = src->dg; + dst->dgm = src->dgm; + dst->dginit = src->dginit; + dst->dgtest = src->dgtest; + dst->dgx = src->dgx; + dst->dgxm = src->dgxm; + dst->dgy = src->dgy; + dst->dgym = src->dgym; + dst->finit = src->finit; + dst->ftest1 = src->ftest1; + dst->fm = src->fm; + dst->fx = src->fx; + dst->fxm = src->fxm; + dst->fy = src->fy; + dst->fym = src->fym; + dst->stx = src->stx; + dst->sty = src->sty; + dst->stmin = src->stmin; + dst->stmax = src->stmax; + dst->width = src->width; + dst->width1 = src->width1; + dst->xtrapf = src->xtrapf; + return ae_true; +} + + +void _linminstate_clear(void* _p) +{ + linminstate *p = (linminstate*)_p; + ae_touch_ptr((void*)p); +} + + +void _linminstate_destroy(void* _p) +{ + linminstate *p = (linminstate*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _armijostate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + armijostate *p = (armijostate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xbase, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _armijostate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + armijostate *dst = (armijostate*)_dst; + armijostate *src = (armijostate*)_src; + dst->needf = src->needf; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + dst->n = src->n; + if( !ae_vector_init_copy(&dst->xbase, &src->xbase, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + dst->stplen = src->stplen; + dst->fcur = src->fcur; + dst->stpmax = src->stpmax; + dst->fmax = src->fmax; + dst->nfev = src->nfev; + dst->info = src->info; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _armijostate_clear(void* _p) +{ + armijostate *p = (armijostate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->xbase); + ae_vector_clear(&p->s); + _rcommstate_clear(&p->rstate); +} + + +void _armijostate_destroy(void* _p) +{ + armijostate *p = (armijostate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->xbase); + ae_vector_destroy(&p->s); + _rcommstate_destroy(&p->rstate); +} + + + + +void findprimitiverootandinverse(ae_int_t n, + ae_int_t* proot, + ae_int_t* invproot, + ae_state *_state) +{ + ae_int_t candroot; + ae_int_t phin; + ae_int_t q; + ae_int_t f; + ae_bool allnonone; + ae_int_t x; + ae_int_t lastx; + ae_int_t y; + ae_int_t lasty; + ae_int_t a; + ae_int_t b; + ae_int_t t; + ae_int_t n2; + + *proot = 0; + *invproot = 0; + + ae_assert(n>=3, "FindPrimitiveRootAndInverse: N<3", _state); + *proot = 0; + *invproot = 0; + + /* + * check that N is prime + */ + ae_assert(ntheory_isprime(n, _state), "FindPrimitiveRoot: N is not prime", _state); + + /* + * Because N is prime, Euler totient function is equal to N-1 + */ + phin = n-1; + + /* + * Test different values of PRoot - from 2 to N-1. + * One of these values MUST be primitive root. + * + * For testing we use algorithm from Wiki (Primitive root modulo n): + * * compute phi(N) + * * determine the different prime factors of phi(N), say p1, ..., pk + * * for every element m of Zn*, compute m^(phi(N)/pi) mod N for i=1..k + * using a fast algorithm for modular exponentiation. + * * a number m for which these k results are all different from 1 is a + * primitive root. + */ + for(candroot=2; candroot<=n-1; candroot++) + { + + /* + * We have current candidate root in CandRoot. + * + * Scan different prime factors of PhiN. Here: + * * F is a current candidate factor + * * Q is a current quotient - amount which was left after dividing PhiN + * by all previous factors + * + * For each factor, perform test mentioned above. + */ + q = phin; + f = 2; + allnonone = ae_true; + while(q>1) + { + if( q%f==0 ) + { + t = ntheory_modexp(candroot, phin/f, n, _state); + if( t==1 ) + { + allnonone = ae_false; + break; + } + while(q%f==0) + { + q = q/f; + } + } + f = f+1; + } + if( allnonone ) + { + *proot = candroot; + break; + } + } + ae_assert(*proot>=2, "FindPrimitiveRoot: internal error (root not found)", _state); + + /* + * Use extended Euclidean algorithm to find multiplicative inverse of primitive root + */ + x = 0; + lastx = 1; + y = 1; + lasty = 0; + a = *proot; + b = n; + while(b!=0) + { + q = a/b; + t = a%b; + a = b; + b = t; + t = lastx-q*x; + lastx = x; + x = t; + t = lasty-q*y; + lasty = y; + y = t; + } + while(lastx<0) + { + lastx = lastx+n; + } + *invproot = lastx; + + /* + * Check that it is safe to perform multiplication modulo N. + * Check results for consistency. + */ + n2 = (n-1)*(n-1); + ae_assert(n2/(n-1)==n-1, "FindPrimitiveRoot: internal error", _state); + ae_assert(*proot*(*invproot)/(*proot)==(*invproot), "FindPrimitiveRoot: internal error", _state); + ae_assert(*proot*(*invproot)/(*invproot)==(*proot), "FindPrimitiveRoot: internal error", _state); + ae_assert(*proot*(*invproot)%n==1, "FindPrimitiveRoot: internal error", _state); +} + + +static ae_bool ntheory_isprime(ae_int_t n, ae_state *_state) +{ + ae_int_t p; + ae_bool result; + + + result = ae_false; + p = 2; + while(p*p<=n) + { + if( n%p==0 ) + { + return result; + } + p = p+1; + } + result = ae_true; + return result; +} + + +static ae_int_t ntheory_modmul(ae_int_t a, + ae_int_t b, + ae_int_t n, + ae_state *_state) +{ + ae_int_t t; + double ra; + double rb; + ae_int_t result; + + + ae_assert(a>=0&&a=N", _state); + ae_assert(b>=0&&b=N", _state); + + /* + * Base cases + */ + ra = a; + rb = b; + if( b==0||a==0 ) + { + result = 0; + return result; + } + if( b==1||a==1 ) + { + result = a*b; + return result; + } + if( ae_fp_eq(ra*rb,a*b) ) + { + result = a*b%n; + return result; + } + + /* + * Non-base cases + */ + if( b%2==0 ) + { + + /* + * A*B = (A*(B/2)) * 2 + * + * Product T=A*(B/2) is calculated recursively, product T*2 is + * calculated as follows: + * * result:=T-N + * * result:=result+T + * * if result<0 then result:=result+N + * + * In case integer result overflows, we generate exception + */ + t = ntheory_modmul(a, b/2, n, _state); + result = t-n; + result = result+t; + if( result<0 ) + { + result = result+n; + } + } + else + { + + /* + * A*B = (A*(B div 2)) * 2 + A + * + * Product T=A*(B/2) is calculated recursively, product T*2 is + * calculated as follows: + * * result:=T-N + * * result:=result+T + * * if result<0 then result:=result+N + * + * In case integer result overflows, we generate exception + */ + t = ntheory_modmul(a, b/2, n, _state); + result = t-n; + result = result+t; + if( result<0 ) + { + result = result+n; + } + result = result-n; + result = result+a; + if( result<0 ) + { + result = result+n; + } + } + return result; +} + + +static ae_int_t ntheory_modexp(ae_int_t a, + ae_int_t b, + ae_int_t n, + ae_state *_state) +{ + ae_int_t t; + ae_int_t result; + + + ae_assert(a>=0&&a=N", _state); + ae_assert(b>=0, "ModExp: B<0", _state); + + /* + * Base cases + */ + if( b==0 ) + { + result = 1; + return result; + } + if( b==1 ) + { + result = a; + return result; + } + + /* + * Non-base cases + */ + if( b%2==0 ) + { + t = ntheory_modmul(a, a, n, _state); + result = ntheory_modexp(t, b/2, n, _state); + } + else + { + t = ntheory_modmul(a, a, n, _state); + result = ntheory_modexp(t, b/2, n, _state); + result = ntheory_modmul(result, a, n, _state); + } + return result; +} + + + + +/************************************************************************* +This subroutine generates FFT plan for K complex FFT's with length N each. + +INPUT PARAMETERS: + N - FFT length (in complex numbers), N>=1 + K - number of repetitions, K>=1 + +OUTPUT PARAMETERS: + Plan - plan + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +void ftcomplexfftplan(ae_int_t n, + ae_int_t k, + fasttransformplan* plan, + ae_state *_state) +{ + ae_frame _frame_block; + srealarray bluesteinbuf; + ae_int_t rowptr; + ae_int_t bluesteinsize; + ae_int_t precrptr; + ae_int_t preciptr; + ae_int_t precrsize; + ae_int_t precisize; + + ae_frame_make(_state, &_frame_block); + _fasttransformplan_clear(plan); + _srealarray_init(&bluesteinbuf, _state, ae_true); + + + /* + * Initial check for parameters + */ + ae_assert(n>0, "FTComplexFFTPlan: N<=0", _state); + ae_assert(k>0, "FTComplexFFTPlan: K<=0", _state); + + /* + * Determine required sizes of precomputed real and integer + * buffers. This stage of code is highly dependent on internals + * of FTComplexFFTPlanRec() and must be kept synchronized with + * possible changes in internals of plan generation function. + * + * Buffer size is determined as follows: + * * N is factorized + * * we factor out anything which is less or equal to MaxRadix + * * prime factor F>RaderThreshold requires 4*FTBaseFindSmooth(2*F-1) + * real entries to store precomputed Quantities for Bluestein's + * transformation + * * prime factor F<=RaderThreshold does NOT require + * precomputed storage + */ + precrsize = 0; + precisize = 0; + ftbase_ftdeterminespacerequirements(n, &precrsize, &precisize, _state); + if( precrsize>0 ) + { + ae_vector_set_length(&plan->precr, precrsize, _state); + } + if( precisize>0 ) + { + ae_vector_set_length(&plan->preci, precisize, _state); + } + + /* + * Generate plan + */ + rowptr = 0; + precrptr = 0; + preciptr = 0; + bluesteinsize = 1; + ae_vector_set_length(&plan->buffer, 2*n*k, _state); + ftbase_ftcomplexfftplanrec(n, k, ae_true, ae_true, &rowptr, &bluesteinsize, &precrptr, &preciptr, plan, _state); + ae_vector_set_length(&bluesteinbuf.val, bluesteinsize, _state); + ae_shared_pool_set_seed(&plan->bluesteinpool, &bluesteinbuf, sizeof(bluesteinbuf), _srealarray_init, _srealarray_init_copy, _srealarray_destroy, _state); + + /* + * Check that actual amount of precomputed space used by transformation + * plan is EXACTLY equal to amount of space allocated by us. + */ + ae_assert(precrptr==precrsize, "FTComplexFFTPlan: internal error (PrecRPtr<>PrecRSize)", _state); + ae_assert(preciptr==precisize, "FTComplexFFTPlan: internal error (PrecRPtr<>PrecRSize)", _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine applies transformation plan to input/output array A. + +INPUT PARAMETERS: + Plan - transformation plan + A - array, must be large enough for plan to work + OffsA - offset of the subarray to process + RepCnt - repetition count (transformation is repeatedly applied + to subsequent subarrays) + +OUTPUT PARAMETERS: + Plan - plan (temporary buffers can be modified, plan itself + is unchanged and can be reused) + A - transformed array + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +void ftapplyplan(fasttransformplan* plan, + /* Real */ ae_vector* a, + ae_int_t offsa, + ae_int_t repcnt, + ae_state *_state) +{ + ae_int_t plansize; + ae_int_t i; + + + plansize = plan->entries.ptr.pp_int[0][ftbase_coloperandscnt]*plan->entries.ptr.pp_int[0][ftbase_coloperandsize]*plan->entries.ptr.pp_int[0][ftbase_colmicrovectorsize]; + for(i=0; i<=repcnt-1; i++) + { + ftbase_ftapplysubplan(plan, 0, a, offsa+plansize*i, 0, &plan->buffer, 1, _state); + } +} + + +/************************************************************************* +Returns good factorization N=N1*N2. + +Usually N1<=N2 (but not always - small N's may be exception). +if N1<>1 then N2<>1. + +Factorization is chosen depending on task type and codelets we have. + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +void ftbasefactorize(ae_int_t n, + ae_int_t tasktype, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state) +{ + ae_int_t j; + + *n1 = 0; + *n2 = 0; + + *n1 = 0; + *n2 = 0; + + /* + * try to find good codelet + */ + if( *n1*(*n2)!=n ) + { + for(j=ftbase_ftbasecodeletrecommended; j>=2; j--) + { + if( n%j==0 ) + { + *n1 = j; + *n2 = n/j; + break; + } + } + } + + /* + * try to factorize N + */ + if( *n1*(*n2)!=n ) + { + for(j=ftbase_ftbasecodeletrecommended+1; j<=n-1; j++) + { + if( n%j==0 ) + { + *n1 = j; + *n2 = n/j; + break; + } + } + } + + /* + * looks like N is prime :( + */ + if( *n1*(*n2)!=n ) + { + *n1 = 1; + *n2 = n; + } + + /* + * normalize + */ + if( *n2==1&&*n1!=1 ) + { + *n2 = *n1; + *n1 = 1; + } +} + + +/************************************************************************* +Is number smooth? + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool ftbaseissmooth(ae_int_t n, ae_state *_state) +{ + ae_int_t i; + ae_bool result; + + + for(i=2; i<=ftbase_ftbasemaxsmoothfactor; i++) + { + while(n%i==0) + { + n = n/i; + } + } + result = n==1; + return result; +} + + +/************************************************************************* +Returns smallest smooth (divisible only by 2, 3, 5) number that is greater +than or equal to max(N,2) + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +ae_int_t ftbasefindsmooth(ae_int_t n, ae_state *_state) +{ + ae_int_t best; + ae_int_t result; + + + best = 2; + while(bestRaderThreshold requires 4*FTBaseFindSmooth(2*F-1) + * real entries to store precomputed Quantities for Bluestein's + * transformation + * * prime factor F<=RaderThreshold requires 2*(F-1)+ESTIMATE(F-1) + * precomputed storage + */ + ncur = n; + for(i=2; i<=ftbase_maxradix; i++) + { + while(ncur%i==0) + { + ncur = ncur/i; + } + } + f = 2; + while(f<=ncur) + { + while(ncur%f==0) + { + if( f>ftbase_raderthreshold ) + { + *precrsize = *precrsize+4*ftbasefindsmooth(2*f-1, _state); + } + else + { + *precrsize = *precrsize+2*(f-1); + ftbase_ftdeterminespacerequirements(f-1, precrsize, precisize, _state); + } + ncur = ncur/f; + } + f = f+1; + } +} + + +/************************************************************************* +Recurrent function called by FTComplexFFTPlan() and other functions. It +recursively builds transformation plan + +INPUT PARAMETERS: + N - FFT length (in complex numbers), N>=1 + K - number of repetitions, K>=1 + ChildPlan - if True, plan generator inserts OpStart/opEnd in the + plan header/footer. + TopmostPlan - if True, plan generator assumes that it is topmost plan: + * it may use global buffer for transpositions + and there is no other plan which executes in parallel + RowPtr - index which points to past-the-last entry generated so far + BluesteinSize- amount of storage (in real numbers) required for Bluestein buffer + PrecRPtr - pointer to unused part of precomputed real buffer (Plan.PrecR): + * when this function stores some data to precomputed buffer, + it advances pointer. + * it is responsibility of the function to assert that + Plan.PrecR has enough space to store data before actually + writing to buffer. + * it is responsibility of the caller to allocate enough + space before calling this function + PrecIPtr - pointer to unused part of precomputed integer buffer (Plan.PrecI): + * when this function stores some data to precomputed buffer, + it advances pointer. + * it is responsibility of the function to assert that + Plan.PrecR has enough space to store data before actually + writing to buffer. + * it is responsibility of the caller to allocate enough + space before calling this function + Plan - plan (generated so far) + +OUTPUT PARAMETERS: + RowPtr - updated pointer (advanced by number of entries generated + by function) + BluesteinSize- updated amount + (may be increased, but may never be decreased) + +NOTE: in case TopmostPlan is True, ChildPlan is also must be True. + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftcomplexfftplanrec(ae_int_t n, + ae_int_t k, + ae_bool childplan, + ae_bool topmostplan, + ae_int_t* rowptr, + ae_int_t* bluesteinsize, + ae_int_t* precrptr, + ae_int_t* preciptr, + fasttransformplan* plan, + ae_state *_state) +{ + ae_frame _frame_block; + srealarray localbuf; + ae_int_t m; + ae_int_t n1; + ae_int_t n2; + ae_int_t gq; + ae_int_t giq; + ae_int_t row0; + ae_int_t row1; + ae_int_t row2; + ae_int_t row3; + + ae_frame_make(_state, &_frame_block); + _srealarray_init(&localbuf, _state, ae_true); + + ae_assert(n>0, "FTComplexFFTPlan: N<=0", _state); + ae_assert(k>0, "FTComplexFFTPlan: K<=0", _state); + ae_assert(!topmostplan||childplan, "FTComplexFFTPlan: ChildPlan is inconsistent with TopmostPlan", _state); + + /* + * Try to generate "topmost" plan + */ + if( topmostplan&&n>ftbase_recursivethreshold ) + { + ftbase_ftfactorize(n, ae_false, &n1, &n2, _state); + if( n1*n2==0 ) + { + + /* + * Handle prime-factor FFT with Bluestein's FFT. + * Determine size of Bluestein's buffer. + */ + m = ftbasefindsmooth(2*n-1, _state); + *bluesteinsize = ae_maxint(2*m, *bluesteinsize, _state); + + /* + * Generate plan + */ + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry4(plan, rowptr, ftbase_opbluesteinsfft, k, n, 2, m, 2, *precrptr, 0, _state); + row0 = *rowptr; + ftbase_ftpushentry(plan, rowptr, ftbase_opjmp, 0, 0, 0, 0, _state); + ftbase_ftcomplexfftplanrec(m, 1, ae_true, ae_true, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + row1 = *rowptr; + plan->entries.ptr.pp_int[row0][ftbase_colparam0] = row1-row0; + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + + /* + * Fill precomputed buffer + */ + ftbase_ftprecomputebluesteinsfft(n, m, &plan->precr, *precrptr, _state); + + /* + * Update pointer to the precomputed area + */ + *precrptr = *precrptr+4*m; + } + else + { + + /* + * Handle composite FFT with recursive Cooley-Tukey which + * uses global buffer instead of local one. + */ + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + row0 = *rowptr; + ftbase_ftpushentry2(plan, rowptr, ftbase_opparallelcall, k*n2, n1, 2, 0, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplexfftfactors, k, n, 2, n1, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n2, _state); + row2 = *rowptr; + ftbase_ftpushentry2(plan, rowptr, ftbase_opparallelcall, k*n1, n2, 2, 0, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + row1 = *rowptr; + ftbase_ftcomplexfftplanrec(n1, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + plan->entries.ptr.pp_int[row0][ftbase_colparam0] = row1-row0; + row3 = *rowptr; + ftbase_ftcomplexfftplanrec(n2, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + plan->entries.ptr.pp_int[row2][ftbase_colparam0] = row3-row2; + } + ae_frame_leave(_state); + return; + } + + /* + * Prepare "non-topmost" plan: + * * calculate factorization + * * use local (shared) buffer + * * update buffer size - ANY plan will need at least + * 2*N temporaries, additional requirements can be + * applied later + */ + ftbase_ftfactorize(n, ae_false, &n1, &n2, _state); + + /* + * Handle FFT's with N1*N2=0: either small-N or prime-factor + */ + if( n1*n2==0 ) + { + if( n<=ftbase_maxradix ) + { + + /* + * Small-N FFT + */ + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplexcodeletfft, k, n, 2, 0, _state); + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + ae_frame_leave(_state); + return; + } + if( n<=ftbase_raderthreshold ) + { + + /* + * Handle prime-factor FFT's with Rader's FFT + */ + m = n-1; + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + findprimitiverootandinverse(n, &gq, &giq, _state); + ftbase_ftpushentry4(plan, rowptr, ftbase_opradersfft, k, n, 2, 2, gq, giq, *precrptr, _state); + ftbase_ftprecomputeradersfft(n, gq, giq, &plan->precr, *precrptr, _state); + *precrptr = *precrptr+2*(n-1); + row0 = *rowptr; + ftbase_ftpushentry(plan, rowptr, ftbase_opjmp, 0, 0, 0, 0, _state); + ftbase_ftcomplexfftplanrec(m, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + row1 = *rowptr; + plan->entries.ptr.pp_int[row0][ftbase_colparam0] = row1-row0; + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + } + else + { + + /* + * Handle prime-factor FFT's with Bluestein's FFT + */ + m = ftbasefindsmooth(2*n-1, _state); + *bluesteinsize = ae_maxint(2*m, *bluesteinsize, _state); + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + ftbase_ftpushentry4(plan, rowptr, ftbase_opbluesteinsfft, k, n, 2, m, 2, *precrptr, 0, _state); + ftbase_ftprecomputebluesteinsfft(n, m, &plan->precr, *precrptr, _state); + *precrptr = *precrptr+4*m; + row0 = *rowptr; + ftbase_ftpushentry(plan, rowptr, ftbase_opjmp, 0, 0, 0, 0, _state); + ftbase_ftcomplexfftplanrec(m, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + row1 = *rowptr; + plan->entries.ptr.pp_int[row0][ftbase_colparam0] = row1-row0; + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + } + ae_frame_leave(_state); + return; + } + + /* + * Handle Cooley-Tukey FFT with small N1 + */ + if( n1<=ftbase_maxradix ) + { + + /* + * Specialized transformation for small N1: + * * N2 short inplace FFT's, each N1-point, with integrated twiddle factors + * * N1 long FFT's + * * final transposition + */ + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplexcodelettwfft, k, n1, 2*n2, 0, _state); + ftbase_ftcomplexfftplanrec(n2, k*n1, ae_false, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + ae_frame_leave(_state); + return; + } + + /* + * Handle general Cooley-Tukey FFT, either "flat" or "recursive" + */ + if( n<=ftbase_recursivethreshold ) + { + + /* + * General code for large N1/N2, "flat" version without explicit recurrence + * (nested subplans are inserted directly into the body of the plan) + */ + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + ftbase_ftcomplexfftplanrec(n1, k*n2, ae_false, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplexfftfactors, k, n, 2, n1, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n2, _state); + ftbase_ftcomplexfftplanrec(n2, k*n1, ae_false, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + } + else + { + + /* + * General code for large N1/N2, "recursive" version - nested subplans + * are separated from the plan body. + * + * Generate parent plan. + */ + if( childplan ) + { + ftbase_ftpushentry2(plan, rowptr, ftbase_opstart, k, n, 2, -1, ftbase_ftoptimisticestimate(n, _state), _state); + } + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + row0 = *rowptr; + ftbase_ftpushentry2(plan, rowptr, ftbase_opparallelcall, k*n2, n1, 2, 0, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplexfftfactors, k, n, 2, n1, _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n2, _state); + row2 = *rowptr; + ftbase_ftpushentry2(plan, rowptr, ftbase_opparallelcall, k*n1, n2, 2, 0, ftbase_ftoptimisticestimate(n, _state), _state); + ftbase_ftpushentry(plan, rowptr, ftbase_opcomplextranspose, k, n, 2, n1, _state); + if( childplan ) + { + ftbase_ftpushentry(plan, rowptr, ftbase_opend, k, n, 2, 0, _state); + } + + /* + * Generate child subplans, insert refence to parent plans + */ + row1 = *rowptr; + ftbase_ftcomplexfftplanrec(n1, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + plan->entries.ptr.pp_int[row0][ftbase_colparam0] = row1-row0; + row3 = *rowptr; + ftbase_ftcomplexfftplanrec(n2, 1, ae_true, ae_false, rowptr, bluesteinsize, precrptr, preciptr, plan, _state); + plan->entries.ptr.pp_int[row2][ftbase_colparam0] = row3-row2; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function pushes one more entry to the plan. It resizes Entries matrix +if needed. + +INPUT PARAMETERS: + Plan - plan (generated so far) + RowPtr - index which points to past-the-last entry generated so far + EType - entry type + EOpCnt - operands count + EOpSize - operand size + EMcvSize - microvector size + EParam0 - parameter 0 + +OUTPUT PARAMETERS: + Plan - updated plan + RowPtr - updated pointer + +NOTE: Param1 is set to -1. + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftpushentry(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_state *_state) +{ + + + ftbase_ftpushentry2(plan, rowptr, etype, eopcnt, eopsize, emcvsize, eparam0, -1, _state); +} + + +/************************************************************************* +Same as FTPushEntry(), but sets Param0 AND Param1. +This function pushes one more entry to the plan. It resized Entries matrix +if needed. + +INPUT PARAMETERS: + Plan - plan (generated so far) + RowPtr - index which points to past-the-last entry generated so far + EType - entry type + EOpCnt - operands count + EOpSize - operand size + EMcvSize - microvector size + EParam0 - parameter 0 + EParam1 - parameter 1 + +OUTPUT PARAMETERS: + Plan - updated plan + RowPtr - updated pointer + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftpushentry2(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_int_t eparam1, + ae_state *_state) +{ + + + if( *rowptr>=plan->entries.rows ) + { + imatrixresize(&plan->entries, ae_maxint(2*plan->entries.rows, 1, _state), ftbase_colscnt, _state); + } + plan->entries.ptr.pp_int[*rowptr][ftbase_coltype] = etype; + plan->entries.ptr.pp_int[*rowptr][ftbase_coloperandscnt] = eopcnt; + plan->entries.ptr.pp_int[*rowptr][ftbase_coloperandsize] = eopsize; + plan->entries.ptr.pp_int[*rowptr][ftbase_colmicrovectorsize] = emcvsize; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam0] = eparam0; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam1] = eparam1; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam2] = 0; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam3] = 0; + *rowptr = *rowptr+1; +} + + +/************************************************************************* +Same as FTPushEntry(), but sets Param0, Param1, Param2 and Param3. +This function pushes one more entry to the plan. It resized Entries matrix +if needed. + +INPUT PARAMETERS: + Plan - plan (generated so far) + RowPtr - index which points to past-the-last entry generated so far + EType - entry type + EOpCnt - operands count + EOpSize - operand size + EMcvSize - microvector size + EParam0 - parameter 0 + EParam1 - parameter 1 + EParam2 - parameter 2 + EParam3 - parameter 3 + +OUTPUT PARAMETERS: + Plan - updated plan + RowPtr - updated pointer + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftpushentry4(fasttransformplan* plan, + ae_int_t* rowptr, + ae_int_t etype, + ae_int_t eopcnt, + ae_int_t eopsize, + ae_int_t emcvsize, + ae_int_t eparam0, + ae_int_t eparam1, + ae_int_t eparam2, + ae_int_t eparam3, + ae_state *_state) +{ + + + if( *rowptr>=plan->entries.rows ) + { + imatrixresize(&plan->entries, ae_maxint(2*plan->entries.rows, 1, _state), ftbase_colscnt, _state); + } + plan->entries.ptr.pp_int[*rowptr][ftbase_coltype] = etype; + plan->entries.ptr.pp_int[*rowptr][ftbase_coloperandscnt] = eopcnt; + plan->entries.ptr.pp_int[*rowptr][ftbase_coloperandsize] = eopsize; + plan->entries.ptr.pp_int[*rowptr][ftbase_colmicrovectorsize] = emcvsize; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam0] = eparam0; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam1] = eparam1; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam2] = eparam2; + plan->entries.ptr.pp_int[*rowptr][ftbase_colparam3] = eparam3; + *rowptr = *rowptr+1; +} + + +/************************************************************************* +This subroutine applies subplan to input/output array A. + +INPUT PARAMETERS: + Plan - transformation plan + SubPlan - subplan index + A - array, must be large enough for plan to work + ABase - base offset in array A, this value points to start of + subarray whose length is equal to length of the plan + AOffset - offset with respect to ABase, 0<=AOffsetentries.ptr.pp_int[subplan][ftbase_coltype]==ftbase_opstart, "FTApplySubPlan: incorrect subplan header", _state); + rowidx = subplan+1; + while(plan->entries.ptr.pp_int[rowidx][ftbase_coltype]!=ftbase_opend) + { + operation = plan->entries.ptr.pp_int[rowidx][ftbase_coltype]; + operandscnt = repcnt*plan->entries.ptr.pp_int[rowidx][ftbase_coloperandscnt]; + operandsize = plan->entries.ptr.pp_int[rowidx][ftbase_coloperandsize]; + microvectorsize = plan->entries.ptr.pp_int[rowidx][ftbase_colmicrovectorsize]; + param0 = plan->entries.ptr.pp_int[rowidx][ftbase_colparam0]; + param1 = plan->entries.ptr.pp_int[rowidx][ftbase_colparam1]; + touchint(¶m1, _state); + + /* + * Process "jump" operation + */ + if( operation==ftbase_opjmp ) + { + rowidx = rowidx+plan->entries.ptr.pp_int[rowidx][ftbase_colparam0]; + continue; + } + + /* + * Process "parallel call" operation: + * * we perform initial check for consistency between parent and child plans + * * we call FTSplitAndApplyParallelPlan(), which splits parallel plan into + * several parallel tasks + */ + if( operation==ftbase_opparallelcall ) + { + parentsize = operandsize*microvectorsize; + childsize = plan->entries.ptr.pp_int[rowidx+param0][ftbase_coloperandscnt]*plan->entries.ptr.pp_int[rowidx+param0][ftbase_coloperandsize]*plan->entries.ptr.pp_int[rowidx+param0][ftbase_colmicrovectorsize]; + ae_assert(plan->entries.ptr.pp_int[rowidx+param0][ftbase_coltype]==ftbase_opstart, "FTApplySubPlan: incorrect child subplan header", _state); + ae_assert(parentsize==childsize, "FTApplySubPlan: incorrect child subplan header", _state); + chunksize = ae_maxint(ftbase_recursivethreshold/childsize, 1, _state); + lastchunksize = operandscnt%chunksize; + if( lastchunksize==0 ) + { + lastchunksize = chunksize; + } + i = 0; + while(ibluesteinpool, &_bufa, _state); + ae_shared_pool_retrieve(&plan->bluesteinpool, &_bufb, _state); + ae_shared_pool_retrieve(&plan->bluesteinpool, &_bufc, _state); + ae_shared_pool_retrieve(&plan->bluesteinpool, &_bufd, _state); + ftbase_ftbluesteinsfft(plan, a, abase, aoffset, operandscnt, operandsize, plan->entries.ptr.pp_int[rowidx][ftbase_colparam0], plan->entries.ptr.pp_int[rowidx][ftbase_colparam2], rowidx+plan->entries.ptr.pp_int[rowidx][ftbase_colparam1], &bufa->val, &bufb->val, &bufc->val, &bufd->val, _state); + ae_shared_pool_recycle(&plan->bluesteinpool, &_bufa, _state); + ae_shared_pool_recycle(&plan->bluesteinpool, &_bufb, _state); + ae_shared_pool_recycle(&plan->bluesteinpool, &_bufc, _state); + ae_shared_pool_recycle(&plan->bluesteinpool, &_bufd, _state); + rowidx = rowidx+1; + continue; + } + + /* + * Process Rader's FFT + */ + if( operation==ftbase_opradersfft ) + { + ftbase_ftradersfft(plan, a, abase, aoffset, operandscnt, operandsize, rowidx+plan->entries.ptr.pp_int[rowidx][ftbase_colparam0], plan->entries.ptr.pp_int[rowidx][ftbase_colparam1], plan->entries.ptr.pp_int[rowidx][ftbase_colparam2], plan->entries.ptr.pp_int[rowidx][ftbase_colparam3], buf, _state); + rowidx = rowidx+1; + continue; + } + + /* + * Process "complex twiddle factors" operation + */ + if( operation==ftbase_opcomplexfftfactors ) + { + ae_assert(microvectorsize==2, "FTApplySubPlan: MicrovectorSize<>1", _state); + n1 = plan->entries.ptr.pp_int[rowidx][ftbase_colparam0]; + n2 = operandsize/n1; + for(i=0; i<=operandscnt-1; i++) + { + ftbase_ffttwcalc(a, abase+aoffset+i*operandsize*2, n1, n2, _state); + } + rowidx = rowidx+1; + continue; + } + + /* + * Process "complex transposition" operation + */ + if( operation==ftbase_opcomplextranspose ) + { + ae_assert(microvectorsize==2, "FTApplySubPlan: MicrovectorSize<>1", _state); + n1 = plan->entries.ptr.pp_int[rowidx][ftbase_colparam0]; + n2 = operandsize/n1; + for(i=0; i<=operandscnt-1; i++) + { + ftbase_internalcomplexlintranspose(a, n1, n2, abase+aoffset+i*operandsize*2, buf, _state); + } + rowidx = rowidx+1; + continue; + } + + /* + * Error + */ + ae_assert(ae_false, "FTApplySubPlan: unexpected plan type", _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine applies complex reference FFT to input/output array A. + +VERY SLOW OPERATION, do not use it in real life plans :) + +INPUT PARAMETERS: + A - array, must be large enough for plan to work + Offs - offset of the subarray to process + OperandsCnt - operands count (see description of FastTransformPlan) + OperandSize - operand size (see description of FastTransformPlan) + MicrovectorSize-microvector size (see description of FastTransformPlan) + Buf - temporary array, must be at least OperandsCnt*OperandSize*MicrovectorSize + +OUTPUT PARAMETERS: + A - transformed array + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftapplycomplexreffft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + /* Real */ ae_vector* buf, + ae_state *_state) +{ + ae_int_t opidx; + ae_int_t i; + ae_int_t k; + double hre; + double him; + double c; + double s; + double re; + double im; + ae_int_t n; + + + ae_assert(operandscnt>=1, "FTApplyComplexRefFFT: OperandsCnt<1", _state); + ae_assert(operandsize>=1, "FTApplyComplexRefFFT: OperandSize<1", _state); + ae_assert(microvectorsize==2, "FTApplyComplexRefFFT: MicrovectorSize<>2", _state); + n = operandsize; + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + for(i=0; i<=n-1; i++) + { + hre = 0; + him = 0; + for(k=0; k<=n-1; k++) + { + re = a->ptr.p_double[offs+opidx*operandsize*2+2*k+0]; + im = a->ptr.p_double[offs+opidx*operandsize*2+2*k+1]; + c = ae_cos(-2*ae_pi*k*i/n, _state); + s = ae_sin(-2*ae_pi*k*i/n, _state); + hre = hre+c*re-s*im; + him = him+c*im+s*re; + } + buf->ptr.p_double[2*i+0] = hre; + buf->ptr.p_double[2*i+1] = him; + } + for(i=0; i<=operandsize*2-1; i++) + { + a->ptr.p_double[offs+opidx*operandsize*2+i] = buf->ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This subroutine applies complex codelet FFT to input/output array A. + +INPUT PARAMETERS: + A - array, must be large enough for plan to work + Offs - offset of the subarray to process + OperandsCnt - operands count (see description of FastTransformPlan) + OperandSize - operand size (see description of FastTransformPlan) + MicrovectorSize-microvector size, must be 2 + +OUTPUT PARAMETERS: + A - transformed array + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftapplycomplexcodeletfft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + ae_state *_state) +{ + ae_int_t opidx; + ae_int_t n; + ae_int_t aoffset; + double a0x; + double a0y; + double a1x; + double a1y; + double a2x; + double a2y; + double a3x; + double a3y; + double a4x; + double a4y; + double a5x; + double a5y; + double v0; + double v1; + double v2; + double v3; + double t1x; + double t1y; + double t2x; + double t2y; + double t3x; + double t3y; + double t4x; + double t4y; + double t5x; + double t5y; + double m1x; + double m1y; + double m2x; + double m2y; + double m3x; + double m3y; + double m4x; + double m4y; + double m5x; + double m5y; + double s1x; + double s1y; + double s2x; + double s2y; + double s3x; + double s3y; + double s4x; + double s4y; + double s5x; + double s5y; + double c1; + double c2; + double c3; + double c4; + double c5; + double v; + + + ae_assert(operandscnt>=1, "FTApplyComplexCodeletFFT: OperandsCnt<1", _state); + ae_assert(operandsize>=1, "FTApplyComplexCodeletFFT: OperandSize<1", _state); + ae_assert(microvectorsize==2, "FTApplyComplexCodeletFFT: MicrovectorSize<>2", _state); + n = operandsize; + + /* + * Hard-coded transforms for different N's + */ + ae_assert(n<=ftbase_maxradix, "FTApplyComplexCodeletFFT: N>MaxRadix", _state); + if( n==2 ) + { + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset = offs+opidx*operandsize*2; + a0x = a->ptr.p_double[aoffset+0]; + a0y = a->ptr.p_double[aoffset+1]; + a1x = a->ptr.p_double[aoffset+2]; + a1y = a->ptr.p_double[aoffset+3]; + v0 = a0x+a1x; + v1 = a0y+a1y; + v2 = a0x-a1x; + v3 = a0y-a1y; + a->ptr.p_double[aoffset+0] = v0; + a->ptr.p_double[aoffset+1] = v1; + a->ptr.p_double[aoffset+2] = v2; + a->ptr.p_double[aoffset+3] = v3; + } + return; + } + if( n==3 ) + { + c1 = ae_cos(2*ae_pi/3, _state)-1; + c2 = ae_sin(2*ae_pi/3, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset = offs+opidx*operandsize*2; + a0x = a->ptr.p_double[aoffset+0]; + a0y = a->ptr.p_double[aoffset+1]; + a1x = a->ptr.p_double[aoffset+2]; + a1y = a->ptr.p_double[aoffset+3]; + a2x = a->ptr.p_double[aoffset+4]; + a2y = a->ptr.p_double[aoffset+5]; + t1x = a1x+a2x; + t1y = a1y+a2y; + a0x = a0x+t1x; + a0y = a0y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a1y-a2y); + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + s1y = a0y+m1y; + a1x = s1x+m2x; + a1y = s1y+m2y; + a2x = s1x-m2x; + a2y = s1y-m2y; + a->ptr.p_double[aoffset+0] = a0x; + a->ptr.p_double[aoffset+1] = a0y; + a->ptr.p_double[aoffset+2] = a1x; + a->ptr.p_double[aoffset+3] = a1y; + a->ptr.p_double[aoffset+4] = a2x; + a->ptr.p_double[aoffset+5] = a2y; + } + return; + } + if( n==4 ) + { + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset = offs+opidx*operandsize*2; + a0x = a->ptr.p_double[aoffset+0]; + a0y = a->ptr.p_double[aoffset+1]; + a1x = a->ptr.p_double[aoffset+2]; + a1y = a->ptr.p_double[aoffset+3]; + a2x = a->ptr.p_double[aoffset+4]; + a2y = a->ptr.p_double[aoffset+5]; + a3x = a->ptr.p_double[aoffset+6]; + a3y = a->ptr.p_double[aoffset+7]; + t1x = a0x+a2x; + t1y = a0y+a2y; + t2x = a1x+a3x; + t2y = a1y+a3y; + m2x = a0x-a2x; + m2y = a0y-a2y; + m3x = a1y-a3y; + m3y = a3x-a1x; + a->ptr.p_double[aoffset+0] = t1x+t2x; + a->ptr.p_double[aoffset+1] = t1y+t2y; + a->ptr.p_double[aoffset+4] = t1x-t2x; + a->ptr.p_double[aoffset+5] = t1y-t2y; + a->ptr.p_double[aoffset+2] = m2x+m3x; + a->ptr.p_double[aoffset+3] = m2y+m3y; + a->ptr.p_double[aoffset+6] = m2x-m3x; + a->ptr.p_double[aoffset+7] = m2y-m3y; + } + return; + } + if( n==5 ) + { + v = 2*ae_pi/5; + c1 = (ae_cos(v, _state)+ae_cos(2*v, _state))/2-1; + c2 = (ae_cos(v, _state)-ae_cos(2*v, _state))/2; + c3 = -ae_sin(v, _state); + c4 = -(ae_sin(v, _state)+ae_sin(2*v, _state)); + c5 = ae_sin(v, _state)-ae_sin(2*v, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset = offs+opidx*operandsize*2; + t1x = a->ptr.p_double[aoffset+2]+a->ptr.p_double[aoffset+8]; + t1y = a->ptr.p_double[aoffset+3]+a->ptr.p_double[aoffset+9]; + t2x = a->ptr.p_double[aoffset+4]+a->ptr.p_double[aoffset+6]; + t2y = a->ptr.p_double[aoffset+5]+a->ptr.p_double[aoffset+7]; + t3x = a->ptr.p_double[aoffset+2]-a->ptr.p_double[aoffset+8]; + t3y = a->ptr.p_double[aoffset+3]-a->ptr.p_double[aoffset+9]; + t4x = a->ptr.p_double[aoffset+6]-a->ptr.p_double[aoffset+4]; + t4y = a->ptr.p_double[aoffset+7]-a->ptr.p_double[aoffset+5]; + t5x = t1x+t2x; + t5y = t1y+t2y; + a->ptr.p_double[aoffset+0] = a->ptr.p_double[aoffset+0]+t5x; + a->ptr.p_double[aoffset+1] = a->ptr.p_double[aoffset+1]+t5y; + m1x = c1*t5x; + m1y = c1*t5y; + m2x = c2*(t1x-t2x); + m2y = c2*(t1y-t2y); + m3x = -c3*(t3y+t4y); + m3y = c3*(t3x+t4x); + m4x = -c4*t4y; + m4y = c4*t4x; + m5x = -c5*t3y; + m5y = c5*t3x; + s3x = m3x-m4x; + s3y = m3y-m4y; + s5x = m3x+m5x; + s5y = m3y+m5y; + s1x = a->ptr.p_double[aoffset+0]+m1x; + s1y = a->ptr.p_double[aoffset+1]+m1y; + s2x = s1x+m2x; + s2y = s1y+m2y; + s4x = s1x-m2x; + s4y = s1y-m2y; + a->ptr.p_double[aoffset+2] = s2x+s3x; + a->ptr.p_double[aoffset+3] = s2y+s3y; + a->ptr.p_double[aoffset+4] = s4x+s5x; + a->ptr.p_double[aoffset+5] = s4y+s5y; + a->ptr.p_double[aoffset+6] = s4x-s5x; + a->ptr.p_double[aoffset+7] = s4y-s5y; + a->ptr.p_double[aoffset+8] = s2x-s3x; + a->ptr.p_double[aoffset+9] = s2y-s3y; + } + return; + } + if( n==6 ) + { + c1 = ae_cos(2*ae_pi/3, _state)-1; + c2 = ae_sin(2*ae_pi/3, _state); + c3 = ae_cos(-ae_pi/3, _state); + c4 = ae_sin(-ae_pi/3, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset = offs+opidx*operandsize*2; + a0x = a->ptr.p_double[aoffset+0]; + a0y = a->ptr.p_double[aoffset+1]; + a1x = a->ptr.p_double[aoffset+2]; + a1y = a->ptr.p_double[aoffset+3]; + a2x = a->ptr.p_double[aoffset+4]; + a2y = a->ptr.p_double[aoffset+5]; + a3x = a->ptr.p_double[aoffset+6]; + a3y = a->ptr.p_double[aoffset+7]; + a4x = a->ptr.p_double[aoffset+8]; + a4y = a->ptr.p_double[aoffset+9]; + a5x = a->ptr.p_double[aoffset+10]; + a5y = a->ptr.p_double[aoffset+11]; + v0 = a0x; + v1 = a0y; + a0x = a0x+a3x; + a0y = a0y+a3y; + a3x = v0-a3x; + a3y = v1-a3y; + v0 = a1x; + v1 = a1y; + a1x = a1x+a4x; + a1y = a1y+a4y; + a4x = v0-a4x; + a4y = v1-a4y; + v0 = a2x; + v1 = a2y; + a2x = a2x+a5x; + a2y = a2y+a5y; + a5x = v0-a5x; + a5y = v1-a5y; + t4x = a4x*c3-a4y*c4; + t4y = a4x*c4+a4y*c3; + a4x = t4x; + a4y = t4y; + t5x = -a5x*c3-a5y*c4; + t5y = a5x*c4-a5y*c3; + a5x = t5x; + a5y = t5y; + t1x = a1x+a2x; + t1y = a1y+a2y; + a0x = a0x+t1x; + a0y = a0y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a1y-a2y); + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + s1y = a0y+m1y; + a1x = s1x+m2x; + a1y = s1y+m2y; + a2x = s1x-m2x; + a2y = s1y-m2y; + t1x = a4x+a5x; + t1y = a4y+a5y; + a3x = a3x+t1x; + a3y = a3y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a4y-a5y); + m2y = c2*(a5x-a4x); + s1x = a3x+m1x; + s1y = a3y+m1y; + a4x = s1x+m2x; + a4y = s1y+m2y; + a5x = s1x-m2x; + a5y = s1y-m2y; + a->ptr.p_double[aoffset+0] = a0x; + a->ptr.p_double[aoffset+1] = a0y; + a->ptr.p_double[aoffset+2] = a3x; + a->ptr.p_double[aoffset+3] = a3y; + a->ptr.p_double[aoffset+4] = a1x; + a->ptr.p_double[aoffset+5] = a1y; + a->ptr.p_double[aoffset+6] = a4x; + a->ptr.p_double[aoffset+7] = a4y; + a->ptr.p_double[aoffset+8] = a2x; + a->ptr.p_double[aoffset+9] = a2y; + a->ptr.p_double[aoffset+10] = a5x; + a->ptr.p_double[aoffset+11] = a5y; + } + return; + } +} + + +/************************************************************************* +This subroutine applies complex "integrated" codelet FFT to input/output +array A. "Integrated" codelet differs from "normal" one in following ways: +* it can work with MicrovectorSize>1 +* hence, it can be used in Cooley-Tukey FFT without transpositions +* it performs inlined multiplication by twiddle factors of Cooley-Tukey + FFT with N2=MicrovectorSize/2. + +INPUT PARAMETERS: + A - array, must be large enough for plan to work + Offs - offset of the subarray to process + OperandsCnt - operands count (see description of FastTransformPlan) + OperandSize - operand size (see description of FastTransformPlan) + MicrovectorSize-microvector size, must be 1 + +OUTPUT PARAMETERS: + A - transformed array + + -- ALGLIB -- + Copyright 05.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftapplycomplexcodelettwfft(/* Real */ ae_vector* a, + ae_int_t offs, + ae_int_t operandscnt, + ae_int_t operandsize, + ae_int_t microvectorsize, + ae_state *_state) +{ + ae_int_t opidx; + ae_int_t mvidx; + ae_int_t n; + ae_int_t m; + ae_int_t aoffset0; + ae_int_t aoffset2; + ae_int_t aoffset4; + ae_int_t aoffset6; + ae_int_t aoffset8; + ae_int_t aoffset10; + double a0x; + double a0y; + double a1x; + double a1y; + double a2x; + double a2y; + double a3x; + double a3y; + double a4x; + double a4y; + double a5x; + double a5y; + double v0; + double v1; + double v2; + double v3; + double q0x; + double q0y; + double t1x; + double t1y; + double t2x; + double t2y; + double t3x; + double t3y; + double t4x; + double t4y; + double t5x; + double t5y; + double m1x; + double m1y; + double m2x; + double m2y; + double m3x; + double m3y; + double m4x; + double m4y; + double m5x; + double m5y; + double s1x; + double s1y; + double s2x; + double s2y; + double s3x; + double s3y; + double s4x; + double s4y; + double s5x; + double s5y; + double c1; + double c2; + double c3; + double c4; + double c5; + double v; + double tw0; + double tw1; + double twx; + double twxm1; + double twy; + double tw2x; + double tw2y; + double tw3x; + double tw3y; + double tw4x; + double tw4y; + double tw5x; + double tw5y; + + + ae_assert(operandscnt>=1, "FTApplyComplexCodeletFFT: OperandsCnt<1", _state); + ae_assert(operandsize>=1, "FTApplyComplexCodeletFFT: OperandSize<1", _state); + ae_assert(microvectorsize>=1, "FTApplyComplexCodeletFFT: MicrovectorSize<>1", _state); + ae_assert(microvectorsize%2==0, "FTApplyComplexCodeletFFT: MicrovectorSize is not even", _state); + n = operandsize; + m = microvectorsize/2; + + /* + * Hard-coded transforms for different N's + */ + ae_assert(n<=ftbase_maxradix, "FTApplyComplexCodeletTwFFT: N>MaxRadix", _state); + if( n==2 ) + { + v = -2*ae_pi/(n*m); + tw0 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + tw1 = ae_sin(v, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset0 = offs+opidx*operandsize*microvectorsize; + aoffset2 = aoffset0+microvectorsize; + twxm1 = 0.0; + twy = 0.0; + for(mvidx=0; mvidx<=m-1; mvidx++) + { + a0x = a->ptr.p_double[aoffset0]; + a0y = a->ptr.p_double[aoffset0+1]; + a1x = a->ptr.p_double[aoffset2]; + a1y = a->ptr.p_double[aoffset2+1]; + v0 = a0x+a1x; + v1 = a0y+a1y; + v2 = a0x-a1x; + v3 = a0y-a1y; + a->ptr.p_double[aoffset0] = v0; + a->ptr.p_double[aoffset0+1] = v1; + a->ptr.p_double[aoffset2] = v2*(1+twxm1)-v3*twy; + a->ptr.p_double[aoffset2+1] = v3*(1+twxm1)+v2*twy; + aoffset0 = aoffset0+2; + aoffset2 = aoffset2+2; + if( (mvidx+1)%ftbase_updatetw==0 ) + { + v = -2*ae_pi*(mvidx+1)/(n*m); + twxm1 = ae_sin(0.5*v, _state); + twxm1 = -2*twxm1*twxm1; + twy = ae_sin(v, _state); + } + else + { + v = twxm1+tw0+twxm1*tw0-twy*tw1; + twy = twy+tw1+twxm1*tw1+twy*tw0; + twxm1 = v; + } + } + } + return; + } + if( n==3 ) + { + v = -2*ae_pi/(n*m); + tw0 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + tw1 = ae_sin(v, _state); + c1 = ae_cos(2*ae_pi/3, _state)-1; + c2 = ae_sin(2*ae_pi/3, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset0 = offs+opidx*operandsize*microvectorsize; + aoffset2 = aoffset0+microvectorsize; + aoffset4 = aoffset2+microvectorsize; + twx = 1.0; + twxm1 = 0.0; + twy = 0.0; + for(mvidx=0; mvidx<=m-1; mvidx++) + { + a0x = a->ptr.p_double[aoffset0]; + a0y = a->ptr.p_double[aoffset0+1]; + a1x = a->ptr.p_double[aoffset2]; + a1y = a->ptr.p_double[aoffset2+1]; + a2x = a->ptr.p_double[aoffset4]; + a2y = a->ptr.p_double[aoffset4+1]; + t1x = a1x+a2x; + t1y = a1y+a2y; + a0x = a0x+t1x; + a0y = a0y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a1y-a2y); + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + s1y = a0y+m1y; + a1x = s1x+m2x; + a1y = s1y+m2y; + a2x = s1x-m2x; + a2y = s1y-m2y; + tw2x = twx*twx-twy*twy; + tw2y = 2*twx*twy; + a->ptr.p_double[aoffset0] = a0x; + a->ptr.p_double[aoffset0+1] = a0y; + a->ptr.p_double[aoffset2] = a1x*twx-a1y*twy; + a->ptr.p_double[aoffset2+1] = a1y*twx+a1x*twy; + a->ptr.p_double[aoffset4] = a2x*tw2x-a2y*tw2y; + a->ptr.p_double[aoffset4+1] = a2y*tw2x+a2x*tw2y; + aoffset0 = aoffset0+2; + aoffset2 = aoffset2+2; + aoffset4 = aoffset4+2; + if( (mvidx+1)%ftbase_updatetw==0 ) + { + v = -2*ae_pi*(mvidx+1)/(n*m); + twxm1 = ae_sin(0.5*v, _state); + twxm1 = -2*twxm1*twxm1; + twy = ae_sin(v, _state); + twx = twxm1+1; + } + else + { + v = twxm1+tw0+twxm1*tw0-twy*tw1; + twy = twy+tw1+twxm1*tw1+twy*tw0; + twxm1 = v; + twx = v+1; + } + } + } + return; + } + if( n==4 ) + { + v = -2*ae_pi/(n*m); + tw0 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + tw1 = ae_sin(v, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset0 = offs+opidx*operandsize*microvectorsize; + aoffset2 = aoffset0+microvectorsize; + aoffset4 = aoffset2+microvectorsize; + aoffset6 = aoffset4+microvectorsize; + twx = 1.0; + twxm1 = 0.0; + twy = 0.0; + for(mvidx=0; mvidx<=m-1; mvidx++) + { + a0x = a->ptr.p_double[aoffset0]; + a0y = a->ptr.p_double[aoffset0+1]; + a1x = a->ptr.p_double[aoffset2]; + a1y = a->ptr.p_double[aoffset2+1]; + a2x = a->ptr.p_double[aoffset4]; + a2y = a->ptr.p_double[aoffset4+1]; + a3x = a->ptr.p_double[aoffset6]; + a3y = a->ptr.p_double[aoffset6+1]; + t1x = a0x+a2x; + t1y = a0y+a2y; + t2x = a1x+a3x; + t2y = a1y+a3y; + m2x = a0x-a2x; + m2y = a0y-a2y; + m3x = a1y-a3y; + m3y = a3x-a1x; + tw2x = twx*twx-twy*twy; + tw2y = 2*twx*twy; + tw3x = twx*tw2x-twy*tw2y; + tw3y = twx*tw2y+twy*tw2x; + a1x = m2x+m3x; + a1y = m2y+m3y; + a2x = t1x-t2x; + a2y = t1y-t2y; + a3x = m2x-m3x; + a3y = m2y-m3y; + a->ptr.p_double[aoffset0] = t1x+t2x; + a->ptr.p_double[aoffset0+1] = t1y+t2y; + a->ptr.p_double[aoffset2] = a1x*twx-a1y*twy; + a->ptr.p_double[aoffset2+1] = a1y*twx+a1x*twy; + a->ptr.p_double[aoffset4] = a2x*tw2x-a2y*tw2y; + a->ptr.p_double[aoffset4+1] = a2y*tw2x+a2x*tw2y; + a->ptr.p_double[aoffset6] = a3x*tw3x-a3y*tw3y; + a->ptr.p_double[aoffset6+1] = a3y*tw3x+a3x*tw3y; + aoffset0 = aoffset0+2; + aoffset2 = aoffset2+2; + aoffset4 = aoffset4+2; + aoffset6 = aoffset6+2; + if( (mvidx+1)%ftbase_updatetw==0 ) + { + v = -2*ae_pi*(mvidx+1)/(n*m); + twxm1 = ae_sin(0.5*v, _state); + twxm1 = -2*twxm1*twxm1; + twy = ae_sin(v, _state); + twx = twxm1+1; + } + else + { + v = twxm1+tw0+twxm1*tw0-twy*tw1; + twy = twy+tw1+twxm1*tw1+twy*tw0; + twxm1 = v; + twx = v+1; + } + } + } + return; + } + if( n==5 ) + { + v = -2*ae_pi/(n*m); + tw0 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + tw1 = ae_sin(v, _state); + v = 2*ae_pi/5; + c1 = (ae_cos(v, _state)+ae_cos(2*v, _state))/2-1; + c2 = (ae_cos(v, _state)-ae_cos(2*v, _state))/2; + c3 = -ae_sin(v, _state); + c4 = -(ae_sin(v, _state)+ae_sin(2*v, _state)); + c5 = ae_sin(v, _state)-ae_sin(2*v, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset0 = offs+opidx*operandsize*microvectorsize; + aoffset2 = aoffset0+microvectorsize; + aoffset4 = aoffset2+microvectorsize; + aoffset6 = aoffset4+microvectorsize; + aoffset8 = aoffset6+microvectorsize; + twx = 1.0; + twxm1 = 0.0; + twy = 0.0; + for(mvidx=0; mvidx<=m-1; mvidx++) + { + a0x = a->ptr.p_double[aoffset0]; + a0y = a->ptr.p_double[aoffset0+1]; + a1x = a->ptr.p_double[aoffset2]; + a1y = a->ptr.p_double[aoffset2+1]; + a2x = a->ptr.p_double[aoffset4]; + a2y = a->ptr.p_double[aoffset4+1]; + a3x = a->ptr.p_double[aoffset6]; + a3y = a->ptr.p_double[aoffset6+1]; + a4x = a->ptr.p_double[aoffset8]; + a4y = a->ptr.p_double[aoffset8+1]; + t1x = a1x+a4x; + t1y = a1y+a4y; + t2x = a2x+a3x; + t2y = a2y+a3y; + t3x = a1x-a4x; + t3y = a1y-a4y; + t4x = a3x-a2x; + t4y = a3y-a2y; + t5x = t1x+t2x; + t5y = t1y+t2y; + q0x = a0x+t5x; + q0y = a0y+t5y; + m1x = c1*t5x; + m1y = c1*t5y; + m2x = c2*(t1x-t2x); + m2y = c2*(t1y-t2y); + m3x = -c3*(t3y+t4y); + m3y = c3*(t3x+t4x); + m4x = -c4*t4y; + m4y = c4*t4x; + m5x = -c5*t3y; + m5y = c5*t3x; + s3x = m3x-m4x; + s3y = m3y-m4y; + s5x = m3x+m5x; + s5y = m3y+m5y; + s1x = q0x+m1x; + s1y = q0y+m1y; + s2x = s1x+m2x; + s2y = s1y+m2y; + s4x = s1x-m2x; + s4y = s1y-m2y; + tw2x = twx*twx-twy*twy; + tw2y = 2*twx*twy; + tw3x = twx*tw2x-twy*tw2y; + tw3y = twx*tw2y+twy*tw2x; + tw4x = tw2x*tw2x-tw2y*tw2y; + tw4y = tw2x*tw2y+tw2y*tw2x; + a1x = s2x+s3x; + a1y = s2y+s3y; + a2x = s4x+s5x; + a2y = s4y+s5y; + a3x = s4x-s5x; + a3y = s4y-s5y; + a4x = s2x-s3x; + a4y = s2y-s3y; + a->ptr.p_double[aoffset0] = q0x; + a->ptr.p_double[aoffset0+1] = q0y; + a->ptr.p_double[aoffset2] = a1x*twx-a1y*twy; + a->ptr.p_double[aoffset2+1] = a1x*twy+a1y*twx; + a->ptr.p_double[aoffset4] = a2x*tw2x-a2y*tw2y; + a->ptr.p_double[aoffset4+1] = a2x*tw2y+a2y*tw2x; + a->ptr.p_double[aoffset6] = a3x*tw3x-a3y*tw3y; + a->ptr.p_double[aoffset6+1] = a3x*tw3y+a3y*tw3x; + a->ptr.p_double[aoffset8] = a4x*tw4x-a4y*tw4y; + a->ptr.p_double[aoffset8+1] = a4x*tw4y+a4y*tw4x; + aoffset0 = aoffset0+2; + aoffset2 = aoffset2+2; + aoffset4 = aoffset4+2; + aoffset6 = aoffset6+2; + aoffset8 = aoffset8+2; + if( (mvidx+1)%ftbase_updatetw==0 ) + { + v = -2*ae_pi*(mvidx+1)/(n*m); + twxm1 = ae_sin(0.5*v, _state); + twxm1 = -2*twxm1*twxm1; + twy = ae_sin(v, _state); + twx = twxm1+1; + } + else + { + v = twxm1+tw0+twxm1*tw0-twy*tw1; + twy = twy+tw1+twxm1*tw1+twy*tw0; + twxm1 = v; + twx = v+1; + } + } + } + return; + } + if( n==6 ) + { + c1 = ae_cos(2*ae_pi/3, _state)-1; + c2 = ae_sin(2*ae_pi/3, _state); + c3 = ae_cos(-ae_pi/3, _state); + c4 = ae_sin(-ae_pi/3, _state); + v = -2*ae_pi/(n*m); + tw0 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + tw1 = ae_sin(v, _state); + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + aoffset0 = offs+opidx*operandsize*microvectorsize; + aoffset2 = aoffset0+microvectorsize; + aoffset4 = aoffset2+microvectorsize; + aoffset6 = aoffset4+microvectorsize; + aoffset8 = aoffset6+microvectorsize; + aoffset10 = aoffset8+microvectorsize; + twx = 1.0; + twxm1 = 0.0; + twy = 0.0; + for(mvidx=0; mvidx<=m-1; mvidx++) + { + a0x = a->ptr.p_double[aoffset0+0]; + a0y = a->ptr.p_double[aoffset0+1]; + a1x = a->ptr.p_double[aoffset2+0]; + a1y = a->ptr.p_double[aoffset2+1]; + a2x = a->ptr.p_double[aoffset4+0]; + a2y = a->ptr.p_double[aoffset4+1]; + a3x = a->ptr.p_double[aoffset6+0]; + a3y = a->ptr.p_double[aoffset6+1]; + a4x = a->ptr.p_double[aoffset8+0]; + a4y = a->ptr.p_double[aoffset8+1]; + a5x = a->ptr.p_double[aoffset10+0]; + a5y = a->ptr.p_double[aoffset10+1]; + v0 = a0x; + v1 = a0y; + a0x = a0x+a3x; + a0y = a0y+a3y; + a3x = v0-a3x; + a3y = v1-a3y; + v0 = a1x; + v1 = a1y; + a1x = a1x+a4x; + a1y = a1y+a4y; + a4x = v0-a4x; + a4y = v1-a4y; + v0 = a2x; + v1 = a2y; + a2x = a2x+a5x; + a2y = a2y+a5y; + a5x = v0-a5x; + a5y = v1-a5y; + t4x = a4x*c3-a4y*c4; + t4y = a4x*c4+a4y*c3; + a4x = t4x; + a4y = t4y; + t5x = -a5x*c3-a5y*c4; + t5y = a5x*c4-a5y*c3; + a5x = t5x; + a5y = t5y; + t1x = a1x+a2x; + t1y = a1y+a2y; + a0x = a0x+t1x; + a0y = a0y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a1y-a2y); + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + s1y = a0y+m1y; + a1x = s1x+m2x; + a1y = s1y+m2y; + a2x = s1x-m2x; + a2y = s1y-m2y; + t1x = a4x+a5x; + t1y = a4y+a5y; + a3x = a3x+t1x; + a3y = a3y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a4y-a5y); + m2y = c2*(a5x-a4x); + s1x = a3x+m1x; + s1y = a3y+m1y; + a4x = s1x+m2x; + a4y = s1y+m2y; + a5x = s1x-m2x; + a5y = s1y-m2y; + tw2x = twx*twx-twy*twy; + tw2y = 2*twx*twy; + tw3x = twx*tw2x-twy*tw2y; + tw3y = twx*tw2y+twy*tw2x; + tw4x = tw2x*tw2x-tw2y*tw2y; + tw4y = 2*tw2x*tw2y; + tw5x = tw3x*tw2x-tw3y*tw2y; + tw5y = tw3x*tw2y+tw3y*tw2x; + a->ptr.p_double[aoffset0+0] = a0x; + a->ptr.p_double[aoffset0+1] = a0y; + a->ptr.p_double[aoffset2+0] = a3x*twx-a3y*twy; + a->ptr.p_double[aoffset2+1] = a3y*twx+a3x*twy; + a->ptr.p_double[aoffset4+0] = a1x*tw2x-a1y*tw2y; + a->ptr.p_double[aoffset4+1] = a1y*tw2x+a1x*tw2y; + a->ptr.p_double[aoffset6+0] = a4x*tw3x-a4y*tw3y; + a->ptr.p_double[aoffset6+1] = a4y*tw3x+a4x*tw3y; + a->ptr.p_double[aoffset8+0] = a2x*tw4x-a2y*tw4y; + a->ptr.p_double[aoffset8+1] = a2y*tw4x+a2x*tw4y; + a->ptr.p_double[aoffset10+0] = a5x*tw5x-a5y*tw5y; + a->ptr.p_double[aoffset10+1] = a5y*tw5x+a5x*tw5y; + aoffset0 = aoffset0+2; + aoffset2 = aoffset2+2; + aoffset4 = aoffset4+2; + aoffset6 = aoffset6+2; + aoffset8 = aoffset8+2; + aoffset10 = aoffset10+2; + if( (mvidx+1)%ftbase_updatetw==0 ) + { + v = -2*ae_pi*(mvidx+1)/(n*m); + twxm1 = ae_sin(0.5*v, _state); + twxm1 = -2*twxm1*twxm1; + twy = ae_sin(v, _state); + twx = twxm1+1; + } + else + { + v = twxm1+tw0+twxm1*tw0-twy*tw1; + twy = twy+tw1+twxm1*tw1+twy*tw0; + twxm1 = v; + twx = v+1; + } + } + } + return; + } +} + + +/************************************************************************* +This subroutine precomputes data for complex Bluestein's FFT and writes +them to array PrecR[] at specified offset. It is responsibility of the +caller to make sure that PrecR[] is large enough. + +INPUT PARAMETERS: + N - original size of the transform + M - size of the "padded" Bluestein's transform + PrecR - preallocated array + Offs - offset + +OUTPUT PARAMETERS: + PrecR - data at Offs:Offs+4*M-1 are modified: + * PrecR[Offs:Offs+2*M-1] stores Z[k]=exp(i*pi*k^2/N) + * PrecR[Offs+2*M:Offs+4*M-1] stores FFT of the Z + Other parts of PrecR are unchanged. + +NOTE: this function performs internal M-point FFT. It allocates temporary + plan which is destroyed after leaving this function. + + -- ALGLIB -- + Copyright 08.05.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftprecomputebluesteinsfft(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* precr, + ae_int_t offs, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + double bx; + double by; + fasttransformplan plan; + + ae_frame_make(_state, &_frame_block); + _fasttransformplan_init(&plan, _state, ae_true); + + + /* + * Fill first half of PrecR with b[k] = exp(i*pi*k^2/N) + */ + for(i=0; i<=2*m-1; i++) + { + precr->ptr.p_double[offs+i] = 0; + } + for(i=0; i<=n-1; i++) + { + bx = ae_cos(ae_pi/n*i*i, _state); + by = ae_sin(ae_pi/n*i*i, _state); + precr->ptr.p_double[offs+2*i+0] = bx; + precr->ptr.p_double[offs+2*i+1] = by; + precr->ptr.p_double[offs+2*((m-i)%m)+0] = bx; + precr->ptr.p_double[offs+2*((m-i)%m)+1] = by; + } + + /* + * Precomputed FFT + */ + ftcomplexfftplan(m, 1, &plan, _state); + for(i=0; i<=2*m-1; i++) + { + precr->ptr.p_double[offs+2*m+i] = precr->ptr.p_double[offs+i]; + } + ftbase_ftapplysubplan(&plan, 0, precr, offs+2*m, 0, &plan.buffer, 1, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine applies complex Bluestein's FFT to input/output array A. + +INPUT PARAMETERS: + Plan - transformation plan + A - array, must be large enough for plan to work + ABase - base offset in array A, this value points to start of + subarray whose length is equal to length of the plan + AOffset - offset with respect to ABase, 0<=AOffsetptr.p_double[p0+0]; + y = a->ptr.p_double[p0+1]; + bx = plan->precr.ptr.p_double[p1+0]; + by = -plan->precr.ptr.p_double[p1+1]; + bufa->ptr.p_double[2*i+0] = x*bx-y*by; + bufa->ptr.p_double[2*i+1] = x*by+y*bx; + p0 = p0+2; + p1 = p1+2; + } + for(i=2*n; i<=2*m-1; i++) + { + bufa->ptr.p_double[i] = 0; + } + + /* + * Perform convolution of A and Z (using precomputed + * FFT of Z stored in Plan structure). + */ + ftbase_ftapplysubplan(plan, subplan, bufa, 0, 0, bufc, 1, _state); + p0 = 0; + p1 = precoffs+2*m; + for(i=0; i<=m-1; i++) + { + ax = bufa->ptr.p_double[p0+0]; + ay = bufa->ptr.p_double[p0+1]; + bx = plan->precr.ptr.p_double[p1+0]; + by = plan->precr.ptr.p_double[p1+1]; + bufa->ptr.p_double[p0+0] = ax*bx-ay*by; + bufa->ptr.p_double[p0+1] = -(ax*by+ay*bx); + p0 = p0+2; + p1 = p1+2; + } + ftbase_ftapplysubplan(plan, subplan, bufa, 0, 0, bufc, 1, _state); + + /* + * Post processing: + * A:=conj(Z)*conj(A)/M + * Here conj(A)/M corresponds to last stage of inverse DFT, + * and conj(Z) comes from Bluestein's FFT algorithm. + */ + p0 = precoffs; + p1 = 0; + p2 = abase+aoffset+op*2*n; + for(i=0; i<=n-1; i++) + { + bx = plan->precr.ptr.p_double[p0+0]; + by = plan->precr.ptr.p_double[p0+1]; + rx = bufa->ptr.p_double[p1+0]/m; + ry = -bufa->ptr.p_double[p1+1]/m; + a->ptr.p_double[p2+0] = rx*bx-ry*(-by); + a->ptr.p_double[p2+1] = rx*(-by)+ry*bx; + p0 = p0+2; + p1 = p1+2; + p2 = p2+2; + } + } +} + + +/************************************************************************* +This subroutine precomputes data for complex Rader's FFT and writes them +to array PrecR[] at specified offset. It is responsibility of the caller +to make sure that PrecR[] is large enough. + +INPUT PARAMETERS: + N - original size of the transform (before reduction to N-1) + RQ - primitive root modulo N + RIQ - inverse of primitive root modulo N + PrecR - preallocated array + Offs - offset + +OUTPUT PARAMETERS: + PrecR - data at Offs:Offs+2*(N-1)-1 store FFT of Rader's factors, + other parts of PrecR are unchanged. + +NOTE: this function performs internal (N-1)-point FFT. It allocates temporary + plan which is destroyed after leaving this function. + + -- ALGLIB -- + Copyright 08.05.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftprecomputeradersfft(ae_int_t n, + ae_int_t rq, + ae_int_t riq, + /* Real */ ae_vector* precr, + ae_int_t offs, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t q; + fasttransformplan plan; + ae_int_t kiq; + double v; + + ae_frame_make(_state, &_frame_block); + _fasttransformplan_init(&plan, _state, ae_true); + + + /* + * Fill PrecR with Rader factors, perform FFT + */ + kiq = 1; + for(q=0; q<=n-2; q++) + { + v = -2*ae_pi*kiq/n; + precr->ptr.p_double[offs+2*q+0] = ae_cos(v, _state); + precr->ptr.p_double[offs+2*q+1] = ae_sin(v, _state); + kiq = kiq*riq%n; + } + ftcomplexfftplan(n-1, 1, &plan, _state); + ftbase_ftapplysubplan(&plan, 0, precr, offs, 0, &plan.buffer, 1, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine applies complex Rader's FFT to input/output array A. + +INPUT PARAMETERS: + A - array, must be large enough for plan to work + ABase - base offset in array A, this value points to start of + subarray whose length is equal to length of the plan + AOffset - offset with respect to ABase, 0<=AOffset=1, "FTApplyComplexRefFFT: OperandsCnt<1", _state); + + /* + * Process operands + */ + for(opidx=0; opidx<=operandscnt-1; opidx++) + { + + /* + * fill QA + */ + kq = 1; + p0 = abase+aoffset+opidx*n*2; + p1 = aoffset+opidx*n*2; + rx = a->ptr.p_double[p0+0]; + ry = a->ptr.p_double[p0+1]; + x0 = rx; + y0 = ry; + for(q=0; q<=n-2; q++) + { + ax = a->ptr.p_double[p0+2*kq+0]; + ay = a->ptr.p_double[p0+2*kq+1]; + buf->ptr.p_double[p1+0] = ax; + buf->ptr.p_double[p1+1] = ay; + rx = rx+ax; + ry = ry+ay; + kq = kq*rq%n; + p1 = p1+2; + } + p0 = abase+aoffset+opidx*n*2; + p1 = aoffset+opidx*n*2; + for(q=0; q<=n-2; q++) + { + a->ptr.p_double[p0] = buf->ptr.p_double[p1]; + a->ptr.p_double[p0+1] = buf->ptr.p_double[p1+1]; + p0 = p0+2; + p1 = p1+2; + } + + /* + * Convolution + */ + ftbase_ftapplysubplan(plan, subplan, a, abase, aoffset+opidx*n*2, buf, 1, _state); + p0 = abase+aoffset+opidx*n*2; + p1 = precoffs; + for(i=0; i<=n-2; i++) + { + ax = a->ptr.p_double[p0+0]; + ay = a->ptr.p_double[p0+1]; + bx = plan->precr.ptr.p_double[p1+0]; + by = plan->precr.ptr.p_double[p1+1]; + a->ptr.p_double[p0+0] = ax*bx-ay*by; + a->ptr.p_double[p0+1] = -(ax*by+ay*bx); + p0 = p0+2; + p1 = p1+2; + } + ftbase_ftapplysubplan(plan, subplan, a, abase, aoffset+opidx*n*2, buf, 1, _state); + p0 = abase+aoffset+opidx*n*2; + for(i=0; i<=n-2; i++) + { + a->ptr.p_double[p0+0] = a->ptr.p_double[p0+0]/(n-1); + a->ptr.p_double[p0+1] = -a->ptr.p_double[p0+1]/(n-1); + p0 = p0+2; + } + + /* + * Result + */ + buf->ptr.p_double[aoffset+opidx*n*2+0] = rx; + buf->ptr.p_double[aoffset+opidx*n*2+1] = ry; + kiq = 1; + p0 = aoffset+opidx*n*2; + p1 = abase+aoffset+opidx*n*2; + for(q=0; q<=n-2; q++) + { + buf->ptr.p_double[p0+2*kiq+0] = x0+a->ptr.p_double[p1+0]; + buf->ptr.p_double[p0+2*kiq+1] = y0+a->ptr.p_double[p1+1]; + kiq = kiq*riq%n; + p1 = p1+2; + } + p0 = abase+aoffset+opidx*n*2; + p1 = aoffset+opidx*n*2; + for(q=0; q<=n-1; q++) + { + a->ptr.p_double[p0] = buf->ptr.p_double[p1]; + a->ptr.p_double[p0+1] = buf->ptr.p_double[p1+1]; + p0 = p0+2; + p1 = p1+2; + } + } +} + + +/************************************************************************* +Factorizes task size N into product of two smaller sizes N1 and N2 + +INPUT PARAMETERS: + N - task size, N>0 + IsRoot - whether taks is root task (first one in a sequence) + +OUTPUT PARAMETERS: + N1, N2 - such numbers that: + * for prime N: N1=N2=0 + * for composite N<=MaxRadix: N1=N2=0 + * for composite N>MaxRadix: 1<=N1<=N2, N1*N2=N + + -- ALGLIB -- + Copyright 08.04.2013 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftfactorize(ae_int_t n, + ae_bool isroot, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state) +{ + ae_int_t j; + ae_int_t k; + + *n1 = 0; + *n2 = 0; + + ae_assert(n>0, "FTFactorize: N<=0", _state); + *n1 = 0; + *n2 = 0; + + /* + * Small N + */ + if( n<=ftbase_maxradix ) + { + return; + } + + /* + * Large N, recursive split + */ + if( n>ftbase_recursivethreshold ) + { + k = ae_iceil(ae_sqrt(n, _state), _state)+1; + ae_assert(k*k>=n, "FTFactorize: internal error during recursive factorization", _state); + for(j=k; j>=2; j--) + { + if( n%j==0 ) + { + *n1 = ae_minint(n/j, j, _state); + *n2 = ae_maxint(n/j, j, _state); + return; + } + } + } + + /* + * N>MaxRadix, try to find good codelet + */ + for(j=ftbase_maxradix; j>=2; j--) + { + if( n%j==0 ) + { + *n1 = j; + *n2 = n/j; + break; + } + } + + /* + * In case no good codelet was found, + * try to factorize N into product of ANY primes. + */ + if( *n1*(*n2)!=n ) + { + for(j=2; j<=n-1; j++) + { + if( n%j==0 ) + { + *n1 = j; + *n2 = n/j; + break; + } + if( j*j>n ) + { + break; + } + } + } + + /* + * normalize + */ + if( *n1>(*n2) ) + { + j = *n1; + *n1 = *n2; + *n2 = j; + } +} + + +/************************************************************************* +Returns optimistic estimate of the FFT cost, in UNITs (1 UNIT = 100 KFLOPs) + +INPUT PARAMETERS: + N - task size, N>0 + +RESULU: + cost in UNITs, rounded down to nearest integer + +NOTE: If FFT cost is less than 1 UNIT, it will return 0 as result. + + -- ALGLIB -- + Copyright 08.04.2013 by Bochkanov Sergey +*************************************************************************/ +static ae_int_t ftbase_ftoptimisticestimate(ae_int_t n, ae_state *_state) +{ + ae_int_t result; + + + ae_assert(n>0, "FTOptimisticEstimate: N<=0", _state); + result = ae_ifloor(1.0E-5*5*n*ae_log(n, _state)/ae_log(2, _state), _state); + return result; +} + + +/************************************************************************* +Twiddle factors calculation + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ffttwcalc(/* Real */ ae_vector* a, + ae_int_t aoffset, + ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j2; + ae_int_t n; + ae_int_t halfn1; + ae_int_t offs; + double x; + double y; + double twxm1; + double twy; + double twbasexm1; + double twbasey; + double twrowxm1; + double twrowy; + double tmpx; + double tmpy; + double v; + ae_int_t updatetw2; + + + + /* + * Multiplication by twiddle factors for complex Cooley-Tukey FFT + * with N factorized as N1*N2. + * + * Naive solution to this problem is given below: + * + * > for K:=1 to N2-1 do + * > for J:=1 to N1-1 do + * > begin + * > Idx:=K*N1+J; + * > X:=A[AOffset+2*Idx+0]; + * > Y:=A[AOffset+2*Idx+1]; + * > TwX:=Cos(-2*Pi()*K*J/(N1*N2)); + * > TwY:=Sin(-2*Pi()*K*J/(N1*N2)); + * > A[AOffset+2*Idx+0]:=X*TwX-Y*TwY; + * > A[AOffset+2*Idx+1]:=X*TwY+Y*TwX; + * > end; + * + * However, there are exist more efficient solutions. + * + * Each pass of the inner cycle corresponds to multiplication of one + * entry of A by W[k,j]=exp(-I*2*pi*k*j/N). This factor can be rewritten + * as exp(-I*2*pi*k/N)^j. So we can replace costly exponentiation by + * repeated multiplication: W[k,j+1]=W[k,j]*exp(-I*2*pi*k/N), with + * second factor being computed once in the beginning of the iteration. + * + * Also, exp(-I*2*pi*k/N) can be represented as exp(-I*2*pi/N)^k, i.e. + * we have W[K+1,1]=W[K,1]*W[1,1]. + * + * In our loop we use following variables: + * * [TwBaseXM1,TwBaseY] = [cos(2*pi/N)-1, sin(2*pi/N)] + * * [TwRowXM1, TwRowY] = [cos(2*pi*I/N)-1, sin(2*pi*I/N)] + * * [TwXM1, TwY] = [cos(2*pi*I*J/N)-1, sin(2*pi*I*J/N)] + * + * Meaning of the variables: + * * [TwXM1,TwY] is current twiddle factor W[I,J] + * * [TwRowXM1, TwRowY] is W[I,1] + * * [TwBaseXM1,TwBaseY] is W[1,1] + * + * During inner loop we multiply current twiddle factor by W[I,1], + * during outer loop we update W[I,1]. + * + */ + ae_assert(ftbase_updatetw>=2, "FFTTwCalc: internal error - UpdateTw<2", _state); + updatetw2 = ftbase_updatetw/2; + halfn1 = n1/2; + n = n1*n2; + v = -2*ae_pi/n; + twbasexm1 = -2*ae_sqr(ae_sin(0.5*v, _state), _state); + twbasey = ae_sin(v, _state); + twrowxm1 = 0; + twrowy = 0; + offs = aoffset; + for(i=0; i<=n2-1; i++) + { + + /* + * Initialize twiddle factor for current row + */ + twxm1 = 0; + twy = 0; + + /* + * N1-point block is separated into 2-point chunks and residual 1-point chunk + * (in case N1 is odd). Unrolled loop is several times faster. + */ + for(j2=0; j2<=halfn1-1; j2++) + { + + /* + * Processing: + * * process first element in a chunk. + * * update twiddle factor (unconditional update) + * * process second element + * * conditional update of the twiddle factor + */ + x = a->ptr.p_double[offs+0]; + y = a->ptr.p_double[offs+1]; + tmpx = x*(1+twxm1)-y*twy; + tmpy = x*twy+y*(1+twxm1); + a->ptr.p_double[offs+0] = tmpx; + a->ptr.p_double[offs+1] = tmpy; + tmpx = (1+twxm1)*twrowxm1-twy*twrowy; + twy = twy+(1+twxm1)*twrowy+twy*twrowxm1; + twxm1 = twxm1+tmpx; + x = a->ptr.p_double[offs+2]; + y = a->ptr.p_double[offs+3]; + tmpx = x*(1+twxm1)-y*twy; + tmpy = x*twy+y*(1+twxm1); + a->ptr.p_double[offs+2] = tmpx; + a->ptr.p_double[offs+3] = tmpy; + offs = offs+4; + if( (j2+1)%updatetw2==0&&j2ptr.p_double[offs+0]; + y = a->ptr.p_double[offs+1]; + tmpx = x*(1+twxm1)-y*twy; + tmpy = x*twy+y*(1+twxm1); + a->ptr.p_double[offs+0] = tmpx; + a->ptr.p_double[offs+1] = tmpy; + offs = offs+2; + } + + /* + * update TwRow: TwRow(new) = TwRow(old)*TwBase + */ + if( iptr.p_double[astart], 1, &buf->ptr.p_double[0], 1, ae_v_len(astart,astart+2*m*n-1)); +} + + +/************************************************************************* +Recurrent subroutine for a InternalComplexLinTranspose + +Write A^T to B, where: +* A is m*n complex matrix stored in array A as pairs of real/image values, + beginning from AStart position, with AStride stride +* B is n*m complex matrix stored in array B as pairs of real/image values, + beginning from BStart position, with BStride stride +stride is measured in complex numbers, i.e. in real/image pairs. + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ffticltrec(/* Real */ ae_vector* a, + ae_int_t astart, + ae_int_t astride, + /* Real */ ae_vector* b, + ae_int_t bstart, + ae_int_t bstride, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t idx1; + ae_int_t idx2; + ae_int_t m2; + ae_int_t m1; + ae_int_t n1; + + + if( m==0||n==0 ) + { + return; + } + if( ae_maxint(m, n, _state)<=8 ) + { + m2 = 2*bstride; + for(i=0; i<=m-1; i++) + { + idx1 = bstart+2*i; + idx2 = astart+2*i*astride; + for(j=0; j<=n-1; j++) + { + b->ptr.p_double[idx1+0] = a->ptr.p_double[idx2+0]; + b->ptr.p_double[idx1+1] = a->ptr.p_double[idx2+1]; + idx1 = idx1+m2; + idx2 = idx2+2; + } + } + return; + } + if( n>m ) + { + + /* + * New partition: + * + * "A^T -> B" becomes "(A1 A2)^T -> ( B1 ) + * ( B2 ) + */ + n1 = n/2; + if( n-n1>=8&&n1%8!=0 ) + { + n1 = n1+(8-n1%8); + } + ae_assert(n-n1>0, "Assertion failed", _state); + ftbase_ffticltrec(a, astart, astride, b, bstart, bstride, m, n1, _state); + ftbase_ffticltrec(a, astart+2*n1, astride, b, bstart+2*n1*bstride, bstride, m, n-n1, _state); + } + else + { + + /* + * New partition: + * + * "A^T -> B" becomes "( A1 )^T -> ( B1 B2 ) + * ( A2 ) + */ + m1 = m/2; + if( m-m1>=8&&m1%8!=0 ) + { + m1 = m1+(8-m1%8); + } + ae_assert(m-m1>0, "Assertion failed", _state); + ftbase_ffticltrec(a, astart, astride, b, bstart, bstride, m1, n, _state); + ftbase_ffticltrec(a, astart+2*m1*astride, astride, b, bstart+2*m1, bstride, m-m1, n, _state); + } +} + + +/************************************************************************* +Recurrent subroutine for a InternalRealLinTranspose + + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_fftirltrec(/* Real */ ae_vector* a, + ae_int_t astart, + ae_int_t astride, + /* Real */ ae_vector* b, + ae_int_t bstart, + ae_int_t bstride, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t idx1; + ae_int_t idx2; + ae_int_t m1; + ae_int_t n1; + + + if( m==0||n==0 ) + { + return; + } + if( ae_maxint(m, n, _state)<=8 ) + { + for(i=0; i<=m-1; i++) + { + idx1 = bstart+i; + idx2 = astart+i*astride; + for(j=0; j<=n-1; j++) + { + b->ptr.p_double[idx1] = a->ptr.p_double[idx2]; + idx1 = idx1+bstride; + idx2 = idx2+1; + } + } + return; + } + if( n>m ) + { + + /* + * New partition: + * + * "A^T -> B" becomes "(A1 A2)^T -> ( B1 ) + * ( B2 ) + */ + n1 = n/2; + if( n-n1>=8&&n1%8!=0 ) + { + n1 = n1+(8-n1%8); + } + ae_assert(n-n1>0, "Assertion failed", _state); + ftbase_fftirltrec(a, astart, astride, b, bstart, bstride, m, n1, _state); + ftbase_fftirltrec(a, astart+n1, astride, b, bstart+n1*bstride, bstride, m, n-n1, _state); + } + else + { + + /* + * New partition: + * + * "A^T -> B" becomes "( A1 )^T -> ( B1 B2 ) + * ( A2 ) + */ + m1 = m/2; + if( m-m1>=8&&m1%8!=0 ) + { + m1 = m1+(8-m1%8); + } + ae_assert(m-m1>0, "Assertion failed", _state); + ftbase_fftirltrec(a, astart, astride, b, bstart, bstride, m1, n, _state); + ftbase_fftirltrec(a, astart+m1*astride, astride, b, bstart+m1, bstride, m-m1, n, _state); + } +} + + +/************************************************************************* +recurrent subroutine for FFTFindSmoothRec + + -- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +static void ftbase_ftbasefindsmoothrec(ae_int_t n, + ae_int_t seed, + ae_int_t leastfactor, + ae_int_t* best, + ae_state *_state) +{ + + + ae_assert(ftbase_ftbasemaxsmoothfactor<=5, "FTBaseFindSmoothRec: internal error!", _state); + if( seed>=n ) + { + *best = ae_minint(*best, seed, _state); + return; + } + if( leastfactor<=2 ) + { + ftbase_ftbasefindsmoothrec(n, seed*2, 2, best, _state); + } + if( leastfactor<=3 ) + { + ftbase_ftbasefindsmoothrec(n, seed*3, 3, best, _state); + } + if( leastfactor<=5 ) + { + ftbase_ftbasefindsmoothrec(n, seed*5, 5, best, _state); + } +} + + +ae_bool _fasttransformplan_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + fasttransformplan *p = (fasttransformplan*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->entries, 0, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->buffer, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->precr, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->preci, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->bluesteinpool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _fasttransformplan_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + fasttransformplan *dst = (fasttransformplan*)_dst; + fasttransformplan *src = (fasttransformplan*)_src; + if( !ae_matrix_init_copy(&dst->entries, &src->entries, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->buffer, &src->buffer, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->precr, &src->precr, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->preci, &src->preci, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init_copy(&dst->bluesteinpool, &src->bluesteinpool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _fasttransformplan_clear(void* _p) +{ + fasttransformplan *p = (fasttransformplan*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->entries); + ae_vector_clear(&p->buffer); + ae_vector_clear(&p->precr); + ae_vector_clear(&p->preci); + ae_shared_pool_clear(&p->bluesteinpool); +} + + +void _fasttransformplan_destroy(void* _p) +{ + fasttransformplan *p = (fasttransformplan*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->entries); + ae_vector_destroy(&p->buffer); + ae_vector_destroy(&p->precr); + ae_vector_destroy(&p->preci); + ae_shared_pool_destroy(&p->bluesteinpool); +} + + + + +double nulog1p(double x, ae_state *_state) +{ + double z; + double lp; + double lq; + double result; + + + z = 1.0+x; + if( ae_fp_less(z,0.70710678118654752440)||ae_fp_greater(z,1.41421356237309504880) ) + { + result = ae_log(z, _state); + return result; + } + z = x*x; + lp = 4.5270000862445199635215E-5; + lp = lp*x+4.9854102823193375972212E-1; + lp = lp*x+6.5787325942061044846969E0; + lp = lp*x+2.9911919328553073277375E1; + lp = lp*x+6.0949667980987787057556E1; + lp = lp*x+5.7112963590585538103336E1; + lp = lp*x+2.0039553499201281259648E1; + lq = 1.0000000000000000000000E0; + lq = lq*x+1.5062909083469192043167E1; + lq = lq*x+8.3047565967967209469434E1; + lq = lq*x+2.2176239823732856465394E2; + lq = lq*x+3.0909872225312059774938E2; + lq = lq*x+2.1642788614495947685003E2; + lq = lq*x+6.0118660497603843919306E1; + z = -0.5*z+x*(z*lp/lq); + result = x+z; + return result; +} + + +double nuexpm1(double x, ae_state *_state) +{ + double r; + double xx; + double ep; + double eq; + double result; + + + if( ae_fp_less(x,-0.5)||ae_fp_greater(x,0.5) ) + { + result = ae_exp(x, _state)-1.0; + return result; + } + xx = x*x; + ep = 1.2617719307481059087798E-4; + ep = ep*xx+3.0299440770744196129956E-2; + ep = ep*xx+9.9999999999999999991025E-1; + eq = 3.0019850513866445504159E-6; + eq = eq*xx+2.5244834034968410419224E-3; + eq = eq*xx+2.2726554820815502876593E-1; + eq = eq*xx+2.0000000000000000000897E0; + r = x*ep; + r = r/(eq-r); + result = r+r; + return result; +} + + +double nucosm1(double x, ae_state *_state) +{ + double xx; + double c; + double result; + + + if( ae_fp_less(x,-0.25*ae_pi)||ae_fp_greater(x,0.25*ae_pi) ) + { + result = ae_cos(x, _state)-1; + return result; + } + xx = x*x; + c = 4.7377507964246204691685E-14; + c = c*xx-1.1470284843425359765671E-11; + c = c*xx+2.0876754287081521758361E-9; + c = c*xx-2.7557319214999787979814E-7; + c = c*xx+2.4801587301570552304991E-5; + c = c*xx-1.3888888888888872993737E-3; + c = c*xx+4.1666666666666666609054E-2; + result = -0.5*xx+xx*xx*c; + return result; +} + + + + + +} + diff --git a/src/inc/alglib/alglibinternal.h b/src/inc/alglib/alglibinternal.h new file mode 100644 index 0000000..a59bf7e --- /dev/null +++ b/src/inc/alglib/alglibinternal.h @@ -0,0 +1,1074 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _alglibinternal_pkg_h +#define _alglibinternal_pkg_h +#include "ap.h" + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_vector ia0; + ae_vector ia1; + ae_vector ia2; + ae_vector ia3; + ae_vector ra0; + ae_vector ra1; + ae_vector ra2; + ae_vector ra3; +} apbuffers; +typedef struct +{ + ae_bool val; +} sboolean; +typedef struct +{ + ae_vector val; +} sbooleanarray; +typedef struct +{ + ae_int_t val; +} sinteger; +typedef struct +{ + ae_vector val; +} sintegerarray; +typedef struct +{ + double val; +} sreal; +typedef struct +{ + ae_vector val; +} srealarray; +typedef struct +{ + ae_complex val; +} scomplex; +typedef struct +{ + ae_vector val; +} scomplexarray; +typedef struct +{ + ae_int_t chunksize; + ae_int_t ntotal; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_vector batch4buf; + ae_vector hpcbuf; + ae_matrix xy; + ae_matrix xy2; + ae_vector xyrow; + ae_vector x; + ae_vector y; + ae_vector desiredy; + double e; + ae_vector g; + ae_vector tmp0; +} mlpbuffers; +typedef struct +{ + ae_bool brackt; + ae_bool stage1; + ae_int_t infoc; + double dg; + double dgm; + double dginit; + double dgtest; + double dgx; + double dgxm; + double dgy; + double dgym; + double finit; + double ftest1; + double fm; + double fx; + double fxm; + double fy; + double fym; + double stx; + double sty; + double stmin; + double stmax; + double width; + double width1; + double xtrapf; +} linminstate; +typedef struct +{ + ae_bool needf; + ae_vector x; + double f; + ae_int_t n; + ae_vector xbase; + ae_vector s; + double stplen; + double fcur; + double stpmax; + ae_int_t fmax; + ae_int_t nfev; + ae_int_t info; + rcommstate rstate; +} armijostate; +typedef struct +{ + ae_matrix entries; + ae_vector buffer; + ae_vector precr; + ae_vector preci; + ae_shared_pool bluesteinpool; +} fasttransformplan; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +ae_bool seterrorflag(ae_bool* flag, ae_bool cond, ae_state *_state); +ae_bool seterrorflagdiff(ae_bool* flag, + double val, + double refval, + double tol, + double s, + ae_state *_state); +void touchint(ae_int_t* a, ae_state *_state); +void touchreal(double* a, ae_state *_state); +double inttoreal(ae_int_t a, ae_state *_state); +double log2(double x, ae_state *_state); +ae_bool approxequalrel(double a, double b, double tol, ae_state *_state); +void taskgenint1d(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void taskgenint1dequidist(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void taskgenint1dcheb1(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void taskgenint1dcheb2(double a, + double b, + ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +ae_bool aredistinct(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +ae_bool aresameboolean(ae_bool v1, ae_bool v2, ae_state *_state); +void bvectorsetlengthatleast(/* Boolean */ ae_vector* x, + ae_int_t n, + ae_state *_state); +void ivectorsetlengthatleast(/* Integer */ ae_vector* x, + ae_int_t n, + ae_state *_state); +void rvectorsetlengthatleast(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +void rmatrixsetlengthatleast(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void rmatrixresize(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void imatrixresize(/* Integer */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +ae_bool isfinitevector(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +ae_bool isfinitecvector(/* Complex */ ae_vector* z, + ae_int_t n, + ae_state *_state); +ae_bool apservisfinitematrix(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +ae_bool apservisfinitecmatrix(/* Complex */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +ae_bool isfinitertrmatrix(/* Real */ ae_matrix* x, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +ae_bool apservisfinitectrmatrix(/* Complex */ ae_matrix* x, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +ae_bool apservisfiniteornanmatrix(/* Real */ ae_matrix* x, + ae_int_t m, + ae_int_t n, + ae_state *_state); +double safepythag2(double x, double y, ae_state *_state); +double safepythag3(double x, double y, double z, ae_state *_state); +ae_int_t saferdiv(double x, double y, double* r, ae_state *_state); +double safeminposrv(double x, double y, double v, ae_state *_state); +void apperiodicmap(double* x, + double a, + double b, + double* k, + ae_state *_state); +double randomnormal(ae_state *_state); +void randomunit(ae_int_t n, /* Real */ ae_vector* x, ae_state *_state); +void inc(ae_int_t* v, ae_state *_state); +void dec(ae_int_t* v, ae_state *_state); +void countdown(ae_int_t* v, ae_state *_state); +double boundval(double x, double b1, double b2, ae_state *_state); +void alloccomplex(ae_serializer* s, ae_complex v, ae_state *_state); +void serializecomplex(ae_serializer* s, ae_complex v, ae_state *_state); +ae_complex unserializecomplex(ae_serializer* s, ae_state *_state); +void allocrealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_int_t n, + ae_state *_state); +void serializerealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_int_t n, + ae_state *_state); +void unserializerealarray(ae_serializer* s, + /* Real */ ae_vector* v, + ae_state *_state); +void allocintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_int_t n, + ae_state *_state); +void serializeintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_int_t n, + ae_state *_state); +void unserializeintegerarray(ae_serializer* s, + /* Integer */ ae_vector* v, + ae_state *_state); +void allocrealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_int_t n0, + ae_int_t n1, + ae_state *_state); +void serializerealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_int_t n0, + ae_int_t n1, + ae_state *_state); +void unserializerealmatrix(ae_serializer* s, + /* Real */ ae_matrix* v, + ae_state *_state); +void copyintegerarray(/* Integer */ ae_vector* src, + /* Integer */ ae_vector* dst, + ae_state *_state); +void copyrealarray(/* Real */ ae_vector* src, + /* Real */ ae_vector* dst, + ae_state *_state); +void copyrealmatrix(/* Real */ ae_matrix* src, + /* Real */ ae_matrix* dst, + ae_state *_state); +ae_int_t recsearch(/* Integer */ ae_vector* a, + ae_int_t nrec, + ae_int_t nheader, + ae_int_t i0, + ae_int_t i1, + /* Integer */ ae_vector* b, + ae_state *_state); +void splitlengtheven(ae_int_t tasksize, + ae_int_t* task0, + ae_int_t* task1, + ae_state *_state); +void splitlength(ae_int_t tasksize, + ae_int_t chunksize, + ae_int_t* task0, + ae_int_t* task1, + ae_state *_state); +ae_bool _apbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _apbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _apbuffers_clear(void* _p); +void _apbuffers_destroy(void* _p); +ae_bool _sboolean_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sboolean_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sboolean_clear(void* _p); +void _sboolean_destroy(void* _p); +ae_bool _sbooleanarray_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sbooleanarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sbooleanarray_clear(void* _p); +void _sbooleanarray_destroy(void* _p); +ae_bool _sinteger_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sinteger_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sinteger_clear(void* _p); +void _sinteger_destroy(void* _p); +ae_bool _sintegerarray_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sintegerarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sintegerarray_clear(void* _p); +void _sintegerarray_destroy(void* _p); +ae_bool _sreal_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sreal_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sreal_clear(void* _p); +void _sreal_destroy(void* _p); +ae_bool _srealarray_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _srealarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _srealarray_clear(void* _p); +void _srealarray_destroy(void* _p); +ae_bool _scomplex_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _scomplex_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _scomplex_clear(void* _p); +void _scomplex_destroy(void* _p); +ae_bool _scomplexarray_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _scomplexarray_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _scomplexarray_clear(void* _p); +void _scomplexarray_destroy(void* _p); +ae_int_t getrdfserializationcode(ae_state *_state); +ae_int_t getkdtreeserializationcode(ae_state *_state); +ae_int_t getmlpserializationcode(ae_state *_state); +ae_int_t getmlpeserializationcode(ae_state *_state); +ae_int_t getrbfserializationcode(ae_state *_state); +void tagsort(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + ae_state *_state); +void tagsortbuf(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + apbuffers* buf, + ae_state *_state); +void tagsortfasti(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Integer */ ae_vector* bufb, + ae_int_t n, + ae_state *_state); +void tagsortfastr(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* bufa, + /* Real */ ae_vector* bufb, + ae_int_t n, + ae_state *_state); +void tagsortfast(/* Real */ ae_vector* a, + /* Real */ ae_vector* bufa, + ae_int_t n, + ae_state *_state); +void tagsortmiddleir(/* Integer */ ae_vector* a, + /* Real */ ae_vector* b, + ae_int_t offset, + ae_int_t n, + ae_state *_state); +void tagheappushi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t* n, + double va, + ae_int_t vb, + ae_state *_state); +void tagheapreplacetopi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t n, + double va, + ae_int_t vb, + ae_state *_state); +void tagheappopi(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t* n, + ae_state *_state); +ae_int_t lowerbound(/* Real */ ae_vector* a, + ae_int_t n, + double t, + ae_state *_state); +ae_int_t upperbound(/* Real */ ae_vector* a, + ae_int_t n, + double t, + ae_state *_state); +void rankx(/* Real */ ae_vector* x, + ae_int_t n, + ae_bool iscentered, + apbuffers* buf, + ae_state *_state); +ae_bool cmatrixrank1f(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_vector* u, + ae_int_t iu, + /* Complex */ ae_vector* v, + ae_int_t iv, + ae_state *_state); +ae_bool rmatrixrank1f(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_vector* u, + ae_int_t iu, + /* Real */ ae_vector* v, + ae_int_t iv, + ae_state *_state); +ae_bool cmatrixmvf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Complex */ ae_vector* x, + ae_int_t ix, + /* Complex */ ae_vector* y, + ae_int_t iy, + ae_state *_state); +ae_bool rmatrixmvf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Real */ ae_vector* x, + ae_int_t ix, + /* Real */ ae_vector* y, + ae_int_t iy, + ae_state *_state); +ae_bool cmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +ae_bool cmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +ae_bool rmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +ae_bool rmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +ae_bool cmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +ae_bool rmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +ae_bool rmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +ae_bool cmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void cmatrixgemmk(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void rmatrixgemmk(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void rmatrixgemmk44v00(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void rmatrixgemmk44v01(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void rmatrixgemmk44v10(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void rmatrixgemmk44v11(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +ae_bool rmatrixsyrkmkl(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +ae_bool rmatrixgemmmkl(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +double vectornorm2(/* Real */ ae_vector* x, + ae_int_t i1, + ae_int_t i2, + ae_state *_state); +ae_int_t vectoridxabsmax(/* Real */ ae_vector* x, + ae_int_t i1, + ae_int_t i2, + ae_state *_state); +ae_int_t columnidxabsmax(/* Real */ ae_matrix* x, + ae_int_t i1, + ae_int_t i2, + ae_int_t j, + ae_state *_state); +ae_int_t rowidxabsmax(/* Real */ ae_matrix* x, + ae_int_t j1, + ae_int_t j2, + ae_int_t i, + ae_state *_state); +double upperhessenberg1norm(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + /* Real */ ae_vector* work, + ae_state *_state); +void copymatrix(/* Real */ ae_matrix* a, + ae_int_t is1, + ae_int_t is2, + ae_int_t js1, + ae_int_t js2, + /* Real */ ae_matrix* b, + ae_int_t id1, + ae_int_t id2, + ae_int_t jd1, + ae_int_t jd2, + ae_state *_state); +void inplacetranspose(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + /* Real */ ae_vector* work, + ae_state *_state); +void copyandtranspose(/* Real */ ae_matrix* a, + ae_int_t is1, + ae_int_t is2, + ae_int_t js1, + ae_int_t js2, + /* Real */ ae_matrix* b, + ae_int_t id1, + ae_int_t id2, + ae_int_t jd1, + ae_int_t jd2, + ae_state *_state); +void matrixvectormultiply(/* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t i2, + ae_int_t j1, + ae_int_t j2, + ae_bool trans, + /* Real */ ae_vector* x, + ae_int_t ix1, + ae_int_t ix2, + double alpha, + /* Real */ ae_vector* y, + ae_int_t iy1, + ae_int_t iy2, + double beta, + ae_state *_state); +double pythag2(double x, double y, ae_state *_state); +void matrixmatrixmultiply(/* Real */ ae_matrix* a, + ae_int_t ai1, + ae_int_t ai2, + ae_int_t aj1, + ae_int_t aj2, + ae_bool transa, + /* Real */ ae_matrix* b, + ae_int_t bi1, + ae_int_t bi2, + ae_int_t bj1, + ae_int_t bj2, + ae_bool transb, + double alpha, + /* Real */ ae_matrix* c, + ae_int_t ci1, + ae_int_t ci2, + ae_int_t cj1, + ae_int_t cj2, + double beta, + /* Real */ ae_vector* work, + ae_state *_state); +void hermitianmatrixvectormultiply(/* Complex */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Complex */ ae_vector* x, + ae_complex alpha, + /* Complex */ ae_vector* y, + ae_state *_state); +void hermitianrank2update(/* Complex */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Complex */ ae_vector* x, + /* Complex */ ae_vector* y, + /* Complex */ ae_vector* t, + ae_complex alpha, + ae_state *_state); +void generatereflection(/* Real */ ae_vector* x, + ae_int_t n, + double* tau, + ae_state *_state); +void applyreflectionfromtheleft(/* Real */ ae_matrix* c, + double tau, + /* Real */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* work, + ae_state *_state); +void applyreflectionfromtheright(/* Real */ ae_matrix* c, + double tau, + /* Real */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* work, + ae_state *_state); +void complexgeneratereflection(/* Complex */ ae_vector* x, + ae_int_t n, + ae_complex* tau, + ae_state *_state); +void complexapplyreflectionfromtheleft(/* Complex */ ae_matrix* c, + ae_complex tau, + /* Complex */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Complex */ ae_vector* work, + ae_state *_state); +void complexapplyreflectionfromtheright(/* Complex */ ae_matrix* c, + ae_complex tau, + /* Complex */ ae_vector* v, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Complex */ ae_vector* work, + ae_state *_state); +void symmetricmatrixvectormultiply(/* Real */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* x, + double alpha, + /* Real */ ae_vector* y, + ae_state *_state); +void symmetricrank2update(/* Real */ ae_matrix* a, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* t, + double alpha, + ae_state *_state); +void applyrotationsfromtheleft(ae_bool isforward, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* c, + /* Real */ ae_vector* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* work, + ae_state *_state); +void applyrotationsfromtheright(ae_bool isforward, + ae_int_t m1, + ae_int_t m2, + ae_int_t n1, + ae_int_t n2, + /* Real */ ae_vector* c, + /* Real */ ae_vector* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* work, + ae_state *_state); +void generaterotation(double f, + double g, + double* cs, + double* sn, + double* r, + ae_state *_state); +ae_bool upperhessenbergschurdecomposition(/* Real */ ae_matrix* h, + ae_int_t n, + /* Real */ ae_matrix* s, + ae_state *_state); +void internalschurdecomposition(/* Real */ ae_matrix* h, + ae_int_t n, + ae_int_t tneeded, + ae_int_t zneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* z, + ae_int_t* info, + ae_state *_state); +void rmatrixtrsafesolve(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* x, + double* s, + ae_bool isupper, + ae_bool istrans, + ae_bool isunit, + ae_state *_state); +void safesolvetriangular(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* x, + double* s, + ae_bool isupper, + ae_bool istrans, + ae_bool isunit, + ae_bool normin, + /* Real */ ae_vector* cnorm, + ae_state *_state); +ae_bool rmatrixscaledtrsafesolve(/* Real */ ae_matrix* a, + double sa, + ae_int_t n, + /* Real */ ae_vector* x, + ae_bool isupper, + ae_int_t trans, + ae_bool isunit, + double maxgrowth, + ae_state *_state); +ae_bool cmatrixscaledtrsafesolve(/* Complex */ ae_matrix* a, + double sa, + ae_int_t n, + /* Complex */ ae_vector* x, + ae_bool isupper, + ae_int_t trans, + ae_bool isunit, + double maxgrowth, + ae_state *_state); +void hpcpreparechunkedgradient(/* Real */ ae_vector* weights, + ae_int_t wcount, + ae_int_t ntotal, + ae_int_t nin, + ae_int_t nout, + mlpbuffers* buf, + ae_state *_state); +void hpcfinalizechunkedgradient(mlpbuffers* buf, + /* Real */ ae_vector* grad, + ae_state *_state); +ae_bool hpcchunkedgradient(/* Real */ ae_vector* weights, + /* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + double* e, + ae_bool naturalerrorfunc, + ae_state *_state); +ae_bool hpcchunkedprocess(/* Real */ ae_vector* weights, + /* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + ae_state *_state); +ae_bool _mlpbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpbuffers_clear(void* _p); +void _mlpbuffers_destroy(void* _p); +void xdot(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* temp, + double* r, + double* rerr, + ae_state *_state); +void xcdot(/* Complex */ ae_vector* a, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* temp, + ae_complex* r, + double* rerr, + ae_state *_state); +void linminnormalized(/* Real */ ae_vector* d, + double* stp, + ae_int_t n, + ae_state *_state); +void mcsrch(ae_int_t n, + /* Real */ ae_vector* x, + double* f, + /* Real */ ae_vector* g, + /* Real */ ae_vector* s, + double* stp, + double stpmax, + double gtol, + ae_int_t* info, + ae_int_t* nfev, + /* Real */ ae_vector* wa, + linminstate* state, + ae_int_t* stage, + ae_state *_state); +void armijocreate(ae_int_t n, + /* Real */ ae_vector* x, + double f, + /* Real */ ae_vector* s, + double stp, + double stpmax, + ae_int_t fmax, + armijostate* state, + ae_state *_state); +ae_bool armijoiteration(armijostate* state, ae_state *_state); +void armijoresults(armijostate* state, + ae_int_t* info, + double* stp, + double* f, + ae_state *_state); +ae_bool _linminstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _linminstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _linminstate_clear(void* _p); +void _linminstate_destroy(void* _p); +ae_bool _armijostate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _armijostate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _armijostate_clear(void* _p); +void _armijostate_destroy(void* _p); +void findprimitiverootandinverse(ae_int_t n, + ae_int_t* proot, + ae_int_t* invproot, + ae_state *_state); +void ftcomplexfftplan(ae_int_t n, + ae_int_t k, + fasttransformplan* plan, + ae_state *_state); +void ftapplyplan(fasttransformplan* plan, + /* Real */ ae_vector* a, + ae_int_t offsa, + ae_int_t repcnt, + ae_state *_state); +void ftbasefactorize(ae_int_t n, + ae_int_t tasktype, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state); +ae_bool ftbaseissmooth(ae_int_t n, ae_state *_state); +ae_int_t ftbasefindsmooth(ae_int_t n, ae_state *_state); +ae_int_t ftbasefindsmootheven(ae_int_t n, ae_state *_state); +double ftbasegetflopestimate(ae_int_t n, ae_state *_state); +ae_bool _fasttransformplan_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _fasttransformplan_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _fasttransformplan_clear(void* _p); +void _fasttransformplan_destroy(void* _p); +double nulog1p(double x, ae_state *_state); +double nuexpm1(double x, ae_state *_state); +double nucosm1(double x, ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/alglibmisc.cpp b/src/inc/alglib/alglibmisc.cpp new file mode 100644 index 0000000..cc4e095 --- /dev/null +++ b/src/inc/alglib/alglibmisc.cpp @@ -0,0 +1,3611 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "alglibmisc.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Portable high quality random number generator state. +Initialized with HQRNDRandomize() or HQRNDSeed(). + +Fields: + S1, S2 - seed values + V - precomputed value + MagicV - 'magic' value used to determine whether State structure + was correctly initialized. +*************************************************************************/ +_hqrndstate_owner::_hqrndstate_owner() +{ + p_struct = (alglib_impl::hqrndstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::hqrndstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_hqrndstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_hqrndstate_owner::_hqrndstate_owner(const _hqrndstate_owner &rhs) +{ + p_struct = (alglib_impl::hqrndstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::hqrndstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_hqrndstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_hqrndstate_owner& _hqrndstate_owner::operator=(const _hqrndstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_hqrndstate_clear(p_struct); + if( !alglib_impl::_hqrndstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_hqrndstate_owner::~_hqrndstate_owner() +{ + alglib_impl::_hqrndstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::hqrndstate* _hqrndstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::hqrndstate* _hqrndstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +hqrndstate::hqrndstate() : _hqrndstate_owner() +{ +} + +hqrndstate::hqrndstate(const hqrndstate &rhs):_hqrndstate_owner(rhs) +{ +} + +hqrndstate& hqrndstate::operator=(const hqrndstate &rhs) +{ + if( this==&rhs ) + return *this; + _hqrndstate_owner::operator=(rhs); + return *this; +} + +hqrndstate::~hqrndstate() +{ +} + +/************************************************************************* +HQRNDState initialization with random values which come from standard +RNG. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndrandomize(hqrndstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hqrndrandomize(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +HQRNDState initialization with seed values + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndseed(const ae_int_t s1, const ae_int_t s2, hqrndstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hqrndseed(s1, s2, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function generates random real number in (0,1), +not including interval boundaries + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrnduniformr(const hqrndstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hqrnduniformr(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function generates random integer number in [0, N) + +1. State structure must be initialized with HQRNDRandomize() or HQRNDSeed() +2. N can be any positive number except for very large numbers: + * close to 2^31 on 32-bit systems + * close to 2^62 on 64-bit systems + An exception will be generated if N is too large. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_int_t hqrnduniformi(const hqrndstate &state, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::hqrnduniformi(const_cast(state.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Random number generator: normal numbers + +This function generates one random number from normal distribution. +Its performance is equal to that of HQRNDNormal2() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrndnormal(const hqrndstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hqrndnormal(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Random number generator: random X and Y such that X^2+Y^2=1 + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndunit2(const hqrndstate &state, double &x, double &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hqrndunit2(const_cast(state.c_ptr()), &x, &y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Random number generator: normal numbers + +This function generates two independent random numbers from normal +distribution. Its performance is equal to that of HQRNDNormal() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndnormal2(const hqrndstate &state, double &x1, double &x2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hqrndnormal2(const_cast(state.c_ptr()), &x1, &x2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Random number generator: exponential distribution + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 11.08.2007 by Bochkanov Sergey +*************************************************************************/ +double hqrndexponential(const hqrndstate &state, const double lambdav) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hqrndexponential(const_cast(state.c_ptr()), lambdav, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function generates random number from discrete distribution given by +finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample + N - number of elements to use, N>=1 + +RESULT + this function returns one of the X[i] for random i=0..N-1 + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrnddiscrete(const hqrndstate &state, const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hqrnddiscrete(const_cast(state.c_ptr()), const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function generates random number from continuous distribution given +by finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample, array[N] (can be larger, in this case only + leading N elements are used). THIS ARRAY MUST BE SORTED BY + ASCENDING. + N - number of elements to use, N>=1 + +RESULT + this function returns random number from continuous distribution which + tries to approximate X as mush as possible. min(X)<=Result<=max(X). + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrndcontinuous(const hqrndstate &state, const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hqrndcontinuous(const_cast(state.c_ptr()), const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_kdtree_owner::_kdtree_owner() +{ + p_struct = (alglib_impl::kdtree*)alglib_impl::ae_malloc(sizeof(alglib_impl::kdtree), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_kdtree_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_kdtree_owner::_kdtree_owner(const _kdtree_owner &rhs) +{ + p_struct = (alglib_impl::kdtree*)alglib_impl::ae_malloc(sizeof(alglib_impl::kdtree), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_kdtree_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_kdtree_owner& _kdtree_owner::operator=(const _kdtree_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_kdtree_clear(p_struct); + if( !alglib_impl::_kdtree_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_kdtree_owner::~_kdtree_owner() +{ + alglib_impl::_kdtree_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::kdtree* _kdtree_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::kdtree* _kdtree_owner::c_ptr() const +{ + return const_cast(p_struct); +} +kdtree::kdtree() : _kdtree_owner() +{ +} + +kdtree::kdtree(const kdtree &rhs):_kdtree_owner(rhs) +{ +} + +kdtree& kdtree::operator=(const kdtree &rhs) +{ + if( this==&rhs ) + return *this; + _kdtree_owner::operator=(rhs); + return *this; +} + +kdtree::~kdtree() +{ +} + + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void kdtreeserialize(kdtree &obj, std::string &s_out) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + alglib_impl::ae_int_t ssize; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_alloc_start(&serializer); + alglib_impl::kdtreealloc(&serializer, obj.c_ptr(), &state); + ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer); + s_out.clear(); + s_out.reserve((size_t)(ssize+1)); + alglib_impl::ae_serializer_sstart_str(&serializer, &s_out); + alglib_impl::kdtreeserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + if( s_out.length()>(size_t)ssize ) + throw ap_error("ALGLIB: serialization integrity error"); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void kdtreeunserialize(std::string &s_in, kdtree &obj) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_ustart_str(&serializer, &s_in); + alglib_impl::kdtreeunserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values and optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + N - number of points, N>=0. + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuild(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreebuild(const_cast(xy.c_ptr()), n, nx, ny, normtype, const_cast(kdt.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values and optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + N - number of points, N>=0. + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuild(const real_2d_array &xy, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = xy.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreebuild(const_cast(xy.c_ptr()), n, nx, ny, normtype, const_cast(kdt.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values, integer tags and +optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + Tags - tags, array[0..N-1], contains integer tags associated + with points. + N - number of points, N>=0 + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuildtagged(const real_2d_array &xy, const integer_1d_array &tags, const ae_int_t n, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreebuildtagged(const_cast(xy.c_ptr()), const_cast(tags.c_ptr()), n, nx, ny, normtype, const_cast(kdt.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values, integer tags and +optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + Tags - tags, array[0..N-1], contains integer tags associated + with points. + N - number of points, N>=0 + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuildtagged(const real_2d_array &xy, const integer_1d_array &tags, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (xy.rows()!=tags.length())) + throw ap_error("Error while calling 'kdtreebuildtagged': looks like one of arguments has wrong size"); + n = xy.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreebuildtagged(const_cast(xy.c_ptr()), const_cast(tags.c_ptr()), n, nx, ny, normtype, const_cast(kdt.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +K-NN query: K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of actual neighbors found (either K or N, if K>N). + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const bool selfmatch) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryknn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), k, selfmatch, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +K-NN query: K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of actual neighbors found (either K or N, if K>N). + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + bool selfmatch; + + selfmatch = true; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryknn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), k, selfmatch, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +R-NN query: all points within R-sphere centered at X + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + R - radius of sphere (in corresponding norm), R>0 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of neighbors found, >=0 + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +actual results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryrnn(const kdtree &kdt, const real_1d_array &x, const double r, const bool selfmatch) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryrnn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), r, selfmatch, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +R-NN query: all points within R-sphere centered at X + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + R - radius of sphere (in corresponding norm), R>0 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of neighbors found, >=0 + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +actual results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryrnn(const kdtree &kdt, const real_1d_array &x, const double r) +{ + alglib_impl::ae_state _alglib_env_state; + bool selfmatch; + + selfmatch = true; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryrnn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), r, selfmatch, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +K-NN query: approximate K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + Eps - approximation factor, Eps>=0. eps-approximate nearest + neighbor is a neighbor whose distance from X is at + most (1+eps) times distance of true nearest neighbor. + +RESULT + number of actual neighbors found (either K or N, if K>N). + +NOTES + significant performance gain may be achieved only when Eps is is on + the order of magnitude of 1 or larger. + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryaknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const bool selfmatch, const double eps) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryaknn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), k, selfmatch, eps, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +K-NN query: approximate K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + Eps - approximation factor, Eps>=0. eps-approximate nearest + neighbor is a neighbor whose distance from X is at + most (1+eps) times distance of true nearest neighbor. + +RESULT + number of actual neighbors found (either K or N, if K>N). + +NOTES + significant performance gain may be achieved only when Eps is is on + the order of magnitude of 1 or larger. + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryaknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const double eps) +{ + alglib_impl::ae_state _alglib_env_state; + bool selfmatch; + + selfmatch = true; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::kdtreequeryaknn(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), k, selfmatch, eps, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +X-values from last query + +INPUT PARAMETERS + KDT - KD-tree + X - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + X - rows are filled with X-values + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsx(const kdtree &kdt, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsx(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +X- and Y-values from last query + +INPUT PARAMETERS + KDT - KD-tree + XY - possibly pre-allocated buffer. If XY is too small to store + result, it is resized. If size(XY) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + XY - rows are filled with points: first NX columns with + X-values, next NY columns - with Y-values. + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxy(const kdtree &kdt, real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsxy(const_cast(kdt.c_ptr()), const_cast(xy.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Tags from last query + +INPUT PARAMETERS + KDT - KD-tree + Tags - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + Tags - filled with tags associated with points, + or, when no tags were supplied, with zeros + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstags(const kdtree &kdt, integer_1d_array &tags) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultstags(const_cast(kdt.c_ptr()), const_cast(tags.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Distances from last query + +INPUT PARAMETERS + KDT - KD-tree + R - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + R - filled with distances (in corresponding norm) + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistances(const kdtree &kdt, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsdistances(const_cast(kdt.c_ptr()), const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +X-values from last query; 'interactive' variant for languages like Python +which support constructs like "X = KDTreeQueryResultsXI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxi(const kdtree &kdt, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsxi(const_cast(kdt.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +XY-values from last query; 'interactive' variant for languages like Python +which support constructs like "XY = KDTreeQueryResultsXYI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxyi(const kdtree &kdt, real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsxyi(const_cast(kdt.c_ptr()), const_cast(xy.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Tags from last query; 'interactive' variant for languages like Python +which support constructs like "Tags = KDTreeQueryResultsTagsI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstagsi(const kdtree &kdt, integer_1d_array &tags) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultstagsi(const_cast(kdt.c_ptr()), const_cast(tags.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Distances from last query; 'interactive' variant for languages like Python +which support constructs like "R = KDTreeQueryResultsDistancesI(KDT)" +and interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistancesi(const kdtree &kdt, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kdtreequeryresultsdistancesi(const_cast(kdt.c_ptr()), const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static ae_int_t hqrnd_hqrndmax = 2147483561; +static ae_int_t hqrnd_hqrndm1 = 2147483563; +static ae_int_t hqrnd_hqrndm2 = 2147483399; +static ae_int_t hqrnd_hqrndmagic = 1634357784; +static ae_int_t hqrnd_hqrndintegerbase(hqrndstate* state, + ae_state *_state); + + +static ae_int_t nearestneighbor_splitnodesize = 6; +static ae_int_t nearestneighbor_kdtreefirstversion = 0; +static void nearestneighbor_kdtreesplit(kdtree* kdt, + ae_int_t i1, + ae_int_t i2, + ae_int_t d, + double s, + ae_int_t* i3, + ae_state *_state); +static void nearestneighbor_kdtreegeneratetreerec(kdtree* kdt, + ae_int_t* nodesoffs, + ae_int_t* splitsoffs, + ae_int_t i1, + ae_int_t i2, + ae_int_t maxleafsize, + ae_state *_state); +static void nearestneighbor_kdtreequerynnrec(kdtree* kdt, + ae_int_t offs, + ae_state *_state); +static void nearestneighbor_kdtreeinitbox(kdtree* kdt, + /* Real */ ae_vector* x, + ae_state *_state); +static void nearestneighbor_kdtreeallocdatasetindependent(kdtree* kdt, + ae_int_t nx, + ae_int_t ny, + ae_state *_state); +static void nearestneighbor_kdtreeallocdatasetdependent(kdtree* kdt, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_state *_state); +static void nearestneighbor_kdtreealloctemporaries(kdtree* kdt, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_state *_state); + + + + + +/************************************************************************* +HQRNDState initialization with random values which come from standard +RNG. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndrandomize(hqrndstate* state, ae_state *_state) +{ + ae_int_t s0; + ae_int_t s1; + + _hqrndstate_clear(state); + + s0 = ae_randominteger(hqrnd_hqrndm1, _state); + s1 = ae_randominteger(hqrnd_hqrndm2, _state); + hqrndseed(s0, s1, state, _state); +} + + +/************************************************************************* +HQRNDState initialization with seed values + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndseed(ae_int_t s1, + ae_int_t s2, + hqrndstate* state, + ae_state *_state) +{ + + _hqrndstate_clear(state); + + + /* + * Protection against negative seeds: + * + * SEED := -(SEED+1) + * + * We can use just "-SEED" because there exists such integer number N + * that N<0, -N=N<0 too. (This number is equal to 0x800...000). Need + * to handle such seed correctly forces us to use a bit complicated + * formula. + */ + if( s1<0 ) + { + s1 = -(s1+1); + } + if( s2<0 ) + { + s2 = -(s2+1); + } + state->s1 = s1%(hqrnd_hqrndm1-1)+1; + state->s2 = s2%(hqrnd_hqrndm2-1)+1; + state->magicv = hqrnd_hqrndmagic; +} + + +/************************************************************************* +This function generates random real number in (0,1), +not including interval boundaries + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrnduniformr(hqrndstate* state, ae_state *_state) +{ + double result; + + + result = (double)(hqrnd_hqrndintegerbase(state, _state)+1)/(double)(hqrnd_hqrndmax+2); + return result; +} + + +/************************************************************************* +This function generates random integer number in [0, N) + +1. State structure must be initialized with HQRNDRandomize() or HQRNDSeed() +2. N can be any positive number except for very large numbers: + * close to 2^31 on 32-bit systems + * close to 2^62 on 64-bit systems + An exception will be generated if N is too large. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_int_t hqrnduniformi(hqrndstate* state, ae_int_t n, ae_state *_state) +{ + ae_int_t maxcnt; + ae_int_t mx; + ae_int_t a; + ae_int_t b; + ae_int_t result; + + + ae_assert(n>0, "HQRNDUniformI: N<=0!", _state); + maxcnt = hqrnd_hqrndmax+1; + + /* + * Two branches: one for N<=MaxCnt, another for N>MaxCnt. + */ + if( n>maxcnt ) + { + + /* + * N>=MaxCnt. + * + * We have two options here: + * a) N is exactly divisible by MaxCnt + * b) N is not divisible by MaxCnt + * + * In both cases we reduce problem on interval spanning [0,N) + * to several subproblems on intervals spanning [0,MaxCnt). + */ + if( n%maxcnt==0 ) + { + + /* + * N is exactly divisible by MaxCnt. + * + * [0,N) range is dividided into N/MaxCnt bins, + * each of them having length equal to MaxCnt. + * + * We generate: + * * random bin number B + * * random offset within bin A + * Both random numbers are generated by recursively + * calling HQRNDUniformI(). + * + * Result is equal to A+MaxCnt*B. + */ + ae_assert(n/maxcnt<=maxcnt, "HQRNDUniformI: N is too large", _state); + a = hqrnduniformi(state, maxcnt, _state); + b = hqrnduniformi(state, n/maxcnt, _state); + result = a+maxcnt*b; + } + else + { + + /* + * N is NOT exactly divisible by MaxCnt. + * + * [0,N) range is dividided into Ceil(N/MaxCnt) bins, + * each of them having length equal to MaxCnt. + * + * We generate: + * * random bin number B in [0, Ceil(N/MaxCnt)-1] + * * random offset within bin A + * * if both of what is below is true + * 1) bin number B is that of the last bin + * 2) A >= N mod MaxCnt + * then we repeat generation of A/B. + * This stage is essential in order to avoid bias in the result. + * * otherwise, we return A*MaxCnt+N + */ + ae_assert(n/maxcnt+1<=maxcnt, "HQRNDUniformI: N is too large", _state); + result = -1; + do + { + a = hqrnduniformi(state, maxcnt, _state); + b = hqrnduniformi(state, n/maxcnt+1, _state); + if( b==n/maxcnt&&a>=n%maxcnt ) + { + continue; + } + result = a+maxcnt*b; + } + while(result<0); + } + } + else + { + + /* + * N<=MaxCnt + * + * Code below is a bit complicated because we can not simply + * return "HQRNDIntegerBase() mod N" - it will be skewed for + * large N's in [0.1*HQRNDMax...HQRNDMax]. + */ + mx = maxcnt-maxcnt%n; + do + { + result = hqrnd_hqrndintegerbase(state, _state); + } + while(result>=mx); + result = result%n; + } + return result; +} + + +/************************************************************************* +Random number generator: normal numbers + +This function generates one random number from normal distribution. +Its performance is equal to that of HQRNDNormal2() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrndnormal(hqrndstate* state, ae_state *_state) +{ + double v1; + double v2; + double result; + + + hqrndnormal2(state, &v1, &v2, _state); + result = v1; + return result; +} + + +/************************************************************************* +Random number generator: random X and Y such that X^2+Y^2=1 + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndunit2(hqrndstate* state, double* x, double* y, ae_state *_state) +{ + double v; + double mx; + double mn; + + *x = 0; + *y = 0; + + do + { + hqrndnormal2(state, x, y, _state); + } + while(!(ae_fp_neq(*x,0)||ae_fp_neq(*y,0))); + mx = ae_maxreal(ae_fabs(*x, _state), ae_fabs(*y, _state), _state); + mn = ae_minreal(ae_fabs(*x, _state), ae_fabs(*y, _state), _state); + v = mx*ae_sqrt(1+ae_sqr(mn/mx, _state), _state); + *x = *x/v; + *y = *y/v; +} + + +/************************************************************************* +Random number generator: normal numbers + +This function generates two independent random numbers from normal +distribution. Its performance is equal to that of HQRNDNormal() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndnormal2(hqrndstate* state, + double* x1, + double* x2, + ae_state *_state) +{ + double u; + double v; + double s; + + *x1 = 0; + *x2 = 0; + + for(;;) + { + u = 2*hqrnduniformr(state, _state)-1; + v = 2*hqrnduniformr(state, _state)-1; + s = ae_sqr(u, _state)+ae_sqr(v, _state); + if( ae_fp_greater(s,0)&&ae_fp_less(s,1) ) + { + + /* + * two Sqrt's instead of one to + * avoid overflow when S is too small + */ + s = ae_sqrt(-2*ae_log(s, _state), _state)/ae_sqrt(s, _state); + *x1 = u*s; + *x2 = v*s; + return; + } + } +} + + +/************************************************************************* +Random number generator: exponential distribution + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 11.08.2007 by Bochkanov Sergey +*************************************************************************/ +double hqrndexponential(hqrndstate* state, + double lambdav, + ae_state *_state) +{ + double result; + + + ae_assert(ae_fp_greater(lambdav,0), "HQRNDExponential: LambdaV<=0!", _state); + result = -ae_log(hqrnduniformr(state, _state), _state)/lambdav; + return result; +} + + +/************************************************************************* +This function generates random number from discrete distribution given by +finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample + N - number of elements to use, N>=1 + +RESULT + this function returns one of the X[i] for random i=0..N-1 + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrnddiscrete(hqrndstate* state, + /* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double result; + + + ae_assert(n>0, "HQRNDDiscrete: N<=0", _state); + ae_assert(n<=x->cnt, "HQRNDDiscrete: Length(X)ptr.p_double[hqrnduniformi(state, n, _state)]; + return result; +} + + +/************************************************************************* +This function generates random number from continuous distribution given +by finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample, array[N] (can be larger, in this case only + leading N elements are used). THIS ARRAY MUST BE SORTED BY + ASCENDING. + N - number of elements to use, N>=1 + +RESULT + this function returns random number from continuous distribution which + tries to approximate X as mush as possible. min(X)<=Result<=max(X). + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrndcontinuous(hqrndstate* state, + /* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double mx; + double mn; + ae_int_t i; + double result; + + + ae_assert(n>0, "HQRNDContinuous: N<=0", _state); + ae_assert(n<=x->cnt, "HQRNDContinuous: Length(X)ptr.p_double[0]; + return result; + } + i = hqrnduniformi(state, n-1, _state); + mn = x->ptr.p_double[i]; + mx = x->ptr.p_double[i+1]; + ae_assert(ae_fp_greater_eq(mx,mn), "HQRNDDiscrete: X is not sorted by ascending", _state); + if( ae_fp_neq(mx,mn) ) + { + result = (mx-mn)*hqrnduniformr(state, _state)+mn; + } + else + { + result = mn; + } + return result; +} + + +/************************************************************************* +This function returns random integer in [0,HQRNDMax] + +L'Ecuyer, Efficient and portable combined random number generators +*************************************************************************/ +static ae_int_t hqrnd_hqrndintegerbase(hqrndstate* state, + ae_state *_state) +{ + ae_int_t k; + ae_int_t result; + + + ae_assert(state->magicv==hqrnd_hqrndmagic, "HQRNDIntegerBase: State is not correctly initialized!", _state); + k = state->s1/53668; + state->s1 = 40014*(state->s1-k*53668)-k*12211; + if( state->s1<0 ) + { + state->s1 = state->s1+2147483563; + } + k = state->s2/52774; + state->s2 = 40692*(state->s2-k*52774)-k*3791; + if( state->s2<0 ) + { + state->s2 = state->s2+2147483399; + } + + /* + * Result + */ + result = state->s1-state->s2; + if( result<1 ) + { + result = result+2147483562; + } + result = result-1; + return result; +} + + +ae_bool _hqrndstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + hqrndstate *p = (hqrndstate*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _hqrndstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + hqrndstate *dst = (hqrndstate*)_dst; + hqrndstate *src = (hqrndstate*)_src; + dst->s1 = src->s1; + dst->s2 = src->s2; + dst->magicv = src->magicv; + return ae_true; +} + + +void _hqrndstate_clear(void* _p) +{ + hqrndstate *p = (hqrndstate*)_p; + ae_touch_ptr((void*)p); +} + + +void _hqrndstate_destroy(void* _p) +{ + hqrndstate *p = (hqrndstate*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values and optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + N - number of points, N>=0. + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuild(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_int_t normtype, + kdtree* kdt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tags; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _kdtree_clear(kdt); + ae_vector_init(&tags, 0, DT_INT, _state, ae_true); + + ae_assert(n>=0, "KDTreeBuild: N<0", _state); + ae_assert(nx>=1, "KDTreeBuild: NX<1", _state); + ae_assert(ny>=0, "KDTreeBuild: NY<0", _state); + ae_assert(normtype>=0&&normtype<=2, "KDTreeBuild: incorrect NormType", _state); + ae_assert(xy->rows>=n, "KDTreeBuild: rows(X)cols>=nx+ny||n==0, "KDTreeBuild: cols(X)0 ) + { + ae_vector_set_length(&tags, n, _state); + for(i=0; i<=n-1; i++) + { + tags.ptr.p_int[i] = 0; + } + } + kdtreebuildtagged(xy, &tags, n, nx, ny, normtype, kdt, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values, integer tags and +optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + Tags - tags, array[0..N-1], contains integer tags associated + with points. + N - number of points, N>=0 + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuildtagged(/* Real */ ae_matrix* xy, + /* Integer */ ae_vector* tags, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_int_t normtype, + kdtree* kdt, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t maxnodes; + ae_int_t nodesoffs; + ae_int_t splitsoffs; + + _kdtree_clear(kdt); + + ae_assert(n>=0, "KDTreeBuildTagged: N<0", _state); + ae_assert(nx>=1, "KDTreeBuildTagged: NX<1", _state); + ae_assert(ny>=0, "KDTreeBuildTagged: NY<0", _state); + ae_assert(normtype>=0&&normtype<=2, "KDTreeBuildTagged: incorrect NormType", _state); + ae_assert(xy->rows>=n, "KDTreeBuildTagged: rows(X)cols>=nx+ny||n==0, "KDTreeBuildTagged: cols(X)n = n; + kdt->nx = nx; + kdt->ny = ny; + kdt->normtype = normtype; + kdt->kcur = 0; + + /* + * N=0 => quick exit + */ + if( n==0 ) + { + return; + } + + /* + * Allocate + */ + nearestneighbor_kdtreeallocdatasetindependent(kdt, nx, ny, _state); + nearestneighbor_kdtreeallocdatasetdependent(kdt, n, nx, ny, _state); + + /* + * Initial fill + */ + for(i=0; i<=n-1; i++) + { + ae_v_move(&kdt->xy.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx-1)); + ae_v_move(&kdt->xy.ptr.pp_double[i][nx], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(nx,2*nx+ny-1)); + kdt->tags.ptr.p_int[i] = tags->ptr.p_int[i]; + } + + /* + * Determine bounding box + */ + ae_v_move(&kdt->boxmin.ptr.p_double[0], 1, &kdt->xy.ptr.pp_double[0][0], 1, ae_v_len(0,nx-1)); + ae_v_move(&kdt->boxmax.ptr.p_double[0], 1, &kdt->xy.ptr.pp_double[0][0], 1, ae_v_len(0,nx-1)); + for(i=1; i<=n-1; i++) + { + for(j=0; j<=nx-1; j++) + { + kdt->boxmin.ptr.p_double[j] = ae_minreal(kdt->boxmin.ptr.p_double[j], kdt->xy.ptr.pp_double[i][j], _state); + kdt->boxmax.ptr.p_double[j] = ae_maxreal(kdt->boxmax.ptr.p_double[j], kdt->xy.ptr.pp_double[i][j], _state); + } + } + + /* + * prepare tree structure + * * MaxNodes=N because we guarantee no trivial splits, i.e. + * every split will generate two non-empty boxes + */ + maxnodes = n; + ae_vector_set_length(&kdt->nodes, nearestneighbor_splitnodesize*2*maxnodes, _state); + ae_vector_set_length(&kdt->splits, 2*maxnodes, _state); + nodesoffs = 0; + splitsoffs = 0; + ae_v_move(&kdt->curboxmin.ptr.p_double[0], 1, &kdt->boxmin.ptr.p_double[0], 1, ae_v_len(0,nx-1)); + ae_v_move(&kdt->curboxmax.ptr.p_double[0], 1, &kdt->boxmax.ptr.p_double[0], 1, ae_v_len(0,nx-1)); + nearestneighbor_kdtreegeneratetreerec(kdt, &nodesoffs, &splitsoffs, 0, n, 8, _state); +} + + +/************************************************************************* +K-NN query: K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of actual neighbors found (either K or N, if K>N). + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryknn(kdtree* kdt, + /* Real */ ae_vector* x, + ae_int_t k, + ae_bool selfmatch, + ae_state *_state) +{ + ae_int_t result; + + + ae_assert(k>=1, "KDTreeQueryKNN: K<1!", _state); + ae_assert(x->cnt>=kdt->nx, "KDTreeQueryKNN: Length(X)nx, _state), "KDTreeQueryKNN: X contains infinite or NaN values!", _state); + result = kdtreequeryaknn(kdt, x, k, selfmatch, 0.0, _state); + return result; +} + + +/************************************************************************* +R-NN query: all points within R-sphere centered at X + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + R - radius of sphere (in corresponding norm), R>0 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of neighbors found, >=0 + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +actual results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryrnn(kdtree* kdt, + /* Real */ ae_vector* x, + double r, + ae_bool selfmatch, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t result; + + + ae_assert(ae_fp_greater(r,0), "KDTreeQueryRNN: incorrect R!", _state); + ae_assert(x->cnt>=kdt->nx, "KDTreeQueryRNN: Length(X)nx, _state), "KDTreeQueryRNN: X contains infinite or NaN values!", _state); + + /* + * Handle special case: KDT.N=0 + */ + if( kdt->n==0 ) + { + kdt->kcur = 0; + result = 0; + return result; + } + + /* + * Prepare parameters + */ + kdt->kneeded = 0; + if( kdt->normtype!=2 ) + { + kdt->rneeded = r; + } + else + { + kdt->rneeded = ae_sqr(r, _state); + } + kdt->selfmatch = selfmatch; + kdt->approxf = 1; + kdt->kcur = 0; + + /* + * calculate distance from point to current bounding box + */ + nearestneighbor_kdtreeinitbox(kdt, x, _state); + + /* + * call recursive search + * results are returned as heap + */ + nearestneighbor_kdtreequerynnrec(kdt, 0, _state); + + /* + * pop from heap to generate ordered representation + * + * last element is not pop'ed because it is already in + * its place + */ + result = kdt->kcur; + j = kdt->kcur; + for(i=kdt->kcur; i>=2; i--) + { + tagheappopi(&kdt->r, &kdt->idx, &j, _state); + } + return result; +} + + +/************************************************************************* +K-NN query: approximate K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + Eps - approximation factor, Eps>=0. eps-approximate nearest + neighbor is a neighbor whose distance from X is at + most (1+eps) times distance of true nearest neighbor. + +RESULT + number of actual neighbors found (either K or N, if K>N). + +NOTES + significant performance gain may be achieved only when Eps is is on + the order of magnitude of 1 or larger. + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryaknn(kdtree* kdt, + /* Real */ ae_vector* x, + ae_int_t k, + ae_bool selfmatch, + double eps, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t result; + + + ae_assert(k>0, "KDTreeQueryAKNN: incorrect K!", _state); + ae_assert(ae_fp_greater_eq(eps,0), "KDTreeQueryAKNN: incorrect Eps!", _state); + ae_assert(x->cnt>=kdt->nx, "KDTreeQueryAKNN: Length(X)nx, _state), "KDTreeQueryAKNN: X contains infinite or NaN values!", _state); + + /* + * Handle special case: KDT.N=0 + */ + if( kdt->n==0 ) + { + kdt->kcur = 0; + result = 0; + return result; + } + + /* + * Prepare parameters + */ + k = ae_minint(k, kdt->n, _state); + kdt->kneeded = k; + kdt->rneeded = 0; + kdt->selfmatch = selfmatch; + if( kdt->normtype==2 ) + { + kdt->approxf = 1/ae_sqr(1+eps, _state); + } + else + { + kdt->approxf = 1/(1+eps); + } + kdt->kcur = 0; + + /* + * calculate distance from point to current bounding box + */ + nearestneighbor_kdtreeinitbox(kdt, x, _state); + + /* + * call recursive search + * results are returned as heap + */ + nearestneighbor_kdtreequerynnrec(kdt, 0, _state); + + /* + * pop from heap to generate ordered representation + * + * last element is non pop'ed because it is already in + * its place + */ + result = kdt->kcur; + j = kdt->kcur; + for(i=kdt->kcur; i>=2; i--) + { + tagheappopi(&kdt->r, &kdt->idx, &j, _state); + } + return result; +} + + +/************************************************************************* +X-values from last query + +INPUT PARAMETERS + KDT - KD-tree + X - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + X - rows are filled with X-values + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsx(kdtree* kdt, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + + if( kdt->kcur==0 ) + { + return; + } + if( x->rowskcur||x->colsnx ) + { + ae_matrix_set_length(x, kdt->kcur, kdt->nx, _state); + } + k = kdt->kcur; + for(i=0; i<=k-1; i++) + { + ae_v_move(&x->ptr.pp_double[i][0], 1, &kdt->xy.ptr.pp_double[kdt->idx.ptr.p_int[i]][kdt->nx], 1, ae_v_len(0,kdt->nx-1)); + } +} + + +/************************************************************************* +X- and Y-values from last query + +INPUT PARAMETERS + KDT - KD-tree + XY - possibly pre-allocated buffer. If XY is too small to store + result, it is resized. If size(XY) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + XY - rows are filled with points: first NX columns with + X-values, next NY columns - with Y-values. + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxy(kdtree* kdt, + /* Real */ ae_matrix* xy, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + + if( kdt->kcur==0 ) + { + return; + } + if( xy->rowskcur||xy->colsnx+kdt->ny ) + { + ae_matrix_set_length(xy, kdt->kcur, kdt->nx+kdt->ny, _state); + } + k = kdt->kcur; + for(i=0; i<=k-1; i++) + { + ae_v_move(&xy->ptr.pp_double[i][0], 1, &kdt->xy.ptr.pp_double[kdt->idx.ptr.p_int[i]][kdt->nx], 1, ae_v_len(0,kdt->nx+kdt->ny-1)); + } +} + + +/************************************************************************* +Tags from last query + +INPUT PARAMETERS + KDT - KD-tree + Tags - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + Tags - filled with tags associated with points, + or, when no tags were supplied, with zeros + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstags(kdtree* kdt, + /* Integer */ ae_vector* tags, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + + if( kdt->kcur==0 ) + { + return; + } + if( tags->cntkcur ) + { + ae_vector_set_length(tags, kdt->kcur, _state); + } + k = kdt->kcur; + for(i=0; i<=k-1; i++) + { + tags->ptr.p_int[i] = kdt->tags.ptr.p_int[kdt->idx.ptr.p_int[i]]; + } +} + + +/************************************************************************* +Distances from last query + +INPUT PARAMETERS + KDT - KD-tree + R - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + R - filled with distances (in corresponding norm) + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistances(kdtree* kdt, + /* Real */ ae_vector* r, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + + if( kdt->kcur==0 ) + { + return; + } + if( r->cntkcur ) + { + ae_vector_set_length(r, kdt->kcur, _state); + } + k = kdt->kcur; + + /* + * unload norms + * + * Abs() call is used to handle cases with negative norms + * (generated during KFN requests) + */ + if( kdt->normtype==0 ) + { + for(i=0; i<=k-1; i++) + { + r->ptr.p_double[i] = ae_fabs(kdt->r.ptr.p_double[i], _state); + } + } + if( kdt->normtype==1 ) + { + for(i=0; i<=k-1; i++) + { + r->ptr.p_double[i] = ae_fabs(kdt->r.ptr.p_double[i], _state); + } + } + if( kdt->normtype==2 ) + { + for(i=0; i<=k-1; i++) + { + r->ptr.p_double[i] = ae_sqrt(ae_fabs(kdt->r.ptr.p_double[i], _state), _state); + } + } +} + + +/************************************************************************* +X-values from last query; 'interactive' variant for languages like Python +which support constructs like "X = KDTreeQueryResultsXI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxi(kdtree* kdt, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + + ae_matrix_clear(x); + + kdtreequeryresultsx(kdt, x, _state); +} + + +/************************************************************************* +XY-values from last query; 'interactive' variant for languages like Python +which support constructs like "XY = KDTreeQueryResultsXYI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxyi(kdtree* kdt, + /* Real */ ae_matrix* xy, + ae_state *_state) +{ + + ae_matrix_clear(xy); + + kdtreequeryresultsxy(kdt, xy, _state); +} + + +/************************************************************************* +Tags from last query; 'interactive' variant for languages like Python +which support constructs like "Tags = KDTreeQueryResultsTagsI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstagsi(kdtree* kdt, + /* Integer */ ae_vector* tags, + ae_state *_state) +{ + + ae_vector_clear(tags); + + kdtreequeryresultstags(kdt, tags, _state); +} + + +/************************************************************************* +Distances from last query; 'interactive' variant for languages like Python +which support constructs like "R = KDTreeQueryResultsDistancesI(KDT)" +and interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistancesi(kdtree* kdt, + /* Real */ ae_vector* r, + ae_state *_state) +{ + + ae_vector_clear(r); + + kdtreequeryresultsdistances(kdt, r, _state); +} + + +/************************************************************************* +Serializer: allocation + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void kdtreealloc(ae_serializer* s, kdtree* tree, ae_state *_state) +{ + + + + /* + * Header + */ + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + + /* + * Data + */ + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + allocrealmatrix(s, &tree->xy, -1, -1, _state); + allocintegerarray(s, &tree->tags, -1, _state); + allocrealarray(s, &tree->boxmin, -1, _state); + allocrealarray(s, &tree->boxmax, -1, _state); + allocintegerarray(s, &tree->nodes, -1, _state); + allocrealarray(s, &tree->splits, -1, _state); +} + + +/************************************************************************* +Serializer: serialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void kdtreeserialize(ae_serializer* s, kdtree* tree, ae_state *_state) +{ + + + + /* + * Header + */ + ae_serializer_serialize_int(s, getkdtreeserializationcode(_state), _state); + ae_serializer_serialize_int(s, nearestneighbor_kdtreefirstversion, _state); + + /* + * Data + */ + ae_serializer_serialize_int(s, tree->n, _state); + ae_serializer_serialize_int(s, tree->nx, _state); + ae_serializer_serialize_int(s, tree->ny, _state); + ae_serializer_serialize_int(s, tree->normtype, _state); + serializerealmatrix(s, &tree->xy, -1, -1, _state); + serializeintegerarray(s, &tree->tags, -1, _state); + serializerealarray(s, &tree->boxmin, -1, _state); + serializerealarray(s, &tree->boxmax, -1, _state); + serializeintegerarray(s, &tree->nodes, -1, _state); + serializerealarray(s, &tree->splits, -1, _state); +} + + +/************************************************************************* +Serializer: unserialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void kdtreeunserialize(ae_serializer* s, kdtree* tree, ae_state *_state) +{ + ae_int_t i0; + ae_int_t i1; + + _kdtree_clear(tree); + + + /* + * check correctness of header + */ + ae_serializer_unserialize_int(s, &i0, _state); + ae_assert(i0==getkdtreeserializationcode(_state), "KDTreeUnserialize: stream header corrupted", _state); + ae_serializer_unserialize_int(s, &i1, _state); + ae_assert(i1==nearestneighbor_kdtreefirstversion, "KDTreeUnserialize: stream header corrupted", _state); + + /* + * Unserialize data + */ + ae_serializer_unserialize_int(s, &tree->n, _state); + ae_serializer_unserialize_int(s, &tree->nx, _state); + ae_serializer_unserialize_int(s, &tree->ny, _state); + ae_serializer_unserialize_int(s, &tree->normtype, _state); + unserializerealmatrix(s, &tree->xy, _state); + unserializeintegerarray(s, &tree->tags, _state); + unserializerealarray(s, &tree->boxmin, _state); + unserializerealarray(s, &tree->boxmax, _state); + unserializeintegerarray(s, &tree->nodes, _state); + unserializerealarray(s, &tree->splits, _state); + nearestneighbor_kdtreealloctemporaries(tree, tree->n, tree->nx, tree->ny, _state); +} + + +/************************************************************************* +Rearranges nodes [I1,I2) using partition in D-th dimension with S as threshold. +Returns split position I3: [I1,I3) and [I3,I2) are created as result. + +This subroutine doesn't create tree structures, just rearranges nodes. +*************************************************************************/ +static void nearestneighbor_kdtreesplit(kdtree* kdt, + ae_int_t i1, + ae_int_t i2, + ae_int_t d, + double s, + ae_int_t* i3, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t ileft; + ae_int_t iright; + double v; + + *i3 = 0; + + ae_assert(kdt->n>0, "KDTreeSplit: internal error", _state); + + /* + * split XY/Tags in two parts: + * * [ILeft,IRight] is non-processed part of XY/Tags + * + * After cycle is done, we have Ileft=IRight. We deal with + * this element separately. + * + * After this, [I1,ILeft) contains left part, and [ILeft,I2) + * contains right part. + */ + ileft = i1; + iright = i2-1; + while(ileftxy.ptr.pp_double[ileft][d],s) ) + { + + /* + * XY[ILeft] is on its place. + * Advance ILeft. + */ + ileft = ileft+1; + } + else + { + + /* + * XY[ILeft,..] must be at IRight. + * Swap and advance IRight. + */ + for(i=0; i<=2*kdt->nx+kdt->ny-1; i++) + { + v = kdt->xy.ptr.pp_double[ileft][i]; + kdt->xy.ptr.pp_double[ileft][i] = kdt->xy.ptr.pp_double[iright][i]; + kdt->xy.ptr.pp_double[iright][i] = v; + } + j = kdt->tags.ptr.p_int[ileft]; + kdt->tags.ptr.p_int[ileft] = kdt->tags.ptr.p_int[iright]; + kdt->tags.ptr.p_int[iright] = j; + iright = iright-1; + } + } + if( ae_fp_less_eq(kdt->xy.ptr.pp_double[ileft][d],s) ) + { + ileft = ileft+1; + } + else + { + iright = iright-1; + } + *i3 = ileft; +} + + +/************************************************************************* +Recursive kd-tree generation subroutine. + +PARAMETERS + KDT tree + NodesOffs unused part of Nodes[] which must be filled by tree + SplitsOffs unused part of Splits[] + I1, I2 points from [I1,I2) are processed + +NodesOffs[] and SplitsOffs[] must be large enough. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreegeneratetreerec(kdtree* kdt, + ae_int_t* nodesoffs, + ae_int_t* splitsoffs, + ae_int_t i1, + ae_int_t i2, + ae_int_t maxleafsize, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nx; + ae_int_t ny; + ae_int_t i; + ae_int_t j; + ae_int_t oldoffs; + ae_int_t i3; + ae_int_t cntless; + ae_int_t cntgreater; + double minv; + double maxv; + ae_int_t minidx; + ae_int_t maxidx; + ae_int_t d; + double ds; + double s; + double v; + double v0; + double v1; + + + ae_assert(kdt->n>0, "KDTreeGenerateTreeRec: internal error", _state); + ae_assert(i2>i1, "KDTreeGenerateTreeRec: internal error", _state); + + /* + * Generate leaf if needed + */ + if( i2-i1<=maxleafsize ) + { + kdt->nodes.ptr.p_int[*nodesoffs+0] = i2-i1; + kdt->nodes.ptr.p_int[*nodesoffs+1] = i1; + *nodesoffs = *nodesoffs+2; + return; + } + + /* + * Load values for easier access + */ + nx = kdt->nx; + ny = kdt->ny; + + /* + * Select dimension to split: + * * D is a dimension number + * In case bounding box has zero size, we enforce creation of the leaf node. + */ + d = 0; + ds = kdt->curboxmax.ptr.p_double[0]-kdt->curboxmin.ptr.p_double[0]; + for(i=1; i<=nx-1; i++) + { + v = kdt->curboxmax.ptr.p_double[i]-kdt->curboxmin.ptr.p_double[i]; + if( ae_fp_greater(v,ds) ) + { + ds = v; + d = i; + } + } + if( ae_fp_eq(ds,0) ) + { + kdt->nodes.ptr.p_int[*nodesoffs+0] = i2-i1; + kdt->nodes.ptr.p_int[*nodesoffs+1] = i1; + *nodesoffs = *nodesoffs+2; + return; + } + + /* + * Select split position S using sliding midpoint rule, + * rearrange points into [I1,I3) and [I3,I2). + * + * In case all points has same value of D-th component + * (MinV=MaxV) we enforce D-th dimension of bounding + * box to become exactly zero and repeat tree construction. + */ + s = kdt->curboxmin.ptr.p_double[d]+0.5*ds; + ae_v_move(&kdt->buf.ptr.p_double[0], 1, &kdt->xy.ptr.pp_double[i1][d], kdt->xy.stride, ae_v_len(0,i2-i1-1)); + n = i2-i1; + cntless = 0; + cntgreater = 0; + minv = kdt->buf.ptr.p_double[0]; + maxv = kdt->buf.ptr.p_double[0]; + minidx = i1; + maxidx = i1; + for(i=0; i<=n-1; i++) + { + v = kdt->buf.ptr.p_double[i]; + if( ae_fp_less(v,minv) ) + { + minv = v; + minidx = i1+i; + } + if( ae_fp_greater(v,maxv) ) + { + maxv = v; + maxidx = i1+i; + } + if( ae_fp_less(v,s) ) + { + cntless = cntless+1; + } + if( ae_fp_greater(v,s) ) + { + cntgreater = cntgreater+1; + } + } + if( ae_fp_eq(minv,maxv) ) + { + + /* + * In case all points has same value of D-th component + * (MinV=MaxV) we enforce D-th dimension of bounding + * box to become exactly zero and repeat tree construction. + */ + v0 = kdt->curboxmin.ptr.p_double[d]; + v1 = kdt->curboxmax.ptr.p_double[d]; + kdt->curboxmin.ptr.p_double[d] = minv; + kdt->curboxmax.ptr.p_double[d] = maxv; + nearestneighbor_kdtreegeneratetreerec(kdt, nodesoffs, splitsoffs, i1, i2, maxleafsize, _state); + kdt->curboxmin.ptr.p_double[d] = v0; + kdt->curboxmax.ptr.p_double[d] = v1; + return; + } + if( cntless>0&&cntgreater>0 ) + { + + /* + * normal midpoint split + */ + nearestneighbor_kdtreesplit(kdt, i1, i2, d, s, &i3, _state); + } + else + { + + /* + * sliding midpoint + */ + if( cntless==0 ) + { + + /* + * 1. move split to MinV, + * 2. place one point to the left bin (move to I1), + * others - to the right bin + */ + s = minv; + if( minidx!=i1 ) + { + for(i=0; i<=2*nx+ny-1; i++) + { + v = kdt->xy.ptr.pp_double[minidx][i]; + kdt->xy.ptr.pp_double[minidx][i] = kdt->xy.ptr.pp_double[i1][i]; + kdt->xy.ptr.pp_double[i1][i] = v; + } + j = kdt->tags.ptr.p_int[minidx]; + kdt->tags.ptr.p_int[minidx] = kdt->tags.ptr.p_int[i1]; + kdt->tags.ptr.p_int[i1] = j; + } + i3 = i1+1; + } + else + { + + /* + * 1. move split to MaxV, + * 2. place one point to the right bin (move to I2-1), + * others - to the left bin + */ + s = maxv; + if( maxidx!=i2-1 ) + { + for(i=0; i<=2*nx+ny-1; i++) + { + v = kdt->xy.ptr.pp_double[maxidx][i]; + kdt->xy.ptr.pp_double[maxidx][i] = kdt->xy.ptr.pp_double[i2-1][i]; + kdt->xy.ptr.pp_double[i2-1][i] = v; + } + j = kdt->tags.ptr.p_int[maxidx]; + kdt->tags.ptr.p_int[maxidx] = kdt->tags.ptr.p_int[i2-1]; + kdt->tags.ptr.p_int[i2-1] = j; + } + i3 = i2-1; + } + } + + /* + * Generate 'split' node + */ + kdt->nodes.ptr.p_int[*nodesoffs+0] = 0; + kdt->nodes.ptr.p_int[*nodesoffs+1] = d; + kdt->nodes.ptr.p_int[*nodesoffs+2] = *splitsoffs; + kdt->splits.ptr.p_double[*splitsoffs+0] = s; + oldoffs = *nodesoffs; + *nodesoffs = *nodesoffs+nearestneighbor_splitnodesize; + *splitsoffs = *splitsoffs+1; + + /* + * Recirsive generation: + * * update CurBox + * * call subroutine + * * restore CurBox + */ + kdt->nodes.ptr.p_int[oldoffs+3] = *nodesoffs; + v = kdt->curboxmax.ptr.p_double[d]; + kdt->curboxmax.ptr.p_double[d] = s; + nearestneighbor_kdtreegeneratetreerec(kdt, nodesoffs, splitsoffs, i1, i3, maxleafsize, _state); + kdt->curboxmax.ptr.p_double[d] = v; + kdt->nodes.ptr.p_int[oldoffs+4] = *nodesoffs; + v = kdt->curboxmin.ptr.p_double[d]; + kdt->curboxmin.ptr.p_double[d] = s; + nearestneighbor_kdtreegeneratetreerec(kdt, nodesoffs, splitsoffs, i3, i2, maxleafsize, _state); + kdt->curboxmin.ptr.p_double[d] = v; +} + + +/************************************************************************* +Recursive subroutine for NN queries. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreequerynnrec(kdtree* kdt, + ae_int_t offs, + ae_state *_state) +{ + double ptdist; + ae_int_t i; + ae_int_t j; + ae_int_t nx; + ae_int_t i1; + ae_int_t i2; + ae_int_t d; + double s; + double v; + double t1; + ae_int_t childbestoffs; + ae_int_t childworstoffs; + ae_int_t childoffs; + double prevdist; + ae_bool todive; + ae_bool bestisleft; + ae_bool updatemin; + + + ae_assert(kdt->n>0, "KDTreeQueryNNRec: internal error", _state); + + /* + * Leaf node. + * Process points. + */ + if( kdt->nodes.ptr.p_int[offs]>0 ) + { + i1 = kdt->nodes.ptr.p_int[offs+1]; + i2 = i1+kdt->nodes.ptr.p_int[offs]; + for(i=i1; i<=i2-1; i++) + { + + /* + * Calculate distance + */ + ptdist = 0; + nx = kdt->nx; + if( kdt->normtype==0 ) + { + for(j=0; j<=nx-1; j++) + { + ptdist = ae_maxreal(ptdist, ae_fabs(kdt->xy.ptr.pp_double[i][j]-kdt->x.ptr.p_double[j], _state), _state); + } + } + if( kdt->normtype==1 ) + { + for(j=0; j<=nx-1; j++) + { + ptdist = ptdist+ae_fabs(kdt->xy.ptr.pp_double[i][j]-kdt->x.ptr.p_double[j], _state); + } + } + if( kdt->normtype==2 ) + { + for(j=0; j<=nx-1; j++) + { + ptdist = ptdist+ae_sqr(kdt->xy.ptr.pp_double[i][j]-kdt->x.ptr.p_double[j], _state); + } + } + + /* + * Skip points with zero distance if self-matches are turned off + */ + if( ae_fp_eq(ptdist,0)&&!kdt->selfmatch ) + { + continue; + } + + /* + * We CAN'T process point if R-criterion isn't satisfied, + * i.e. (RNeeded<>0) AND (PtDist>R). + */ + if( ae_fp_eq(kdt->rneeded,0)||ae_fp_less_eq(ptdist,kdt->rneeded) ) + { + + /* + * R-criterion is satisfied, we must either: + * * replace worst point, if (KNeeded<>0) AND (KCur=KNeeded) + * (or skip, if worst point is better) + * * add point without replacement otherwise + */ + if( kdt->kcurkneeded||kdt->kneeded==0 ) + { + + /* + * add current point to heap without replacement + */ + tagheappushi(&kdt->r, &kdt->idx, &kdt->kcur, ptdist, i, _state); + } + else + { + + /* + * New points are added or not, depending on their distance. + * If added, they replace element at the top of the heap + */ + if( ae_fp_less(ptdist,kdt->r.ptr.p_double[0]) ) + { + if( kdt->kneeded==1 ) + { + kdt->idx.ptr.p_int[0] = i; + kdt->r.ptr.p_double[0] = ptdist; + } + else + { + tagheapreplacetopi(&kdt->r, &kdt->idx, kdt->kneeded, ptdist, i, _state); + } + } + } + } + } + return; + } + + /* + * Simple split + */ + if( kdt->nodes.ptr.p_int[offs]==0 ) + { + + /* + * Load: + * * D dimension to split + * * S split position + */ + d = kdt->nodes.ptr.p_int[offs+1]; + s = kdt->splits.ptr.p_double[kdt->nodes.ptr.p_int[offs+2]]; + + /* + * Calculate: + * * ChildBestOffs child box with best chances + * * ChildWorstOffs child box with worst chances + */ + if( ae_fp_less_eq(kdt->x.ptr.p_double[d],s) ) + { + childbestoffs = kdt->nodes.ptr.p_int[offs+3]; + childworstoffs = kdt->nodes.ptr.p_int[offs+4]; + bestisleft = ae_true; + } + else + { + childbestoffs = kdt->nodes.ptr.p_int[offs+4]; + childworstoffs = kdt->nodes.ptr.p_int[offs+3]; + bestisleft = ae_false; + } + + /* + * 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 + */ + if( i==0 ) + { + childoffs = childbestoffs; + updatemin = !bestisleft; + } + else + { + updatemin = bestisleft; + childoffs = childworstoffs; + } + + /* + * Update bounding box and current distance + */ + if( updatemin ) + { + prevdist = kdt->curdist; + t1 = kdt->x.ptr.p_double[d]; + v = kdt->curboxmin.ptr.p_double[d]; + if( ae_fp_less_eq(t1,s) ) + { + if( kdt->normtype==0 ) + { + kdt->curdist = ae_maxreal(kdt->curdist, s-t1, _state); + } + if( kdt->normtype==1 ) + { + kdt->curdist = kdt->curdist-ae_maxreal(v-t1, 0, _state)+s-t1; + } + if( kdt->normtype==2 ) + { + kdt->curdist = kdt->curdist-ae_sqr(ae_maxreal(v-t1, 0, _state), _state)+ae_sqr(s-t1, _state); + } + } + kdt->curboxmin.ptr.p_double[d] = s; + } + else + { + prevdist = kdt->curdist; + t1 = kdt->x.ptr.p_double[d]; + v = kdt->curboxmax.ptr.p_double[d]; + if( ae_fp_greater_eq(t1,s) ) + { + if( kdt->normtype==0 ) + { + kdt->curdist = ae_maxreal(kdt->curdist, t1-s, _state); + } + if( kdt->normtype==1 ) + { + kdt->curdist = kdt->curdist-ae_maxreal(t1-v, 0, _state)+t1-s; + } + if( kdt->normtype==2 ) + { + kdt->curdist = kdt->curdist-ae_sqr(ae_maxreal(t1-v, 0, _state), _state)+ae_sqr(t1-s, _state); + } + } + kdt->curboxmax.ptr.p_double[d] = s; + } + + /* + * Decide: to dive into cell or not to dive + */ + if( ae_fp_neq(kdt->rneeded,0)&&ae_fp_greater(kdt->curdist,kdt->rneeded) ) + { + todive = ae_false; + } + else + { + if( kdt->kcurkneeded||kdt->kneeded==0 ) + { + + /* + * KCurcurdist,kdt->r.ptr.p_double[0]*kdt->approxf); + } + } + if( todive ) + { + nearestneighbor_kdtreequerynnrec(kdt, childoffs, _state); + } + + /* + * Restore bounding box and distance + */ + if( updatemin ) + { + kdt->curboxmin.ptr.p_double[d] = v; + } + else + { + kdt->curboxmax.ptr.p_double[d] = v; + } + kdt->curdist = prevdist; + } + return; + } +} + + +/************************************************************************* +Copies X[] to KDT.X[] +Loads distance from X[] to bounding box. +Initializes CurBox[]. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreeinitbox(kdtree* kdt, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t i; + double vx; + double vmin; + double vmax; + + + ae_assert(kdt->n>0, "KDTreeInitBox: internal error", _state); + + /* + * calculate distance from point to current bounding box + */ + kdt->curdist = 0; + if( kdt->normtype==0 ) + { + for(i=0; i<=kdt->nx-1; i++) + { + vx = x->ptr.p_double[i]; + vmin = kdt->boxmin.ptr.p_double[i]; + vmax = kdt->boxmax.ptr.p_double[i]; + kdt->x.ptr.p_double[i] = vx; + kdt->curboxmin.ptr.p_double[i] = vmin; + kdt->curboxmax.ptr.p_double[i] = vmax; + if( ae_fp_less(vx,vmin) ) + { + kdt->curdist = ae_maxreal(kdt->curdist, vmin-vx, _state); + } + else + { + if( ae_fp_greater(vx,vmax) ) + { + kdt->curdist = ae_maxreal(kdt->curdist, vx-vmax, _state); + } + } + } + } + if( kdt->normtype==1 ) + { + for(i=0; i<=kdt->nx-1; i++) + { + vx = x->ptr.p_double[i]; + vmin = kdt->boxmin.ptr.p_double[i]; + vmax = kdt->boxmax.ptr.p_double[i]; + kdt->x.ptr.p_double[i] = vx; + kdt->curboxmin.ptr.p_double[i] = vmin; + kdt->curboxmax.ptr.p_double[i] = vmax; + if( ae_fp_less(vx,vmin) ) + { + kdt->curdist = kdt->curdist+vmin-vx; + } + else + { + if( ae_fp_greater(vx,vmax) ) + { + kdt->curdist = kdt->curdist+vx-vmax; + } + } + } + } + if( kdt->normtype==2 ) + { + for(i=0; i<=kdt->nx-1; i++) + { + vx = x->ptr.p_double[i]; + vmin = kdt->boxmin.ptr.p_double[i]; + vmax = kdt->boxmax.ptr.p_double[i]; + kdt->x.ptr.p_double[i] = vx; + kdt->curboxmin.ptr.p_double[i] = vmin; + kdt->curboxmax.ptr.p_double[i] = vmax; + if( ae_fp_less(vx,vmin) ) + { + kdt->curdist = kdt->curdist+ae_sqr(vmin-vx, _state); + } + else + { + if( ae_fp_greater(vx,vmax) ) + { + kdt->curdist = kdt->curdist+ae_sqr(vx-vmax, _state); + } + } + } + } +} + + +/************************************************************************* +This function allocates all dataset-independent array fields of KDTree, +i.e. such array fields that their dimensions do not depend on dataset +size. + +This function do not sets KDT.NX or KDT.NY - it just allocates arrays + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreeallocdatasetindependent(kdtree* kdt, + ae_int_t nx, + ae_int_t ny, + ae_state *_state) +{ + + + ae_assert(kdt->n>0, "KDTreeAllocDatasetIndependent: internal error", _state); + ae_vector_set_length(&kdt->x, nx, _state); + ae_vector_set_length(&kdt->boxmin, nx, _state); + ae_vector_set_length(&kdt->boxmax, nx, _state); + ae_vector_set_length(&kdt->curboxmin, nx, _state); + ae_vector_set_length(&kdt->curboxmax, nx, _state); +} + + +/************************************************************************* +This function allocates all dataset-dependent array fields of KDTree, i.e. +such array fields that their dimensions depend on dataset size. + +This function do not sets KDT.N, KDT.NX or KDT.NY - +it just allocates arrays. + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreeallocdatasetdependent(kdtree* kdt, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_state *_state) +{ + + + ae_assert(n>0, "KDTreeAllocDatasetDependent: internal error", _state); + ae_matrix_set_length(&kdt->xy, n, 2*nx+ny, _state); + ae_vector_set_length(&kdt->tags, n, _state); + ae_vector_set_length(&kdt->idx, n, _state); + ae_vector_set_length(&kdt->r, n, _state); + ae_vector_set_length(&kdt->x, nx, _state); + ae_vector_set_length(&kdt->buf, ae_maxint(n, nx, _state), _state); + ae_vector_set_length(&kdt->nodes, nearestneighbor_splitnodesize*2*n, _state); + ae_vector_set_length(&kdt->splits, 2*n, _state); +} + + +/************************************************************************* +This function allocates temporaries. + +This function do not sets KDT.N, KDT.NX or KDT.NY - +it just allocates arrays. + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +static void nearestneighbor_kdtreealloctemporaries(kdtree* kdt, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_state *_state) +{ + + + ae_assert(n>0, "KDTreeAllocTemporaries: internal error", _state); + ae_vector_set_length(&kdt->x, nx, _state); + ae_vector_set_length(&kdt->idx, n, _state); + ae_vector_set_length(&kdt->r, n, _state); + ae_vector_set_length(&kdt->buf, ae_maxint(n, nx, _state), _state); + ae_vector_set_length(&kdt->curboxmin, nx, _state); + ae_vector_set_length(&kdt->curboxmax, nx, _state); +} + + +ae_bool _kdtree_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + kdtree *p = (kdtree*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tags, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->boxmin, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->boxmax, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->nodes, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->splits, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->idx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->r, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->buf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->curboxmin, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->curboxmax, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _kdtree_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + kdtree *dst = (kdtree*)_dst; + kdtree *src = (kdtree*)_src; + dst->n = src->n; + dst->nx = src->nx; + dst->ny = src->ny; + dst->normtype = src->normtype; + if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tags, &src->tags, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->boxmin, &src->boxmin, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->boxmax, &src->boxmax, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->nodes, &src->nodes, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->splits, &src->splits, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->kneeded = src->kneeded; + dst->rneeded = src->rneeded; + dst->selfmatch = src->selfmatch; + dst->approxf = src->approxf; + dst->kcur = src->kcur; + if( !ae_vector_init_copy(&dst->idx, &src->idx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->r, &src->r, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->buf, &src->buf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->curboxmin, &src->curboxmin, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->curboxmax, &src->curboxmax, _state, make_automatic) ) + return ae_false; + dst->curdist = src->curdist; + dst->debugcounter = src->debugcounter; + return ae_true; +} + + +void _kdtree_clear(void* _p) +{ + kdtree *p = (kdtree*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->xy); + ae_vector_clear(&p->tags); + ae_vector_clear(&p->boxmin); + ae_vector_clear(&p->boxmax); + ae_vector_clear(&p->nodes); + ae_vector_clear(&p->splits); + ae_vector_clear(&p->x); + ae_vector_clear(&p->idx); + ae_vector_clear(&p->r); + ae_vector_clear(&p->buf); + ae_vector_clear(&p->curboxmin); + ae_vector_clear(&p->curboxmax); +} + + +void _kdtree_destroy(void* _p) +{ + kdtree *p = (kdtree*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->xy); + ae_vector_destroy(&p->tags); + ae_vector_destroy(&p->boxmin); + ae_vector_destroy(&p->boxmax); + ae_vector_destroy(&p->nodes); + ae_vector_destroy(&p->splits); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->idx); + ae_vector_destroy(&p->r); + ae_vector_destroy(&p->buf); + ae_vector_destroy(&p->curboxmin); + ae_vector_destroy(&p->curboxmax); +} + + + +} + diff --git a/src/inc/alglib/alglibmisc.h b/src/inc/alglib/alglibmisc.h new file mode 100644 index 0000000..8209ac6 --- /dev/null +++ b/src/inc/alglib/alglibmisc.h @@ -0,0 +1,769 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _alglibmisc_pkg_h +#define _alglibmisc_pkg_h +#include "ap.h" +#include "alglibinternal.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t magicv; +} hqrndstate; +typedef struct +{ + ae_int_t n; + ae_int_t nx; + ae_int_t ny; + ae_int_t normtype; + ae_matrix xy; + ae_vector tags; + ae_vector boxmin; + ae_vector boxmax; + ae_vector nodes; + ae_vector splits; + ae_vector x; + ae_int_t kneeded; + double rneeded; + ae_bool selfmatch; + double approxf; + ae_int_t kcur; + ae_vector idx; + ae_vector r; + ae_vector buf; + ae_vector curboxmin; + ae_vector curboxmax; + double curdist; + ae_int_t debugcounter; +} kdtree; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + +/************************************************************************* +Portable high quality random number generator state. +Initialized with HQRNDRandomize() or HQRNDSeed(). + +Fields: + S1, S2 - seed values + V - precomputed value + MagicV - 'magic' value used to determine whether State structure + was correctly initialized. +*************************************************************************/ +class _hqrndstate_owner +{ +public: + _hqrndstate_owner(); + _hqrndstate_owner(const _hqrndstate_owner &rhs); + _hqrndstate_owner& operator=(const _hqrndstate_owner &rhs); + virtual ~_hqrndstate_owner(); + alglib_impl::hqrndstate* c_ptr(); + alglib_impl::hqrndstate* c_ptr() const; +protected: + alglib_impl::hqrndstate *p_struct; +}; +class hqrndstate : public _hqrndstate_owner +{ +public: + hqrndstate(); + hqrndstate(const hqrndstate &rhs); + hqrndstate& operator=(const hqrndstate &rhs); + virtual ~hqrndstate(); + +}; + +/************************************************************************* + +*************************************************************************/ +class _kdtree_owner +{ +public: + _kdtree_owner(); + _kdtree_owner(const _kdtree_owner &rhs); + _kdtree_owner& operator=(const _kdtree_owner &rhs); + virtual ~_kdtree_owner(); + alglib_impl::kdtree* c_ptr(); + alglib_impl::kdtree* c_ptr() const; +protected: + alglib_impl::kdtree *p_struct; +}; +class kdtree : public _kdtree_owner +{ +public: + kdtree(); + kdtree(const kdtree &rhs); + kdtree& operator=(const kdtree &rhs); + virtual ~kdtree(); + +}; + +/************************************************************************* +HQRNDState initialization with random values which come from standard +RNG. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndrandomize(hqrndstate &state); + + +/************************************************************************* +HQRNDState initialization with seed values + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndseed(const ae_int_t s1, const ae_int_t s2, hqrndstate &state); + + +/************************************************************************* +This function generates random real number in (0,1), +not including interval boundaries + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrnduniformr(const hqrndstate &state); + + +/************************************************************************* +This function generates random integer number in [0, N) + +1. State structure must be initialized with HQRNDRandomize() or HQRNDSeed() +2. N can be any positive number except for very large numbers: + * close to 2^31 on 32-bit systems + * close to 2^62 on 64-bit systems + An exception will be generated if N is too large. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +ae_int_t hqrnduniformi(const hqrndstate &state, const ae_int_t n); + + +/************************************************************************* +Random number generator: normal numbers + +This function generates one random number from normal distribution. +Its performance is equal to that of HQRNDNormal2() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double hqrndnormal(const hqrndstate &state); + + +/************************************************************************* +Random number generator: random X and Y such that X^2+Y^2=1 + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndunit2(const hqrndstate &state, double &x, double &y); + + +/************************************************************************* +Random number generator: normal numbers + +This function generates two independent random numbers from normal +distribution. Its performance is equal to that of HQRNDNormal() + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void hqrndnormal2(const hqrndstate &state, double &x1, double &x2); + + +/************************************************************************* +Random number generator: exponential distribution + +State structure must be initialized with HQRNDRandomize() or HQRNDSeed(). + + -- ALGLIB -- + Copyright 11.08.2007 by Bochkanov Sergey +*************************************************************************/ +double hqrndexponential(const hqrndstate &state, const double lambdav); + + +/************************************************************************* +This function generates random number from discrete distribution given by +finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample + N - number of elements to use, N>=1 + +RESULT + this function returns one of the X[i] for random i=0..N-1 + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrnddiscrete(const hqrndstate &state, const real_1d_array &x, const ae_int_t n); + + +/************************************************************************* +This function generates random number from continuous distribution given +by finite sample X. + +INPUT PARAMETERS + State - high quality random number generator, must be + initialized with HQRNDRandomize() or HQRNDSeed(). + X - finite sample, array[N] (can be larger, in this case only + leading N elements are used). THIS ARRAY MUST BE SORTED BY + ASCENDING. + N - number of elements to use, N>=1 + +RESULT + this function returns random number from continuous distribution which + tries to approximate X as mush as possible. min(X)<=Result<=max(X). + + -- ALGLIB -- + Copyright 08.11.2011 by Bochkanov Sergey +*************************************************************************/ +double hqrndcontinuous(const hqrndstate &state, const real_1d_array &x, const ae_int_t n); + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void kdtreeserialize(kdtree &obj, std::string &s_out); + + +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void kdtreeunserialize(std::string &s_in, kdtree &obj); + + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values and optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + N - number of points, N>=0. + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuild(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt); +void kdtreebuild(const real_2d_array &xy, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt); + + +/************************************************************************* +KD-tree creation + +This subroutine creates KD-tree from set of X-values, integer tags and +optional Y-values + +INPUT PARAMETERS + XY - dataset, array[0..N-1,0..NX+NY-1]. + one row corresponds to one point. + first NX columns contain X-values, next NY (NY may be zero) + columns may contain associated Y-values + Tags - tags, array[0..N-1], contains integer tags associated + with points. + N - number of points, N>=0 + NX - space dimension, NX>=1. + NY - number of optional Y-values, NY>=0. + NormType- norm type: + * 0 denotes infinity-norm + * 1 denotes 1-norm + * 2 denotes 2-norm (Euclidean norm) + +OUTPUT PARAMETERS + KDT - KD-tree + +NOTES + +1. KD-tree creation have O(N*logN) complexity and O(N*(2*NX+NY)) memory + requirements. +2. Although KD-trees may be used with any combination of N and NX, they + are more efficient than brute-force search only when N >> 4^NX. So they + are most useful in low-dimensional tasks (NX=2, NX=3). NX=1 is another + inefficient case, because simple binary search (without additional + structures) is much more efficient in such tasks than KD-trees. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreebuildtagged(const real_2d_array &xy, const integer_1d_array &tags, const ae_int_t n, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt); +void kdtreebuildtagged(const real_2d_array &xy, const integer_1d_array &tags, const ae_int_t nx, const ae_int_t ny, const ae_int_t normtype, kdtree &kdt); + + +/************************************************************************* +K-NN query: K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of actual neighbors found (either K or N, if K>N). + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const bool selfmatch); +ae_int_t kdtreequeryknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k); + + +/************************************************************************* +R-NN query: all points within R-sphere centered at X + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + R - radius of sphere (in corresponding norm), R>0 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + +RESULT + number of neighbors found, >=0 + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +actual results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryrnn(const kdtree &kdt, const real_1d_array &x, const double r, const bool selfmatch); +ae_int_t kdtreequeryrnn(const kdtree &kdt, const real_1d_array &x, const double r); + + +/************************************************************************* +K-NN query: approximate K nearest neighbors + +INPUT PARAMETERS + KDT - KD-tree + X - point, array[0..NX-1]. + K - number of neighbors to return, K>=1 + SelfMatch - whether self-matches are allowed: + * if True, nearest neighbor may be the point itself + (if it exists in original dataset) + * if False, then only points with non-zero distance + are returned + * if not given, considered True + Eps - approximation factor, Eps>=0. eps-approximate nearest + neighbor is a neighbor whose distance from X is at + most (1+eps) times distance of true nearest neighbor. + +RESULT + number of actual neighbors found (either K or N, if K>N). + +NOTES + significant performance gain may be achieved only when Eps is is on + the order of magnitude of 1 or larger. + +This subroutine performs query and stores its result in the internal +structures of the KD-tree. You can use following subroutines to obtain +these results: +* KDTreeQueryResultsX() to get X-values +* KDTreeQueryResultsXY() to get X- and Y-values +* KDTreeQueryResultsTags() to get tag values +* KDTreeQueryResultsDistances() to get distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +ae_int_t kdtreequeryaknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const bool selfmatch, const double eps); +ae_int_t kdtreequeryaknn(const kdtree &kdt, const real_1d_array &x, const ae_int_t k, const double eps); + + +/************************************************************************* +X-values from last query + +INPUT PARAMETERS + KDT - KD-tree + X - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + X - rows are filled with X-values + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsx(const kdtree &kdt, real_2d_array &x); + + +/************************************************************************* +X- and Y-values from last query + +INPUT PARAMETERS + KDT - KD-tree + XY - possibly pre-allocated buffer. If XY is too small to store + result, it is resized. If size(XY) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + XY - rows are filled with points: first NX columns with + X-values, next NY columns - with Y-values. + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsTags() tag values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxy(const kdtree &kdt, real_2d_array &xy); + + +/************************************************************************* +Tags from last query + +INPUT PARAMETERS + KDT - KD-tree + Tags - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + Tags - filled with tags associated with points, + or, when no tags were supplied, with zeros + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsDistances() distances + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstags(const kdtree &kdt, integer_1d_array &tags); + + +/************************************************************************* +Distances from last query + +INPUT PARAMETERS + KDT - KD-tree + R - possibly pre-allocated buffer. If X is too small to store + result, it is resized. If size(X) is enough to store + result, it is left unchanged. + +OUTPUT PARAMETERS + R - filled with distances (in corresponding norm) + +NOTES +1. points are ordered by distance from the query point (first = closest) +2. if XY is larger than required to store result, only leading part will + be overwritten; trailing part will be left unchanged. So if on input + XY = [[A,B],[C,D]], and result is [1,2], then on exit we will get + XY = [[1,2],[C,D]]. This is done purposely to increase performance; if + you want function to resize array according to result size, use + function with same name and suffix 'I'. + +SEE ALSO +* KDTreeQueryResultsX() X-values +* KDTreeQueryResultsXY() X- and Y-values +* KDTreeQueryResultsTags() tag values + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistances(const kdtree &kdt, real_1d_array &r); + + +/************************************************************************* +X-values from last query; 'interactive' variant for languages like Python +which support constructs like "X = KDTreeQueryResultsXI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxi(const kdtree &kdt, real_2d_array &x); + + +/************************************************************************* +XY-values from last query; 'interactive' variant for languages like Python +which support constructs like "XY = KDTreeQueryResultsXYI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsxyi(const kdtree &kdt, real_2d_array &xy); + + +/************************************************************************* +Tags from last query; 'interactive' variant for languages like Python +which support constructs like "Tags = KDTreeQueryResultsTagsI(KDT)" and +interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultstagsi(const kdtree &kdt, integer_1d_array &tags); + + +/************************************************************************* +Distances from last query; 'interactive' variant for languages like Python +which support constructs like "R = KDTreeQueryResultsDistancesI(KDT)" +and interactive mode of interpreter. + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void kdtreequeryresultsdistancesi(const kdtree &kdt, real_1d_array &r); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void hqrndrandomize(hqrndstate* state, ae_state *_state); +void hqrndseed(ae_int_t s1, + ae_int_t s2, + hqrndstate* state, + ae_state *_state); +double hqrnduniformr(hqrndstate* state, ae_state *_state); +ae_int_t hqrnduniformi(hqrndstate* state, ae_int_t n, ae_state *_state); +double hqrndnormal(hqrndstate* state, ae_state *_state); +void hqrndunit2(hqrndstate* state, double* x, double* y, ae_state *_state); +void hqrndnormal2(hqrndstate* state, + double* x1, + double* x2, + ae_state *_state); +double hqrndexponential(hqrndstate* state, + double lambdav, + ae_state *_state); +double hqrnddiscrete(hqrndstate* state, + /* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +double hqrndcontinuous(hqrndstate* state, + /* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +ae_bool _hqrndstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _hqrndstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _hqrndstate_clear(void* _p); +void _hqrndstate_destroy(void* _p); +void kdtreebuild(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_int_t normtype, + kdtree* kdt, + ae_state *_state); +void kdtreebuildtagged(/* Real */ ae_matrix* xy, + /* Integer */ ae_vector* tags, + ae_int_t n, + ae_int_t nx, + ae_int_t ny, + ae_int_t normtype, + kdtree* kdt, + ae_state *_state); +ae_int_t kdtreequeryknn(kdtree* kdt, + /* Real */ ae_vector* x, + ae_int_t k, + ae_bool selfmatch, + ae_state *_state); +ae_int_t kdtreequeryrnn(kdtree* kdt, + /* Real */ ae_vector* x, + double r, + ae_bool selfmatch, + ae_state *_state); +ae_int_t kdtreequeryaknn(kdtree* kdt, + /* Real */ ae_vector* x, + ae_int_t k, + ae_bool selfmatch, + double eps, + ae_state *_state); +void kdtreequeryresultsx(kdtree* kdt, + /* Real */ ae_matrix* x, + ae_state *_state); +void kdtreequeryresultsxy(kdtree* kdt, + /* Real */ ae_matrix* xy, + ae_state *_state); +void kdtreequeryresultstags(kdtree* kdt, + /* Integer */ ae_vector* tags, + ae_state *_state); +void kdtreequeryresultsdistances(kdtree* kdt, + /* Real */ ae_vector* r, + ae_state *_state); +void kdtreequeryresultsxi(kdtree* kdt, + /* Real */ ae_matrix* x, + ae_state *_state); +void kdtreequeryresultsxyi(kdtree* kdt, + /* Real */ ae_matrix* xy, + ae_state *_state); +void kdtreequeryresultstagsi(kdtree* kdt, + /* Integer */ ae_vector* tags, + ae_state *_state); +void kdtreequeryresultsdistancesi(kdtree* kdt, + /* Real */ ae_vector* r, + ae_state *_state); +void kdtreealloc(ae_serializer* s, kdtree* tree, ae_state *_state); +void kdtreeserialize(ae_serializer* s, kdtree* tree, ae_state *_state); +void kdtreeunserialize(ae_serializer* s, kdtree* tree, ae_state *_state); +ae_bool _kdtree_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _kdtree_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _kdtree_clear(void* _p); +void _kdtree_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/ap.cpp b/src/inc/alglib/ap.cpp new file mode 100644 index 0000000..cc8140e --- /dev/null +++ b/src/inc/alglib/ap.cpp @@ -0,0 +1,10661 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "ap.h" +#include +#include +using namespace std; + +#if defined(AE_CPU) +#if (AE_CPU==AE_INTEL) + +#if AE_COMPILER==AE_MSVC +#include +#endif + +#endif +#endif + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION IMPLEMENTS BASIC FUNCTIONALITY LIKE +// MEMORY MANAGEMENT FOR VECTORS/MATRICES WHICH IS +// SHARED BETWEEN C++ AND PURE C LIBRARIES +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +/* + * local definitions + */ +#define x_nb 16 +#define AE_DATA_ALIGN 16 +#define AE_PTR_ALIGN sizeof(void*) +#define DYN_BOTTOM ((void*)1) +#define DYN_FRAME ((void*)2) +#define AE_LITTLE_ENDIAN 1 +#define AE_BIG_ENDIAN 2 +#define AE_MIXED_ENDIAN 3 +#define AE_SER_ENTRY_LENGTH 11 +#define AE_SER_ENTRIES_PER_ROW 5 + +#define AE_SM_DEFAULT 0 +#define AE_SM_ALLOC 1 +#define AE_SM_READY2S 2 +#define AE_SM_TO_STRING 10 +#define AE_SM_FROM_STRING 20 +#define AE_SM_TO_CPPSTRING 11 + +#define AE_LOCK_CYCLES 512 +#define AE_LOCK_TESTS_BEFORE_YIELD 16 +#define AE_CRITICAL_ASSERT(x) if( !(x) ) abort() + + +/* + * alloc counter (if used) + */ +#ifdef AE_USE_ALLOC_COUNTER +ae_int64_t _alloc_counter = 0; +#endif +#ifdef AE_DEBUGRNG +static ae_int_t _debug_rng_s0 = 11; +static ae_int_t _debug_rng_s1 = 13; +#endif +#ifdef AE_SMP_DEBUGCOUNTERS +__declspec(align(AE_LOCK_ALIGNMENT)) volatile ae_int64_t _ae_dbg_lock_acquisitions = 0; +__declspec(align(AE_LOCK_ALIGNMENT)) volatile ae_int64_t _ae_dbg_lock_spinwaits = 0; +__declspec(align(AE_LOCK_ALIGNMENT)) volatile ae_int64_t _ae_dbg_lock_yields = 0; +#endif + +/* + * These declarations are used to ensure that + * sizeof(ae_int32_t)==4, sizeof(ae_int64_t)==8, sizeof(ae_int_t)==sizeof(void*). + * they will lead to syntax error otherwise (array size will be negative). + * + * you can remove them, if you want - they are not used anywhere. + * + */ +static char _ae_int32_t_must_be_32_bits_wide[1-2*((int)(sizeof(ae_int32_t))-4)*((int)(sizeof(ae_int32_t))-4)]; +static char _ae_int64_t_must_be_64_bits_wide[1-2*((int)(sizeof(ae_int64_t))-8)*((int)(sizeof(ae_int64_t))-8)]; +static char _ae_int_t_must_be_pointer_sized [1-2*((int)(sizeof(ae_int_t))-(int)sizeof(void*))*((int)(sizeof(ae_int_t))-(int)(sizeof(void*)))]; + +/* + * This variable is used to prevent some tricky optimizations which may degrade multithreaded performance. + * It is touched once in the ae_init_pool() function from smp.c in order to prevent optimizations. + * + */ +static volatile ae_int_t ae_never_change_it = 1; + +ae_int_t ae_misalignment(const void *ptr, size_t alignment) +{ + union _u + { + const void *ptr; + ae_int_t iptr; + } u; + u.ptr = ptr; + return (ae_int_t)(u.iptr%alignment); +} + +void* ae_align(void *ptr, size_t alignment) +{ + char *result = (char*)ptr; + if( (result-(char*)0)%alignment!=0 ) + result += alignment - (result-(char*)0)%alignment; + return result; +} + +void ae_break(ae_state *state, ae_error_type error_type, const char *msg) +{ +#ifndef AE_USE_CPP_ERROR_HANDLING + if( state!=NULL ) + { + if( state->thread_exception_handler!=NULL ) + state->thread_exception_handler(state); + ae_state_clear(state); + state->last_error = error_type; + state->error_msg = msg; + if( state->break_jump!=NULL ) + longjmp(*(state->break_jump), 1); + else + abort(); + } + else + abort(); +#else + if( state!=NULL ) + { + if( state->thread_exception_handler!=NULL ) + state->thread_exception_handler(state); + ae_state_clear(state); + state->last_error = error_type; + state->error_msg = msg; + } + throw error_type; +#endif +} + +void* aligned_malloc(size_t size, size_t alignment) +{ + if( size==0 ) + return NULL; + if( alignment<=1 ) + { + /* no alignment, just call malloc */ + void *block; + void **p; ; + block = malloc(sizeof(void*)+size); + if( block==NULL ) + return NULL; + p = (void**)block; + *p = block; +#ifdef AE_USE_ALLOC_COUNTER + _alloc_counter++; +#endif + return (void*)((char*)block+sizeof(void*)); + } + else + { + /* align */ + void *block; + char *result; + block = malloc(alignment-1+sizeof(void*)+size); + if( block==NULL ) + return NULL; + result = (char*)block+sizeof(void*); + /*if( (result-(char*)0)%alignment!=0 ) + result += alignment - (result-(char*)0)%alignment;*/ + result = (char*)ae_align(result, alignment); + *((void**)(result-sizeof(void*))) = block; +#ifdef AE_USE_ALLOC_COUNTER + _alloc_counter++; +#endif + return result; + } +} + +void aligned_free(void *block) +{ + void *p; + if( block==NULL ) + return; + p = *((void**)((char*)block-sizeof(void*))); + free(p); +#ifdef AE_USE_ALLOC_COUNTER + _alloc_counter--; +#endif +} + +/************************************************************************ +Malloc's memory with automatic alignment. + +Returns NULL when zero size is specified. + +Error handling: +* if state is NULL, returns NULL on allocation error +* if state is not NULL, calls ae_break() on allocation error +************************************************************************/ +void* ae_malloc(size_t size, ae_state *state) +{ + void *result; + if( size==0 ) + return NULL; + result = aligned_malloc(size,AE_DATA_ALIGN); + if( result==NULL && state!=NULL) + { + char buf[256]; + sprintf(buf, "ae_malloc(): out of memory (attempted to allocate %llu bytes)", (unsigned long long)size); + ae_break(state, ERR_OUT_OF_MEMORY, buf); + } + return result; +} + +void ae_free(void *p) +{ + if( p!=NULL ) + aligned_free(p); +} + +/************************************************************************ +Sets pointers to the matrix rows. + +* dst must be correctly initialized matrix +* dst->data.ptr points to the beginning of memory block allocated for + row pointers. +* dst->ptr - undefined (initialized during algorithm processing) +* storage parameter points to the beginning of actual storage +************************************************************************/ +void ae_matrix_update_row_pointers(ae_matrix *dst, void *storage) +{ + char *p_base; + void **pp_ptr; + ae_int_t i; + if( dst->rows>0 && dst->cols>0 ) + { + p_base = (char*)storage; + pp_ptr = (void**)dst->data.ptr; + dst->ptr.pp_void = pp_ptr; + for(i=0; irows; i++, p_base+=dst->stride*ae_sizeof(dst->datatype)) + pp_ptr[i] = p_base; + } + else + dst->ptr.pp_void = NULL; +} + +/************************************************************************ +Returns size of datatype. +Zero for dynamic types like strings or multiple precision types. +************************************************************************/ +ae_int_t ae_sizeof(ae_datatype datatype) +{ + switch(datatype) + { + case DT_BOOL: return (ae_int_t)sizeof(ae_bool); + case DT_INT: return (ae_int_t)sizeof(ae_int_t); + case DT_REAL: return (ae_int_t)sizeof(double); + case DT_COMPLEX: return 2*(ae_int_t)sizeof(double); + default: return 0; + } +} + + +/************************************************************************ +This dummy function is used to prevent compiler messages about unused +locals in automatically generated code. + +It makes nothing - just accepts pointer, "touches" it - and that is all. +It performs several tricky operations without side effects which confuse +compiler so it does not compain about unused locals in THIS function. +************************************************************************/ +void ae_touch_ptr(void *p) +{ + void * volatile fake_variable0 = p; + void * volatile fake_variable1 = fake_variable0; + fake_variable0 = fake_variable1; +} + +/************************************************************************ +This function initializes ALGLIB environment state. + +NOTES: +* stacks contain no frames, so ae_make_frame() must be called before + attaching dynamic blocks. Without it ae_leave_frame() will cycle + forever (which is intended behavior). +************************************************************************/ +void ae_state_init(ae_state *state) +{ + ae_int32_t *vp; + + /* + * p_next points to itself because: + * * correct program should be able to detect end of the list + * by looking at the ptr field. + * * NULL p_next may be used to distinguish automatic blocks + * (in the list) from non-automatic (not in the list) + */ + state->last_block.p_next = &(state->last_block); + state->last_block.deallocator = NULL; + state->last_block.ptr = DYN_BOTTOM; + state->p_top_block = &(state->last_block); +#ifndef AE_USE_CPP_ERROR_HANDLING + state->break_jump = NULL; +#endif + state->error_msg = ""; + + /* + * determine endianness and initialize precomputed IEEE special quantities. + */ + state->endianness = ae_get_endianness(); + if( state->endianness==AE_LITTLE_ENDIAN ) + { + vp = (ae_int32_t*)(&state->v_nan); + vp[0] = 0; + vp[1] = (ae_int32_t)0x7FF80000; + vp = (ae_int32_t*)(&state->v_posinf); + vp[0] = 0; + vp[1] = (ae_int32_t)0x7FF00000; + vp = (ae_int32_t*)(&state->v_neginf); + vp[0] = 0; + vp[1] = (ae_int32_t)0xFFF00000; + } + else if( state->endianness==AE_BIG_ENDIAN ) + { + vp = (ae_int32_t*)(&state->v_nan); + vp[1] = 0; + vp[0] = (ae_int32_t)0x7FF80000; + vp = (ae_int32_t*)(&state->v_posinf); + vp[1] = 0; + vp[0] = (ae_int32_t)0x7FF00000; + vp = (ae_int32_t*)(&state->v_neginf); + vp[1] = 0; + vp[0] = (ae_int32_t)0xFFF00000; + } + else + abort(); + + /* + * set threading information + */ + state->worker_thread = NULL; + state->parent_task = NULL; + state->thread_exception_handler = NULL; +} + + +/************************************************************************ +This function clears ALGLIB environment state. +All dynamic data controlled by state are freed. +************************************************************************/ +void ae_state_clear(ae_state *state) +{ + while( state->p_top_block->ptr!=DYN_BOTTOM ) + ae_frame_leave(state); +} + + +#ifndef AE_USE_CPP_ERROR_HANDLING +/************************************************************************ +This function sets jump buffer for error handling. + +buf may be NULL. +************************************************************************/ +void ae_state_set_break_jump(ae_state *state, jmp_buf *buf) +{ + state->break_jump = buf; +} +#endif + + +/************************************************************************ +This function makes new stack frame. + +This function takes two parameters: environment state and pointer to the +dynamic block which will be used as indicator of the frame beginning. +This dynamic block must be initialized by caller and mustn't be changed/ +deallocated/reused till ae_leave_frame called. It may be global or local +variable (local is even better). +************************************************************************/ +void ae_frame_make(ae_state *state, ae_frame *tmp) +{ + tmp->db_marker.p_next = state->p_top_block; + tmp->db_marker.deallocator = NULL; + tmp->db_marker.ptr = DYN_FRAME; + state->p_top_block = &tmp->db_marker; +} + + +/************************************************************************ +This function leaves current stack frame and deallocates all automatic +dynamic blocks which were attached to this frame. +************************************************************************/ +void ae_frame_leave(ae_state *state) +{ + while( state->p_top_block->ptr!=DYN_FRAME && state->p_top_block->ptr!=DYN_BOTTOM) + { + if( state->p_top_block->ptr!=NULL && state->p_top_block->deallocator!=NULL) + ((ae_deallocator)(state->p_top_block->deallocator))(state->p_top_block->ptr); + state->p_top_block = state->p_top_block->p_next; + } + state->p_top_block = state->p_top_block->p_next; +} + + +/************************************************************************ +This function attaches block to the dynamic block list + +block block +state ALGLIB environment state + +NOTES: +* never call it for special blocks which marks frame boundaries! +************************************************************************/ +void ae_db_attach(ae_dyn_block *block, ae_state *state) +{ + block->p_next = state->p_top_block; + state->p_top_block = block; +} + + +/************************************************************************ +This function malloc's dynamic block: + +block destination block, assumed to be uninitialized +size size (in bytes) +state ALGLIB environment state. May be NULL. +make_automatic if true, vector is added to the dynamic block list + +block is assumed to be uninitialized, its fields are ignored. + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +NOTES: +* never call it for blocks which are already in the list +************************************************************************/ +ae_bool ae_db_malloc(ae_dyn_block *block, ae_int_t size, ae_state *state, ae_bool make_automatic) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(size>=0, "ae_db_malloc(): negative size", state); + if( size<0 ) + return ae_false; + + /* alloc */ + block->ptr = ae_malloc((size_t)size, state); + if( block->ptr==NULL && size!=0 ) + return ae_false; + if( make_automatic && state!=NULL ) + ae_db_attach(block, state); + else + block->p_next = NULL; + block->deallocator = ae_free; + return ae_true; +} + + +/************************************************************************ +This function realloc's dynamic block: + +block destination block (initialized) +size new size (in bytes) +state ALGLIB environment state + +block is assumed to be initialized. + +This function: +* deletes old contents +* preserves automatic state + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +NOTES: +* never call it for special blocks which mark frame boundaries! +************************************************************************/ +ae_bool ae_db_realloc(ae_dyn_block *block, ae_int_t size, ae_state *state) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(size>=0, "ae_db_realloc(): negative size", state); + if( size<0 ) + return ae_false; + + /* realloc */ + if( block->ptr!=NULL ) + ((ae_deallocator)block->deallocator)(block->ptr); + block->ptr = ae_malloc((size_t)size, state); + if( block->ptr==NULL && size!=0 ) + return ae_false; + block->deallocator = ae_free; + return ae_true; +} + + +/************************************************************************ +This function clears dynamic block (releases all dynamically allocated +memory). Dynamic block may be in automatic management list - in this case +it will NOT be removed from list. + +block destination block (initialized) + +NOTES: +* never call it for special blocks which marks frame boundaries! +************************************************************************/ +void ae_db_free(ae_dyn_block *block) +{ + if( block->ptr!=NULL ) + ((ae_deallocator)block->deallocator)(block->ptr); + block->ptr = NULL; + block->deallocator = ae_free; +} + +/************************************************************************ +This function swaps contents of two dynamic blocks (pointers and +deallocators) leaving other parameters (automatic management settings, +etc.) unchanged. + +NOTES: +* never call it for special blocks which marks frame boundaries! +************************************************************************/ +void ae_db_swap(ae_dyn_block *block1, ae_dyn_block *block2) +{ + void (*deallocator)(void*) = NULL; + void * volatile ptr; + ptr = block1->ptr; + deallocator = block1->deallocator; + block1->ptr = block2->ptr; + block1->deallocator = block2->deallocator; + block2->ptr = ptr; + block2->deallocator = deallocator; +} + +/************************************************************************ +This function creates ae_vector. + +Vector size may be zero. Vector contents is uninitialized. + +dst destination vector +size vector size, may be zero +datatype guess what... +state ALGLIB environment state +make_automatic if true, vector is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +ae_bool ae_vector_init(ae_vector *dst, ae_int_t size, ae_datatype datatype, ae_state *state, ae_bool make_automatic) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(size>=0, "ae_vector_init(): negative size", state); + if( size<0 ) + return ae_false; + + /* init */ + dst->cnt = size; + dst->datatype = datatype; + if( !ae_db_malloc(&dst->data, size*ae_sizeof(datatype), state, make_automatic) ) + return ae_false; + dst->ptr.p_ptr = dst->data.ptr; + return ae_true; +} + + +/************************************************************************ +This function creates copy of ae_vector. + +dst destination vector +src well, it is source +state ALGLIB environment state +make_automatic if true, vector is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +ae_bool ae_vector_init_copy(ae_vector *dst, ae_vector *src, ae_state *state, ae_bool make_automatic) +{ + if( !ae_vector_init(dst, src->cnt, src->datatype, state, make_automatic) ) + return ae_false; + if( src->cnt!=0 ) + memcpy(dst->ptr.p_ptr, src->ptr.p_ptr, (size_t)(src->cnt*ae_sizeof(src->datatype))); + return ae_true; +} + +/************************************************************************ +This function creates ae_vector from x_vector: + +dst destination vector +src source, vector in x-format +state ALGLIB environment state +make_automatic if true, vector is added to the dynamic block list + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +void ae_vector_init_from_x(ae_vector *dst, x_vector *src, ae_state *state, ae_bool make_automatic) +{ + ae_vector_init(dst, (ae_int_t)src->cnt, (ae_datatype)src->datatype, state, make_automatic); + if( src->cnt>0 ) + memcpy(dst->ptr.p_ptr, src->ptr, (size_t)(((ae_int_t)src->cnt)*ae_sizeof((ae_datatype)src->datatype))); +} + + +/************************************************************************ +This function changes length of ae_vector. + +dst destination vector +newsize vector size, may be zero +state ALGLIB environment state + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +NOTES: +* vector must be initialized +* all contents is destroyed during setlength() call +* new size may be zero. +************************************************************************/ +ae_bool ae_vector_set_length(ae_vector *dst, ae_int_t newsize, ae_state *state) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(newsize>=0, "ae_vector_set_length(): negative size", state); + if( newsize<0 ) + return ae_false; + + /* set length */ + if( dst->cnt==newsize ) + return ae_true; + dst->cnt = newsize; + if( !ae_db_realloc(&dst->data, newsize*ae_sizeof(dst->datatype), state) ) + return ae_false; + dst->ptr.p_ptr = dst->data.ptr; + return ae_true; +} + + +/************************************************************************ +This function provides "CLEAR" functionality for vector (contents is +cleared, but structure still left in valid state). + +The function clears vector contents (releases all dynamically allocated +memory). Vector may be in automatic management list - in this case it +will NOT be removed from list. + +IMPORTANT: this function does NOT invalidates dst; it just releases all +dynamically allocated storage, but dst still may be used after call to +ae_vector_set_length(). + +dst destination vector +************************************************************************/ +void ae_vector_clear(ae_vector *dst) +{ + dst->cnt = 0; + ae_db_free(&dst->data); + dst->ptr.p_ptr = 0; +} + + +/************************************************************************ +This function provides "DESTROY" functionality for vector (contents is +cleared, all internal structures are destroyed). For vectors it is same +as CLEAR. + +dst destination vector +************************************************************************/ +void ae_vector_destroy(ae_vector *dst) +{ + ae_vector_clear(dst); +} + + +/************************************************************************ +This function efficiently swaps contents of two vectors, leaving other +pararemeters (automatic management, etc.) unchanged. +************************************************************************/ +void ae_swap_vectors(ae_vector *vec1, ae_vector *vec2) +{ + ae_int_t cnt; + ae_datatype datatype; + void *p_ptr; + + ae_db_swap(&vec1->data, &vec2->data); + + cnt = vec1->cnt; + datatype = vec1->datatype; + p_ptr = vec1->ptr.p_ptr; + vec1->cnt = vec2->cnt; + vec1->datatype = vec2->datatype; + vec1->ptr.p_ptr = vec2->ptr.p_ptr; + vec2->cnt = cnt; + vec2->datatype = datatype; + vec2->ptr.p_ptr = p_ptr; +} + +/************************************************************************ +This function creates ae_matrix. + +Matrix size may be zero, in such cases both rows and cols are zero. +Matrix contents is uninitialized. + +dst destination natrix +rows rows count +cols cols count +datatype element type +state ALGLIB environment state +make_automatic if true, matrix is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +ae_bool ae_matrix_init(ae_matrix *dst, ae_int_t rows, ae_int_t cols, ae_datatype datatype, ae_state *state, ae_bool make_automatic) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(rows>=0 && cols>=0, "ae_matrix_init(): negative length", state); + if( rows<0 || cols<0 ) + return ae_false; + + /* if one of rows/cols is zero, another MUST be too */ + if( rows==0 || cols==0 ) + { + rows = 0; + cols = 0; + } + + /* init */ + dst->rows = rows; + dst->cols = cols; + dst->stride = cols; + while( dst->stride*ae_sizeof(datatype)%AE_DATA_ALIGN!=0 ) + dst->stride++; + dst->datatype = datatype; + if( !ae_db_malloc(&dst->data, dst->rows*((ae_int_t)sizeof(void*)+dst->stride*ae_sizeof(datatype))+AE_DATA_ALIGN-1, state, make_automatic) ) + return ae_false; + ae_matrix_update_row_pointers(dst, ae_align((char*)dst->data.ptr+dst->rows*sizeof(void*),AE_DATA_ALIGN)); + return ae_true; +} + + +/************************************************************************ +This function creates copy of ae_matrix. + +dst destination matrix +src well, it is source +state ALGLIB environment state +make_automatic if true, matrix is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +ae_bool ae_matrix_init_copy(ae_matrix *dst, ae_matrix *src, ae_state *state, ae_bool make_automatic) +{ + ae_int_t i; + if( !ae_matrix_init(dst, src->rows, src->cols, src->datatype, state, make_automatic) ) + return ae_false; + if( src->rows!=0 && src->cols!=0 ) + { + if( dst->stride==src->stride ) + memcpy(dst->ptr.pp_void[0], src->ptr.pp_void[0], (size_t)(src->rows*src->stride*ae_sizeof(src->datatype))); + else + for(i=0; irows; i++) + memcpy(dst->ptr.pp_void[i], src->ptr.pp_void[i], (size_t)(dst->cols*ae_sizeof(dst->datatype))); + } + return ae_true; +} + + +void ae_matrix_init_from_x(ae_matrix *dst, x_matrix *src, ae_state *state, ae_bool make_automatic) +{ + char *p_src_row; + char *p_dst_row; + ae_int_t row_size; + ae_int_t i; + ae_matrix_init(dst, (ae_int_t)src->rows, (ae_int_t)src->cols, (ae_datatype)src->datatype, state, make_automatic); + if( src->rows!=0 && src->cols!=0 ) + { + p_src_row = (char*)src->ptr; + p_dst_row = (char*)(dst->ptr.pp_void[0]); + row_size = ae_sizeof((ae_datatype)src->datatype)*(ae_int_t)src->cols; + for(i=0; irows; i++, p_src_row+=src->stride*ae_sizeof((ae_datatype)src->datatype), p_dst_row+=dst->stride*ae_sizeof((ae_datatype)src->datatype)) + memcpy(p_dst_row, p_src_row, (size_t)(row_size)); + } +} + + +/************************************************************************ +This function changes length of ae_matrix. + +dst destination matrix +rows size, may be zero +cols size, may be zero +state ALGLIB environment state + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +NOTES: +* matrix must be initialized +* all contents is destroyed during setlength() call +* new size may be zero. +************************************************************************/ +ae_bool ae_matrix_set_length(ae_matrix *dst, ae_int_t rows, ae_int_t cols, ae_state *state) +{ + /* ensure that size is >=0 + two ways to exit: 1) through ae_assert, if we have non-NULL state, 2) by returning ae_false */ + if( state!=NULL ) + ae_assert(rows>=0 && cols>=0, "ae_matrix_set_length(): negative length", state); + if( rows<0 || cols<0 ) + return ae_false; + + if( dst->rows==rows && dst->cols==cols ) + return ae_true; + dst->rows = rows; + dst->cols = cols; + dst->stride = cols; + while( dst->stride*ae_sizeof(dst->datatype)%AE_DATA_ALIGN!=0 ) + dst->stride++; + if( !ae_db_realloc(&dst->data, dst->rows*((ae_int_t)sizeof(void*)+dst->stride*ae_sizeof(dst->datatype))+AE_DATA_ALIGN-1, state) ) + return ae_false; + ae_matrix_update_row_pointers(dst, ae_align((char*)dst->data.ptr+dst->rows*sizeof(void*),AE_DATA_ALIGN)); + return ae_true; +} + + +/************************************************************************ +This function provides "CLEAR" functionality for vector (contents is +cleared, but structure still left in valid state). + +The function clears matrix contents (releases all dynamically allocated +memory). Matrix may be in automatic management list - in this case it +will NOT be removed from list. + +IMPORTANT: this function does NOT invalidates dst; it just releases all +dynamically allocated storage, but dst still may be used after call to +ae_matrix_set_length(). + +dst destination matrix +************************************************************************/ +void ae_matrix_clear(ae_matrix *dst) +{ + dst->rows = 0; + dst->cols = 0; + dst->stride = 0; + ae_db_free(&dst->data); + dst->ptr.p_ptr = 0; +} + + +/************************************************************************ +This function provides "DESTROY" functionality for matrix (contents is +cleared, but structure still left in valid state). + +For matrices it is same as CLEAR. + +dst destination matrix +************************************************************************/ +void ae_matrix_destroy(ae_matrix *dst) +{ + ae_matrix_clear(dst); +} + + +/************************************************************************ +This function efficiently swaps contents of two vectors, leaving other +pararemeters (automatic management, etc.) unchanged. +************************************************************************/ +void ae_swap_matrices(ae_matrix *mat1, ae_matrix *mat2) +{ + ae_int_t rows; + ae_int_t cols; + ae_int_t stride; + ae_datatype datatype; + void *p_ptr; + + ae_db_swap(&mat1->data, &mat2->data); + + rows = mat1->rows; + cols = mat1->cols; + stride = mat1->stride; + datatype = mat1->datatype; + p_ptr = mat1->ptr.p_ptr; + + mat1->rows = mat2->rows; + mat1->cols = mat2->cols; + mat1->stride = mat2->stride; + mat1->datatype = mat2->datatype; + mat1->ptr.p_ptr = mat2->ptr.p_ptr; + + mat2->rows = rows; + mat2->cols = cols; + mat2->stride = stride; + mat2->datatype = datatype; + mat2->ptr.p_ptr = p_ptr; +} + + +/************************************************************************ +This function creates smart pointer structure. + +dst destination smart pointer. + already allocated, but not initialized. +subscriber pointer to pointer which receives updates in the + internal object stored in ae_smart_ptr. Any update to + dst->ptr is translated to subscriber. Can be NULL. +state ALGLIB environment state +make_automatic if true, smart pointer is added to the dynamic block list + +After initialization, smart pointer stores NULL pointer. + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success +************************************************************************/ +ae_bool ae_smart_ptr_init(ae_smart_ptr *dst, void **subscriber, ae_state *state, ae_bool make_automatic) +{ + dst->subscriber = subscriber; + dst->ptr = NULL; + if( dst->subscriber!=NULL ) + *(dst->subscriber) = dst->ptr; + dst->is_owner = ae_false; + dst->is_dynamic = ae_false; + dst->frame_entry.deallocator = ae_smart_ptr_destroy; + dst->frame_entry.ptr = dst; + if( make_automatic && state!=NULL ) + ae_db_attach(&dst->frame_entry, state); + return ae_true; +} + + +/************************************************************************ +This function clears smart pointer structure. + +dst destination smart pointer. + +After call to this function smart pointer contains NULL reference, which +is propagated to its subscriber (in cases non-NULL subscruber was +specified during pointer creation). +************************************************************************/ +void ae_smart_ptr_clear(void *_dst) +{ + ae_smart_ptr *dst = (ae_smart_ptr*)_dst; + if( dst->is_owner && dst->ptr!=NULL ) + { + dst->destroy(dst->ptr); + if( dst->is_dynamic ) + ae_free(dst->ptr); + } + dst->is_owner = ae_false; + dst->is_dynamic = ae_false; + dst->ptr = NULL; + dst->destroy = NULL; + if( dst->subscriber!=NULL ) + *(dst->subscriber) = NULL; +} + + +/************************************************************************ +This function dstroys smart pointer structure (same as clearing it). + +dst destination smart pointer. +************************************************************************/ +void ae_smart_ptr_destroy(void *_dst) +{ + ae_smart_ptr_clear(_dst); +} + + +/************************************************************************ +This function assigns pointer to ae_smart_ptr structure. + +dst destination smart pointer. +new_ptr new pointer to assign +is_owner whether smart pointer owns new_ptr +is_dynamic whether object is dynamic - clearing such object + requires BOTH calling destructor function AND calling + ae_free() for memory occupied by object. +destroy destructor function + +In case smart pointer already contains non-NULL value and owns this value, +it is freed before assigning new pointer. + +Changes in pointer are propagated to its subscriber (in case non-NULL +subscriber was specified during pointer creation). + +You can specify NULL new_ptr, in which case is_owner/destroy are ignored. +************************************************************************/ +void ae_smart_ptr_assign(ae_smart_ptr *dst, void *new_ptr, ae_bool is_owner, ae_bool is_dynamic, void (*destroy)(void*)) +{ + if( dst->is_owner && dst->ptr!=NULL ) + dst->destroy(dst->ptr); + if( new_ptr!=NULL ) + { + dst->ptr = new_ptr; + dst->is_owner = is_owner; + dst->is_dynamic = is_dynamic; + dst->destroy = destroy; + } + else + { + dst->ptr = NULL; + dst->is_owner = ae_false; + dst->is_dynamic = ae_false; + dst->destroy = NULL; + } + if( dst->subscriber!=NULL ) + *(dst->subscriber) = dst->ptr; +} + + +/************************************************************************ +This function releases pointer owned by ae_smart_ptr structure: +* all internal fields are set to NULL +* destructor function for internal pointer is NOT called even when we own + this pointer. After this call ae_smart_ptr releases ownership of its + pointer and passes it to caller. +* changes in pointer are propagated to its subscriber (in case non-NULL + subscriber was specified during pointer creation). + +dst destination smart pointer. +************************************************************************/ +void ae_smart_ptr_release(ae_smart_ptr *dst) +{ + dst->is_owner = ae_false; + dst->is_dynamic = ae_false; + dst->ptr = NULL; + dst->destroy = NULL; + if( dst->subscriber!=NULL ) + *(dst->subscriber) = NULL; +} + +/************************************************************************ +This function fills x_vector by ae_vector's contents: + +dst destination vector +src source, vector in x-format +state ALGLIB environment state + +NOTES: +* dst is assumed to be initialized. Its contents is freed before copying + data from src (if size / type are different) or overwritten (if + possible given destination size). +************************************************************************/ +void ae_x_set_vector(x_vector *dst, ae_vector *src, ae_state *state) +{ + if( dst->cnt!=src->cnt || dst->datatype!=src->datatype ) + { + if( dst->owner==OWN_AE ) + ae_free(dst->ptr); + dst->ptr = ae_malloc((size_t)(src->cnt*ae_sizeof(src->datatype)), state); + dst->last_action = ACT_NEW_LOCATION; + dst->cnt = src->cnt; + dst->datatype = src->datatype; + dst->owner = OWN_AE; + } + else + dst->last_action = ACT_SAME_LOCATION; + if( src->cnt ) + memcpy(dst->ptr, src->ptr.p_ptr, (size_t)(src->cnt*ae_sizeof(src->datatype))); +} + +/************************************************************************ +This function fills x_matrix by ae_matrix's contents: + +dst destination vector +src source, matrix in x-format +state ALGLIB environment state + +NOTES: +* dst is assumed to be initialized. Its contents is freed before copying + data from src (if size / type are different) or overwritten (if + possible given destination size). +************************************************************************/ +void ae_x_set_matrix(x_matrix *dst, ae_matrix *src, ae_state *state) +{ + char *p_src_row; + char *p_dst_row; + ae_int_t i; + ae_int_t row_size; + if( dst->rows!=src->rows || dst->cols!=src->cols || dst->datatype!=src->datatype ) + { + if( dst->owner==OWN_AE ) + ae_free(dst->ptr); + dst->rows = src->rows; + dst->cols = src->cols; + dst->stride = src->cols; + dst->datatype = src->datatype; + dst->ptr = ae_malloc((size_t)(dst->rows*((ae_int_t)dst->stride)*ae_sizeof(src->datatype)), state); + dst->last_action = ACT_NEW_LOCATION; + dst->owner = OWN_AE; + } + else + dst->last_action = ACT_SAME_LOCATION; + if( src->rows!=0 && src->cols!=0 ) + { + p_src_row = (char*)(src->ptr.pp_void[0]); + p_dst_row = (char*)dst->ptr; + row_size = ae_sizeof(src->datatype)*src->cols; + for(i=0; irows; i++, p_src_row+=src->stride*ae_sizeof(src->datatype), p_dst_row+=dst->stride*ae_sizeof(src->datatype)) + memcpy(p_dst_row, p_src_row, (size_t)(row_size)); + } +} + +/************************************************************************ +This function attaches x_vector to ae_vector's contents. +Ownership of memory allocated is not changed (it is still managed by +ae_matrix). + +dst destination vector +src source, vector in x-format +state ALGLIB environment state + +NOTES: +* dst is assumed to be initialized. Its contents is freed before + attaching to src. +* this function doesn't need ae_state parameter because it can't fail + (assuming correctly initialized src) +************************************************************************/ +void ae_x_attach_to_vector(x_vector *dst, ae_vector *src) +{ + if( dst->owner==OWN_AE ) + ae_free(dst->ptr); + dst->ptr = src->ptr.p_ptr; + dst->last_action = ACT_NEW_LOCATION; + dst->cnt = src->cnt; + dst->datatype = src->datatype; + dst->owner = OWN_CALLER; +} + +/************************************************************************ +This function attaches x_matrix to ae_matrix's contents. +Ownership of memory allocated is not changed (it is still managed by +ae_matrix). + +dst destination vector +src source, matrix in x-format +state ALGLIB environment state + +NOTES: +* dst is assumed to be initialized. Its contents is freed before + attaching to src. +* this function doesn't need ae_state parameter because it can't fail + (assuming correctly initialized src) +************************************************************************/ +void ae_x_attach_to_matrix(x_matrix *dst, ae_matrix *src) +{ + if( dst->owner==OWN_AE ) + ae_free(dst->ptr); + dst->rows = src->rows; + dst->cols = src->cols; + dst->stride = src->stride; + dst->datatype = src->datatype; + dst->ptr = &(src->ptr.pp_double[0][0]); + dst->last_action = ACT_NEW_LOCATION; + dst->owner = OWN_CALLER; +} + +/************************************************************************ +This function clears x_vector. It does nothing if vector is not owned by +ALGLIB environment. + +dst vector +************************************************************************/ +void x_vector_clear(x_vector *dst) +{ + if( dst->owner==OWN_AE ) + aligned_free(dst->ptr); + dst->ptr = NULL; + dst->cnt = 0; +} + +/************************************************************************ +Assertion +************************************************************************/ +void ae_assert(ae_bool cond, const char *msg, ae_state *state) +{ + if( !cond ) + ae_break(state, ERR_ASSERTION_FAILED, msg); +} + +/************************************************************************ +CPUID + +Returns information about features CPU and compiler support. + +You must tell ALGLIB what CPU family is used by defining AE_CPU symbol +(without this hint zero will be returned). + +Note: results of this function depend on both CPU and compiler; +if compiler doesn't support SSE intrinsics, function won't set +corresponding flag. +************************************************************************/ +static volatile ae_bool _ae_cpuid_initialized = ae_false; +static volatile ae_bool _ae_cpuid_has_sse2 = ae_false; +ae_int_t ae_cpuid() +{ + /* + * to speed up CPU detection we cache results from previous attempts + * there is no synchronization, but it is still thread safe. + * + * thread safety is guaranteed on all modern architectures which + * have following property: simultaneous writes by different cores + * to the same location will be executed in serial manner. + * + */ + ae_int_t result; + + /* + * if not initialized, determine system properties + */ + if( !_ae_cpuid_initialized ) + { + /* + * SSE2 + */ +#if defined(AE_CPU) +#if (AE_CPU==AE_INTEL) && defined(AE_HAS_SSE2_INTRINSICS) +#if AE_COMPILER==AE_MSVC + { + int CPUInfo[4]; + __cpuid(CPUInfo, 1); + if( (CPUInfo[3]&0x04000000)!=0 ) + _ae_cpuid_has_sse2 = ae_true; + } +#elif AE_COMPILER==AE_GNUC + { + ae_int_t a,b,c,d; + __asm__ __volatile__ ("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (1)); + if( (d&0x04000000)!=0 ) + _ae_cpuid_has_sse2 = ae_true; + } +#elif AE_COMPILER==AE_SUNC + { + ae_int_t a,b,c,d; + __asm__ __volatile__ ("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (1)); + if( (d&0x04000000)!=0 ) + _ae_cpuid_has_sse2 = ae_true; + } +#else +#endif +#endif +#endif + /* + * set initialization flag + */ + _ae_cpuid_initialized = ae_true; + } + + /* + * return + */ + result = 0; + if( _ae_cpuid_has_sse2 ) + result = result|CPU_SSE2; + return result; +} + +/************************************************************************ +Real math functions +************************************************************************/ +ae_bool ae_fp_eq(double v1, double v2) +{ + /* IEEE-strict floating point comparison */ + volatile double x = v1; + volatile double y = v2; + return x==y; +} + +ae_bool ae_fp_neq(double v1, double v2) +{ + /* IEEE-strict floating point comparison */ + return !ae_fp_eq(v1,v2); +} + +ae_bool ae_fp_less(double v1, double v2) +{ + /* IEEE-strict floating point comparison */ + volatile double x = v1; + volatile double y = v2; + return xy; +} + +ae_bool ae_fp_greater_eq(double v1, double v2) +{ + /* IEEE-strict floating point comparison */ + volatile double x = v1; + volatile double y = v2; + return x>=y; +} + +ae_bool ae_isfinite_stateless(double x, ae_int_t endianness) +{ + union _u + { + double a; + ae_int32_t p[2]; + } u; + ae_int32_t high; + u.a = x; + if( endianness==AE_LITTLE_ENDIAN ) + high = u.p[1]; + else + high = u.p[0]; + return (high & (ae_int32_t)0x7FF00000)!=(ae_int32_t)0x7FF00000; +} + +ae_bool ae_isnan_stateless(double x, ae_int_t endianness) +{ + union _u + { + double a; + ae_int32_t p[2]; + } u; + ae_int32_t high, low; + u.a = x; + if( endianness==AE_LITTLE_ENDIAN ) + { + high = u.p[1]; + low = u.p[0]; + } + else + { + high = u.p[0]; + low = u.p[1]; + } + return ((high &0x7FF00000)==0x7FF00000) && (((high &0x000FFFFF)!=0) || (low!=0)); +} + +ae_bool ae_isinf_stateless(double x, ae_int_t endianness) +{ + union _u + { + double a; + ae_int32_t p[2]; + } u; + ae_int32_t high, low; + u.a = x; + if( endianness==AE_LITTLE_ENDIAN ) + { + high = u.p[1]; + low = u.p[0]; + } + else + { + high = u.p[0]; + low = u.p[1]; + } + + /* 31 least significant bits of high are compared */ + return ((high&0x7FFFFFFF)==0x7FF00000) && (low==0); +} + +ae_bool ae_isposinf_stateless(double x, ae_int_t endianness) +{ + union _u + { + double a; + ae_int32_t p[2]; + } u; + ae_int32_t high, low; + u.a = x; + if( endianness==AE_LITTLE_ENDIAN ) + { + high = u.p[1]; + low = u.p[0]; + } + else + { + high = u.p[0]; + low = u.p[1]; + } + + /* all 32 bits of high are compared */ + return (high==(ae_int32_t)0x7FF00000) && (low==0); +} + +ae_bool ae_isneginf_stateless(double x, ae_int_t endianness) +{ + union _u + { + double a; + ae_int32_t p[2]; + } u; + ae_int32_t high, low; + u.a = x; + if( endianness==AE_LITTLE_ENDIAN ) + { + high = u.p[1]; + low = u.p[0]; + } + else + { + high = u.p[0]; + low = u.p[1]; + } + + /* this code is a bit tricky to avoid comparison of high with 0xFFF00000, which may be unsafe with some buggy compilers */ + return ((high&0x7FFFFFFF)==0x7FF00000) && (high!=(ae_int32_t)0x7FF00000) && (low==0); +} + +ae_int_t ae_get_endianness() +{ + union + { + double a; + ae_int32_t p[2]; + } u; + + /* + * determine endianness + * two types are supported: big-endian and little-endian. + * mixed-endian hardware is NOT supported. + * + * 1983 is used as magic number because its non-periodic double + * representation allow us to easily distinguish between upper + * and lower halfs and to detect mixed endian hardware. + * + */ + u.a = 1.0/1983.0; + if( u.p[1]==(ae_int32_t)0x3f408642 ) + return AE_LITTLE_ENDIAN; + if( u.p[0]==(ae_int32_t)0x3f408642 ) + return AE_BIG_ENDIAN; + return AE_MIXED_ENDIAN; +} + +ae_bool ae_isfinite(double x,ae_state *state) +{ + return ae_isfinite_stateless(x, state->endianness); +} + +ae_bool ae_isnan(double x, ae_state *state) +{ + return ae_isnan_stateless(x, state->endianness); +} + +ae_bool ae_isinf(double x, ae_state *state) +{ + return ae_isinf_stateless(x, state->endianness); +} + +ae_bool ae_isposinf(double x,ae_state *state) +{ + return ae_isposinf_stateless(x, state->endianness); +} + +ae_bool ae_isneginf(double x,ae_state *state) +{ + return ae_isneginf_stateless(x, state->endianness); +} + +double ae_fabs(double x, ae_state *state) +{ + return fabs(x); +} + +ae_int_t ae_iabs(ae_int_t x, ae_state *state) +{ + return x>=0 ? x : -x; +} + +double ae_sqr(double x, ae_state *state) +{ + return x*x; +} + +double ae_sqrt(double x, ae_state *state) +{ + return sqrt(x); +} + +ae_int_t ae_sign(double x, ae_state *state) +{ + if( x>0 ) return 1; + if( x<0 ) return -1; + return 0; +} + +ae_int_t ae_round(double x, ae_state *state) +{ + return (ae_int_t)(ae_ifloor(x+0.5,state)); +} + +ae_int_t ae_trunc(double x, ae_state *state) +{ + return (ae_int_t)(x>0 ? ae_ifloor(x,state) : ae_iceil(x,state)); +} + +ae_int_t ae_ifloor(double x, ae_state *state) +{ + return (ae_int_t)(floor(x)); +} + +ae_int_t ae_iceil(double x, ae_state *state) +{ + return (ae_int_t)(ceil(x)); +} + +ae_int_t ae_maxint(ae_int_t m1, ae_int_t m2, ae_state *state) +{ + return m1>m2 ? m1 : m2; +} + +ae_int_t ae_minint(ae_int_t m1, ae_int_t m2, ae_state *state) +{ + return m1>m2 ? m2 : m1; +} + +double ae_maxreal(double m1, double m2, ae_state *state) +{ + return m1>m2 ? m1 : m2; +} + +double ae_minreal(double m1, double m2, ae_state *state) +{ + return m1>m2 ? m2 : m1; +} + +#ifdef AE_DEBUGRNG +ae_int_t ae_debugrng() +{ + ae_int_t k; + ae_int_t result; + k = _debug_rng_s0/53668; + _debug_rng_s0 = 40014*(_debug_rng_s0-k*53668)-k*12211; + if( _debug_rng_s0<0 ) + _debug_rng_s0 = _debug_rng_s0+2147483563; + k = _debug_rng_s1/52774; + _debug_rng_s1 = 40692*(_debug_rng_s1-k*52774)-k*3791; + if( _debug_rng_s1<0 ) + _debug_rng_s1 = _debug_rng_s1+2147483399; + result = _debug_rng_s0-_debug_rng_s1; + if( result<1 ) + result = result+2147483562; + return result; +} +#endif + +double ae_randomreal(ae_state *state) +{ +#ifdef AE_DEBUGRNG + return ae_debugrng()/2147483563.0; +#else + int i1 = rand(); + int i2 = rand(); + double mx = (double)(RAND_MAX)+1.0; + volatile double tmp0 = i2/mx; + volatile double tmp1 = i1+tmp0; + return tmp1/mx; +#endif +} + +ae_int_t ae_randominteger(ae_int_t maxv, ae_state *state) +{ +#ifdef AE_DEBUGRNG + return (ae_debugrng()-1)%maxv; +#else + return rand()%maxv; +#endif +} + +double ae_sin(double x, ae_state *state) +{ + return sin(x); +} + +double ae_cos(double x, ae_state *state) +{ + return cos(x); +} + +double ae_tan(double x, ae_state *state) +{ + return tan(x); +} + +double ae_sinh(double x, ae_state *state) +{ + return sinh(x); +} + +double ae_cosh(double x, ae_state *state) +{ + return cosh(x); +} +double ae_tanh(double x, ae_state *state) +{ + return tanh(x); +} + +double ae_asin(double x, ae_state *state) +{ + return asin(x); +} + +double ae_acos(double x, ae_state *state) +{ + return acos(x); +} + +double ae_atan(double x, ae_state *state) +{ + return atan(x); +} + +double ae_atan2(double y, double x, ae_state *state) +{ + return atan2(y,x); +} + +double ae_log(double x, ae_state *state) +{ + return log(x); +} + +double ae_pow(double x, double y, ae_state *state) +{ + return pow(x,y); +} + +double ae_exp(double x, ae_state *state) +{ + return exp(x); +} + +/************************************************************************ +Symmetric/Hermitian properties: check and force +************************************************************************/ +static void x_split_length(ae_int_t n, ae_int_t nb, ae_int_t* n1, ae_int_t* n2) +{ + ae_int_t r; + if( n<=nb ) + { + *n1 = n; + *n2 = 0; + } + else + { + if( n%nb!=0 ) + { + *n2 = n%nb; + *n1 = n-(*n2); + } + else + { + *n2 = n/2; + *n1 = n-(*n2); + if( *n1%nb==0 ) + { + return; + } + r = nb-*n1%nb; + *n1 = *n1+r; + *n2 = *n2-r; + } + } +} +static double x_safepythag2(double x, double y) +{ + double w; + double xabs; + double yabs; + double z; + xabs = fabs(x); + yabs = fabs(y); + w = xabs>yabs ? xabs : yabs; + z = xabsx_nb || len1>x_nb ) + { + ae_int_t n1, n2; + if( len0>len1 ) + { + x_split_length(len0, x_nb, &n1, &n2); + is_symmetric_rec_off_stat(a, offset0, offset1, n1, len1, nonfinite, mx, err, _state); + is_symmetric_rec_off_stat(a, offset0+n1, offset1, n2, len1, nonfinite, mx, err, _state); + } + else + { + x_split_length(len1, x_nb, &n1, &n2); + is_symmetric_rec_off_stat(a, offset0, offset1, len0, n1, nonfinite, mx, err, _state); + is_symmetric_rec_off_stat(a, offset0, offset1+n1, len0, n2, nonfinite, mx, err, _state); + } + return; + } + else + { + /* base case */ + double *p1, *p2, *prow, *pcol; + double v; + ae_int_t i, j; + + p1 = (double*)(a->ptr)+offset0*a->stride+offset1; + p2 = (double*)(a->ptr)+offset1*a->stride+offset0; + for(i=0; istride; + for(j=0; jv ? *mx : v; + v = fabs(*prow); + *mx = *mx>v ? *mx : v; + v = fabs(*pcol-*prow); + *err = *err>v ? *err : v; + } + pcol += a->stride; + prow++; + } + } + } +} +/* + * this function checks that diagonal block A0 is symmetric. + * Block A0 is specified by its offset and size. + * + * [ . ] + * [ A0 ] + * A = [ . ] + * [ . ] + * + * this subroutine updates current values of: + * a) mx maximum value of A[i,j] found so far + * b) err componentwise difference between A0 and A0^T + * + */ +static void is_symmetric_rec_diag_stat(x_matrix *a, ae_int_t offset, ae_int_t len, ae_bool *nonfinite, double *mx, double *err, ae_state *_state) +{ + double *p, *prow, *pcol; + double v; + ae_int_t i, j; + + /* try to split problem into two smaller ones */ + if( len>x_nb ) + { + ae_int_t n1, n2; + x_split_length(len, x_nb, &n1, &n2); + is_symmetric_rec_diag_stat(a, offset, n1, nonfinite, mx, err, _state); + is_symmetric_rec_diag_stat(a, offset+n1, n2, nonfinite, mx, err, _state); + is_symmetric_rec_off_stat(a, offset+n1, offset, n2, n1, nonfinite, mx, err, _state); + return; + } + + /* base case */ + p = (double*)(a->ptr)+offset*a->stride+offset; + for(i=0; istride; + for(j=0; jstride,prow++) + { + if( !ae_isfinite(*pcol,_state) || !ae_isfinite(*prow,_state) ) + { + *nonfinite = ae_true; + } + else + { + v = fabs(*pcol); + *mx = *mx>v ? *mx : v; + v = fabs(*prow); + *mx = *mx>v ? *mx : v; + v = fabs(*pcol-*prow); + *err = *err>v ? *err : v; + } + } + v = fabs(p[i+i*a->stride]); + *mx = *mx>v ? *mx : v; + } +} +/* + * this function checks difference between offdiagonal blocks BL and BU + * (see below). Block BL is specified by offsets (offset0,offset1) and + * sizes (len0,len1). + * + * [ . ] + * [ A0 BU ] + * A = [ BL A1 ] + * [ . ] + * + * this subroutine updates current values of: + * a) mx maximum value of A[i,j] found so far + * b) err componentwise difference between elements of BL and BU^H + * + */ +static void is_hermitian_rec_off_stat(x_matrix *a, ae_int_t offset0, ae_int_t offset1, ae_int_t len0, ae_int_t len1, ae_bool *nonfinite, double *mx, double *err, ae_state *_state) +{ + /* try to split problem into two smaller ones */ + if( len0>x_nb || len1>x_nb ) + { + ae_int_t n1, n2; + if( len0>len1 ) + { + x_split_length(len0, x_nb, &n1, &n2); + is_hermitian_rec_off_stat(a, offset0, offset1, n1, len1, nonfinite, mx, err, _state); + is_hermitian_rec_off_stat(a, offset0+n1, offset1, n2, len1, nonfinite, mx, err, _state); + } + else + { + x_split_length(len1, x_nb, &n1, &n2); + is_hermitian_rec_off_stat(a, offset0, offset1, len0, n1, nonfinite, mx, err, _state); + is_hermitian_rec_off_stat(a, offset0, offset1+n1, len0, n2, nonfinite, mx, err, _state); + } + return; + } + else + { + /* base case */ + ae_complex *p1, *p2, *prow, *pcol; + double v; + ae_int_t i, j; + + p1 = (ae_complex*)(a->ptr)+offset0*a->stride+offset1; + p2 = (ae_complex*)(a->ptr)+offset1*a->stride+offset0; + for(i=0; istride; + for(j=0; jx, _state) || !ae_isfinite(pcol->y, _state) || !ae_isfinite(prow->x, _state) || !ae_isfinite(prow->y, _state) ) + { + *nonfinite = ae_true; + } + else + { + v = x_safepythag2(pcol->x, pcol->y); + *mx = *mx>v ? *mx : v; + v = x_safepythag2(prow->x, prow->y); + *mx = *mx>v ? *mx : v; + v = x_safepythag2(pcol->x-prow->x, pcol->y+prow->y); + *err = *err>v ? *err : v; + } + pcol += a->stride; + prow++; + } + } + } +} +/* + * this function checks that diagonal block A0 is Hermitian. + * Block A0 is specified by its offset and size. + * + * [ . ] + * [ A0 ] + * A = [ . ] + * [ . ] + * + * this subroutine updates current values of: + * a) mx maximum value of A[i,j] found so far + * b) err componentwise difference between A0 and A0^H + * + */ +static void is_hermitian_rec_diag_stat(x_matrix *a, ae_int_t offset, ae_int_t len, ae_bool *nonfinite, double *mx, double *err, ae_state *_state) +{ + ae_complex *p, *prow, *pcol; + double v; + ae_int_t i, j; + + /* try to split problem into two smaller ones */ + if( len>x_nb ) + { + ae_int_t n1, n2; + x_split_length(len, x_nb, &n1, &n2); + is_hermitian_rec_diag_stat(a, offset, n1, nonfinite, mx, err, _state); + is_hermitian_rec_diag_stat(a, offset+n1, n2, nonfinite, mx, err, _state); + is_hermitian_rec_off_stat(a, offset+n1, offset, n2, n1, nonfinite, mx, err, _state); + return; + } + + /* base case */ + p = (ae_complex*)(a->ptr)+offset*a->stride+offset; + for(i=0; istride; + for(j=0; jstride,prow++) + { + if( !ae_isfinite(pcol->x, _state) || !ae_isfinite(pcol->y, _state) || !ae_isfinite(prow->x, _state) || !ae_isfinite(prow->y, _state) ) + { + *nonfinite = ae_true; + } + else + { + v = x_safepythag2(pcol->x, pcol->y); + *mx = *mx>v ? *mx : v; + v = x_safepythag2(prow->x, prow->y); + *mx = *mx>v ? *mx : v; + v = x_safepythag2(pcol->x-prow->x, pcol->y+prow->y); + *err = *err>v ? *err : v; + } + } + if( !ae_isfinite(p[i+i*a->stride].x, _state) || !ae_isfinite(p[i+i*a->stride].y, _state) ) + { + *nonfinite = ae_true; + } + else + { + v = fabs(p[i+i*a->stride].x); + *mx = *mx>v ? *mx : v; + v = fabs(p[i+i*a->stride].y); + *err = *err>v ? *err : v; + } + } +} +/* + * this function copies offdiagonal block BL to its symmetric counterpart + * BU (see below). Block BL is specified by offsets (offset0,offset1) + * and sizes (len0,len1). + * + * [ . ] + * [ A0 BU ] + * A = [ BL A1 ] + * [ . ] + * + */ +static void force_symmetric_rec_off_stat(x_matrix *a, ae_int_t offset0, ae_int_t offset1, ae_int_t len0, ae_int_t len1) +{ + /* try to split problem into two smaller ones */ + if( len0>x_nb || len1>x_nb ) + { + ae_int_t n1, n2; + if( len0>len1 ) + { + x_split_length(len0, x_nb, &n1, &n2); + force_symmetric_rec_off_stat(a, offset0, offset1, n1, len1); + force_symmetric_rec_off_stat(a, offset0+n1, offset1, n2, len1); + } + else + { + x_split_length(len1, x_nb, &n1, &n2); + force_symmetric_rec_off_stat(a, offset0, offset1, len0, n1); + force_symmetric_rec_off_stat(a, offset0, offset1+n1, len0, n2); + } + return; + } + else + { + /* base case */ + double *p1, *p2, *prow, *pcol; + ae_int_t i, j; + + p1 = (double*)(a->ptr)+offset0*a->stride+offset1; + p2 = (double*)(a->ptr)+offset1*a->stride+offset0; + for(i=0; istride; + for(j=0; jstride; + prow++; + } + } + } +} +/* + * this function copies lower part of diagonal block A0 to its upper part + * Block is specified by offset and size. + * + * [ . ] + * [ A0 ] + * A = [ . ] + * [ . ] + * + */ +static void force_symmetric_rec_diag_stat(x_matrix *a, ae_int_t offset, ae_int_t len) +{ + double *p, *prow, *pcol; + ae_int_t i, j; + + /* try to split problem into two smaller ones */ + if( len>x_nb ) + { + ae_int_t n1, n2; + x_split_length(len, x_nb, &n1, &n2); + force_symmetric_rec_diag_stat(a, offset, n1); + force_symmetric_rec_diag_stat(a, offset+n1, n2); + force_symmetric_rec_off_stat(a, offset+n1, offset, n2, n1); + return; + } + + /* base case */ + p = (double*)(a->ptr)+offset*a->stride+offset; + for(i=0; istride; + for(j=0; jstride,prow++) + *pcol = *prow; + } +} +/* + * this function copies Hermitian transpose of offdiagonal block BL to + * its symmetric counterpart BU (see below). Block BL is specified by + * offsets (offset0,offset1) and sizes (len0,len1). + * + * [ . ] + * [ A0 BU ] + * A = [ BL A1 ] + * [ . ] + */ +static void force_hermitian_rec_off_stat(x_matrix *a, ae_int_t offset0, ae_int_t offset1, ae_int_t len0, ae_int_t len1) +{ + /* try to split problem into two smaller ones */ + if( len0>x_nb || len1>x_nb ) + { + ae_int_t n1, n2; + if( len0>len1 ) + { + x_split_length(len0, x_nb, &n1, &n2); + force_hermitian_rec_off_stat(a, offset0, offset1, n1, len1); + force_hermitian_rec_off_stat(a, offset0+n1, offset1, n2, len1); + } + else + { + x_split_length(len1, x_nb, &n1, &n2); + force_hermitian_rec_off_stat(a, offset0, offset1, len0, n1); + force_hermitian_rec_off_stat(a, offset0, offset1+n1, len0, n2); + } + return; + } + else + { + /* base case */ + ae_complex *p1, *p2, *prow, *pcol; + ae_int_t i, j; + + p1 = (ae_complex*)(a->ptr)+offset0*a->stride+offset1; + p2 = (ae_complex*)(a->ptr)+offset1*a->stride+offset0; + for(i=0; istride; + for(j=0; jstride; + prow++; + } + } + } +} +/* + * this function copies Hermitian transpose of lower part of + * diagonal block A0 to its upper part Block is specified by offset and size. + * + * [ . ] + * [ A0 ] + * A = [ . ] + * [ . ] + * + */ +static void force_hermitian_rec_diag_stat(x_matrix *a, ae_int_t offset, ae_int_t len) +{ + ae_complex *p, *prow, *pcol; + ae_int_t i, j; + + /* try to split problem into two smaller ones */ + if( len>x_nb ) + { + ae_int_t n1, n2; + x_split_length(len, x_nb, &n1, &n2); + force_hermitian_rec_diag_stat(a, offset, n1); + force_hermitian_rec_diag_stat(a, offset+n1, n2); + force_hermitian_rec_off_stat(a, offset+n1, offset, n2, n1); + return; + } + + /* base case */ + p = (ae_complex*)(a->ptr)+offset*a->stride+offset; + for(i=0; istride; + for(j=0; jstride,prow++) + *pcol = *prow; + } +} +ae_bool x_is_symmetric(x_matrix *a) +{ + double mx, err; + ae_bool nonfinite; + ae_state _alglib_env_state; + if( a->datatype!=DT_REAL ) + return ae_false; + if( a->cols!=a->rows ) + return ae_false; + if( a->cols==0 || a->rows==0 ) + return ae_true; + ae_state_init(&_alglib_env_state); + mx = 0; + err = 0; + nonfinite = ae_false; + is_symmetric_rec_diag_stat(a, 0, (ae_int_t)a->rows, &nonfinite, &mx, &err, &_alglib_env_state); + if( nonfinite ) + return ae_false; + if( mx==0 ) + return ae_true; + return err/mx<=1.0E-14; +} +ae_bool x_is_hermitian(x_matrix *a) +{ + double mx, err; + ae_bool nonfinite; + ae_state _alglib_env_state; + if( a->datatype!=DT_COMPLEX ) + return ae_false; + if( a->cols!=a->rows ) + return ae_false; + if( a->cols==0 || a->rows==0 ) + return ae_true; + ae_state_init(&_alglib_env_state); + mx = 0; + err = 0; + nonfinite = ae_false; + is_hermitian_rec_diag_stat(a, 0, (ae_int_t)a->rows, &nonfinite, &mx, &err, &_alglib_env_state); + if( nonfinite ) + return ae_false; + if( mx==0 ) + return ae_true; + return err/mx<=1.0E-14; +} +ae_bool x_force_symmetric(x_matrix *a) +{ + if( a->datatype!=DT_REAL ) + return ae_false; + if( a->cols!=a->rows ) + return ae_false; + if( a->cols==0 || a->rows==0 ) + return ae_true; + force_symmetric_rec_diag_stat(a, 0, (ae_int_t)a->rows); + return ae_true; +} +ae_bool x_force_hermitian(x_matrix *a) +{ + if( a->datatype!=DT_COMPLEX ) + return ae_false; + if( a->cols!=a->rows ) + return ae_false; + if( a->cols==0 || a->rows==0 ) + return ae_true; + force_hermitian_rec_diag_stat(a, 0, (ae_int_t)a->rows); + return ae_true; +} + +ae_bool ae_is_symmetric(ae_matrix *a) +{ + x_matrix x; + x.owner = OWN_CALLER; + ae_x_attach_to_matrix(&x, a); + return x_is_symmetric(&x); +} + +ae_bool ae_is_hermitian(ae_matrix *a) +{ + x_matrix x; + x.owner = OWN_CALLER; + ae_x_attach_to_matrix(&x, a); + return x_is_hermitian(&x); +} + +ae_bool ae_force_symmetric(ae_matrix *a) +{ + x_matrix x; + x.owner = OWN_CALLER; + ae_x_attach_to_matrix(&x, a); + return x_force_symmetric(&x); +} + +ae_bool ae_force_hermitian(ae_matrix *a) +{ + x_matrix x; + x.owner = OWN_CALLER; + ae_x_attach_to_matrix(&x, a); + return x_force_hermitian(&x); +} + +/************************************************************************ +This function converts six-bit value (from 0 to 63) to character (only +digits, lowercase and uppercase letters, minus and underscore are used). + +If v is negative or greater than 63, this function returns '?'. +************************************************************************/ +static char _sixbits2char_tbl[64] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '-', '_' }; + +char ae_sixbits2char(ae_int_t v) +{ + + if( v<0 || v>63 ) + return '?'; + return _sixbits2char_tbl[v]; + + /* v is correct, process it */ + /*if( v<10 ) + return '0'+v; + v -= 10; + if( v<26 ) + return 'A'+v; + v -= 26; + if( v<26 ) + return 'a'+v; + v -= 26; + return v==0 ? '-' : '_';*/ +} + +/************************************************************************ +This function converts character to six-bit value (from 0 to 63). + +This function is inverse of ae_sixbits2char() +If c is not correct character, this function returns -1. +************************************************************************/ +static ae_int_t _ae_char2sixbits_tbl[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 62, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, -1, -1, -1, -1, 63, + -1, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, -1, -1, -1, -1, -1 }; +ae_int_t ae_char2sixbits(char c) +{ + return (c>=0 && c<127) ? _ae_char2sixbits_tbl[(int)c] : -1; +} + +/************************************************************************ +This function converts three bytes (24 bits) to four six-bit values +(24 bits again). + +src pointer to three bytes +dst pointer to four ints +************************************************************************/ +void ae_threebytes2foursixbits(const unsigned char *src, ae_int_t *dst) +{ + dst[0] = src[0] & 0x3F; + dst[1] = (src[0]>>6) | ((src[1]&0x0F)<<2); + dst[2] = (src[1]>>4) | ((src[2]&0x03)<<4); + dst[3] = src[2]>>2; +} + +/************************************************************************ +This function converts four six-bit values (24 bits) to three bytes +(24 bits again). + +src pointer to four ints +dst pointer to three bytes +************************************************************************/ +void ae_foursixbits2threebytes(const ae_int_t *src, unsigned char *dst) +{ + dst[0] = (unsigned char)( src[0] | ((src[1]&0x03)<<6)); + dst[1] = (unsigned char)((src[1]>>2) | ((src[2]&0x0F)<<4)); + dst[2] = (unsigned char)((src[2]>>4) | (src[3]<<2)); +} + +/************************************************************************ +This function serializes boolean value into buffer + +v boolean value to be serialized +buf buffer, at least 12 characters wide + (11 chars for value, one for trailing zero) +state ALGLIB environment state +************************************************************************/ +void ae_bool2str(ae_bool v, char *buf, ae_state *state) +{ + char c = v ? '1' : '0'; + ae_int_t i; + for(i=0; iendianness==AE_BIG_ENDIAN ) + { + for(i=0; i<(ae_int_t)(sizeof(ae_int_t)/2); i++) + { + unsigned char tc; + tc = u.bytes[i]; + u.bytes[i] = u.bytes[sizeof(ae_int_t)-1-i]; + u.bytes[sizeof(ae_int_t)-1-i] = tc; + } + } + + /* + * convert to six-bit representation, output + * + * NOTE: last 12th element of sixbits is always zero, we do not output it + */ + ae_threebytes2foursixbits(u.bytes+0, sixbits+0); + ae_threebytes2foursixbits(u.bytes+3, sixbits+4); + ae_threebytes2foursixbits(u.bytes+6, sixbits+8); + for(i=0; i=AE_SER_ENTRY_LENGTH ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + sixbits[sixbitsread] = d; + sixbitsread++; + buf++; + } + *pasttheend = buf; + if( sixbitsread==0 ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + for(i=sixbitsread; i<12; i++) + sixbits[i] = 0; + ae_foursixbits2threebytes(sixbits+0, u.bytes+0); + ae_foursixbits2threebytes(sixbits+4, u.bytes+3); + ae_foursixbits2threebytes(sixbits+8, u.bytes+6); + if( state->endianness==AE_BIG_ENDIAN ) + { + for(i=0; i<(ae_int_t)(sizeof(ae_int_t)/2); i++) + { + unsigned char tc; + tc = u.bytes[i]; + u.bytes[i] = u.bytes[sizeof(ae_int_t)-1-i]; + u.bytes[sizeof(ae_int_t)-1-i] = tc; + } + } + return u.ival; +} + + +/************************************************************************ +This function serializes double value into buffer + +v double value to be serialized +buf buffer, at least 12 characters wide + (11 chars for value, one for trailing zero) +state ALGLIB environment state +************************************************************************/ +void ae_double2str(double v, char *buf, ae_state *state) +{ + union _u + { + double dval; + unsigned char bytes[9]; + } u; + ae_int_t i; + ae_int_t sixbits[12]; + + /* + * handle special quantities + */ + if( ae_isnan(v, state) ) + { + const char *s = ".nan_______"; + memcpy(buf, s, strlen(s)+1); + return; + } + if( ae_isposinf(v, state) ) + { + const char *s = ".posinf____"; + memcpy(buf, s, strlen(s)+1); + return; + } + if( ae_isneginf(v, state) ) + { + const char *s = ".neginf____"; + memcpy(buf, s, strlen(s)+1); + return; + } + + /* + * process general case: + * 1. copy v to array of chars + * 2. set 9th byte of u.bytes to zero in order to + * simplify conversion to six-bit representation + * 3. convert to little endian (if needed) + * 4. convert to six-bit representation + * (last 12th element of sixbits is always zero, we do not output it) + */ + u.dval = v; + u.bytes[8] = 0; + if( state->endianness==AE_BIG_ENDIAN ) + { + for(i=0; i<(ae_int_t)(sizeof(double)/2); i++) + { + unsigned char tc; + tc = u.bytes[i]; + u.bytes[i] = u.bytes[sizeof(double)-1-i]; + u.bytes[sizeof(double)-1-i] = tc; + } + } + ae_threebytes2foursixbits(u.bytes+0, sixbits+0); + ae_threebytes2foursixbits(u.bytes+3, sixbits+4); + ae_threebytes2foursixbits(u.bytes+6, sixbits+8); + for(i=0; iv_nan; + } + if( strncmp(buf, s_posinf, strlen(s_posinf))==0 ) + { + *pasttheend = buf+strlen(s_posinf); + return state->v_posinf; + } + if( strncmp(buf, s_neginf, strlen(s_neginf))==0 ) + { + *pasttheend = buf+strlen(s_neginf); + return state->v_neginf; + } + ae_break(state, ERR_ASSERTION_FAILED, emsg); + } + + /* + * General case: + * 1. read and decode six-bit digits + * 2. check that all 11 digits were read + * 3. set last 12th digit to zero (needed for simplicity of conversion) + * 4. convert to 8 bytes + * 5. convert to big endian representation, if needed + */ + sixbitsread = 0; + while( *buf!=' ' && *buf!='\t' && *buf!='\n' && *buf!='\r' && *buf!=0 ) + { + ae_int_t d; + d = ae_char2sixbits(*buf); + if( d<0 || sixbitsread>=AE_SER_ENTRY_LENGTH ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + sixbits[sixbitsread] = d; + sixbitsread++; + buf++; + } + *pasttheend = buf; + if( sixbitsread!=AE_SER_ENTRY_LENGTH ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + sixbits[AE_SER_ENTRY_LENGTH] = 0; + ae_foursixbits2threebytes(sixbits+0, u.bytes+0); + ae_foursixbits2threebytes(sixbits+4, u.bytes+3); + ae_foursixbits2threebytes(sixbits+8, u.bytes+6); + if( state->endianness==AE_BIG_ENDIAN ) + { + for(i=0; i<(ae_int_t)(sizeof(double)/2); i++) + { + unsigned char tc; + tc = u.bytes[i]; + u.bytes[i] = u.bytes[sizeof(double)-1-i]; + u.bytes[sizeof(double)-1-i] = tc; + } + } + return u.dval; +} + + +/************************************************************************ +This function performs given number of spin-wait iterations +************************************************************************/ +void ae_spin_wait(ae_int_t cnt) +{ + /* + * these strange operations with ae_never_change_it are necessary to + * prevent compiler optimization of the loop. + */ + volatile ae_int_t i; + + /* very unlikely because no one will wait for such amount of cycles */ + if( cnt>0x12345678 ) + ae_never_change_it = cnt%10; + + /* spin wait, test condition which will never be true */ + for(i=0; i0 ) + ae_never_change_it--; +} + + +/************************************************************************ +This function causes the calling thread to relinquish the CPU. The thread +is moved to the end of the queue and some other thread gets to run. + +NOTE: this function should NOT be called when AE_OS is AE_UNKNOWN - the + whole program will be abnormally terminated. +************************************************************************/ +void ae_yield() +{ +#if AE_OS==AE_WINDOWS + if( !SwitchToThread() ) + Sleep(0); +#elif AE_OS==AE_POSIX + sched_yield(); +#else + abort(); +#endif +} + +/************************************************************************ +This function initializes ae_lock structure and sets lock in a free mode. +************************************************************************/ +void ae_init_lock(ae_lock *lock) +{ +#if AE_OS==AE_WINDOWS + lock->p_lock = (ae_int_t*)ae_align((void*)(&lock->buf),AE_LOCK_ALIGNMENT); + lock->p_lock[0] = 0; +#elif AE_OS==AE_POSIX + pthread_mutex_init(&lock->mutex, NULL); +#else + lock->is_locked = ae_false; +#endif +} + + +/************************************************************************ +This function acquires lock. In case lock is busy, we perform several +iterations inside tight loop before trying again. +************************************************************************/ +void ae_acquire_lock(ae_lock *lock) +{ +#if AE_OS==AE_WINDOWS + ae_int_t cnt = 0; +#ifdef AE_SMP_DEBUGCOUNTERS + InterlockedIncrement((LONG volatile *)&_ae_dbg_lock_acquisitions); +#endif + for(;;) + { + if( InterlockedCompareExchange((LONG volatile *)lock->p_lock, 1, 0)==0 ) + return; + ae_spin_wait(AE_LOCK_CYCLES); +#ifdef AE_SMP_DEBUGCOUNTERS + InterlockedIncrement((LONG volatile *)&_ae_dbg_lock_spinwaits); +#endif + cnt++; + if( cnt%AE_LOCK_TESTS_BEFORE_YIELD==0 ) + { +#ifdef AE_SMP_DEBUGCOUNTERS + InterlockedIncrement((LONG volatile *)&_ae_dbg_lock_yields); +#endif + ae_yield(); + } + } +#elif AE_OS==AE_POSIX + ae_int_t cnt = 0; + for(;;) + { + if( pthread_mutex_trylock(&lock->mutex)==0 ) + return; + ae_spin_wait(AE_LOCK_CYCLES); + cnt++; + if( cnt%AE_LOCK_TESTS_BEFORE_YIELD==0 ) + ae_yield(); + } + ; +#else + AE_CRITICAL_ASSERT(!lock->is_locked); + lock->is_locked = ae_true; +#endif +} + + +/************************************************************************ +This function releases lock. +************************************************************************/ +void ae_release_lock(ae_lock *lock) +{ +#if AE_OS==AE_WINDOWS + InterlockedExchange((LONG volatile *)lock->p_lock, 0); +#elif AE_OS==AE_POSIX + pthread_mutex_unlock(&lock->mutex); +#else + lock->is_locked = ae_false; +#endif +} + + +/************************************************************************ +This function frees ae_lock structure. +************************************************************************/ +void ae_free_lock(ae_lock *lock) +{ +#if AE_OS==AE_POSIX + pthread_mutex_destroy(&lock->mutex); +#endif +} + + +/************************************************************************ +This function creates ae_shared_pool structure. + +dst destination shared pool; + already allocated, but not initialized. +state ALGLIB environment state +make_automatic if true, pool is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. +************************************************************************/ +ae_bool ae_shared_pool_init(void *_dst, ae_state *state, ae_bool make_automatic) +{ + ae_shared_pool *dst; + + dst = (ae_shared_pool*)_dst; + + /* init */ + dst->seed_object = NULL; + dst->recycled_objects = NULL; + dst->recycled_entries = NULL; + dst->enumeration_counter = NULL; + dst->size_of_object = 0; + dst->init = NULL; + dst->init_copy = NULL; + dst->destroy = NULL; + dst->frame_entry.deallocator = ae_shared_pool_destroy; + dst->frame_entry.ptr = dst; + if( make_automatic && state!=NULL ) + ae_db_attach(&dst->frame_entry, state); + ae_init_lock(&dst->pool_lock); + return ae_true; +} + + +/************************************************************************ +This function clears all dynamically allocated fields of the pool except +for the lock. It does NOT try to acquire pool_lock. + +NOTE: this function is NOT thread-safe, it is not protected by lock. +************************************************************************/ +static void ae_shared_pool_internalclear(ae_shared_pool *dst) +{ + ae_shared_pool_entry *ptr, *tmp; + + /* destroy seed */ + if( dst->seed_object!=NULL ) + { + dst->destroy((void*)dst->seed_object); + ae_free((void*)dst->seed_object); + dst->seed_object = NULL; + } + + /* destroy recycled objects */ + for(ptr=dst->recycled_objects; ptr!=NULL;) + { + tmp = (ae_shared_pool_entry*)ptr->next_entry; + dst->destroy(ptr->obj); + ae_free(ptr->obj); + ae_free(ptr); + ptr = tmp; + } + dst->recycled_objects = NULL; + + /* destroy recycled entries */ + for(ptr=dst->recycled_entries; ptr!=NULL;) + { + tmp = (ae_shared_pool_entry*)ptr->next_entry; + ae_free(ptr); + ptr = tmp; + } + dst->recycled_entries = NULL; +} + + +/************************************************************************ +This function creates copy of ae_shared_pool. + +dst destination pool, allocated but not initialized +src source pool +state ALGLIB environment state +make_automatic if true, pool is added to the dynamic block list + +Error handling: +* if state is NULL, returns ae_false on allocation error +* if state is not NULL, calls ae_break() on allocation error +* returns ae_true on success + +dst is assumed to be uninitialized, its fields are ignored. + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +ae_bool ae_shared_pool_init_copy(void *_dst, void *_src, ae_state *state, ae_bool make_automatic) +{ + ae_shared_pool *dst, *src; + ae_shared_pool_entry *ptr; + + dst = (ae_shared_pool*)_dst; + src = (ae_shared_pool*)_src; + if( !ae_shared_pool_init(dst, state, make_automatic) ) + return ae_false; + + /* copy non-pointer fields */ + dst->size_of_object = src->size_of_object; + dst->init = src->init; + dst->init_copy = src->init_copy; + dst->destroy = src->destroy; + ae_init_lock(&dst->pool_lock); + + /* copy seed object */ + if( src->seed_object!=NULL ) + { + dst->seed_object = ae_malloc(dst->size_of_object, state); + if( dst->seed_object==NULL ) + return ae_false; + if( !dst->init_copy(dst->seed_object, src->seed_object, state, ae_false) ) + return ae_false; + } + + /* copy recycled objects */ + dst->recycled_objects = NULL; + for(ptr=src->recycled_objects; ptr!=NULL; ptr=(ae_shared_pool_entry*)ptr->next_entry) + { + ae_shared_pool_entry *tmp; + tmp = (ae_shared_pool_entry*)ae_malloc(sizeof(ae_shared_pool_entry), state); + if( tmp==NULL ) + return ae_false; + tmp->obj = ae_malloc(dst->size_of_object, state); + if( tmp->obj==NULL ) + return ae_false; + if( !dst->init_copy(tmp->obj, ptr->obj, state, ae_false) ) + return ae_false; + tmp->next_entry = dst->recycled_objects; + dst->recycled_objects = tmp; + } + + /* recycled entries are not copied because they do not store any information */ + dst->recycled_entries = NULL; + + /* enumeration counter is reset on copying */ + dst->enumeration_counter = NULL; + + /* initialize frame record */ + dst->frame_entry.deallocator = ae_shared_pool_destroy; + dst->frame_entry.ptr = dst; + + /* return */ + return ae_true; +} + + +/************************************************************************ +This function clears contents of the pool, but pool remain usable. + +IMPORTANT: this function invalidates dst, it can not be used after it is + cleared. + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +void ae_shared_pool_clear(void *_dst) +{ + ae_shared_pool *dst = (ae_shared_pool*)_dst; + + /* clear seed and lists */ + ae_shared_pool_internalclear(dst); + + /* clear fields */ + dst->seed_object = NULL; + dst->recycled_objects = NULL; + dst->recycled_entries = NULL; + dst->enumeration_counter = NULL; + dst->size_of_object = 0; + dst->init = NULL; + dst->init_copy = NULL; + dst->destroy = NULL; +} + + +/************************************************************************ +This function destroys pool (object is left in invalid state, all +dynamically allocated memory is freed). + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +void ae_shared_pool_destroy(void *_dst) +{ + ae_shared_pool *dst = (ae_shared_pool*)_dst; + ae_shared_pool_clear(_dst); + ae_free_lock(&dst->pool_lock); +} + + +/************************************************************************ +This function returns True, if internal seed object was set. It returns +False for un-seeded pool. + +dst destination pool (initialized by constructor function) + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +ae_bool ae_shared_pool_is_initialized(void *_dst) +{ + ae_shared_pool *dst = (ae_shared_pool*)_dst; + return dst->seed_object!=NULL; +} + + +/************************************************************************ +This function sets internal seed object. All objects owned by the pool +(current seed object, recycled objects) are automatically freed. + +dst destination pool (initialized by constructor function) +seed_object new seed object +size_of_object sizeof(), used to allocate memory +init constructor function +init_copy copy constructor +clear destructor function +state ALGLIB environment state + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +void ae_shared_pool_set_seed( + ae_shared_pool *dst, + void *seed_object, + ae_int_t size_of_object, + ae_bool (*init)(void* dst, ae_state* state, ae_bool make_automatic), + ae_bool (*init_copy)(void* dst, void* src, ae_state* state, ae_bool make_automatic), + void (*destroy)(void* ptr), + ae_state *state) +{ + /* destroy internal objects */ + ae_shared_pool_internalclear(dst); + + /* set non-pointer fields */ + dst->size_of_object = size_of_object; + dst->init = init; + dst->init_copy = init_copy; + dst->destroy = destroy; + + /* set seed object */ + dst->seed_object = ae_malloc(size_of_object, state); + ae_assert(dst->seed_object!=NULL, "ALGLIB: unable to allocate memory for ae_shared_pool_set_seed()", state); + ae_assert( + init_copy(dst->seed_object, seed_object, state, ae_false), + "ALGLIB: unable to initialize seed in ae_shared_pool_set_seed()", + state); +} + + +/************************************************************************ +This function retrieves a copy of the seed object from the pool and +stores it to target smart pointer ptr. + +In case target pointer owns non-NULL value, it is deallocated before +storing value retrieved from pool. Target pointer becomes owner of the +value which was retrieved from pool. + +pool pool +pptr pointer to ae_smart_ptr structure +state ALGLIB environment state + +NOTE: this function IS thread-safe. It acquires pool lock during its + operation and can be used simultaneously from several threads. +************************************************************************/ +void ae_shared_pool_retrieve( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state) +{ + void *new_obj; + + /* assert that pool was seeded */ + ae_assert( + pool->seed_object!=NULL, + "ALGLIB: shared pool is not seeded, PoolRetrieve() failed", + state); + + /* acquire lock */ + ae_acquire_lock(&pool->pool_lock); + + /* try to reuse recycled objects */ + if( pool->recycled_objects!=NULL ) + { + void *new_obj; + ae_shared_pool_entry *result; + + /* retrieve entry/object from list of recycled objects */ + result = pool->recycled_objects; + pool->recycled_objects = (ae_shared_pool_entry*)pool->recycled_objects->next_entry; + new_obj = result->obj; + result->obj = NULL; + + /* move entry to list of recycled entries */ + result->next_entry = pool->recycled_entries; + pool->recycled_entries = result; + + /* release lock */ + ae_release_lock(&pool->pool_lock); + + /* assign object to smart pointer */ + ae_smart_ptr_assign(pptr, new_obj, ae_true, ae_true, pool->destroy); + return; + } + + /* release lock; we do not need it anymore because copy constructor does not modify source variable */ + ae_release_lock(&pool->pool_lock); + + /* create new object from seed */ + new_obj = ae_malloc(pool->size_of_object, state); + ae_assert(new_obj!=NULL, "ALGLIB: unable to allocate memory for ae_shared_pool_retrieve()", state); + ae_assert( + pool->init_copy(new_obj, pool->seed_object, state, ae_false), + "ALGLIB: unable to initialize object in ae_shared_pool_retrieve()", + state); + + /* assign object to smart pointer and return */ + ae_smart_ptr_assign(pptr, new_obj, ae_true, ae_true, pool->destroy); +} + + +/************************************************************************ +This function recycles object owned by smart pointer by moving it to +internal storage of the shared pool. + +Source pointer must own the object. After function is over, it owns NULL +pointer. + +pool pool +pptr pointer to ae_smart_ptr structure +state ALGLIB environment state + +NOTE: this function IS thread-safe. It acquires pool lock during its + operation and can be used simultaneously from several threads. +************************************************************************/ +void ae_shared_pool_recycle( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state) +{ + ae_shared_pool_entry *new_entry; + + /* assert that pool was seeded */ + ae_assert( + pool->seed_object!=NULL, + "ALGLIB: shared pool is not seeded, PoolRecycle() failed", + state); + + /* assert that pointer non-null and owns the object */ + ae_assert(pptr->is_owner, "ALGLIB: pptr in ae_shared_pool_recycle() does not own its pointer", state); + ae_assert(pptr->ptr!=NULL, "ALGLIB: pptr in ae_shared_pool_recycle() is NULL", state); + + /* acquire lock */ + ae_acquire_lock(&pool->pool_lock); + + /* acquire shared pool entry (reuse one from recycled_entries or malloc new one) */ + if( pool->recycled_entries!=NULL ) + { + /* reuse previously allocated entry */ + new_entry = pool->recycled_entries; + pool->recycled_entries = (ae_shared_pool_entry*)new_entry->next_entry; + } + else + { + /* + * Allocate memory for new entry. + * + * NOTE: we release pool lock during allocation because ae_malloc() may raise + * exception and we do not want our pool to be left in the locked state. + */ + ae_release_lock(&pool->pool_lock); + new_entry = (ae_shared_pool_entry*)ae_malloc(sizeof(ae_shared_pool_entry), state); + ae_assert(new_entry!=NULL, "ALGLIB: unable to allocate memory in ae_shared_pool_recycle()", state); + ae_acquire_lock(&pool->pool_lock); + } + + /* add object to the list of recycled objects */ + new_entry->obj = pptr->ptr; + new_entry->next_entry = pool->recycled_objects; + pool->recycled_objects = new_entry; + + /* release lock object */ + ae_release_lock(&pool->pool_lock); + + /* release source pointer */ + ae_smart_ptr_release(pptr); +} + + +/************************************************************************ +This function clears internal list of recycled objects, but does not +change seed object managed by the pool. + +pool pool +state ALGLIB environment state + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +void ae_shared_pool_clear_recycled( + ae_shared_pool *pool, + ae_state *state) +{ + ae_shared_pool_entry *ptr, *tmp; + + /* clear recycled objects */ + for(ptr=pool->recycled_objects; ptr!=NULL;) + { + tmp = (ae_shared_pool_entry*)ptr->next_entry; + pool->destroy(ptr->obj); + ae_free(ptr->obj); + ae_free(ptr); + ptr = tmp; + } + pool->recycled_objects = NULL; +} + + +/************************************************************************ +This function allows to enumerate recycled elements of the shared pool. +It stores pointer to the first recycled object in the smart pointer. + +IMPORTANT: +* in case target pointer owns non-NULL value, it is deallocated before + storing value retrieved from pool. +* recycled object IS NOT removed from pool +* target pointer DOES NOT become owner of the new value +* this function IS NOT thread-safe +* you SHOULD NOT modify shared pool during enumeration (although you can + modify state of the objects retrieved from pool) +* in case there is no recycled objects in the pool, NULL is stored to pptr +* in case pool is not seeded, NULL is stored to pptr + +pool pool +pptr pointer to ae_smart_ptr structure +state ALGLIB environment state +************************************************************************/ +void ae_shared_pool_first_recycled( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state) +{ + /* modify internal enumeration counter */ + pool->enumeration_counter = pool->recycled_objects; + + /* exit on empty list */ + if( pool->enumeration_counter==NULL ) + { + ae_smart_ptr_assign(pptr, NULL, ae_false, ae_false, NULL); + return; + } + + /* assign object to smart pointer */ + ae_smart_ptr_assign(pptr, pool->enumeration_counter->obj, ae_false, ae_false, pool->destroy); +} + + +/************************************************************************ +This function allows to enumerate recycled elements of the shared pool. +It stores pointer to the next recycled object in the smart pointer. + +IMPORTANT: +* in case target pointer owns non-NULL value, it is deallocated before + storing value retrieved from pool. +* recycled object IS NOT removed from pool +* target pointer DOES NOT become owner of the new value +* this function IS NOT thread-safe +* you SHOULD NOT modify shared pool during enumeration (although you can + modify state of the objects retrieved from pool) +* in case there is no recycled objects left in the pool, NULL is stored. +* in case pool is not seeded, NULL is stored. + +pool pool +pptr pointer to ae_smart_ptr structure +state ALGLIB environment state +************************************************************************/ +void ae_shared_pool_next_recycled( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state) +{ + /* exit on end of list */ + if( pool->enumeration_counter==NULL ) + { + ae_smart_ptr_assign(pptr, NULL, ae_false, ae_false, NULL); + return; + } + + /* modify internal enumeration counter */ + pool->enumeration_counter = (ae_shared_pool_entry*)pool->enumeration_counter->next_entry; + + /* exit on empty list */ + if( pool->enumeration_counter==NULL ) + { + ae_smart_ptr_assign(pptr, NULL, ae_false, ae_false, NULL); + return; + } + + /* assign object to smart pointer */ + ae_smart_ptr_assign(pptr, pool->enumeration_counter->obj, ae_false, ae_false, pool->destroy); +} + + + +/************************************************************************ +This function clears internal list of recycled objects and seed object. +However, pool still can be used (after initialization with another seed). + +pool pool +state ALGLIB environment state + +NOTE: this function is NOT thread-safe. It does not acquire pool lock, so + you should NOT call it when lock can be used by another thread. +************************************************************************/ +void ae_shared_pool_reset( + ae_shared_pool *pool, + ae_state *state) +{ + /* clear seed and lists */ + ae_shared_pool_internalclear(pool); + + /* clear fields */ + pool->seed_object = NULL; + pool->recycled_objects = NULL; + pool->recycled_entries = NULL; + pool->enumeration_counter = NULL; + pool->size_of_object = 0; + pool->init = NULL; + pool->init_copy = NULL; + pool->destroy = NULL; +} + + +/************************************************************************ +This function initializes serializer +************************************************************************/ +void ae_serializer_init(ae_serializer *serializer) +{ + serializer->mode = AE_SM_DEFAULT; + serializer->entries_needed = 0; + serializer->bytes_asked = 0; +} + +void ae_serializer_clear(ae_serializer *serializer) +{ +} + +void ae_serializer_alloc_start(ae_serializer *serializer) +{ + serializer->entries_needed = 0; + serializer->bytes_asked = 0; + serializer->mode = AE_SM_ALLOC; +} + +void ae_serializer_alloc_entry(ae_serializer *serializer) +{ + serializer->entries_needed++; +} + +ae_int_t ae_serializer_get_alloc_size(ae_serializer *serializer) +{ + ae_int_t rows, lastrowsize, result; + + serializer->mode = AE_SM_READY2S; + + /* if no entries needes (degenerate case) */ + if( serializer->entries_needed==0 ) + { + serializer->bytes_asked = 1; + return serializer->bytes_asked; + } + + /* non-degenerate case */ + rows = serializer->entries_needed/AE_SER_ENTRIES_PER_ROW; + lastrowsize = AE_SER_ENTRIES_PER_ROW; + if( serializer->entries_needed%AE_SER_ENTRIES_PER_ROW ) + { + lastrowsize = serializer->entries_needed%AE_SER_ENTRIES_PER_ROW; + rows++; + } + + /* calculate result size */ + result = ((rows-1)*AE_SER_ENTRIES_PER_ROW+lastrowsize)*AE_SER_ENTRY_LENGTH; + result += (rows-1)*(AE_SER_ENTRIES_PER_ROW-1)+(lastrowsize-1); + result += rows*2; + serializer->bytes_asked = result; + return result; +} + +#ifdef AE_USE_CPP_SERIALIZATION +void ae_serializer_sstart_str(ae_serializer *serializer, std::string *buf) +{ + serializer->mode = AE_SM_TO_CPPSTRING; + serializer->out_cppstr = buf; + serializer->entries_saved = 0; + serializer->bytes_written = 0; +} +#endif + +#ifdef AE_USE_CPP_SERIALIZATION +void ae_serializer_ustart_str(ae_serializer *serializer, const std::string *buf) +{ + serializer->mode = AE_SM_FROM_STRING; + serializer->in_str = buf->c_str(); +} +#endif + +void ae_serializer_sstart_str(ae_serializer *serializer, char *buf) +{ + serializer->mode = AE_SM_TO_STRING; + serializer->out_str = buf; + serializer->out_str[0] = 0; + serializer->entries_saved = 0; + serializer->bytes_written = 0; +} + +void ae_serializer_ustart_str(ae_serializer *serializer, const char *buf) +{ + serializer->mode = AE_SM_FROM_STRING; + serializer->in_str = buf; +} + +void ae_serializer_serialize_bool(ae_serializer *serializer, ae_bool v, ae_state *state) +{ + char buf[AE_SER_ENTRY_LENGTH+2+1]; + const char *emsg = "ALGLIB: serialization integrity error"; + ae_int_t bytes_appended; + + /* prepare serialization, check consistency */ + ae_bool2str(v, buf, state); + serializer->entries_saved++; + if( serializer->entries_saved%AE_SER_ENTRIES_PER_ROW ) + strcat(buf, " "); + else + strcat(buf, "\r\n"); + bytes_appended = (ae_int_t)strlen(buf); + if( serializer->bytes_written+bytes_appended > serializer->bytes_asked ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + serializer->bytes_written += bytes_appended; + + /* append to buffer */ +#ifdef AE_USE_CPP_SERIALIZATION + if( serializer->mode==AE_SM_TO_CPPSTRING ) + { + *(serializer->out_cppstr) += buf; + return; + } +#endif + if( serializer->mode==AE_SM_TO_STRING ) + { + strcat(serializer->out_str, buf); + serializer->out_str += bytes_appended; + return; + } + ae_break(state, ERR_ASSERTION_FAILED, emsg); +} + +void ae_serializer_serialize_int(ae_serializer *serializer, ae_int_t v, ae_state *state) +{ + char buf[AE_SER_ENTRY_LENGTH+2+1]; + const char *emsg = "ALGLIB: serialization integrity error"; + ae_int_t bytes_appended; + + /* prepare serialization, check consistency */ + ae_int2str(v, buf, state); + serializer->entries_saved++; + if( serializer->entries_saved%AE_SER_ENTRIES_PER_ROW ) + strcat(buf, " "); + else + strcat(buf, "\r\n"); + bytes_appended = (ae_int_t)strlen(buf); + if( serializer->bytes_written+bytes_appended > serializer->bytes_asked ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + serializer->bytes_written += bytes_appended; + + /* append to buffer */ +#ifdef AE_USE_CPP_SERIALIZATION + if( serializer->mode==AE_SM_TO_CPPSTRING ) + { + *(serializer->out_cppstr) += buf; + return; + } +#endif + if( serializer->mode==AE_SM_TO_STRING ) + { + strcat(serializer->out_str, buf); + serializer->out_str += bytes_appended; + return; + } + ae_break(state, ERR_ASSERTION_FAILED, emsg); +} + +void ae_serializer_serialize_double(ae_serializer *serializer, double v, ae_state *state) +{ + char buf[AE_SER_ENTRY_LENGTH+2+1]; + const char *emsg = "ALGLIB: serialization integrity error"; + ae_int_t bytes_appended; + + /* prepare serialization, check consistency */ + ae_double2str(v, buf, state); + serializer->entries_saved++; + if( serializer->entries_saved%AE_SER_ENTRIES_PER_ROW ) + strcat(buf, " "); + else + strcat(buf, "\r\n"); + bytes_appended = (ae_int_t)strlen(buf); + if( serializer->bytes_written+bytes_appended > serializer->bytes_asked ) + ae_break(state, ERR_ASSERTION_FAILED, emsg); + serializer->bytes_written += bytes_appended; + + /* append to buffer */ +#ifdef AE_USE_CPP_SERIALIZATION + if( serializer->mode==AE_SM_TO_CPPSTRING ) + { + *(serializer->out_cppstr) += buf; + return; + } +#endif + if( serializer->mode==AE_SM_TO_STRING ) + { + strcat(serializer->out_str, buf); + serializer->out_str += bytes_appended; + return; + } + ae_break(state, ERR_ASSERTION_FAILED, emsg); +} + +void ae_serializer_unserialize_bool(ae_serializer *serializer, ae_bool *v, ae_state *state) +{ + *v = ae_str2bool(serializer->in_str, state, &serializer->in_str); +} + +void ae_serializer_unserialize_int(ae_serializer *serializer, ae_int_t *v, ae_state *state) +{ + *v = ae_str2int(serializer->in_str, state, &serializer->in_str); +} + +void ae_serializer_unserialize_double(ae_serializer *serializer, double *v, ae_state *state) +{ + *v = ae_str2double(serializer->in_str, state, &serializer->in_str); +} + +void ae_serializer_stop(ae_serializer *serializer) +{ +} + + +/************************************************************************ +Complex math functions +************************************************************************/ +ae_complex ae_complex_from_d(double v) +{ + ae_complex r; + r.x = v; + r.y = 0.0; + return r; +} + +ae_complex ae_c_neg(ae_complex lhs) +{ + ae_complex result; + result.x = -lhs.x; + result.y = -lhs.y; + return result; +} + +ae_complex ae_c_conj(ae_complex lhs, ae_state *state) +{ + ae_complex result; + result.x = +lhs.x; + result.y = -lhs.y; + return result; +} + +ae_complex ae_c_sqr(ae_complex lhs, ae_state *state) +{ + ae_complex result; + result.x = lhs.x*lhs.x-lhs.y*lhs.y; + result.y = 2*lhs.x*lhs.y; + return result; +} + +double ae_c_abs(ae_complex z, ae_state *state) +{ + double w; + double xabs; + double yabs; + double v; + + xabs = fabs(z.x); + yabs = fabs(z.y); + w = xabs>yabs ? xabs : yabs; + v = xabsx; + v0y = -v0->y; + v1x = v1->x; + v1y = -v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( !bconj0 && bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = v0->y; + v1x = v1->x; + v1y = -v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( bconj0 && !bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = -v0->y; + v1x = v1->x; + v1y = v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( !bconj0 && !bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = v0->y; + v1x = v1->x; + v1y = v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + result.x = rx; + result.y = ry; + return result; +} + +void ae_v_cmove(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + for(i=0; ix = vsrc->x; + vdst->y = -vsrc->y; + } + } + else + { + for(i=0; ix = vsrc->x; + vdst->y = -vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = -vsrc->y; + } + } + } + else + { + /* + * optimized case + */ + if( bconj ) + { + for(i=0; ix = -vsrc->x; + vdst->y = vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = -vsrc->y; + } + } + } +} + +void ae_v_cmoved(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = -alpha*vsrc->y; + } + } + else + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = alpha*vsrc->y; + } + } + } + else + { + /* + * optimized case + */ + if( bconj ) + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = -alpha*vsrc->y; + } + } + else + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = alpha*vsrc->y; + } + } + } +} + +void ae_v_cmovec(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x+ay*vsrc->y; + vdst->y = -ax*vsrc->y+ay*vsrc->x; + } + } + else + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x-ay*vsrc->y; + vdst->y = ax*vsrc->y+ay*vsrc->x; + } + } + } + else + { + /* + * highly optimized case + */ + if( bconj ) + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x+ay*vsrc->y; + vdst->y = -ax*vsrc->y+ay*vsrc->x; + } + } + else + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x-ay*vsrc->y; + vdst->y = ax*vsrc->y+ay*vsrc->x; + } + } + } +} + +void ae_v_cadd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + for(i=0; ix += vsrc->x; + vdst->y -= vsrc->y; + } + } + else + { + for(i=0; ix += vsrc->x; + vdst->y += vsrc->y; + } + } + } + else + { + /* + * optimized case + */ + if( bconj ) + { + for(i=0; ix += vsrc->x; + vdst->y -= vsrc->y; + } + } + else + { + for(i=0; ix += vsrc->x; + vdst->y += vsrc->y; + } + } + } +} + +void ae_v_caddd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + for(i=0; ix += alpha*vsrc->x; + vdst->y -= alpha*vsrc->y; + } + } + else + { + for(i=0; ix += alpha*vsrc->x; + vdst->y += alpha*vsrc->y; + } + } + } + else + { + /* + * optimized case + */ + if( bconj ) + { + for(i=0; ix += alpha*vsrc->x; + vdst->y -= alpha*vsrc->y; + } + } + else + { + for(i=0; ix += alpha*vsrc->x; + vdst->y += alpha*vsrc->y; + } + } + } +} + +void ae_v_caddc(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + double ax = alpha.x, ay = alpha.y; + if( bconj ) + { + for(i=0; ix += ax*vsrc->x+ay*vsrc->y; + vdst->y -= ax*vsrc->y-ay*vsrc->x; + } + } + else + { + for(i=0; ix += ax*vsrc->x-ay*vsrc->y; + vdst->y += ax*vsrc->y+ay*vsrc->x; + } + } + } + else + { + /* + * highly optimized case + */ + double ax = alpha.x, ay = alpha.y; + if( bconj ) + { + for(i=0; ix += ax*vsrc->x+ay*vsrc->y; + vdst->y -= ax*vsrc->y-ay*vsrc->x; + } + } + else + { + for(i=0; ix += ax*vsrc->x-ay*vsrc->y; + vdst->y += ax*vsrc->y+ay*vsrc->x; + } + } + } +} + +void ae_v_csub(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n) +{ + ae_bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + /* + * general unoptimized case + */ + if( bconj ) + { + for(i=0; ix -= vsrc->x; + vdst->y += vsrc->y; + } + } + else + { + for(i=0; ix -= vsrc->x; + vdst->y -= vsrc->y; + } + } + } + else + { + /* + * highly optimized case + */ + if( bconj ) + { + for(i=0; ix -= vsrc->x; + vdst->y += vsrc->y; + } + } + else + { + for(i=0; ix -= vsrc->x; + vdst->y -= vsrc->y; + } + } + } +} + +void ae_v_csubd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha) +{ + ae_v_caddd(vdst, stride_dst, vsrc, stride_src, conj_src, n, -alpha); +} + +void ae_v_csubc(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha) +{ + alpha.x = -alpha.x; + alpha.y = -alpha.y; + ae_v_caddc(vdst, stride_dst, vsrc, stride_src, conj_src, n, alpha); +} + +void ae_v_cmuld(ae_complex *vdst, ae_int_t stride_dst, ae_int_t n, double alpha) +{ + ae_int_t i; + if( stride_dst!=1 ) + { + /* + * general unoptimized case + */ + for(i=0; ix *= alpha; + vdst->y *= alpha; + } + } + else + { + /* + * optimized case + */ + for(i=0; ix *= alpha; + vdst->y *= alpha; + } + } +} + +void ae_v_cmulc(ae_complex *vdst, ae_int_t stride_dst, ae_int_t n, ae_complex alpha) +{ + ae_int_t i; + if( stride_dst!=1 ) + { + /* + * general unoptimized case + */ + double ax = alpha.x, ay = alpha.y; + for(i=0; ix, dsty = vdst->y; + vdst->x = ax*dstx-ay*dsty; + vdst->y = ax*dsty+ay*dstx; + } + } + else + { + /* + * highly optimized case + */ + double ax = alpha.x, ay = alpha.y; + for(i=0; ix, dsty = vdst->y; + vdst->x = ax*dstx-ay*dsty; + vdst->y = ax*dsty+ay*dstx; + } + } +} + +/************************************************************************ +Real BLAS operations +************************************************************************/ +double ae_v_dotproduct(const double *v0, ae_int_t stride0, const double *v1, ae_int_t stride1, ae_int_t n) +{ + double result = 0; + ae_int_t i; + if( stride0!=1 || stride1!=1 ) + { + /* + * slow general code + */ + for(i=0; iba, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ia, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ra, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ca, 0, DT_COMPLEX, _state, make_automatic) ) + return ae_false; + return ae_true; +} + +ae_bool _rcommstate_init_copy(rcommstate* dst, rcommstate* src, ae_state *_state, ae_bool make_automatic) +{ + if( !ae_vector_init_copy(&dst->ba, &src->ba, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ia, &src->ia, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ra, &src->ra, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ca, &src->ca, _state, make_automatic) ) + return ae_false; + dst->stage = src->stage; + return ae_true; +} + +void _rcommstate_clear(rcommstate* p) +{ + ae_vector_clear(&p->ba); + ae_vector_clear(&p->ia); + ae_vector_clear(&p->ra); + ae_vector_clear(&p->ca); +} + +void _rcommstate_destroy(rcommstate* p) +{ + _rcommstate_clear(p); +} + +#ifdef AE_DEBUG4WINDOWS +int _tickcount() +{ + return GetTickCount(); +} +#endif + +#ifdef AE_DEBUG4POSIX +#include +int _tickcount() +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) ) + return 0; + return now.tv_sec * 1000.0 + now.tv_nsec / 1000000.0; +} +#endif + +#ifdef AE_DEBUGRNG +void ae_set_seed(ae_int_t s0, ae_int_t s1) +{ + ae_int_t hqrnd_hqrndm1 = 2147483563; + ae_int_t hqrnd_hqrndm2 = 2147483399; + + while(s0<1) + s0 += hqrnd_hqrndm1-1; + while(s0>hqrnd_hqrndm1-1) + s0 -= hqrnd_hqrndm1-1; + + while(s1<1) + s1 += hqrnd_hqrndm2-1; + while(s1>hqrnd_hqrndm2-1) + s1 -= hqrnd_hqrndm2-1; + + _debug_rng_s0 = s0; + _debug_rng_s1 = s1; +} + +void ae_get_seed(ae_int_t *s0, ae_int_t *s1) +{ + *s0 = _debug_rng_s0; + *s1 = _debug_rng_s1; +} +#endif + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ RELATED FUNCTIONALITY +// +///////////////////////////////////////////////////////////////////////// +/******************************************************************** +Internal forwards +********************************************************************/ +namespace alglib +{ + double get_aenv_nan(); + double get_aenv_posinf(); + double get_aenv_neginf(); + ae_int_t my_stricmp(const char *s1, const char *s2); + char* filter_spaces(const char *s); + void str_vector_create(const char *src, bool match_head_only, std::vector *p_vec); + void str_matrix_create(const char *src, std::vector< std::vector > *p_mat); + + ae_bool parse_bool_delim(const char *s, const char *delim); + ae_int_t parse_int_delim(const char *s, const char *delim); + bool _parse_real_delim(const char *s, const char *delim, double *result, const char **new_s); + double parse_real_delim(const char *s, const char *delim); + alglib::complex parse_complex_delim(const char *s, const char *delim); + + std::string arraytostring(const bool *ptr, ae_int_t n); + std::string arraytostring(const ae_int_t *ptr, ae_int_t n); + std::string arraytostring(const double *ptr, ae_int_t n, int dps); + std::string arraytostring(const alglib::complex *ptr, ae_int_t n, int dps); +} + +/******************************************************************** +Global and local constants +********************************************************************/ +const double alglib::machineepsilon = 5E-16; +const double alglib::maxrealnumber = 1E300; +const double alglib::minrealnumber = 1E-300; +const alglib::ae_int_t alglib::endianness = alglib_impl::ae_get_endianness(); +const double alglib::fp_nan = alglib::get_aenv_nan(); +const double alglib::fp_posinf = alglib::get_aenv_posinf(); +const double alglib::fp_neginf = alglib::get_aenv_neginf(); + + +/******************************************************************** +ap_error +********************************************************************/ +alglib::ap_error::ap_error() +{ +} + +alglib::ap_error::ap_error(const char *s) +{ + msg = s; +} + +void alglib::ap_error::make_assertion(bool bClause) +{ + if(!bClause) + throw ap_error(); +} + +void alglib::ap_error::make_assertion(bool bClause, const char *msg) +{ + if(!bClause) + throw ap_error(msg); +} + + +/******************************************************************** +Complex number with double precision. +********************************************************************/ +alglib::complex::complex():x(0.0),y(0.0) +{ +} + +alglib::complex::complex(const double &_x):x(_x),y(0.0) +{ +} + +alglib::complex::complex(const double &_x, const double &_y):x(_x),y(_y) +{ +} + +alglib::complex::complex(const alglib::complex &z):x(z.x),y(z.y) +{ +} + +alglib::complex& alglib::complex::operator= (const double& v) +{ + x = v; + y = 0.0; + return *this; +} + +alglib::complex& alglib::complex::operator+=(const double& v) +{ + x += v; + return *this; +} + +alglib::complex& alglib::complex::operator-=(const double& v) +{ + x -= v; + return *this; +} + +alglib::complex& alglib::complex::operator*=(const double& v) +{ + x *= v; + y *= v; + return *this; +} + +alglib::complex& alglib::complex::operator/=(const double& v) +{ + x /= v; + y /= v; + return *this; +} + +alglib::complex& alglib::complex::operator= (const alglib::complex& z) +{ + x = z.x; + y = z.y; + return *this; +} + +alglib::complex& alglib::complex::operator+=(const alglib::complex& z) +{ + x += z.x; + y += z.y; + return *this; +} + +alglib::complex& alglib::complex::operator-=(const alglib::complex& z) +{ + x -= z.x; + y -= z.y; + return *this; +} + +alglib::complex& alglib::complex::operator*=(const alglib::complex& z) +{ + double t = x*z.x-y*z.y; + y = x*z.y+y*z.x; + x = t; + return *this; +} + +alglib::complex& alglib::complex::operator/=(const alglib::complex& z) +{ + alglib::complex result; + double e; + double f; + if( fabs(z.y)=0 ? _dps : -_dps; + if( dps<=0 || dps>=20 ) + throw ap_error("complex::tostring(): incorrect dps"); + + // handle IEEE special quantities + if( fp_isnan(x) || fp_isnan(y) ) + return "NAN"; + if( fp_isinf(x) || fp_isinf(y) ) + return "INF"; + + // generate mask + if( sprintf(mask, "%%.%d%s", dps, _dps>=0 ? "f" : "e")>=(int)sizeof(mask) ) + throw ap_error("complex::tostring(): buffer overflow"); + + // print |x|, |y| and zero with same mask and compare + if( sprintf(buf_x, mask, (double)(fabs(x)))>=(int)sizeof(buf_x) ) + throw ap_error("complex::tostring(): buffer overflow"); + if( sprintf(buf_y, mask, (double)(fabs(y)))>=(int)sizeof(buf_y) ) + throw ap_error("complex::tostring(): buffer overflow"); + if( sprintf(buf_zero, mask, (double)0)>=(int)sizeof(buf_zero) ) + throw ap_error("complex::tostring(): buffer overflow"); + + // different zero/nonzero patterns + if( strcmp(buf_x,buf_zero)!=0 && strcmp(buf_y,buf_zero)!=0 ) + return std::string(x>0 ? "" : "-")+buf_x+(y>0 ? "+" : "-")+buf_y+"i"; + if( strcmp(buf_x,buf_zero)!=0 && strcmp(buf_y,buf_zero)==0 ) + return std::string(x>0 ? "" : "-")+buf_x; + if( strcmp(buf_x,buf_zero)==0 && strcmp(buf_y,buf_zero)!=0 ) + return std::string(y>0 ? "" : "-")+buf_y+"i"; + return std::string("0"); +} + +const bool alglib::operator==(const alglib::complex& lhs, const alglib::complex& rhs) +{ + volatile double x1 = lhs.x; + volatile double x2 = rhs.x; + volatile double y1 = lhs.y; + volatile double y2 = rhs.y; + return x1==x2 && y1==y2; +} + +const bool alglib::operator!=(const alglib::complex& lhs, const alglib::complex& rhs) +{ return !(lhs==rhs); } + +const alglib::complex alglib::operator+(const alglib::complex& lhs) +{ return lhs; } + +const alglib::complex alglib::operator-(const alglib::complex& lhs) +{ return alglib::complex(-lhs.x, -lhs.y); } + +const alglib::complex alglib::operator+(const alglib::complex& lhs, const alglib::complex& rhs) +{ alglib::complex r = lhs; r += rhs; return r; } + +const alglib::complex alglib::operator+(const alglib::complex& lhs, const double& rhs) +{ alglib::complex r = lhs; r += rhs; return r; } + +const alglib::complex alglib::operator+(const double& lhs, const alglib::complex& rhs) +{ alglib::complex r = rhs; r += lhs; return r; } + +const alglib::complex alglib::operator-(const alglib::complex& lhs, const alglib::complex& rhs) +{ alglib::complex r = lhs; r -= rhs; return r; } + +const alglib::complex alglib::operator-(const alglib::complex& lhs, const double& rhs) +{ alglib::complex r = lhs; r -= rhs; return r; } + +const alglib::complex alglib::operator-(const double& lhs, const alglib::complex& rhs) +{ alglib::complex r = lhs; r -= rhs; return r; } + +const alglib::complex alglib::operator*(const alglib::complex& lhs, const alglib::complex& rhs) +{ return alglib::complex(lhs.x*rhs.x - lhs.y*rhs.y, lhs.x*rhs.y + lhs.y*rhs.x); } + +const alglib::complex alglib::operator*(const alglib::complex& lhs, const double& rhs) +{ return alglib::complex(lhs.x*rhs, lhs.y*rhs); } + +const alglib::complex alglib::operator*(const double& lhs, const alglib::complex& rhs) +{ return alglib::complex(lhs*rhs.x, lhs*rhs.y); } + +const alglib::complex alglib::operator/(const alglib::complex& lhs, const alglib::complex& rhs) +{ + alglib::complex result; + double e; + double f; + if( fabs(rhs.y)yabs ? xabs : yabs; + v = xabsx; + v0y = -v0->y; + v1x = v1->x; + v1y = -v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( !bconj0 && bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = v0->y; + v1x = v1->x; + v1y = -v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( bconj0 && !bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = -v0->y; + v1x = v1->x; + v1y = v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + if( !bconj0 && !bconj1 ) + { + double v0x, v0y, v1x, v1y; + for(i=0; ix; + v0y = v0->y; + v1x = v1->x; + v1y = v1->y; + rx += v0x*v1x-v0y*v1y; + ry += v0x*v1y+v0y*v1x; + } + } + return alglib::complex(rx,ry); +} + +alglib::complex alglib::vdotproduct(const alglib::complex *v1, const alglib::complex *v2, ae_int_t N) +{ + return vdotproduct(v1, 1, "N", v2, 1, "N", N); +} + +void alglib::vmove(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n) +{ + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix = vsrc->x; + vdst->y = -vsrc->y; + } + } + else + { + for(i=0; ix = vsrc->x; + vdst->y = -vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = -vsrc->y; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + for(i=0; ix = -vsrc->x; + vdst->y = vsrc->y; + } + } + else + { + for(i=0; ix = -vsrc->x; + vdst->y = -vsrc->y; + } + } + } +} + +void alglib::vmoveneg(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N) +{ + vmoveneg(vdst, 1, vsrc, 1, "N", N); +} + +void alglib::vmove(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n, double alpha) +{ + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix = alpha*vsrc->x; + vdst->y = -alpha*vsrc->y; + } + } + else + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = alpha*vsrc->y; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = -alpha*vsrc->y; + } + } + else + { + for(i=0; ix = alpha*vsrc->x; + vdst->y = alpha*vsrc->y; + } + } + } +} + +void alglib::vmove(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, double alpha) +{ + vmove(vdst, 1, vsrc, 1, "N", N, alpha); +} + +void alglib::vmove(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha) +{ + bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + if( bconj ) + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x+ay*vsrc->y; + vdst->y = -ax*vsrc->y+ay*vsrc->x; + } + } + else + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x-ay*vsrc->y; + vdst->y = ax*vsrc->y+ay*vsrc->x; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x+ay*vsrc->y; + vdst->y = -ax*vsrc->y+ay*vsrc->x; + } + } + else + { + double ax = alpha.x, ay = alpha.y; + for(i=0; ix = ax*vsrc->x-ay*vsrc->y; + vdst->y = ax*vsrc->y+ay*vsrc->x; + } + } + } +} + +void alglib::vmove(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, alglib::complex alpha) +{ + vmove(vdst, 1, vsrc, 1, "N", N, alpha); +} + +void alglib::vadd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n) +{ + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix += vsrc->x; + vdst->y -= vsrc->y; + } + } + else + { + for(i=0; ix += vsrc->x; + vdst->y += vsrc->y; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + for(i=0; ix += vsrc->x; + vdst->y -= vsrc->y; + } + } + else + { + for(i=0; ix += vsrc->x; + vdst->y += vsrc->y; + } + } + } +} + +void alglib::vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N) +{ + vadd(vdst, 1, vsrc, 1, "N", N); +} + +void alglib::vadd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha) +{ + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix += alpha*vsrc->x; + vdst->y -= alpha*vsrc->y; + } + } + else + { + for(i=0; ix += alpha*vsrc->x; + vdst->y += alpha*vsrc->y; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + for(i=0; ix += alpha*vsrc->x; + vdst->y -= alpha*vsrc->y; + } + } + else + { + for(i=0; ix += alpha*vsrc->x; + vdst->y += alpha*vsrc->y; + } + } + } +} + +void alglib::vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, double alpha) +{ + vadd(vdst, 1, vsrc, 1, "N", N, alpha); +} + +void alglib::vadd(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha) +{ + bool bconj = !((conj_src[0]=='N') || (conj_src[0]=='n')); + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + double ax = alpha.x, ay = alpha.y; + if( bconj ) + { + for(i=0; ix += ax*vsrc->x+ay*vsrc->y; + vdst->y -= ax*vsrc->y-ay*vsrc->x; + } + } + else + { + for(i=0; ix += ax*vsrc->x-ay*vsrc->y; + vdst->y += ax*vsrc->y+ay*vsrc->x; + } + } + } + else + { + // + // optimized case + // + double ax = alpha.x, ay = alpha.y; + if( bconj ) + { + for(i=0; ix += ax*vsrc->x+ay*vsrc->y; + vdst->y -= ax*vsrc->y-ay*vsrc->x; + } + } + else + { + for(i=0; ix += ax*vsrc->x-ay*vsrc->y; + vdst->y += ax*vsrc->y+ay*vsrc->x; + } + } + } +} + +void alglib::vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, alglib::complex alpha) +{ + vadd(vdst, 1, vsrc, 1, "N", N, alpha); +} + +void alglib::vsub(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n) +{ + ae_int_t i; + if( stride_dst!=1 || stride_src!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix -= vsrc->x; + vdst->y += vsrc->y; + } + } + else + { + for(i=0; ix -= vsrc->x; + vdst->y -= vsrc->y; + } + } + } + else + { + // + // optimized case + // + if( bconj ) + { + for(i=0; ix -= vsrc->x; + vdst->y += vsrc->y; + } + } + else + { + for(i=0; ix -= vsrc->x; + vdst->y -= vsrc->y; + } + } + } +} + +void alglib::vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N) +{ + vsub(vdst, 1, vsrc, 1, "N", N); +} + +void alglib::vsub(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha) +{ + vadd(vdst, stride_dst, vsrc, stride_src, n, -alpha); +} + +void alglib::vsub(double *vdst, const double *vsrc, ae_int_t N, double alpha) +{ + vadd(vdst, 1, vsrc, 1, N, -alpha); +} + +void alglib::vsub(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha) +{ + vadd(vdst, stride_dst, vsrc, stride_src, conj_src, n, -alpha); +} + +void alglib::vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t n, double alpha) +{ + vadd(vdst, 1, vsrc, 1, "N", n, -alpha); +} + +void alglib::vsub(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha) +{ + vadd(vdst, stride_dst, vsrc, stride_src, conj_src, n, -alpha); +} + +void alglib::vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t n, alglib::complex alpha) +{ + vadd(vdst, 1, vsrc, 1, "N", n, -alpha); +} +void alglib::vmul(double *vdst, ae_int_t stride_dst, ae_int_t n, double alpha) +{ + ae_int_t i; + if( stride_dst!=1 ) + { + // + // general unoptimized case + // + for(i=0; ix *= alpha; + vdst->y *= alpha; + } + } + else + { + // + // optimized case + // + for(i=0; ix *= alpha; + vdst->y *= alpha; + } + } +} + +void alglib::vmul(alglib::complex *vdst, ae_int_t N, double alpha) +{ + vmul(vdst, 1, N, alpha); +} + +void alglib::vmul(alglib::complex *vdst, ae_int_t stride_dst, ae_int_t n, alglib::complex alpha) +{ + ae_int_t i; + if( stride_dst!=1 ) + { + // + // general unoptimized case + // + double ax = alpha.x, ay = alpha.y; + for(i=0; ix, dsty = vdst->y; + vdst->x = ax*dstx-ay*dsty; + vdst->y = ax*dsty+ay*dstx; + } + } + else + { + // + // optimized case + // + double ax = alpha.x, ay = alpha.y; + for(i=0; ix, dsty = vdst->y; + vdst->x = ax*dstx-ay*dsty; + vdst->y = ax*dsty+ay*dstx; + } + } +} + +void alglib::vmul(alglib::complex *vdst, ae_int_t N, alglib::complex alpha) +{ + vmul(vdst, 1, N, alpha); +} + + +/******************************************************************** +Matrices and vectors +********************************************************************/ +alglib::ae_vector_wrapper::ae_vector_wrapper() +{ + p_vec = NULL; +} + +alglib::ae_vector_wrapper::~ae_vector_wrapper() +{ + if( p_vec==&vec ) + ae_vector_clear(p_vec); +} + +void alglib::ae_vector_wrapper::setlength(ae_int_t iLen) +{ + if( p_vec==NULL ) + throw alglib::ap_error("ALGLIB: setlength() error, p_vec==NULL (array was not correctly initialized)"); + if( p_vec!=&vec ) + throw alglib::ap_error("ALGLIB: setlength() error, p_vec!=&vec (attempt to resize frozen array)"); + if( !ae_vector_set_length(p_vec, iLen, NULL) ) + throw alglib::ap_error("ALGLIB: malloc error"); +} + +alglib::ae_int_t alglib::ae_vector_wrapper::length() const +{ + if( p_vec==NULL ) + return 0; + return p_vec->cnt; +} + +void alglib::ae_vector_wrapper::attach_to(alglib_impl::ae_vector *ptr) +{ + if( ptr==&vec ) + throw alglib::ap_error("ALGLIB: attempt to attach vector to itself"); + if( p_vec==&vec ) + ae_vector_clear(p_vec); + p_vec = ptr; +} + +void alglib::ae_vector_wrapper::allocate_own(ae_int_t size, alglib_impl::ae_datatype datatype) +{ + if( p_vec==&vec ) + ae_vector_clear(p_vec); + p_vec = &vec; + if( !ae_vector_init(p_vec, size, datatype, NULL, false) ) + throw alglib::ap_error("ALGLIB: malloc error"); +} + +const alglib_impl::ae_vector* alglib::ae_vector_wrapper::c_ptr() const +{ + return p_vec; +} + +alglib_impl::ae_vector* alglib::ae_vector_wrapper::c_ptr() +{ + return p_vec; +} + +void alglib::ae_vector_wrapper::create(const alglib::ae_vector_wrapper &rhs) +{ + if( rhs.p_vec!=NULL ) + { + p_vec = &vec; + if( !ae_vector_init_copy(p_vec, rhs.p_vec, NULL, ae_false) ) + throw alglib::ap_error("ALGLIB: malloc error!"); + } + else + p_vec = NULL; +} + +void alglib::ae_vector_wrapper::create(const char *s, alglib_impl::ae_datatype datatype) +{ + std::vector svec; + size_t i; + char *p = filter_spaces(s); + try + { + str_vector_create(p, true, &svec); + allocate_own((ae_int_t)(svec.size()), datatype); + for(i=0; iptr.p_bool[i] = parse_bool_delim(svec[i],",]"); + if( datatype==alglib_impl::DT_INT ) + p_vec->ptr.p_int[i] = parse_int_delim(svec[i],",]"); + if( datatype==alglib_impl::DT_REAL ) + p_vec->ptr.p_double[i] = parse_real_delim(svec[i],",]"); + if( datatype==alglib_impl::DT_COMPLEX ) + { + alglib::complex t = parse_complex_delim(svec[i],",]"); + p_vec->ptr.p_complex[i].x = t.x; + p_vec->ptr.p_complex[i].y = t.y; + } + } + alglib_impl::ae_free(p); + } + catch(...) + { + alglib_impl::ae_free(p); + throw; + } +} + +void alglib::ae_vector_wrapper::assign(const alglib::ae_vector_wrapper &rhs) +{ + if( this==&rhs ) + return; + if( p_vec==&vec || p_vec==NULL ) + { + // + // Assignment to non-proxy object + // + ae_vector_clear(p_vec); + if( rhs.p_vec!=NULL ) + { + p_vec = &vec; + if( !ae_vector_init_copy(p_vec, rhs.p_vec, NULL, ae_false) ) + throw alglib::ap_error("ALGLIB: malloc error!"); + } + else + p_vec = NULL; + } + else + { + // + // Assignment to proxy object + // + if( rhs.p_vec==NULL ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (sizes dont match)"); + if( rhs.p_vec->datatype!=p_vec->datatype ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (types dont match)"); + if( rhs.p_vec->cnt!=p_vec->cnt ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (sizes dont match)"); + memcpy(p_vec->ptr.p_ptr, rhs.p_vec->ptr.p_ptr, p_vec->cnt*alglib_impl::ae_sizeof(p_vec->datatype)); + } +} + +alglib::boolean_1d_array::boolean_1d_array() +{ + allocate_own(0, alglib_impl::DT_BOOL); +} + +alglib::boolean_1d_array::boolean_1d_array(const char *s) +{ + create(s, alglib_impl::DT_BOOL); +} + +alglib::boolean_1d_array::boolean_1d_array(const alglib::boolean_1d_array &rhs) +{ + create(rhs); +} + +alglib::boolean_1d_array::boolean_1d_array(alglib_impl::ae_vector *p) +{ + p_vec = NULL; + attach_to(p); +} + +const alglib::boolean_1d_array& alglib::boolean_1d_array::operator=(const alglib::boolean_1d_array &rhs) +{ + assign(rhs); + return *this; +} + +alglib::boolean_1d_array::~boolean_1d_array() +{ +} + +const ae_bool& alglib::boolean_1d_array::operator()(ae_int_t i) const +{ + return p_vec->ptr.p_bool[i]; +} + +ae_bool& alglib::boolean_1d_array::operator()(ae_int_t i) +{ + return p_vec->ptr.p_bool[i]; +} + +const ae_bool& alglib::boolean_1d_array::operator[](ae_int_t i) const +{ + return p_vec->ptr.p_bool[i]; +} + +ae_bool& alglib::boolean_1d_array::operator[](ae_int_t i) +{ + return p_vec->ptr.p_bool[i]; +} + +void alglib::boolean_1d_array::setcontent(ae_int_t iLen, const bool *pContent ) +{ + ae_int_t i; + setlength(iLen); + for(i=0; iptr.p_bool[i] = pContent[i]; +} + +ae_bool* alglib::boolean_1d_array::getcontent() +{ + return p_vec->ptr.p_bool; +} + +const ae_bool* alglib::boolean_1d_array::getcontent() const +{ + return p_vec->ptr.p_bool; +} + +std::string alglib::boolean_1d_array::tostring() const +{ + if( length()==0 ) + return "[]"; + return arraytostring(&(operator()(0)), length()); +} + +alglib::integer_1d_array::integer_1d_array() +{ + allocate_own(0, alglib_impl::DT_INT); +} + +alglib::integer_1d_array::integer_1d_array(alglib_impl::ae_vector *p) +{ + p_vec = NULL; + attach_to(p); +} + +alglib::integer_1d_array::integer_1d_array(const char *s) +{ + create(s, alglib_impl::DT_INT); +} + +alglib::integer_1d_array::integer_1d_array(const alglib::integer_1d_array &rhs) +{ + create(rhs); +} + +const alglib::integer_1d_array& alglib::integer_1d_array::operator=(const alglib::integer_1d_array &rhs) +{ + assign(rhs); + return *this; +} + +alglib::integer_1d_array::~integer_1d_array() +{ +} + +const alglib::ae_int_t& alglib::integer_1d_array::operator()(ae_int_t i) const +{ + return p_vec->ptr.p_int[i]; +} + +alglib::ae_int_t& alglib::integer_1d_array::operator()(ae_int_t i) +{ + return p_vec->ptr.p_int[i]; +} + +const alglib::ae_int_t& alglib::integer_1d_array::operator[](ae_int_t i) const +{ + return p_vec->ptr.p_int[i]; +} + +alglib::ae_int_t& alglib::integer_1d_array::operator[](ae_int_t i) +{ + return p_vec->ptr.p_int[i]; +} + +void alglib::integer_1d_array::setcontent(ae_int_t iLen, const ae_int_t *pContent ) +{ + ae_int_t i; + setlength(iLen); + for(i=0; iptr.p_int[i] = pContent[i]; +} + +alglib::ae_int_t* alglib::integer_1d_array::getcontent() +{ + return p_vec->ptr.p_int; +} + +const alglib::ae_int_t* alglib::integer_1d_array::getcontent() const +{ + return p_vec->ptr.p_int; +} + +std::string alglib::integer_1d_array::tostring() const +{ + if( length()==0 ) + return "[]"; + return arraytostring(&operator()(0), length()); +} + +alglib::real_1d_array::real_1d_array() +{ + allocate_own(0, alglib_impl::DT_REAL); +} + +alglib::real_1d_array::real_1d_array(alglib_impl::ae_vector *p) +{ + p_vec = NULL; + attach_to(p); +} + +alglib::real_1d_array::real_1d_array(const char *s) +{ + create(s, alglib_impl::DT_REAL); +} + +alglib::real_1d_array::real_1d_array(const alglib::real_1d_array &rhs) +{ + create(rhs); +} + +const alglib::real_1d_array& alglib::real_1d_array::operator=(const alglib::real_1d_array &rhs) +{ + assign(rhs); + return *this; +} + +alglib::real_1d_array::~real_1d_array() +{ +} + +const double& alglib::real_1d_array::operator()(ae_int_t i) const +{ + return p_vec->ptr.p_double[i]; +} + +double& alglib::real_1d_array::operator()(ae_int_t i) +{ + return p_vec->ptr.p_double[i]; +} + +const double& alglib::real_1d_array::operator[](ae_int_t i) const +{ + return p_vec->ptr.p_double[i]; +} + +double& alglib::real_1d_array::operator[](ae_int_t i) +{ + return p_vec->ptr.p_double[i]; +} + +void alglib::real_1d_array::setcontent(ae_int_t iLen, const double *pContent ) +{ + ae_int_t i; + setlength(iLen); + for(i=0; iptr.p_double[i] = pContent[i]; +} + +double* alglib::real_1d_array::getcontent() +{ + return p_vec->ptr.p_double; +} + +const double* alglib::real_1d_array::getcontent() const +{ + return p_vec->ptr.p_double; +} + +std::string alglib::real_1d_array::tostring(int dps) const +{ + if( length()==0 ) + return "[]"; + return arraytostring(&operator()(0), length(), dps); +} + +alglib::complex_1d_array::complex_1d_array() +{ + allocate_own(0, alglib_impl::DT_COMPLEX); +} + +alglib::complex_1d_array::complex_1d_array(alglib_impl::ae_vector *p) +{ + p_vec = NULL; + attach_to(p); +} + +alglib::complex_1d_array::complex_1d_array(const char *s) +{ + create(s, alglib_impl::DT_COMPLEX); +} + +alglib::complex_1d_array::complex_1d_array(const alglib::complex_1d_array &rhs) +{ + create(rhs); +} + +const alglib::complex_1d_array& alglib::complex_1d_array::operator=(const alglib::complex_1d_array &rhs) +{ + assign(rhs); + return *this; +} + +alglib::complex_1d_array::~complex_1d_array() +{ +} + +const alglib::complex& alglib::complex_1d_array::operator()(ae_int_t i) const +{ + return *((const alglib::complex*)(p_vec->ptr.p_complex+i)); +} + +alglib::complex& alglib::complex_1d_array::operator()(ae_int_t i) +{ + return *((alglib::complex*)(p_vec->ptr.p_complex+i)); +} + +const alglib::complex& alglib::complex_1d_array::operator[](ae_int_t i) const +{ + return *((const alglib::complex*)(p_vec->ptr.p_complex+i)); +} + +alglib::complex& alglib::complex_1d_array::operator[](ae_int_t i) +{ + return *((alglib::complex*)(p_vec->ptr.p_complex+i)); +} + +void alglib::complex_1d_array::setcontent(ae_int_t iLen, const alglib::complex *pContent ) +{ + ae_int_t i; + setlength(iLen); + for(i=0; iptr.p_complex[i].x = pContent[i].x; + p_vec->ptr.p_complex[i].y = pContent[i].y; + } +} + + alglib::complex* alglib::complex_1d_array::getcontent() +{ + return (alglib::complex*)p_vec->ptr.p_complex; +} + +const alglib::complex* alglib::complex_1d_array::getcontent() const +{ + return (const alglib::complex*)p_vec->ptr.p_complex; +} + +std::string alglib::complex_1d_array::tostring(int dps) const +{ + if( length()==0 ) + return "[]"; + return arraytostring(&operator()(0), length(), dps); +} + +alglib::ae_matrix_wrapper::ae_matrix_wrapper() +{ + p_mat = NULL; +} + +alglib::ae_matrix_wrapper::~ae_matrix_wrapper() +{ + if( p_mat==&mat ) + ae_matrix_clear(p_mat); +} + +const alglib::ae_matrix_wrapper& alglib::ae_matrix_wrapper::operator=(const alglib::ae_matrix_wrapper &rhs) +{ + assign(rhs); + return *this; +} + +void alglib::ae_matrix_wrapper::create(const ae_matrix_wrapper &rhs) +{ + if( rhs.p_mat!=NULL ) + { + p_mat = &mat; + if( !ae_matrix_init_copy(p_mat, rhs.p_mat, NULL, ae_false) ) + throw alglib::ap_error("ALGLIB: malloc error!"); + } + else + p_mat = NULL; +} + +void alglib::ae_matrix_wrapper::create(const char *s, alglib_impl::ae_datatype datatype) +{ + std::vector< std::vector > smat; + size_t i, j; + char *p = filter_spaces(s); + try + { + str_matrix_create(p, &smat); + if( smat.size()!=0 ) + { + allocate_own((ae_int_t)(smat.size()), (ae_int_t)(smat[0].size()), datatype); + for(i=0; iptr.pp_bool[i][j] = parse_bool_delim(smat[i][j],",]"); + if( datatype==alglib_impl::DT_INT ) + p_mat->ptr.pp_int[i][j] = parse_int_delim(smat[i][j],",]"); + if( datatype==alglib_impl::DT_REAL ) + p_mat->ptr.pp_double[i][j] = parse_real_delim(smat[i][j],",]"); + if( datatype==alglib_impl::DT_COMPLEX ) + { + alglib::complex t = parse_complex_delim(smat[i][j],",]"); + p_mat->ptr.pp_complex[i][j].x = t.x; + p_mat->ptr.pp_complex[i][j].y = t.y; + } + } + } + else + allocate_own(0, 0, datatype); + alglib_impl::ae_free(p); + } + catch(...) + { + alglib_impl::ae_free(p); + throw; + } +} + +void alglib::ae_matrix_wrapper::assign(const alglib::ae_matrix_wrapper &rhs) +{ + if( this==&rhs ) + return; + if( p_mat==&mat || p_mat==NULL ) + { + // + // Assignment to non-proxy object + // + ae_matrix_clear(p_mat); + if( rhs.p_mat!=NULL ) + { + p_mat = &mat; + if( !ae_matrix_init_copy(p_mat, rhs.p_mat, NULL, ae_false) ) + throw alglib::ap_error("ALGLIB: malloc error!"); + } + else + p_mat = NULL; + } + else + { + // + // Assignment to proxy object + // + ae_int_t i; + if( rhs.p_mat==NULL ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (sizes dont match)"); + if( rhs.p_mat->datatype!=p_mat->datatype ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (types dont match)"); + if( rhs.p_mat->rows!=p_mat->rows ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (sizes dont match)"); + if( rhs.p_mat->cols!=p_mat->cols ) + throw alglib::ap_error("ALGLIB: incorrect assignment to array (sizes dont match)"); + for(i=0; irows; i++) + memcpy(p_mat->ptr.pp_void[i], rhs.p_mat->ptr.pp_void[i], p_mat->cols*alglib_impl::ae_sizeof(p_mat->datatype)); + } +} + +void alglib::ae_matrix_wrapper::setlength(ae_int_t rows, ae_int_t cols) +{ + if( p_mat==NULL ) + throw alglib::ap_error("ALGLIB: setlength() error, p_mat==NULL (array was not correctly initialized)"); + if( p_mat!=&mat ) + throw alglib::ap_error("ALGLIB: setlength() error, p_mat!=&mat (attempt to resize frozen array)"); + if( !ae_matrix_set_length(p_mat, rows, cols, NULL) ) + throw alglib::ap_error("ALGLIB: malloc error"); +} + +alglib::ae_int_t alglib::ae_matrix_wrapper::rows() const +{ + if( p_mat==NULL ) + return 0; + return p_mat->rows; +} + +alglib::ae_int_t alglib::ae_matrix_wrapper::cols() const +{ + if( p_mat==NULL ) + return 0; + return p_mat->cols; +} + +bool alglib::ae_matrix_wrapper::isempty() const +{ + return rows()==0 || cols()==0; +} + +alglib::ae_int_t alglib::ae_matrix_wrapper::getstride() const +{ + if( p_mat==NULL ) + return 0; + return p_mat->stride; +} + +void alglib::ae_matrix_wrapper::attach_to(alglib_impl::ae_matrix *ptr) +{ + if( ptr==&mat ) + throw alglib::ap_error("ALGLIB: attempt to attach matrix to itself"); + if( p_mat==&mat ) + ae_matrix_clear(p_mat); + p_mat = ptr; +} + +void alglib::ae_matrix_wrapper::allocate_own(ae_int_t rows, ae_int_t cols, alglib_impl::ae_datatype datatype) +{ + if( p_mat==&mat ) + ae_matrix_clear(p_mat); + p_mat = &mat; + if( !ae_matrix_init(p_mat, rows, cols, datatype, NULL, false) ) + throw alglib::ap_error("ALGLIB: malloc error"); +} + +const alglib_impl::ae_matrix* alglib::ae_matrix_wrapper::c_ptr() const +{ + return p_mat; +} + +alglib_impl::ae_matrix* alglib::ae_matrix_wrapper::c_ptr() +{ + return p_mat; +} + +alglib::boolean_2d_array::boolean_2d_array() +{ + allocate_own(0, 0, alglib_impl::DT_BOOL); +} + +alglib::boolean_2d_array::boolean_2d_array(const alglib::boolean_2d_array &rhs) +{ + create(rhs); +} + +alglib::boolean_2d_array::boolean_2d_array(alglib_impl::ae_matrix *p) +{ + p_mat = NULL; + attach_to(p); +} + +alglib::boolean_2d_array::boolean_2d_array(const char *s) +{ + create(s, alglib_impl::DT_BOOL); +} + +alglib::boolean_2d_array::~boolean_2d_array() +{ +} + +const ae_bool& alglib::boolean_2d_array::operator()(ae_int_t i, ae_int_t j) const +{ + return p_mat->ptr.pp_bool[i][j]; +} + +ae_bool& alglib::boolean_2d_array::operator()(ae_int_t i, ae_int_t j) +{ + return p_mat->ptr.pp_bool[i][j]; +} + +const ae_bool* alglib::boolean_2d_array::operator[](ae_int_t i) const +{ + return p_mat->ptr.pp_bool[i]; +} + +ae_bool* alglib::boolean_2d_array::operator[](ae_int_t i) +{ + return p_mat->ptr.pp_bool[i]; +} + +void alglib::boolean_2d_array::setcontent(ae_int_t irows, ae_int_t icols, const bool *pContent ) +{ + ae_int_t i, j; + setlength(irows, icols); + for(i=0; iptr.pp_bool[i][j] = pContent[i*icols+j]; +} + +std::string alglib::boolean_2d_array::tostring() const +{ + std::string result; + ae_int_t i; + if( isempty() ) + return "[[]]"; + result = "["; + for(i=0; iptr.pp_int[i][j]; +} + +alglib::ae_int_t& alglib::integer_2d_array::operator()(ae_int_t i, ae_int_t j) +{ + return p_mat->ptr.pp_int[i][j]; +} + +const alglib::ae_int_t* alglib::integer_2d_array::operator[](ae_int_t i) const +{ + return p_mat->ptr.pp_int[i]; +} + +alglib::ae_int_t* alglib::integer_2d_array::operator[](ae_int_t i) +{ + return p_mat->ptr.pp_int[i]; +} + +void alglib::integer_2d_array::setcontent(ae_int_t irows, ae_int_t icols, const ae_int_t *pContent ) +{ + ae_int_t i, j; + setlength(irows, icols); + for(i=0; iptr.pp_int[i][j] = pContent[i*icols+j]; +} + +std::string alglib::integer_2d_array::tostring() const +{ + std::string result; + ae_int_t i; + if( isempty() ) + return "[[]]"; + result = "["; + for(i=0; iptr.pp_double[i][j]; +} + +double& alglib::real_2d_array::operator()(ae_int_t i, ae_int_t j) +{ + return p_mat->ptr.pp_double[i][j]; +} + +const double* alglib::real_2d_array::operator[](ae_int_t i) const +{ + return p_mat->ptr.pp_double[i]; +} + +double* alglib::real_2d_array::operator[](ae_int_t i) +{ + return p_mat->ptr.pp_double[i]; +} + +void alglib::real_2d_array::setcontent(ae_int_t irows, ae_int_t icols, const double *pContent ) +{ + ae_int_t i, j; + setlength(irows, icols); + for(i=0; iptr.pp_double[i][j] = pContent[i*icols+j]; +} + +std::string alglib::real_2d_array::tostring(int dps) const +{ + std::string result; + ae_int_t i; + if( isempty() ) + return "[[]]"; + result = "["; + for(i=0; iptr.pp_complex[i]+j)); +} + +alglib::complex& alglib::complex_2d_array::operator()(ae_int_t i, ae_int_t j) +{ + return *((alglib::complex*)(p_mat->ptr.pp_complex[i]+j)); +} + +const alglib::complex* alglib::complex_2d_array::operator[](ae_int_t i) const +{ + return (const alglib::complex*)(p_mat->ptr.pp_complex[i]); +} + +alglib::complex* alglib::complex_2d_array::operator[](ae_int_t i) +{ + return (alglib::complex*)(p_mat->ptr.pp_complex[i]); +} + +void alglib::complex_2d_array::setcontent(ae_int_t irows, ae_int_t icols, const alglib::complex *pContent ) +{ + ae_int_t i, j; + setlength(irows, icols); + for(i=0; iptr.pp_complex[i][j].x = pContent[i*icols+j].x; + p_mat->ptr.pp_complex[i][j].y = pContent[i*icols+j].y; + } +} + +std::string alglib::complex_2d_array::tostring(int dps) const +{ + std::string result; + ae_int_t i; + if( isempty() ) + return "[[]]"; + result = "["; + for(i=0; ic2 ) + return +1; + } +} + +char* alglib::filter_spaces(const char *s) +{ + size_t i, n; + char *r; + char *r0; + n = strlen(s); + r = (char*)alglib_impl::ae_malloc(n+1, NULL); + if( r==NULL ) + throw ap_error("malloc error"); + for(i=0,r0=r; i<=n; i++,s++) + if( !isspace(*s) ) + { + *r0 = *s; + r0++; + } + return r; +} + +void alglib::str_vector_create(const char *src, bool match_head_only, std::vector *p_vec) +{ + // + // parse beginning of the string. + // try to handle "[]" string + // + p_vec->clear(); + if( *src!='[' ) + throw alglib::ap_error("Incorrect initializer for vector"); + src++; + if( *src==']' ) + return; + p_vec->push_back(src); + for(;;) + { + if( *src==0 ) + throw alglib::ap_error("Incorrect initializer for vector"); + if( *src==']' ) + { + if( src[1]==0 || !match_head_only) + return; + throw alglib::ap_error("Incorrect initializer for vector"); + } + if( *src==',' ) + { + p_vec->push_back(src+1); + src++; + continue; + } + src++; + } +} + +void alglib::str_matrix_create(const char *src, std::vector< std::vector > *p_mat) +{ + p_mat->clear(); + + // + // Try to handle "[[]]" string + // + if( strcmp(src, "[[]]")==0 ) + return; + + // + // Parse non-empty string + // + if( *src!='[' ) + throw alglib::ap_error("Incorrect initializer for matrix"); + src++; + for(;;) + { + p_mat->push_back(std::vector()); + str_vector_create(src, false, &p_mat->back()); + if( p_mat->back().size()==0 || p_mat->back().size()!=(*p_mat)[0].size() ) + throw alglib::ap_error("Incorrect initializer for matrix"); + src = strchr(src, ']'); + if( src==NULL ) + throw alglib::ap_error("Incorrect initializer for matrix"); + src++; + if( *src==',' ) + { + src++; + continue; + } + if( *src==']' ) + break; + throw alglib::ap_error("Incorrect initializer for matrix"); + } + src++; + if( *src!=0 ) + throw alglib::ap_error("Incorrect initializer for matrix"); +} + +ae_bool alglib::parse_bool_delim(const char *s, const char *delim) +{ + const char *p; + char buf[8]; + + // try to parse false + p = "false"; + memset(buf, 0, sizeof(buf)); + strncpy(buf, s, strlen(p)); + if( my_stricmp(buf, p)==0 ) + { + if( s[strlen(p)]==0 || strchr(delim,s[strlen(p)])==NULL ) + throw alglib::ap_error("Cannot parse value"); + return ae_false; + } + + // try to parse true + p = "true"; + memset(buf, 0, sizeof(buf)); + strncpy(buf, s, strlen(p)); + if( my_stricmp(buf, p)==0 ) + { + if( s[strlen(p)]==0 || strchr(delim,s[strlen(p)])==NULL ) + throw alglib::ap_error("Cannot parse value"); + return ae_true; + } + + // error + throw alglib::ap_error("Cannot parse value"); +} + +alglib::ae_int_t alglib::parse_int_delim(const char *s, const char *delim) +{ + const char *p; + long long_val; + volatile ae_int_t ae_val; + + p = s; + + // + // check string structure: + // * leading sign + // * at least one digit + // * delimiter + // + if( *s=='-' || *s=='+' ) + s++; + if( *s==0 || strchr("1234567890",*s)==NULL) + throw alglib::ap_error("Cannot parse value"); + while( *s!=0 && strchr("1234567890",*s)!=NULL ) + s++; + if( *s==0 || strchr(delim,*s)==NULL ) + throw alglib::ap_error("Cannot parse value"); + + // convert and ensure that value fits into ae_int_t + s = p; + long_val = atol(s); + ae_val = long_val; + if( ae_val!=long_val ) + throw alglib::ap_error("Cannot parse value"); + return ae_val; +} + +bool alglib::_parse_real_delim(const char *s, const char *delim, double *result, const char **new_s) +{ + const char *p; + char *t; + bool has_digits; + char buf[64]; + int isign; + lconv *loc; + + p = s; + + // + // check string structure and decide what to do + // + isign = 1; + if( *s=='-' || *s=='+' ) + { + isign = *s=='-' ? -1 : +1; + s++; + } + memset(buf, 0, sizeof(buf)); + strncpy(buf, s, 3); + if( my_stricmp(buf,"nan")!=0 && my_stricmp(buf,"inf")!=0 ) + { + // + // [sign] [ddd] [.] [ddd] [e|E[sign]ddd] + // + has_digits = false; + if( *s!=0 && strchr("1234567890",*s)!=NULL ) + { + has_digits = true; + while( *s!=0 && strchr("1234567890",*s)!=NULL ) + s++; + } + if( *s=='.' ) + s++; + if( *s!=0 && strchr("1234567890",*s)!=NULL ) + { + has_digits = true; + while( *s!=0 && strchr("1234567890",*s)!=NULL ) + s++; + } + if (!has_digits ) + return false; + if( *s=='e' || *s=='E' ) + { + s++; + if( *s=='-' || *s=='+' ) + s++; + if( *s==0 || strchr("1234567890",*s)==NULL ) + return false; + while( *s!=0 && strchr("1234567890",*s)!=NULL ) + s++; + } + if( *s==0 || strchr(delim,*s)==NULL ) + return false; + *new_s = s; + + // + // finite value conversion + // + if( *new_s-p>=(int)sizeof(buf) ) + return false; + strncpy(buf, p, (size_t)(*new_s-p)); + buf[*new_s-p] = 0; + loc = localeconv(); + t = strchr(buf,'.'); + if( t!=NULL ) + *t = *loc->decimal_point; + *result = atof(buf); + return true; + } + else + { + // + // check delimiter and update *new_s + // + s += 3; + if( *s==0 || strchr(delim,*s)==NULL ) + return false; + *new_s = s; + + // + // NAN, INF conversion + // + if( my_stricmp(buf,"nan")==0 ) + *result = fp_nan; + if( my_stricmp(buf,"inf")==0 ) + *result = isign>0 ? fp_posinf : fp_neginf; + return true; + } +} + +double alglib::parse_real_delim(const char *s, const char *delim) +{ + double result; + const char *new_s; + if( !_parse_real_delim(s, delim, &result, &new_s) ) + throw alglib::ap_error("Cannot parse value"); + return result; +} + +alglib::complex alglib::parse_complex_delim(const char *s, const char *delim) +{ + double d_result; + const char *new_s; + alglib::complex c_result; + + // parse as real value + if( _parse_real_delim(s, delim, &d_result, &new_s) ) + return d_result; + + // parse as "a+bi" or "a-bi" + if( _parse_real_delim(s, "+-", &c_result.x, &new_s) ) + { + s = new_s; + if( !_parse_real_delim(s, "i", &c_result.y, &new_s) ) + throw alglib::ap_error("Cannot parse value"); + s = new_s+1; + if( *s==0 || strchr(delim,*s)==NULL ) + throw alglib::ap_error("Cannot parse value"); + return c_result; + } + + // parse as complex value "bi+a" or "bi-a" + if( _parse_real_delim(s, "i", &c_result.y, &new_s) ) + { + s = new_s+1; + if( *s==0 ) + throw alglib::ap_error("Cannot parse value"); + if( strchr(delim,*s)!=NULL ) + { + c_result.x = 0; + return c_result; + } + if( strchr("+-",*s)!=NULL ) + { + if( !_parse_real_delim(s, delim, &c_result.x, &new_s) ) + throw alglib::ap_error("Cannot parse value"); + return c_result; + } + throw alglib::ap_error("Cannot parse value"); + } + + // error + throw alglib::ap_error("Cannot parse value"); +} + +std::string alglib::arraytostring(const bool *ptr, ae_int_t n) +{ + std::string result; + ae_int_t i; + result = "["; + for(i=0; i=(int)sizeof(buf) ) + throw ap_error("arraytostring(): buffer overflow"); + result += buf; + } + result += "]"; + return result; +} + +std::string alglib::arraytostring(const double *ptr, ae_int_t n, int _dps) +{ + std::string result; + ae_int_t i; + char buf[64]; + char mask1[64]; + char mask2[64]; + int dps = _dps>=0 ? _dps : -_dps; + result = "["; + if( sprintf(mask1, "%%.%d%s", dps, _dps>=0 ? "f" : "e")>=(int)sizeof(mask1) ) + throw ap_error("arraytostring(): buffer overflow"); + if( sprintf(mask2, ",%s", mask1)>=(int)sizeof(mask2) ) + throw ap_error("arraytostring(): buffer overflow"); + for(i=0; i=(int)sizeof(buf) ) + throw ap_error("arraytostring(): buffer overflow"); + } + else if( fp_isnan(ptr[i]) ) + strcpy(buf, i==0 ? "NAN" : ",NAN"); + else if( fp_isposinf(ptr[i]) ) + strcpy(buf, i==0 ? "+INF" : ",+INF"); + else if( fp_isneginf(ptr[i]) ) + strcpy(buf, i==0 ? "-INF" : ",-INF"); + result += buf; + } + result += "]"; + return result; +} + +std::string alglib::arraytostring(const alglib::complex *ptr, ae_int_t n, int dps) +{ + std::string result; + ae_int_t i; + result = "["; + for(i=0; i0 ) return 1; + if( x<0 ) return -1; + return 0; +} + +double alglib::randomreal() +{ +#ifdef AE_DEBUGRNG + return alglib_impl::ae_debugrng()/2147483563.0; +#else + int i1 = rand(); + int i2 = rand(); + double mx = (double)(RAND_MAX)+1.0; + volatile double tmp0 = i2/mx; + volatile double tmp1 = i1+tmp0; + return tmp1/mx; +#endif +} + +alglib::ae_int_t alglib::randominteger(alglib::ae_int_t maxv) +{ +#ifdef AE_DEBUGRNG + return ((alglib::ae_int_t)(alglib_impl::ae_debugrng()-1))%maxv; +#else + return ((alglib::ae_int_t)rand())%maxv; +#endif +} + +int alglib::round(double x) +{ return int(floor(x+0.5)); } + +int alglib::trunc(double x) +{ return int(x>0 ? floor(x) : ceil(x)); } + +int alglib::ifloor(double x) +{ return int(floor(x)); } + +int alglib::iceil(double x) +{ return int(ceil(x)); } + +double alglib::pi() +{ return 3.14159265358979323846; } + +double alglib::sqr(double x) +{ return x*x; } + +int alglib::maxint(int m1, int m2) +{ + return m1>m2 ? m1 : m2; +} + +int alglib::minint(int m1, int m2) +{ + return m1>m2 ? m2 : m1; +} + +double alglib::maxreal(double m1, double m2) +{ + return m1>m2 ? m1 : m2; +} + +double alglib::minreal(double m1, double m2) +{ + return m1>m2 ? m2 : m1; +} + +bool alglib::fp_eq(double v1, double v2) +{ + // IEEE-strict floating point comparison + volatile double x = v1; + volatile double y = v2; + return x==y; +} + +bool alglib::fp_neq(double v1, double v2) +{ + // IEEE-strict floating point comparison + return !fp_eq(v1,v2); +} + +bool alglib::fp_less(double v1, double v2) +{ + // IEEE-strict floating point comparison + volatile double x = v1; + volatile double y = v2; + return xy; +} + +bool alglib::fp_greater_eq(double v1, double v2) +{ + // IEEE-strict floating point comparison + volatile double x = v1; + volatile double y = v2; + return x>=y; +} + +bool alglib::fp_isnan(double x) +{ + return alglib_impl::ae_isnan_stateless(x,endianness); +} + +bool alglib::fp_isposinf(double x) +{ + return alglib_impl::ae_isposinf_stateless(x,endianness); +} + +bool alglib::fp_isneginf(double x) +{ + return alglib_impl::ae_isneginf_stateless(x,endianness); +} + +bool alglib::fp_isinf(double x) +{ + return alglib_impl::ae_isinf_stateless(x,endianness); +} + +bool alglib::fp_isfinite(double x) +{ + return alglib_impl::ae_isfinite_stateless(x,endianness); +} + +/******************************************************************** +Dataset functions +********************************************************************/ +/*bool alglib::readstrings(std::string file, std::list *pOutput) +{ + return readstrings(file, pOutput, ""); +} + +bool alglib::readstrings(std::string file, std::list *pOutput, std::string comment) +{ + std::string cmd, s; + FILE *f; + char buf[32768]; + char *str; + + f = fopen(file.c_str(), "rb"); + if( !f ) + return false; + s = ""; + pOutput->clear(); + while(str=fgets(buf, sizeof(buf), f)) + { + // TODO: read file by small chunks, combine in one large string + if( strlen(str)==0 ) + continue; + + // + // trim trailing newline chars + // + char *eos = str+strlen(str)-1; + if( *eos=='\n' ) + { + *eos = 0; + eos--; + } + if( *eos=='\r' ) + { + *eos = 0; + eos--; + } + s = str; + + // + // skip comments + // + if( comment.length()>0 ) + if( strncmp(s.c_str(), comment.c_str(), comment.length())==0 ) + { + s = ""; + continue; + } + + // + // read data + // + if( s.length()<1 ) + { + fclose(f); + throw alglib::ap_error("internal error in read_strings"); + } + pOutput->push_back(s); + } + fclose(f); + return true; +} + +void alglib::explodestring(std::string s, char sep, std::vector *pOutput) +{ + std::string tmp; + int i; + tmp = ""; + pOutput->clear(); + for(i=0; ipush_back(tmp); + tmp = ""; + } + if( tmp.length()!=0 ) + pOutput->push_back(tmp); +} + +std::string alglib::strtolower(const std::string &s) +{ + std::string r = s; + for(int i=0; i Lines; + std::vector Values, RowsArr, ColsArr, VarsArr, HeadArr; + std::list::iterator i; + std::string s; + int TrnFirst, TrnLast, ValFirst, ValLast, TstFirst, TstLast, LinesRead, j; + + // + // Read data + // + if( pdataset==NULL ) + return false; + if( !readstrings(file, &Lines, "//") ) + return false; + i = Lines.begin(); + *pdataset = dataset(); + + // + // Read header + // + if( i==Lines.end() ) + return false; + s = alglib::xtrim(*i); + alglib::explodestring(s, '#', &HeadArr); + if( HeadArr.size()!=2 ) + return false; + + // + // Rows info + // + alglib::explodestring(alglib::xtrim(HeadArr[0]), ' ', &RowsArr); + if( RowsArr.size()==0 || RowsArr.size()>3 ) + return false; + if( RowsArr.size()==1 ) + { + pdataset->totalsize = atol(RowsArr[0].c_str()); + pdataset->trnsize = pdataset->totalsize; + } + if( RowsArr.size()==2 ) + { + pdataset->trnsize = atol(RowsArr[0].c_str()); + pdataset->tstsize = atol(RowsArr[1].c_str()); + pdataset->totalsize = pdataset->trnsize + pdataset->tstsize; + } + if( RowsArr.size()==3 ) + { + pdataset->trnsize = atol(RowsArr[0].c_str()); + pdataset->valsize = atol(RowsArr[1].c_str()); + pdataset->tstsize = atol(RowsArr[2].c_str()); + pdataset->totalsize = pdataset->trnsize + pdataset->valsize + pdataset->tstsize; + } + if( pdataset->totalsize<=0 || pdataset->trnsize<0 || pdataset->valsize<0 || pdataset->tstsize<0 ) + return false; + TrnFirst = 0; + TrnLast = TrnFirst + pdataset->trnsize; + ValFirst = TrnLast; + ValLast = ValFirst + pdataset->valsize; + TstFirst = ValLast; + TstLast = TstFirst + pdataset->tstsize; + + // + // columns + // + alglib::explodestring(alglib::xtrim(HeadArr[1]), ' ', &ColsArr); + if( ColsArr.size()!=1 && ColsArr.size()!=4 ) + return false; + if( ColsArr.size()==1 ) + { + pdataset->nin = atoi(ColsArr[0].c_str()); + if( pdataset->nin<=0 ) + return false; + } + if( ColsArr.size()==4 ) + { + if( alglib::strtolower(ColsArr[0])!="reg" && alglib::strtolower(ColsArr[0])!="cls" ) + return false; + if( ColsArr[2]!="=>" ) + return false; + pdataset->nin = atol(ColsArr[1].c_str()); + if( pdataset->nin<1 ) + return false; + if( alglib::strtolower(ColsArr[0])=="reg" ) + { + pdataset->nclasses = 0; + pdataset->nout = atol(ColsArr[3].c_str()); + if( pdataset->nout<1 ) + return false; + } + else + { + pdataset->nclasses = atol(ColsArr[3].c_str()); + pdataset->nout = 1; + if( pdataset->nclasses<2 ) + return false; + } + } + + // + // initialize arrays + // + pdataset->all.setlength(pdataset->totalsize, pdataset->nin+pdataset->nout); + if( pdataset->trnsize>0 ) pdataset->trn.setlength(pdataset->trnsize, pdataset->nin+pdataset->nout); + if( pdataset->valsize>0 ) pdataset->val.setlength(pdataset->valsize, pdataset->nin+pdataset->nout); + if( pdataset->tstsize>0 ) pdataset->tst.setlength(pdataset->tstsize, pdataset->nin+pdataset->nout); + + // + // read data + // + for(LinesRead=0, i++; i!=Lines.end() && LinesReadtotalsize; i++, LinesRead++) + { + std::string sss = *i; + alglib::explodestring(alglib::xtrim(*i), ' ', &VarsArr); + if( VarsArr.size()!=pdataset->nin+pdataset->nout ) + return false; + int tmpc = alglib::round(atof(VarsArr[pdataset->nin+pdataset->nout-1].c_str())); + if( pdataset->nclasses>0 && (tmpc<0 || tmpc>=pdataset->nclasses) ) + return false; + for(j=0; jnin+pdataset->nout; j++) + { + pdataset->all(LinesRead,j) = atof(VarsArr[j].c_str()); + if( LinesRead>=TrnFirst && LinesReadtrn(LinesRead-TrnFirst,j) = atof(VarsArr[j].c_str()); + if( LinesRead>=ValFirst && LinesReadval(LinesRead-ValFirst,j) = atof(VarsArr[j].c_str()); + if( LinesRead>=TstFirst && LinesReadtst(LinesRead-TstFirst,j) = atof(VarsArr[j].c_str()); + } + } + if( LinesRead!=pdataset->totalsize ) + return false; + return true; +}*/ + +/* +previous variant +bool alglib::opendataset(std::string file, dataset *pdataset) +{ + std::list Lines; + std::vector Values; + std::list::iterator i; + int nCol, nRow, nSplitted; + int nColumns, nRows; + + // + // Read data + // + if( pdataset==NULL ) + return false; + if( !readstrings(file, &Lines, "//") ) + return false; + i = Lines.begin(); + *pdataset = dataset(); + + // + // Read columns info + // + if( i==Lines.end() ) + return false; + if( sscanf(i->c_str(), " columns = %d %d ", &pdataset->nin, &pdataset->nout)!=2 ) + return false; + if( pdataset->nin<=0 || pdataset->nout==0 || pdataset->nout==-1) + return false; + if( pdataset->nout<0 ) + { + pdataset->nclasses = -pdataset->nout; + pdataset->nout = 1; + pdataset->iscls = true; + } + else + { + pdataset->isreg = true; + } + nColumns = pdataset->nin+pdataset->nout; + i++; + + // + // Read rows info + // + if( i==Lines.end() ) + return false; + if( sscanf(i->c_str(), " rows = %d %d %d ", &pdataset->trnsize, &pdataset->valsize, &pdataset->tstsize)!=3 ) + return false; + if( (pdataset->trnsize<0) || (pdataset->valsize<0) || (pdataset->tstsize<0) ) + return false; + if( (pdataset->trnsize==0) && (pdataset->valsize==0) && (pdataset->tstsize==0) ) + return false; + nRows = pdataset->trnsize+pdataset->valsize+pdataset->tstsize; + pdataset->size = nRows; + if( Lines.size()!=nRows+2 ) + return false; + i++; + + // + // Read all cases + // + alglib::real_2d_array &arr = pdataset->all; + arr.setbounds(0, nRows-1, 0, nColumns-1); + for(nRow=0; nRowiscls && ((round(v)<0) || (round(v)>=pdataset->nclasses)) ) + return false; + if( (nCol==nColumns-1) && pdataset->iscls ) + arr(nRow, nCol) = round(v); + else + arr(nRow, nCol) = v; + } + i++; + } + + // + // Split to training, validation and test sets + // + if( pdataset->trnsize>0 ) + pdataset->trn.setbounds(0, pdataset->trnsize-1, 0, nColumns-1); + if( pdataset->valsize>0 ) + pdataset->val.setbounds(0, pdataset->valsize-1, 0, nColumns-1); + if( pdataset->tstsize>0 ) + pdataset->tst.setbounds(0, pdataset->tstsize-1, 0, nColumns-1); + nSplitted=0; + for(nRow=0; nRow<=pdataset->trnsize-1; nRow++, nSplitted++) + for(nCol=0; nCol<=nColumns-1; nCol++) + pdataset->trn(nRow,nCol) = arr(nSplitted,nCol); + for(nRow=0; nRow<=pdataset->valsize-1; nRow++, nSplitted++) + for(nCol=0; nCol<=nColumns-1; nCol++) + pdataset->val(nRow,nCol) = arr(nSplitted,nCol); + for(nRow=0; nRow<=pdataset->tstsize-1; nRow++, nSplitted++) + for(nCol=0; nCol<=nColumns-1; nCol++) + pdataset->tst(nRow,nCol) = arr(nSplitted,nCol); + return true; +}*/ + +alglib::ae_int_t alglib::vlen(ae_int_t n1, ae_int_t n2) +{ + return n2-n1+1; +} + + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTIONS CONTAINS OPTIMIZED LINEAR ALGEBRA CODE +// IT IS SHARED BETWEEN C++ AND PURE C LIBRARIES +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +#define alglib_simd_alignment 16 + +#define alglib_r_block 32 +#define alglib_half_r_block 16 +#define alglib_twice_r_block 64 + +#define alglib_c_block 24 +#define alglib_half_c_block 12 +#define alglib_twice_c_block 48 + + +/******************************************************************** +This subroutine calculates fast 32x32 real matrix-vector product: + + y := beta*y + alpha*A*x + +using either generic C code or native optimizations (if available) + +IMPORTANT: +* A must be stored in row-major order, + stride is alglib_r_block, + aligned on alglib_simd_alignment boundary +* X must be aligned on alglib_simd_alignment boundary +* Y may be non-aligned +********************************************************************/ +void _ialglib_mv_32(const double *a, const double *x, double *y, ae_int_t stride, double alpha, double beta) +{ + ae_int_t i, k; + const double *pa0, *pa1, *pb; + + pa0 = a; + pa1 = a+alglib_r_block; + pb = x; + for(i=0; i<16; i++) + { + double v0 = 0, v1 = 0; + for(k=0; k<4; k++) + { + v0 += pa0[0]*pb[0]; + v1 += pa1[0]*pb[0]; + v0 += pa0[1]*pb[1]; + v1 += pa1[1]*pb[1]; + v0 += pa0[2]*pb[2]; + v1 += pa1[2]*pb[2]; + v0 += pa0[3]*pb[3]; + v1 += pa1[3]*pb[3]; + v0 += pa0[4]*pb[4]; + v1 += pa1[4]*pb[4]; + v0 += pa0[5]*pb[5]; + v1 += pa1[5]*pb[5]; + v0 += pa0[6]*pb[6]; + v1 += pa1[6]*pb[6]; + v0 += pa0[7]*pb[7]; + v1 += pa1[7]*pb[7]; + pa0 += 8; + pa1 += 8; + pb += 8; + } + y[0] = beta*y[0]+alpha*v0; + y[stride] = beta*y[stride]+alpha*v1; + + /* + * now we've processed rows I and I+1, + * pa0 and pa1 are pointing to rows I+1 and I+2. + * move to I+2 and I+3. + */ + pa0 += alglib_r_block; + pa1 += alglib_r_block; + pb = x; + y+=2*stride; + } +} + + +/************************************************************************* +This function calculates MxN real matrix-vector product: + + y := beta*y + alpha*A*x + +using generic C code. It calls _ialglib_mv_32 if both M=32 and N=32. + +If beta is zero, we do not use previous values of y (they are overwritten +by alpha*A*x without ever being read). If alpha is zero, no matrix-vector +product is calculated (only beta is updated); however, this update is not +efficient and this function should NOT be used for multiplication of +vector and scalar. + +IMPORTANT: +* 0<=M<=alglib_r_block, 0<=N<=alglib_r_block +* A must be stored in row-major order with stride equal to alglib_r_block +*************************************************************************/ +void _ialglib_rmv(ae_int_t m, ae_int_t n, const double *a, const double *x, double *y, ae_int_t stride, double alpha, double beta) +{ + /* + * Handle special cases: + * - alpha is zero or n is zero + * - m is zero + */ + if( m==0 ) + return; + if( alpha==0.0 || n==0 ) + { + ae_int_t i; + if( beta==0.0 ) + { + for(i=0; ix-beta.y*cy->y)+(alpha.x*v0-alpha.y*v1); + double ty = (beta.x*cy->y+beta.y*cy->x)+(alpha.x*v1+alpha.y*v0); + cy->x = tx; + cy->y = ty; + cy+=stride; + } + else + { + double tx = (beta.x*dy[0]-beta.y*dy[1])+(alpha.x*v0-alpha.y*v1); + double ty = (beta.x*dy[1]+beta.y*dy[0])+(alpha.x*v1+alpha.y*v0); + dy[0] = tx; + dy[1] = ty; + dy += 2*stride; + } + parow += 2*alglib_c_block; + } +} + + +/************************************************************************* +This subroutine calculates fast MxN complex matrix-vector product: + + y := beta*y + alpha*A*x + +using generic C code, where A, x, y, alpha and beta are complex. + +If beta is zero, we do not use previous values of y (they are overwritten +by alpha*A*x without ever being read). However, when alpha is zero, we +still calculate A*x and multiply it by alpha (this distinction can be +important when A or x contain infinities/NANs). + +IMPORTANT: +* 0<=M<=alglib_c_block, 0<=N<=alglib_c_block +* A must be stored in row-major order, as sequence of double precision + pairs. Stride is alglib_c_block (it is measured in pairs of doubles, not + in doubles). +* Y may be referenced by cy (pointer to ae_complex) or + dy (pointer to array of double precision pair) depending on what type of + output you wish. Pass pointer to Y as one of these parameters, + AND SET OTHER PARAMETER TO NULL. +* both A and x must be aligned; y may be non-aligned. + +This function supports SSE2; it can be used when: +1. AE_HAS_SSE2_INTRINSICS was defined (checked at compile-time) +2. ae_cpuid() result contains CPU_SSE2 (checked at run-time) + +If (1) is failed, this function will be undefined. If (2) is failed, call +to this function will probably crash your system. + +If you want to know whether it is safe to call it, you should check +results of ae_cpuid(). If CPU_SSE2 bit is set, this function is callable +and will do its work. +*************************************************************************/ +#if defined(AE_HAS_SSE2_INTRINSICS) +void _ialglib_cmv_sse2(ae_int_t m, ae_int_t n, const double *a, const double *x, ae_complex *cy, double *dy, ae_int_t stride, ae_complex alpha, ae_complex beta) +{ + ae_int_t i, j, m2; + const double *pa0, *pa1, *parow, *pb; + __m128d vbeta, vbetax, vbetay; + __m128d valpha, valphax, valphay; + + m2 = m/2; + parow = a; + if( cy!=NULL ) + { + dy = (double*)cy; + cy = NULL; + } + vbeta = _mm_loadh_pd(_mm_load_sd(&beta.x),&beta.y); + vbetax = _mm_unpacklo_pd(vbeta,vbeta); + vbetay = _mm_unpackhi_pd(vbeta,vbeta); + valpha = _mm_loadh_pd(_mm_load_sd(&alpha.x),&alpha.y); + valphax = _mm_unpacklo_pd(valpha,valpha); + valphay = _mm_unpackhi_pd(valpha,valpha); + for(i=0; ix = 0.0; + p->y = 0.0; + } + } + else + { + for(i=0; ix = 0.0; + p->y = 0.0; + } + } +} + + +/******************************************************************** +This subroutine copies unaligned real vector +********************************************************************/ +void _ialglib_vcopy(ae_int_t n, const double *a, ae_int_t stridea, double *b, ae_int_t strideb) +{ + ae_int_t i, n2; + if( stridea==1 && strideb==1 ) + { + n2 = n/2; + for(i=n2; i!=0; i--, a+=2, b+=2) + { + b[0] = a[0]; + b[1] = a[1]; + } + if( n%2!=0 ) + b[0] = a[0]; + } + else + { + for(i=0; ix; + b[1] = a->y; + } + } + else + { + for(i=0; ix; + b[1] = -a->y; + } + } +} + + +/******************************************************************** +This subroutine copies unaligned complex vector (passed as double*) + +1. strideb is stride measured in complex numbers, not doubles +2. conj may be "N" (no conj.) or "C" (conj.) +********************************************************************/ +void _ialglib_vcopy_dcomplex(ae_int_t n, const double *a, ae_int_t stridea, double *b, ae_int_t strideb, const char *conj) +{ + ae_int_t i; + + /* + * more general case + */ + if( conj[0]=='N' || conj[0]=='n' ) + { + for(i=0; ix; + pdst[1] = psrc->y; + } + } + if( op==1 ) + { + for(i=0,psrc=a; ix; + pdst[1] = psrc->y; + } + } + if( op==2 ) + { + for(i=0,psrc=a; ix; + pdst[1] = -psrc->y; + } + } + if( op==3 ) + { + for(i=0,psrc=a; ix; + pdst[1] = -psrc->y; + } + } +} + + +/******************************************************************** +This subroutine copies matrix from aligned contigous storage to +non-aligned non-contigous storage + +A: +* 2*alglib_c_block*alglib_c_block doubles (only MxN submatrix is used) +* aligned +* stride is alglib_c_block +* pointer to double is passed +* may be transformed during copying (as prescribed by op) + +B: +* MxN +* non-aligned +* non-contigous +* pointer to ae_complex is passed + +Transformation types: +* 0 - no transform +* 1 - transposition +* 2 - conjugate transposition +* 3 - conjugate, but no transposition +********************************************************************/ +void _ialglib_mcopyunblock_complex(ae_int_t m, ae_int_t n, const double *a, ae_int_t op, ae_complex* b, ae_int_t stride) +{ + ae_int_t i, j; + const double *psrc; + ae_complex *pdst; + if( op==0 ) + { + for(i=0,psrc=a; ix = psrc[0]; + pdst->y = psrc[1]; + } + } + if( op==1 ) + { + for(i=0,psrc=a; ix = psrc[0]; + pdst->y = psrc[1]; + } + } + if( op==2 ) + { + for(i=0,psrc=a; ix = psrc[0]; + pdst->y = -psrc[1]; + } + } + if( op==3 ) + { + for(i=0,psrc=a; ix = psrc[0]; + pdst->y = -psrc[1]; + } + } +} + + +/******************************************************************** +Real GEMM kernel +********************************************************************/ +ae_bool _ialglib_rmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + double *_a, + ae_int_t _a_stride, + ae_int_t optypea, + double *_b, + ae_int_t _b_stride, + ae_int_t optypeb, + double beta, + double *_c, + ae_int_t _c_stride) +{ + int i; + double *crow; + double _abuf[alglib_r_block+alglib_simd_alignment]; + double _bbuf[alglib_r_block*alglib_r_block+alglib_simd_alignment]; + double * const abuf = (double * const) ae_align(_abuf,alglib_simd_alignment); + double * const b = (double * const) ae_align(_bbuf,alglib_simd_alignment); + void (*rmv)(ae_int_t, ae_int_t, const double *, const double *, double *, ae_int_t, double, double) = &_ialglib_rmv; + void (*mcopyblock)(ae_int_t, ae_int_t, const double *, ae_int_t, ae_int_t, double *) = &_ialglib_mcopyblock; + + if( m>alglib_r_block || n>alglib_r_block || k>alglib_r_block || m<=0 || n<=0 || k<=0 || alpha==0.0 ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + rmv = &_ialglib_rmv_sse2; + mcopyblock = &_ialglib_mcopyblock_sse2; + } +#endif + + /* + * copy b + */ + if( optypeb==0 ) + mcopyblock(k, n, _b, 1, _b_stride, b); + else + mcopyblock(n, k, _b, 0, _b_stride, b); + + /* + * multiply B by A (from the right, by rows) + * and store result in C + */ + crow = _c; + if( optypea==0 ) + { + const double *arow = _a; + for(i=0; ialglib_c_block || n>alglib_c_block || k>alglib_c_block ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + cmv = &_ialglib_cmv_sse2; + } +#endif + + /* + * copy b + */ + brows = optypeb==0 ? k : n; + bcols = optypeb==0 ? n : k; + if( optypeb==0 ) + _ialglib_mcopyblock_complex(brows, bcols, _b, 1, _b_stride, b); + if( optypeb==1 ) + _ialglib_mcopyblock_complex(brows, bcols, _b, 0, _b_stride, b); + if( optypeb==2 ) + _ialglib_mcopyblock_complex(brows, bcols, _b, 3, _b_stride, b); + + /* + * multiply B by A (from the right, by rows) + * and store result in C + */ + arow = _a; + crow = _c; + for(i=0; ialglib_c_block || n>alglib_c_block ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + cmv = &_ialglib_cmv_sse2; + } +#endif + + /* + * Prepare + */ + _ialglib_mcopyblock_complex(n, n, _a, optype, _a_stride, abuf); + _ialglib_mcopyblock_complex(m, n, _x, 0, _x_stride, xbuf); + if( isunit ) + for(i=0,pdiag=abuf; i=0; i--,pdiag-=2*(alglib_c_block+1)) + { + ae_complex tmp_c; + ae_complex beta; + ae_complex alpha; + tmp_c.x = pdiag[0]; + tmp_c.y = pdiag[1]; + beta = ae_c_d_div(1.0, tmp_c); + alpha.x = -beta.x; + alpha.y = -beta.y; + _ialglib_vcopy_dcomplex(n-1-i, pdiag+2*alglib_c_block, alglib_c_block, tmpbuf, 1, "No conj"); + cmv(m, n-1-i, xbuf+2*(i+1), tmpbuf, NULL, xbuf+2*i, alglib_c_block, alpha, beta); + } + _ialglib_mcopyunblock_complex(m, n, xbuf, 0, _x, _x_stride); + } + return ae_true; +} + + +/******************************************************************** +real TRSM kernel +********************************************************************/ +ae_bool _ialglib_rmatrixrighttrsm(ae_int_t m, + ae_int_t n, + double *_a, + ae_int_t _a_stride, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + double *_x, + ae_int_t _x_stride) +{ + /* + * local buffers + */ + double *pdiag; + ae_int_t i; + double _loc_abuf[alglib_r_block*alglib_r_block+alglib_simd_alignment]; + double _loc_xbuf[alglib_r_block*alglib_r_block+alglib_simd_alignment]; + double _loc_tmpbuf[alglib_r_block+alglib_simd_alignment]; + double * const abuf = (double * const) ae_align(_loc_abuf, alglib_simd_alignment); + double * const xbuf = (double * const) ae_align(_loc_xbuf, alglib_simd_alignment); + double * const tmpbuf = (double * const) ae_align(_loc_tmpbuf,alglib_simd_alignment); + ae_bool uppera; + void (*rmv)(ae_int_t, ae_int_t, const double *, const double *, double *, ae_int_t, double, double) = &_ialglib_rmv; + void (*mcopyblock)(ae_int_t, ae_int_t, const double *, ae_int_t, ae_int_t, double *) = &_ialglib_mcopyblock; + + if( m>alglib_r_block || n>alglib_r_block ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + rmv = &_ialglib_rmv_sse2; + mcopyblock = &_ialglib_mcopyblock_sse2; + } +#endif + + /* + * Prepare + */ + mcopyblock(n, n, _a, optype, _a_stride, abuf); + mcopyblock(m, n, _x, 0, _x_stride, xbuf); + if( isunit ) + for(i=0,pdiag=abuf; i=0; i--,pdiag-=alglib_r_block+1) + { + double beta = 1.0/(*pdiag); + double alpha = -beta; + _ialglib_vcopy(n-1-i, pdiag+alglib_r_block, alglib_r_block, tmpbuf+i+1, 1); + rmv(m, n-1-i, xbuf+i+1, tmpbuf+i+1, xbuf+i, alglib_r_block, alpha, beta); + } + _ialglib_mcopyunblock(m, n, xbuf, 0, _x, _x_stride); + } + return ae_true; +} + + +/******************************************************************** +complex TRSM kernel +********************************************************************/ +ae_bool _ialglib_cmatrixlefttrsm(ae_int_t m, + ae_int_t n, + ae_complex *_a, + ae_int_t _a_stride, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_complex *_x, + ae_int_t _x_stride) +{ + /* + * local buffers + */ + double *pdiag, *arow; + ae_int_t i; + double _loc_abuf[2*alglib_c_block*alglib_c_block+alglib_simd_alignment]; + double _loc_xbuf[2*alglib_c_block*alglib_c_block+alglib_simd_alignment]; + double _loc_tmpbuf[2*alglib_c_block+alglib_simd_alignment]; + double * const abuf = (double * const) ae_align(_loc_abuf, alglib_simd_alignment); + double * const xbuf = (double * const) ae_align(_loc_xbuf, alglib_simd_alignment); + double * const tmpbuf = (double * const) ae_align(_loc_tmpbuf,alglib_simd_alignment); + ae_bool uppera; + void (*cmv)(ae_int_t, ae_int_t, const double *, const double *, ae_complex *, double *, ae_int_t, ae_complex, ae_complex) = &_ialglib_cmv; + + if( m>alglib_c_block || n>alglib_c_block ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + cmv = &_ialglib_cmv_sse2; + } +#endif + + /* + * Prepare + * Transpose X (so we may use mv, which calculates A*x, but not x*A) + */ + _ialglib_mcopyblock_complex(m, m, _a, optype, _a_stride, abuf); + _ialglib_mcopyblock_complex(m, n, _x, 1, _x_stride, xbuf); + if( isunit ) + for(i=0,pdiag=abuf; i=0; i--,pdiag-=2*(alglib_c_block+1)) + { + ae_complex tmp_c; + ae_complex beta; + ae_complex alpha; + tmp_c.x = pdiag[0]; + tmp_c.y = pdiag[1]; + beta = ae_c_d_div(1.0, tmp_c); + alpha.x = -beta.x; + alpha.y = -beta.y; + _ialglib_vcopy_dcomplex(m-1-i, pdiag+2, 1, tmpbuf, 1, "No conj"); + cmv(n, m-1-i, xbuf+2*(i+1), tmpbuf, NULL, xbuf+2*i, alglib_c_block, alpha, beta); + } + _ialglib_mcopyunblock_complex(m, n, xbuf, 1, _x, _x_stride); + } + else + { for(i=0,pdiag=abuf,arow=abuf; ialglib_r_block || n>alglib_r_block ) + return ae_false; + + /* + * Check for SSE2 support + */ +#ifdef AE_HAS_SSE2_INTRINSICS + if( ae_cpuid() & CPU_SSE2 ) + { + rmv = &_ialglib_rmv_sse2; + mcopyblock = &_ialglib_mcopyblock_sse2; + } +#endif + + /* + * Prepare + * Transpose X (so we may use mv, which calculates A*x, but not x*A) + */ + mcopyblock(m, m, _a, optype, _a_stride, abuf); + mcopyblock(m, n, _x, 1, _x_stride, xbuf); + if( isunit ) + for(i=0,pdiag=abuf; i=0; i--,pdiag-=alglib_r_block+1) + { + double beta = 1.0/(*pdiag); + double alpha = -beta; + _ialglib_vcopy(m-1-i, pdiag+1, 1, tmpbuf+i+1, 1); + rmv(n, m-1-i, xbuf+i+1, tmpbuf+i+1, xbuf+i, alglib_r_block, alpha, beta); + } + _ialglib_mcopyunblock(m, n, xbuf, 1, _x, _x_stride); + } + else + { for(i=0,pdiag=abuf,arow=abuf; ialglib_c_block || k>alglib_c_block ) + return ae_false; + if( n==0 ) + return ae_true; + + /* + * copy A and C, task is transformed to "A*A^H"-form. + * if beta==0, then C is filled by zeros (and not referenced) + * + * alpha==0 or k==0 are correctly processed (A is not referenced) + */ + c_alpha.x = alpha; + c_alpha.y = 0; + c_beta.x = beta; + c_beta.y = 0; + if( alpha==0 ) + k = 0; + if( k>0 ) + { + if( optypea==0 ) + _ialglib_mcopyblock_complex(n, k, _a, 3, _a_stride, abuf); + else + _ialglib_mcopyblock_complex(k, n, _a, 1, _a_stride, abuf); + } + _ialglib_mcopyblock_complex(n, n, _c, 0, _c_stride, cbuf); + if( beta==0 ) + { + for(i=0,crow=cbuf; ialglib_r_block || k>alglib_r_block ) + return ae_false; + if( n==0 ) + return ae_true; + + /* + * copy A and C, task is transformed to "A*A^T"-form. + * if beta==0, then C is filled by zeros (and not referenced) + * + * alpha==0 or k==0 are correctly processed (A is not referenced) + */ + if( alpha==0 ) + k = 0; + if( k>0 ) + { + if( optypea==0 ) + _ialglib_mcopyblock(n, k, _a, 0, _a_stride, abuf); + else + _ialglib_mcopyblock(k, n, _a, 1, _a_stride, abuf); + } + _ialglib_mcopyblock(n, n, _c, 0, _c_stride, cbuf); + if( beta==0 ) + { + for(i=0,crow=cbuf; iptr.pp_double[ia]+ja, _a->stride, optypea, _b->ptr.pp_double[ib]+jb, _b->stride, optypeb, beta, _c->ptr.pp_double[ic]+jc, _c->stride); +} + +ae_bool _ialglib_i_cmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + ae_matrix *_a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + ae_matrix *_b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + ae_matrix *_c, + ae_int_t ic, + ae_int_t jc) +{ + return _ialglib_cmatrixgemm(m, n, k, alpha, _a->ptr.pp_complex[ia]+ja, _a->stride, optypea, _b->ptr.pp_complex[ib]+jb, _b->stride, optypeb, beta, _c->ptr.pp_complex[ic]+jc, _c->stride); +} + +ae_bool _ialglib_i_cmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2) +{ + return _ialglib_cmatrixrighttrsm(m, n, &a->ptr.pp_complex[i1][j1], a->stride, isupper, isunit, optype, &x->ptr.pp_complex[i2][j2], x->stride); +} + +ae_bool _ialglib_i_rmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2) +{ + return _ialglib_rmatrixrighttrsm(m, n, &a->ptr.pp_double[i1][j1], a->stride, isupper, isunit, optype, &x->ptr.pp_double[i2][j2], x->stride); +} + +ae_bool _ialglib_i_cmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2) +{ + return _ialglib_cmatrixlefttrsm(m, n, &a->ptr.pp_complex[i1][j1], a->stride, isupper, isunit, optype, &x->ptr.pp_complex[i2][j2], x->stride); +} + +ae_bool _ialglib_i_rmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2) +{ + return _ialglib_rmatrixlefttrsm(m, n, &a->ptr.pp_double[i1][j1], a->stride, isupper, isunit, optype, &x->ptr.pp_double[i2][j2], x->stride); +} + +ae_bool _ialglib_i_cmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper) +{ + return _ialglib_cmatrixsyrk(n, k, alpha, &a->ptr.pp_complex[ia][ja], a->stride, optypea, beta, &c->ptr.pp_complex[ic][jc], c->stride, isupper); +} + +ae_bool _ialglib_i_rmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper) +{ + return _ialglib_rmatrixsyrk(n, k, alpha, &a->ptr.pp_double[ia][ja], a->stride, optypea, beta, &c->ptr.pp_double[ic][jc], c->stride, isupper); +} + +ae_bool _ialglib_i_cmatrixrank1f(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_vector *u, + ae_int_t uoffs, + ae_vector *v, + ae_int_t voffs) +{ + return _ialglib_cmatrixrank1(m, n, &a->ptr.pp_complex[ia][ja], a->stride, &u->ptr.p_complex[uoffs], &v->ptr.p_complex[voffs]); +} + +ae_bool _ialglib_i_rmatrixrank1f(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_vector *u, + ae_int_t uoffs, + ae_vector *v, + ae_int_t voffs) +{ + return _ialglib_rmatrixrank1(m, n, &a->ptr.pp_double[ia][ja], a->stride, &u->ptr.p_double[uoffs], &v->ptr.p_double[voffs]); +} + + + + +/******************************************************************** +This function reads rectangular matrix A given by two column pointers +col0 and col1 and stride src_stride and moves it into contiguous row- +by-row storage given by dst. + +It can handle following special cases: +* col1==NULL in this case second column of A is filled by zeros +********************************************************************/ +void _ialglib_pack_n2( + double *col0, + double *col1, + ae_int_t n, + ae_int_t src_stride, + double *dst) +{ + ae_int_t n2, j, stride2; + + /* + * handle special case + */ + if( col1==NULL ) + { + for(j=0; j>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _ap_h +#define _ap_h + +#include +#include +#include +#include +#include +#include + +#ifdef __BORLANDC__ +#include +#include +#else +#include +#include +#endif + +#define AE_USE_CPP +/* Definitions */ +#define AE_UNKNOWN 0 +#define AE_MSVC 1 +#define AE_GNUC 2 +#define AE_SUNC 3 +#define AE_INTEL 1 +#define AE_SPARC 2 +#define AE_WINDOWS 1 +#define AE_POSIX 2 +#define AE_LOCK_ALIGNMENT 16 + +/* in case no OS is defined, use AE_UNKNOWN */ +#ifndef AE_OS +#define AE_OS AE_UNKNOWN +#endif + +/* automatically determine compiler */ +#define AE_COMPILER AE_UNKNOWN +#ifdef __GNUC__ +#undef AE_COMPILER +#define AE_COMPILER AE_GNUC +#endif +#if defined(__SUNPRO_C)||defined(__SUNPRO_CC) +#undef AE_COMPILER +#define AE_COMPILER AE_SUNC +#endif +#ifdef _MSC_VER +#undef AE_COMPILER +#define AE_COMPILER AE_MSVC +#endif + +/* now we are ready to include headers */ +#include +#include +#include +#include +#include +#include + +#if AE_OS==AE_WINDOWS +#include +#include +#elif AE_OS==AE_POSIX +#include +#include +#include +#include +#endif + +#if defined(AE_HAVE_STDINT) +#include +#endif + +/* + * SSE2 intrinsics + * + * Preprocessor directives below: + * - include headers for SSE2 intrinsics + * - define AE_HAS_SSE2_INTRINSICS definition + * + * These actions are performed when we have: + * - x86 architecture definition (AE_CPU==AE_INTEL) + * - compiler which supports intrinsics + * + * Presence of AE_HAS_SSE2_INTRINSICS does NOT mean that our CPU + * actually supports SSE2 - such things should be determined at runtime + * with ae_cpuid() call. It means that we are working under Intel and + * out compiler can issue SSE2-capable code. + * + */ +#if defined(AE_CPU) +#if AE_CPU==AE_INTEL +#if AE_COMPILER==AE_MSVC +#include +#define AE_HAS_SSE2_INTRINSICS +#endif +#if AE_COMPILER==AE_GNUC +#include +#define AE_HAS_SSE2_INTRINSICS +#endif +#if AE_COMPILER==AE_SUNC +#include +#include +#define AE_HAS_SSE2_INTRINSICS +#endif +#endif +#endif + +/* Debugging helpers for Windows */ +#ifdef AE_DEBUG4WINDOWS +#include +#include +#endif + + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS DECLARATIONS FOR BASIC FUNCTIONALITY +// LIKE MEMORY MANAGEMENT FOR VECTORS/MATRICES WHICH IS SHARED +// BETWEEN C++ AND PURE C LIBRARIES +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + +/* if we work under C++ environment, define several conditions */ +#ifdef AE_USE_CPP +#define AE_USE_CPP_BOOL +#define AE_USE_CPP_ERROR_HANDLING +#define AE_USE_CPP_SERIALIZATION +#endif + +/* + * define ae_int32_t, ae_int64_t, ae_int_t, ae_bool, ae_complex, ae_error_type and ae_datatype + */ + +#if defined(AE_INT32_T) +typedef AE_INT32_T ae_int32_t; +#endif +#if defined(AE_HAVE_STDINT) && !defined(AE_INT32_T) +typedef int32_t ae_int32_t; +#endif +#if !defined(AE_HAVE_STDINT) && !defined(AE_INT32_T) +#if AE_COMPILER==AE_MSVC +typedef _int32 ae_int32_t; +#endif +#if (AE_COMPILER==AE_GNUC) || (AE_COMPILER==AE_SUNC) || (AE_COMPILER==AE_UNKNOWN) +typedef int ae_int32_t; +#endif +#endif + +#if defined(AE_INT64_T) +typedef AE_INT64_T ae_int64_t; +#endif +#if defined(AE_HAVE_STDINT) && !defined(AE_INT64_T) +typedef int64_t ae_int64_t; +#endif +#if !defined(AE_HAVE_STDINT) && !defined(AE_INT64_T) +#if AE_COMPILER==AE_MSVC +typedef _int64 ae_int64_t; +#endif +#if (AE_COMPILER==AE_GNUC) || (AE_COMPILER==AE_SUNC) || (AE_COMPILER==AE_UNKNOWN) +typedef signed long long ae_int64_t; +#endif +#endif + +#if !defined(AE_INT_T) +typedef ptrdiff_t ae_int_t; +#endif + +#if !defined(AE_USE_CPP_BOOL) +#define ae_bool char +#define ae_true 1 +#define ae_false 0 +#else +#define ae_bool bool +#define ae_true true +#define ae_false false +#endif + +typedef struct { double x, y; } ae_complex; + +typedef enum +{ + ERR_OK = 0, + ERR_OUT_OF_MEMORY = 1, + ERR_XARRAY_TOO_LARGE = 2, + ERR_ASSERTION_FAILED = 3 +} ae_error_type; + +typedef ae_int_t ae_datatype; + +/* + * other definitions + */ +enum { OWN_CALLER=1, OWN_AE=2 }; +enum { ACT_UNCHANGED=1, ACT_SAME_LOCATION=2, ACT_NEW_LOCATION=3 }; +enum { DT_BOOL=1, DT_INT=2, DT_REAL=3, DT_COMPLEX=4 }; +enum { CPU_SSE2=1 }; + +/************************************************************************ +x-string (zero-terminated): + owner OWN_CALLER or OWN_AE. Determines what to do on realloc(). + If vector is owned by caller, X-interface will just set + ptr to NULL before realloc(). If it is owned by X, it + will call ae_free/x_free/aligned_free family functions. + + last_action ACT_UNCHANGED, ACT_SAME_LOCATION, ACT_NEW_LOCATION + contents is either: unchanged, stored at the same location, + stored at the new location. + this field is set on return from X. + + ptr pointer to the actual data + +Members of this structure are ae_int64_t to avoid alignment problems. +************************************************************************/ +typedef struct +{ + ae_int64_t owner; + ae_int64_t last_action; + char *ptr; +} x_string; + +/************************************************************************ +x-vector: + cnt number of elements + + datatype one of the DT_XXXX values + + owner OWN_CALLER or OWN_AE. Determines what to do on realloc(). + If vector is owned by caller, X-interface will just set + ptr to NULL before realloc(). If it is owned by X, it + will call ae_free/x_free/aligned_free family functions. + + last_action ACT_UNCHANGED, ACT_SAME_LOCATION, ACT_NEW_LOCATION + contents is either: unchanged, stored at the same location, + stored at the new location. + this field is set on return from X interface and may be + used by caller as hint when deciding what to do with data + (if it was ACT_UNCHANGED or ACT_SAME_LOCATION, no array + reallocation or copying is required). + + ptr pointer to the actual data + +Members of this structure are ae_int64_t to avoid alignment problems. +************************************************************************/ +typedef struct +{ + ae_int64_t cnt; + ae_int64_t datatype; + ae_int64_t owner; + ae_int64_t last_action; + void *ptr; +} x_vector; + + +/************************************************************************ +x-matrix: + rows number of rows. may be zero only when cols is zero too. + + cols number of columns. may be zero only when rows is zero too. + + stride stride, i.e. distance between first elements of rows (in bytes) + + datatype one of the DT_XXXX values + + owner OWN_CALLER or OWN_AE. Determines what to do on realloc(). + If vector is owned by caller, X-interface will just set + ptr to NULL before realloc(). If it is owned by X, it + will call ae_free/x_free/aligned_free family functions. + + last_action ACT_UNCHANGED, ACT_SAME_LOCATION, ACT_NEW_LOCATION + contents is either: unchanged, stored at the same location, + stored at the new location. + this field is set on return from X interface and may be + used by caller as hint when deciding what to do with data + (if it was ACT_UNCHANGED or ACT_SAME_LOCATION, no array + reallocation or copying is required). + + ptr pointer to the actual data, stored rowwise + +Members of this structure are ae_int64_t to avoid alignment problems. +************************************************************************/ +typedef struct +{ + ae_int64_t rows; + ae_int64_t cols; + ae_int64_t stride; + ae_int64_t datatype; + ae_int64_t owner; + ae_int64_t last_action; + void *ptr; +} x_matrix; + + +/************************************************************************ +dynamic block which may be automatically deallocated during stack unwinding + +p_next next block in the stack unwinding list. + NULL means that this block is not in the list +deallocator deallocator function which should be used to deallocate block. + NULL for "special" blocks (frame/stack boundaries) +ptr pointer which should be passed to the deallocator. + may be null (for zero-size block), DYN_BOTTOM or DYN_FRAME + for "special" blocks (frame/stack boundaries). + +************************************************************************/ +typedef struct ae_dyn_block +{ + struct ae_dyn_block * volatile p_next; + /* void *deallocator; */ + void (*deallocator)(void*); + void * volatile ptr; +} ae_dyn_block; + +/************************************************************************ +frame marker +************************************************************************/ +typedef struct ae_frame +{ + ae_dyn_block db_marker; +} ae_frame; + +/************************************************************************ +ALGLIB environment state +************************************************************************/ +typedef struct ae_state +{ + /* + * endianness type: AE_LITTLE_ENDIAN or AE_BIG_ENDIAN + */ + ae_int_t endianness; + + /* + * double value for NAN + */ + double v_nan; + + /* + * double value for +INF + */ + double v_posinf; + + /* + * double value for -INF + */ + double v_neginf; + + /* + * pointer to the top block in a stack of frames + * which hold dynamically allocated objects + */ + ae_dyn_block * volatile p_top_block; + ae_dyn_block last_block; + + /* + * jmp_buf for cases when C-style exception handling is used + */ +#ifndef AE_USE_CPP_ERROR_HANDLING + jmp_buf * volatile break_jump; +#endif + + /* + * ae_error_type of the last error (filled when exception is thrown) + */ + ae_error_type volatile last_error; + + /* + * human-readable message (filled when exception is thrown) + */ + const char* volatile error_msg; + + /* + * threading information: + * a) current thread pool + * b) current worker thread + * c) parent task (one we are solving right now) + * d) thread exception handler (function which must be called + * by ae_assert before raising exception). + * + * NOTE: we use void* to store pointers in order to avoid explicit dependency on smp.h + */ + void *worker_thread; + void *parent_task; + void (*thread_exception_handler)(void*); + +} ae_state; + +/************************************************************************ +Serializer +************************************************************************/ +typedef struct +{ + ae_int_t mode; + ae_int_t entries_needed; + ae_int_t entries_saved; + ae_int_t bytes_asked; + ae_int_t bytes_written; + +#ifdef AE_USE_CPP_SERIALIZATION + std::string *out_cppstr; +#endif + char *out_str; + const char *in_str; +} ae_serializer; + +typedef void(*ae_deallocator)(void*); + +typedef struct ae_vector +{ + ae_int_t cnt; + ae_datatype datatype; + ae_dyn_block data; + union + { + void *p_ptr; + ae_bool *p_bool; + ae_int_t *p_int; + double *p_double; + ae_complex *p_complex; + } ptr; +} ae_vector; + +typedef struct ae_matrix +{ + ae_int_t rows; + ae_int_t cols; + ae_int_t stride; + ae_datatype datatype; + ae_dyn_block data; + union + { + void *p_ptr; + void **pp_void; + ae_bool **pp_bool; + ae_int_t **pp_int; + double **pp_double; + ae_complex **pp_complex; + } ptr; +} ae_matrix; + +typedef struct ae_smart_ptr +{ + /* pointer to subscriber; all changes in ptr are translated to subscriber */ + void **subscriber; + + /* pointer to object */ + void *ptr; + + /* whether smart pointer owns ptr */ + ae_bool is_owner; + + /* whether object pointed by ptr is dynamic - clearing such object requires BOTH + calling destructor function AND calling ae_free for memory occupied by object. */ + ae_bool is_dynamic; + + /* destructor function for pointer; clears all dynamically allocated memory */ + void (*destroy)(void*); + + /* frame entry; used to ensure automatic deallocation of smart pointer in case of exception/exit */ + ae_dyn_block frame_entry; +} ae_smart_ptr; + + +/************************************************************************* +Lock. + +This structure provides OS-independent non-reentrant lock: +* under Windows/Posix systems it uses system-provided locks +* under Boost it uses OS-independent lock provided by Boost package +* when no OS is defined, it uses "fake lock" (just stub which is not thread-safe): + a) "fake lock" can be in locked or free mode + b) "fake lock" can be used only from one thread - one which created lock + c) when thread acquires free lock, it immediately returns + d) when thread acquires busy lock, program is terminated + (because lock is already acquired and no one else can free it) +*************************************************************************/ +typedef struct +{ +#if AE_OS==AE_WINDOWS + volatile ae_int_t * volatile p_lock; + char buf[sizeof(ae_int_t)+AE_LOCK_ALIGNMENT]; +#elif AE_OS==AE_POSIX + pthread_mutex_t mutex; +#else + ae_bool is_locked; +#endif +} ae_lock; + + +/************************************************************************* +Shared pool: data structure used to provide thread-safe access to pool of +temporary variables. +*************************************************************************/ +typedef struct ae_shared_pool_entry +{ + void * volatile obj; + void * volatile next_entry; +} ae_shared_pool_entry; + +typedef struct ae_shared_pool +{ + /* lock object which protects pool */ + ae_lock pool_lock; + + /* seed object (used to create new instances of temporaries) */ + void * volatile seed_object; + + /* + * list of recycled OBJECTS: + * 1. entries in this list store pointers to recycled objects + * 2. every time we retrieve object, we retrieve first entry from this list, + * move it to recycled_entries and return its obj field to caller/ + */ + ae_shared_pool_entry * volatile recycled_objects; + + /* + * list of recycled ENTRIES: + * 1. this list holds entries which are not used to store recycled objects; + * every time recycled object is retrieved, its entry is moved to this list. + * 2. every time object is recycled, we try to fetch entry for him from this list + * before allocating it with malloc() + */ + ae_shared_pool_entry * volatile recycled_entries; + + /* enumeration pointer, points to current recycled object*/ + ae_shared_pool_entry * volatile enumeration_counter; + + /* size of object; this field is used when we call malloc() for new objects */ + ae_int_t size_of_object; + + /* initializer function; accepts pointer to malloc'ed object, initializes its fields */ + ae_bool (*init)(void* dst, ae_state* state, ae_bool make_automatic); + + /* copy constructor; accepts pointer to malloc'ed, but not initialized object */ + ae_bool (*init_copy)(void* dst, void* src, ae_state* state, ae_bool make_automatic); + + /* destructor function; */ + void (*destroy)(void* ptr); + + /* frame entry; contains pointer to the pool object itself */ + ae_dyn_block frame_entry; +} ae_shared_pool; + +ae_int_t ae_misalignment(const void *ptr, size_t alignment); +void* ae_align(void *ptr, size_t alignment); +void* aligned_malloc(size_t size, size_t alignment); +void aligned_free(void *block); + +void* ae_malloc(size_t size, ae_state *state); +void ae_free(void *p); +ae_int_t ae_sizeof(ae_datatype datatype); +void ae_touch_ptr(void *p); + +void ae_state_init(ae_state *state); +void ae_state_clear(ae_state *state); +#ifndef AE_USE_CPP_ERROR_HANDLING +void ae_state_set_break_jump(ae_state *state, jmp_buf *buf); +#endif +void ae_break(ae_state *state, ae_error_type error_type, const char *msg); + +void ae_frame_make(ae_state *state, ae_frame *tmp); +void ae_frame_leave(ae_state *state); + +void ae_db_attach(ae_dyn_block *block, ae_state *state); +ae_bool ae_db_malloc(ae_dyn_block *block, ae_int_t size, ae_state *state, ae_bool make_automatic); +ae_bool ae_db_realloc(ae_dyn_block *block, ae_int_t size, ae_state *state); +void ae_db_free(ae_dyn_block *block); +void ae_db_swap(ae_dyn_block *block1, ae_dyn_block *block2); + +ae_bool ae_vector_init(ae_vector *dst, ae_int_t size, ae_datatype datatype, ae_state *state, ae_bool make_automatic); +ae_bool ae_vector_init_copy(ae_vector *dst, ae_vector *src, ae_state *state, ae_bool make_automatic); +void ae_vector_init_from_x(ae_vector *dst, x_vector *src, ae_state *state, ae_bool make_automatic); +ae_bool ae_vector_set_length(ae_vector *dst, ae_int_t newsize, ae_state *state); +void ae_vector_clear(ae_vector *dst); +void ae_vector_destroy(ae_vector *dst); +void ae_swap_vectors(ae_vector *vec1, ae_vector *vec2); + +ae_bool ae_matrix_init(ae_matrix *dst, ae_int_t rows, ae_int_t cols, ae_datatype datatype, ae_state *state, ae_bool make_automatic); +ae_bool ae_matrix_init_copy(ae_matrix *dst, ae_matrix *src, ae_state *state, ae_bool make_automatic); +void ae_matrix_init_from_x(ae_matrix *dst, x_matrix *src, ae_state *state, ae_bool make_automatic); +ae_bool ae_matrix_set_length(ae_matrix *dst, ae_int_t rows, ae_int_t cols, ae_state *state); +void ae_matrix_clear(ae_matrix *dst); +void ae_matrix_destroy(ae_matrix *dst); +void ae_swap_matrices(ae_matrix *mat1, ae_matrix *mat2); + +ae_bool ae_smart_ptr_init(ae_smart_ptr *dst, void **subscriber, ae_state *state, ae_bool make_automatic); +void ae_smart_ptr_clear(void *_dst); /* accepts ae_smart_ptr* */ +void ae_smart_ptr_destroy(void *_dst); +void ae_smart_ptr_assign(ae_smart_ptr *dst, void *new_ptr, ae_bool is_owner, ae_bool is_dynamic, void (*destroy)(void*)); +void ae_smart_ptr_release(ae_smart_ptr *dst); + +void ae_yield(); +void ae_init_lock(ae_lock *lock); +void ae_acquire_lock(ae_lock *lock); +void ae_release_lock(ae_lock *lock); +void ae_free_lock(ae_lock *lock); + +ae_bool ae_shared_pool_init(void *_dst, ae_state *state, ae_bool make_automatic); +ae_bool ae_shared_pool_init_copy(void *_dst, void *_src, ae_state *state, ae_bool make_automatic); +void ae_shared_pool_clear(void *dst); +void ae_shared_pool_destroy(void *dst); +ae_bool ae_shared_pool_is_initialized(void *_dst); +void ae_shared_pool_set_seed( + ae_shared_pool *dst, + void *seed_object, + ae_int_t size_of_object, + ae_bool (*init)(void* dst, ae_state* state, ae_bool make_automatic), + ae_bool (*init_copy)(void* dst, void* src, ae_state* state, ae_bool make_automatic), + void (*destroy)(void* ptr), + ae_state *state); +void ae_shared_pool_retrieve( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state); +void ae_shared_pool_recycle( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state); +void ae_shared_pool_clear_recycled( + ae_shared_pool *pool, + ae_state *state); +void ae_shared_pool_first_recycled( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state); +void ae_shared_pool_next_recycled( + ae_shared_pool *pool, + ae_smart_ptr *pptr, + ae_state *state); +void ae_shared_pool_reset( + ae_shared_pool *pool, + ae_state *state); + +void ae_x_set_vector(x_vector *dst, ae_vector *src, ae_state *state); +void ae_x_set_matrix(x_matrix *dst, ae_matrix *src, ae_state *state); +void ae_x_attach_to_vector(x_vector *dst, ae_vector *src); +void ae_x_attach_to_matrix(x_matrix *dst, ae_matrix *src); + +void x_vector_clear(x_vector *dst); + +ae_bool x_is_symmetric(x_matrix *a); +ae_bool x_is_hermitian(x_matrix *a); +ae_bool x_force_symmetric(x_matrix *a); +ae_bool x_force_hermitian(x_matrix *a); +ae_bool ae_is_symmetric(ae_matrix *a); +ae_bool ae_is_hermitian(ae_matrix *a); +ae_bool ae_force_symmetric(ae_matrix *a); +ae_bool ae_force_hermitian(ae_matrix *a); + +void ae_serializer_init(ae_serializer *serializer); +void ae_serializer_clear(ae_serializer *serializer); + +void ae_serializer_alloc_start(ae_serializer *serializer); +void ae_serializer_alloc_entry(ae_serializer *serializer); +ae_int_t ae_serializer_get_alloc_size(ae_serializer *serializer); + +#ifdef AE_USE_CPP_SERIALIZATION +void ae_serializer_sstart_str(ae_serializer *serializer, std::string *buf); +void ae_serializer_ustart_str(ae_serializer *serializer, const std::string *buf); +#endif +void ae_serializer_sstart_str(ae_serializer *serializer, char *buf); +void ae_serializer_ustart_str(ae_serializer *serializer, const char *buf); + +void ae_serializer_serialize_bool(ae_serializer *serializer, ae_bool v, ae_state *state); +void ae_serializer_serialize_int(ae_serializer *serializer, ae_int_t v, ae_state *state); +void ae_serializer_serialize_double(ae_serializer *serializer, double v, ae_state *state); +void ae_serializer_unserialize_bool(ae_serializer *serializer, ae_bool *v, ae_state *state); +void ae_serializer_unserialize_int(ae_serializer *serializer, ae_int_t *v, ae_state *state); +void ae_serializer_unserialize_double(ae_serializer *serializer, double *v, ae_state *state); + +void ae_serializer_stop(ae_serializer *serializer); + +/************************************************************************ +Service functions +************************************************************************/ +void ae_assert(ae_bool cond, const char *msg, ae_state *state); +ae_int_t ae_cpuid(); + +/************************************************************************ +Real math functions: +* IEEE-compliant floating point comparisons +* standard functions +************************************************************************/ +ae_bool ae_fp_eq(double v1, double v2); +ae_bool ae_fp_neq(double v1, double v2); +ae_bool ae_fp_less(double v1, double v2); +ae_bool ae_fp_less_eq(double v1, double v2); +ae_bool ae_fp_greater(double v1, double v2); +ae_bool ae_fp_greater_eq(double v1, double v2); + +ae_bool ae_isfinite_stateless(double x, ae_int_t endianness); +ae_bool ae_isnan_stateless(double x, ae_int_t endianness); +ae_bool ae_isinf_stateless(double x, ae_int_t endianness); +ae_bool ae_isposinf_stateless(double x, ae_int_t endianness); +ae_bool ae_isneginf_stateless(double x, ae_int_t endianness); + +ae_int_t ae_get_endianness(); + +ae_bool ae_isfinite(double x,ae_state *state); +ae_bool ae_isnan(double x, ae_state *state); +ae_bool ae_isinf(double x, ae_state *state); +ae_bool ae_isposinf(double x,ae_state *state); +ae_bool ae_isneginf(double x,ae_state *state); + +double ae_fabs(double x, ae_state *state); +ae_int_t ae_iabs(ae_int_t x, ae_state *state); +double ae_sqr(double x, ae_state *state); +double ae_sqrt(double x, ae_state *state); + +ae_int_t ae_sign(double x, ae_state *state); +ae_int_t ae_round(double x, ae_state *state); +ae_int_t ae_trunc(double x, ae_state *state); +ae_int_t ae_ifloor(double x, ae_state *state); +ae_int_t ae_iceil(double x, ae_state *state); + +ae_int_t ae_maxint(ae_int_t m1, ae_int_t m2, ae_state *state); +ae_int_t ae_minint(ae_int_t m1, ae_int_t m2, ae_state *state); +double ae_maxreal(double m1, double m2, ae_state *state); +double ae_minreal(double m1, double m2, ae_state *state); +double ae_randomreal(ae_state *state); +ae_int_t ae_randominteger(ae_int_t maxv, ae_state *state); + +double ae_sin(double x, ae_state *state); +double ae_cos(double x, ae_state *state); +double ae_tan(double x, ae_state *state); +double ae_sinh(double x, ae_state *state); +double ae_cosh(double x, ae_state *state); +double ae_tanh(double x, ae_state *state); +double ae_asin(double x, ae_state *state); +double ae_acos(double x, ae_state *state); +double ae_atan(double x, ae_state *state); +double ae_atan2(double y, double x, ae_state *state); + +double ae_log(double x, ae_state *state); +double ae_pow(double x, double y, ae_state *state); +double ae_exp(double x, ae_state *state); + +/************************************************************************ +Complex math functions: +* basic arithmetic operations +* standard functions +************************************************************************/ +ae_complex ae_complex_from_d(double v); + +ae_complex ae_c_neg(ae_complex lhs); +ae_bool ae_c_eq(ae_complex lhs, ae_complex rhs); +ae_bool ae_c_neq(ae_complex lhs, ae_complex rhs); +ae_complex ae_c_add(ae_complex lhs, ae_complex rhs); +ae_complex ae_c_mul(ae_complex lhs, ae_complex rhs); +ae_complex ae_c_sub(ae_complex lhs, ae_complex rhs); +ae_complex ae_c_div(ae_complex lhs, ae_complex rhs); +ae_bool ae_c_eq_d(ae_complex lhs, double rhs); +ae_bool ae_c_neq_d(ae_complex lhs, double rhs); +ae_complex ae_c_add_d(ae_complex lhs, double rhs); +ae_complex ae_c_mul_d(ae_complex lhs, double rhs); +ae_complex ae_c_sub_d(ae_complex lhs, double rhs); +ae_complex ae_c_d_sub(double lhs, ae_complex rhs); +ae_complex ae_c_div_d(ae_complex lhs, double rhs); +ae_complex ae_c_d_div(double lhs, ae_complex rhs); + +ae_complex ae_c_conj(ae_complex lhs, ae_state *state); +ae_complex ae_c_sqr(ae_complex lhs, ae_state *state); +double ae_c_abs(ae_complex z, ae_state *state); + +/************************************************************************ +Complex BLAS operations +************************************************************************/ +ae_complex ae_v_cdotproduct(const ae_complex *v0, ae_int_t stride0, const char *conj0, const ae_complex *v1, ae_int_t stride1, const char *conj1, ae_int_t n); +void ae_v_cmove(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void ae_v_cmoveneg(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void ae_v_cmoved(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void ae_v_cmovec(ae_complex *vdst, ae_int_t stride_dst, const ae_complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha); +void ae_v_cadd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void ae_v_caddd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void ae_v_caddc(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha); +void ae_v_csub(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void ae_v_csubd(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void ae_v_csubc(ae_complex *vdst, ae_int_t stride_dst, const ae_complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, ae_complex alpha); +void ae_v_cmuld(ae_complex *vdst, ae_int_t stride_dst, ae_int_t n, double alpha); +void ae_v_cmulc(ae_complex *vdst, ae_int_t stride_dst, ae_int_t n, ae_complex alpha); + +/************************************************************************ +Real BLAS operations +************************************************************************/ +double ae_v_dotproduct(const double *v0, ae_int_t stride0, const double *v1, ae_int_t stride1, ae_int_t n); +void ae_v_move(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n); +void ae_v_moveneg(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n); +void ae_v_moved(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void ae_v_add(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n); +void ae_v_addd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void ae_v_sub(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n); +void ae_v_subd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void ae_v_muld(double *vdst, ae_int_t stride_dst, ae_int_t n, double alpha); + +/************************************************************************ +Other functions +************************************************************************/ +ae_int_t ae_v_len(ae_int_t a, ae_int_t b); + +/* +extern const double ae_machineepsilon; +extern const double ae_maxrealnumber; +extern const double ae_minrealnumber; +extern const double ae_pi; +*/ +#define ae_machineepsilon 5E-16 +#define ae_maxrealnumber 1E300 +#define ae_minrealnumber 1E-300 +#define ae_pi 3.1415926535897932384626433832795 + + +/************************************************************************ +RComm functions +************************************************************************/ +typedef struct rcommstate +{ + int stage; + ae_vector ia; + ae_vector ba; + ae_vector ra; + ae_vector ca; +} rcommstate; +ae_bool _rcommstate_init(rcommstate* p, ae_state *_state, ae_bool make_automatic); +ae_bool _rcommstate_init_copy(rcommstate* dst, rcommstate* src, ae_state *_state, ae_bool make_automatic); +void _rcommstate_clear(rcommstate* p); +void _rcommstate_destroy(rcommstate* p); + +#ifdef AE_USE_ALLOC_COUNTER +extern ae_int64_t _alloc_counter; +#endif + + +/************************************************************************ +debug functions (must be turned on by preprocessor definitions): +* tickcount(), which is wrapper around GetTickCount() +* flushconsole(), fluches console +* ae_debugrng(), returns random number generated with high-quality random numbers generator +* ae_set_seed(), sets seed of the debug RNG (NON-THREAD-SAFE!!!) +* ae_get_seed(), returns two seed values of the debug RNG (NON-THREAD-SAFE!!!) +************************************************************************/ +#ifdef AE_DEBUG4WINDOWS +#define flushconsole(s) fflush(stdout) +#define tickcount(s) _tickcount() +int _tickcount(); +#endif +#ifdef AE_DEBUG4POSIX +#define flushconsole(s) fflush(stdout) +#define tickcount(s) _tickcount() +int _tickcount(); +#endif +#ifdef AE_DEBUGRNG +ae_int_t ae_debugrng(); +void ae_set_seed(ae_int_t s0, ae_int_t s1); +void ae_get_seed(ae_int_t *s0, ae_int_t *s1); +#endif + + +} + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS DECLARATIONS FOR C++ RELATED FUNCTIONALITY +// +///////////////////////////////////////////////////////////////////////// + +namespace alglib +{ + +typedef alglib_impl::ae_int_t ae_int_t; + +/******************************************************************** +Class forwards +********************************************************************/ +class complex; + +ae_int_t vlen(ae_int_t n1, ae_int_t n2); + +/******************************************************************** +Exception class. +********************************************************************/ +class ap_error +{ +public: + std::string msg; + + ap_error(); + ap_error(const char *s); + static void make_assertion(bool bClause); + static void make_assertion(bool bClause, const char *msg); +private: +}; + +/******************************************************************** +Complex number with double precision. +********************************************************************/ +class complex +{ +public: + complex(); + complex(const double &_x); + complex(const double &_x, const double &_y); + complex(const complex &z); + + complex& operator= (const double& v); + complex& operator+=(const double& v); + complex& operator-=(const double& v); + complex& operator*=(const double& v); + complex& operator/=(const double& v); + + complex& operator= (const complex& z); + complex& operator+=(const complex& z); + complex& operator-=(const complex& z); + complex& operator*=(const complex& z); + complex& operator/=(const complex& z); + + alglib_impl::ae_complex* c_ptr(); + const alglib_impl::ae_complex* c_ptr() const; + + std::string tostring(int dps) const; + + double x, y; +}; + +const alglib::complex operator/(const alglib::complex& lhs, const alglib::complex& rhs); +const bool operator==(const alglib::complex& lhs, const alglib::complex& rhs); +const bool operator!=(const alglib::complex& lhs, const alglib::complex& rhs); +const alglib::complex operator+(const alglib::complex& lhs); +const alglib::complex operator-(const alglib::complex& lhs); +const alglib::complex operator+(const alglib::complex& lhs, const alglib::complex& rhs); +const alglib::complex operator+(const alglib::complex& lhs, const double& rhs); +const alglib::complex operator+(const double& lhs, const alglib::complex& rhs); +const alglib::complex operator-(const alglib::complex& lhs, const alglib::complex& rhs); +const alglib::complex operator-(const alglib::complex& lhs, const double& rhs); +const alglib::complex operator-(const double& lhs, const alglib::complex& rhs); +const alglib::complex operator*(const alglib::complex& lhs, const alglib::complex& rhs); +const alglib::complex operator*(const alglib::complex& lhs, const double& rhs); +const alglib::complex operator*(const double& lhs, const alglib::complex& rhs); +const alglib::complex operator/(const alglib::complex& lhs, const alglib::complex& rhs); +const alglib::complex operator/(const double& lhs, const alglib::complex& rhs); +const alglib::complex operator/(const alglib::complex& lhs, const double& rhs); +double abscomplex(const alglib::complex &z); +alglib::complex conj(const alglib::complex &z); +alglib::complex csqr(const alglib::complex &z); +void setnworkers(alglib::ae_int_t nworkers); + +/******************************************************************** +Level 1 BLAS functions + +NOTES: +* destination and source should NOT overlap +* stride is assumed to be positive, but it is not + assert'ed within function +* conj_src parameter specifies whether complex source is conjugated + before processing or not. Pass string which starts with 'N' or 'n' + ("No conj", for example) to use unmodified parameter. All other + values will result in conjugation of input, but it is recommended + to use "Conj" in such cases. +********************************************************************/ +double vdotproduct(const double *v0, ae_int_t stride0, const double *v1, ae_int_t stride1, ae_int_t n); +double vdotproduct(const double *v1, const double *v2, ae_int_t N); + +alglib::complex vdotproduct(const alglib::complex *v0, ae_int_t stride0, const char *conj0, const alglib::complex *v1, ae_int_t stride1, const char *conj1, ae_int_t n); +alglib::complex vdotproduct(const alglib::complex *v1, const alglib::complex *v2, ae_int_t N); + +void vmove(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n); +void vmove(double *vdst, const double* vsrc, ae_int_t N); + +void vmove(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void vmove(alglib::complex *vdst, const alglib::complex* vsrc, ae_int_t N); + +void vmoveneg(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n); +void vmoveneg(double *vdst, const double *vsrc, ae_int_t N); + +void vmoveneg(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void vmoveneg(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N); + +void vmove(double *vdst, ae_int_t stride_dst, const double* vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void vmove(double *vdst, const double *vsrc, ae_int_t N, double alpha); + +void vmove(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void vmove(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, double alpha); + +void vmove(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex* vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha); +void vmove(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, alglib::complex alpha); + +void vadd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n); +void vadd(double *vdst, const double *vsrc, ae_int_t N); + +void vadd(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N); + +void vadd(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void vadd(double *vdst, const double *vsrc, ae_int_t N, double alpha); + +void vadd(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, double alpha); + +void vadd(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha); +void vadd(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, alglib::complex alpha); + +void vsub(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n); +void vsub(double *vdst, const double *vsrc, ae_int_t N); + +void vsub(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n); +void vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N); + +void vsub(double *vdst, ae_int_t stride_dst, const double *vsrc, ae_int_t stride_src, ae_int_t n, double alpha); +void vsub(double *vdst, const double *vsrc, ae_int_t N, double alpha); + +void vsub(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, double alpha); +void vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, double alpha); + +void vsub(alglib::complex *vdst, ae_int_t stride_dst, const alglib::complex *vsrc, ae_int_t stride_src, const char *conj_src, ae_int_t n, alglib::complex alpha); +void vsub(alglib::complex *vdst, const alglib::complex *vsrc, ae_int_t N, alglib::complex alpha); + +void vmul(double *vdst, ae_int_t stride_dst, ae_int_t n, double alpha); +void vmul(double *vdst, ae_int_t N, double alpha); + +void vmul(alglib::complex *vdst, ae_int_t stride_dst, ae_int_t n, double alpha); +void vmul(alglib::complex *vdst, ae_int_t N, double alpha); + +void vmul(alglib::complex *vdst, ae_int_t stride_dst, ae_int_t n, alglib::complex alpha); +void vmul(alglib::complex *vdst, ae_int_t N, alglib::complex alpha); + + + +/******************************************************************** +string conversion functions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +********************************************************************/ + +/******************************************************************** +1- and 2-dimensional arrays +********************************************************************/ +class ae_vector_wrapper +{ +public: + ae_vector_wrapper(); + virtual ~ae_vector_wrapper(); + + void setlength(ae_int_t iLen); + ae_int_t length() const; + + void attach_to(alglib_impl::ae_vector *ptr); + void allocate_own(ae_int_t size, alglib_impl::ae_datatype datatype); + const alglib_impl::ae_vector* c_ptr() const; + alglib_impl::ae_vector* c_ptr(); +private: + ae_vector_wrapper(const ae_vector_wrapper &rhs); + const ae_vector_wrapper& operator=(const ae_vector_wrapper &rhs); +protected: + // + // Copies source vector RHS into current object. + // + // Current object is considered empty (this function should be + // called from copy constructor). + // + void create(const ae_vector_wrapper &rhs); + + // + // Copies array given by string into current object. Additional + // parameter DATATYPE contains information about type of the data + // in S and type of the array to create. + // + // Current object is considered empty (this function should be + // called from copy constructor). + // + void create(const char *s, alglib_impl::ae_datatype datatype); + + // + // Assigns RHS to current object. + // + // It has several branches depending on target object status: + // * in case it is proxy object, data are copied into memory pointed by + // proxy. Function checks that source has exactly same size as target + // (exception is thrown on failure). + // * in case it is non-proxy object, data allocated by object are cleared + // and a copy of RHS is created in target. + // + // NOTE: this function correctly handles assignments of the object to itself. + // + void assign(const ae_vector_wrapper &rhs); + + alglib_impl::ae_vector *p_vec; + alglib_impl::ae_vector vec; +}; + +class boolean_1d_array : public ae_vector_wrapper +{ +public: + boolean_1d_array(); + boolean_1d_array(const char *s); + boolean_1d_array(const boolean_1d_array &rhs); + boolean_1d_array(alglib_impl::ae_vector *p); + const boolean_1d_array& operator=(const boolean_1d_array &rhs); + virtual ~boolean_1d_array() ; + + const ae_bool& operator()(ae_int_t i) const; + ae_bool& operator()(ae_int_t i); + + const ae_bool& operator[](ae_int_t i) const; + ae_bool& operator[](ae_int_t i); + + void setcontent(ae_int_t iLen, const bool *pContent ); + ae_bool* getcontent(); + const ae_bool* getcontent() const; + + std::string tostring() const; +}; + +class integer_1d_array : public ae_vector_wrapper +{ +public: + integer_1d_array(); + integer_1d_array(const char *s); + integer_1d_array(const integer_1d_array &rhs); + integer_1d_array(alglib_impl::ae_vector *p); + const integer_1d_array& operator=(const integer_1d_array &rhs); + virtual ~integer_1d_array(); + + const ae_int_t& operator()(ae_int_t i) const; + ae_int_t& operator()(ae_int_t i); + + const ae_int_t& operator[](ae_int_t i) const; + ae_int_t& operator[](ae_int_t i); + + void setcontent(ae_int_t iLen, const ae_int_t *pContent ); + + ae_int_t* getcontent(); + const ae_int_t* getcontent() const; + + std::string tostring() const; +}; + +class real_1d_array : public ae_vector_wrapper +{ +public: + real_1d_array(); + real_1d_array(const char *s); + real_1d_array(const real_1d_array &rhs); + real_1d_array(alglib_impl::ae_vector *p); + const real_1d_array& operator=(const real_1d_array &rhs); + virtual ~real_1d_array(); + + const double& operator()(ae_int_t i) const; + double& operator()(ae_int_t i); + + const double& operator[](ae_int_t i) const; + double& operator[](ae_int_t i); + + void setcontent(ae_int_t iLen, const double *pContent ); + double* getcontent(); + const double* getcontent() const; + + std::string tostring(int dps) const; +}; + +class complex_1d_array : public ae_vector_wrapper +{ +public: + complex_1d_array(); + complex_1d_array(const char *s); + complex_1d_array(const complex_1d_array &rhs); + complex_1d_array(alglib_impl::ae_vector *p); + const complex_1d_array& operator=(const complex_1d_array &rhs); + virtual ~complex_1d_array(); + + const alglib::complex& operator()(ae_int_t i) const; + alglib::complex& operator()(ae_int_t i); + + const alglib::complex& operator[](ae_int_t i) const; + alglib::complex& operator[](ae_int_t i); + + void setcontent(ae_int_t iLen, const alglib::complex *pContent ); + alglib::complex* getcontent(); + const alglib::complex* getcontent() const; + + std::string tostring(int dps) const; +}; + +class ae_matrix_wrapper +{ +public: + ae_matrix_wrapper(); + virtual ~ae_matrix_wrapper(); + const ae_matrix_wrapper& operator=(const ae_matrix_wrapper &rhs); + + void setlength(ae_int_t rows, ae_int_t cols); + ae_int_t rows() const; + ae_int_t cols() const; + bool isempty() const; + ae_int_t getstride() const; + + void attach_to(alglib_impl::ae_matrix *ptr); + void allocate_own(ae_int_t rows, ae_int_t cols, alglib_impl::ae_datatype datatype); + const alglib_impl::ae_matrix* c_ptr() const; + alglib_impl::ae_matrix* c_ptr(); +private: + ae_matrix_wrapper(const ae_matrix_wrapper &rhs); +protected: + // + // Copies source matrix RHS into current object. + // + // Current object is considered empty (this function should be + // called from copy constructor). + // + void create(const ae_matrix_wrapper &rhs); + + // + // Copies array given by string into current object. Additional + // parameter DATATYPE contains information about type of the data + // in S and type of the array to create. + // + // Current object is considered empty (this function should be + // called from copy constructor). + // + void create(const char *s, alglib_impl::ae_datatype datatype); + + // + // Assigns RHS to current object. + // + // It has several branches depending on target object status: + // * in case it is proxy object, data are copied into memory pointed by + // proxy. Function checks that source has exactly same size as target + // (exception is thrown on failure). + // * in case it is non-proxy object, data allocated by object are cleared + // and a copy of RHS is created in target. + // + // NOTE: this function correctly handles assignments of the object to itself. + // + void assign(const ae_matrix_wrapper &rhs); + + alglib_impl::ae_matrix *p_mat; + alglib_impl::ae_matrix mat; +}; + +class boolean_2d_array : public ae_matrix_wrapper +{ +public: + boolean_2d_array(); + boolean_2d_array(const boolean_2d_array &rhs); + boolean_2d_array(alglib_impl::ae_matrix *p); + boolean_2d_array(const char *s); + virtual ~boolean_2d_array(); + + const ae_bool& operator()(ae_int_t i, ae_int_t j) const; + ae_bool& operator()(ae_int_t i, ae_int_t j); + + const ae_bool* operator[](ae_int_t i) const; + ae_bool* operator[](ae_int_t i); + + void setcontent(ae_int_t irows, ae_int_t icols, const bool *pContent ); + + std::string tostring() const ; +}; + +class integer_2d_array : public ae_matrix_wrapper +{ +public: + integer_2d_array(); + integer_2d_array(const integer_2d_array &rhs); + integer_2d_array(alglib_impl::ae_matrix *p); + integer_2d_array(const char *s); + virtual ~integer_2d_array(); + + const ae_int_t& operator()(ae_int_t i, ae_int_t j) const; + ae_int_t& operator()(ae_int_t i, ae_int_t j); + + const ae_int_t* operator[](ae_int_t i) const; + ae_int_t* operator[](ae_int_t i); + + void setcontent(ae_int_t irows, ae_int_t icols, const ae_int_t *pContent ); + + std::string tostring() const; +}; + +class real_2d_array : public ae_matrix_wrapper +{ +public: + real_2d_array(); + real_2d_array(const real_2d_array &rhs); + real_2d_array(alglib_impl::ae_matrix *p); + real_2d_array(const char *s); + virtual ~real_2d_array(); + + const double& operator()(ae_int_t i, ae_int_t j) const; + double& operator()(ae_int_t i, ae_int_t j); + + const double* operator[](ae_int_t i) const; + double* operator[](ae_int_t i); + + void setcontent(ae_int_t irows, ae_int_t icols, const double *pContent ); + + std::string tostring(int dps) const; +}; + +class complex_2d_array : public ae_matrix_wrapper +{ +public: + complex_2d_array(); + complex_2d_array(const complex_2d_array &rhs); + complex_2d_array(alglib_impl::ae_matrix *p); + complex_2d_array(const char *s); + virtual ~complex_2d_array(); + + const alglib::complex& operator()(ae_int_t i, ae_int_t j) const; + alglib::complex& operator()(ae_int_t i, ae_int_t j); + + const alglib::complex* operator[](ae_int_t i) const; + alglib::complex* operator[](ae_int_t i); + + void setcontent(ae_int_t irows, ae_int_t icols, const alglib::complex *pContent ); + + std::string tostring(int dps) const; +}; + + + +/******************************************************************** +dataset information. + +can store regression dataset, classification dataset, or non-labeled +task: +* nout==0 means non-labeled task (clustering, for example) +* nout>0 && nclasses==0 means regression task +* nout>0 && nclasses>0 means classification task +********************************************************************/ +/*class dataset +{ +public: + dataset():nin(0), nout(0), nclasses(0), trnsize(0), valsize(0), tstsize(0), totalsize(0){}; + + int nin, nout, nclasses; + + int trnsize; + int valsize; + int tstsize; + int totalsize; + + alglib::real_2d_array trn; + alglib::real_2d_array val; + alglib::real_2d_array tst; + alglib::real_2d_array all; +}; + +bool opendataset(std::string file, dataset *pdataset); + +// +// internal functions +// +std::string strtolower(const std::string &s); +bool readstrings(std::string file, std::list *pOutput); +bool readstrings(std::string file, std::list *pOutput, std::string comment); +void explodestring(std::string s, char sep, std::vector *pOutput); +std::string xtrim(std::string s);*/ + +/******************************************************************** +Constants and functions introduced for compatibility with AlgoPascal +********************************************************************/ +extern const double machineepsilon; +extern const double maxrealnumber; +extern const double minrealnumber; +extern const double fp_nan; +extern const double fp_posinf; +extern const double fp_neginf; +extern const ae_int_t endianness; + +int sign(double x); +double randomreal(); +ae_int_t randominteger(ae_int_t maxv); +int round(double x); +int trunc(double x); +int ifloor(double x); +int iceil(double x); +double pi(); +double sqr(double x); +int maxint(int m1, int m2); +int minint(int m1, int m2); +double maxreal(double m1, double m2); +double minreal(double m1, double m2); + +bool fp_eq(double v1, double v2); +bool fp_neq(double v1, double v2); +bool fp_less(double v1, double v2); +bool fp_less_eq(double v1, double v2); +bool fp_greater(double v1, double v2); +bool fp_greater_eq(double v1, double v2); + +bool fp_isnan(double x); +bool fp_isposinf(double x); +bool fp_isneginf(double x); +bool fp_isinf(double x); +bool fp_isfinite(double x); + + +}//namespace alglib + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTIONS CONTAINS DECLARATIONS FOR OPTIMIZED LINEAR ALGEBRA CODES +// IT IS SHARED BETWEEN C++ AND PURE C LIBRARIES +// +///////////////////////////////////////////////////////////////////////// + +namespace alglib_impl +{ +#define ALGLIB_INTERCEPTS_ABLAS +void _ialglib_vzero(ae_int_t n, double *p, ae_int_t stride); +void _ialglib_vzero_complex(ae_int_t n, ae_complex *p, ae_int_t stride); +void _ialglib_vcopy(ae_int_t n, const double *a, ae_int_t stridea, double *b, ae_int_t strideb); +void _ialglib_vcopy_complex(ae_int_t n, const ae_complex *a, ae_int_t stridea, double *b, ae_int_t strideb, const char *conj); +void _ialglib_vcopy_dcomplex(ae_int_t n, const double *a, ae_int_t stridea, double *b, ae_int_t strideb, const char *conj); +void _ialglib_mcopyblock(ae_int_t m, ae_int_t n, const double *a, ae_int_t op, ae_int_t stride, double *b); +void _ialglib_mcopyunblock(ae_int_t m, ae_int_t n, const double *a, ae_int_t op, double *b, ae_int_t stride); +void _ialglib_mcopyblock_complex(ae_int_t m, ae_int_t n, const ae_complex *a, ae_int_t op, ae_int_t stride, double *b); +void _ialglib_mcopyunblock_complex(ae_int_t m, ae_int_t n, const double *a, ae_int_t op, ae_complex* b, ae_int_t stride); + +ae_bool _ialglib_i_rmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + ae_matrix *b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc); +ae_bool _ialglib_i_cmatrixgemmf(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + ae_matrix *b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc); +ae_bool _ialglib_i_cmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2); +ae_bool _ialglib_i_rmatrixrighttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2); +ae_bool _ialglib_i_cmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2); +ae_bool _ialglib_i_rmatrixlefttrsmf(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + ae_matrix *x, + ae_int_t i2, + ae_int_t j2); +ae_bool _ialglib_i_cmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper); +ae_bool _ialglib_i_rmatrixsyrkf(ae_int_t n, + ae_int_t k, + double alpha, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + ae_matrix *c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper); +ae_bool _ialglib_i_cmatrixrank1f(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_vector *u, + ae_int_t uoffs, + ae_vector *v, + ae_int_t voffs); +ae_bool _ialglib_i_rmatrixrank1f(ae_int_t m, + ae_int_t n, + ae_matrix *a, + ae_int_t ia, + ae_int_t ja, + ae_vector *u, + ae_int_t uoffs, + ae_vector *v, + ae_int_t voffs); + + + +} + + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS PARALLEL SUBROUTINES +// +///////////////////////////////////////////////////////////////////////// + +namespace alglib_impl +{ + +} + + +#endif + diff --git a/src/inc/alglib/dataanalysis.cpp b/src/inc/alglib/dataanalysis.cpp new file mode 100644 index 0000000..1ef4452 --- /dev/null +++ b/src/inc/alglib/dataanalysis.cpp @@ -0,0 +1,35078 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "dataanalysis.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Optimal binary classification + +Algorithms finds optimal (=with minimal cross-entropy) binary partition. +Internal subroutine. + +INPUT PARAMETERS: + A - array[0..N-1], variable + C - array[0..N-1], class numbers (0 or 1). + N - array size + +OUTPUT PARAMETERS: + Info - completetion code: + * -3, all values of A[] are same (partition is impossible) + * -2, one of C[] is incorrect (<0, >1) + * -1, incorrect pararemets were passed (N<=0). + * 1, OK + Threshold- partiton boundary. Left part contains values which are + strictly less than Threshold. Right part contains values + which are greater than or equal to Threshold. + PAL, PBL- probabilities P(0|v=Threshold) and P(1|v>=Threshold) + CVE - cross-validation estimate of cross-entropy + + -- ALGLIB -- + Copyright 22.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2(const real_1d_array &a, const integer_1d_array &c, const ae_int_t n, ae_int_t &info, double &threshold, double &pal, double &pbl, double &par, double &pbr, double &cve) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dsoptimalsplit2(const_cast(a.c_ptr()), const_cast(c.c_ptr()), n, &info, &threshold, &pal, &pbl, &par, &pbr, &cve, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Optimal partition, internal subroutine. Fast version. + +Accepts: + A array[0..N-1] array of attributes array[0..N-1] + C array[0..N-1] array of class labels + TiesBuf array[0..N] temporaries (ties) + CntBuf array[0..2*NC-1] temporaries (counts) + Alpha centering factor (0<=alpha<=1, recommended value - 0.05) + BufR array[0..N-1] temporaries + BufI array[0..N-1] temporaries + +Output: + Info error code (">0"=OK, "<0"=bad) + RMS training set RMS error + CVRMS leave-one-out RMS error + +Note: + content of all arrays is changed by subroutine; + it doesn't allocate temporaries. + + -- ALGLIB -- + Copyright 11.12.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2fast(real_1d_array &a, integer_1d_array &c, integer_1d_array &tiesbuf, integer_1d_array &cntbuf, real_1d_array &bufr, integer_1d_array &bufi, const ae_int_t n, const ae_int_t nc, const double alpha, ae_int_t &info, double &threshold, double &rms, double &cvrms) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dsoptimalsplit2fast(const_cast(a.c_ptr()), const_cast(c.c_ptr()), const_cast(tiesbuf.c_ptr()), const_cast(cntbuf.c_ptr()), const_cast(bufr.c_ptr()), const_cast(bufi.c_ptr()), n, nc, alpha, &info, &threshold, &rms, &cvrms, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This structure is a clusterization engine. + +You should not try to access its fields directly. +Use ALGLIB functions in order to work with this object. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +_clusterizerstate_owner::_clusterizerstate_owner() +{ + p_struct = (alglib_impl::clusterizerstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::clusterizerstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_clusterizerstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_clusterizerstate_owner::_clusterizerstate_owner(const _clusterizerstate_owner &rhs) +{ + p_struct = (alglib_impl::clusterizerstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::clusterizerstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_clusterizerstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_clusterizerstate_owner& _clusterizerstate_owner::operator=(const _clusterizerstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_clusterizerstate_clear(p_struct); + if( !alglib_impl::_clusterizerstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_clusterizerstate_owner::~_clusterizerstate_owner() +{ + alglib_impl::_clusterizerstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::clusterizerstate* _clusterizerstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::clusterizerstate* _clusterizerstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +clusterizerstate::clusterizerstate() : _clusterizerstate_owner() +{ +} + +clusterizerstate::clusterizerstate(const clusterizerstate &rhs):_clusterizerstate_owner(rhs) +{ +} + +clusterizerstate& clusterizerstate::operator=(const clusterizerstate &rhs) +{ + if( this==&rhs ) + return *this; + _clusterizerstate_owner::operator=(rhs); + return *this; +} + +clusterizerstate::~clusterizerstate() +{ +} + + +/************************************************************************* +This structure is used to store results of the agglomerative hierarchical +clustering (AHC). + +Following information is returned: + +* NPoints contains number of points in the original dataset + +* Z contains information about merges performed (see below). Z contains + indexes from the original (unsorted) dataset and it can be used when you + need to know what points were merged. However, it is not convenient when + you want to build a dendrograd (see below). + +* if you want to build dendrogram, you can use Z, but it is not good + option, because Z contains indexes from unsorted dataset. Dendrogram + built from such dataset is likely to have intersections. So, you have to + reorder you points before building dendrogram. + Permutation which reorders point is returned in P. Another representation + of merges, which is more convenient for dendorgram construction, is + returned in PM. + +* more information on format of Z, P and PM can be found below and in the + examples from ALGLIB Reference Manual. + +FORMAL DESCRIPTION OF FIELDS: + NPoints number of points + Z array[NPoints-1,2], contains indexes of clusters + linked in pairs to form clustering tree. I-th row + corresponds to I-th merge: + * Z[I,0] - index of the first cluster to merge + * Z[I,1] - index of the second cluster to merge + * Z[I,0](rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_ahcreport_owner& _ahcreport_owner::operator=(const _ahcreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_ahcreport_clear(p_struct); + if( !alglib_impl::_ahcreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_ahcreport_owner::~_ahcreport_owner() +{ + alglib_impl::_ahcreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::ahcreport* _ahcreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::ahcreport* _ahcreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +ahcreport::ahcreport() : _ahcreport_owner() ,npoints(p_struct->npoints),p(&p_struct->p),z(&p_struct->z),pz(&p_struct->pz),pm(&p_struct->pm),mergedist(&p_struct->mergedist) +{ +} + +ahcreport::ahcreport(const ahcreport &rhs):_ahcreport_owner(rhs) ,npoints(p_struct->npoints),p(&p_struct->p),z(&p_struct->z),pz(&p_struct->pz),pm(&p_struct->pm),mergedist(&p_struct->mergedist) +{ +} + +ahcreport& ahcreport::operator=(const ahcreport &rhs) +{ + if( this==&rhs ) + return *this; + _ahcreport_owner::operator=(rhs); + return *this; +} + +ahcreport::~ahcreport() +{ +} + + +/************************************************************************* +This structure is used to store results of the k-means++ clustering +algorithm. + +Following information is always returned: +* NPoints contains number of points in the original dataset +* TerminationType contains completion code, negative on failure, positive + on success +* K contains number of clusters + +For positive TerminationType we return: +* NFeatures contains number of variables in the original dataset +* C, which contains centers found by algorithm +* CIdx, which maps points of the original dataset to clusters + +FORMAL DESCRIPTION OF FIELDS: + NPoints number of points, >=0 + NFeatures number of variables, >=1 + TerminationType completion code: + * -5 if distance type is anything different from + Euclidean metric + * -3 for degenerate dataset: a) less than K distinct + points, b) K=0 for non-empty dataset. + * +1 for successful completion + K number of clusters + C array[K,NFeatures], rows of the array store centers + CIdx array[NPoints], which contains cluster indexes + + -- ALGLIB -- + Copyright 27.11.2012 by Bochkanov Sergey +*************************************************************************/ +_kmeansreport_owner::_kmeansreport_owner() +{ + p_struct = (alglib_impl::kmeansreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::kmeansreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_kmeansreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_kmeansreport_owner::_kmeansreport_owner(const _kmeansreport_owner &rhs) +{ + p_struct = (alglib_impl::kmeansreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::kmeansreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_kmeansreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_kmeansreport_owner& _kmeansreport_owner::operator=(const _kmeansreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_kmeansreport_clear(p_struct); + if( !alglib_impl::_kmeansreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_kmeansreport_owner::~_kmeansreport_owner() +{ + alglib_impl::_kmeansreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::kmeansreport* _kmeansreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::kmeansreport* _kmeansreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +kmeansreport::kmeansreport() : _kmeansreport_owner() ,npoints(p_struct->npoints),nfeatures(p_struct->nfeatures),terminationtype(p_struct->terminationtype),k(p_struct->k),c(&p_struct->c),cidx(&p_struct->cidx) +{ +} + +kmeansreport::kmeansreport(const kmeansreport &rhs):_kmeansreport_owner(rhs) ,npoints(p_struct->npoints),nfeatures(p_struct->nfeatures),terminationtype(p_struct->terminationtype),k(p_struct->k),c(&p_struct->c),cidx(&p_struct->cidx) +{ +} + +kmeansreport& kmeansreport::operator=(const kmeansreport &rhs) +{ + if( this==&rhs ) + return *this; + _kmeansreport_owner::operator=(rhs); + return *this; +} + +kmeansreport::~kmeansreport() +{ +} + +/************************************************************************* +This function initializes clusterizer object. Newly initialized object is +empty, i.e. it does not contain dataset. You should use it as follows: +1. creation +2. dataset is added with ClusterizerSetPoints() +3. additional parameters are set +3. clusterization is performed with one of the clustering functions + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizercreate(clusterizerstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizercreate(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset to the clusterizer structure. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +NOTE 1: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + +NOTE 2: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric + * k-means++ clustering algorithm may be used only with Euclidean + distance function + Thus, list of specific clustering algorithms you may use depends + on distance function you specify when you set your dataset. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetpoints(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), npoints, nfeatures, disttype, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset to the clusterizer structure. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +NOTE 1: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + +NOTE 2: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric + * k-means++ clustering algorithm may be used only with Euclidean + distance function + Thus, list of specific clustering algorithms you may use depends + on distance function you specify when you set your dataset. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t disttype) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + ae_int_t nfeatures; + + npoints = xy.rows(); + nfeatures = xy.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetpoints(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), npoints, nfeatures, disttype, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset given by distance matrix to the clusterizer +structure. It is important that dataset is not given explicitly - only +distance matrix is given. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + D - array[NPoints,NPoints], distance matrix given by its upper + or lower triangle (main diagonal is ignored because its + entries are expected to be zero). + NPoints - number of points + IsUpper - whether upper or lower triangle of D is given. + +NOTE 1: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric, including one which is given by + distance matrix + * k-means++ clustering algorithm may be used only with Euclidean + distance function and explicitly given points - it can not be + used with dataset given by distance matrix + Thus, if you call this function, you will be unable to use k-means + clustering algorithm to process your problem. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const ae_int_t npoints, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetdistances(const_cast(s.c_ptr()), const_cast(d.c_ptr()), npoints, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset given by distance matrix to the clusterizer +structure. It is important that dataset is not given explicitly - only +distance matrix is given. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + D - array[NPoints,NPoints], distance matrix given by its upper + or lower triangle (main diagonal is ignored because its + entries are expected to be zero). + NPoints - number of points + IsUpper - whether upper or lower triangle of D is given. + +NOTE 1: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric, including one which is given by + distance matrix + * k-means++ clustering algorithm may be used only with Euclidean + distance function and explicitly given points - it can not be + used with dataset given by distance matrix + Thus, if you call this function, you will be unable to use k-means + clustering algorithm to process your problem. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + if( (d.rows()!=d.cols())) + throw ap_error("Error while calling 'clusterizersetdistances': looks like one of arguments has wrong size"); + npoints = d.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetdistances(const_cast(s.c_ptr()), const_cast(d.c_ptr()), npoints, isupper, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets agglomerative hierarchical clustering algorithm + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Algo - algorithm type: + * 0 complete linkage (default algorithm) + * 1 single linkage + * 2 unweighted average linkage + * 3 weighted average linkage + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetahcalgo(const clusterizerstate &s, const ae_int_t algo) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetahcalgo(const_cast(s.c_ptr()), algo, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets k-means++ properties : number of restarts and maximum +number of iterations per one run. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Restarts- restarts count, >=1. + k-means++ algorithm performs several restarts and chooses + best set of centers (one with minimum squared distance). + MaxIts - maximum number of k-means iterations performed during one + run. >=0, zero value means that algorithm performs unlimited + number of iterations. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetkmeanslimits(const clusterizerstate &s, const ae_int_t restarts, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizersetkmeanslimits(const_cast(s.c_ptr()), restarts, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function performs agglomerative hierarchical clustering + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + +OUTPUT PARAMETERS: + Rep - clustering results; see description of AHCReport + structure for more information. + +NOTE 1: hierarchical clustering algorithms require large amounts of memory. + In particular, this implementation needs sizeof(double)*NPoints^2 + bytes, which are used to store distance matrix. In case we work + with user-supplied matrix, this amount is multiplied by 2 (we have + to store original matrix and to work with its copy). + + For example, problem with 10000 points would require 800M of RAM, + even when working in a 1-dimensional space. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunahc(const clusterizerstate &s, ahcreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizerrunahc(const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_clusterizerrunahc(const clusterizerstate &s, ahcreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_clusterizerrunahc(const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function performs clustering by k-means++ algorithm. + +You may change algorithm properties like number of restarts or iterations +limit by calling ClusterizerSetKMeansLimits() functions. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + K - number of clusters, K>=0. + K can be zero only when algorithm is called for empty + dataset, in this case completion code is set to + success (+1). + If K=0 and dataset size is non-zero, we can not + meaningfully assign points to some center (there are no + centers because K=0) and return -3 as completion code + (failure). + +OUTPUT PARAMETERS: + Rep - clustering results; see description of KMeansReport + structure for more information. + +NOTE 1: k-means clustering can be performed only for datasets with + Euclidean distance function. Algorithm will return negative + completion code in Rep.TerminationType in case dataset was added + to clusterizer with DistType other than Euclidean (or dataset was + specified by distance matrix instead of explicitly given points). + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunkmeans(const clusterizerstate &s, const ae_int_t k, kmeansreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizerrunkmeans(const_cast(s.c_ptr()), k, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns distance matrix for dataset + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +OUTPUT PARAMETERS: + D - array[NPoints,NPoints], distance matrix + (full matrix is returned, with lower and upper triangles) + +NOTES: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizergetdistances(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype, real_2d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::clusterizergetdistances(const_cast(xy.c_ptr()), npoints, nfeatures, disttype, const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_clusterizergetdistances(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype, real_2d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_clusterizergetdistances(const_cast(xy.c_ptr()), npoints, nfeatures, disttype, const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function takes as input clusterization report Rep, desired clusters +count K, and builds top K clusters from hierarchical clusterization tree. +It returns assignment of points to clusters (array of cluster indexes). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + K - desired number of clusters, 1<=K<=NPoints. + K can be zero only when NPoints=0. + +OUTPUT PARAMETERS: + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I](rep.c_ptr()), k, const_cast(cidx.c_ptr()), const_cast(cz.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function accepts AHC report Rep, desired minimum intercluster +distance and returns top clusters from hierarchical clusterization tree +which are separated by distance R or HIGHER. + +It returns assignment of points to clusters (array of cluster indexes). + +There is one more function with similar name - ClusterizerSeparatedByCorr, +which returns clusters with intercluster correlation equal to R or LOWER +(note: higher for distance, lower for correlation). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + R - desired minimum intercluster distance, R>=0 + +OUTPUT PARAMETERS: + K - number of clusters, 1<=K<=NPoints + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I](rep.c_ptr()), r, &k, const_cast(cidx.c_ptr()), const_cast(cz.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function accepts AHC report Rep, desired maximum intercluster +correlation and returns top clusters from hierarchical clusterization tree +which are separated by correlation R or LOWER. + +It returns assignment of points to clusters (array of cluster indexes). + +There is one more function with similar name - ClusterizerSeparatedByDist, +which returns clusters with intercluster distance equal to R or HIGHER +(note: higher for distance, lower for correlation). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + R - desired maximum intercluster correlation, -1<=R<=+1 + +OUTPUT PARAMETERS: + K - number of clusters, 1<=K<=NPoints + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I](rep.c_ptr()), r, &k, const_cast(cidx.c_ptr()), const_cast(cz.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +k-means++ clusterization. +Backward compatibility function, we recommend to use CLUSTERING subpackage +as better replacement. + + -- ALGLIB -- + Copyright 21.03.2009 by Bochkanov Sergey +*************************************************************************/ +void kmeansgenerate(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t k, const ae_int_t restarts, ae_int_t &info, real_2d_array &c, integer_1d_array &xyc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::kmeansgenerate(const_cast(xy.c_ptr()), npoints, nvars, k, restarts, &info, const_cast(c.c_ptr()), const_cast(xyc.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_decisionforest_owner::_decisionforest_owner() +{ + p_struct = (alglib_impl::decisionforest*)alglib_impl::ae_malloc(sizeof(alglib_impl::decisionforest), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_decisionforest_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_decisionforest_owner::_decisionforest_owner(const _decisionforest_owner &rhs) +{ + p_struct = (alglib_impl::decisionforest*)alglib_impl::ae_malloc(sizeof(alglib_impl::decisionforest), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_decisionforest_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_decisionforest_owner& _decisionforest_owner::operator=(const _decisionforest_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_decisionforest_clear(p_struct); + if( !alglib_impl::_decisionforest_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_decisionforest_owner::~_decisionforest_owner() +{ + alglib_impl::_decisionforest_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::decisionforest* _decisionforest_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::decisionforest* _decisionforest_owner::c_ptr() const +{ + return const_cast(p_struct); +} +decisionforest::decisionforest() : _decisionforest_owner() +{ +} + +decisionforest::decisionforest(const decisionforest &rhs):_decisionforest_owner(rhs) +{ +} + +decisionforest& decisionforest::operator=(const decisionforest &rhs) +{ + if( this==&rhs ) + return *this; + _decisionforest_owner::operator=(rhs); + return *this; +} + +decisionforest::~decisionforest() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_dfreport_owner::_dfreport_owner() +{ + p_struct = (alglib_impl::dfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::dfreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_dfreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_dfreport_owner::_dfreport_owner(const _dfreport_owner &rhs) +{ + p_struct = (alglib_impl::dfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::dfreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_dfreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_dfreport_owner& _dfreport_owner::operator=(const _dfreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_dfreport_clear(p_struct); + if( !alglib_impl::_dfreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_dfreport_owner::~_dfreport_owner() +{ + alglib_impl::_dfreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::dfreport* _dfreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::dfreport* _dfreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +dfreport::dfreport() : _dfreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),oobrelclserror(p_struct->oobrelclserror),oobavgce(p_struct->oobavgce),oobrmserror(p_struct->oobrmserror),oobavgerror(p_struct->oobavgerror),oobavgrelerror(p_struct->oobavgrelerror) +{ +} + +dfreport::dfreport(const dfreport &rhs):_dfreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),oobrelclserror(p_struct->oobrelclserror),oobavgce(p_struct->oobavgce),oobrmserror(p_struct->oobrmserror),oobavgerror(p_struct->oobavgerror),oobavgrelerror(p_struct->oobavgrelerror) +{ +} + +dfreport& dfreport::operator=(const dfreport &rhs) +{ + if( this==&rhs ) + return *this; + _dfreport_owner::operator=(rhs); + return *this; +} + +dfreport::~dfreport() +{ +} + + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void dfserialize(decisionforest &obj, std::string &s_out) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + alglib_impl::ae_int_t ssize; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_alloc_start(&serializer); + alglib_impl::dfalloc(&serializer, obj.c_ptr(), &state); + ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer); + s_out.clear(); + s_out.reserve((size_t)(ssize+1)); + alglib_impl::ae_serializer_sstart_str(&serializer, &s_out); + alglib_impl::dfserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + if( s_out.length()>(size_t)ssize ) + throw ap_error("ALGLIB: serialization integrity error"); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void dfunserialize(std::string &s_in, decisionforest &obj) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_ustart_str(&serializer, &s_in); + alglib_impl::dfunserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} + +/************************************************************************* +This subroutine builds random decision forest. + +INPUT PARAMETERS: + XY - training set + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforest(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const double r, ae_int_t &info, decisionforest &df, dfreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dfbuildrandomdecisionforest(const_cast(xy.c_ptr()), npoints, nvars, nclasses, ntrees, r, &info, const_cast(df.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds random decision forest. +This function gives ability to tune number of variables used when choosing +best split. + +INPUT PARAMETERS: + XY - training set + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + NRndVars - number of variables used when choosing best split + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforestx1(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const ae_int_t nrndvars, const double r, ae_int_t &info, decisionforest &df, dfreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dfbuildrandomdecisionforestx1(const_cast(xy.c_ptr()), npoints, nvars, nclasses, ntrees, nrndvars, r, &info, const_cast(df.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + DF - decision forest model + X - input vector, array[0..NVars-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also DFProcessI. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfprocess(const decisionforest &df, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dfprocess(const_cast(df.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +'interactive' variant of DFProcess for languages like Python which support +constructs like "Y = DFProcessI(DF,X)" and interactive mode of interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void dfprocessi(const decisionforest &df, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::dfprocessi(const_cast(df.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrelclserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dfrelclserror(const_cast(df.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgce(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dfavgce(const_cast(df.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for + classification task, RMS error means error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrmserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dfrmserror(const_cast(df.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dfavgerror(const_cast(df.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average relative error when estimating + posterior probability of belonging to the correct class. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgrelerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dfavgrelerror(const_cast(df.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_linearmodel_owner::_linearmodel_owner() +{ + p_struct = (alglib_impl::linearmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::linearmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linearmodel_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linearmodel_owner::_linearmodel_owner(const _linearmodel_owner &rhs) +{ + p_struct = (alglib_impl::linearmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::linearmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linearmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linearmodel_owner& _linearmodel_owner::operator=(const _linearmodel_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_linearmodel_clear(p_struct); + if( !alglib_impl::_linearmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_linearmodel_owner::~_linearmodel_owner() +{ + alglib_impl::_linearmodel_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::linearmodel* _linearmodel_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::linearmodel* _linearmodel_owner::c_ptr() const +{ + return const_cast(p_struct); +} +linearmodel::linearmodel() : _linearmodel_owner() +{ +} + +linearmodel::linearmodel(const linearmodel &rhs):_linearmodel_owner(rhs) +{ +} + +linearmodel& linearmodel::operator=(const linearmodel &rhs) +{ + if( this==&rhs ) + return *this; + _linearmodel_owner::operator=(rhs); + return *this; +} + +linearmodel::~linearmodel() +{ +} + + +/************************************************************************* +LRReport structure contains additional information about linear model: +* C - covariation matrix, array[0..NVars,0..NVars]. + C[i,j] = Cov(A[i],A[j]) +* RMSError - root mean square error on a training set +* AvgError - average error on a training set +* AvgRelError - average relative error on a training set (excluding + observations with zero function value). +* CVRMSError - leave-one-out cross-validation estimate of + generalization error. Calculated using fast algorithm + with O(NVars*NPoints) complexity. +* CVAvgError - cross-validation estimate of average error +* CVAvgRelError - cross-validation estimate of average relative error + +All other fields of the structure are intended for internal use and should +not be used outside ALGLIB. +*************************************************************************/ +_lrreport_owner::_lrreport_owner() +{ + p_struct = (alglib_impl::lrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lrreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lrreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lrreport_owner::_lrreport_owner(const _lrreport_owner &rhs) +{ + p_struct = (alglib_impl::lrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lrreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lrreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lrreport_owner& _lrreport_owner::operator=(const _lrreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_lrreport_clear(p_struct); + if( !alglib_impl::_lrreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_lrreport_owner::~_lrreport_owner() +{ + alglib_impl::_lrreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::lrreport* _lrreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::lrreport* _lrreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +lrreport::lrreport() : _lrreport_owner() ,c(&p_struct->c),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),cvrmserror(p_struct->cvrmserror),cvavgerror(p_struct->cvavgerror),cvavgrelerror(p_struct->cvavgrelerror),ncvdefects(p_struct->ncvdefects),cvdefects(&p_struct->cvdefects) +{ +} + +lrreport::lrreport(const lrreport &rhs):_lrreport_owner(rhs) ,c(&p_struct->c),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),cvrmserror(p_struct->cvrmserror),cvavgerror(p_struct->cvavgerror),cvavgrelerror(p_struct->cvavgrelerror),ncvdefects(p_struct->ncvdefects),cvdefects(&p_struct->cvdefects) +{ +} + +lrreport& lrreport::operator=(const lrreport &rhs) +{ + if( this==&rhs ) + return *this; + _lrreport_owner::operator=(rhs); + return *this; +} + +lrreport::~lrreport() +{ +} + +/************************************************************************* +Linear regression + +Subroutine builds model: + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N) + +and model found in ALGLIB format, covariation matrix, training set errors +(rms, average, average relative) and leave-one-out cross-validation +estimate of the generalization error. CV estimate calculated using fast +algorithm with O(NPoints*NVars) complexity. + +When covariation matrix is calculated standard deviations of function +values are assumed to be equal to RMS error on the training set. + +INPUT PARAMETERS: + XY - training set, array [0..NPoints-1,0..NVars]: + * NVars columns - independent variables + * last column - dependent variable + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPoints(xy.c_ptr()), npoints, nvars, &info, const_cast(lm.c_ptr()), const_cast(ar.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Linear regression + +Variant of LRBuild which uses vector of standatd deviations (errors in +function values). + +INPUT PARAMETERS: + XY - training set, array [0..NPoints-1,0..NVars]: + * NVars columns - independent variables + * last column - dependent variable + S - standard deviations (errors in function values) + array[0..NPoints-1], S[i]>0. + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPoints(xy.c_ptr()), const_cast(s.c_ptr()), npoints, nvars, &info, const_cast(lm.c_ptr()), const_cast(ar.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like LRBuildS, but builds model + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + +i.e. with zero constant term. + + -- ALGLIB -- + Copyright 30.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lrbuildzs(const real_2d_array &xy, const real_1d_array &s, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lrbuildzs(const_cast(xy.c_ptr()), const_cast(s.c_ptr()), npoints, nvars, &info, const_cast(lm.c_ptr()), const_cast(ar.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like LRBuild but builds model + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + +i.e. with zero constant term. + + -- ALGLIB -- + Copyright 30.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lrbuildz(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, linearmodel &lm, lrreport &ar) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lrbuildz(const_cast(xy.c_ptr()), npoints, nvars, &info, const_cast(lm.c_ptr()), const_cast(ar.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacks coefficients of linear model. + +INPUT PARAMETERS: + LM - linear model in ALGLIB format + +OUTPUT PARAMETERS: + V - coefficients, array[0..NVars] + constant term (intercept) is stored in the V[NVars]. + NVars - number of independent variables (one less than number + of coefficients) + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +void lrunpack(const linearmodel &lm, real_1d_array &v, ae_int_t &nvars) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lrunpack(const_cast(lm.c_ptr()), const_cast(v.c_ptr()), &nvars, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +"Packs" coefficients and creates linear model in ALGLIB format (LRUnpack +reversed). + +INPUT PARAMETERS: + V - coefficients, array[0..NVars] + NVars - number of independent variables + +OUTPUT PAREMETERS: + LM - linear model. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +void lrpack(const real_1d_array &v, const ae_int_t nvars, linearmodel &lm) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lrpack(const_cast(v.c_ptr()), nvars, const_cast(lm.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + LM - linear model + X - input vector, array[0..NVars-1]. + +Result: + value of linear model regression estimate + + -- ALGLIB -- + Copyright 03.09.2008 by Bochkanov Sergey +*************************************************************************/ +double lrprocess(const linearmodel &lm, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::lrprocess(const_cast(lm.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lrrmserror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::lrrmserror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + average error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lravgerror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::lravgerror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + average relative error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lravgrelerror(const linearmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::lravgrelerror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: simple moving averages (unsymmetric). + +This filter replaces array by results of SMA(K) filter. SMA(K) is defined +as filter which averages at most K previous points (previous - not points +AROUND central point) - or less, in case of the first K-1 points. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filtersma(real_1d_array &x, const ae_int_t n, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::filtersma(const_cast(x.c_ptr()), n, k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: simple moving averages (unsymmetric). + +This filter replaces array by results of SMA(K) filter. SMA(K) is defined +as filter which averages at most K previous points (previous - not points +AROUND central point) - or less, in case of the first K-1 points. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filtersma(real_1d_array &x, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::filtersma(const_cast(x.c_ptr()), n, k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: exponential moving averages. + +This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is +defined as filter which replaces X[] by S[]: + S[0] = X[0] + S[t] = alpha*X[t] + (1-alpha)*S[t-1] + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + alpha - 0(x.c_ptr()), n, alpha, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: exponential moving averages. + +This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is +defined as filter which replaces X[] by S[]: + S[0] = X[0] + S[t] = alpha*X[t] + (1-alpha)*S[t-1] + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + alpha - 0(x.c_ptr()), n, alpha, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: linear regression moving averages. + +This filter replaces array by results of LRMA(K) filter. + +LRMA(K) is defined as filter which, for each data point, builds linear +regression model using K prevous points (point itself is included in +these K points) and calculates value of this linear model at the point in +question. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filterlrma(real_1d_array &x, const ae_int_t n, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::filterlrma(const_cast(x.c_ptr()), n, k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Filters: linear regression moving averages. + +This filter replaces array by results of LRMA(K) filter. + +LRMA(K) is defined as filter which, for each data point, builds linear +regression model using K prevous points (point itself is included in +these K points) and calculates value of this linear model at the point in +question. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filterlrma(real_1d_array &x, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::filterlrma(const_cast(x.c_ptr()), n, k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiclass Fisher LDA + +Subroutine finds coefficients of linear combination which optimally separates +training set on classes. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - linear combination coefficients, array[0..NVars-1] + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherlda(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fisherlda(const_cast(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +N-dimensional multiclass Fisher LDA + +Subroutine finds coefficients of linear combinations which optimally separates +training set on classes. It returns N-dimensional basis whose vector are sorted +by quality of training set separation (in descending order). + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - basis, array[0..NVars-1,0..NVars-1] + columns of matrix stores basis vectors, sorted by + quality of training set separation (in descending order) + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherldan(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_2d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fisherldan(const_cast(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Model's errors: + * RelCLSError - fraction of misclassified cases. + * AvgCE - acerage cross-entropy + * RMSError - root-mean-square error + * AvgError - average error + * AvgRelError - average relative error + +NOTE 1: RelCLSError/AvgCE are zero on regression problems. + +NOTE 2: on classification problems RMSError/AvgError/AvgRelError contain + errors in prediction of posterior probabilities +*************************************************************************/ +_modelerrors_owner::_modelerrors_owner() +{ + p_struct = (alglib_impl::modelerrors*)alglib_impl::ae_malloc(sizeof(alglib_impl::modelerrors), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_modelerrors_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_modelerrors_owner::_modelerrors_owner(const _modelerrors_owner &rhs) +{ + p_struct = (alglib_impl::modelerrors*)alglib_impl::ae_malloc(sizeof(alglib_impl::modelerrors), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_modelerrors_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_modelerrors_owner& _modelerrors_owner::operator=(const _modelerrors_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_modelerrors_clear(p_struct); + if( !alglib_impl::_modelerrors_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_modelerrors_owner::~_modelerrors_owner() +{ + alglib_impl::_modelerrors_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::modelerrors* _modelerrors_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::modelerrors* _modelerrors_owner::c_ptr() const +{ + return const_cast(p_struct); +} +modelerrors::modelerrors() : _modelerrors_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror) +{ +} + +modelerrors::modelerrors(const modelerrors &rhs):_modelerrors_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror) +{ +} + +modelerrors& modelerrors::operator=(const modelerrors &rhs) +{ + if( this==&rhs ) + return *this; + _modelerrors_owner::operator=(rhs); + return *this; +} + +modelerrors::~modelerrors() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_multilayerperceptron_owner::_multilayerperceptron_owner() +{ + p_struct = (alglib_impl::multilayerperceptron*)alglib_impl::ae_malloc(sizeof(alglib_impl::multilayerperceptron), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_multilayerperceptron_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_multilayerperceptron_owner::_multilayerperceptron_owner(const _multilayerperceptron_owner &rhs) +{ + p_struct = (alglib_impl::multilayerperceptron*)alglib_impl::ae_malloc(sizeof(alglib_impl::multilayerperceptron), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_multilayerperceptron_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_multilayerperceptron_owner& _multilayerperceptron_owner::operator=(const _multilayerperceptron_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_multilayerperceptron_clear(p_struct); + if( !alglib_impl::_multilayerperceptron_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_multilayerperceptron_owner::~_multilayerperceptron_owner() +{ + alglib_impl::_multilayerperceptron_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::multilayerperceptron* _multilayerperceptron_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::multilayerperceptron* _multilayerperceptron_owner::c_ptr() const +{ + return const_cast(p_struct); +} +multilayerperceptron::multilayerperceptron() : _multilayerperceptron_owner() +{ +} + +multilayerperceptron::multilayerperceptron(const multilayerperceptron &rhs):_multilayerperceptron_owner(rhs) +{ +} + +multilayerperceptron& multilayerperceptron::operator=(const multilayerperceptron &rhs) +{ + if( this==&rhs ) + return *this; + _multilayerperceptron_owner::operator=(rhs); + return *this; +} + +multilayerperceptron::~multilayerperceptron() +{ +} + + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void mlpserialize(multilayerperceptron &obj, std::string &s_out) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + alglib_impl::ae_int_t ssize; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_alloc_start(&serializer); + alglib_impl::mlpalloc(&serializer, obj.c_ptr(), &state); + ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer); + s_out.clear(); + s_out.reserve((size_t)(ssize+1)); + alglib_impl::ae_serializer_sstart_str(&serializer, &s_out); + alglib_impl::mlpserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + if( s_out.length()>(size_t)ssize ) + throw ap_error("ALGLIB: serialization integrity error"); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void mlpunserialize(std::string &s_in, multilayerperceptron &obj) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_ustart_str(&serializer, &s_in); + alglib_impl::mlpunserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers, with linear output layer. Network weights are filled with small +random values. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreate0(nin, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreate0, but with one hidden layer (NHid neurons) with +non-linear activation function. Output layer is linear. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreate1(nin, nhid, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreate0, but with two hidden layers (NHid1 and NHid2 neurons) +with non-linear activation function. Output layer is linear. + $ALL + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreate2(nin, nhid1, nhid2, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. + +Activation function of the output layer takes values: + + (B, +INF), if D>=0 + +or + + (-INF, B), if D<0. + + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreateb0(nin, nout, b, d, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateB0 but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreateb1(nin, nhid, nout, b, d, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateB0 but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreateb2(nin, nhid1, nhid2, nout, b, d, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. Activation function of the output layer takes values [A,B]. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreater0(nin, nout, a, b, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateR0, but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreater1(nin, nhid, nout, a, b, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateR0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreater2(nin, nhid1, nhid2, nout, a, b, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creates classifier network with NIn inputs and NOut possible classes. +Network contains no hidden layers and linear output layer with SOFTMAX- +normalization (so outputs sums up to 1.0 and converge to posterior +probabilities). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreatec0(nin, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateC0, but with one non-linear hidden layer. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreatec1(nin, nhid, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Same as MLPCreateC0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreatec2(nin, nhid1, nhid2, nout, const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Randomization of neural network weights + + -- ALGLIB -- + Copyright 06.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlprandomize(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlprandomize(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Randomization of neural network weights and standartisator + + -- ALGLIB -- + Copyright 10.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlprandomizefull(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlprandomizefull(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns information about initialized network: number of inputs, outputs, +weights. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpproperties(const multilayerperceptron &network, ae_int_t &nin, ae_int_t &nout, ae_int_t &wcount) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpproperties(const_cast(network.c_ptr()), &nin, &nout, &wcount, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns number of inputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetinputscount(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpgetinputscount(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns number of outputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetoutputscount(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpgetoutputscount(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns number of weights. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetweightscount(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpgetweightscount(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Tells whether network is SOFTMAX-normalized (i.e. classifier) or not. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +bool mlpissoftmax(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::mlpissoftmax(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns total number of layers (including input, hidden and +output layers). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayerscount(const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpgetlayerscount(const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns size of K-th layer. + +K=0 corresponds to input layer, K=CNT-1 corresponds to output layer. + +Size of the output layer is always equal to the number of outputs, although +when we have softmax-normalized network, last neuron doesn't have any +connections - it is just zero. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayersize(const multilayerperceptron &network, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpgetlayersize(const_cast(network.c_ptr()), k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetinputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgetinputscaling(const_cast(network.c_ptr()), i, &mean, &sigma, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. In case we have SOFTMAX-normalized network, +we return (Mean,Sigma)=(0.0,1.0). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetoutputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgetoutputscaling(const_cast(network.c_ptr()), i, &mean, &sigma, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + +OUTPUT PARAMETERS: + FKind - activation function type (used by MLPActivationFunction()) + this value is zero for input or linear neurons + Threshold - also called offset, bias + zero for input neurons + +NOTE: this function throws exception if layer or neuron with given index +do not exists. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, ae_int_t &fkind, double &threshold) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgetneuroninfo(const_cast(network.c_ptr()), k, i, &fkind, &threshold, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + +RESULT: + connection weight (zero for non-existent connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. returns zero if neurons exist, but there is no connection between them + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +double mlpgetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpgetweight(const_cast(network.c_ptr()), k0, i0, k1, i1, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +NTE: I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network. This function sets Mean and Sigma. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetinputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetinputscaling(const_cast(network.c_ptr()), i, mean, sigma, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +OUTPUT PARAMETERS: + +NOTE: I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. This function sets Sigma/Mean. In case we +have SOFTMAX-normalized network, you can not set (Sigma,Mean) to anything +other than(0.0,1.0) - this function will throw exception. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetoutputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetoutputscaling(const_cast(network.c_ptr()), i, mean, sigma, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function modifies information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + FKind - activation function type (used by MLPActivationFunction()) + this value must be zero for input neurons + (you can not set activation function for input neurons) + Threshold - also called offset, bias + this value must be zero for input neurons + (you can not set threshold for input neurons) + +NOTES: +1. this function throws exception if layer or neuron with given index do + not exists. +2. this function also throws exception when you try to set non-linear + activation function for input neurons (any kind of network) or for output + neurons of classifier network. +3. this function throws exception when you try to set non-zero threshold for + input neurons (any kind of network). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, const ae_int_t fkind, const double threshold) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetneuroninfo(const_cast(network.c_ptr()), k, i, fkind, threshold, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function modifies information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + W - connection weight (must be zero for non-existent + connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. throws exception if you try to set non-zero weight for non-existent + connection + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1, const double w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetweight(const_cast(network.c_ptr()), k0, i0, k1, i1, w, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Neural network activation function + +INPUT PARAMETERS: + NET - neuron input + K - function index (zero for linear function) + +OUTPUT PARAMETERS: + F - function + DF - its derivative + D2F - its second derivative + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpactivationfunction(const double net, const ae_int_t k, double &f, double &df, double &d2f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpactivationfunction(net, k, &f, &df, &d2f, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Network - neural network + X - input vector, array[0..NIn-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also MLPProcessI + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpprocess(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpprocess(const_cast(network.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +'interactive' variant of MLPProcess for languages like Python which +support constructs like "Y = MLPProcess(NN,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 21.09.2010 by Bochkanov Sergey +*************************************************************************/ +void mlpprocessi(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpprocessi(const_cast(network.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlperror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlperror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Error of the neural network on dataset given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0 + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlperrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlperrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Natural error function for neural network, internal subroutine. + +NOTE: this function is single-threaded. Unlike other error function, it +receives no speed-up from being executed in SMP mode. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperrorn(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperrorn(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Classification error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + classification error (number of misclassified cases) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mlpclserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +ae_int_t smp_mlpclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::_pexec_mlpclserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Relative classification error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 25.12.2008 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlprelclserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlprelclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlprelclserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Relative classification error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. Sparse matrix must use CRS format + for storage. + NPoints - points count, >=0. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlprelclserrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlprelclserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlprelclserrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 08.01.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpavgce(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgce(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgce(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgce(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set given by +sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 9.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgcesparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgcesparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgcesparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgcesparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set given. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlprmserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlprmserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlprmserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlprmserror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprmserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlprmserrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlprmserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlprmserrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average absolute error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgerror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgerror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average absolute error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgerrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgerrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average relative error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgrelerror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgrelerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgrelerror(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average relative error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpavgrelerrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlpavgrelerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlpavgrelerrorsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Gradient calculation + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgrad(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgrad(const_cast(network.c_ptr()), const_cast(x.c_ptr()), const_cast(desiredy.c_ptr()), &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Gradient calculation (natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradn(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradn(const_cast(network.c_ptr()), const_cast(x.c_ptr()), const_cast(desiredy.c_ptr()), &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradbatch(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpgradbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpgradbatch(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs given by sparse +matrices + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs. + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t ssize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradbatchsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpgradbatchsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t ssize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpgradbatchsparse(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch gradient calculation for a subset of dataset + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradbatchsubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(idx.c_ptr()), subsetsize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpgradbatchsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpgradbatchsubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(idx.c_ptr()), subsetsize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs for a subset of +dataset given by set of indexes. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatchSparse + function. + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradbatchsparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(idx.c_ptr()), subsetsize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpgradbatchsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpgradbatchsparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(idx.c_ptr()), subsetsize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs +(natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - set of inputs/outputs; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradnbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpgradnbatch(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch Hessian calculation (natural error function) using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessiannbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlphessiannbatch(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), const_cast(h.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Batch Hessian calculation using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessianbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlphessianbatch(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), ssize, &e, const_cast(grad.c_ptr()), const_cast(h.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of all types of errors. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpallerrorssubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpallerrorssubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpallerrorssubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of all types of errors on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset given by sparse matrix; + one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpallerrorssparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpallerrorssparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpallerrorssparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperrorsubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlperrorsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlperrorsubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Error of the neural network on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + SetSize - real size of XY, SetSize>=0; + it is used when SubsetSize<0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperrorsparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +double smp_mlperrorsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::_pexec_mlperrorsparsesubset(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), setsize, const_cast(subset.c_ptr()), subsetsize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_logitmodel_owner::_logitmodel_owner() +{ + p_struct = (alglib_impl::logitmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::logitmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_logitmodel_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_logitmodel_owner::_logitmodel_owner(const _logitmodel_owner &rhs) +{ + p_struct = (alglib_impl::logitmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::logitmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_logitmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_logitmodel_owner& _logitmodel_owner::operator=(const _logitmodel_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_logitmodel_clear(p_struct); + if( !alglib_impl::_logitmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_logitmodel_owner::~_logitmodel_owner() +{ + alglib_impl::_logitmodel_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::logitmodel* _logitmodel_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::logitmodel* _logitmodel_owner::c_ptr() const +{ + return const_cast(p_struct); +} +logitmodel::logitmodel() : _logitmodel_owner() +{ +} + +logitmodel::logitmodel(const logitmodel &rhs):_logitmodel_owner(rhs) +{ +} + +logitmodel& logitmodel::operator=(const logitmodel &rhs) +{ + if( this==&rhs ) + return *this; + _logitmodel_owner::operator=(rhs); + return *this; +} + +logitmodel::~logitmodel() +{ +} + + +/************************************************************************* +MNLReport structure contains information about training process: +* NGrad - number of gradient calculations +* NHess - number of Hessian calculations +*************************************************************************/ +_mnlreport_owner::_mnlreport_owner() +{ + p_struct = (alglib_impl::mnlreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mnlreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mnlreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mnlreport_owner::_mnlreport_owner(const _mnlreport_owner &rhs) +{ + p_struct = (alglib_impl::mnlreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mnlreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mnlreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mnlreport_owner& _mnlreport_owner::operator=(const _mnlreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mnlreport_clear(p_struct); + if( !alglib_impl::_mnlreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mnlreport_owner::~_mnlreport_owner() +{ + alglib_impl::_mnlreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mnlreport* _mnlreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mnlreport* _mnlreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mnlreport::mnlreport() : _mnlreport_owner() ,ngrad(p_struct->ngrad),nhess(p_struct->nhess) +{ +} + +mnlreport::mnlreport(const mnlreport &rhs):_mnlreport_owner(rhs) ,ngrad(p_struct->ngrad),nhess(p_struct->nhess) +{ +} + +mnlreport& mnlreport::operator=(const mnlreport &rhs) +{ + if( this==&rhs ) + return *this; + _mnlreport_owner::operator=(rhs); + return *this; +} + +mnlreport::~mnlreport() +{ +} + +/************************************************************************* +This subroutine trains logit model. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars] + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints(xy.c_ptr()), npoints, nvars, nclasses, &info, const_cast(lm.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + LM - logit model, passed by non-constant reference + (some fields of structure are used as temporaries + when calculating model output). + X - input vector, array[0..NVars-1]. + Y - (possibly) preallocated buffer; if size of Y is less than + NClasses, it will be reallocated.If it is large enough, it + is NOT reallocated, so we can save some time on reallocation. + +OUTPUT PARAMETERS: + Y - result, array[0..NClasses-1] + Vector of posterior probabilities for classification task. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlprocess(const logitmodel &lm, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mnlprocess(const_cast(lm.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +'interactive' variant of MNLProcess for languages like Python which +support constructs like "Y = MNLProcess(LM,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlprocessi(const logitmodel &lm, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mnlprocessi(const_cast(lm.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacks coefficients of logit model. Logit model have form: + + P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1)) + S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]), when i(lm.c_ptr()), const_cast(a.c_ptr()), &nvars, &nclasses, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +"Packs" coefficients and creates logit model in ALGLIB format (MNLUnpack +reversed). + +INPUT PARAMETERS: + A - model (see MNLUnpack) + NVars - number of independent variables + NClasses - number of classes + +OUTPUT PARAMETERS: + LM - logit model. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlpack(const real_2d_array &a, const ae_int_t nvars, const ae_int_t nclasses, logitmodel &lm) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mnlpack(const_cast(a.c_ptr()), nvars, nclasses, const_cast(lm.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*ln(2)). + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgce(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mnlavgce(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlrelclserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mnlrelclserror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + root mean square error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlrmserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mnlrmserror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + average error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgerror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mnlavgerror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + average relative error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgrelerror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t ssize) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mnlavgrelerror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), ssize, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Classification error on test set = MNLRelClsError*NPoints + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mnlclserror(const logitmodel &lm, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::mnlclserror(const_cast(lm.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This structure is a MCPD (Markov Chains for Population Data) solver. + +You should use ALGLIB functions in order to work with this object. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +_mcpdstate_owner::_mcpdstate_owner() +{ + p_struct = (alglib_impl::mcpdstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mcpdstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mcpdstate_owner::_mcpdstate_owner(const _mcpdstate_owner &rhs) +{ + p_struct = (alglib_impl::mcpdstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mcpdstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mcpdstate_owner& _mcpdstate_owner::operator=(const _mcpdstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mcpdstate_clear(p_struct); + if( !alglib_impl::_mcpdstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mcpdstate_owner::~_mcpdstate_owner() +{ + alglib_impl::_mcpdstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mcpdstate* _mcpdstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mcpdstate* _mcpdstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mcpdstate::mcpdstate() : _mcpdstate_owner() +{ +} + +mcpdstate::mcpdstate(const mcpdstate &rhs):_mcpdstate_owner(rhs) +{ +} + +mcpdstate& mcpdstate::operator=(const mcpdstate &rhs) +{ + if( this==&rhs ) + return *this; + _mcpdstate_owner::operator=(rhs); + return *this; +} + +mcpdstate::~mcpdstate() +{ +} + + +/************************************************************************* +This structure is a MCPD training report: + InnerIterationsCount - number of inner iterations of the + underlying optimization algorithm + OuterIterationsCount - number of outer iterations of the + underlying optimization algorithm + NFEV - number of merit function evaluations + TerminationType - termination type + (same as for MinBLEIC optimizer, positive + values denote success, negative ones - + failure) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +_mcpdreport_owner::_mcpdreport_owner() +{ + p_struct = (alglib_impl::mcpdreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mcpdreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mcpdreport_owner::_mcpdreport_owner(const _mcpdreport_owner &rhs) +{ + p_struct = (alglib_impl::mcpdreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mcpdreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mcpdreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mcpdreport_owner& _mcpdreport_owner::operator=(const _mcpdreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mcpdreport_clear(p_struct); + if( !alglib_impl::_mcpdreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mcpdreport_owner::~_mcpdreport_owner() +{ + alglib_impl::_mcpdreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mcpdreport* _mcpdreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mcpdreport* _mcpdreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mcpdreport::mcpdreport() : _mcpdreport_owner() ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype) +{ +} + +mcpdreport::mcpdreport(const mcpdreport &rhs):_mcpdreport_owner(rhs) ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype) +{ +} + +mcpdreport& mcpdreport::operator=(const mcpdreport &rhs) +{ + if( this==&rhs ) + return *this; + _mcpdreport_owner::operator=(rhs); + return *this; +} + +mcpdreport::~mcpdreport() +{ +} + +/************************************************************************* +DESCRIPTION: + +This function creates MCPD (Markov Chains for Population Data) solver. + +This solver can be used to find transition matrix P for N-dimensional +prediction problem where transition from X[i] to X[i+1] is modelled as + X[i+1] = P*X[i] +where X[i] and X[i+1] are N-dimensional population vectors (components of +each X are non-negative), and P is a N*N transition matrix (elements of P +are non-negative, each column sums to 1.0). + +Such models arise when when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is constant, i.e. there is no new individuals and no one + leaves population +* you want to model transitions of individuals from one state into another + +USAGE: + +Here we give very brief outline of the MCPD. We strongly recommend you to +read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on data analysis which is available at http://www.alglib.net/dataanalysis/ + +1. User initializes algorithm state with MCPDCreate() call + +2. User adds one or more tracks - sequences of states which describe + evolution of a system being modelled from different starting conditions + +3. User may add optional boundary, equality and/or linear constraints on + the coefficients of P by calling one of the following functions: + * MCPDSetEC() to set equality constraints + * MCPDSetBC() to set bound constraints + * MCPDSetLC() to set linear constraints + +4. Optionally, user may set custom weights for prediction errors (by + default, algorithm assigns non-equal, automatically chosen weights for + errors in the prediction of different components of X). It can be done + with a call of MCPDSetPredictionWeights() function. + +5. User calls MCPDSolve() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +6. User calls MCPDResults() to get solution + +INPUT PARAMETERS: + N - problem dimension, N>=1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreate(const ae_int_t n, mcpdstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdcreate(n, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "entry" state and is treated +in a special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +Such conditions basically mean that row of P which corresponds to "entry" +state is zero. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - at every moment of time there is some + (unpredictable) amount of "new" individuals, which can transit into one + of the states at the next turn, but still no one leaves population +* you want to model transitions of individuals from one state into another +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentry(const ae_int_t n, const ae_int_t entrystate, mcpdstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdcreateentry(n, entrystate, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Exit-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "exit" state and is treated +in a special way: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that column of P which corresponds to +"exit" state is zero. Multiplication by such P may decrease sum of vector +components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - individuals can move into "exit" state + and leave population at the next turn, but there are no new individuals +* amount of individuals which leave population can be predicted +* you want to model transitions of individuals from one state into another + (including transitions into the "exit" state) + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateexit(const ae_int_t n, const ae_int_t exitstate, mcpdstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdcreateexit(n, exitstate, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-Exit-states" model, i.e. model where transition from X[i] to +X[i+1] is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +one selected component of X[] is called "entry" state and is treated in a +special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +and another one component of X[] is called "exit" state and is treated in +a special way too: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that: + row of P which corresponds to "entry" state is zero + column of P which corresponds to "exit" state is zero +Multiplication by such P may decrease sum of vector components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant +* at every moment of time there is some (unpredictable) amount of "new" + individuals, which can transit into one of the states at the next turn +* some individuals can move (predictably) into "exit" state and leave + population at the next turn +* you want to model transitions of individuals from one state into another, + including transitions from the "entry" state and into the "exit" state. +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentryexit(const ae_int_t n, const ae_int_t entrystate, const ae_int_t exitstate, mcpdstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdcreateentryexit(n, entrystate, exitstate, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add a track - sequence of system states at the +different moments of its evolution. + +You may add one or several tracks to the MCPD solver. In case you have +several tracks, they won't overwrite each other. For example, if you pass +two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then +solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, +t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it +wont try to model transition from t=A+3 to t=B+1. + +INPUT PARAMETERS: + S - solver + XY - track, array[K,N]: + * I-th row is a state at t=I + * elements of XY must be non-negative (exception will be + thrown on negative elements) + K - number of points in a track + * if given, only leading K rows of XY are used + * if not given, automatically determined from size of XY + +NOTES: + +1. Track may contain either proportional or population data: + * with proportional data all rows of XY must sum to 1.0, i.e. we have + proportions instead of absolute population values + * with population data rows of XY contain population counts and generally + do not sum to 1.0 (although they still must be non-negative) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdaddtrack(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add a track - sequence of system states at the +different moments of its evolution. + +You may add one or several tracks to the MCPD solver. In case you have +several tracks, they won't overwrite each other. For example, if you pass +two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then +solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, +t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it +wont try to model transition from t=A+3 to t=B+1. + +INPUT PARAMETERS: + S - solver + XY - track, array[K,N]: + * I-th row is a state at t=I + * elements of XY must be non-negative (exception will be + thrown on negative elements) + K - number of points in a track + * if given, only leading K rows of XY are used + * if not given, automatically determined from size of XY + +NOTES: + +1. Track may contain either proportional or population data: + * with proportional data all rows of XY must sum to 1.0, i.e. we have + proportions instead of absolute population values + * with population data rows of XY contain population counts and generally + do not sum to 1.0 (although they still must be non-negative) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t k; + + k = xy.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdaddtrack(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place equality constraints on arbitrary +subset of elements of P. Set of constraints is specified by EC, which may +contain either NAN's or finite numbers from [0,1]. NAN denotes absence of +constraint, finite number denotes equality constraint on specific element +of P. + +You can also use MCPDAddEC() function which allows to ADD equality +constraint for one element of P without changing constraints for other +elements. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + EC - equality constraints, array[N,N]. Elements of EC can be + either NAN's or finite numbers from [0,1]. NAN denotes + absence of constraints, while finite value denotes + equality constraint on the corresponding element of P. + +NOTES: + +1. infinite values of EC will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetec(const mcpdstate &s, const real_2d_array &ec) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsetec(const_cast(s.c_ptr()), const_cast(ec.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD equality constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetEC() function which allows you to specify +arbitrary set of equality constraints in one call. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + I - row index of element being constrained + J - column index of element being constrained + C - value (constraint for P[I,J]). Can be either NAN (no + constraint) or finite value from [0,1]. + +NOTES: + +1. infinite values of C will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddec(const mcpdstate &s, const ae_int_t i, const ae_int_t j, const double c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdaddec(const_cast(s.c_ptr()), i, j, c, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add bound constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place bound constraints on arbitrary +subset of elements of P. Set of constraints is specified by BndL/BndU +matrices, which may contain arbitrary combination of finite numbers or +infinities (like -INF(s.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to add bound constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD bound constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetBC() function which allows to place bound +constraints on arbitrary subset of elements of P. Set of constraints is +specified by BndL/BndU matrices, which may contain arbitrary combination +of finite numbers or infinities (like -INF(s.c_ptr()), i, j, bndl, bndu, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to set linear equality/inequality constraints on the +elements of the transition matrix P. + +This function can be used to set one or several general linear constraints +on the elements of P. Two types of constraints are supported: +* equality constraints +* inequality constraints (both less-or-equal and greater-or-equal) + +Coefficients of constraints are specified by matrix C (one of the +parameters). One row of C corresponds to one constraint. Because +transition matrix P has N*N elements, we need N*N columns to store all +coefficients (they are stored row by row), and one more column to store +right part - hence C has N*N+1 columns. Constraint kind is stored in the +CT array. + +Thus, I-th linear constraint is + P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] + + + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... + + + P[N-1,N-1]*C[I,N*N-1] ?=? C[I,N*N] +where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" (CT[i]>0). + +Your constraint may involve only some subset of P (less than N*N elements). +For example it can be something like + P[0,0] + P[0,1] = 0.5 +In this case you still should pass matrix with N*N+1 columns, but all its +elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero. + +INPUT PARAMETERS: + S - solver + C - array[K,N*N+1] - coefficients of constraints + (see above for complete description) + CT - array[K] - constraint types + (see above for complete description) + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsetlc(const_cast(s.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to set linear equality/inequality constraints on the +elements of the transition matrix P. + +This function can be used to set one or several general linear constraints +on the elements of P. Two types of constraints are supported: +* equality constraints +* inequality constraints (both less-or-equal and greater-or-equal) + +Coefficients of constraints are specified by matrix C (one of the +parameters). One row of C corresponds to one constraint. Because +transition matrix P has N*N elements, we need N*N columns to store all +coefficients (they are stored row by row), and one more column to store +right part - hence C has N*N+1 columns. Constraint kind is stored in the +CT array. + +Thus, I-th linear constraint is + P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] + + + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... + + + P[N-1,N-1]*C[I,N*N-1] ?=? C[I,N*N] +where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" (CT[i]>0). + +Your constraint may involve only some subset of P (less than N*N elements). +For example it can be something like + P[0,0] + P[0,1] = 0.5 +In this case you still should pass matrix with N*N+1 columns, but all its +elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero. + +INPUT PARAMETERS: + S - solver + C - array[K,N*N+1] - coefficients of constraints + (see above for complete description) + CT - array[K] - constraint types + (see above for complete description) + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t k; + if( (c.rows()!=ct.length())) + throw ap_error("Error while calling 'mcpdsetlc': looks like one of arguments has wrong size"); + k = c.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsetlc(const_cast(s.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function allows to tune amount of Tikhonov regularization being +applied to your problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change coefficient r. You can also change +prior values with MCPDSetPrior() function. + +INPUT PARAMETERS: + S - solver + V - regularization coefficient, finite non-negative value. It + is not recommended to specify zero value unless you are + pretty sure that you want it. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsettikhonovregularizer(const mcpdstate &s, const double v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsettikhonovregularizer(const_cast(s.c_ptr()), v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function allows to set prior values used for regularization of your +problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change prior values prior_P. You can also +change r with MCPDSetTikhonovRegularizer() function. + +INPUT PARAMETERS: + S - solver + PP - array[N,N], matrix of prior values: + 1. elements must be real numbers from [0,1] + 2. columns must sum to 1.0. + First property is checked (exception is thrown otherwise), + while second one is not checked/enforced. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetprior(const mcpdstate &s, const real_2d_array &pp) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsetprior(const_cast(s.c_ptr()), const_cast(pp.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to change prediction weights + +MCPD solver scales prediction errors as follows + Error(P) = ||W*(y-P*x)||^2 +where + x is a system state at time t + y is a system state at time t+1 + P is a transition matrix + W is a diagonal scaling matrix + +By default, weights are chosen in order to minimize relative prediction +error instead of absolute one. For example, if one component of state is +about 0.5 in magnitude and another one is about 0.05, then algorithm will +make corresponding weights equal to 2.0 and 20.0. + +INPUT PARAMETERS: + S - solver + PW - array[N], weights: + * must be non-negative values (exception will be thrown otherwise) + * zero values will be replaced by automatically chosen values + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetpredictionweights(const mcpdstate &s, const real_1d_array &pw) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsetpredictionweights(const_cast(s.c_ptr()), const_cast(pw.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to start solution of the MCPD problem. + +After return from this function, you can use MCPDResults() to get solution +and completion code. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsolve(const mcpdstate &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdsolve(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +MCPD results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + P - array[N,N], transition matrix + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one. Speaking short, positive values denote + success, negative ones are failures. + More information about fields of this structure can be + found in the comments on MCPDReport datatype. + + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdresults(const mcpdstate &s, real_2d_array &p, mcpdreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mcpdresults(const_cast(s.c_ptr()), const_cast(p.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Neural networks ensemble +*************************************************************************/ +_mlpensemble_owner::_mlpensemble_owner() +{ + p_struct = (alglib_impl::mlpensemble*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpensemble), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpensemble_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpensemble_owner::_mlpensemble_owner(const _mlpensemble_owner &rhs) +{ + p_struct = (alglib_impl::mlpensemble*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpensemble), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpensemble_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpensemble_owner& _mlpensemble_owner::operator=(const _mlpensemble_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mlpensemble_clear(p_struct); + if( !alglib_impl::_mlpensemble_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mlpensemble_owner::~_mlpensemble_owner() +{ + alglib_impl::_mlpensemble_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mlpensemble* _mlpensemble_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mlpensemble* _mlpensemble_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mlpensemble::mlpensemble() : _mlpensemble_owner() +{ +} + +mlpensemble::mlpensemble(const mlpensemble &rhs):_mlpensemble_owner(rhs) +{ +} + +mlpensemble& mlpensemble::operator=(const mlpensemble &rhs) +{ + if( this==&rhs ) + return *this; + _mlpensemble_owner::operator=(rhs); + return *this; +} + +mlpensemble::~mlpensemble() +{ +} + + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void mlpeserialize(mlpensemble &obj, std::string &s_out) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + alglib_impl::ae_int_t ssize; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_alloc_start(&serializer); + alglib_impl::mlpealloc(&serializer, obj.c_ptr(), &state); + ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer); + s_out.clear(); + s_out.reserve((size_t)(ssize+1)); + alglib_impl::ae_serializer_sstart_str(&serializer, &s_out); + alglib_impl::mlpeserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + if( s_out.length()>(size_t)ssize ) + throw ap_error("ALGLIB: serialization integrity error"); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void mlpeunserialize(std::string &s_in, mlpensemble &obj) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_ustart_str(&serializer, &s_in); + alglib_impl::mlpeunserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} + +/************************************************************************* +Like MLPCreate0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreate0(nin, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreate1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreate1(nin, nhid, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreate2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreate2(nin, nhid1, nhid2, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateB0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreateb0(nin, nout, b, d, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateB1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreateb1(nin, nhid, nout, b, d, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateB2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreateb2(nin, nhid1, nhid2, nout, b, d, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateR0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreater0(nin, nout, a, b, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateR1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreater1(nin, nhid, nout, a, b, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateR2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreater2(nin, nhid1, nhid2, nout, a, b, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateC0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreatec0(nin, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateC1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreatec1(nin, nhid, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Like MLPCreateC2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreatec2(nin, nhid1, nhid2, nout, ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creates ensemble from network. Only network geometry is copied. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatefromnetwork(const multilayerperceptron &network, const ae_int_t ensemblesize, mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpecreatefromnetwork(const_cast(network.c_ptr()), ensemblesize, const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Randomization of MLP ensemble + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlperandomize(const mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlperandomize(const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Return ensemble properties (number of inputs and outputs). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeproperties(const mlpensemble &ensemble, ae_int_t &nin, ae_int_t &nout) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpeproperties(const_cast(ensemble.c_ptr()), &nin, &nout, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Return normalization type (whether ensemble is SOFTMAX-normalized or not). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +bool mlpeissoftmax(const mlpensemble &ensemble) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::mlpeissoftmax(const_cast(ensemble.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Ensemble- neural networks ensemble + X - input vector, array[0..NIn-1]. + Y - (possibly) preallocated buffer; if size of Y is less than + NOut, it will be reallocated. If it is large enough, it + is NOT reallocated, so we can save some time on reallocation. + + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocess(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpeprocess(const_cast(ensemble.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +'interactive' variant of MLPEProcess for languages like Python which +support constructs like "Y = MLPEProcess(LM,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocessi(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpeprocessi(const_cast(ensemble.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Works both for classifier betwork and for regression networks which +are used as classifiers. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlperelclserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlperelclserror(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if ensemble solves regression task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgce(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpeavgce(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for classification task +RMS error means error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpermserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpermserror(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpeavgerror(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average relative error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgrelerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::mlpeavgrelerror(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Training report: + * RelCLSError - fraction of misclassified cases. + * AvgCE - acerage cross-entropy + * RMSError - root-mean-square error + * AvgError - average error + * AvgRelError - average relative error + * NGrad - number of gradient calculations + * NHess - number of Hessian calculations + * NCholesky - number of Cholesky decompositions + +NOTE 1: RelCLSError/AvgCE are zero on regression problems. + +NOTE 2: on classification problems RMSError/AvgError/AvgRelError contain + errors in prediction of posterior probabilities +*************************************************************************/ +_mlpreport_owner::_mlpreport_owner() +{ + p_struct = (alglib_impl::mlpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpreport_owner::_mlpreport_owner(const _mlpreport_owner &rhs) +{ + p_struct = (alglib_impl::mlpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpreport_owner& _mlpreport_owner::operator=(const _mlpreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mlpreport_clear(p_struct); + if( !alglib_impl::_mlpreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mlpreport_owner::~_mlpreport_owner() +{ + alglib_impl::_mlpreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mlpreport* _mlpreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mlpreport* _mlpreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mlpreport::mlpreport() : _mlpreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky) +{ +} + +mlpreport::mlpreport(const mlpreport &rhs):_mlpreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky) +{ +} + +mlpreport& mlpreport::operator=(const mlpreport &rhs) +{ + if( this==&rhs ) + return *this; + _mlpreport_owner::operator=(rhs); + return *this; +} + +mlpreport::~mlpreport() +{ +} + + +/************************************************************************* +Cross-validation estimates of generalization error +*************************************************************************/ +_mlpcvreport_owner::_mlpcvreport_owner() +{ + p_struct = (alglib_impl::mlpcvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpcvreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpcvreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpcvreport_owner::_mlpcvreport_owner(const _mlpcvreport_owner &rhs) +{ + p_struct = (alglib_impl::mlpcvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlpcvreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlpcvreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlpcvreport_owner& _mlpcvreport_owner::operator=(const _mlpcvreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mlpcvreport_clear(p_struct); + if( !alglib_impl::_mlpcvreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mlpcvreport_owner::~_mlpcvreport_owner() +{ + alglib_impl::_mlpcvreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mlpcvreport* _mlpcvreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mlpcvreport* _mlpcvreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mlpcvreport::mlpcvreport() : _mlpcvreport_owner() ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror) +{ +} + +mlpcvreport::mlpcvreport(const mlpcvreport &rhs):_mlpcvreport_owner(rhs) ,relclserror(p_struct->relclserror),avgce(p_struct->avgce),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror) +{ +} + +mlpcvreport& mlpcvreport::operator=(const mlpcvreport &rhs) +{ + if( this==&rhs ) + return *this; + _mlpcvreport_owner::operator=(rhs); + return *this; +} + +mlpcvreport::~mlpcvreport() +{ +} + + +/************************************************************************* +Trainer object for neural network. + +You should not try to access fields of this object directly - use ALGLIB +functions to work with this object. +*************************************************************************/ +_mlptrainer_owner::_mlptrainer_owner() +{ + p_struct = (alglib_impl::mlptrainer*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlptrainer), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlptrainer_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlptrainer_owner::_mlptrainer_owner(const _mlptrainer_owner &rhs) +{ + p_struct = (alglib_impl::mlptrainer*)alglib_impl::ae_malloc(sizeof(alglib_impl::mlptrainer), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mlptrainer_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mlptrainer_owner& _mlptrainer_owner::operator=(const _mlptrainer_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mlptrainer_clear(p_struct); + if( !alglib_impl::_mlptrainer_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mlptrainer_owner::~_mlptrainer_owner() +{ + alglib_impl::_mlptrainer_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mlptrainer* _mlptrainer_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mlptrainer* _mlptrainer_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mlptrainer::mlptrainer() : _mlptrainer_owner() +{ +} + +mlptrainer::mlptrainer(const mlptrainer &rhs):_mlptrainer_owner(rhs) +{ +} + +mlptrainer& mlptrainer::operator=(const mlptrainer &rhs) +{ + if( this==&rhs ) + return *this; + _mlptrainer_owner::operator=(rhs); + return *this; +} + +mlptrainer::~mlptrainer() +{ +} + +/************************************************************************* +Neural network training using modified Levenberg-Marquardt with exact +Hessian calculation and regularization. Subroutine trains neural network +with restarts from random positions. Algorithm is well suited for small +and medium scale problems (hundreds of weights). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -9, if internal matrix inverse subroutine failed + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlptrainlm(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Neural network training using L-BFGS algorithm with regularization. +Subroutine trains neural network with restarts from random positions. +Algorithm is well suited for problems of any dimensionality (memory +requirements and step complexity are linear by weights number). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + MaxIts - stopping criterion. Algorithm stops after MaxIts + iterations (NOT gradient calculations). Zero MaxIts + means stopping when step is sufficiently small. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlptrainlbfgs(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Neural network training using early stopping (base algorithm - L-BFGS with +regularization). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + TrnXY - training set + TrnSize - training set size, TrnSize>0 + ValXY - validation set + ValSize - validation set size, ValSize>0 + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts, either: + * strictly positive number - algorithm make specified + number of restarts from random position. + * -1, in which case algorithm makes exactly one run + from the initial state of the network (no randomization). + If you don't know what Restarts to choose, choose one + one the following: + * -1 (deterministic start) + * +1 (one random restart) + * +5 (moderate amount of random restarts) + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1, ...). + * 2, task has been solved, stopping criterion met - + sufficiently small step size. Not expected (we + use EARLY stopping) but possible and not an + error. + * 6, task has been solved, stopping criterion met - + increasing of validation set error. + Rep - training report + +NOTE: + +Algorithm stops if validation set error increases for a long enough or +step size is small enought (there are task where validation set may +decrease for eternity). In any case solution returned corresponds to the +minimum of validation set error. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptraines(const multilayerperceptron &network, const real_2d_array &trnxy, const ae_int_t trnsize, const real_2d_array &valxy, const ae_int_t valsize, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlptraines(const_cast(network.c_ptr()), const_cast(trnxy.c_ptr()), trnsize, const_cast(valxy.c_ptr()), valsize, decay, restarts, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - L-BFGS. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpkfoldcvlbfgs(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, foldscount, &info, const_cast(rep.c_ptr()), const_cast(cvrep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - Levenberg-Marquardt. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpkfoldcvlm(const_cast(network.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, foldscount, &info, const_cast(rep.c_ptr()), const_cast(cvrep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function estimates generalization error using cross-validation on the +current dataset with current training settings. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * FoldsCount cross-validation rounds (always) + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. Network is not changed during cross- + validation and is not trained - it is used only as + representative of its architecture. I.e., we estimate + generalization properties of ARCHITECTURE, not some + specific network. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that for each cross-validation + round specified number of random restarts is + performed, with best network being chosen after + training. + * NRestarts=0 is same as NRestarts=1 + FoldsCount - number of folds in k-fold cross-validation: + * 2<=FoldsCount<=size of dataset + * recommended value: 10. + * values larger than dataset size will be silently + truncated down to dataset size + +OUTPUT PARAMETERS: + Rep - structure which contains cross-validation estimates: + * Rep.RelCLSError - fraction of misclassified cases. + * Rep.AvgCE - acerage cross-entropy + * Rep.RMSError - root-mean-square error + * Rep.AvgError - average error + * Rep.AvgRelError - average relative error + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or subset with only one point was given, zeros are returned as + estimates. + +NOTE: this method performs FoldsCount cross-validation rounds, each one + with NRestarts random starts. Thus, FoldsCount*NRestarts networks + are trained in total. + +NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems. + +NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError + contain errors in prediction of posterior probabilities. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpkfoldcv(const_cast(s.c_ptr()), const_cast(network.c_ptr()), nrestarts, foldscount, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlpkfoldcv(const_cast(s.c_ptr()), const_cast(network.c_ptr()), nrestarts, foldscount, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creation of the network trainer object for regression networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NOut - number of outputs, NOut>=1 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any regression + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainer(const ae_int_t nin, const ae_int_t nout, mlptrainer &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreatetrainer(nin, nout, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Creation of the network trainer object for classification networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any classification + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainercls(const ae_int_t nin, const ae_int_t nclasses, mlptrainer &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpcreatetrainercls(nin, nclasses, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user. + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. + NPoints - points count, >=0. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdataset(const mlptrainer &s, const real_2d_array &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetdataset(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user (sparse matrix is used to store dataset). + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Any sparse storage format can be used: + Hash-table, CRS... + NPoints - points count, >=0 + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetsparsedataset(const mlptrainer &s, const sparsematrix &xy, const ae_int_t npoints) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetsparsedataset(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), npoints, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets weight decay coefficient which is used for training. + +INPUT PARAMETERS: + S - trainer object + Decay - weight decay coefficient, >=0. Weight decay term + 'Decay*||Weights||^2' is added to error function. If + you don't know what Decay to choose, use 1.0E-3. + Weight decay can be set to zero, in this case network + is trained without weight decay. + +NOTE: by default network uses some small nonzero value for weight decay. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdecay(const mlptrainer &s, const double decay) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetdecay(const_cast(s.c_ptr()), decay, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping criteria for the optimizer. + +INPUT PARAMETERS: + S - trainer object + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + WStep>=0. + MaxIts - stopping criterion. Algorithm stops after MaxIts + epochs (full passes over entire dataset). Zero MaxIts + means stopping when step is sufficiently small. + MaxIts>=0. + +NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values are also + used when MLPSetCond() is called with WStep=0 and MaxIts=0. + +NOTE: these stopping criteria are used for all kinds of neural training - + from "conventional" networks to early stopping ensembles. When used + for "conventional" networks, they are used as the only stopping + criteria. When combined with early stopping, they used as ADDITIONAL + stopping criteria which can terminate early stopping algorithm. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetcond(const mlptrainer &s, const double wstep, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetcond(const_cast(s.c_ptr()), wstep, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets training algorithm: batch training using L-BFGS will be +used. + +This algorithm: +* the most robust for small-scale problems, but may be too slow for large + scale ones. +* perfoms full pass through the dataset before performing step +* uses conditions specified by MLPSetCond() for stopping +* is default one used by trainer object + +INPUT PARAMETERS: + S - trainer object + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetalgobatch(const mlptrainer &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpsetalgobatch(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function trains neural network passed to this function, using current +dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset()) +and current training settings. Training from NRestarts random starting +positions is performed, best network is chosen. + +Training is performed using current training algorithm. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed, best network is chosen after + training + * NRestarts=0 means that current state of the network + is used for training. + +OUTPUT PARAMETERS: + Network - trained network + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + network is filled by zero values. Same behavior for functions + MLPStartTraining and MLPContinueTraining. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainnetwork(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlptrainnetwork(const_cast(s.c_ptr()), const_cast(network.c_ptr()), nrestarts, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlptrainnetwork(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlptrainnetwork(const_cast(s.c_ptr()), const_cast(network.c_ptr()), nrestarts, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +After call to this function trainer object remembers network and is ready +to train it. However, no training is performed until first call to +MLPContinueTraining() function. Subsequent calls to MLPContinueTraining() +will advance training progress one iteration further. + +EXAMPLE: + > + > ...initialize network and trainer object.... + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > ...visualize training progress... + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + RandomStart - randomize network before training or not: + * True means that network is randomized and its + initial state (one which was passed to the trainer + object) is lost. + * False means that training is started from the + current state of the network + +OUTPUT PARAMETERS: + Network - neural network which is ready to training (weights are + initialized, preprocessor is initialized using current + training set) + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpstarttraining(const mlptrainer &s, const multilayerperceptron &network, const bool randomstart) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpstarttraining(const_cast(s.c_ptr()), const_cast(network.c_ptr()), randomstart, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +This function performs one more iteration of the training and returns +either True (training continues) or False (training stopped). In case True +was returned, Network weights are updated according to the current state +of the optimization progress. In case False was returned, no additional +updates is performed (previous update of the network weights moved us to +the final point, and no additional updates is needed). + +EXAMPLE: + > + > [initialize network and trainer object] + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > [visualize training progress] + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network structure, which is used to store + current state of the training process. + +OUTPUT PARAMETERS: + Network - weights of the neural network are rewritten by the + current approximation. + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + +NOTE: It is expected that Network is the same one which was passed to + MLPStartTraining() function. However, THIS function checks only + following: + * that number of network inputs is consistent with trainer object + settings + * that number of network outputs/classes is consistent with trainer + object settings + * that number of network weights is the same as number of weights in + the network passed to MLPStartTraining() function + Exception is thrown when these conditions are violated. + + It is also expected that you do not change state of the network on + your own - the only party who has right to change network during its + training is a trainer object. Any attempt to interfere with trainer + may lead to unpredictable results. + + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +bool mlpcontinuetraining(const mlptrainer &s, const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::mlpcontinuetraining(const_cast(s.c_ptr()), const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +bool smp_mlpcontinuetraining(const mlptrainer &s, const multilayerperceptron &network) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::_pexec_mlpcontinuetraining(const_cast(s.c_ptr()), const_cast(network.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +Modified Levenberg-Marquardt algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglm(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpebagginglm(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, &info, const_cast(rep.c_ptr()), const_cast(ooberrors.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +L-BFGS algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglbfgs(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpebagginglbfgs(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, wstep, maxits, &info, const_cast(rep.c_ptr()), const_cast(ooberrors.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Training neural networks ensemble using early stopping. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 6, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpetraines(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlpetraines(const_cast(ensemble.c_ptr()), const_cast(xy.c_ptr()), npoints, decay, restarts, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function trains neural network ensemble passed to this function using +current dataset and early stopping training algorithm. Each early stopping +round performs NRestarts random restarts (thus, EnsembleSize*NRestarts +training rounds is performed in total). + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * EnsembleSize training sessions performed for each of ensemble + ! members (always parallelized) + ! * NRestarts training sessions performed within each of training + ! sessions (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object; + Ensemble - neural network ensemble. It must have same number of + inputs and outputs/classes as was specified during + creation of the trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed during each ES round; + * NRestarts=0 is silently replaced by 1. + +OUTPUT PARAMETERS: + Ensemble - trained ensemble; + Rep - it contains all type of errors. + +NOTE: this training method uses BOTH early stopping and weight decay! So, + you should select weight decay before starting training just as you + select it before training "conventional" networks. + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or single-point dataset was passed, ensemble is filled by zero + values. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 22.08.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainensemblees(const mlptrainer &s, const mlpensemble &ensemble, const ae_int_t nrestarts, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mlptrainensemblees(const_cast(s.c_ptr()), const_cast(ensemble.c_ptr()), nrestarts, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_mlptrainensemblees(const mlptrainer &s, const mlpensemble &ensemble, const ae_int_t nrestarts, mlpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_mlptrainensemblees(const_cast(s.c_ptr()), const_cast(ensemble.c_ptr()), nrestarts, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Principal components analysis + +Subroutine builds orthogonal basis where first axis corresponds to +direction with maximum variance, second axis maximizes variance in subspace +orthogonal to first axis and so on. + +It should be noted that, unlike LDA, PCA does not use class labels. + +INPUT PARAMETERS: + X - dataset, array[0..NPoints-1,0..NVars-1]. + matrix contains ONLY INDEPENDENT VARIABLES. + NPoints - dataset size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + +ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ: + Info - return code: + * -4, if SVD subroutine haven't converged + * -1, if wrong parameters has been passed (NPoints<0, + NVars<1) + * 1, if task is solved + S2 - array[0..NVars-1]. variance values corresponding + to basis vectors. + V - array[0..NVars-1,0..NVars-1] + matrix, whose columns store basis vectors. + + -- ALGLIB -- + Copyright 25.08.2008 by Bochkanov Sergey +*************************************************************************/ +void pcabuildbasis(const real_2d_array &x, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, real_1d_array &s2, real_2d_array &v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pcabuildbasis(const_cast(x.c_ptr()), npoints, nvars, &info, const_cast(s2.c_ptr()), const_cast(v.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static double bdss_xlny(double x, double y, ae_state *_state); +static double bdss_getcv(/* Integer */ ae_vector* cnt, + ae_int_t nc, + ae_state *_state); +static void bdss_tieaddc(/* Integer */ ae_vector* c, + /* Integer */ ae_vector* ties, + ae_int_t ntie, + ae_int_t nc, + /* Integer */ ae_vector* cnt, + ae_state *_state); +static void bdss_tiesubc(/* Integer */ ae_vector* c, + /* Integer */ ae_vector* ties, + ae_int_t ntie, + ae_int_t nc, + /* Integer */ ae_vector* cnt, + ae_state *_state); + + +static ae_int_t clustering_parallelcomplexity = 200000; +static ae_bool clustering_selectcenterpp(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + /* Real */ ae_matrix* centers, + /* Boolean */ ae_vector* busycenters, + ae_int_t ccnt, + /* Real */ ae_vector* d2, + /* Real */ ae_vector* p, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void clustering_clusterizerrunahcinternal(clusterizerstate* s, + /* Real */ ae_matrix* d, + ahcreport* rep, + ae_state *_state); +static void clustering_evaluatedistancematrixrec(/* Real */ ae_matrix* xy, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, + ae_int_t i0, + ae_int_t i1, + ae_int_t j0, + ae_int_t j1, + ae_state *_state); + + + + +static ae_int_t dforest_innernodewidth = 3; +static ae_int_t dforest_leafnodewidth = 2; +static ae_int_t dforest_dfusestrongsplits = 1; +static ae_int_t dforest_dfuseevs = 2; +static ae_int_t dforest_dffirstversion = 0; +static ae_int_t dforest_dfclserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +static void dforest_dfprocessinternal(decisionforest* df, + ae_int_t offs, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +static void dforest_dfbuildtree(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t nfeatures, + ae_int_t nvarsinpool, + ae_int_t flags, + dfinternalbuffers* bufs, + hqrndstate* rs, + ae_state *_state); +static void dforest_dfbuildtreerec(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t nfeatures, + ae_int_t nvarsinpool, + ae_int_t flags, + ae_int_t* numprocessed, + ae_int_t idx1, + ae_int_t idx2, + dfinternalbuffers* bufs, + hqrndstate* rs, + ae_state *_state); +static void dforest_dfsplitc(/* Real */ ae_vector* x, + /* Integer */ ae_vector* c, + /* Integer */ ae_vector* cntbuf, + ae_int_t n, + ae_int_t nc, + ae_int_t flags, + ae_int_t* info, + double* threshold, + double* e, + /* Real */ ae_vector* sortrbuf, + /* Integer */ ae_vector* sortibuf, + ae_state *_state); +static void dforest_dfsplitr(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t flags, + ae_int_t* info, + double* threshold, + double* e, + /* Real */ ae_vector* sortrbuf, + /* Real */ ae_vector* sortrbuf2, + ae_state *_state); + + +static ae_int_t linreg_lrvnum = 5; +static void linreg_lrinternal(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state); + + + + + + +static ae_int_t mlpbase_mlpvnum = 7; +static ae_int_t mlpbase_mlpfirstversion = 0; +static ae_int_t mlpbase_nfieldwidth = 4; +static ae_int_t mlpbase_hlconnfieldwidth = 5; +static ae_int_t mlpbase_hlnfieldwidth = 4; +static ae_int_t mlpbase_gradbasecasecost = 50000; +static ae_int_t mlpbase_microbatchsize = 64; +static void mlpbase_addinputlayer(ae_int_t ncount, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state); +static void mlpbase_addbiasedsummatorlayer(ae_int_t ncount, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state); +static void mlpbase_addactivationlayer(ae_int_t functype, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state); +static void mlpbase_addzerolayer(/* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state); +static void mlpbase_hladdinputlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t nin, + ae_state *_state); +static void mlpbase_hladdoutputlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t* weightsidx, + ae_int_t k, + ae_int_t nprev, + ae_int_t nout, + ae_bool iscls, + ae_bool islinearout, + ae_state *_state); +static void mlpbase_hladdhiddenlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t* weightsidx, + ae_int_t k, + ae_int_t nprev, + ae_int_t ncur, + ae_state *_state); +static void mlpbase_fillhighlevelinformation(multilayerperceptron* network, + ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_bool iscls, + ae_bool islinearout, + ae_state *_state); +static void mlpbase_mlpcreate(ae_int_t nin, + ae_int_t nout, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t layerscount, + ae_bool isclsnet, + multilayerperceptron* network, + ae_state *_state); +static void mlpbase_mlphessianbatchinternal(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_bool naturalerr, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state); +static void mlpbase_mlpinternalcalculategradient(multilayerperceptron* network, + /* Real */ ae_vector* neurons, + /* Real */ ae_vector* weights, + /* Real */ ae_vector* derror, + /* Real */ ae_vector* grad, + ae_bool naturalerrorfunc, + ae_state *_state); +static void mlpbase_mlpchunkedgradient(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + double* e, + ae_bool naturalerrorfunc, + ae_state *_state); +static void mlpbase_mlpchunkedprocess(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + ae_state *_state); +static double mlpbase_safecrossentropy(double t, + double z, + ae_state *_state); +static void mlpbase_randomizebackwardpass(multilayerperceptron* network, + ae_int_t neuronidx, + double v, + ae_state *_state); + + +static double logit_xtol = 100*ae_machineepsilon; +static double logit_ftol = 0.0001; +static double logit_gtol = 0.3; +static ae_int_t logit_maxfev = 20; +static double logit_stpmin = 1.0E-2; +static double logit_stpmax = 1.0E5; +static ae_int_t logit_logitvnum = 6; +static void logit_mnliexp(/* Real */ ae_vector* w, + /* Real */ ae_vector* x, + ae_state *_state); +static void logit_mnlallerrors(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double* relcls, + double* avgce, + double* rms, + double* avg, + double* avgrel, + ae_state *_state); +static void logit_mnlmcsrch(ae_int_t n, + /* Real */ ae_vector* x, + double* f, + /* Real */ ae_vector* g, + /* Real */ ae_vector* s, + double* stp, + ae_int_t* info, + ae_int_t* nfev, + /* Real */ ae_vector* wa, + logitmcstate* state, + ae_int_t* stage, + ae_state *_state); +static void logit_mnlmcstep(double* stx, + double* fx, + double* dx, + double* sty, + double* fy, + double* dy, + double* stp, + double fp, + double dp, + ae_bool* brackt, + double stmin, + double stmax, + ae_int_t* info, + ae_state *_state); + + +static double mcpd_xtol = 1.0E-8; +static void mcpd_mcpdinit(ae_int_t n, + ae_int_t entrystate, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state); + + +static ae_int_t mlpe_mlpefirstversion = 1; + + +static double mlptrain_mindecay = 0.001; +static ae_int_t mlptrain_defaultlbfgsfactor = 6; +static void mlptrain_mlpkfoldcvgeneral(multilayerperceptron* n, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t foldscount, + ae_bool lmalgorithm, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state); +static void mlptrain_mlpkfoldsplit(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nclasses, + ae_int_t foldscount, + ae_bool stratifiedsplits, + /* Integer */ ae_vector* folds, + ae_state *_state); +static void mlptrain_mthreadcv(mlptrainer* s, + ae_int_t rowsize, + ae_int_t nrestarts, + /* Integer */ ae_vector* folds, + ae_int_t fold, + ae_int_t dfold, + /* Real */ ae_matrix* cvy, + ae_shared_pool* pooldatacv, + ae_state *_state); +static void mlptrain_mlptrainnetworkx(mlptrainer* s, + ae_int_t nrestarts, + ae_int_t algokind, + /* Integer */ ae_vector* trnsubset, + ae_int_t trnsubsetsize, + /* Integer */ ae_vector* valsubset, + ae_int_t valsubsetsize, + multilayerperceptron* network, + mlpreport* rep, + ae_bool isrootcall, + ae_shared_pool* sessions, + ae_state *_state); +static void mlptrain_mlptrainensemblex(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t idx0, + ae_int_t idx1, + ae_int_t nrestarts, + ae_int_t trainingmethod, + sinteger* ngrad, + ae_bool isrootcall, + ae_shared_pool* esessions, + ae_state *_state); +static void mlptrain_mlpstarttrainingx(mlptrainer* s, + ae_bool randomstart, + ae_int_t algokind, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + smlptrnsession* session, + ae_state *_state); +static ae_bool mlptrain_mlpcontinuetrainingx(mlptrainer* s, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_int_t* ngradbatch, + smlptrnsession* session, + ae_state *_state); +static void mlptrain_mlpebagginginternal(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_bool lmalgorithm, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state); +static void mlptrain_initmlptrnsession(multilayerperceptron* networktrained, + ae_bool randomizenetwork, + mlptrainer* trainer, + smlptrnsession* session, + ae_state *_state); +static void mlptrain_initmlptrnsessions(multilayerperceptron* networktrained, + ae_bool randomizenetwork, + mlptrainer* trainer, + ae_shared_pool* sessions, + ae_state *_state); +static void mlptrain_initmlpetrnsession(multilayerperceptron* individualnetwork, + mlptrainer* trainer, + mlpetrnsession* session, + ae_state *_state); +static void mlptrain_initmlpetrnsessions(multilayerperceptron* individualnetwork, + mlptrainer* trainer, + ae_shared_pool* sessions, + ae_state *_state); + + + + + + + +/************************************************************************* +This set of routines (DSErrAllocate, DSErrAccumulate, DSErrFinish) +calculates different error functions (classification error, cross-entropy, +rms, avg, avg.rel errors). + +1. DSErrAllocate prepares buffer. +2. DSErrAccumulate accumulates individual errors: + * Y contains predicted output (posterior probabilities for classification) + * DesiredY contains desired output (class number for classification) +3. DSErrFinish outputs results: + * Buf[0] contains relative classification error (zero for regression tasks) + * Buf[1] contains avg. cross-entropy (zero for regression tasks) + * Buf[2] contains rms error (regression, classification) + * Buf[3] contains average error (regression, classification) + * Buf[4] contains average relative error (regression, classification) + +NOTES(1): + "NClasses>0" means that we have classification task. + "NClasses<0" means regression task with -NClasses real outputs. + +NOTES(2): + rms. avg, avg.rel errors for classification tasks are interpreted as + errors in posterior probabilities with respect to probabilities given + by training/test set. + + -- ALGLIB -- + Copyright 11.01.2009 by Bochkanov Sergey +*************************************************************************/ +void dserrallocate(ae_int_t nclasses, + /* Real */ ae_vector* buf, + ae_state *_state) +{ + + ae_vector_clear(buf); + + ae_vector_set_length(buf, 7+1, _state); + buf->ptr.p_double[0] = 0; + buf->ptr.p_double[1] = 0; + buf->ptr.p_double[2] = 0; + buf->ptr.p_double[3] = 0; + buf->ptr.p_double[4] = 0; + buf->ptr.p_double[5] = nclasses; + buf->ptr.p_double[6] = 0; + buf->ptr.p_double[7] = 0; +} + + +/************************************************************************* +See DSErrAllocate for comments on this routine. + + -- ALGLIB -- + Copyright 11.01.2009 by Bochkanov Sergey +*************************************************************************/ +void dserraccumulate(/* Real */ ae_vector* buf, + /* Real */ ae_vector* y, + /* Real */ ae_vector* desiredy, + ae_state *_state) +{ + ae_int_t nclasses; + ae_int_t nout; + ae_int_t offs; + ae_int_t mmax; + ae_int_t rmax; + ae_int_t j; + double v; + double ev; + + + offs = 5; + nclasses = ae_round(buf->ptr.p_double[offs], _state); + if( nclasses>0 ) + { + + /* + * Classification + */ + rmax = ae_round(desiredy->ptr.p_double[0], _state); + mmax = 0; + for(j=1; j<=nclasses-1; j++) + { + if( ae_fp_greater(y->ptr.p_double[j],y->ptr.p_double[mmax]) ) + { + mmax = j; + } + } + if( mmax!=rmax ) + { + buf->ptr.p_double[0] = buf->ptr.p_double[0]+1; + } + if( ae_fp_greater(y->ptr.p_double[rmax],0) ) + { + buf->ptr.p_double[1] = buf->ptr.p_double[1]-ae_log(y->ptr.p_double[rmax], _state); + } + else + { + buf->ptr.p_double[1] = buf->ptr.p_double[1]+ae_log(ae_maxrealnumber, _state); + } + for(j=0; j<=nclasses-1; j++) + { + v = y->ptr.p_double[j]; + if( j==rmax ) + { + ev = 1; + } + else + { + ev = 0; + } + buf->ptr.p_double[2] = buf->ptr.p_double[2]+ae_sqr(v-ev, _state); + buf->ptr.p_double[3] = buf->ptr.p_double[3]+ae_fabs(v-ev, _state); + if( ae_fp_neq(ev,0) ) + { + buf->ptr.p_double[4] = buf->ptr.p_double[4]+ae_fabs((v-ev)/ev, _state); + buf->ptr.p_double[offs+2] = buf->ptr.p_double[offs+2]+1; + } + } + buf->ptr.p_double[offs+1] = buf->ptr.p_double[offs+1]+1; + } + else + { + + /* + * Regression + */ + nout = -nclasses; + rmax = 0; + for(j=1; j<=nout-1; j++) + { + if( ae_fp_greater(desiredy->ptr.p_double[j],desiredy->ptr.p_double[rmax]) ) + { + rmax = j; + } + } + mmax = 0; + for(j=1; j<=nout-1; j++) + { + if( ae_fp_greater(y->ptr.p_double[j],y->ptr.p_double[mmax]) ) + { + mmax = j; + } + } + if( mmax!=rmax ) + { + buf->ptr.p_double[0] = buf->ptr.p_double[0]+1; + } + for(j=0; j<=nout-1; j++) + { + v = y->ptr.p_double[j]; + ev = desiredy->ptr.p_double[j]; + buf->ptr.p_double[2] = buf->ptr.p_double[2]+ae_sqr(v-ev, _state); + buf->ptr.p_double[3] = buf->ptr.p_double[3]+ae_fabs(v-ev, _state); + if( ae_fp_neq(ev,0) ) + { + buf->ptr.p_double[4] = buf->ptr.p_double[4]+ae_fabs((v-ev)/ev, _state); + buf->ptr.p_double[offs+2] = buf->ptr.p_double[offs+2]+1; + } + } + buf->ptr.p_double[offs+1] = buf->ptr.p_double[offs+1]+1; + } +} + + +/************************************************************************* +See DSErrAllocate for comments on this routine. + + -- ALGLIB -- + Copyright 11.01.2009 by Bochkanov Sergey +*************************************************************************/ +void dserrfinish(/* Real */ ae_vector* buf, ae_state *_state) +{ + ae_int_t nout; + ae_int_t offs; + + + offs = 5; + nout = ae_iabs(ae_round(buf->ptr.p_double[offs], _state), _state); + if( ae_fp_neq(buf->ptr.p_double[offs+1],0) ) + { + buf->ptr.p_double[0] = buf->ptr.p_double[0]/buf->ptr.p_double[offs+1]; + buf->ptr.p_double[1] = buf->ptr.p_double[1]/buf->ptr.p_double[offs+1]; + buf->ptr.p_double[2] = ae_sqrt(buf->ptr.p_double[2]/(nout*buf->ptr.p_double[offs+1]), _state); + buf->ptr.p_double[3] = buf->ptr.p_double[3]/(nout*buf->ptr.p_double[offs+1]); + } + if( ae_fp_neq(buf->ptr.p_double[offs+2],0) ) + { + buf->ptr.p_double[4] = buf->ptr.p_double[4]/buf->ptr.p_double[offs+2]; + } +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 19.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsnormalize(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* means, + /* Real */ ae_vector* sigmas, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector tmp; + double mean; + double variance; + double skewness; + double kurtosis; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(means); + ae_vector_clear(sigmas); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * Test parameters + */ + if( npoints<=0||nvars<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Standartization + */ + ae_vector_set_length(means, nvars-1+1, _state); + ae_vector_set_length(sigmas, nvars-1+1, _state); + ae_vector_set_length(&tmp, npoints-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1)); + samplemoments(&tmp, npoints, &mean, &variance, &skewness, &kurtosis, _state); + means->ptr.p_double[j] = mean; + sigmas->ptr.p_double[j] = ae_sqrt(variance, _state); + if( ae_fp_eq(sigmas->ptr.p_double[j],0) ) + { + sigmas->ptr.p_double[j] = 1; + } + for(i=0; i<=npoints-1; i++) + { + xy->ptr.pp_double[i][j] = (xy->ptr.pp_double[i][j]-means->ptr.p_double[j])/sigmas->ptr.p_double[j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 19.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsnormalizec(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* means, + /* Real */ ae_vector* sigmas, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t j; + ae_vector tmp; + double mean; + double variance; + double skewness; + double kurtosis; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(means); + ae_vector_clear(sigmas); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * Test parameters + */ + if( npoints<=0||nvars<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Standartization + */ + ae_vector_set_length(means, nvars-1+1, _state); + ae_vector_set_length(sigmas, nvars-1+1, _state); + ae_vector_set_length(&tmp, npoints-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1)); + samplemoments(&tmp, npoints, &mean, &variance, &skewness, &kurtosis, _state); + means->ptr.p_double[j] = mean; + sigmas->ptr.p_double[j] = ae_sqrt(variance, _state); + if( ae_fp_eq(sigmas->ptr.p_double[j],0) ) + { + sigmas->ptr.p_double[j] = 1; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 19.05.2008 by Bochkanov Sergey +*************************************************************************/ +double dsgetmeanmindistance(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector tmp; + ae_vector tmp2; + double v; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp2, 0, DT_REAL, _state, ae_true); + + + /* + * Test parameters + */ + if( npoints<=0||nvars<1 ) + { + result = 0; + ae_frame_leave(_state); + return result; + } + + /* + * Process + */ + ae_vector_set_length(&tmp, npoints-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + tmp.ptr.p_double[i] = ae_maxrealnumber; + } + ae_vector_set_length(&tmp2, nvars-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + for(j=i+1; j<=npoints-1; j++) + { + ae_v_move(&tmp2.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tmp2.ptr.p_double[0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + v = ae_v_dotproduct(&tmp2.ptr.p_double[0], 1, &tmp2.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + v = ae_sqrt(v, _state); + tmp.ptr.p_double[i] = ae_minreal(tmp.ptr.p_double[i], v, _state); + tmp.ptr.p_double[j] = ae_minreal(tmp.ptr.p_double[j], v, _state); + } + } + result = 0; + for(i=0; i<=npoints-1; i++) + { + result = result+tmp.ptr.p_double[i]/npoints; + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 19.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dstie(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* ties, + ae_int_t* tiecount, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(ties); + *tiecount = 0; + ae_vector_clear(p1); + ae_vector_clear(p2); + ae_vector_init(&tmp, 0, DT_INT, _state, ae_true); + + + /* + * Special case + */ + if( n<=0 ) + { + *tiecount = 0; + ae_frame_leave(_state); + return; + } + + /* + * Sort A + */ + tagsort(a, n, p1, p2, _state); + + /* + * Process ties + */ + *tiecount = 1; + for(i=1; i<=n-1; i++) + { + if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) ) + { + *tiecount = *tiecount+1; + } + } + ae_vector_set_length(ties, *tiecount+1, _state); + ties->ptr.p_int[0] = 0; + k = 1; + for(i=1; i<=n-1; i++) + { + if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) ) + { + ties->ptr.p_int[k] = i; + k = k+1; + } + } + ties->ptr.p_int[*tiecount] = n; + ae_frame_leave(_state); +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 11.12.2008 by Bochkanov Sergey +*************************************************************************/ +void dstiefasti(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t n, + /* Integer */ ae_vector* ties, + ae_int_t* tiecount, + /* Real */ ae_vector* bufr, + /* Integer */ ae_vector* bufi, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + *tiecount = 0; + ae_vector_init(&tmp, 0, DT_INT, _state, ae_true); + + + /* + * Special case + */ + if( n<=0 ) + { + *tiecount = 0; + ae_frame_leave(_state); + return; + } + + /* + * Sort A + */ + tagsortfasti(a, b, bufr, bufi, n, _state); + + /* + * Process ties + */ + ties->ptr.p_int[0] = 0; + k = 1; + for(i=1; i<=n-1; i++) + { + if( ae_fp_neq(a->ptr.p_double[i],a->ptr.p_double[i-1]) ) + { + ties->ptr.p_int[k] = i; + k = k+1; + } + } + ties->ptr.p_int[k] = n; + *tiecount = k; + ae_frame_leave(_state); +} + + +/************************************************************************* +Optimal binary classification + +Algorithms finds optimal (=with minimal cross-entropy) binary partition. +Internal subroutine. + +INPUT PARAMETERS: + A - array[0..N-1], variable + C - array[0..N-1], class numbers (0 or 1). + N - array size + +OUTPUT PARAMETERS: + Info - completetion code: + * -3, all values of A[] are same (partition is impossible) + * -2, one of C[] is incorrect (<0, >1) + * -1, incorrect pararemets were passed (N<=0). + * 1, OK + Threshold- partiton boundary. Left part contains values which are + strictly less than Threshold. Right part contains values + which are greater than or equal to Threshold. + PAL, PBL- probabilities P(0|v=Threshold) and P(1|v>=Threshold) + CVE - cross-validation estimate of cross-entropy + + -- ALGLIB -- + Copyright 22.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t* info, + double* threshold, + double* pal, + double* pbl, + double* par, + double* pbr, + double* cve, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _a; + ae_vector _c; + ae_int_t i; + ae_int_t t; + double s; + ae_vector ties; + ae_int_t tiecount; + ae_vector p1; + ae_vector p2; + ae_int_t k; + ae_int_t koptimal; + double pak; + double pbk; + double cvoptimal; + double cv; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init_copy(&_c, c, _state, ae_true); + c = &_c; + *info = 0; + *threshold = 0; + *pal = 0; + *pbl = 0; + *par = 0; + *pbr = 0; + *cve = 0; + ae_vector_init(&ties, 0, DT_INT, _state, ae_true); + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * Test for errors in inputs + */ + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + if( c->ptr.p_int[i]!=0&&c->ptr.p_int[i]!=1 ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Tie + */ + dstie(a, n, &ties, &tiecount, &p1, &p2, _state); + for(i=0; i<=n-1; i++) + { + if( p2.ptr.p_int[i]!=i ) + { + t = c->ptr.p_int[i]; + c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]]; + c->ptr.p_int[p2.ptr.p_int[i]] = t; + } + } + + /* + * Special case: number of ties is 1. + * + * NOTE: we assume that P[i,j] equals to 0 or 1, + * intermediate values are not allowed. + */ + if( tiecount==1 ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * General case, number of ties > 1 + * + * NOTE: we assume that P[i,j] equals to 0 or 1, + * intermediate values are not allowed. + */ + *pal = 0; + *pbl = 0; + *par = 0; + *pbr = 0; + for(i=0; i<=n-1; i++) + { + if( c->ptr.p_int[i]==0 ) + { + *par = *par+1; + } + if( c->ptr.p_int[i]==1 ) + { + *pbr = *pbr+1; + } + } + koptimal = -1; + cvoptimal = ae_maxrealnumber; + for(k=0; k<=tiecount-2; k++) + { + + /* + * first, obtain information about K-th tie which is + * moved from R-part to L-part + */ + pak = 0; + pbk = 0; + for(i=ties.ptr.p_int[k]; i<=ties.ptr.p_int[k+1]-1; i++) + { + if( c->ptr.p_int[i]==0 ) + { + pak = pak+1; + } + if( c->ptr.p_int[i]==1 ) + { + pbk = pbk+1; + } + } + + /* + * Calculate cross-validation CE + */ + cv = 0; + cv = cv-bdss_xlny(*pal+pak, (*pal+pak)/(*pal+pak+(*pbl)+pbk+1), _state); + cv = cv-bdss_xlny(*pbl+pbk, (*pbl+pbk)/(*pal+pak+1+(*pbl)+pbk), _state); + cv = cv-bdss_xlny(*par-pak, (*par-pak)/(*par-pak+(*pbr)-pbk+1), _state); + cv = cv-bdss_xlny(*pbr-pbk, (*pbr-pbk)/(*par-pak+1+(*pbr)-pbk), _state); + + /* + * Compare with best + */ + if( ae_fp_less(cv,cvoptimal) ) + { + cvoptimal = cv; + koptimal = k; + } + + /* + * update + */ + *pal = *pal+pak; + *pbl = *pbl+pbk; + *par = *par-pak; + *pbr = *pbr-pbk; + } + *cve = cvoptimal; + *threshold = 0.5*(a->ptr.p_double[ties.ptr.p_int[koptimal]]+a->ptr.p_double[ties.ptr.p_int[koptimal+1]]); + *pal = 0; + *pbl = 0; + *par = 0; + *pbr = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_less(a->ptr.p_double[i],*threshold) ) + { + if( c->ptr.p_int[i]==0 ) + { + *pal = *pal+1; + } + else + { + *pbl = *pbl+1; + } + } + else + { + if( c->ptr.p_int[i]==0 ) + { + *par = *par+1; + } + else + { + *pbr = *pbr+1; + } + } + } + s = *pal+(*pbl); + *pal = *pal/s; + *pbl = *pbl/s; + s = *par+(*pbr); + *par = *par/s; + *pbr = *pbr/s; + ae_frame_leave(_state); +} + + +/************************************************************************* +Optimal partition, internal subroutine. Fast version. + +Accepts: + A array[0..N-1] array of attributes array[0..N-1] + C array[0..N-1] array of class labels + TiesBuf array[0..N] temporaries (ties) + CntBuf array[0..2*NC-1] temporaries (counts) + Alpha centering factor (0<=alpha<=1, recommended value - 0.05) + BufR array[0..N-1] temporaries + BufI array[0..N-1] temporaries + +Output: + Info error code (">0"=OK, "<0"=bad) + RMS training set RMS error + CVRMS leave-one-out RMS error + +Note: + content of all arrays is changed by subroutine; + it doesn't allocate temporaries. + + -- ALGLIB -- + Copyright 11.12.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2fast(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + /* Integer */ ae_vector* tiesbuf, + /* Integer */ ae_vector* cntbuf, + /* Real */ ae_vector* bufr, + /* Integer */ ae_vector* bufi, + ae_int_t n, + ae_int_t nc, + double alpha, + ae_int_t* info, + double* threshold, + double* rms, + double* cvrms, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t cl; + ae_int_t tiecount; + double cbest; + double cc; + ae_int_t koptimal; + ae_int_t sl; + ae_int_t sr; + double v; + double w; + double x; + + *info = 0; + *threshold = 0; + *rms = 0; + *cvrms = 0; + + + /* + * Test for errors in inputs + */ + if( n<=0||nc<2 ) + { + *info = -1; + return; + } + for(i=0; i<=n-1; i++) + { + if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc ) + { + *info = -2; + return; + } + } + *info = 1; + + /* + * Tie + */ + dstiefasti(a, c, n, tiesbuf, &tiecount, bufr, bufi, _state); + + /* + * Special case: number of ties is 1. + */ + if( tiecount==1 ) + { + *info = -3; + return; + } + + /* + * General case, number of ties > 1 + */ + for(i=0; i<=2*nc-1; i++) + { + cntbuf->ptr.p_int[i] = 0; + } + for(i=0; i<=n-1; i++) + { + cntbuf->ptr.p_int[nc+c->ptr.p_int[i]] = cntbuf->ptr.p_int[nc+c->ptr.p_int[i]]+1; + } + koptimal = -1; + *threshold = a->ptr.p_double[n-1]; + cbest = ae_maxrealnumber; + sl = 0; + sr = n; + for(k=0; k<=tiecount-2; k++) + { + + /* + * first, move Kth tie from right to left + */ + for(i=tiesbuf->ptr.p_int[k]; i<=tiesbuf->ptr.p_int[k+1]-1; i++) + { + cl = c->ptr.p_int[i]; + cntbuf->ptr.p_int[cl] = cntbuf->ptr.p_int[cl]+1; + cntbuf->ptr.p_int[nc+cl] = cntbuf->ptr.p_int[nc+cl]-1; + } + sl = sl+(tiesbuf->ptr.p_int[k+1]-tiesbuf->ptr.p_int[k]); + sr = sr-(tiesbuf->ptr.p_int[k+1]-tiesbuf->ptr.p_int[k]); + + /* + * Calculate RMS error + */ + v = 0; + for(i=0; i<=nc-1; i++) + { + w = cntbuf->ptr.p_int[i]; + v = v+w*ae_sqr(w/sl-1, _state); + v = v+(sl-w)*ae_sqr(w/sl, _state); + w = cntbuf->ptr.p_int[nc+i]; + v = v+w*ae_sqr(w/sr-1, _state); + v = v+(sr-w)*ae_sqr(w/sr, _state); + } + v = ae_sqrt(v/(nc*n), _state); + + /* + * Compare with best + */ + x = (double)(2*sl)/(double)(sl+sr)-1; + cc = v*(1-alpha+alpha*ae_sqr(x, _state)); + if( ae_fp_less(cc,cbest) ) + { + + /* + * store split + */ + *rms = v; + koptimal = k; + cbest = cc; + + /* + * calculate CVRMS error + */ + *cvrms = 0; + for(i=0; i<=nc-1; i++) + { + if( sl>1 ) + { + w = cntbuf->ptr.p_int[i]; + *cvrms = *cvrms+w*ae_sqr((w-1)/(sl-1)-1, _state); + *cvrms = *cvrms+(sl-w)*ae_sqr(w/(sl-1), _state); + } + else + { + w = cntbuf->ptr.p_int[i]; + *cvrms = *cvrms+w*ae_sqr((double)1/(double)nc-1, _state); + *cvrms = *cvrms+(sl-w)*ae_sqr((double)1/(double)nc, _state); + } + if( sr>1 ) + { + w = cntbuf->ptr.p_int[nc+i]; + *cvrms = *cvrms+w*ae_sqr((w-1)/(sr-1)-1, _state); + *cvrms = *cvrms+(sr-w)*ae_sqr(w/(sr-1), _state); + } + else + { + w = cntbuf->ptr.p_int[nc+i]; + *cvrms = *cvrms+w*ae_sqr((double)1/(double)nc-1, _state); + *cvrms = *cvrms+(sr-w)*ae_sqr((double)1/(double)nc, _state); + } + } + *cvrms = ae_sqrt(*cvrms/(nc*n), _state); + } + } + + /* + * Calculate threshold. + * Code is a bit complicated because there can be such + * numbers that 0.5(A+B) equals to A or B (if A-B=epsilon) + */ + *threshold = 0.5*(a->ptr.p_double[tiesbuf->ptr.p_int[koptimal]]+a->ptr.p_double[tiesbuf->ptr.p_int[koptimal+1]]); + if( ae_fp_less_eq(*threshold,a->ptr.p_double[tiesbuf->ptr.p_int[koptimal]]) ) + { + *threshold = a->ptr.p_double[tiesbuf->ptr.p_int[koptimal+1]]; + } +} + + +/************************************************************************* +Automatic non-optimal discretization, internal subroutine. + + -- ALGLIB -- + Copyright 22.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dssplitk(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t nc, + ae_int_t kmax, + ae_int_t* info, + /* Real */ ae_vector* thresholds, + ae_int_t* ni, + double* cve, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _a; + ae_vector _c; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t k; + ae_vector ties; + ae_int_t tiecount; + ae_vector p1; + ae_vector p2; + ae_vector cnt; + double v2; + ae_int_t bestk; + double bestcve; + ae_vector bestsizes; + double curcve; + ae_vector cursizes; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init_copy(&_c, c, _state, ae_true); + c = &_c; + *info = 0; + ae_vector_clear(thresholds); + *ni = 0; + *cve = 0; + ae_vector_init(&ties, 0, DT_INT, _state, ae_true); + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + ae_vector_init(&cnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&bestsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(&cursizes, 0, DT_INT, _state, ae_true); + + + /* + * Test for errors in inputs + */ + if( (n<=0||nc<2)||kmax<2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Tie + */ + dstie(a, n, &ties, &tiecount, &p1, &p2, _state); + for(i=0; i<=n-1; i++) + { + if( p2.ptr.p_int[i]!=i ) + { + k = c->ptr.p_int[i]; + c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]]; + c->ptr.p_int[p2.ptr.p_int[i]] = k; + } + } + + /* + * Special cases + */ + if( tiecount==1 ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * General case: + * 0. allocate arrays + */ + kmax = ae_minint(kmax, tiecount, _state); + ae_vector_set_length(&bestsizes, kmax-1+1, _state); + ae_vector_set_length(&cursizes, kmax-1+1, _state); + ae_vector_set_length(&cnt, nc-1+1, _state); + + /* + * General case: + * 1. prepare "weak" solution (two subintervals, divided at median) + */ + v2 = ae_maxrealnumber; + j = -1; + for(i=1; i<=tiecount-1; i++) + { + if( ae_fp_less(ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state),v2) ) + { + v2 = ae_fabs(ties.ptr.p_int[i]-0.5*n, _state); + j = i; + } + } + ae_assert(j>0, "DSSplitK: internal error #1!", _state); + bestk = 2; + bestsizes.ptr.p_int[0] = ties.ptr.p_int[j]; + bestsizes.ptr.p_int[1] = n-j; + bestcve = 0; + for(i=0; i<=nc-1; i++) + { + cnt.ptr.p_int[i] = 0; + } + for(i=0; i<=j-1; i++) + { + bdss_tieaddc(c, &ties, i, nc, &cnt, _state); + } + bestcve = bestcve+bdss_getcv(&cnt, nc, _state); + for(i=0; i<=nc-1; i++) + { + cnt.ptr.p_int[i] = 0; + } + for(i=j; i<=tiecount-1; i++) + { + bdss_tieaddc(c, &ties, i, nc, &cnt, _state); + } + bestcve = bestcve+bdss_getcv(&cnt, nc, _state); + + /* + * General case: + * 2. Use greedy algorithm to find sub-optimal split in O(KMax*N) time + */ + for(k=2; k<=kmax; k++) + { + + /* + * Prepare greedy K-interval split + */ + for(i=0; i<=k-1; i++) + { + cursizes.ptr.p_int[i] = 0; + } + i = 0; + j = 0; + while(j<=tiecount-1&&i<=k-1) + { + + /* + * Rule: I-th bin is empty, fill it + */ + if( cursizes.ptr.p_int[i]==0 ) + { + cursizes.ptr.p_int[i] = ties.ptr.p_int[j+1]-ties.ptr.p_int[j]; + j = j+1; + continue; + } + + /* + * Rule: (K-1-I) bins left, (K-1-I) ties left (1 tie per bin); next bin + */ + if( tiecount-j==k-1-i ) + { + i = i+1; + continue; + } + + /* + * Rule: last bin, always place in current + */ + if( i==k-1 ) + { + cursizes.ptr.p_int[i] = cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j]; + j = j+1; + continue; + } + + /* + * Place J-th tie in I-th bin, or leave for I+1-th bin. + */ + if( ae_fp_less(ae_fabs(cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j]-(double)n/(double)k, _state),ae_fabs(cursizes.ptr.p_int[i]-(double)n/(double)k, _state)) ) + { + cursizes.ptr.p_int[i] = cursizes.ptr.p_int[i]+ties.ptr.p_int[j+1]-ties.ptr.p_int[j]; + j = j+1; + } + else + { + i = i+1; + } + } + ae_assert(cursizes.ptr.p_int[k-1]!=0&&j==tiecount, "DSSplitK: internal error #1", _state); + + /* + * Calculate CVE + */ + curcve = 0; + j = 0; + for(i=0; i<=k-1; i++) + { + for(j1=0; j1<=nc-1; j1++) + { + cnt.ptr.p_int[j1] = 0; + } + for(j1=j; j1<=j+cursizes.ptr.p_int[i]-1; j1++) + { + cnt.ptr.p_int[c->ptr.p_int[j1]] = cnt.ptr.p_int[c->ptr.p_int[j1]]+1; + } + curcve = curcve+bdss_getcv(&cnt, nc, _state); + j = j+cursizes.ptr.p_int[i]; + } + + /* + * Choose best variant + */ + if( ae_fp_less(curcve,bestcve) ) + { + for(i=0; i<=k-1; i++) + { + bestsizes.ptr.p_int[i] = cursizes.ptr.p_int[i]; + } + bestcve = curcve; + bestk = k; + } + } + + /* + * Transform from sizes to thresholds + */ + *cve = bestcve; + *ni = bestk; + ae_vector_set_length(thresholds, *ni-2+1, _state); + j = bestsizes.ptr.p_int[0]; + for(i=1; i<=bestk-1; i++) + { + thresholds->ptr.p_double[i-1] = 0.5*(a->ptr.p_double[j-1]+a->ptr.p_double[j]); + j = j+bestsizes.ptr.p_int[i]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Automatic optimal discretization, internal subroutine. + + -- ALGLIB -- + Copyright 22.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplitk(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t nc, + ae_int_t kmax, + ae_int_t* info, + /* Real */ ae_vector* thresholds, + ae_int_t* ni, + double* cve, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _a; + ae_vector _c; + ae_int_t i; + ae_int_t j; + ae_int_t s; + ae_int_t jl; + ae_int_t jr; + double v2; + ae_vector ties; + ae_int_t tiecount; + ae_vector p1; + ae_vector p2; + double cvtemp; + ae_vector cnt; + ae_vector cnt2; + ae_matrix cv; + ae_matrix splits; + ae_int_t k; + ae_int_t koptimal; + double cvoptimal; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init_copy(&_c, c, _state, ae_true); + c = &_c; + *info = 0; + ae_vector_clear(thresholds); + *ni = 0; + *cve = 0; + ae_vector_init(&ties, 0, DT_INT, _state, ae_true); + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + ae_vector_init(&cnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&cnt2, 0, DT_INT, _state, ae_true); + ae_matrix_init(&cv, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&splits, 0, 0, DT_INT, _state, ae_true); + + + /* + * Test for errors in inputs + */ + if( (n<=0||nc<2)||kmax<2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + if( c->ptr.p_int[i]<0||c->ptr.p_int[i]>=nc ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Tie + */ + dstie(a, n, &ties, &tiecount, &p1, &p2, _state); + for(i=0; i<=n-1; i++) + { + if( p2.ptr.p_int[i]!=i ) + { + k = c->ptr.p_int[i]; + c->ptr.p_int[i] = c->ptr.p_int[p2.ptr.p_int[i]]; + c->ptr.p_int[p2.ptr.p_int[i]] = k; + } + } + + /* + * Special cases + */ + if( tiecount==1 ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * General case + * Use dynamic programming to find best split in O(KMax*NC*TieCount^2) time + */ + kmax = ae_minint(kmax, tiecount, _state); + ae_matrix_set_length(&cv, kmax-1+1, tiecount-1+1, _state); + ae_matrix_set_length(&splits, kmax-1+1, tiecount-1+1, _state); + ae_vector_set_length(&cnt, nc-1+1, _state); + ae_vector_set_length(&cnt2, nc-1+1, _state); + for(j=0; j<=nc-1; j++) + { + cnt.ptr.p_int[j] = 0; + } + for(j=0; j<=tiecount-1; j++) + { + bdss_tieaddc(c, &ties, j, nc, &cnt, _state); + splits.ptr.pp_int[0][j] = 0; + cv.ptr.pp_double[0][j] = bdss_getcv(&cnt, nc, _state); + } + for(k=1; k<=kmax-1; k++) + { + for(j=0; j<=nc-1; j++) + { + cnt.ptr.p_int[j] = 0; + } + + /* + * Subtask size J in [K..TieCount-1]: + * optimal K-splitting on ties from 0-th to J-th. + */ + for(j=k; j<=tiecount-1; j++) + { + + /* + * Update Cnt - let it contain classes of ties from K-th to J-th + */ + bdss_tieaddc(c, &ties, j, nc, &cnt, _state); + + /* + * Search for optimal split point S in [K..J] + */ + for(i=0; i<=nc-1; i++) + { + cnt2.ptr.p_int[i] = cnt.ptr.p_int[i]; + } + cv.ptr.pp_double[k][j] = cv.ptr.pp_double[k-1][j-1]+bdss_getcv(&cnt2, nc, _state); + splits.ptr.pp_int[k][j] = j; + for(s=k+1; s<=j; s++) + { + + /* + * Update Cnt2 - let it contain classes of ties from S-th to J-th + */ + bdss_tiesubc(c, &ties, s-1, nc, &cnt2, _state); + + /* + * Calculate CVE + */ + cvtemp = cv.ptr.pp_double[k-1][s-1]+bdss_getcv(&cnt2, nc, _state); + if( ae_fp_less(cvtemp,cv.ptr.pp_double[k][j]) ) + { + cv.ptr.pp_double[k][j] = cvtemp; + splits.ptr.pp_int[k][j] = s; + } + } + } + } + + /* + * Choose best partition, output result + */ + koptimal = -1; + cvoptimal = ae_maxrealnumber; + for(k=0; k<=kmax-1; k++) + { + if( ae_fp_less(cv.ptr.pp_double[k][tiecount-1],cvoptimal) ) + { + cvoptimal = cv.ptr.pp_double[k][tiecount-1]; + koptimal = k; + } + } + ae_assert(koptimal>=0, "DSOptimalSplitK: internal error #1!", _state); + if( koptimal==0 ) + { + + /* + * Special case: best partition is one big interval. + * Even 2-partition is not better. + * This is possible when dealing with "weak" predictor variables. + * + * Make binary split as close to the median as possible. + */ + v2 = ae_maxrealnumber; + j = -1; + for(i=1; i<=tiecount-1; i++) + { + if( ae_fp_less(ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state),v2) ) + { + v2 = ae_fabs(ties.ptr.p_int[i]-0.5*(n-1), _state); + j = i; + } + } + ae_assert(j>0, "DSOptimalSplitK: internal error #2!", _state); + ae_vector_set_length(thresholds, 0+1, _state); + thresholds->ptr.p_double[0] = 0.5*(a->ptr.p_double[ties.ptr.p_int[j-1]]+a->ptr.p_double[ties.ptr.p_int[j]]); + *ni = 2; + *cve = 0; + for(i=0; i<=nc-1; i++) + { + cnt.ptr.p_int[i] = 0; + } + for(i=0; i<=j-1; i++) + { + bdss_tieaddc(c, &ties, i, nc, &cnt, _state); + } + *cve = *cve+bdss_getcv(&cnt, nc, _state); + for(i=0; i<=nc-1; i++) + { + cnt.ptr.p_int[i] = 0; + } + for(i=j; i<=tiecount-1; i++) + { + bdss_tieaddc(c, &ties, i, nc, &cnt, _state); + } + *cve = *cve+bdss_getcv(&cnt, nc, _state); + } + else + { + + /* + * General case: 2 or more intervals + * + * NOTE: we initialize both JL and JR (left and right bounds), + * altough algorithm needs only JL. + */ + ae_vector_set_length(thresholds, koptimal-1+1, _state); + *ni = koptimal+1; + *cve = cv.ptr.pp_double[koptimal][tiecount-1]; + jl = splits.ptr.pp_int[koptimal][tiecount-1]; + jr = tiecount-1; + for(k=koptimal; k>=1; k--) + { + thresholds->ptr.p_double[k-1] = 0.5*(a->ptr.p_double[ties.ptr.p_int[jl-1]]+a->ptr.p_double[ties.ptr.p_int[jl]]); + jr = jl-1; + jl = splits.ptr.pp_int[k-1][jl-1]; + } + touchint(&jr, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal function +*************************************************************************/ +static double bdss_xlny(double x, double y, ae_state *_state) +{ + double result; + + + if( ae_fp_eq(x,0) ) + { + result = 0; + } + else + { + result = x*ae_log(y, _state); + } + return result; +} + + +/************************************************************************* +Internal function, +returns number of samples of class I in Cnt[I] +*************************************************************************/ +static double bdss_getcv(/* Integer */ ae_vector* cnt, + ae_int_t nc, + ae_state *_state) +{ + ae_int_t i; + double s; + double result; + + + s = 0; + for(i=0; i<=nc-1; i++) + { + s = s+cnt->ptr.p_int[i]; + } + result = 0; + for(i=0; i<=nc-1; i++) + { + result = result-bdss_xlny(cnt->ptr.p_int[i], cnt->ptr.p_int[i]/(s+nc-1), _state); + } + return result; +} + + +/************************************************************************* +Internal function, adds number of samples of class I in tie NTie to Cnt[I] +*************************************************************************/ +static void bdss_tieaddc(/* Integer */ ae_vector* c, + /* Integer */ ae_vector* ties, + ae_int_t ntie, + ae_int_t nc, + /* Integer */ ae_vector* cnt, + ae_state *_state) +{ + ae_int_t i; + + + for(i=ties->ptr.p_int[ntie]; i<=ties->ptr.p_int[ntie+1]-1; i++) + { + cnt->ptr.p_int[c->ptr.p_int[i]] = cnt->ptr.p_int[c->ptr.p_int[i]]+1; + } +} + + +/************************************************************************* +Internal function, subtracts number of samples of class I in tie NTie to Cnt[I] +*************************************************************************/ +static void bdss_tiesubc(/* Integer */ ae_vector* c, + /* Integer */ ae_vector* ties, + ae_int_t ntie, + ae_int_t nc, + /* Integer */ ae_vector* cnt, + ae_state *_state) +{ + ae_int_t i; + + + for(i=ties->ptr.p_int[ntie]; i<=ties->ptr.p_int[ntie+1]-1; i++) + { + cnt->ptr.p_int[c->ptr.p_int[i]] = cnt->ptr.p_int[c->ptr.p_int[i]]-1; + } +} + + +ae_bool _cvreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + cvreport *p = (cvreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _cvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + cvreport *dst = (cvreport*)_dst; + cvreport *src = (cvreport*)_src; + dst->relclserror = src->relclserror; + dst->avgce = src->avgce; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + return ae_true; +} + + +void _cvreport_clear(void* _p) +{ + cvreport *p = (cvreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _cvreport_destroy(void* _p) +{ + cvreport *p = (cvreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +This function initializes clusterizer object. Newly initialized object is +empty, i.e. it does not contain dataset. You should use it as follows: +1. creation +2. dataset is added with ClusterizerSetPoints() +3. additional parameters are set +3. clusterization is performed with one of the clustering functions + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizercreate(clusterizerstate* s, ae_state *_state) +{ + + _clusterizerstate_clear(s); + + s->npoints = 0; + s->nfeatures = 0; + s->disttype = 2; + s->ahcalgo = 0; + s->kmeansrestarts = 1; + s->kmeansmaxits = 0; +} + + +/************************************************************************* +This function adds dataset to the clusterizer structure. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +NOTE 1: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + +NOTE 2: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric + * k-means++ clustering algorithm may be used only with Euclidean + distance function + Thus, list of specific clustering algorithms you may use depends + on distance function you specify when you set your dataset. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetpoints(clusterizerstate* s, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert((((((((disttype==0||disttype==1)||disttype==2)||disttype==10)||disttype==11)||disttype==12)||disttype==13)||disttype==20)||disttype==21, "ClusterizerSetPoints: incorrect DistType", _state); + ae_assert(npoints>=0, "ClusterizerSetPoints: NPoints<0", _state); + ae_assert(nfeatures>=1, "ClusterizerSetPoints: NFeatures<1", _state); + ae_assert(xy->rows>=npoints, "ClusterizerSetPoints: Rows(XY)cols>=nfeatures, "ClusterizerSetPoints: Cols(XY)npoints = npoints; + s->nfeatures = nfeatures; + s->disttype = disttype; + rmatrixsetlengthatleast(&s->xy, npoints, nfeatures, _state); + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&s->xy.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1)); + } +} + + +/************************************************************************* +This function adds dataset given by distance matrix to the clusterizer +structure. It is important that dataset is not given explicitly - only +distance matrix is given. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + D - array[NPoints,NPoints], distance matrix given by its upper + or lower triangle (main diagonal is ignored because its + entries are expected to be zero). + NPoints - number of points + IsUpper - whether upper or lower triangle of D is given. + +NOTE 1: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric, including one which is given by + distance matrix + * k-means++ clustering algorithm may be used only with Euclidean + distance function and explicitly given points - it can not be + used with dataset given by distance matrix + Thus, if you call this function, you will be unable to use k-means + clustering algorithm to process your problem. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetdistances(clusterizerstate* s, + /* Real */ ae_matrix* d, + ae_int_t npoints, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t j0; + ae_int_t j1; + + + ae_assert(npoints>=0, "ClusterizerSetDistances: NPoints<0", _state); + ae_assert(d->rows>=npoints, "ClusterizerSetDistances: Rows(D)cols>=npoints, "ClusterizerSetDistances: Cols(D)npoints = npoints; + s->nfeatures = 0; + s->disttype = -1; + rmatrixsetlengthatleast(&s->d, npoints, npoints, _state); + for(i=0; i<=npoints-1; i++) + { + if( isupper ) + { + j0 = i+1; + j1 = npoints-1; + } + else + { + j0 = 0; + j1 = i-1; + } + for(j=j0; j<=j1; j++) + { + ae_assert(ae_isfinite(d->ptr.pp_double[i][j], _state)&&ae_fp_greater_eq(d->ptr.pp_double[i][j],0), "ClusterizerSetDistances: D contains infinite, NAN or negative elements", _state); + s->d.ptr.pp_double[i][j] = d->ptr.pp_double[i][j]; + s->d.ptr.pp_double[j][i] = d->ptr.pp_double[i][j]; + } + s->d.ptr.pp_double[i][i] = 0; + } +} + + +/************************************************************************* +This function sets agglomerative hierarchical clustering algorithm + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Algo - algorithm type: + * 0 complete linkage (default algorithm) + * 1 single linkage + * 2 unweighted average linkage + * 3 weighted average linkage + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetahcalgo(clusterizerstate* s, + ae_int_t algo, + ae_state *_state) +{ + + + ae_assert(((algo==0||algo==1)||algo==2)||algo==3, "ClusterizerSetHCAlgo: incorrect algorithm type", _state); + s->ahcalgo = algo; +} + + +/************************************************************************* +This function sets k-means++ properties : number of restarts and maximum +number of iterations per one run. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Restarts- restarts count, >=1. + k-means++ algorithm performs several restarts and chooses + best set of centers (one with minimum squared distance). + MaxIts - maximum number of k-means iterations performed during one + run. >=0, zero value means that algorithm performs unlimited + number of iterations. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetkmeanslimits(clusterizerstate* s, + ae_int_t restarts, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(restarts>=1, "ClusterizerSetKMeansLimits: Restarts<=0", _state); + ae_assert(maxits>=0, "ClusterizerSetKMeansLimits: MaxIts<0", _state); + s->kmeansrestarts = restarts; + s->kmeansmaxits = maxits; +} + + +/************************************************************************* +This function performs agglomerative hierarchical clustering + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + +OUTPUT PARAMETERS: + Rep - clustering results; see description of AHCReport + structure for more information. + +NOTE 1: hierarchical clustering algorithms require large amounts of memory. + In particular, this implementation needs sizeof(double)*NPoints^2 + bytes, which are used to store distance matrix. In case we work + with user-supplied matrix, this amount is multiplied by 2 (we have + to store original matrix and to work with its copy). + + For example, problem with 10000 points would require 800M of RAM, + even when working in a 1-dimensional space. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunahc(clusterizerstate* s, + ahcreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t npoints; + ae_int_t nfeatures; + ae_matrix d; + + ae_frame_make(_state, &_frame_block); + _ahcreport_clear(rep); + ae_matrix_init(&d, 0, 0, DT_REAL, _state, ae_true); + + npoints = s->npoints; + nfeatures = s->nfeatures; + + /* + * Fill Rep.NPoints, quick exit when NPoints<=1 + */ + rep->npoints = npoints; + if( npoints==0 ) + { + ae_vector_set_length(&rep->p, 0, _state); + ae_matrix_set_length(&rep->z, 0, 0, _state); + ae_matrix_set_length(&rep->pz, 0, 0, _state); + ae_matrix_set_length(&rep->pm, 0, 0, _state); + ae_vector_set_length(&rep->mergedist, 0, _state); + ae_frame_leave(_state); + return; + } + if( npoints==1 ) + { + ae_vector_set_length(&rep->p, 1, _state); + ae_matrix_set_length(&rep->z, 0, 0, _state); + ae_matrix_set_length(&rep->pz, 0, 0, _state); + ae_matrix_set_length(&rep->pm, 0, 0, _state); + ae_vector_set_length(&rep->mergedist, 0, _state); + rep->p.ptr.p_int[0] = 0; + ae_frame_leave(_state); + return; + } + + /* + * More than one point + */ + if( s->disttype==-1 ) + { + + /* + * Run clusterizer with user-supplied distance matrix + */ + clustering_clusterizerrunahcinternal(s, &s->d, rep, _state); + ae_frame_leave(_state); + return; + } + else + { + + /* + * Build distance matrix D. + */ + clusterizergetdistances(&s->xy, npoints, nfeatures, s->disttype, &d, _state); + + /* + * Run clusterizer + */ + clustering_clusterizerrunahcinternal(s, &d, rep, _state); + ae_frame_leave(_state); + return; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_clusterizerrunahc(clusterizerstate* s, + ahcreport* rep, ae_state *_state) +{ + clusterizerrunahc(s,rep, _state); +} + + +/************************************************************************* +This function performs clustering by k-means++ algorithm. + +You may change algorithm properties like number of restarts or iterations +limit by calling ClusterizerSetKMeansLimits() functions. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + K - number of clusters, K>=0. + K can be zero only when algorithm is called for empty + dataset, in this case completion code is set to + success (+1). + If K=0 and dataset size is non-zero, we can not + meaningfully assign points to some center (there are no + centers because K=0) and return -3 as completion code + (failure). + +OUTPUT PARAMETERS: + Rep - clustering results; see description of KMeansReport + structure for more information. + +NOTE 1: k-means clustering can be performed only for datasets with + Euclidean distance function. Algorithm will return negative + completion code in Rep.TerminationType in case dataset was added + to clusterizer with DistType other than Euclidean (or dataset was + specified by distance matrix instead of explicitly given points). + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunkmeans(clusterizerstate* s, + ae_int_t k, + kmeansreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix dummy; + + ae_frame_make(_state, &_frame_block); + _kmeansreport_clear(rep); + ae_matrix_init(&dummy, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(k>=0, "ClusterizerRunKMeans: K<0", _state); + + /* + * Incorrect distance type + */ + if( s->disttype!=2 ) + { + rep->npoints = s->npoints; + rep->terminationtype = -5; + rep->k = k; + ae_frame_leave(_state); + return; + } + + /* + * K>NPoints or (K=0 and NPoints>0) + */ + if( k>s->npoints||(k==0&&s->npoints>0) ) + { + rep->npoints = s->npoints; + rep->terminationtype = -3; + rep->k = k; + ae_frame_leave(_state); + return; + } + + /* + * No points + */ + if( s->npoints==0 ) + { + rep->npoints = 0; + rep->terminationtype = 1; + rep->k = k; + ae_frame_leave(_state); + return; + } + + /* + * Normal case: + * 1<=K<=NPoints, Euclidean distance + */ + rep->npoints = s->npoints; + rep->nfeatures = s->nfeatures; + rep->k = k; + rep->npoints = s->npoints; + rep->nfeatures = s->nfeatures; + kmeansgenerateinternal(&s->xy, s->npoints, s->nfeatures, k, s->kmeansmaxits, s->kmeansrestarts, &rep->terminationtype, &dummy, ae_false, &rep->c, ae_true, &rep->cidx, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This function returns distance matrix for dataset + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +OUTPUT PARAMETERS: + D - array[NPoints,NPoints], distance matrix + (full matrix is returned, with lower and upper triangles) + +NOTES: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizergetdistances(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double vv; + double vr; + ae_matrix tmpxy; + ae_vector tmpx; + ae_vector tmpy; + ae_vector diagbuf; + apbuffers buf; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(d); + ae_matrix_init(&tmpxy, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&diagbuf, 0, DT_REAL, _state, ae_true); + _apbuffers_init(&buf, _state, ae_true); + + ae_assert(nfeatures>=1, "ClusterizerGetDistances: NFeatures<1", _state); + ae_assert(npoints>=0, "ClusterizerGetDistances: NPoints<1", _state); + ae_assert((((((((disttype==0||disttype==1)||disttype==2)||disttype==10)||disttype==11)||disttype==12)||disttype==13)||disttype==20)||disttype==21, "ClusterizerGetDistances: incorrect DistType", _state); + ae_assert(xy->rows>=npoints, "ClusterizerGetDistances: Rows(XY)cols>=nfeatures, "ClusterizerGetDistances: Cols(XY)ptr.pp_double[0][0] = 0; + ae_frame_leave(_state); + return; + } + + /* + * Build distance matrix D. + */ + if( disttype==0||disttype==1 ) + { + + /* + * Chebyshev or city-block distances: + * * recursively calculate upper triangle (with main diagonal) + * * copy it to the bottom part of the matrix + */ + ae_matrix_set_length(d, npoints, npoints, _state); + clustering_evaluatedistancematrixrec(xy, nfeatures, disttype, d, 0, npoints, 0, npoints, _state); + rmatrixenforcesymmetricity(d, npoints, ae_true, _state); + ae_frame_leave(_state); + return; + } + if( disttype==2 ) + { + + /* + * Euclidean distance + * + * NOTE: parallelization is done within RMatrixSYRK + */ + ae_matrix_set_length(d, npoints, npoints, _state); + ae_matrix_set_length(&tmpxy, npoints, nfeatures, _state); + ae_vector_set_length(&tmpx, nfeatures, _state); + ae_vector_set_length(&diagbuf, npoints, _state); + for(j=0; j<=nfeatures-1; j++) + { + tmpx.ptr.p_double[j] = 0.0; + } + v = (double)1/(double)npoints; + for(i=0; i<=npoints-1; i++) + { + ae_v_addd(&tmpx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1), v); + } + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&tmpxy.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1)); + ae_v_sub(&tmpxy.ptr.pp_double[i][0], 1, &tmpx.ptr.p_double[0], 1, ae_v_len(0,nfeatures-1)); + } + rmatrixsyrk(npoints, nfeatures, 1.0, &tmpxy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state); + for(i=0; i<=npoints-1; i++) + { + diagbuf.ptr.p_double[i] = d->ptr.pp_double[i][i]; + } + for(i=0; i<=npoints-1; i++) + { + d->ptr.pp_double[i][i] = 0.0; + for(j=i+1; j<=npoints-1; j++) + { + v = ae_sqrt(ae_maxreal(diagbuf.ptr.p_double[i]+diagbuf.ptr.p_double[j]-2*d->ptr.pp_double[i][j], 0.0, _state), _state); + d->ptr.pp_double[i][j] = v; + } + } + rmatrixenforcesymmetricity(d, npoints, ae_true, _state); + ae_frame_leave(_state); + return; + } + if( disttype==10||disttype==11 ) + { + + /* + * Absolute/nonabsolute Pearson correlation distance + * + * NOTE: parallelization is done within PearsonCorrM, which calls RMatrixSYRK internally + */ + ae_matrix_set_length(d, npoints, npoints, _state); + ae_vector_set_length(&diagbuf, npoints, _state); + ae_matrix_set_length(&tmpxy, npoints, nfeatures, _state); + for(i=0; i<=npoints-1; i++) + { + v = 0.0; + for(j=0; j<=nfeatures-1; j++) + { + v = v+xy->ptr.pp_double[i][j]; + } + v = v/nfeatures; + for(j=0; j<=nfeatures-1; j++) + { + tmpxy.ptr.pp_double[i][j] = xy->ptr.pp_double[i][j]-v; + } + } + rmatrixsyrk(npoints, nfeatures, 1.0, &tmpxy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state); + for(i=0; i<=npoints-1; i++) + { + diagbuf.ptr.p_double[i] = d->ptr.pp_double[i][i]; + } + for(i=0; i<=npoints-1; i++) + { + d->ptr.pp_double[i][i] = 0.0; + for(j=i+1; j<=npoints-1; j++) + { + v = d->ptr.pp_double[i][j]/ae_sqrt(diagbuf.ptr.p_double[i]*diagbuf.ptr.p_double[j], _state); + if( disttype==10 ) + { + v = 1-v; + } + else + { + v = 1-ae_fabs(v, _state); + } + v = ae_maxreal(v, 0.0, _state); + d->ptr.pp_double[i][j] = v; + } + } + rmatrixenforcesymmetricity(d, npoints, ae_true, _state); + ae_frame_leave(_state); + return; + } + if( disttype==12||disttype==13 ) + { + + /* + * Absolute/nonabsolute uncentered Pearson correlation distance + * + * NOTE: parallelization is done within RMatrixSYRK + */ + ae_matrix_set_length(d, npoints, npoints, _state); + ae_vector_set_length(&diagbuf, npoints, _state); + rmatrixsyrk(npoints, nfeatures, 1.0, xy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state); + for(i=0; i<=npoints-1; i++) + { + diagbuf.ptr.p_double[i] = d->ptr.pp_double[i][i]; + } + for(i=0; i<=npoints-1; i++) + { + d->ptr.pp_double[i][i] = 0.0; + for(j=i+1; j<=npoints-1; j++) + { + v = d->ptr.pp_double[i][j]/ae_sqrt(diagbuf.ptr.p_double[i]*diagbuf.ptr.p_double[j], _state); + if( disttype==13 ) + { + v = ae_fabs(v, _state); + } + v = ae_minreal(v, 1.0, _state); + d->ptr.pp_double[i][j] = 1-v; + } + } + rmatrixenforcesymmetricity(d, npoints, ae_true, _state); + ae_frame_leave(_state); + return; + } + if( disttype==20||disttype==21 ) + { + + /* + * Spearman rank correlation + * + * NOTE: parallelization of correlation matrix is done within + * PearsonCorrM, which calls RMatrixSYRK internally + */ + ae_matrix_set_length(d, npoints, npoints, _state); + ae_vector_set_length(&diagbuf, npoints, _state); + ae_matrix_set_length(&tmpxy, npoints, nfeatures, _state); + rmatrixcopy(npoints, nfeatures, xy, 0, 0, &tmpxy, 0, 0, _state); + rankdatacentered(&tmpxy, npoints, nfeatures, _state); + rmatrixsyrk(npoints, nfeatures, 1.0, &tmpxy, 0, 0, 0, 0.0, d, 0, 0, ae_true, _state); + for(i=0; i<=npoints-1; i++) + { + if( ae_fp_greater(d->ptr.pp_double[i][i],0) ) + { + diagbuf.ptr.p_double[i] = 1/ae_sqrt(d->ptr.pp_double[i][i], _state); + } + else + { + diagbuf.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=npoints-1; i++) + { + v = diagbuf.ptr.p_double[i]; + d->ptr.pp_double[i][i] = 0.0; + for(j=i+1; j<=npoints-1; j++) + { + vv = d->ptr.pp_double[i][j]*v*diagbuf.ptr.p_double[j]; + if( disttype==20 ) + { + vr = 1-vv; + } + else + { + vr = 1-ae_fabs(vv, _state); + } + if( ae_fp_less(vr,0) ) + { + vr = 0.0; + } + d->ptr.pp_double[i][j] = vr; + } + } + rmatrixenforcesymmetricity(d, npoints, ae_true, _state); + ae_frame_leave(_state); + return; + } + ae_assert(ae_false, "Assertion failed", _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_clusterizergetdistances(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, ae_state *_state) +{ + clusterizergetdistances(xy,npoints,nfeatures,disttype,d, _state); +} + + +/************************************************************************* +This function takes as input clusterization report Rep, desired clusters +count K, and builds top K clusters from hierarchical clusterization tree. +It returns assignment of points to clusters (array of cluster indexes). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + K - desired number of clusters, 1<=K<=NPoints. + K can be zero only when NPoints=0. + +OUTPUT PARAMETERS: + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I]npoints; + ae_assert(npoints>=0, "ClusterizerGetKClusters: internal error in Rep integrity", _state); + ae_assert(k>=0, "ClusterizerGetKClusters: K<=0", _state); + ae_assert(k<=npoints, "ClusterizerGetKClusters: K>NPoints", _state); + ae_assert(k>0||npoints==0, "ClusterizerGetKClusters: K<=0", _state); + ae_assert(npoints==rep->npoints, "ClusterizerGetKClusters: NPoints<>Rep.NPoints", _state); + + /* + * Quick exit + */ + if( npoints==0 ) + { + ae_frame_leave(_state); + return; + } + if( npoints==1 ) + { + ae_vector_set_length(cz, 1, _state); + ae_vector_set_length(cidx, 1, _state); + cz->ptr.p_int[0] = 0; + cidx->ptr.p_int[0] = 0; + ae_frame_leave(_state); + return; + } + + /* + * Replay merges, from top to bottom, + * keep track of clusters being present at the moment + */ + ae_vector_set_length(&presentclusters, 2*npoints-1, _state); + ae_vector_set_length(&tmpidx, npoints, _state); + for(i=0; i<=2*npoints-3; i++) + { + presentclusters.ptr.p_bool[i] = ae_false; + } + presentclusters.ptr.p_bool[2*npoints-2] = ae_true; + for(i=0; i<=npoints-1; i++) + { + tmpidx.ptr.p_int[i] = 2*npoints-2; + } + for(mergeidx=npoints-2; mergeidx>=npoints-k; mergeidx--) + { + + /* + * Update information about clusters being present at the moment + */ + presentclusters.ptr.p_bool[npoints+mergeidx] = ae_false; + presentclusters.ptr.p_bool[rep->z.ptr.pp_int[mergeidx][0]] = ae_true; + presentclusters.ptr.p_bool[rep->z.ptr.pp_int[mergeidx][1]] = ae_true; + + /* + * Update TmpIdx according to the current state of the dataset + * + * NOTE: TmpIdx contains cluster indexes from [0..2*NPoints-2]; + * we will convert them to [0..K-1] later. + */ + i0 = rep->pm.ptr.pp_int[mergeidx][0]; + i1 = rep->pm.ptr.pp_int[mergeidx][1]; + t = rep->z.ptr.pp_int[mergeidx][0]; + for(i=i0; i<=i1; i++) + { + tmpidx.ptr.p_int[i] = t; + } + i0 = rep->pm.ptr.pp_int[mergeidx][2]; + i1 = rep->pm.ptr.pp_int[mergeidx][3]; + t = rep->z.ptr.pp_int[mergeidx][1]; + for(i=i0; i<=i1; i++) + { + tmpidx.ptr.p_int[i] = t; + } + } + + /* + * Fill CZ - array which allows us to convert cluster indexes + * from one system to another. + */ + ae_vector_set_length(cz, k, _state); + ae_vector_set_length(&clusterindexes, 2*npoints-1, _state); + t = 0; + for(i=0; i<=2*npoints-2; i++) + { + if( presentclusters.ptr.p_bool[i] ) + { + cz->ptr.p_int[t] = i; + clusterindexes.ptr.p_int[i] = t; + t = t+1; + } + } + ae_assert(t==k, "ClusterizerGetKClusters: internal error", _state); + + /* + * Convert indexes stored in CIdx + */ + ae_vector_set_length(cidx, npoints, _state); + for(i=0; i<=npoints-1; i++) + { + cidx->ptr.p_int[i] = clusterindexes.ptr.p_int[tmpidx.ptr.p_int[rep->p.ptr.p_int[i]]]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function accepts AHC report Rep, desired minimum intercluster +distance and returns top clusters from hierarchical clusterization tree +which are separated by distance R or HIGHER. + +It returns assignment of points to clusters (array of cluster indexes). + +There is one more function with similar name - ClusterizerSeparatedByCorr, +which returns clusters with intercluster correlation equal to R or LOWER +(note: higher for distance, lower for correlation). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + R - desired minimum intercluster distance, R>=0 + +OUTPUT PARAMETERS: + K - number of clusters, 1<=K<=NPoints + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I]npoints&&ae_fp_greater_eq(rep->mergedist.ptr.p_double[rep->npoints-1-(*k)],r)) + { + *k = *k+1; + } + clusterizergetkclusters(rep, *k, cidx, cz, _state); +} + + +/************************************************************************* +This function accepts AHC report Rep, desired maximum intercluster +correlation and returns top clusters from hierarchical clusterization tree +which are separated by correlation R or LOWER. + +It returns assignment of points to clusters (array of cluster indexes). + +There is one more function with similar name - ClusterizerSeparatedByDist, +which returns clusters with intercluster distance equal to R or HIGHER +(note: higher for distance, lower for correlation). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + R - desired maximum intercluster correlation, -1<=R<=+1 + +OUTPUT PARAMETERS: + K - number of clusters, 1<=K<=NPoints + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I]npoints&&ae_fp_greater_eq(rep->mergedist.ptr.p_double[rep->npoints-1-(*k)],1-r)) + { + *k = *k+1; + } + clusterizergetkclusters(rep, *k, cidx, cz, _state); +} + + +/************************************************************************* +K-means++ clusterization + +INPUT PARAMETERS: + XY - dataset, array [0..NPoints-1,0..NVars-1]. + NPoints - dataset size, NPoints>=K + NVars - number of variables, NVars>=1 + K - desired number of clusters, K>=1 + Restarts - number of restarts, Restarts>=1 + +OUTPUT PARAMETERS: + Info - return code: + * -3, if task is degenerate (number of distinct points is + less than K) + * -1, if incorrect NPoints/NFeatures/K/Restarts was passed + * 1, if subroutine finished successfully + CCol - array[0..NVars-1,0..K-1].matrix whose columns store + cluster's centers + NeedCCol - True in case caller requires to store result in CCol + CRow - array[0..K-1,0..NVars-1], same as CCol, but centers are + stored in rows + NeedCRow - True in case caller requires to store result in CCol + XYC - array[NPoints], which contains cluster indexes + + -- ALGLIB -- + Copyright 21.03.2009 by Bochkanov Sergey +*************************************************************************/ +void kmeansgenerateinternal(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t k, + ae_int_t maxits, + ae_int_t restarts, + ae_int_t* info, + /* Real */ ae_matrix* ccol, + ae_bool needccol, + /* Real */ ae_matrix* crow, + ae_bool needcrow, + /* Integer */ ae_vector* xyc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_matrix ct; + ae_matrix ctbest; + ae_vector xycbest; + double e; + double eprev; + double ebest; + ae_vector x; + ae_vector tmp; + ae_vector d2; + ae_vector p; + ae_vector csizes; + ae_vector cbusy; + double v; + ae_int_t cclosest; + double dclosest; + ae_vector work; + ae_bool waschanges; + ae_bool zerosizeclusters; + ae_int_t pass; + ae_int_t itcnt; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_matrix_clear(ccol); + ae_matrix_clear(crow); + ae_vector_clear(xyc); + ae_matrix_init(&ct, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ctbest, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xycbest, 0, DT_INT, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_REAL, _state, ae_true); + ae_vector_init(&csizes, 0, DT_INT, _state, ae_true); + ae_vector_init(&cbusy, 0, DT_BOOL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&rs, _state, ae_true); + + + /* + * Test parameters + */ + if( ((npointsptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + cbusy.ptr.p_bool[0] = ae_true; + for(i=1; i<=k-1; i++) + { + cbusy.ptr.p_bool[i] = ae_false; + } + if( !clustering_selectcenterpp(xy, npoints, nvars, &ct, &cbusy, k, &d2, &p, &tmp, _state) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Update centers: + * 2. update center positions + */ + for(i=0; i<=npoints-1; i++) + { + xyc->ptr.p_int[i] = -1; + } + eprev = ae_maxrealnumber; + itcnt = 0; + e = 0; + while(maxits==0||itcntptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tmp.ptr.p_double[0], 1, &ct.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + v = ae_v_dotproduct(&tmp.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + if( ae_fp_less(v,dclosest) ) + { + cclosest = j; + dclosest = v; + } + } + if( xyc->ptr.p_int[i]!=cclosest ) + { + waschanges = ae_true; + } + xyc->ptr.p_int[i] = cclosest; + } + + /* + * Update centers + */ + for(j=0; j<=k-1; j++) + { + csizes.ptr.p_int[j] = 0; + } + for(i=0; i<=k-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + ct.ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=npoints-1; i++) + { + csizes.ptr.p_int[xyc->ptr.p_int[i]] = csizes.ptr.p_int[xyc->ptr.p_int[i]]+1; + ae_v_add(&ct.ptr.pp_double[xyc->ptr.p_int[i]][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + } + zerosizeclusters = ae_false; + for(j=0; j<=k-1; j++) + { + if( csizes.ptr.p_int[j]!=0 ) + { + v = (double)1/(double)csizes.ptr.p_int[j]; + ae_v_muld(&ct.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1), v); + } + cbusy.ptr.p_bool[j] = csizes.ptr.p_int[j]!=0; + zerosizeclusters = zerosizeclusters||csizes.ptr.p_int[j]==0; + } + if( zerosizeclusters ) + { + + /* + * Some clusters have zero size - rare, but possible. + * We'll choose new centers for such clusters using k-means++ rule + * and restart algorithm + */ + if( !clustering_selectcenterpp(xy, npoints, nvars, &ct, &cbusy, k, &d2, &p, &tmp, _state) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + continue; + } + + /* + * Stop if one of two conditions is met: + * 1. nothing has changed during iteration + * 2. energy function increased + */ + e = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tmp.ptr.p_double[0], 1, &ct.ptr.pp_double[xyc->ptr.p_int[i]][0], 1, ae_v_len(0,nvars-1)); + v = ae_v_dotproduct(&tmp.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + e = e+v; + } + if( !waschanges||ae_fp_greater_eq(e,eprev) ) + { + break; + } + + /* + * Update EPrev + */ + eprev = e; + } + + /* + * 3. Calculate E, compare with best centers found so far + */ + if( ae_fp_less(e,ebest) ) + { + + /* + * store partition. + */ + ebest = e; + copymatrix(&ct, 0, k-1, 0, nvars-1, &ctbest, 0, k-1, 0, nvars-1, _state); + for(i=0; i<=npoints-1; i++) + { + xycbest.ptr.p_int[i] = xyc->ptr.p_int[i]; + } + } + } + + /* + * Copy and transpose + */ + if( needccol ) + { + ae_matrix_set_length(ccol, nvars, k, _state); + copyandtranspose(&ctbest, 0, k-1, 0, nvars-1, ccol, 0, nvars-1, 0, k-1, _state); + } + if( needcrow ) + { + ae_matrix_set_length(crow, k, nvars, _state); + rmatrixcopy(k, nvars, &ctbest, 0, 0, crow, 0, 0, _state); + } + for(i=0; i<=npoints-1; i++) + { + xyc->ptr.p_int[i] = xycbest.ptr.p_int[i]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Select center for a new cluster using k-means++ rule +*************************************************************************/ +static ae_bool clustering_selectcenterpp(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + /* Real */ ae_matrix* centers, + /* Boolean */ ae_vector* busycenters, + ae_int_t ccnt, + /* Real */ ae_vector* d2, + /* Real */ ae_vector* p, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t cc; + double v; + double s; + ae_bool result; + + + result = ae_true; + for(cc=0; cc<=ccnt-1; cc++) + { + if( !busycenters->ptr.p_bool[cc] ) + { + + /* + * fill D2 + */ + for(i=0; i<=npoints-1; i++) + { + d2->ptr.p_double[i] = ae_maxrealnumber; + for(j=0; j<=ccnt-1; j++) + { + if( busycenters->ptr.p_bool[j] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tmp->ptr.p_double[0], 1, ¢ers->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + v = ae_v_dotproduct(&tmp->ptr.p_double[0], 1, &tmp->ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + if( ae_fp_less(v,d2->ptr.p_double[i]) ) + { + d2->ptr.p_double[i] = v; + } + } + } + } + + /* + * calculate P (non-cumulative) + */ + s = 0; + for(i=0; i<=npoints-1; i++) + { + s = s+d2->ptr.p_double[i]; + } + if( ae_fp_eq(s,0) ) + { + result = ae_false; + return result; + } + s = 1/s; + ae_v_moved(&p->ptr.p_double[0], 1, &d2->ptr.p_double[0], 1, ae_v_len(0,npoints-1), s); + + /* + * choose one of points with probability P + * random number within (0,1) is generated and + * inverse empirical CDF is used to randomly choose a point. + */ + s = 0; + v = ae_randomreal(_state); + for(i=0; i<=npoints-1; i++) + { + s = s+p->ptr.p_double[i]; + if( ae_fp_less_eq(v,s)||i==npoints-1 ) + { + ae_v_move(¢ers->ptr.pp_double[cc][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + busycenters->ptr.p_bool[cc] = ae_true; + break; + } + } + } + } + return result; +} + + +/************************************************************************* +This function performs agglomerative hierarchical clustering using +precomputed distance matrix. Internal function, should not be called +directly. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + D - distance matrix, array[S.NFeatures,S.NFeatures] + Contents of the matrix is destroyed during + algorithm operation. + +OUTPUT PARAMETERS: + Rep - clustering results; see description of AHCReport + structure for more information. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +static void clustering_clusterizerrunahcinternal(clusterizerstate* s, + /* Real */ ae_matrix* d, + ahcreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + ae_int_t mergeidx; + ae_int_t c0; + ae_int_t c1; + ae_int_t s0; + ae_int_t s1; + ae_int_t ar; + ae_int_t br; + ae_int_t npoints; + ae_vector cidx; + ae_vector csizes; + ae_vector nnidx; + ae_matrix cinfo; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&cidx, 0, DT_INT, _state, ae_true); + ae_vector_init(&csizes, 0, DT_INT, _state, ae_true); + ae_vector_init(&nnidx, 0, DT_INT, _state, ae_true); + ae_matrix_init(&cinfo, 0, 0, DT_INT, _state, ae_true); + + npoints = s->npoints; + + /* + * Fill Rep.NPoints, quick exit when NPoints<=1 + */ + rep->npoints = npoints; + if( npoints==0 ) + { + ae_vector_set_length(&rep->p, 0, _state); + ae_matrix_set_length(&rep->z, 0, 0, _state); + ae_matrix_set_length(&rep->pz, 0, 0, _state); + ae_matrix_set_length(&rep->pm, 0, 0, _state); + ae_vector_set_length(&rep->mergedist, 0, _state); + ae_frame_leave(_state); + return; + } + if( npoints==1 ) + { + ae_vector_set_length(&rep->p, 1, _state); + ae_matrix_set_length(&rep->z, 0, 0, _state); + ae_matrix_set_length(&rep->pz, 0, 0, _state); + ae_matrix_set_length(&rep->pm, 0, 0, _state); + ae_vector_set_length(&rep->mergedist, 0, _state); + rep->p.ptr.p_int[0] = 0; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&rep->z, npoints-1, 2, _state); + ae_vector_set_length(&rep->mergedist, npoints-1, _state); + + /* + * Build list of nearest neighbors + */ + ae_vector_set_length(&nnidx, npoints, _state); + for(i=0; i<=npoints-1; i++) + { + + /* + * Calculate index of the nearest neighbor + */ + k = -1; + v = ae_maxrealnumber; + for(j=0; j<=npoints-1; j++) + { + if( j!=i&&ae_fp_less(d->ptr.pp_double[i][j],v) ) + { + k = j; + v = d->ptr.pp_double[i][j]; + } + } + ae_assert(ae_fp_less(v,ae_maxrealnumber), "ClusterizerRunAHC: internal error", _state); + nnidx.ptr.p_int[i] = k; + } + + /* + * Distance matrix is built, perform merges. + * + * NOTE 1: CIdx is array[NPoints] which maps rows/columns of the + * distance matrix D to indexes of clusters. Values of CIdx + * from [0,NPoints) denote single-point clusters, and values + * from [NPoints,2*NPoints-1) denote ones obtained by merging + * smaller clusters. Negative calues correspond to absent clusters. + * + * Initially it contains [0...NPoints-1], after each merge + * one element of CIdx (one with index C0) is replaced by + * NPoints+MergeIdx, and another one with index C1 is + * rewritten by -1. + * + * NOTE 2: CSizes is array[NPoints] which stores sizes of clusters. + * + */ + ae_vector_set_length(&cidx, npoints, _state); + ae_vector_set_length(&csizes, npoints, _state); + for(i=0; i<=npoints-1; i++) + { + cidx.ptr.p_int[i] = i; + csizes.ptr.p_int[i] = 1; + } + for(mergeidx=0; mergeidx<=npoints-2; mergeidx++) + { + + /* + * Select pair of clusters (C0,C1) with CIdx[C0]=0 ) + { + if( ae_fp_less(d->ptr.pp_double[i][nnidx.ptr.p_int[i]],v) ) + { + c0 = i; + c1 = nnidx.ptr.p_int[i]; + v = d->ptr.pp_double[i][nnidx.ptr.p_int[i]]; + } + } + } + ae_assert(ae_fp_less(v,ae_maxrealnumber), "ClusterizerRunAHC: internal error", _state); + if( cidx.ptr.p_int[c0]>cidx.ptr.p_int[c1] ) + { + i = c1; + c1 = c0; + c0 = i; + } + + /* + * Fill one row of Rep.Z and one element of Rep.MergeDist + */ + rep->z.ptr.pp_int[mergeidx][0] = cidx.ptr.p_int[c0]; + rep->z.ptr.pp_int[mergeidx][1] = cidx.ptr.p_int[c1]; + rep->mergedist.ptr.p_double[mergeidx] = v; + + /* + * Update distance matrix: + * * row/column C0 are updated by distances to the new cluster + * * row/column C1 are considered empty (we can fill them by zeros, + * but do not want to spend time - we just ignore them) + * + * NOTE: it is important to update distance matrix BEFORE CIdx/CSizes + * are updated. + */ + ae_assert(((s->ahcalgo==0||s->ahcalgo==1)||s->ahcalgo==2)||s->ahcalgo==3, "ClusterizerRunAHC: internal error", _state); + for(i=0; i<=npoints-1; i++) + { + if( i!=c0&&i!=c1 ) + { + if( s->ahcalgo==0 ) + { + d->ptr.pp_double[i][c0] = ae_maxreal(d->ptr.pp_double[i][c0], d->ptr.pp_double[i][c1], _state); + } + if( s->ahcalgo==1 ) + { + d->ptr.pp_double[i][c0] = ae_minreal(d->ptr.pp_double[i][c0], d->ptr.pp_double[i][c1], _state); + } + if( s->ahcalgo==2 ) + { + d->ptr.pp_double[i][c0] = (csizes.ptr.p_int[c0]*d->ptr.pp_double[i][c0]+csizes.ptr.p_int[c1]*d->ptr.pp_double[i][c1])/(csizes.ptr.p_int[c0]+csizes.ptr.p_int[c1]); + } + if( s->ahcalgo==3 ) + { + d->ptr.pp_double[i][c0] = (d->ptr.pp_double[i][c0]+d->ptr.pp_double[i][c1])/2; + } + d->ptr.pp_double[c0][i] = d->ptr.pp_double[i][c0]; + } + } + + /* + * Update CIdx and CSizes + */ + cidx.ptr.p_int[c0] = npoints+mergeidx; + cidx.ptr.p_int[c1] = -1; + csizes.ptr.p_int[c0] = csizes.ptr.p_int[c0]+csizes.ptr.p_int[c1]; + csizes.ptr.p_int[c1] = 0; + + /* + * Update nearest neighbors array: + * * update nearest neighbors of everything except for C0/C1 + * * update neighbors of C0/C1 + */ + for(i=0; i<=npoints-1; i++) + { + if( (cidx.ptr.p_int[i]>=0&&i!=c0)&&(nnidx.ptr.p_int[i]==c0||nnidx.ptr.p_int[i]==c1) ) + { + + /* + * I-th cluster which is distinct from C0/C1 has former C0/C1 cluster as its nearest + * neighbor. We handle this issue depending on specific AHC algorithm being used. + */ + if( s->ahcalgo==1 ) + { + + /* + * Single linkage. Merging of two clusters together + * does NOT change distances between new cluster and + * other clusters. + * + * The only thing we have to do is to update nearest neighbor index + */ + nnidx.ptr.p_int[i] = c0; + } + else + { + + /* + * Something other than single linkage. We have to re-examine + * all the row to find nearest neighbor. + */ + k = -1; + v = ae_maxrealnumber; + for(j=0; j<=npoints-1; j++) + { + if( (cidx.ptr.p_int[j]>=0&&j!=i)&&ae_fp_less(d->ptr.pp_double[i][j],v) ) + { + k = j; + v = d->ptr.pp_double[i][j]; + } + } + ae_assert(ae_fp_less(v,ae_maxrealnumber)||mergeidx==npoints-2, "ClusterizerRunAHC: internal error", _state); + nnidx.ptr.p_int[i] = k; + } + } + } + k = -1; + v = ae_maxrealnumber; + for(j=0; j<=npoints-1; j++) + { + if( (cidx.ptr.p_int[j]>=0&&j!=c0)&&ae_fp_less(d->ptr.pp_double[c0][j],v) ) + { + k = j; + v = d->ptr.pp_double[c0][j]; + } + } + ae_assert(ae_fp_less(v,ae_maxrealnumber)||mergeidx==npoints-2, "ClusterizerRunAHC: internal error", _state); + nnidx.ptr.p_int[c0] = k; + } + + /* + * Calculate Rep.P and Rep.PM. + * + * In order to do that, we fill CInfo matrix - (2*NPoints-1)*3 matrix, + * with I-th row containing: + * * CInfo[I,0] - size of I-th cluster + * * CInfo[I,1] - beginning of I-th cluster + * * CInfo[I,2] - end of I-th cluster + * * CInfo[I,3] - height of I-th cluster + * + * We perform it as follows: + * * first NPoints clusters have unit size (CInfo[I,0]=1) and zero + * height (CInfo[I,3]=0) + * * we replay NPoints-1 merges from first to last and fill sizes of + * corresponding clusters (new size is a sum of sizes of clusters + * being merged) and height (new height is max(heights)+1). + * * now we ready to determine locations of clusters. Last cluster + * spans entire dataset, we know it. We replay merges from last to + * first, during each merge we already know location of the merge + * result, and we can position first cluster to the left part of + * the result, and second cluster to the right part. + */ + ae_vector_set_length(&rep->p, npoints, _state); + ae_matrix_set_length(&rep->pm, npoints-1, 6, _state); + ae_matrix_set_length(&cinfo, 2*npoints-1, 4, _state); + for(i=0; i<=npoints-1; i++) + { + cinfo.ptr.pp_int[i][0] = 1; + cinfo.ptr.pp_int[i][3] = 0; + } + for(i=0; i<=npoints-2; i++) + { + cinfo.ptr.pp_int[npoints+i][0] = cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][0]][0]+cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][1]][0]; + cinfo.ptr.pp_int[npoints+i][3] = ae_maxint(cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][0]][3], cinfo.ptr.pp_int[rep->z.ptr.pp_int[i][1]][3], _state)+1; + } + cinfo.ptr.pp_int[2*npoints-2][1] = 0; + cinfo.ptr.pp_int[2*npoints-2][2] = npoints-1; + for(i=npoints-2; i>=0; i--) + { + + /* + * We merge C0 which spans [A0,B0] and C1 (spans [A1,B1]), + * with unknown A0, B0, A1, B1. However, we know that result + * is CR, which spans [AR,BR] with known AR/BR, and we know + * sizes of C0, C1, CR (denotes as S0, S1, SR). + */ + c0 = rep->z.ptr.pp_int[i][0]; + c1 = rep->z.ptr.pp_int[i][1]; + s0 = cinfo.ptr.pp_int[c0][0]; + s1 = cinfo.ptr.pp_int[c1][0]; + ar = cinfo.ptr.pp_int[npoints+i][1]; + br = cinfo.ptr.pp_int[npoints+i][2]; + cinfo.ptr.pp_int[c0][1] = ar; + cinfo.ptr.pp_int[c0][2] = ar+s0-1; + cinfo.ptr.pp_int[c1][1] = br-(s1-1); + cinfo.ptr.pp_int[c1][2] = br; + rep->pm.ptr.pp_int[i][0] = cinfo.ptr.pp_int[c0][1]; + rep->pm.ptr.pp_int[i][1] = cinfo.ptr.pp_int[c0][2]; + rep->pm.ptr.pp_int[i][2] = cinfo.ptr.pp_int[c1][1]; + rep->pm.ptr.pp_int[i][3] = cinfo.ptr.pp_int[c1][2]; + rep->pm.ptr.pp_int[i][4] = cinfo.ptr.pp_int[c0][3]; + rep->pm.ptr.pp_int[i][5] = cinfo.ptr.pp_int[c1][3]; + } + for(i=0; i<=npoints-1; i++) + { + ae_assert(cinfo.ptr.pp_int[i][1]==cinfo.ptr.pp_int[i][2], "Assertion failed", _state); + rep->p.ptr.p_int[i] = cinfo.ptr.pp_int[i][1]; + } + + /* + * Calculate Rep.PZ + */ + ae_matrix_set_length(&rep->pz, npoints-1, 2, _state); + for(i=0; i<=npoints-2; i++) + { + rep->pz.ptr.pp_int[i][0] = rep->z.ptr.pp_int[i][0]; + rep->pz.ptr.pp_int[i][1] = rep->z.ptr.pp_int[i][1]; + if( rep->pz.ptr.pp_int[i][0]pz.ptr.pp_int[i][0] = rep->p.ptr.p_int[rep->pz.ptr.pp_int[i][0]]; + } + if( rep->pz.ptr.pp_int[i][1]pz.ptr.pp_int[i][1] = rep->p.ptr.p_int[rep->pz.ptr.pp_int[i][1]]; + } + } + ae_frame_leave(_state); +} + + +static void clustering_evaluatedistancematrixrec(/* Real */ ae_matrix* xy, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, + ae_int_t i0, + ae_int_t i1, + ae_int_t j0, + ae_int_t j1, + ae_state *_state) +{ + double rcomplexity; + ae_int_t len0; + ae_int_t len1; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + double vv; + + + ae_assert(disttype==0||disttype==1, "EvaluateDistanceMatrixRec: incorrect DistType", _state); + + /* + * Normalize J0/J1: + * * J0:=max(J0,I0) - we ignore lower triangle + * * J1:=max(J1,J0) - normalize J1 + */ + j0 = ae_maxint(j0, i0, _state); + j1 = ae_maxint(j1, j0, _state); + if( j1<=j0||i1<=i0 ) + { + return; + } + + /* + * Try to process in parallel. Two condtions must hold in order to + * activate parallel processing: + * 1. I1-I0>2 or J1-J0>2 + * 2. (I1-I0)*(J1-J0)*NFeatures>=ParallelComplexity + * + * NOTE: all quantities are converted to reals in order to avoid + * integer overflow during multiplication + * + * NOTE: strict inequality in (1) is necessary to reduce task to 2x2 + * basecases. In future versions we will be able to handle such + * basecases more efficiently than 1x1 cases. + */ + rcomplexity = i1-i0; + rcomplexity = rcomplexity*(j1-j0); + rcomplexity = rcomplexity*nfeatures; + if( ae_fp_greater_eq(rcomplexity,clustering_parallelcomplexity)&&(i1-i0>2||j1-j0>2) ) + { + + /* + * Recursive division along largest of dimensions + */ + if( i1-i0>j1-j0 ) + { + splitlengtheven(i1-i0, &len0, &len1, _state); + clustering_evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i0+len0, j0, j1, _state); + clustering_evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0+len0, i1, j0, j1, _state); + } + else + { + splitlengtheven(j1-j0, &len0, &len1, _state); + clustering_evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i1, j0, j0+len0, _state); + clustering_evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i1, j0+len0, j1, _state); + } + return; + } + + /* + * Sequential processing + */ + for(i=i0; i<=i1-1; i++) + { + for(j=j0; j<=j1-1; j++) + { + if( j>=i ) + { + v = 0.0; + if( disttype==0 ) + { + for(k=0; k<=nfeatures-1; k++) + { + vv = xy->ptr.pp_double[i][k]-xy->ptr.pp_double[j][k]; + if( ae_fp_less(vv,0) ) + { + vv = -vv; + } + if( ae_fp_greater(vv,v) ) + { + v = vv; + } + } + } + if( disttype==1 ) + { + for(k=0; k<=nfeatures-1; k++) + { + vv = xy->ptr.pp_double[i][k]-xy->ptr.pp_double[j][k]; + if( ae_fp_less(vv,0) ) + { + vv = -vv; + } + v = v+vv; + } + } + d->ptr.pp_double[i][j] = v; + } + } + } +} + + +ae_bool _clusterizerstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + clusterizerstate *p = (clusterizerstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->d, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _clusterizerstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + clusterizerstate *dst = (clusterizerstate*)_dst; + clusterizerstate *src = (clusterizerstate*)_src; + dst->npoints = src->npoints; + dst->nfeatures = src->nfeatures; + dst->disttype = src->disttype; + if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + dst->ahcalgo = src->ahcalgo; + dst->kmeansrestarts = src->kmeansrestarts; + dst->kmeansmaxits = src->kmeansmaxits; + return ae_true; +} + + +void _clusterizerstate_clear(void* _p) +{ + clusterizerstate *p = (clusterizerstate*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->xy); + ae_matrix_clear(&p->d); +} + + +void _clusterizerstate_destroy(void* _p) +{ + clusterizerstate *p = (clusterizerstate*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->xy); + ae_matrix_destroy(&p->d); +} + + +ae_bool _ahcreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + ahcreport *p = (ahcreport*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->p, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->z, 0, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->pz, 0, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->pm, 0, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mergedist, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _ahcreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + ahcreport *dst = (ahcreport*)_dst; + ahcreport *src = (ahcreport*)_src; + dst->npoints = src->npoints; + if( !ae_vector_init_copy(&dst->p, &src->p, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->z, &src->z, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->pz, &src->pz, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->pm, &src->pm, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mergedist, &src->mergedist, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _ahcreport_clear(void* _p) +{ + ahcreport *p = (ahcreport*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->p); + ae_matrix_clear(&p->z); + ae_matrix_clear(&p->pz); + ae_matrix_clear(&p->pm); + ae_vector_clear(&p->mergedist); +} + + +void _ahcreport_destroy(void* _p) +{ + ahcreport *p = (ahcreport*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->p); + ae_matrix_destroy(&p->z); + ae_matrix_destroy(&p->pz); + ae_matrix_destroy(&p->pm); + ae_vector_destroy(&p->mergedist); +} + + +ae_bool _kmeansreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + kmeansreport *p = (kmeansreport*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cidx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _kmeansreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + kmeansreport *dst = (kmeansreport*)_dst; + kmeansreport *src = (kmeansreport*)_src; + dst->npoints = src->npoints; + dst->nfeatures = src->nfeatures; + dst->terminationtype = src->terminationtype; + dst->k = src->k; + if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cidx, &src->cidx, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _kmeansreport_clear(void* _p) +{ + kmeansreport *p = (kmeansreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->c); + ae_vector_clear(&p->cidx); +} + + +void _kmeansreport_destroy(void* _p) +{ + kmeansreport *p = (kmeansreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->c); + ae_vector_destroy(&p->cidx); +} + + + + +/************************************************************************* +k-means++ clusterization. +Backward compatibility function, we recommend to use CLUSTERING subpackage +as better replacement. + + -- ALGLIB -- + Copyright 21.03.2009 by Bochkanov Sergey +*************************************************************************/ +void kmeansgenerate(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t k, + ae_int_t restarts, + ae_int_t* info, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* xyc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix dummy; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_matrix_clear(c); + ae_vector_clear(xyc); + ae_matrix_init(&dummy, 0, 0, DT_REAL, _state, ae_true); + + kmeansgenerateinternal(xy, npoints, nvars, k, 0, restarts, info, c, ae_true, &dummy, ae_false, xyc, _state); + ae_frame_leave(_state); +} + + + + +/************************************************************************* +This subroutine builds random decision forest. + +INPUT PARAMETERS: + XY - training set + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforest(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + double r, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state) +{ + ae_int_t samplesize; + + *info = 0; + _decisionforest_clear(df); + _dfreport_clear(rep); + + if( ae_fp_less_eq(r,0)||ae_fp_greater(r,1) ) + { + *info = -1; + return; + } + samplesize = ae_maxint(ae_round(r*npoints, _state), 1, _state); + dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, ae_maxint(nvars/2, 1, _state), dforest_dfusestrongsplits+dforest_dfuseevs, info, df, rep, _state); +} + + +/************************************************************************* +This subroutine builds random decision forest. +This function gives ability to tune number of variables used when choosing +best split. + +INPUT PARAMETERS: + XY - training set + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + NRndVars - number of variables used when choosing best split + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforestx1(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + ae_int_t nrndvars, + double r, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state) +{ + ae_int_t samplesize; + + *info = 0; + _decisionforest_clear(df); + _dfreport_clear(rep); + + if( ae_fp_less_eq(r,0)||ae_fp_greater(r,1) ) + { + *info = -1; + return; + } + if( nrndvars<=0||nrndvars>nvars ) + { + *info = -1; + return; + } + samplesize = ae_maxint(ae_round(r*npoints, _state), 1, _state); + dfbuildinternal(xy, npoints, nvars, nclasses, ntrees, samplesize, nrndvars, dforest_dfusestrongsplits+dforest_dfuseevs, info, df, rep, _state); +} + + +void dfbuildinternal(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + ae_int_t samplesize, + ae_int_t nfeatures, + ae_int_t flags, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t tmpi; + ae_int_t lasttreeoffs; + ae_int_t offs; + ae_int_t ooboffs; + ae_int_t treesize; + ae_int_t nvarsinpool; + ae_bool useevs; + dfinternalbuffers bufs; + ae_vector permbuf; + ae_vector oobbuf; + ae_vector oobcntbuf; + ae_matrix xys; + ae_vector x; + ae_vector y; + ae_int_t oobcnt; + ae_int_t oobrelcnt; + double v; + double vmin; + double vmax; + ae_bool bflag; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _decisionforest_clear(df); + _dfreport_clear(rep); + _dfinternalbuffers_init(&bufs, _state, ae_true); + ae_vector_init(&permbuf, 0, DT_INT, _state, ae_true); + ae_vector_init(&oobbuf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&oobcntbuf, 0, DT_INT, _state, ae_true); + ae_matrix_init(&xys, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&rs, _state, ae_true); + + + /* + * Test for inputs + */ + if( (((((npoints<1||samplesize<1)||samplesize>npoints)||nvars<1)||nclasses<1)||ntrees<1)||nfeatures<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( nclasses>1 ) + { + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + *info = 1; + + /* + * Flags + */ + useevs = flags/dforest_dfuseevs%2!=0; + + /* + * Allocate data, prepare header + */ + treesize = 1+dforest_innernodewidth*(samplesize-1)+dforest_leafnodewidth*samplesize; + ae_vector_set_length(&permbuf, npoints-1+1, _state); + ae_vector_set_length(&bufs.treebuf, treesize-1+1, _state); + ae_vector_set_length(&bufs.idxbuf, npoints-1+1, _state); + ae_vector_set_length(&bufs.tmpbufr, npoints-1+1, _state); + ae_vector_set_length(&bufs.tmpbufr2, npoints-1+1, _state); + ae_vector_set_length(&bufs.tmpbufi, npoints-1+1, _state); + ae_vector_set_length(&bufs.sortrbuf, npoints, _state); + ae_vector_set_length(&bufs.sortrbuf2, npoints, _state); + ae_vector_set_length(&bufs.sortibuf, npoints, _state); + ae_vector_set_length(&bufs.varpool, nvars-1+1, _state); + ae_vector_set_length(&bufs.evsbin, nvars-1+1, _state); + ae_vector_set_length(&bufs.evssplits, nvars-1+1, _state); + ae_vector_set_length(&bufs.classibuf, 2*nclasses-1+1, _state); + ae_vector_set_length(&oobbuf, nclasses*npoints-1+1, _state); + ae_vector_set_length(&oobcntbuf, npoints-1+1, _state); + ae_vector_set_length(&df->trees, ntrees*treesize-1+1, _state); + ae_matrix_set_length(&xys, samplesize-1+1, nvars+1, _state); + ae_vector_set_length(&x, nvars-1+1, _state); + ae_vector_set_length(&y, nclasses-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + permbuf.ptr.p_int[i] = i; + } + for(i=0; i<=npoints*nclasses-1; i++) + { + oobbuf.ptr.p_double[i] = 0; + } + for(i=0; i<=npoints-1; i++) + { + oobcntbuf.ptr.p_int[i] = 0; + } + + /* + * Prepare variable pool and EVS (extended variable selection/splitting) buffers + * (whether EVS is turned on or not): + * 1. detect binary variables and pre-calculate splits for them + * 2. detect variables with non-distinct values and exclude them from pool + */ + for(i=0; i<=nvars-1; i++) + { + bufs.varpool.ptr.p_int[i] = i; + } + nvarsinpool = nvars; + if( useevs ) + { + for(j=0; j<=nvars-1; j++) + { + vmin = xy->ptr.pp_double[0][j]; + vmax = vmin; + for(i=0; i<=npoints-1; i++) + { + v = xy->ptr.pp_double[i][j]; + vmin = ae_minreal(vmin, v, _state); + vmax = ae_maxreal(vmax, v, _state); + } + if( ae_fp_eq(vmin,vmax) ) + { + + /* + * exclude variable from pool + */ + bufs.varpool.ptr.p_int[j] = bufs.varpool.ptr.p_int[nvarsinpool-1]; + bufs.varpool.ptr.p_int[nvarsinpool-1] = -1; + nvarsinpool = nvarsinpool-1; + continue; + } + bflag = ae_false; + for(i=0; i<=npoints-1; i++) + { + v = xy->ptr.pp_double[i][j]; + if( ae_fp_neq(v,vmin)&&ae_fp_neq(v,vmax) ) + { + bflag = ae_true; + break; + } + } + if( bflag ) + { + + /* + * non-binary variable + */ + bufs.evsbin.ptr.p_bool[j] = ae_false; + } + else + { + + /* + * Prepare + */ + bufs.evsbin.ptr.p_bool[j] = ae_true; + bufs.evssplits.ptr.p_double[j] = 0.5*(vmin+vmax); + if( ae_fp_less_eq(bufs.evssplits.ptr.p_double[j],vmin) ) + { + bufs.evssplits.ptr.p_double[j] = vmax; + } + } + } + } + + /* + * RANDOM FOREST FORMAT + * W[0] - size of array + * W[1] - version number + * W[2] - NVars + * W[3] - NClasses (1 for regression) + * W[4] - NTrees + * W[5] - trees offset + * + * + * TREE FORMAT + * W[Offs] - size of sub-array + * node info: + * W[K+0] - variable number (-1 for leaf mode) + * W[K+1] - threshold (class/value for leaf node) + * W[K+2] - ">=" branch index (absent for leaf node) + * + */ + df->nvars = nvars; + df->nclasses = nclasses; + df->ntrees = ntrees; + + /* + * Build forest + */ + hqrndrandomize(&rs, _state); + offs = 0; + for(i=0; i<=ntrees-1; i++) + { + + /* + * Prepare sample + */ + for(k=0; k<=samplesize-1; k++) + { + j = k+hqrnduniformi(&rs, npoints-k, _state); + tmpi = permbuf.ptr.p_int[k]; + permbuf.ptr.p_int[k] = permbuf.ptr.p_int[j]; + permbuf.ptr.p_int[j] = tmpi; + j = permbuf.ptr.p_int[k]; + ae_v_move(&xys.ptr.pp_double[k][0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars)); + } + + /* + * build tree, copy + */ + dforest_dfbuildtree(&xys, samplesize, nvars, nclasses, nfeatures, nvarsinpool, flags, &bufs, &rs, _state); + j = ae_round(bufs.treebuf.ptr.p_double[0], _state); + ae_v_move(&df->trees.ptr.p_double[offs], 1, &bufs.treebuf.ptr.p_double[0], 1, ae_v_len(offs,offs+j-1)); + lasttreeoffs = offs; + offs = offs+j; + + /* + * OOB estimates + */ + for(k=samplesize; k<=npoints-1; k++) + { + for(j=0; j<=nclasses-1; j++) + { + y.ptr.p_double[j] = 0; + } + j = permbuf.ptr.p_int[k]; + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + dforest_dfprocessinternal(df, lasttreeoffs, &x, &y, _state); + ae_v_add(&oobbuf.ptr.p_double[j*nclasses], 1, &y.ptr.p_double[0], 1, ae_v_len(j*nclasses,(j+1)*nclasses-1)); + oobcntbuf.ptr.p_int[j] = oobcntbuf.ptr.p_int[j]+1; + } + } + df->bufsize = offs; + + /* + * Normalize OOB results + */ + for(i=0; i<=npoints-1; i++) + { + if( oobcntbuf.ptr.p_int[i]!=0 ) + { + v = (double)1/(double)oobcntbuf.ptr.p_int[i]; + ae_v_muld(&oobbuf.ptr.p_double[i*nclasses], 1, ae_v_len(i*nclasses,i*nclasses+nclasses-1), v); + } + } + + /* + * Calculate training set estimates + */ + rep->relclserror = dfrelclserror(df, xy, npoints, _state); + rep->avgce = dfavgce(df, xy, npoints, _state); + rep->rmserror = dfrmserror(df, xy, npoints, _state); + rep->avgerror = dfavgerror(df, xy, npoints, _state); + rep->avgrelerror = dfavgrelerror(df, xy, npoints, _state); + + /* + * Calculate OOB estimates. + */ + rep->oobrelclserror = 0; + rep->oobavgce = 0; + rep->oobrmserror = 0; + rep->oobavgerror = 0; + rep->oobavgrelerror = 0; + oobcnt = 0; + oobrelcnt = 0; + for(i=0; i<=npoints-1; i++) + { + if( oobcntbuf.ptr.p_int[i]!=0 ) + { + ooboffs = i*nclasses; + if( nclasses>1 ) + { + + /* + * classification-specific code + */ + k = ae_round(xy->ptr.pp_double[i][nvars], _state); + tmpi = 0; + for(j=1; j<=nclasses-1; j++) + { + if( ae_fp_greater(oobbuf.ptr.p_double[ooboffs+j],oobbuf.ptr.p_double[ooboffs+tmpi]) ) + { + tmpi = j; + } + } + if( tmpi!=k ) + { + rep->oobrelclserror = rep->oobrelclserror+1; + } + if( ae_fp_neq(oobbuf.ptr.p_double[ooboffs+k],0) ) + { + rep->oobavgce = rep->oobavgce-ae_log(oobbuf.ptr.p_double[ooboffs+k], _state); + } + else + { + rep->oobavgce = rep->oobavgce-ae_log(ae_minrealnumber, _state); + } + for(j=0; j<=nclasses-1; j++) + { + if( j==k ) + { + rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs+j]-1, _state); + rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j]-1, _state); + rep->oobavgrelerror = rep->oobavgrelerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j]-1, _state); + oobrelcnt = oobrelcnt+1; + } + else + { + rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs+j], _state); + rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs+j], _state); + } + } + } + else + { + + /* + * regression-specific code + */ + rep->oobrmserror = rep->oobrmserror+ae_sqr(oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars], _state); + rep->oobavgerror = rep->oobavgerror+ae_fabs(oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars], _state); + if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) ) + { + rep->oobavgrelerror = rep->oobavgrelerror+ae_fabs((oobbuf.ptr.p_double[ooboffs]-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state); + oobrelcnt = oobrelcnt+1; + } + } + + /* + * update OOB estimates count. + */ + oobcnt = oobcnt+1; + } + } + if( oobcnt>0 ) + { + rep->oobrelclserror = rep->oobrelclserror/oobcnt; + rep->oobavgce = rep->oobavgce/oobcnt; + rep->oobrmserror = ae_sqrt(rep->oobrmserror/(oobcnt*nclasses), _state); + rep->oobavgerror = rep->oobavgerror/(oobcnt*nclasses); + if( oobrelcnt>0 ) + { + rep->oobavgrelerror = rep->oobavgrelerror/oobrelcnt; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + DF - decision forest model + X - input vector, array[0..NVars-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also DFProcessI. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfprocess(decisionforest* df, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t offs; + ae_int_t i; + double v; + + + + /* + * Proceed + */ + if( y->cntnclasses ) + { + ae_vector_set_length(y, df->nclasses, _state); + } + offs = 0; + for(i=0; i<=df->nclasses-1; i++) + { + y->ptr.p_double[i] = 0; + } + for(i=0; i<=df->ntrees-1; i++) + { + + /* + * Process basic tree + */ + dforest_dfprocessinternal(df, offs, x, y, _state); + + /* + * Next tree + */ + offs = offs+ae_round(df->trees.ptr.p_double[offs], _state); + } + v = (double)1/(double)df->ntrees; + ae_v_muld(&y->ptr.p_double[0], 1, ae_v_len(0,df->nclasses-1), v); +} + + +/************************************************************************* +'interactive' variant of DFProcess for languages like Python which support +constructs like "Y = DFProcessI(DF,X)" and interactive mode of interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void dfprocessi(decisionforest* df, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + ae_vector_clear(y); + + dfprocess(df, x, y, _state); +} + + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrelclserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + result = (double)dforest_dfclserror(df, xy, npoints, _state)/(double)npoints; + return result; +} + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgce(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t tmpi; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&x, df->nvars-1+1, _state); + ae_vector_set_length(&y, df->nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1)); + dfprocess(df, &x, &y, _state); + if( df->nclasses>1 ) + { + + /* + * classification-specific code + */ + k = ae_round(xy->ptr.pp_double[i][df->nvars], _state); + tmpi = 0; + for(j=1; j<=df->nclasses-1; j++) + { + if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) ) + { + tmpi = j; + } + } + if( ae_fp_neq(y.ptr.p_double[k],0) ) + { + result = result-ae_log(y.ptr.p_double[k], _state); + } + else + { + result = result-ae_log(ae_minrealnumber, _state); + } + } + } + result = result/npoints; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for + classification task, RMS error means error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrmserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t tmpi; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&x, df->nvars-1+1, _state); + ae_vector_set_length(&y, df->nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1)); + dfprocess(df, &x, &y, _state); + if( df->nclasses>1 ) + { + + /* + * classification-specific code + */ + k = ae_round(xy->ptr.pp_double[i][df->nvars], _state); + tmpi = 0; + for(j=1; j<=df->nclasses-1; j++) + { + if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) ) + { + tmpi = j; + } + } + for(j=0; j<=df->nclasses-1; j++) + { + if( j==k ) + { + result = result+ae_sqr(y.ptr.p_double[j]-1, _state); + } + else + { + result = result+ae_sqr(y.ptr.p_double[j], _state); + } + } + } + else + { + + /* + * regression-specific code + */ + result = result+ae_sqr(y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars], _state); + } + } + result = ae_sqrt(result/(npoints*df->nclasses), _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgerror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&x, df->nvars-1+1, _state); + ae_vector_set_length(&y, df->nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1)); + dfprocess(df, &x, &y, _state); + if( df->nclasses>1 ) + { + + /* + * classification-specific code + */ + k = ae_round(xy->ptr.pp_double[i][df->nvars], _state); + for(j=0; j<=df->nclasses-1; j++) + { + if( j==k ) + { + result = result+ae_fabs(y.ptr.p_double[j]-1, _state); + } + else + { + result = result+ae_fabs(y.ptr.p_double[j], _state); + } + } + } + else + { + + /* + * regression-specific code + */ + result = result+ae_fabs(y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars], _state); + } + } + result = result/(npoints*df->nclasses); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average relative error when estimating + posterior probability of belonging to the correct class. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgrelerror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_int_t relcnt; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&x, df->nvars-1+1, _state); + ae_vector_set_length(&y, df->nclasses-1+1, _state); + result = 0; + relcnt = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1)); + dfprocess(df, &x, &y, _state); + if( df->nclasses>1 ) + { + + /* + * classification-specific code + */ + k = ae_round(xy->ptr.pp_double[i][df->nvars], _state); + for(j=0; j<=df->nclasses-1; j++) + { + if( j==k ) + { + result = result+ae_fabs(y.ptr.p_double[j]-1, _state); + relcnt = relcnt+1; + } + } + } + else + { + + /* + * regression-specific code + */ + if( ae_fp_neq(xy->ptr.pp_double[i][df->nvars],0) ) + { + result = result+ae_fabs((y.ptr.p_double[0]-xy->ptr.pp_double[i][df->nvars])/xy->ptr.pp_double[i][df->nvars], _state); + relcnt = relcnt+1; + } + } + } + if( relcnt>0 ) + { + result = result/relcnt; + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Copying of DecisionForest strucure + +INPUT PARAMETERS: + DF1 - original + +OUTPUT PARAMETERS: + DF2 - copy + + -- ALGLIB -- + Copyright 13.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfcopy(decisionforest* df1, decisionforest* df2, ae_state *_state) +{ + + _decisionforest_clear(df2); + + df2->nvars = df1->nvars; + df2->nclasses = df1->nclasses; + df2->ntrees = df1->ntrees; + df2->bufsize = df1->bufsize; + ae_vector_set_length(&df2->trees, df1->bufsize-1+1, _state); + ae_v_move(&df2->trees.ptr.p_double[0], 1, &df1->trees.ptr.p_double[0], 1, ae_v_len(0,df1->bufsize-1)); +} + + +/************************************************************************* +Serializer: allocation + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void dfalloc(ae_serializer* s, decisionforest* forest, ae_state *_state) +{ + + + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + allocrealarray(s, &forest->trees, forest->bufsize, _state); +} + + +/************************************************************************* +Serializer: serialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void dfserialize(ae_serializer* s, + decisionforest* forest, + ae_state *_state) +{ + + + ae_serializer_serialize_int(s, getrdfserializationcode(_state), _state); + ae_serializer_serialize_int(s, dforest_dffirstversion, _state); + ae_serializer_serialize_int(s, forest->nvars, _state); + ae_serializer_serialize_int(s, forest->nclasses, _state); + ae_serializer_serialize_int(s, forest->ntrees, _state); + ae_serializer_serialize_int(s, forest->bufsize, _state); + serializerealarray(s, &forest->trees, forest->bufsize, _state); +} + + +/************************************************************************* +Serializer: unserialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void dfunserialize(ae_serializer* s, + decisionforest* forest, + ae_state *_state) +{ + ae_int_t i0; + ae_int_t i1; + + _decisionforest_clear(forest); + + + /* + * check correctness of header + */ + ae_serializer_unserialize_int(s, &i0, _state); + ae_assert(i0==getrdfserializationcode(_state), "DFUnserialize: stream header corrupted", _state); + ae_serializer_unserialize_int(s, &i1, _state); + ae_assert(i1==dforest_dffirstversion, "DFUnserialize: stream header corrupted", _state); + + /* + * Unserialize data + */ + ae_serializer_unserialize_int(s, &forest->nvars, _state); + ae_serializer_unserialize_int(s, &forest->nclasses, _state); + ae_serializer_unserialize_int(s, &forest->ntrees, _state); + ae_serializer_unserialize_int(s, &forest->bufsize, _state); + unserializerealarray(s, &forest->trees, _state); +} + + +/************************************************************************* +Classification error +*************************************************************************/ +static ae_int_t dforest_dfclserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t tmpi; + ae_int_t result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + if( df->nclasses<=1 ) + { + result = 0; + ae_frame_leave(_state); + return result; + } + ae_vector_set_length(&x, df->nvars-1+1, _state); + ae_vector_set_length(&y, df->nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,df->nvars-1)); + dfprocess(df, &x, &y, _state); + k = ae_round(xy->ptr.pp_double[i][df->nvars], _state); + tmpi = 0; + for(j=1; j<=df->nclasses-1; j++) + { + if( ae_fp_greater(y.ptr.p_double[j],y.ptr.p_double[tmpi]) ) + { + tmpi = j; + } + } + if( tmpi!=k ) + { + result = result+1; + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Internal subroutine for processing one decision tree starting at Offs +*************************************************************************/ +static void dforest_dfprocessinternal(decisionforest* df, + ae_int_t offs, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t k; + ae_int_t idx; + + + + /* + * Set pointer to the root + */ + k = offs+1; + + /* + * Navigate through the tree + */ + for(;;) + { + if( ae_fp_eq(df->trees.ptr.p_double[k],-1) ) + { + if( df->nclasses==1 ) + { + y->ptr.p_double[0] = y->ptr.p_double[0]+df->trees.ptr.p_double[k+1]; + } + else + { + idx = ae_round(df->trees.ptr.p_double[k+1], _state); + y->ptr.p_double[idx] = y->ptr.p_double[idx]+1; + } + break; + } + if( ae_fp_less(x->ptr.p_double[ae_round(df->trees.ptr.p_double[k], _state)],df->trees.ptr.p_double[k+1]) ) + { + k = k+dforest_innernodewidth; + } + else + { + k = offs+ae_round(df->trees.ptr.p_double[k+2], _state); + } + } +} + + +/************************************************************************* +Builds one decision tree. Just a wrapper for the DFBuildTreeRec. +*************************************************************************/ +static void dforest_dfbuildtree(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t nfeatures, + ae_int_t nvarsinpool, + ae_int_t flags, + dfinternalbuffers* bufs, + hqrndstate* rs, + ae_state *_state) +{ + ae_int_t numprocessed; + ae_int_t i; + + + ae_assert(npoints>0, "Assertion failed", _state); + + /* + * Prepare IdxBuf. It stores indices of the training set elements. + * When training set is being split, contents of IdxBuf is + * correspondingly reordered so we can know which elements belong + * to which branch of decision tree. + */ + for(i=0; i<=npoints-1; i++) + { + bufs->idxbuf.ptr.p_int[i] = i; + } + + /* + * Recursive procedure + */ + numprocessed = 1; + dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, &numprocessed, 0, npoints-1, bufs, rs, _state); + bufs->treebuf.ptr.p_double[0] = numprocessed; +} + + +/************************************************************************* +Builds one decision tree (internal recursive subroutine) + +Parameters: + TreeBuf - large enough array, at least TreeSize + IdxBuf - at least NPoints elements + TmpBufR - at least NPoints + TmpBufR2 - at least NPoints + TmpBufI - at least NPoints + TmpBufI2 - at least NPoints+1 +*************************************************************************/ +static void dforest_dfbuildtreerec(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t nfeatures, + ae_int_t nvarsinpool, + ae_int_t flags, + ae_int_t* numprocessed, + ae_int_t idx1, + ae_int_t idx2, + dfinternalbuffers* bufs, + hqrndstate* rs, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_bool bflag; + ae_int_t i1; + ae_int_t i2; + ae_int_t info; + double sl; + double sr; + double w; + ae_int_t idxbest; + double ebest; + double tbest; + ae_int_t varcur; + double s; + double v; + double v1; + double v2; + double threshold; + ae_int_t oldnp; + double currms; + ae_bool useevs; + + + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + tbest = 0; + + /* + * Prepare + */ + ae_assert(npoints>0, "Assertion failed", _state); + ae_assert(idx2>=idx1, "Assertion failed", _state); + useevs = flags/dforest_dfuseevs%2!=0; + + /* + * Leaf node + */ + if( idx2==idx1 ) + { + bufs->treebuf.ptr.p_double[*numprocessed] = -1; + bufs->treebuf.ptr.p_double[*numprocessed+1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[idx1]][nvars]; + *numprocessed = *numprocessed+dforest_leafnodewidth; + return; + } + + /* + * Non-leaf node. + * Select random variable, prepare split: + * 1. prepare default solution - no splitting, class at random + * 2. investigate possible splits, compare with default/best + */ + idxbest = -1; + if( nclasses>1 ) + { + + /* + * default solution for classification + */ + for(i=0; i<=nclasses-1; i++) + { + bufs->classibuf.ptr.p_int[i] = 0; + } + s = idx2-idx1+1; + for(i=idx1; i<=idx2; i++) + { + j = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars], _state); + bufs->classibuf.ptr.p_int[j] = bufs->classibuf.ptr.p_int[j]+1; + } + ebest = 0; + for(i=0; i<=nclasses-1; i++) + { + ebest = ebest+bufs->classibuf.ptr.p_int[i]*ae_sqr(1-bufs->classibuf.ptr.p_int[i]/s, _state)+(s-bufs->classibuf.ptr.p_int[i])*ae_sqr(bufs->classibuf.ptr.p_int[i]/s, _state); + } + ebest = ae_sqrt(ebest/(nclasses*(idx2-idx1+1)), _state); + } + else + { + + /* + * default solution for regression + */ + v = 0; + for(i=idx1; i<=idx2; i++) + { + v = v+xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars]; + } + v = v/(idx2-idx1+1); + ebest = 0; + for(i=idx1; i<=idx2; i++) + { + ebest = ebest+ae_sqr(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars]-v, _state); + } + ebest = ae_sqrt(ebest/(idx2-idx1+1), _state); + } + i = 0; + while(i<=ae_minint(nfeatures, nvarsinpool, _state)-1) + { + + /* + * select variables from pool + */ + j = i+hqrnduniformi(rs, nvarsinpool-i, _state); + k = bufs->varpool.ptr.p_int[i]; + bufs->varpool.ptr.p_int[i] = bufs->varpool.ptr.p_int[j]; + bufs->varpool.ptr.p_int[j] = k; + varcur = bufs->varpool.ptr.p_int[i]; + + /* + * load variable values to working array + * + * apply EVS preprocessing: if all variable values are same, + * variable is excluded from pool. + * + * This is necessary for binary pre-splits (see later) to work. + */ + for(j=idx1; j<=idx2; j++) + { + bufs->tmpbufr.ptr.p_double[j-idx1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][varcur]; + } + if( useevs ) + { + bflag = ae_false; + v = bufs->tmpbufr.ptr.p_double[0]; + for(j=0; j<=idx2-idx1; j++) + { + if( ae_fp_neq(bufs->tmpbufr.ptr.p_double[j],v) ) + { + bflag = ae_true; + break; + } + } + if( !bflag ) + { + + /* + * exclude variable from pool, + * go to the next iteration. + * I is not increased. + */ + k = bufs->varpool.ptr.p_int[i]; + bufs->varpool.ptr.p_int[i] = bufs->varpool.ptr.p_int[nvarsinpool-1]; + bufs->varpool.ptr.p_int[nvarsinpool-1] = k; + nvarsinpool = nvarsinpool-1; + continue; + } + } + + /* + * load labels to working array + */ + if( nclasses>1 ) + { + for(j=idx1; j<=idx2; j++) + { + bufs->tmpbufi.ptr.p_int[j-idx1] = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][nvars], _state); + } + } + else + { + for(j=idx1; j<=idx2; j++) + { + bufs->tmpbufr2.ptr.p_double[j-idx1] = xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[j]][nvars]; + } + } + + /* + * calculate split + */ + if( useevs&&bufs->evsbin.ptr.p_bool[varcur] ) + { + + /* + * Pre-calculated splits for binary variables. + * Threshold is already known, just calculate RMS error + */ + threshold = bufs->evssplits.ptr.p_double[varcur]; + if( nclasses>1 ) + { + + /* + * classification-specific code + */ + for(j=0; j<=2*nclasses-1; j++) + { + bufs->classibuf.ptr.p_int[j] = 0; + } + sl = 0; + sr = 0; + for(j=0; j<=idx2-idx1; j++) + { + k = bufs->tmpbufi.ptr.p_int[j]; + if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) ) + { + bufs->classibuf.ptr.p_int[k] = bufs->classibuf.ptr.p_int[k]+1; + sl = sl+1; + } + else + { + bufs->classibuf.ptr.p_int[k+nclasses] = bufs->classibuf.ptr.p_int[k+nclasses]+1; + sr = sr+1; + } + } + ae_assert(ae_fp_neq(sl,0)&&ae_fp_neq(sr,0), "DFBuildTreeRec: something strange!", _state); + currms = 0; + for(j=0; j<=nclasses-1; j++) + { + w = bufs->classibuf.ptr.p_int[j]; + currms = currms+w*ae_sqr(w/sl-1, _state); + currms = currms+(sl-w)*ae_sqr(w/sl, _state); + w = bufs->classibuf.ptr.p_int[nclasses+j]; + currms = currms+w*ae_sqr(w/sr-1, _state); + currms = currms+(sr-w)*ae_sqr(w/sr, _state); + } + currms = ae_sqrt(currms/(nclasses*(idx2-idx1+1)), _state); + } + else + { + + /* + * regression-specific code + */ + sl = 0; + sr = 0; + v1 = 0; + v2 = 0; + for(j=0; j<=idx2-idx1; j++) + { + if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) ) + { + v1 = v1+bufs->tmpbufr2.ptr.p_double[j]; + sl = sl+1; + } + else + { + v2 = v2+bufs->tmpbufr2.ptr.p_double[j]; + sr = sr+1; + } + } + ae_assert(ae_fp_neq(sl,0)&&ae_fp_neq(sr,0), "DFBuildTreeRec: something strange!", _state); + v1 = v1/sl; + v2 = v2/sr; + currms = 0; + for(j=0; j<=idx2-idx1; j++) + { + if( ae_fp_less(bufs->tmpbufr.ptr.p_double[j],threshold) ) + { + currms = currms+ae_sqr(v1-bufs->tmpbufr2.ptr.p_double[j], _state); + } + else + { + currms = currms+ae_sqr(v2-bufs->tmpbufr2.ptr.p_double[j], _state); + } + } + currms = ae_sqrt(currms/(idx2-idx1+1), _state); + } + info = 1; + } + else + { + + /* + * Generic splits + */ + if( nclasses>1 ) + { + dforest_dfsplitc(&bufs->tmpbufr, &bufs->tmpbufi, &bufs->classibuf, idx2-idx1+1, nclasses, dforest_dfusestrongsplits, &info, &threshold, &currms, &bufs->sortrbuf, &bufs->sortibuf, _state); + } + else + { + dforest_dfsplitr(&bufs->tmpbufr, &bufs->tmpbufr2, idx2-idx1+1, dforest_dfusestrongsplits, &info, &threshold, &currms, &bufs->sortrbuf, &bufs->sortrbuf2, _state); + } + } + if( info>0 ) + { + if( ae_fp_less_eq(currms,ebest) ) + { + ebest = currms; + idxbest = varcur; + tbest = threshold; + } + } + + /* + * Next iteration + */ + i = i+1; + } + + /* + * to split or not to split + */ + if( idxbest<0 ) + { + + /* + * All values are same, cannot split. + */ + bufs->treebuf.ptr.p_double[*numprocessed] = -1; + if( nclasses>1 ) + { + + /* + * Select random class label (randomness allows us to + * approximate distribution of the classes) + */ + bufs->treebuf.ptr.p_double[*numprocessed+1] = ae_round(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[idx1+hqrnduniformi(rs, idx2-idx1+1, _state)]][nvars], _state); + } + else + { + + /* + * Select average (for regression task). + */ + v = 0; + for(i=idx1; i<=idx2; i++) + { + v = v+xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i]][nvars]/(idx2-idx1+1); + } + bufs->treebuf.ptr.p_double[*numprocessed+1] = v; + } + *numprocessed = *numprocessed+dforest_leafnodewidth; + } + else + { + + /* + * we can split + */ + bufs->treebuf.ptr.p_double[*numprocessed] = idxbest; + bufs->treebuf.ptr.p_double[*numprocessed+1] = tbest; + i1 = idx1; + i2 = idx2; + while(i1<=i2) + { + + /* + * Reorder indices so that left partition is in [Idx1..I1-1], + * and right partition is in [I2+1..Idx2] + */ + if( ae_fp_less(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i1]][idxbest],tbest) ) + { + i1 = i1+1; + continue; + } + if( ae_fp_greater_eq(xy->ptr.pp_double[bufs->idxbuf.ptr.p_int[i2]][idxbest],tbest) ) + { + i2 = i2-1; + continue; + } + j = bufs->idxbuf.ptr.p_int[i1]; + bufs->idxbuf.ptr.p_int[i1] = bufs->idxbuf.ptr.p_int[i2]; + bufs->idxbuf.ptr.p_int[i2] = j; + i1 = i1+1; + i2 = i2-1; + } + oldnp = *numprocessed; + *numprocessed = *numprocessed+dforest_innernodewidth; + dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, numprocessed, idx1, i1-1, bufs, rs, _state); + bufs->treebuf.ptr.p_double[oldnp+2] = *numprocessed; + dforest_dfbuildtreerec(xy, npoints, nvars, nclasses, nfeatures, nvarsinpool, flags, numprocessed, i2+1, idx2, bufs, rs, _state); + } +} + + +/************************************************************************* +Makes split on attribute +*************************************************************************/ +static void dforest_dfsplitc(/* Real */ ae_vector* x, + /* Integer */ ae_vector* c, + /* Integer */ ae_vector* cntbuf, + ae_int_t n, + ae_int_t nc, + ae_int_t flags, + ae_int_t* info, + double* threshold, + double* e, + /* Real */ ae_vector* sortrbuf, + /* Integer */ ae_vector* sortibuf, + ae_state *_state) +{ + ae_int_t i; + ae_int_t neq; + ae_int_t nless; + ae_int_t ngreater; + ae_int_t q; + ae_int_t qmin; + ae_int_t qmax; + ae_int_t qcnt; + double cursplit; + ae_int_t nleft; + double v; + double cure; + double w; + double sl; + double sr; + + *info = 0; + *threshold = 0; + *e = 0; + + tagsortfasti(x, c, sortrbuf, sortibuf, n, _state); + *e = ae_maxrealnumber; + *threshold = 0.5*(x->ptr.p_double[0]+x->ptr.p_double[n-1]); + *info = -3; + if( flags/dforest_dfusestrongsplits%2==0 ) + { + + /* + * weak splits, split at half + */ + qcnt = 2; + qmin = 1; + qmax = 1; + } + else + { + + /* + * strong splits: choose best quartile + */ + qcnt = 4; + qmin = 1; + qmax = 3; + } + for(q=qmin; q<=qmax; q++) + { + cursplit = x->ptr.p_double[n*q/qcnt]; + neq = 0; + nless = 0; + ngreater = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_less(x->ptr.p_double[i],cursplit) ) + { + nless = nless+1; + } + if( ae_fp_eq(x->ptr.p_double[i],cursplit) ) + { + neq = neq+1; + } + if( ae_fp_greater(x->ptr.p_double[i],cursplit) ) + { + ngreater = ngreater+1; + } + } + ae_assert(neq!=0, "DFSplitR: NEq=0, something strange!!!", _state); + if( nless!=0||ngreater!=0 ) + { + + /* + * set threshold between two partitions, with + * some tweaking to avoid problems with floating point + * arithmetics. + * + * The problem is that when you calculates C = 0.5*(A+B) there + * can be no C which lies strictly between A and B (for example, + * there is no floating point number which is + * greater than 1 and less than 1+eps). In such situations + * we choose right side as theshold (remember that + * points which lie on threshold falls to the right side). + */ + if( nlessptr.p_double[nless+neq-1]+x->ptr.p_double[nless+neq]); + nleft = nless+neq; + if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless+neq-1]) ) + { + cursplit = x->ptr.p_double[nless+neq]; + } + } + else + { + cursplit = 0.5*(x->ptr.p_double[nless-1]+x->ptr.p_double[nless]); + nleft = nless; + if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless-1]) ) + { + cursplit = x->ptr.p_double[nless]; + } + } + *info = 1; + cure = 0; + for(i=0; i<=2*nc-1; i++) + { + cntbuf->ptr.p_int[i] = 0; + } + for(i=0; i<=nleft-1; i++) + { + cntbuf->ptr.p_int[c->ptr.p_int[i]] = cntbuf->ptr.p_int[c->ptr.p_int[i]]+1; + } + for(i=nleft; i<=n-1; i++) + { + cntbuf->ptr.p_int[nc+c->ptr.p_int[i]] = cntbuf->ptr.p_int[nc+c->ptr.p_int[i]]+1; + } + sl = nleft; + sr = n-nleft; + v = 0; + for(i=0; i<=nc-1; i++) + { + w = cntbuf->ptr.p_int[i]; + v = v+w*ae_sqr(w/sl-1, _state); + v = v+(sl-w)*ae_sqr(w/sl, _state); + w = cntbuf->ptr.p_int[nc+i]; + v = v+w*ae_sqr(w/sr-1, _state); + v = v+(sr-w)*ae_sqr(w/sr, _state); + } + cure = ae_sqrt(v/(nc*n), _state); + if( ae_fp_less(cure,*e) ) + { + *threshold = cursplit; + *e = cure; + } + } + } +} + + +/************************************************************************* +Makes split on attribute +*************************************************************************/ +static void dforest_dfsplitr(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t flags, + ae_int_t* info, + double* threshold, + double* e, + /* Real */ ae_vector* sortrbuf, + /* Real */ ae_vector* sortrbuf2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t neq; + ae_int_t nless; + ae_int_t ngreater; + ae_int_t q; + ae_int_t qmin; + ae_int_t qmax; + ae_int_t qcnt; + double cursplit; + ae_int_t nleft; + double v; + double cure; + + *info = 0; + *threshold = 0; + *e = 0; + + tagsortfastr(x, y, sortrbuf, sortrbuf2, n, _state); + *e = ae_maxrealnumber; + *threshold = 0.5*(x->ptr.p_double[0]+x->ptr.p_double[n-1]); + *info = -3; + if( flags/dforest_dfusestrongsplits%2==0 ) + { + + /* + * weak splits, split at half + */ + qcnt = 2; + qmin = 1; + qmax = 1; + } + else + { + + /* + * strong splits: choose best quartile + */ + qcnt = 4; + qmin = 1; + qmax = 3; + } + for(q=qmin; q<=qmax; q++) + { + cursplit = x->ptr.p_double[n*q/qcnt]; + neq = 0; + nless = 0; + ngreater = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_less(x->ptr.p_double[i],cursplit) ) + { + nless = nless+1; + } + if( ae_fp_eq(x->ptr.p_double[i],cursplit) ) + { + neq = neq+1; + } + if( ae_fp_greater(x->ptr.p_double[i],cursplit) ) + { + ngreater = ngreater+1; + } + } + ae_assert(neq!=0, "DFSplitR: NEq=0, something strange!!!", _state); + if( nless!=0||ngreater!=0 ) + { + + /* + * set threshold between two partitions, with + * some tweaking to avoid problems with floating point + * arithmetics. + * + * The problem is that when you calculates C = 0.5*(A+B) there + * can be no C which lies strictly between A and B (for example, + * there is no floating point number which is + * greater than 1 and less than 1+eps). In such situations + * we choose right side as theshold (remember that + * points which lie on threshold falls to the right side). + */ + if( nlessptr.p_double[nless+neq-1]+x->ptr.p_double[nless+neq]); + nleft = nless+neq; + if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless+neq-1]) ) + { + cursplit = x->ptr.p_double[nless+neq]; + } + } + else + { + cursplit = 0.5*(x->ptr.p_double[nless-1]+x->ptr.p_double[nless]); + nleft = nless; + if( ae_fp_less_eq(cursplit,x->ptr.p_double[nless-1]) ) + { + cursplit = x->ptr.p_double[nless]; + } + } + *info = 1; + cure = 0; + v = 0; + for(i=0; i<=nleft-1; i++) + { + v = v+y->ptr.p_double[i]; + } + v = v/nleft; + for(i=0; i<=nleft-1; i++) + { + cure = cure+ae_sqr(y->ptr.p_double[i]-v, _state); + } + v = 0; + for(i=nleft; i<=n-1; i++) + { + v = v+y->ptr.p_double[i]; + } + v = v/(n-nleft); + for(i=nleft; i<=n-1; i++) + { + cure = cure+ae_sqr(y->ptr.p_double[i]-v, _state); + } + cure = ae_sqrt(cure/n, _state); + if( ae_fp_less(cure,*e) ) + { + *threshold = cursplit; + *e = cure; + } + } + } +} + + +ae_bool _decisionforest_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + decisionforest *p = (decisionforest*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->trees, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _decisionforest_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + decisionforest *dst = (decisionforest*)_dst; + decisionforest *src = (decisionforest*)_src; + dst->nvars = src->nvars; + dst->nclasses = src->nclasses; + dst->ntrees = src->ntrees; + dst->bufsize = src->bufsize; + if( !ae_vector_init_copy(&dst->trees, &src->trees, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _decisionforest_clear(void* _p) +{ + decisionforest *p = (decisionforest*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->trees); +} + + +void _decisionforest_destroy(void* _p) +{ + decisionforest *p = (decisionforest*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->trees); +} + + +ae_bool _dfreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + dfreport *p = (dfreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _dfreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + dfreport *dst = (dfreport*)_dst; + dfreport *src = (dfreport*)_src; + dst->relclserror = src->relclserror; + dst->avgce = src->avgce; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->oobrelclserror = src->oobrelclserror; + dst->oobavgce = src->oobavgce; + dst->oobrmserror = src->oobrmserror; + dst->oobavgerror = src->oobavgerror; + dst->oobavgrelerror = src->oobavgrelerror; + return ae_true; +} + + +void _dfreport_clear(void* _p) +{ + dfreport *p = (dfreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _dfreport_destroy(void* _p) +{ + dfreport *p = (dfreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _dfinternalbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + dfinternalbuffers *p = (dfinternalbuffers*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->treebuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->idxbuf, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpbufr, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpbufr2, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpbufi, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->classibuf, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->sortrbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->sortrbuf2, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->sortibuf, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->varpool, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->evsbin, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->evssplits, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _dfinternalbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + dfinternalbuffers *dst = (dfinternalbuffers*)_dst; + dfinternalbuffers *src = (dfinternalbuffers*)_src; + if( !ae_vector_init_copy(&dst->treebuf, &src->treebuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->idxbuf, &src->idxbuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpbufr, &src->tmpbufr, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpbufr2, &src->tmpbufr2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpbufi, &src->tmpbufi, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->classibuf, &src->classibuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->sortrbuf, &src->sortrbuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->sortrbuf2, &src->sortrbuf2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->sortibuf, &src->sortibuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->varpool, &src->varpool, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->evsbin, &src->evsbin, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->evssplits, &src->evssplits, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _dfinternalbuffers_clear(void* _p) +{ + dfinternalbuffers *p = (dfinternalbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->treebuf); + ae_vector_clear(&p->idxbuf); + ae_vector_clear(&p->tmpbufr); + ae_vector_clear(&p->tmpbufr2); + ae_vector_clear(&p->tmpbufi); + ae_vector_clear(&p->classibuf); + ae_vector_clear(&p->sortrbuf); + ae_vector_clear(&p->sortrbuf2); + ae_vector_clear(&p->sortibuf); + ae_vector_clear(&p->varpool); + ae_vector_clear(&p->evsbin); + ae_vector_clear(&p->evssplits); +} + + +void _dfinternalbuffers_destroy(void* _p) +{ + dfinternalbuffers *p = (dfinternalbuffers*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->treebuf); + ae_vector_destroy(&p->idxbuf); + ae_vector_destroy(&p->tmpbufr); + ae_vector_destroy(&p->tmpbufr2); + ae_vector_destroy(&p->tmpbufi); + ae_vector_destroy(&p->classibuf); + ae_vector_destroy(&p->sortrbuf); + ae_vector_destroy(&p->sortrbuf2); + ae_vector_destroy(&p->sortibuf); + ae_vector_destroy(&p->varpool); + ae_vector_destroy(&p->evsbin); + ae_vector_destroy(&p->evssplits); +} + + + + +/************************************************************************* +Linear regression + +Subroutine builds model: + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N) + +and model found in ALGLIB format, covariation matrix, training set errors +(rms, average, average relative) and leave-one-out cross-validation +estimate of the generalization error. CV estimate calculated using fast +algorithm with O(NPoints*NVars) complexity. + +When covariation matrix is calculated standard deviations of function +values are assumed to be equal to RMS error on the training set. + +INPUT PARAMETERS: + XY - training set, array [0..NPoints-1,0..NVars]: + * NVars columns - independent variables + * last column - dependent variable + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPointsrmserror, _state)*npoints/(npoints-nvars-1); + for(i=0; i<=nvars; i++) + { + ae_v_muld(&ar->c.ptr.pp_double[i][0], 1, ae_v_len(0,nvars), sigma2); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Linear regression + +Variant of LRBuild which uses vector of standatd deviations (errors in +function values). + +INPUT PARAMETERS: + XY - training set, array [0..NPoints-1,0..NVars]: + * NVars columns - independent variables + * last column - dependent variable + S - standard deviations (errors in function values) + array[0..NPoints-1], S[i]>0. + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPointsptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + xyi.ptr.pp_double[i][nvars] = 1; + xyi.ptr.pp_double[i][nvars+1] = xy->ptr.pp_double[i][nvars]; + } + + /* + * Standartization + */ + ae_vector_set_length(&x, npoints-1+1, _state); + ae_vector_set_length(&means, nvars-1+1, _state); + ae_vector_set_length(&sigmas, nvars-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1)); + samplemoments(&x, npoints, &mean, &variance, &skewness, &kurtosis, _state); + means.ptr.p_double[j] = mean; + sigmas.ptr.p_double[j] = ae_sqrt(variance, _state); + if( ae_fp_eq(sigmas.ptr.p_double[j],0) ) + { + sigmas.ptr.p_double[j] = 1; + } + for(i=0; i<=npoints-1; i++) + { + xyi.ptr.pp_double[i][j] = (xyi.ptr.pp_double[i][j]-means.ptr.p_double[j])/sigmas.ptr.p_double[j]; + } + } + + /* + * Internal processing + */ + linreg_lrinternal(&xyi, s, npoints, nvars+1, info, lm, ar, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Un-standartization + */ + offs = ae_round(lm->w.ptr.p_double[3], _state); + for(j=0; j<=nvars-1; j++) + { + + /* + * Constant term is updated (and its covariance too, + * since it gets some variance from J-th component) + */ + lm->w.ptr.p_double[offs+nvars] = lm->w.ptr.p_double[offs+nvars]-lm->w.ptr.p_double[offs+j]*means.ptr.p_double[j]/sigmas.ptr.p_double[j]; + v = means.ptr.p_double[j]/sigmas.ptr.p_double[j]; + ae_v_subd(&ar->c.ptr.pp_double[nvars][0], 1, &ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v); + ae_v_subd(&ar->c.ptr.pp_double[0][nvars], ar->c.stride, &ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v); + + /* + * J-th term is updated + */ + lm->w.ptr.p_double[offs+j] = lm->w.ptr.p_double[offs+j]/sigmas.ptr.p_double[j]; + v = 1/sigmas.ptr.p_double[j]; + ae_v_muld(&ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v); + ae_v_muld(&ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Like LRBuildS, but builds model + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + +i.e. with zero constant term. + + -- ALGLIB -- + Copyright 30.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lrbuildzs(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix xyi; + ae_vector x; + ae_vector c; + ae_int_t i; + ae_int_t j; + double v; + ae_int_t offs; + double mean; + double variance; + double skewness; + double kurtosis; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _linearmodel_clear(lm); + _lrreport_clear(ar); + ae_matrix_init(&xyi, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_REAL, _state, ae_true); + + + /* + * Test parameters + */ + if( npoints<=nvars+1||nvars<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + + /* + * Copy data, add one more column (constant term) + */ + ae_matrix_set_length(&xyi, npoints-1+1, nvars+1+1, _state); + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&xyi.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + xyi.ptr.pp_double[i][nvars] = 0; + xyi.ptr.pp_double[i][nvars+1] = xy->ptr.pp_double[i][nvars]; + } + + /* + * Standartization: unusual scaling + */ + ae_vector_set_length(&x, npoints-1+1, _state); + ae_vector_set_length(&c, nvars-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[0][j], xy->stride, ae_v_len(0,npoints-1)); + samplemoments(&x, npoints, &mean, &variance, &skewness, &kurtosis, _state); + if( ae_fp_greater(ae_fabs(mean, _state),ae_sqrt(variance, _state)) ) + { + + /* + * variation is relatively small, it is better to + * bring mean value to 1 + */ + c.ptr.p_double[j] = mean; + } + else + { + + /* + * variation is large, it is better to bring variance to 1 + */ + if( ae_fp_eq(variance,0) ) + { + variance = 1; + } + c.ptr.p_double[j] = ae_sqrt(variance, _state); + } + for(i=0; i<=npoints-1; i++) + { + xyi.ptr.pp_double[i][j] = xyi.ptr.pp_double[i][j]/c.ptr.p_double[j]; + } + } + + /* + * Internal processing + */ + linreg_lrinternal(&xyi, s, npoints, nvars+1, info, lm, ar, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Un-standartization + */ + offs = ae_round(lm->w.ptr.p_double[3], _state); + for(j=0; j<=nvars-1; j++) + { + + /* + * J-th term is updated + */ + lm->w.ptr.p_double[offs+j] = lm->w.ptr.p_double[offs+j]/c.ptr.p_double[j]; + v = 1/c.ptr.p_double[j]; + ae_v_muld(&ar->c.ptr.pp_double[j][0], 1, ae_v_len(0,nvars), v); + ae_v_muld(&ar->c.ptr.pp_double[0][j], ar->c.stride, ae_v_len(0,nvars), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Like LRBuild but builds model + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + +i.e. with zero constant term. + + -- ALGLIB -- + Copyright 30.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lrbuildz(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector s; + ae_int_t i; + double sigma2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _linearmodel_clear(lm); + _lrreport_clear(ar); + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + + if( npoints<=nvars+1||nvars<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&s, npoints-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + s.ptr.p_double[i] = 1; + } + lrbuildzs(xy, &s, npoints, nvars, info, lm, ar, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + sigma2 = ae_sqr(ar->rmserror, _state)*npoints/(npoints-nvars-1); + for(i=0; i<=nvars; i++) + { + ae_v_muld(&ar->c.ptr.pp_double[i][0], 1, ae_v_len(0,nvars), sigma2); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacks coefficients of linear model. + +INPUT PARAMETERS: + LM - linear model in ALGLIB format + +OUTPUT PARAMETERS: + V - coefficients, array[0..NVars] + constant term (intercept) is stored in the V[NVars]. + NVars - number of independent variables (one less than number + of coefficients) + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +void lrunpack(linearmodel* lm, + /* Real */ ae_vector* v, + ae_int_t* nvars, + ae_state *_state) +{ + ae_int_t offs; + + ae_vector_clear(v); + *nvars = 0; + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state); + *nvars = ae_round(lm->w.ptr.p_double[2], _state); + offs = ae_round(lm->w.ptr.p_double[3], _state); + ae_vector_set_length(v, *nvars+1, _state); + ae_v_move(&v->ptr.p_double[0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,*nvars)); +} + + +/************************************************************************* +"Packs" coefficients and creates linear model in ALGLIB format (LRUnpack +reversed). + +INPUT PARAMETERS: + V - coefficients, array[0..NVars] + NVars - number of independent variables + +OUTPUT PAREMETERS: + LM - linear model. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +void lrpack(/* Real */ ae_vector* v, + ae_int_t nvars, + linearmodel* lm, + ae_state *_state) +{ + ae_int_t offs; + + _linearmodel_clear(lm); + + ae_vector_set_length(&lm->w, 4+nvars+1, _state); + offs = 4; + lm->w.ptr.p_double[0] = 4+nvars+1; + lm->w.ptr.p_double[1] = linreg_lrvnum; + lm->w.ptr.p_double[2] = nvars; + lm->w.ptr.p_double[3] = offs; + ae_v_move(&lm->w.ptr.p_double[offs], 1, &v->ptr.p_double[0], 1, ae_v_len(offs,offs+nvars)); +} + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + LM - linear model + X - input vector, array[0..NVars-1]. + +Result: + value of linear model regression estimate + + -- ALGLIB -- + Copyright 03.09.2008 by Bochkanov Sergey +*************************************************************************/ +double lrprocess(linearmodel* lm, + /* Real */ ae_vector* x, + ae_state *_state) +{ + double v; + ae_int_t offs; + ae_int_t nvars; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + offs = ae_round(lm->w.ptr.p_double[3], _state); + v = ae_v_dotproduct(&x->ptr.p_double[0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1)); + result = v+lm->w.ptr.p_double[offs+nvars]; + return result; +} + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lrrmserror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_int_t i; + double v; + ae_int_t offs; + ae_int_t nvars; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + offs = ae_round(lm->w.ptr.p_double[3], _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1)); + v = v+lm->w.ptr.p_double[offs+nvars]; + result = result+ae_sqr(v-xy->ptr.pp_double[i][nvars], _state); + } + result = ae_sqrt(result/npoints, _state); + return result; +} + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + average error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lravgerror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_int_t i; + double v; + ae_int_t offs; + ae_int_t nvars; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + offs = ae_round(lm->w.ptr.p_double[3], _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1)); + v = v+lm->w.ptr.p_double[offs+nvars]; + result = result+ae_fabs(v-xy->ptr.pp_double[i][nvars], _state); + } + result = result/npoints; + return result; +} + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - linear model + XY - test set + NPoints - test set size + +RESULT: + average relative error. + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double lravgrelerror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + double v; + ae_int_t offs; + ae_int_t nvars; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==linreg_lrvnum, "LINREG: Incorrect LINREG version!", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + offs = ae_round(lm->w.ptr.p_double[3], _state); + result = 0; + k = 0; + for(i=0; i<=npoints-1; i++) + { + if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) ) + { + v = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1)); + v = v+lm->w.ptr.p_double[offs+nvars]; + result = result+ae_fabs((v-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state); + k = k+1; + } + } + if( k!=0 ) + { + result = result/k; + } + return result; +} + + +/************************************************************************* +Copying of LinearModel strucure + +INPUT PARAMETERS: + LM1 - original + +OUTPUT PARAMETERS: + LM2 - copy + + -- ALGLIB -- + Copyright 15.03.2009 by Bochkanov Sergey +*************************************************************************/ +void lrcopy(linearmodel* lm1, linearmodel* lm2, ae_state *_state) +{ + ae_int_t k; + + _linearmodel_clear(lm2); + + k = ae_round(lm1->w.ptr.p_double[0], _state); + ae_vector_set_length(&lm2->w, k-1+1, _state); + ae_v_move(&lm2->w.ptr.p_double[0], 1, &lm1->w.ptr.p_double[0], 1, ae_v_len(0,k-1)); +} + + +void lrlines(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t n, + ae_int_t* info, + double* a, + double* b, + double* vara, + double* varb, + double* covab, + double* corrab, + double* p, + ae_state *_state) +{ + ae_int_t i; + double ss; + double sx; + double sxx; + double sy; + double stt; + double e1; + double e2; + double t; + double chi2; + + *info = 0; + *a = 0; + *b = 0; + *vara = 0; + *varb = 0; + *covab = 0; + *corrab = 0; + *p = 0; + + if( n<2 ) + { + *info = -1; + return; + } + for(i=0; i<=n-1; i++) + { + if( ae_fp_less_eq(s->ptr.p_double[i],0) ) + { + *info = -2; + return; + } + } + *info = 1; + + /* + * Calculate S, SX, SY, SXX + */ + ss = 0; + sx = 0; + sy = 0; + sxx = 0; + for(i=0; i<=n-1; i++) + { + t = ae_sqr(s->ptr.p_double[i], _state); + ss = ss+1/t; + sx = sx+xy->ptr.pp_double[i][0]/t; + sy = sy+xy->ptr.pp_double[i][1]/t; + sxx = sxx+ae_sqr(xy->ptr.pp_double[i][0], _state)/t; + } + + /* + * Test for condition number + */ + t = ae_sqrt(4*ae_sqr(sx, _state)+ae_sqr(ss-sxx, _state), _state); + e1 = 0.5*(ss+sxx+t); + e2 = 0.5*(ss+sxx-t); + if( ae_fp_less_eq(ae_minreal(e1, e2, _state),1000*ae_machineepsilon*ae_maxreal(e1, e2, _state)) ) + { + *info = -3; + return; + } + + /* + * Calculate A, B + */ + *a = 0; + *b = 0; + stt = 0; + for(i=0; i<=n-1; i++) + { + t = (xy->ptr.pp_double[i][0]-sx/ss)/s->ptr.p_double[i]; + *b = *b+t*xy->ptr.pp_double[i][1]/s->ptr.p_double[i]; + stt = stt+ae_sqr(t, _state); + } + *b = *b/stt; + *a = (sy-sx*(*b))/ss; + + /* + * Calculate goodness-of-fit + */ + if( n>2 ) + { + chi2 = 0; + for(i=0; i<=n-1; i++) + { + chi2 = chi2+ae_sqr((xy->ptr.pp_double[i][1]-(*a)-*b*xy->ptr.pp_double[i][0])/s->ptr.p_double[i], _state); + } + *p = incompletegammac((double)(n-2)/(double)2, chi2/2, _state); + } + else + { + *p = 1; + } + + /* + * Calculate other parameters + */ + *vara = (1+ae_sqr(sx, _state)/(ss*stt))/ss; + *varb = 1/stt; + *covab = -sx/(ss*stt); + *corrab = *covab/ae_sqrt(*vara*(*varb), _state); +} + + +void lrline(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t* info, + double* a, + double* b, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector s; + ae_int_t i; + double vara; + double varb; + double covab; + double corrab; + double p; + + ae_frame_make(_state, &_frame_block); + *info = 0; + *a = 0; + *b = 0; + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + + if( n<2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&s, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + s.ptr.p_double[i] = 1; + } + lrlines(xy, &s, n, info, a, b, &vara, &varb, &covab, &corrab, &p, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal linear regression subroutine +*************************************************************************/ +static void linreg_lrinternal(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix a; + ae_matrix u; + ae_matrix vt; + ae_matrix vm; + ae_matrix xym; + ae_vector b; + ae_vector sv; + ae_vector t; + ae_vector svi; + ae_vector work; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t ncv; + ae_int_t na; + ae_int_t nacv; + double r; + double p; + double epstol; + lrreport ar2; + ae_int_t offs; + linearmodel tlm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _linearmodel_clear(lm); + _lrreport_clear(ar); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xym, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sv, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&svi, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + _lrreport_init(&ar2, _state, ae_true); + _linearmodel_init(&tlm, _state, ae_true); + + epstol = 1000; + + /* + * Check for errors in data + */ + if( npointsptr.p_double[i],0) ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Create design matrix + */ + ae_matrix_set_length(&a, npoints-1+1, nvars-1+1, _state); + ae_vector_set_length(&b, npoints-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + r = 1/s->ptr.p_double[i]; + ae_v_moved(&a.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), r); + b.ptr.p_double[i] = xy->ptr.pp_double[i][nvars]/s->ptr.p_double[i]; + } + + /* + * Allocate W: + * W[0] array size + * W[1] version number, 0 + * W[2] NVars (minus 1, to be compatible with external representation) + * W[3] coefficients offset + */ + ae_vector_set_length(&lm->w, 4+nvars-1+1, _state); + offs = 4; + lm->w.ptr.p_double[0] = 4+nvars; + lm->w.ptr.p_double[1] = linreg_lrvnum; + lm->w.ptr.p_double[2] = nvars-1; + lm->w.ptr.p_double[3] = offs; + + /* + * Solve problem using SVD: + * + * 0. check for degeneracy (different types) + * 1. A = U*diag(sv)*V' + * 2. T = b'*U + * 3. w = SUM((T[i]/sv[i])*V[..,i]) + * 4. cov(wi,wj) = SUM(Vji*Vjk/sv[i]^2,K=1..M) + * + * see $15.4 of "Numerical Recipes in C" for more information + */ + ae_vector_set_length(&t, nvars-1+1, _state); + ae_vector_set_length(&svi, nvars-1+1, _state); + ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state); + ae_matrix_set_length(&vm, nvars-1+1, nvars-1+1, _state); + if( !rmatrixsvd(&a, npoints, nvars, 1, 1, 2, &sv, &u, &vt, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + if( ae_fp_less_eq(sv.ptr.p_double[0],0) ) + { + + /* + * Degenerate case: zero design matrix. + */ + for(i=offs; i<=offs+nvars-1; i++) + { + lm->w.ptr.p_double[i] = 0; + } + ar->rmserror = lrrmserror(lm, xy, npoints, _state); + ar->avgerror = lravgerror(lm, xy, npoints, _state); + ar->avgrelerror = lravgrelerror(lm, xy, npoints, _state); + ar->cvrmserror = ar->rmserror; + ar->cvavgerror = ar->avgerror; + ar->cvavgrelerror = ar->avgrelerror; + ar->ncvdefects = 0; + ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state); + ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state); + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + ar->c.ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + if( ae_fp_less_eq(sv.ptr.p_double[nvars-1],epstol*ae_machineepsilon*sv.ptr.p_double[0]) ) + { + + /* + * Degenerate case, non-zero design matrix. + * + * We can leave it and solve task in SVD least squares fashion. + * Solution and covariance matrix will be obtained correctly, + * but CV error estimates - will not. It is better to reduce + * it to non-degenerate task and to obtain correct CV estimates. + */ + for(k=nvars; k>=1; k--) + { + if( ae_fp_greater(sv.ptr.p_double[k-1],epstol*ae_machineepsilon*sv.ptr.p_double[0]) ) + { + + /* + * Reduce + */ + ae_matrix_set_length(&xym, npoints-1+1, k+1, _state); + for(i=0; i<=npoints-1; i++) + { + for(j=0; j<=k-1; j++) + { + r = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &vt.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + xym.ptr.pp_double[i][j] = r; + } + xym.ptr.pp_double[i][k] = xy->ptr.pp_double[i][nvars]; + } + + /* + * Solve + */ + linreg_lrinternal(&xym, s, npoints, k, info, &tlm, &ar2, _state); + if( *info!=1 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Convert back to un-reduced format + */ + for(j=0; j<=nvars-1; j++) + { + lm->w.ptr.p_double[offs+j] = 0; + } + for(j=0; j<=k-1; j++) + { + r = tlm.w.ptr.p_double[offs+j]; + ae_v_addd(&lm->w.ptr.p_double[offs], 1, &vt.ptr.pp_double[j][0], 1, ae_v_len(offs,offs+nvars-1), r); + } + ar->rmserror = ar2.rmserror; + ar->avgerror = ar2.avgerror; + ar->avgrelerror = ar2.avgrelerror; + ar->cvrmserror = ar2.cvrmserror; + ar->cvavgerror = ar2.cvavgerror; + ar->cvavgrelerror = ar2.cvavgrelerror; + ar->ncvdefects = ar2.ncvdefects; + ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state); + for(j=0; j<=ar->ncvdefects-1; j++) + { + ar->cvdefects.ptr.p_int[j] = ar2.cvdefects.ptr.p_int[j]; + } + ae_matrix_set_length(&ar->c, nvars-1+1, nvars-1+1, _state); + ae_vector_set_length(&work, nvars+1, _state); + matrixmatrixmultiply(&ar2.c, 0, k-1, 0, k-1, ae_false, &vt, 0, k-1, 0, nvars-1, ae_false, 1.0, &vm, 0, k-1, 0, nvars-1, 0.0, &work, _state); + matrixmatrixmultiply(&vt, 0, k-1, 0, nvars-1, ae_true, &vm, 0, k-1, 0, nvars-1, ae_false, 1.0, &ar->c, 0, nvars-1, 0, nvars-1, 0.0, &work, _state); + ae_frame_leave(_state); + return; + } + } + *info = -255; + ae_frame_leave(_state); + return; + } + for(i=0; i<=nvars-1; i++) + { + if( ae_fp_greater(sv.ptr.p_double[i],epstol*ae_machineepsilon*sv.ptr.p_double[0]) ) + { + svi.ptr.p_double[i] = 1/sv.ptr.p_double[i]; + } + else + { + svi.ptr.p_double[i] = 0; + } + } + for(i=0; i<=nvars-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=npoints-1; i++) + { + r = b.ptr.p_double[i]; + ae_v_addd(&t.ptr.p_double[0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), r); + } + for(i=0; i<=nvars-1; i++) + { + lm->w.ptr.p_double[offs+i] = 0; + } + for(i=0; i<=nvars-1; i++) + { + r = t.ptr.p_double[i]*svi.ptr.p_double[i]; + ae_v_addd(&lm->w.ptr.p_double[offs], 1, &vt.ptr.pp_double[i][0], 1, ae_v_len(offs,offs+nvars-1), r); + } + for(j=0; j<=nvars-1; j++) + { + r = svi.ptr.p_double[j]; + ae_v_moved(&vm.ptr.pp_double[0][j], vm.stride, &vt.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1), r); + } + for(i=0; i<=nvars-1; i++) + { + for(j=i; j<=nvars-1; j++) + { + r = ae_v_dotproduct(&vm.ptr.pp_double[i][0], 1, &vm.ptr.pp_double[j][0], 1, ae_v_len(0,nvars-1)); + ar->c.ptr.pp_double[i][j] = r; + ar->c.ptr.pp_double[j][i] = r; + } + } + + /* + * Leave-1-out cross-validation error. + * + * NOTATIONS: + * A design matrix + * A*x = b original linear least squares task + * U*S*V' SVD of A + * ai i-th row of the A + * bi i-th element of the b + * xf solution of the original LLS task + * + * Cross-validation error of i-th element from a sample is + * calculated using following formula: + * + * ERRi = ai*xf - (ai*xf-bi*(ui*ui'))/(1-ui*ui') (1) + * + * This formula can be derived from normal equations of the + * original task + * + * (A'*A)x = A'*b (2) + * + * by applying modification (zeroing out i-th row of A) to (2): + * + * (A-ai)'*(A-ai) = (A-ai)'*b + * + * and using Sherman-Morrison formula for updating matrix inverse + * + * NOTE 1: b is not zeroed out since it is much simpler and + * does not influence final result. + * + * NOTE 2: some design matrices A have such ui that 1-ui*ui'=0. + * Formula (1) can't be applied for such cases and they are skipped + * from CV calculation (which distorts resulting CV estimate). + * But from the properties of U we can conclude that there can + * be no more than NVars such vectors. Usually + * NVars << NPoints, so in a normal case it only slightly + * influences result. + */ + ncv = 0; + na = 0; + nacv = 0; + ar->rmserror = 0; + ar->avgerror = 0; + ar->avgrelerror = 0; + ar->cvrmserror = 0; + ar->cvavgerror = 0; + ar->cvavgrelerror = 0; + ar->ncvdefects = 0; + ae_vector_set_length(&ar->cvdefects, nvars-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + + /* + * Error on a training set + */ + r = ae_v_dotproduct(&xy->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs], 1, ae_v_len(0,nvars-1)); + ar->rmserror = ar->rmserror+ae_sqr(r-xy->ptr.pp_double[i][nvars], _state); + ar->avgerror = ar->avgerror+ae_fabs(r-xy->ptr.pp_double[i][nvars], _state); + if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) ) + { + ar->avgrelerror = ar->avgrelerror+ae_fabs((r-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state); + na = na+1; + } + + /* + * Error using fast leave-one-out cross-validation + */ + p = ae_v_dotproduct(&u.ptr.pp_double[i][0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + if( ae_fp_greater(p,1-epstol*ae_machineepsilon) ) + { + ar->cvdefects.ptr.p_int[ar->ncvdefects] = i; + ar->ncvdefects = ar->ncvdefects+1; + continue; + } + r = s->ptr.p_double[i]*(r/s->ptr.p_double[i]-b.ptr.p_double[i]*p)/(1-p); + ar->cvrmserror = ar->cvrmserror+ae_sqr(r-xy->ptr.pp_double[i][nvars], _state); + ar->cvavgerror = ar->cvavgerror+ae_fabs(r-xy->ptr.pp_double[i][nvars], _state); + if( ae_fp_neq(xy->ptr.pp_double[i][nvars],0) ) + { + ar->cvavgrelerror = ar->cvavgrelerror+ae_fabs((r-xy->ptr.pp_double[i][nvars])/xy->ptr.pp_double[i][nvars], _state); + nacv = nacv+1; + } + ncv = ncv+1; + } + if( ncv==0 ) + { + + /* + * Something strange: ALL ui are degenerate. + * Unexpected... + */ + *info = -255; + ae_frame_leave(_state); + return; + } + ar->rmserror = ae_sqrt(ar->rmserror/npoints, _state); + ar->avgerror = ar->avgerror/npoints; + if( na!=0 ) + { + ar->avgrelerror = ar->avgrelerror/na; + } + ar->cvrmserror = ae_sqrt(ar->cvrmserror/ncv, _state); + ar->cvavgerror = ar->cvavgerror/ncv; + if( nacv!=0 ) + { + ar->cvavgrelerror = ar->cvavgrelerror/nacv; + } + ae_frame_leave(_state); +} + + +ae_bool _linearmodel_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + linearmodel *p = (linearmodel*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->w, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _linearmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + linearmodel *dst = (linearmodel*)_dst; + linearmodel *src = (linearmodel*)_src; + if( !ae_vector_init_copy(&dst->w, &src->w, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _linearmodel_clear(void* _p) +{ + linearmodel *p = (linearmodel*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->w); +} + + +void _linearmodel_destroy(void* _p) +{ + linearmodel *p = (linearmodel*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->w); +} + + +ae_bool _lrreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + lrreport *p = (lrreport*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cvdefects, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _lrreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + lrreport *dst = (lrreport*)_dst; + lrreport *src = (lrreport*)_src; + if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) ) + return ae_false; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->cvrmserror = src->cvrmserror; + dst->cvavgerror = src->cvavgerror; + dst->cvavgrelerror = src->cvavgrelerror; + dst->ncvdefects = src->ncvdefects; + if( !ae_vector_init_copy(&dst->cvdefects, &src->cvdefects, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _lrreport_clear(void* _p) +{ + lrreport *p = (lrreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->c); + ae_vector_clear(&p->cvdefects); +} + + +void _lrreport_destroy(void* _p) +{ + lrreport *p = (lrreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->c); + ae_vector_destroy(&p->cvdefects); +} + + + + +/************************************************************************* +Filters: simple moving averages (unsymmetric). + +This filter replaces array by results of SMA(K) filter. SMA(K) is defined +as filter which averages at most K previous points (previous - not points +AROUND central point) - or less, in case of the first K-1 points. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filtersma(/* Real */ ae_vector* x, + ae_int_t n, + ae_int_t k, + ae_state *_state) +{ + ae_int_t i; + double runningsum; + double termsinsum; + ae_int_t zeroprefix; + double v; + + + ae_assert(n>=0, "FilterSMA: N<0", _state); + ae_assert(x->cnt>=n, "FilterSMA: Length(X)=1, "FilterSMA: K<1", _state); + + /* + * Quick exit, if necessary + */ + if( n<=1||k==1 ) + { + return; + } + + /* + * Prepare variables (see below for explanation) + */ + runningsum = 0.0; + termsinsum = 0; + for(i=ae_maxint(n-k, 0, _state); i<=n-1; i++) + { + runningsum = runningsum+x->ptr.p_double[i]; + termsinsum = termsinsum+1; + } + i = ae_maxint(n-k, 0, _state); + zeroprefix = 0; + while(i<=n-1&&ae_fp_eq(x->ptr.p_double[i],0)) + { + zeroprefix = zeroprefix+1; + i = i+1; + } + + /* + * General case: we assume that N>1 and K>1 + * + * Make one pass through all elements. At the beginning of + * the iteration we have: + * * I element being processed + * * RunningSum current value of the running sum + * (including I-th element) + * * TermsInSum number of terms in sum, 0<=TermsInSum<=K + * * ZeroPrefix length of the sequence of zero elements + * which starts at X[I-K+1] and continues towards X[I]. + * Equal to zero in case X[I-K+1] is non-zero. + * This value is used to make RunningSum exactly zero + * when it follows from the problem properties. + */ + for(i=n-1; i>=0; i--) + { + + /* + * Store new value of X[i], save old value in V + */ + v = x->ptr.p_double[i]; + x->ptr.p_double[i] = runningsum/termsinsum; + + /* + * Update RunningSum and TermsInSum + */ + if( i-k>=0 ) + { + runningsum = runningsum-v+x->ptr.p_double[i-k]; + } + else + { + runningsum = runningsum-v; + termsinsum = termsinsum-1; + } + + /* + * Update ZeroPrefix. + * In case we have ZeroPrefix=TermsInSum, + * RunningSum is reset to zero. + */ + if( i-k>=0 ) + { + if( ae_fp_neq(x->ptr.p_double[i-k],0) ) + { + zeroprefix = 0; + } + else + { + zeroprefix = ae_minint(zeroprefix+1, k, _state); + } + } + else + { + zeroprefix = ae_minint(zeroprefix, i+1, _state); + } + if( ae_fp_eq(zeroprefix,termsinsum) ) + { + runningsum = 0; + } + } +} + + +/************************************************************************* +Filters: exponential moving averages. + +This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is +defined as filter which replaces X[] by S[]: + S[0] = X[0] + S[t] = alpha*X[t] + (1-alpha)*S[t-1] + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + alpha - 0=0, "FilterEMA: N<0", _state); + ae_assert(x->cnt>=n, "FilterEMA: Length(X)1", _state); + + /* + * Quick exit, if necessary + */ + if( n<=1||ae_fp_eq(alpha,1) ) + { + return; + } + + /* + * Process + */ + for(i=1; i<=n-1; i++) + { + x->ptr.p_double[i] = alpha*x->ptr.p_double[i]+(1-alpha)*x->ptr.p_double[i-1]; + } +} + + +/************************************************************************* +Filters: linear regression moving averages. + +This filter replaces array by results of LRMA(K) filter. + +LRMA(K) is defined as filter which, for each data point, builds linear +regression model using K prevous points (point itself is included in +these K points) and calculates value of this linear model at the point in +question. + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filterlrma(/* Real */ ae_vector* x, + ae_int_t n, + ae_int_t k, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t m; + ae_matrix xy; + ae_vector s; + ae_int_t info; + double a; + double b; + double vara; + double varb; + double covab; + double corrab; + double p; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init(&xy, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "FilterLRMA: N<0", _state); + ae_assert(x->cnt>=n, "FilterLRMA: Length(X)=1, "FilterLRMA: K<1", _state); + + /* + * Quick exit, if necessary: + * * either N is equal to 1 (nothing to average) + * * or K is 1 (only point itself is used) or 2 (model is too simple, + * we will always get identity transformation) + */ + if( n<=1||k<=2 ) + { + ae_frame_leave(_state); + return; + } + + /* + * General case: K>2, N>1. + * We do not process points with I<2 because first two points (I=0 and I=1) will be + * left unmodified by LRMA filter in any case. + */ + ae_matrix_set_length(&xy, k, 2, _state); + ae_vector_set_length(&s, k, _state); + for(i=0; i<=k-1; i++) + { + xy.ptr.pp_double[i][0] = i; + s.ptr.p_double[i] = 1.0; + } + for(i=n-1; i>=2; i--) + { + m = ae_minint(i+1, k, _state); + ae_v_move(&xy.ptr.pp_double[0][1], xy.stride, &x->ptr.p_double[i-m+1], 1, ae_v_len(0,m-1)); + lrlines(&xy, &s, m, &info, &a, &b, &vara, &varb, &covab, &corrab, &p, _state); + ae_assert(info==1, "FilterLRMA: internal error", _state); + x->ptr.p_double[i] = a+b*(m-1); + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Multiclass Fisher LDA + +Subroutine finds coefficients of linear combination which optimally separates +training set on classes. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - linear combination coefficients, array[0..NVars-1] + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherlda(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t* info, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix w2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(w); + ae_matrix_init(&w2, 0, 0, DT_REAL, _state, ae_true); + + fisherldan(xy, npoints, nvars, nclasses, info, &w2, _state); + if( *info>0 ) + { + ae_vector_set_length(w, nvars-1+1, _state); + ae_v_move(&w->ptr.p_double[0], 1, &w2.ptr.pp_double[0][0], w2.stride, ae_v_len(0,nvars-1)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +N-dimensional multiclass Fisher LDA + +Subroutine finds coefficients of linear combinations which optimally separates +training set on classes. It returns N-dimensional basis whose vector are sorted +by quality of training set separation (in descending order). + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - basis, array[0..NVars-1,0..NVars-1] + columns of matrix stores basis vectors, sorted by + quality of training set separation (in descending order) + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherldan(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t* info, + /* Real */ ae_matrix* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t m; + double v; + ae_vector c; + ae_vector mu; + ae_matrix muc; + ae_vector nc; + ae_matrix sw; + ae_matrix st; + ae_matrix z; + ae_matrix z2; + ae_matrix tm; + ae_matrix sbroot; + ae_matrix a; + ae_matrix xyproj; + ae_matrix wproj; + ae_vector tf; + ae_vector d; + ae_vector d2; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_matrix_clear(w); + ae_vector_init(&c, 0, DT_INT, _state, ae_true); + ae_vector_init(&mu, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&muc, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&nc, 0, DT_INT, _state, ae_true); + ae_matrix_init(&sw, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&st, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z2, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&sbroot, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xyproj, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&wproj, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + + /* + * Test data + */ + if( (npoints<0||nvars<1)||nclasses<2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Special case: NPoints<=1 + * Degenerate task. + */ + if( npoints<=1 ) + { + *info = 2; + ae_matrix_set_length(w, nvars-1+1, nvars-1+1, _state); + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + if( i==j ) + { + w->ptr.pp_double[i][j] = 1; + } + else + { + w->ptr.pp_double[i][j] = 0; + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * Prepare temporaries + */ + ae_vector_set_length(&tf, nvars-1+1, _state); + ae_vector_set_length(&work, ae_maxint(nvars, npoints, _state)+1, _state); + + /* + * Convert class labels from reals to integers (just for convenience) + */ + ae_vector_set_length(&c, npoints-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + c.ptr.p_int[i] = ae_round(xy->ptr.pp_double[i][nvars], _state); + } + + /* + * Calculate class sizes and means + */ + ae_vector_set_length(&mu, nvars-1+1, _state); + ae_matrix_set_length(&muc, nclasses-1+1, nvars-1+1, _state); + ae_vector_set_length(&nc, nclasses-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + mu.ptr.p_double[j] = 0; + } + for(i=0; i<=nclasses-1; i++) + { + nc.ptr.p_int[i] = 0; + for(j=0; j<=nvars-1; j++) + { + muc.ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=npoints-1; i++) + { + ae_v_add(&mu.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_add(&muc.ptr.pp_double[c.ptr.p_int[i]][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + nc.ptr.p_int[c.ptr.p_int[i]] = nc.ptr.p_int[c.ptr.p_int[i]]+1; + } + for(i=0; i<=nclasses-1; i++) + { + v = (double)1/(double)nc.ptr.p_int[i]; + ae_v_muld(&muc.ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1), v); + } + v = (double)1/(double)npoints; + ae_v_muld(&mu.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v); + + /* + * Create ST matrix + */ + ae_matrix_set_length(&st, nvars-1+1, nvars-1+1, _state); + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + st.ptr.pp_double[i][j] = 0; + } + } + for(k=0; k<=npoints-1; k++) + { + ae_v_move(&tf.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tf.ptr.p_double[0], 1, &mu.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + for(i=0; i<=nvars-1; i++) + { + v = tf.ptr.p_double[i]; + ae_v_addd(&st.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v); + } + } + + /* + * Create SW matrix + */ + ae_matrix_set_length(&sw, nvars-1+1, nvars-1+1, _state); + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + sw.ptr.pp_double[i][j] = 0; + } + } + for(k=0; k<=npoints-1; k++) + { + ae_v_move(&tf.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&tf.ptr.p_double[0], 1, &muc.ptr.pp_double[c.ptr.p_int[k]][0], 1, ae_v_len(0,nvars-1)); + for(i=0; i<=nvars-1; i++) + { + v = tf.ptr.p_double[i]; + ae_v_addd(&sw.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1), v); + } + } + + /* + * Maximize ratio J=(w'*ST*w)/(w'*SW*w). + * + * First, make transition from w to v such that w'*ST*w becomes v'*v: + * v = root(ST)*w = R*w + * R = root(D)*Z' + * w = (root(ST)^-1)*v = RI*v + * RI = Z*inv(root(D)) + * J = (v'*v)/(v'*(RI'*SW*RI)*v) + * ST = Z*D*Z' + * + * so we have + * + * J = (v'*v) / (v'*(inv(root(D))*Z'*SW*Z*inv(root(D)))*v) = + * = (v'*v) / (v'*A*v) + */ + if( !smatrixevd(&st, nvars, 1, ae_true, &d, &z, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(w, nvars-1+1, nvars-1+1, _state); + if( ae_fp_less_eq(d.ptr.p_double[nvars-1],0)||ae_fp_less_eq(d.ptr.p_double[0],1000*ae_machineepsilon*d.ptr.p_double[nvars-1]) ) + { + + /* + * Special case: D[NVars-1]<=0 + * Degenerate task (all variables takes the same value). + */ + if( ae_fp_less_eq(d.ptr.p_double[nvars-1],0) ) + { + *info = 2; + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + if( i==j ) + { + w->ptr.pp_double[i][j] = 1; + } + else + { + w->ptr.pp_double[i][j] = 0; + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * Special case: degenerate ST matrix, multicollinearity found. + * Since we know ST eigenvalues/vectors we can translate task to + * non-degenerate form. + * + * Let WG is orthogonal basis of the non zero variance subspace + * of the ST and let WZ is orthogonal basis of the zero variance + * subspace. + * + * Projection on WG allows us to use LDA on reduced M-dimensional + * subspace, N-M vectors of WZ allows us to update reduced LDA + * factors to full N-dimensional subspace. + */ + m = 0; + for(k=0; k<=nvars-1; k++) + { + if( ae_fp_less_eq(d.ptr.p_double[k],1000*ae_machineepsilon*d.ptr.p_double[nvars-1]) ) + { + m = k+1; + } + } + ae_assert(m!=0, "FisherLDAN: internal error #1", _state); + ae_matrix_set_length(&xyproj, npoints-1+1, nvars-m+1, _state); + matrixmatrixmultiply(xy, 0, npoints-1, 0, nvars-1, ae_false, &z, 0, nvars-1, m, nvars-1, ae_false, 1.0, &xyproj, 0, npoints-1, 0, nvars-m-1, 0.0, &work, _state); + for(i=0; i<=npoints-1; i++) + { + xyproj.ptr.pp_double[i][nvars-m] = xy->ptr.pp_double[i][nvars]; + } + fisherldan(&xyproj, npoints, nvars-m, nclasses, info, &wproj, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + matrixmatrixmultiply(&z, 0, nvars-1, m, nvars-1, ae_false, &wproj, 0, nvars-m-1, 0, nvars-m-1, ae_false, 1.0, w, 0, nvars-1, 0, nvars-m-1, 0.0, &work, _state); + for(k=nvars-m; k<=nvars-1; k++) + { + ae_v_move(&w->ptr.pp_double[0][k], w->stride, &z.ptr.pp_double[0][k-(nvars-m)], z.stride, ae_v_len(0,nvars-1)); + } + *info = 2; + } + else + { + + /* + * General case: no multicollinearity + */ + ae_matrix_set_length(&tm, nvars-1+1, nvars-1+1, _state); + ae_matrix_set_length(&a, nvars-1+1, nvars-1+1, _state); + matrixmatrixmultiply(&sw, 0, nvars-1, 0, nvars-1, ae_false, &z, 0, nvars-1, 0, nvars-1, ae_false, 1.0, &tm, 0, nvars-1, 0, nvars-1, 0.0, &work, _state); + matrixmatrixmultiply(&z, 0, nvars-1, 0, nvars-1, ae_true, &tm, 0, nvars-1, 0, nvars-1, ae_false, 1.0, &a, 0, nvars-1, 0, nvars-1, 0.0, &work, _state); + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + a.ptr.pp_double[i][j] = a.ptr.pp_double[i][j]/ae_sqrt(d.ptr.p_double[i]*d.ptr.p_double[j], _state); + } + } + if( !smatrixevd(&a, nvars, 1, ae_true, &d2, &z2, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + for(k=0; k<=nvars-1; k++) + { + for(i=0; i<=nvars-1; i++) + { + tf.ptr.p_double[i] = z2.ptr.pp_double[i][k]/ae_sqrt(d.ptr.p_double[i], _state); + } + for(i=0; i<=nvars-1; i++) + { + v = ae_v_dotproduct(&z.ptr.pp_double[i][0], 1, &tf.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + w->ptr.pp_double[i][k] = v; + } + } + } + + /* + * Post-processing: + * * normalization + * * converting to non-negative form, if possible + */ + for(k=0; k<=nvars-1; k++) + { + v = ae_v_dotproduct(&w->ptr.pp_double[0][k], w->stride, &w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1)); + v = 1/ae_sqrt(v, _state); + ae_v_muld(&w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1), v); + v = 0; + for(i=0; i<=nvars-1; i++) + { + v = v+w->ptr.pp_double[i][k]; + } + if( ae_fp_less(v,0) ) + { + ae_v_muld(&w->ptr.pp_double[0][k], w->stride, ae_v_len(0,nvars-1), -1); + } + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +This function returns number of weights updates which is required for +gradient calculation problem to be splitted. +*************************************************************************/ +ae_int_t mlpgradsplitcost(ae_state *_state) +{ + ae_int_t result; + + + result = mlpbase_gradbasecasecost; + return result; +} + + +/************************************************************************* +This function returns number of elements in subset of dataset which is +required for gradient calculation problem to be splitted. +*************************************************************************/ +ae_int_t mlpgradsplitsize(ae_state *_state) +{ + ae_int_t result; + + + result = mlpbase_microbatchsize; + return result; +} + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers, with linear output layer. Network weights are filled with small +random values. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate0(ae_int_t nin, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(-5, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreate0, but with one hidden layer (NHid neurons) with +non-linear activation function. Output layer is linear. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(-5, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreate0, but with two hidden layers (NHid1 and NHid2 neurons) +with non-linear activation function. Output layer is linear. + $ALL + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(-5, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. + +Activation function of the output layer takes values: + + (B, +INF), if D>=0 + +or + + (-INF, B), if D<0. + + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb0(ae_int_t nin, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3; + if( ae_fp_greater_eq(d,0) ) + { + d = 1; + } + else + { + d = -1; + } + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(3, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_false, _state); + + /* + * Turn on ouputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = b; + network->columnsigmas.ptr.p_double[i] = d; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateB0 but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3; + if( ae_fp_greater_eq(d,0) ) + { + d = 1; + } + else + { + d = -1; + } + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(3, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_false, _state); + + /* + * Turn on ouputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = b; + network->columnsigmas.ptr.p_double[i] = d; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateB0 but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3+3; + if( ae_fp_greater_eq(d,0) ) + { + d = 1; + } + else + { + d = -1; + } + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(3, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_false, _state); + + /* + * Turn on ouputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = b; + network->columnsigmas.ptr.p_double[i] = d; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. Activation function of the output layer takes values [A,B]. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater0(ae_int_t nin, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_false, ae_false, _state); + + /* + * Turn on outputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = 0.5*(a+b); + network->columnsigmas.ptr.p_double[i] = 0.5*(a-b); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateR0, but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_false, ae_false, _state); + + /* + * Turn on outputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = 0.5*(a+b); + network->columnsigmas.ptr.p_double[i] = 0.5*(a-b); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateR0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + layerscount = 1+3+3+3; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_false, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_false, ae_false, _state); + + /* + * Turn on outputs shift/scaling. + */ + for(i=nin; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = 0.5*(a+b); + network->columnsigmas.ptr.p_double[i] = 0.5*(a-b); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Creates classifier network with NIn inputs and NOut possible classes. +Network contains no hidden layers and linear output layer with SOFTMAX- +normalization (so outputs sums up to 1.0 and converge to posterior +probabilities). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec0(ae_int_t nin, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + ae_assert(nout>=2, "MLPCreateC0: NOut<2!", _state); + layerscount = 1+2+1; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addzerolayer(&lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state); + mlpbase_fillhighlevelinformation(network, nin, 0, 0, nout, ae_true, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateC0, but with one non-linear hidden layer. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + ae_assert(nout>=2, "MLPCreateC1: NOut<2!", _state); + layerscount = 1+3+2+1; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addzerolayer(&lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid, 0, nout, ae_true, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Same as MLPCreateC0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector lsizes; + ae_vector ltypes; + ae_vector lconnfirst; + ae_vector lconnlast; + ae_int_t layerscount; + ae_int_t lastproc; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&lsizes, 0, DT_INT, _state, ae_true); + ae_vector_init(<ypes, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lconnlast, 0, DT_INT, _state, ae_true); + + ae_assert(nout>=2, "MLPCreateC2: NOut<2!", _state); + layerscount = 1+3+3+2+1; + + /* + * Allocate arrays + */ + ae_vector_set_length(&lsizes, layerscount-1+1, _state); + ae_vector_set_length(<ypes, layerscount-1+1, _state); + ae_vector_set_length(&lconnfirst, layerscount-1+1, _state); + ae_vector_set_length(&lconnlast, layerscount-1+1, _state); + + /* + * Layers + */ + mlpbase_addinputlayer(nin, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nhid2, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addactivationlayer(1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addbiasedsummatorlayer(nout-1, &lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + mlpbase_addzerolayer(&lsizes, <ypes, &lconnfirst, &lconnlast, &lastproc, _state); + + /* + * Create + */ + mlpbase_mlpcreate(nin, nout, &lsizes, <ypes, &lconnfirst, &lconnlast, layerscount, ae_true, network, _state); + mlpbase_fillhighlevelinformation(network, nin, nhid1, nhid2, nout, ae_true, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Copying of neural network + +INPUT PARAMETERS: + Network1 - original + +OUTPUT PARAMETERS: + Network2 - copy + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcopy(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state) +{ + + _multilayerperceptron_clear(network2); + + mlpcopyshared(network1, network2, _state); +} + + +/************************************************************************* +Copying of neural network (second parameter is passed as shared object). + +INPUT PARAMETERS: + Network1 - original + +OUTPUT PARAMETERS: + Network2 - copy + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcopyshared(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t wcount; + ae_int_t i; + mlpbuffers buf; + smlpgrad sgrad; + + ae_frame_make(_state, &_frame_block); + _mlpbuffers_init(&buf, _state, ae_true); + _smlpgrad_init(&sgrad, _state, ae_true); + + + /* + * Copy scalar and array fields + */ + network2->hlnetworktype = network1->hlnetworktype; + network2->hlnormtype = network1->hlnormtype; + copyintegerarray(&network1->hllayersizes, &network2->hllayersizes, _state); + copyintegerarray(&network1->hlconnections, &network2->hlconnections, _state); + copyintegerarray(&network1->hlneurons, &network2->hlneurons, _state); + copyintegerarray(&network1->structinfo, &network2->structinfo, _state); + copyrealarray(&network1->weights, &network2->weights, _state); + copyrealarray(&network1->columnmeans, &network2->columnmeans, _state); + copyrealarray(&network1->columnsigmas, &network2->columnsigmas, _state); + copyrealarray(&network1->neurons, &network2->neurons, _state); + copyrealarray(&network1->dfdnet, &network2->dfdnet, _state); + copyrealarray(&network1->derror, &network2->derror, _state); + copyrealarray(&network1->x, &network2->x, _state); + copyrealarray(&network1->y, &network2->y, _state); + copyrealarray(&network1->nwbuf, &network2->nwbuf, _state); + copyintegerarray(&network1->integerbuf, &network2->integerbuf, _state); + + /* + * copy buffers + */ + wcount = mlpgetweightscount(network1, _state); + ae_shared_pool_set_seed(&network2->buf, &buf, sizeof(buf), _mlpbuffers_init, _mlpbuffers_init_copy, _mlpbuffers_destroy, _state); + ae_vector_set_length(&sgrad.g, wcount, _state); + sgrad.f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad.g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_set_seed(&network2->gradbuf, &sgrad, sizeof(sgrad), _smlpgrad_init, _smlpgrad_init_copy, _smlpgrad_destroy, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This function compares architectures of neural networks. Only geometries +are compared, weights and other parameters are not tested. + + -- ALGLIB -- + Copyright 20.06.2013 by Bochkanov Sergey +*************************************************************************/ +ae_bool mlpsamearchitecture(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ninfo; + ae_bool result; + + + ae_assert(network1->structinfo.cnt>0&&network1->structinfo.cnt>=network1->structinfo.ptr.p_int[0], "MLPSameArchitecture: Network1 is uninitialized", _state); + ae_assert(network2->structinfo.cnt>0&&network2->structinfo.cnt>=network2->structinfo.ptr.p_int[0], "MLPSameArchitecture: Network2 is uninitialized", _state); + result = ae_false; + if( network1->structinfo.ptr.p_int[0]!=network2->structinfo.ptr.p_int[0] ) + { + return result; + } + ninfo = network1->structinfo.ptr.p_int[0]; + for(i=0; i<=ninfo-1; i++) + { + if( network1->structinfo.ptr.p_int[i]!=network2->structinfo.ptr.p_int[i] ) + { + return result; + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function copies tunable parameters (weights/means/sigmas) from one +network to another with same architecture. It performs some rudimentary +checks that architectures are same, and throws exception if check fails. + +It is intended for fast copying of states between two network which are +known to have same geometry. + +INPUT PARAMETERS: + Network1 - source, must be correctly initialized + Network2 - target, must have same architecture + +OUTPUT PARAMETERS: + Network2 - network state is copied from source to target + + -- ALGLIB -- + Copyright 20.06.2013 by Bochkanov Sergey +*************************************************************************/ +void mlpcopytunableparameters(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ninfo; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + + + ae_assert(network1->structinfo.cnt>0&&network1->structinfo.cnt>=network1->structinfo.ptr.p_int[0], "MLPCopyTunableParameters: Network1 is uninitialized", _state); + ae_assert(network2->structinfo.cnt>0&&network2->structinfo.cnt>=network2->structinfo.ptr.p_int[0], "MLPCopyTunableParameters: Network2 is uninitialized", _state); + ae_assert(network1->structinfo.ptr.p_int[0]==network2->structinfo.ptr.p_int[0], "MLPCopyTunableParameters: Network1 geometry differs from that of Network2", _state); + ninfo = network1->structinfo.ptr.p_int[0]; + for(i=0; i<=ninfo-1; i++) + { + ae_assert(network1->structinfo.ptr.p_int[i]==network2->structinfo.ptr.p_int[i], "MLPCopyTunableParameters: Network1 geometry differs from that of Network2", _state); + } + mlpproperties(network1, &nin, &nout, &wcount, _state); + for(i=0; i<=wcount-1; i++) + { + network2->weights.ptr.p_double[i] = network1->weights.ptr.p_double[i]; + } + if( mlpissoftmax(network1, _state) ) + { + for(i=0; i<=nin-1; i++) + { + network2->columnmeans.ptr.p_double[i] = network1->columnmeans.ptr.p_double[i]; + network2->columnsigmas.ptr.p_double[i] = network1->columnsigmas.ptr.p_double[i]; + } + } + else + { + for(i=0; i<=nin+nout-1; i++) + { + network2->columnmeans.ptr.p_double[i] = network1->columnmeans.ptr.p_double[i]; + network2->columnsigmas.ptr.p_double[i] = network1->columnsigmas.ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This function exports tunable parameters (weights/means/sigmas) from +network to contiguous array. Nothing is guaranteed about array format, the +only thing you can count for is that MLPImportTunableParameters() will be +able to parse it. + +It is intended for fast copying of states between network and backup array + +INPUT PARAMETERS: + Network - source, must be correctly initialized + P - array to use. If its size is enough to store data, it + is reused. + +OUTPUT PARAMETERS: + P - array which stores network parameters, resized if needed + PCount - number of parameters stored in array. + + -- ALGLIB -- + Copyright 20.06.2013 by Bochkanov Sergey +*************************************************************************/ +void mlpexporttunableparameters(multilayerperceptron* network, + /* Real */ ae_vector* p, + ae_int_t* pcount, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + + *pcount = 0; + + ae_assert(network->structinfo.cnt>0&&network->structinfo.cnt>=network->structinfo.ptr.p_int[0], "MLPExportTunableParameters: Network is uninitialized", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + if( mlpissoftmax(network, _state) ) + { + *pcount = wcount+2*nin; + rvectorsetlengthatleast(p, *pcount, _state); + k = 0; + for(i=0; i<=wcount-1; i++) + { + p->ptr.p_double[k] = network->weights.ptr.p_double[i]; + k = k+1; + } + for(i=0; i<=nin-1; i++) + { + p->ptr.p_double[k] = network->columnmeans.ptr.p_double[i]; + k = k+1; + p->ptr.p_double[k] = network->columnsigmas.ptr.p_double[i]; + k = k+1; + } + } + else + { + *pcount = wcount+2*(nin+nout); + rvectorsetlengthatleast(p, *pcount, _state); + k = 0; + for(i=0; i<=wcount-1; i++) + { + p->ptr.p_double[k] = network->weights.ptr.p_double[i]; + k = k+1; + } + for(i=0; i<=nin+nout-1; i++) + { + p->ptr.p_double[k] = network->columnmeans.ptr.p_double[i]; + k = k+1; + p->ptr.p_double[k] = network->columnsigmas.ptr.p_double[i]; + k = k+1; + } + } +} + + +/************************************************************************* +This function imports tunable parameters (weights/means/sigmas) which +were exported by MLPExportTunableParameters(). + +It is intended for fast copying of states between network and backup array + +INPUT PARAMETERS: + Network - target: + * must be correctly initialized + * must have same geometry as network used to export params + P - array with parameters + + -- ALGLIB -- + Copyright 20.06.2013 by Bochkanov Sergey +*************************************************************************/ +void mlpimporttunableparameters(multilayerperceptron* network, + /* Real */ ae_vector* p, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + + + ae_assert(network->structinfo.cnt>0&&network->structinfo.cnt>=network->structinfo.ptr.p_int[0], "MLPImportTunableParameters: Network is uninitialized", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + if( mlpissoftmax(network, _state) ) + { + k = 0; + for(i=0; i<=wcount-1; i++) + { + network->weights.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + } + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + network->columnsigmas.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + } + } + else + { + k = 0; + for(i=0; i<=wcount-1; i++) + { + network->weights.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + } + for(i=0; i<=nin+nout-1; i++) + { + network->columnmeans.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + network->columnsigmas.ptr.p_double[i] = p->ptr.p_double[k]; + k = k+1; + } + } +} + + +/************************************************************************* +Serialization of MultiLayerPerceptron strucure + +INPUT PARAMETERS: + Network - original + +OUTPUT PARAMETERS: + RA - array of real numbers which stores network, + array[0..RLen-1] + RLen - RA lenght + + -- ALGLIB -- + Copyright 29.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpserializeold(multilayerperceptron* network, + /* Real */ ae_vector* ra, + ae_int_t* rlen, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ssize; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t sigmalen; + ae_int_t offs; + + ae_vector_clear(ra); + *rlen = 0; + + + /* + * Unload info + */ + ssize = network->structinfo.ptr.p_int[0]; + nin = network->structinfo.ptr.p_int[1]; + nout = network->structinfo.ptr.p_int[2]; + wcount = network->structinfo.ptr.p_int[4]; + if( mlpissoftmax(network, _state) ) + { + sigmalen = nin; + } + else + { + sigmalen = nin+nout; + } + + /* + * RA format: + * LEN DESRC. + * 1 RLen + * 1 version (MLPVNum) + * 1 StructInfo size + * SSize StructInfo + * WCount Weights + * SigmaLen ColumnMeans + * SigmaLen ColumnSigmas + */ + *rlen = 3+ssize+wcount+2*sigmalen; + ae_vector_set_length(ra, *rlen-1+1, _state); + ra->ptr.p_double[0] = *rlen; + ra->ptr.p_double[1] = mlpbase_mlpvnum; + ra->ptr.p_double[2] = ssize; + offs = 3; + for(i=0; i<=ssize-1; i++) + { + ra->ptr.p_double[offs+i] = network->structinfo.ptr.p_int[i]; + } + offs = offs+ssize; + ae_v_move(&ra->ptr.p_double[offs], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(offs,offs+wcount-1)); + offs = offs+wcount; + ae_v_move(&ra->ptr.p_double[offs], 1, &network->columnmeans.ptr.p_double[0], 1, ae_v_len(offs,offs+sigmalen-1)); + offs = offs+sigmalen; + ae_v_move(&ra->ptr.p_double[offs], 1, &network->columnsigmas.ptr.p_double[0], 1, ae_v_len(offs,offs+sigmalen-1)); + offs = offs+sigmalen; +} + + +/************************************************************************* +Unserialization of MultiLayerPerceptron strucure + +INPUT PARAMETERS: + RA - real array which stores network + +OUTPUT PARAMETERS: + Network - restored network + + -- ALGLIB -- + Copyright 29.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpunserializeold(/* Real */ ae_vector* ra, + multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ssize; + ae_int_t ntotal; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t sigmalen; + ae_int_t offs; + + _multilayerperceptron_clear(network); + + ae_assert(ae_round(ra->ptr.p_double[1], _state)==mlpbase_mlpvnum, "MLPUnserialize: incorrect array!", _state); + + /* + * Unload StructInfo from IA + */ + offs = 3; + ssize = ae_round(ra->ptr.p_double[2], _state); + ae_vector_set_length(&network->structinfo, ssize-1+1, _state); + for(i=0; i<=ssize-1; i++) + { + network->structinfo.ptr.p_int[i] = ae_round(ra->ptr.p_double[offs+i], _state); + } + offs = offs+ssize; + + /* + * Unload info from StructInfo + */ + ssize = network->structinfo.ptr.p_int[0]; + nin = network->structinfo.ptr.p_int[1]; + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + wcount = network->structinfo.ptr.p_int[4]; + if( network->structinfo.ptr.p_int[6]==0 ) + { + sigmalen = nin+nout; + } + else + { + sigmalen = nin; + } + + /* + * Allocate space for other fields + */ + ae_vector_set_length(&network->weights, wcount-1+1, _state); + ae_vector_set_length(&network->columnmeans, sigmalen-1+1, _state); + ae_vector_set_length(&network->columnsigmas, sigmalen-1+1, _state); + ae_vector_set_length(&network->neurons, ntotal-1+1, _state); + ae_vector_set_length(&network->nwbuf, ae_maxint(wcount, 2*nout, _state)-1+1, _state); + ae_vector_set_length(&network->dfdnet, ntotal-1+1, _state); + ae_vector_set_length(&network->x, nin-1+1, _state); + ae_vector_set_length(&network->y, nout-1+1, _state); + ae_vector_set_length(&network->derror, ntotal-1+1, _state); + + /* + * Copy parameters from RA + */ + ae_v_move(&network->weights.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,wcount-1)); + offs = offs+wcount; + ae_v_move(&network->columnmeans.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,sigmalen-1)); + offs = offs+sigmalen; + ae_v_move(&network->columnsigmas.ptr.p_double[0], 1, &ra->ptr.p_double[offs], 1, ae_v_len(0,sigmalen-1)); + offs = offs+sigmalen; +} + + +/************************************************************************* +Randomization of neural network weights + + -- ALGLIB -- + Copyright 06.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlprandomize(multilayerperceptron* network, ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + hqrndstate r; + ae_int_t entrysize; + ae_int_t entryoffs; + ae_int_t neuronidx; + ae_int_t neurontype; + double vmean; + double vvar; + ae_int_t i; + ae_int_t n1; + ae_int_t n2; + double desiredsigma; + ae_int_t montecarlocnt; + double ef; + double ef2; + double v; + double wscale; + + ae_frame_make(_state, &_frame_block); + _hqrndstate_init(&r, _state, ae_true); + + hqrndrandomize(&r, _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + desiredsigma = 0.5; + montecarlocnt = 20; + + /* + * Stage 1: + * * Network.Weights is filled by standard deviation of weights + * * default values: sigma=1 + */ + for(i=0; i<=wcount-1; i++) + { + network->weights.ptr.p_double[i] = 1.0; + } + + /* + * Stage 2: + * * assume that input neurons have zero mean and unit standard deviation + * * assume that constant neurons have zero standard deviation + * * perform forward pass along neurons + * * for each non-input non-constant neuron: + * * calculate mean and standard deviation of neuron's output + * assuming that we know means/deviations of neurons which feed it + * and assuming that weights has unit variance and zero mean. + * * for each nonlinear neuron additionally we perform backward pass: + * * scale variances of weights which feed it in such way that neuron's + * input has unit standard deviation + * + * NOTE: this algorithm assumes that each connection feeds at most one + * non-linear neuron. This assumption can be incorrect in upcoming + * architectures with strong neurons. However, algorithm should + * work smoothly even in this case. + * + * During this stage we use Network.RndBuf, which is grouped into NTotal + * entries, each of them having following format: + * + * Buf[Offset+0] mean value of neuron's output + * Buf[Offset+1] standard deviation of neuron's output + * + * + */ + entrysize = 2; + rvectorsetlengthatleast(&network->rndbuf, entrysize*ntotal, _state); + for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++) + { + neurontype = network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+0]; + entryoffs = entrysize*neuronidx; + if( neurontype==-2 ) + { + + /* + * Input neuron: zero mean, unit variance. + */ + network->rndbuf.ptr.p_double[entryoffs+0] = 0.0; + network->rndbuf.ptr.p_double[entryoffs+1] = 1.0; + continue; + } + if( neurontype==-3 ) + { + + /* + * "-1" neuron: mean=-1, zero variance. + */ + network->rndbuf.ptr.p_double[entryoffs+0] = -1.0; + network->rndbuf.ptr.p_double[entryoffs+1] = 0.0; + continue; + } + if( neurontype==-4 ) + { + + /* + * "0" neuron: mean=0, zero variance. + */ + network->rndbuf.ptr.p_double[entryoffs+0] = 0.0; + network->rndbuf.ptr.p_double[entryoffs+1] = 0.0; + continue; + } + if( neurontype==0 ) + { + + /* + * Adaptive summator neuron: + * * calculate its mean and variance. + * * we assume that weights of this neuron have unit variance and zero mean. + * * thus, neuron's output is always have zero mean + * * as for variance, it is a bit more interesting: + * * let n[i] is i-th input neuron + * * let w[i] is i-th weight + * * we assume that n[i] and w[i] are independently distributed + * * Var(n0*w0+n1*w1+...) = Var(n0*w0)+Var(n1*w1)+... + * * Var(X*Y) = mean(X)^2*Var(Y) + mean(Y)^2*Var(X) + Var(X)*Var(Y) + * * mean(w[i])=0, var(w[i])=1 + * * Var(n[i]*w[i]) = mean(n[i])^2 + Var(n[i]) + */ + n1 = network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+2]; + n2 = n1+network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+1]-1; + vmean = 0.0; + vvar = 0.0; + for(i=n1; i<=n2; i++) + { + vvar = vvar+ae_sqr(network->rndbuf.ptr.p_double[entrysize*i+0], _state)+ae_sqr(network->rndbuf.ptr.p_double[entrysize*i+1], _state); + } + network->rndbuf.ptr.p_double[entryoffs+0] = vmean; + network->rndbuf.ptr.p_double[entryoffs+1] = ae_sqrt(vvar, _state); + continue; + } + if( neurontype==-5 ) + { + + /* + * Linear activation function + */ + i = network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+2]; + vmean = network->rndbuf.ptr.p_double[entrysize*i+0]; + vvar = ae_sqr(network->rndbuf.ptr.p_double[entrysize*i+1], _state); + if( ae_fp_greater(vvar,0) ) + { + wscale = desiredsigma/ae_sqrt(vvar, _state); + } + else + { + wscale = 1.0; + } + mlpbase_randomizebackwardpass(network, i, wscale, _state); + network->rndbuf.ptr.p_double[entryoffs+0] = vmean*wscale; + network->rndbuf.ptr.p_double[entryoffs+1] = desiredsigma; + continue; + } + if( neurontype>0 ) + { + + /* + * Nonlinear activation function: + * * scale its inputs + * * estimate mean/sigma of its output using Monte-Carlo method + * (we simulate different inputs with unit deviation and + * sample activation function output on such inputs) + */ + i = network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+2]; + vmean = network->rndbuf.ptr.p_double[entrysize*i+0]; + vvar = ae_sqr(network->rndbuf.ptr.p_double[entrysize*i+1], _state); + if( ae_fp_greater(vvar,0) ) + { + wscale = desiredsigma/ae_sqrt(vvar, _state); + } + else + { + wscale = 1.0; + } + mlpbase_randomizebackwardpass(network, i, wscale, _state); + ef = 0.0; + ef2 = 0.0; + vmean = vmean*wscale; + for(i=0; i<=montecarlocnt-1; i++) + { + v = vmean+desiredsigma*hqrndnormal(&r, _state); + ef = ef+v; + ef2 = ef2+v*v; + } + ef = ef/montecarlocnt; + ef2 = ef2/montecarlocnt; + network->rndbuf.ptr.p_double[entryoffs+0] = ef; + network->rndbuf.ptr.p_double[entryoffs+1] = ae_maxreal(ef2-ef*ef, 0.0, _state); + continue; + } + ae_assert(ae_false, "MLPRandomize: unexpected neuron type", _state); + } + + /* + * Stage 3: generate weights. + */ + for(i=0; i<=wcount-1; i++) + { + network->weights.ptr.p_double[i] = network->weights.ptr.p_double[i]*hqrndnormal(&r, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Randomization of neural network weights and standartisator + + -- ALGLIB -- + Copyright 10.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlprandomizefull(multilayerperceptron* network, ae_state *_state) +{ + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t offs; + ae_int_t ntype; + + + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Process network + */ + mlprandomize(network, _state); + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = ae_randomreal(_state)-0.5; + network->columnsigmas.ptr.p_double[i] = ae_randomreal(_state)+0.5; + } + if( !mlpissoftmax(network, _state) ) + { + for(i=0; i<=nout-1; i++) + { + offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth; + ntype = network->structinfo.ptr.p_int[offs+0]; + if( ntype==0 ) + { + + /* + * Shifts are changed only for linear outputs neurons + */ + network->columnmeans.ptr.p_double[nin+i] = 2*ae_randomreal(_state)-1; + } + if( ntype==0||ntype==3 ) + { + + /* + * Scales are changed only for linear or bounded outputs neurons. + * Note that scale randomization preserves sign. + */ + network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*(1.5*ae_randomreal(_state)+0.5); + } + } + } +} + + +/************************************************************************* +Internal subroutine. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpinitpreprocessor(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t jmax; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t offs; + ae_int_t ntype; + ae_vector means; + ae_vector sigmas; + double s; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&means, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true); + + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Means/Sigmas + */ + if( mlpissoftmax(network, _state) ) + { + jmax = nin-1; + } + else + { + jmax = nin+nout-1; + } + ae_vector_set_length(&means, jmax+1, _state); + ae_vector_set_length(&sigmas, jmax+1, _state); + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = 0; + sigmas.ptr.p_double[i] = 0; + } + for(i=0; i<=ssize-1; i++) + { + for(j=0; j<=jmax; j++) + { + means.ptr.p_double[j] = means.ptr.p_double[j]+xy->ptr.pp_double[i][j]; + } + } + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = means.ptr.p_double[i]/ssize; + } + for(i=0; i<=ssize-1; i++) + { + for(j=0; j<=jmax; j++) + { + sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(xy->ptr.pp_double[i][j]-means.ptr.p_double[j], _state); + } + } + for(i=0; i<=jmax; i++) + { + sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/ssize, _state); + } + + /* + * Inputs + */ + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i]; + network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) ) + { + network->columnsigmas.ptr.p_double[i] = 1; + } + } + + /* + * Outputs + */ + if( !mlpissoftmax(network, _state) ) + { + for(i=0; i<=nout-1; i++) + { + offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth; + ntype = network->structinfo.ptr.p_int[offs+0]; + + /* + * Linear outputs + */ + if( ntype==0 ) + { + network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i]; + network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + + /* + * Bounded outputs (half-interval) + */ + if( ntype==3 ) + { + s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i]; + if( ae_fp_eq(s,0) ) + { + s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state); + } + if( ae_fp_eq(s,0) ) + { + s = 1.0; + } + network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state); + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. +Initialization for preprocessor based on a sample. + +INPUT + Network - initialized neural network; + XY - sample, given by sparse matrix; + SSize - sample size. + +OUTPUT + Network - neural network with initialised preprocessor. + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpinitpreprocessorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t jmax; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t offs; + ae_int_t ntype; + ae_vector means; + ae_vector sigmas; + double s; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&means, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true); + + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Means/Sigmas + */ + if( mlpissoftmax(network, _state) ) + { + jmax = nin-1; + } + else + { + jmax = nin+nout-1; + } + ae_vector_set_length(&means, jmax+1, _state); + ae_vector_set_length(&sigmas, jmax+1, _state); + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = 0; + sigmas.ptr.p_double[i] = 0; + } + for(i=0; i<=ssize-1; i++) + { + sparsegetrow(xy, i, &network->xyrow, _state); + for(j=0; j<=jmax; j++) + { + means.ptr.p_double[j] = means.ptr.p_double[j]+network->xyrow.ptr.p_double[j]; + } + } + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = means.ptr.p_double[i]/ssize; + } + for(i=0; i<=ssize-1; i++) + { + sparsegetrow(xy, i, &network->xyrow, _state); + for(j=0; j<=jmax; j++) + { + sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(network->xyrow.ptr.p_double[j]-means.ptr.p_double[j], _state); + } + } + for(i=0; i<=jmax; i++) + { + sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/ssize, _state); + } + + /* + * Inputs + */ + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i]; + network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) ) + { + network->columnsigmas.ptr.p_double[i] = 1; + } + } + + /* + * Outputs + */ + if( !mlpissoftmax(network, _state) ) + { + for(i=0; i<=nout-1; i++) + { + offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth; + ntype = network->structinfo.ptr.p_int[offs+0]; + + /* + * Linear outputs + */ + if( ntype==0 ) + { + network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i]; + network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + + /* + * Bounded outputs (half-interval) + */ + if( ntype==3 ) + { + s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i]; + if( ae_fp_eq(s,0) ) + { + s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state); + } + if( ae_fp_eq(s,0) ) + { + s = 1.0; + } + network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state); + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. +Initialization for preprocessor based on a subsample. + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array. + +OUTPUT: + Network - neural network with initialised preprocessor. + +NOTE: when SubsetSize<0 is used full dataset by call MLPInitPreprocessor + function. + + -- ALGLIB -- + Copyright 23.08.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpinitpreprocessorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t jmax; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t offs; + ae_int_t ntype; + ae_vector means; + ae_vector sigmas; + double s; + ae_int_t npoints; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&means, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true); + + ae_assert(setsize>=0, "MLPInitPreprocessorSubset: SetSize<0", _state); + if( subsetsize<0 ) + { + mlpinitpreprocessor(network, xy, setsize, _state); + ae_frame_leave(_state); + return; + } + ae_assert(subsetsize<=idx->cnt, "MLPInitPreprocessorSubset: SubsetSize>Length(Idx)", _state); + npoints = setsize; + for(i=0; i<=subsetsize-1; i++) + { + ae_assert(idx->ptr.p_int[i]>=0, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]<0)", _state); + ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state); + } + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Means/Sigmas + */ + if( mlpissoftmax(network, _state) ) + { + jmax = nin-1; + } + else + { + jmax = nin+nout-1; + } + ae_vector_set_length(&means, jmax+1, _state); + ae_vector_set_length(&sigmas, jmax+1, _state); + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = 0; + sigmas.ptr.p_double[i] = 0; + } + for(i=0; i<=subsetsize-1; i++) + { + for(j=0; j<=jmax; j++) + { + means.ptr.p_double[j] = means.ptr.p_double[j]+xy->ptr.pp_double[idx->ptr.p_int[i]][j]; + } + } + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = means.ptr.p_double[i]/subsetsize; + } + for(i=0; i<=subsetsize-1; i++) + { + for(j=0; j<=jmax; j++) + { + sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(xy->ptr.pp_double[idx->ptr.p_int[i]][j]-means.ptr.p_double[j], _state); + } + } + for(i=0; i<=jmax; i++) + { + sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/subsetsize, _state); + } + + /* + * Inputs + */ + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i]; + network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) ) + { + network->columnsigmas.ptr.p_double[i] = 1; + } + } + + /* + * Outputs + */ + if( !mlpissoftmax(network, _state) ) + { + for(i=0; i<=nout-1; i++) + { + offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth; + ntype = network->structinfo.ptr.p_int[offs+0]; + + /* + * Linear outputs + */ + if( ntype==0 ) + { + network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i]; + network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + + /* + * Bounded outputs (half-interval) + */ + if( ntype==3 ) + { + s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i]; + if( ae_fp_eq(s,0) ) + { + s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state); + } + if( ae_fp_eq(s,0) ) + { + s = 1.0; + } + network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state); + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. +Initialization for preprocessor based on a subsample. + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset, given by sparse matrix; + one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array. + +OUTPUT: + Network - neural network with initialised preprocessor. + +NOTE: when SubsetSize<0 is used full dataset by call + MLPInitPreprocessorSparse function. + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpinitpreprocessorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t jmax; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t offs; + ae_int_t ntype; + ae_vector means; + ae_vector sigmas; + double s; + ae_int_t npoints; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&means, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sigmas, 0, DT_REAL, _state, ae_true); + + ae_assert(setsize>=0, "MLPInitPreprocessorSparseSubset: SetSize<0", _state); + if( subsetsize<0 ) + { + mlpinitpreprocessorsparse(network, xy, setsize, _state); + ae_frame_leave(_state); + return; + } + ae_assert(subsetsize<=idx->cnt, "MLPInitPreprocessorSparseSubset: SubsetSize>Length(Idx)", _state); + npoints = setsize; + for(i=0; i<=subsetsize-1; i++) + { + ae_assert(idx->ptr.p_int[i]>=0, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]<0)", _state); + ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state); + } + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Means/Sigmas + */ + if( mlpissoftmax(network, _state) ) + { + jmax = nin-1; + } + else + { + jmax = nin+nout-1; + } + ae_vector_set_length(&means, jmax+1, _state); + ae_vector_set_length(&sigmas, jmax+1, _state); + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = 0; + sigmas.ptr.p_double[i] = 0; + } + for(i=0; i<=subsetsize-1; i++) + { + sparsegetrow(xy, idx->ptr.p_int[i], &network->xyrow, _state); + for(j=0; j<=jmax; j++) + { + means.ptr.p_double[j] = means.ptr.p_double[j]+network->xyrow.ptr.p_double[j]; + } + } + for(i=0; i<=jmax; i++) + { + means.ptr.p_double[i] = means.ptr.p_double[i]/subsetsize; + } + for(i=0; i<=subsetsize-1; i++) + { + sparsegetrow(xy, idx->ptr.p_int[i], &network->xyrow, _state); + for(j=0; j<=jmax; j++) + { + sigmas.ptr.p_double[j] = sigmas.ptr.p_double[j]+ae_sqr(network->xyrow.ptr.p_double[j]-means.ptr.p_double[j], _state); + } + } + for(i=0; i<=jmax; i++) + { + sigmas.ptr.p_double[i] = ae_sqrt(sigmas.ptr.p_double[i]/subsetsize, _state); + } + + /* + * Inputs + */ + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = means.ptr.p_double[i]; + network->columnsigmas.ptr.p_double[i] = sigmas.ptr.p_double[i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[i],0) ) + { + network->columnsigmas.ptr.p_double[i] = 1; + } + } + + /* + * Outputs + */ + if( !mlpissoftmax(network, _state) ) + { + for(i=0; i<=nout-1; i++) + { + offs = istart+(ntotal-nout+i)*mlpbase_nfieldwidth; + ntype = network->structinfo.ptr.p_int[offs+0]; + + /* + * Linear outputs + */ + if( ntype==0 ) + { + network->columnmeans.ptr.p_double[nin+i] = means.ptr.p_double[nin+i]; + network->columnsigmas.ptr.p_double[nin+i] = sigmas.ptr.p_double[nin+i]; + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + + /* + * Bounded outputs (half-interval) + */ + if( ntype==3 ) + { + s = means.ptr.p_double[nin+i]-network->columnmeans.ptr.p_double[nin+i]; + if( ae_fp_eq(s,0) ) + { + s = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state); + } + if( ae_fp_eq(s,0) ) + { + s = 1.0; + } + network->columnsigmas.ptr.p_double[nin+i] = ae_sign(network->columnsigmas.ptr.p_double[nin+i], _state)*ae_fabs(s, _state); + if( ae_fp_eq(network->columnsigmas.ptr.p_double[nin+i],0) ) + { + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns information about initialized network: number of inputs, outputs, +weights. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpproperties(multilayerperceptron* network, + ae_int_t* nin, + ae_int_t* nout, + ae_int_t* wcount, + ae_state *_state) +{ + + *nin = 0; + *nout = 0; + *wcount = 0; + + *nin = network->structinfo.ptr.p_int[1]; + *nout = network->structinfo.ptr.p_int[2]; + *wcount = network->structinfo.ptr.p_int[4]; +} + + +/************************************************************************* +Returns number of "internal", low-level neurons in the network (one which +is stored in StructInfo). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpntotal(multilayerperceptron* network, ae_state *_state) +{ + ae_int_t result; + + + result = network->structinfo.ptr.p_int[3]; + return result; +} + + +/************************************************************************* +Returns number of inputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetinputscount(multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t result; + + + result = network->structinfo.ptr.p_int[1]; + return result; +} + + +/************************************************************************* +Returns number of outputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetoutputscount(multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t result; + + + result = network->structinfo.ptr.p_int[2]; + return result; +} + + +/************************************************************************* +Returns number of weights. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetweightscount(multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t result; + + + result = network->structinfo.ptr.p_int[4]; + return result; +} + + +/************************************************************************* +Tells whether network is SOFTMAX-normalized (i.e. classifier) or not. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +ae_bool mlpissoftmax(multilayerperceptron* network, ae_state *_state) +{ + ae_bool result; + + + result = network->structinfo.ptr.p_int[6]==1; + return result; +} + + +/************************************************************************* +This function returns total number of layers (including input, hidden and +output layers). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayerscount(multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t result; + + + result = network->hllayersizes.cnt; + return result; +} + + +/************************************************************************* +This function returns size of K-th layer. + +K=0 corresponds to input layer, K=CNT-1 corresponds to output layer. + +Size of the output layer is always equal to the number of outputs, although +when we have softmax-normalized network, last neuron doesn't have any +connections - it is just zero. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayersize(multilayerperceptron* network, + ae_int_t k, + ae_state *_state) +{ + ae_int_t result; + + + ae_assert(k>=0&&khllayersizes.cnt, "MLPGetLayerSize: incorrect layer index", _state); + result = network->hllayersizes.ptr.p_int[k]; + return result; +} + + +/************************************************************************* +This function returns offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetinputscaling(multilayerperceptron* network, + ae_int_t i, + double* mean, + double* sigma, + ae_state *_state) +{ + + *mean = 0; + *sigma = 0; + + ae_assert(i>=0&&ihllayersizes.ptr.p_int[0], "MLPGetInputScaling: incorrect (nonexistent) I", _state); + *mean = network->columnmeans.ptr.p_double[i]; + *sigma = network->columnsigmas.ptr.p_double[i]; + if( ae_fp_eq(*sigma,0) ) + { + *sigma = 1; + } +} + + +/************************************************************************* +This function returns offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. In case we have SOFTMAX-normalized network, +we return (Mean,Sigma)=(0.0,1.0). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetoutputscaling(multilayerperceptron* network, + ae_int_t i, + double* mean, + double* sigma, + ae_state *_state) +{ + + *mean = 0; + *sigma = 0; + + ae_assert(i>=0&&ihllayersizes.ptr.p_int[network->hllayersizes.cnt-1], "MLPGetOutputScaling: incorrect (nonexistent) I", _state); + if( network->structinfo.ptr.p_int[6]==1 ) + { + *mean = 0; + *sigma = 1; + } + else + { + *mean = network->columnmeans.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i]; + *sigma = network->columnsigmas.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i]; + } +} + + +/************************************************************************* +This function returns information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + +OUTPUT PARAMETERS: + FKind - activation function type (used by MLPActivationFunction()) + this value is zero for input or linear neurons + Threshold - also called offset, bias + zero for input neurons + +NOTE: this function throws exception if layer or neuron with given index +do not exists. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetneuroninfo(multilayerperceptron* network, + ae_int_t k, + ae_int_t i, + ae_int_t* fkind, + double* threshold, + ae_state *_state) +{ + ae_int_t ncnt; + ae_int_t istart; + ae_int_t highlevelidx; + ae_int_t activationoffset; + + *fkind = 0; + *threshold = 0; + + ncnt = network->hlneurons.cnt/mlpbase_hlnfieldwidth; + istart = network->structinfo.ptr.p_int[5]; + + /* + * search + */ + network->integerbuf.ptr.p_int[0] = k; + network->integerbuf.ptr.p_int[1] = i; + highlevelidx = recsearch(&network->hlneurons, mlpbase_hlnfieldwidth, 2, 0, ncnt, &network->integerbuf, _state); + ae_assert(highlevelidx>=0, "MLPGetNeuronInfo: incorrect (nonexistent) layer or neuron index", _state); + + /* + * 1. find offset of the activation function record in the + */ + if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]>=0 ) + { + activationoffset = istart+network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]*mlpbase_nfieldwidth; + *fkind = network->structinfo.ptr.p_int[activationoffset+0]; + } + else + { + *fkind = 0; + } + if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]>=0 ) + { + *threshold = network->weights.ptr.p_double[network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]]; + } + else + { + *threshold = 0; + } +} + + +/************************************************************************* +This function returns information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + +RESULT: + connection weight (zero for non-existent connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. returns zero if neurons exist, but there is no connection between them + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +double mlpgetweight(multilayerperceptron* network, + ae_int_t k0, + ae_int_t i0, + ae_int_t k1, + ae_int_t i1, + ae_state *_state) +{ + ae_int_t ccnt; + ae_int_t highlevelidx; + double result; + + + ccnt = network->hlconnections.cnt/mlpbase_hlconnfieldwidth; + + /* + * check params + */ + ae_assert(k0>=0&&k0hllayersizes.cnt, "MLPGetWeight: incorrect (nonexistent) K0", _state); + ae_assert(i0>=0&&i0hllayersizes.ptr.p_int[k0], "MLPGetWeight: incorrect (nonexistent) I0", _state); + ae_assert(k1>=0&&k1hllayersizes.cnt, "MLPGetWeight: incorrect (nonexistent) K1", _state); + ae_assert(i1>=0&&i1hllayersizes.ptr.p_int[k1], "MLPGetWeight: incorrect (nonexistent) I1", _state); + + /* + * search + */ + network->integerbuf.ptr.p_int[0] = k0; + network->integerbuf.ptr.p_int[1] = i0; + network->integerbuf.ptr.p_int[2] = k1; + network->integerbuf.ptr.p_int[3] = i1; + highlevelidx = recsearch(&network->hlconnections, mlpbase_hlconnfieldwidth, 4, 0, ccnt, &network->integerbuf, _state); + if( highlevelidx>=0 ) + { + result = network->weights.ptr.p_double[network->hlconnections.ptr.p_int[highlevelidx*mlpbase_hlconnfieldwidth+4]]; + } + else + { + result = 0; + } + return result; +} + + +/************************************************************************* +This function sets offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +NTE: I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network. This function sets Mean and Sigma. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetinputscaling(multilayerperceptron* network, + ae_int_t i, + double mean, + double sigma, + ae_state *_state) +{ + + + ae_assert(i>=0&&ihllayersizes.ptr.p_int[0], "MLPSetInputScaling: incorrect (nonexistent) I", _state); + ae_assert(ae_isfinite(mean, _state), "MLPSetInputScaling: infinite or NAN Mean", _state); + ae_assert(ae_isfinite(sigma, _state), "MLPSetInputScaling: infinite or NAN Sigma", _state); + if( ae_fp_eq(sigma,0) ) + { + sigma = 1; + } + network->columnmeans.ptr.p_double[i] = mean; + network->columnsigmas.ptr.p_double[i] = sigma; +} + + +/************************************************************************* +This function sets offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +OUTPUT PARAMETERS: + +NOTE: I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. This function sets Sigma/Mean. In case we +have SOFTMAX-normalized network, you can not set (Sigma,Mean) to anything +other than(0.0,1.0) - this function will throw exception. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetoutputscaling(multilayerperceptron* network, + ae_int_t i, + double mean, + double sigma, + ae_state *_state) +{ + + + ae_assert(i>=0&&ihllayersizes.ptr.p_int[network->hllayersizes.cnt-1], "MLPSetOutputScaling: incorrect (nonexistent) I", _state); + ae_assert(ae_isfinite(mean, _state), "MLPSetOutputScaling: infinite or NAN Mean", _state); + ae_assert(ae_isfinite(sigma, _state), "MLPSetOutputScaling: infinite or NAN Sigma", _state); + if( network->structinfo.ptr.p_int[6]==1 ) + { + ae_assert(ae_fp_eq(mean,0), "MLPSetOutputScaling: you can not set non-zero Mean term for classifier network", _state); + ae_assert(ae_fp_eq(sigma,1), "MLPSetOutputScaling: you can not set non-unit Sigma term for classifier network", _state); + } + else + { + if( ae_fp_eq(sigma,0) ) + { + sigma = 1; + } + network->columnmeans.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i] = mean; + network->columnsigmas.ptr.p_double[network->hllayersizes.ptr.p_int[0]+i] = sigma; + } +} + + +/************************************************************************* +This function modifies information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + FKind - activation function type (used by MLPActivationFunction()) + this value must be zero for input neurons + (you can not set activation function for input neurons) + Threshold - also called offset, bias + this value must be zero for input neurons + (you can not set threshold for input neurons) + +NOTES: +1. this function throws exception if layer or neuron with given index do + not exists. +2. this function also throws exception when you try to set non-linear + activation function for input neurons (any kind of network) or for output + neurons of classifier network. +3. this function throws exception when you try to set non-zero threshold for + input neurons (any kind of network). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetneuroninfo(multilayerperceptron* network, + ae_int_t k, + ae_int_t i, + ae_int_t fkind, + double threshold, + ae_state *_state) +{ + ae_int_t ncnt; + ae_int_t istart; + ae_int_t highlevelidx; + ae_int_t activationoffset; + + + ae_assert(ae_isfinite(threshold, _state), "MLPSetNeuronInfo: infinite or NAN Threshold", _state); + + /* + * convenience vars + */ + ncnt = network->hlneurons.cnt/mlpbase_hlnfieldwidth; + istart = network->structinfo.ptr.p_int[5]; + + /* + * search + */ + network->integerbuf.ptr.p_int[0] = k; + network->integerbuf.ptr.p_int[1] = i; + highlevelidx = recsearch(&network->hlneurons, mlpbase_hlnfieldwidth, 2, 0, ncnt, &network->integerbuf, _state); + ae_assert(highlevelidx>=0, "MLPSetNeuronInfo: incorrect (nonexistent) layer or neuron index", _state); + + /* + * activation function + */ + if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]>=0 ) + { + activationoffset = istart+network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+2]*mlpbase_nfieldwidth; + network->structinfo.ptr.p_int[activationoffset+0] = fkind; + } + else + { + ae_assert(fkind==0, "MLPSetNeuronInfo: you try to set activation function for neuron which can not have one", _state); + } + + /* + * Threshold + */ + if( network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]>=0 ) + { + network->weights.ptr.p_double[network->hlneurons.ptr.p_int[highlevelidx*mlpbase_hlnfieldwidth+3]] = threshold; + } + else + { + ae_assert(ae_fp_eq(threshold,0), "MLPSetNeuronInfo: you try to set non-zero threshold for neuron which can not have one", _state); + } +} + + +/************************************************************************* +This function modifies information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + W - connection weight (must be zero for non-existent + connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. throws exception if you try to set non-zero weight for non-existent + connection + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetweight(multilayerperceptron* network, + ae_int_t k0, + ae_int_t i0, + ae_int_t k1, + ae_int_t i1, + double w, + ae_state *_state) +{ + ae_int_t ccnt; + ae_int_t highlevelidx; + + + ccnt = network->hlconnections.cnt/mlpbase_hlconnfieldwidth; + + /* + * check params + */ + ae_assert(k0>=0&&k0hllayersizes.cnt, "MLPSetWeight: incorrect (nonexistent) K0", _state); + ae_assert(i0>=0&&i0hllayersizes.ptr.p_int[k0], "MLPSetWeight: incorrect (nonexistent) I0", _state); + ae_assert(k1>=0&&k1hllayersizes.cnt, "MLPSetWeight: incorrect (nonexistent) K1", _state); + ae_assert(i1>=0&&i1hllayersizes.ptr.p_int[k1], "MLPSetWeight: incorrect (nonexistent) I1", _state); + ae_assert(ae_isfinite(w, _state), "MLPSetWeight: infinite or NAN weight", _state); + + /* + * search + */ + network->integerbuf.ptr.p_int[0] = k0; + network->integerbuf.ptr.p_int[1] = i0; + network->integerbuf.ptr.p_int[2] = k1; + network->integerbuf.ptr.p_int[3] = i1; + highlevelidx = recsearch(&network->hlconnections, mlpbase_hlconnfieldwidth, 4, 0, ccnt, &network->integerbuf, _state); + if( highlevelidx>=0 ) + { + network->weights.ptr.p_double[network->hlconnections.ptr.p_int[highlevelidx*mlpbase_hlconnfieldwidth+4]] = w; + } + else + { + ae_assert(ae_fp_eq(w,0), "MLPSetWeight: you try to set non-zero weight for non-existent connection", _state); + } +} + + +/************************************************************************* +Neural network activation function + +INPUT PARAMETERS: + NET - neuron input + K - function index (zero for linear function) + +OUTPUT PARAMETERS: + F - function + DF - its derivative + D2F - its second derivative + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpactivationfunction(double net, + ae_int_t k, + double* f, + double* df, + double* d2f, + ae_state *_state) +{ + double net2; + double arg; + double root; + double r; + + *f = 0; + *df = 0; + *d2f = 0; + + if( k==0||k==-5 ) + { + *f = net; + *df = 1; + *d2f = 0; + return; + } + if( k==1 ) + { + + /* + * TanH activation function + */ + if( ae_fp_less(ae_fabs(net, _state),100) ) + { + *f = ae_tanh(net, _state); + } + else + { + *f = ae_sign(net, _state); + } + *df = 1-*f*(*f); + *d2f = -2*(*f)*(*df); + return; + } + if( k==3 ) + { + + /* + * EX activation function + */ + if( ae_fp_greater_eq(net,0) ) + { + net2 = net*net; + arg = net2+1; + root = ae_sqrt(arg, _state); + *f = net+root; + r = net/root; + *df = 1+r; + *d2f = (root-net*r)/arg; + } + else + { + *f = ae_exp(net, _state); + *df = *f; + *d2f = *f; + } + return; + } + if( k==2 ) + { + *f = ae_exp(-ae_sqr(net, _state), _state); + *df = -2*net*(*f); + *d2f = -2*(*f+*df*net); + return; + } + *f = 0; + *df = 0; + *d2f = 0; +} + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Network - neural network + X - input vector, array[0..NIn-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also MLPProcessI + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpprocess(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + + if( y->cntstructinfo.ptr.p_int[2] ) + { + ae_vector_set_length(y, network->structinfo.ptr.p_int[2], _state); + } + mlpinternalprocessvector(&network->structinfo, &network->weights, &network->columnmeans, &network->columnsigmas, &network->neurons, &network->dfdnet, x, y, _state); +} + + +/************************************************************************* +'interactive' variant of MLPProcess for languages like Python which +support constructs like "Y = MLPProcess(NN,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 21.09.2010 by Bochkanov Sergey +*************************************************************************/ +void mlpprocessi(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + ae_vector_clear(y); + + mlpprocess(network, x, y, _state); +} + + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPError: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = ae_sqr(network->err.rmserror, _state)*npoints*mlpgetoutputscount(network, _state)/2; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlperror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlperror(network,xy,npoints, _state); +} + + +/************************************************************************* +Error of the neural network on dataset given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0 + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPErrorSparse: XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPErrorSparse: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPErrorSparse: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPErrorSparse: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = ae_sqr(network->err.rmserror, _state)*npoints*mlpgetoutputscount(network, _state)/2; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlperrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlperrorsparse(network,xy,npoints, _state); +} + + +/************************************************************************* +Natural error function for neural network, internal subroutine. + +NOTE: this function is single-threaded. Unlike other error function, it +receives no speed-up from being executed in SMP mode. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperrorn(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + double e; + double result; + + + mlpproperties(network, &nin, &nout, &wcount, _state); + result = 0; + for(i=0; i<=ssize-1; i++) + { + + /* + * Process vector + */ + ae_v_move(&network->x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1)); + mlpprocess(network, &network->x, &network->y, _state); + + /* + * Update error function + */ + if( network->structinfo.ptr.p_int[6]==0 ) + { + + /* + * Least squares error function + */ + ae_v_sub(&network->y.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1)); + e = ae_v_dotproduct(&network->y.ptr.p_double[0], 1, &network->y.ptr.p_double[0], 1, ae_v_len(0,nout-1)); + result = result+e/2; + } + else + { + + /* + * Cross-entropy error function + */ + k = ae_round(xy->ptr.pp_double[i][nin], _state); + if( k>=0&&ky.ptr.p_double[k], _state); + } + } + } + return result; +} + + +/************************************************************************* +Classification error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + classification error (number of misclassified cases) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_int_t result; + + + ae_assert(xy->rows>=npoints, "MLPClsError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPClsError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPClsError: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = ae_round(npoints*network->err.relclserror, _state); + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +ae_int_t _pexec_mlpclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpclserror(network,xy,npoints, _state); +} + + +/************************************************************************* +Relative classification error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 25.12.2008 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPRelClsError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPRelClsError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPRelClsError: XY has less than NIn+NOut columns", _state); + } + } + if( npoints>0 ) + { + result = (double)mlpclserror(network, xy, npoints, _state)/(double)npoints; + } + else + { + result = 0.0; + } + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlprelclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlprelclserror(network,xy,npoints, _state); +} + + +/************************************************************************* +Relative classification error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. Sparse matrix must use CRS format + for storage. + NPoints - points count, >=0. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPRelClsErrorSparse: sparse matrix XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPRelClsErrorSparse: sparse matrix XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPRelClsErrorSparse: sparse matrix XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPRelClsErrorSparse: sparse matrix XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.relclserror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlprelclserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlprelclserrorsparse(network,xy,npoints, _state); +} + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 08.01.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpavgce(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPAvgCE: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPAvgCE: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgCE: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgce; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgce(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgce(network,xy,npoints, _state); +} + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set given by +sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 9.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgcesparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPAvgCESparse: sparse matrix XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPAvgCESparse: sparse matrix XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPAvgCESparse: sparse matrix XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgCESparse: sparse matrix XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgce; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgcesparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgcesparse(network,xy,npoints, _state); +} + + +/************************************************************************* +RMS error on the test set given. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlprmserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPRMSError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPRMSError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPRMSError: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.rmserror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlprmserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlprmserror(network,xy,npoints, _state); +} + + +/************************************************************************* +RMS error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprmserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPRMSErrorSparse: sparse matrix XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPRMSErrorSparse: sparse matrix XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPRMSErrorSparse: sparse matrix XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPRMSErrorSparse: sparse matrix XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.rmserror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlprmserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlprmserrorsparse(network,xy,npoints, _state); +} + + +/************************************************************************* +Average absolute error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPAvgError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPAvgError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgError: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgerror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgerror(network,xy,npoints, _state); +} + + +/************************************************************************* +Average absolute error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPAvgErrorSparse: XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPAvgErrorSparse: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPAvgErrorSparse: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgErrorSparse: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgerror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgerrorsparse(network,xy,npoints, _state); +} + + +/************************************************************************* +Average relative error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(xy->rows>=npoints, "MLPAvgRelError: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPAvgRelError: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgRelError: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, xy, &network->dummysxy, npoints, 0, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgrelerror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgrelerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgrelerror(network,xy,npoints, _state); +} + + +/************************************************************************* +Average relative error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPAvgRelErrorSparse: XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=npoints, "MLPAvgRelErrorSparse: XY has less than NPoints rows", _state); + if( npoints>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPAvgRelErrorSparse: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAvgRelErrorSparse: XY has less than NIn+NOut columns", _state); + } + } + mlpallerrorsx(network, &network->dummydxy, xy, npoints, 1, &network->dummyidx, 0, npoints, 0, &network->buf, &network->err, _state); + result = network->err.avgrelerror; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlpavgrelerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state) +{ + return mlpavgrelerrorsparse(network,xy,npoints, _state); +} + + +/************************************************************************* +Gradient calculation + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgrad(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* desiredy, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_int_t i; + ae_int_t nout; + ae_int_t ntotal; + + *e = 0; + + + /* + * Alloc + */ + rvectorsetlengthatleast(grad, network->structinfo.ptr.p_int[4], _state); + + /* + * Prepare dError/dOut, internal structures + */ + mlpprocess(network, x, &network->y, _state); + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + *e = 0; + for(i=0; i<=ntotal-1; i++) + { + network->derror.ptr.p_double[i] = 0; + } + for(i=0; i<=nout-1; i++) + { + network->derror.ptr.p_double[ntotal-nout+i] = network->y.ptr.p_double[i]-desiredy->ptr.p_double[i]; + *e = *e+ae_sqr(network->y.ptr.p_double[i]-desiredy->ptr.p_double[i], _state)/2; + } + + /* + * gradient + */ + mlpbase_mlpinternalcalculategradient(network, &network->neurons, &network->weights, &network->derror, grad, ae_false, _state); +} + + +/************************************************************************* +Gradient calculation (natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradn(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* desiredy, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + double s; + ae_int_t i; + ae_int_t nout; + ae_int_t ntotal; + + *e = 0; + + + /* + * Alloc + */ + rvectorsetlengthatleast(grad, network->structinfo.ptr.p_int[4], _state); + + /* + * Prepare dError/dOut, internal structures + */ + mlpprocess(network, x, &network->y, _state); + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + for(i=0; i<=ntotal-1; i++) + { + network->derror.ptr.p_double[i] = 0; + } + *e = 0; + if( network->structinfo.ptr.p_int[6]==0 ) + { + + /* + * Regression network, least squares + */ + for(i=0; i<=nout-1; i++) + { + network->derror.ptr.p_double[ntotal-nout+i] = network->y.ptr.p_double[i]-desiredy->ptr.p_double[i]; + *e = *e+ae_sqr(network->y.ptr.p_double[i]-desiredy->ptr.p_double[i], _state)/2; + } + } + else + { + + /* + * Classification network, cross-entropy + */ + s = 0; + for(i=0; i<=nout-1; i++) + { + s = s+desiredy->ptr.p_double[i]; + } + for(i=0; i<=nout-1; i++) + { + network->derror.ptr.p_double[ntotal-nout+i] = s*network->y.ptr.p_double[i]-desiredy->ptr.p_double[i]; + *e = *e+mlpbase_safecrossentropy(desiredy->ptr.p_double[i], network->y.ptr.p_double[i], _state); + } + } + + /* + * gradient + */ + mlpbase_mlpinternalcalculategradient(network, &network->neurons, &network->weights, &network->derror, grad, ae_true, _state); +} + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t subset0; + ae_int_t subset1; + ae_int_t subsettype; + smlpgrad *sgrad; + ae_smart_ptr _sgrad; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_smart_ptr_init(&_sgrad, (void**)&sgrad, _state, ae_true); + + ae_assert(ssize>=0, "MLPGradBatchSparse: SSize<0", _state); + subset0 = 0; + subset1 = ssize; + subsettype = 0; + mlpproperties(network, &nin, &nout, &wcount, _state); + rvectorsetlengthatleast(grad, wcount, _state); + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + sgrad->f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad->g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + mlpgradbatchx(network, xy, &network->dummysxy, ssize, 0, &network->dummyidx, subset0, subset1, subsettype, &network->buf, &network->gradbuf, _state); + *e = 0.0; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = 0.0; + } + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + *e = *e+sgrad->f; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = grad->ptr.p_double[i]+sgrad->g.ptr.p_double[i]; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpgradbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state) +{ + mlpgradbatch(network,xy,ssize,e,grad, _state); +} + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs given by sparse +matrices + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs. + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t subset0; + ae_int_t subset1; + ae_int_t subsettype; + smlpgrad *sgrad; + ae_smart_ptr _sgrad; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_smart_ptr_init(&_sgrad, (void**)&sgrad, _state, ae_true); + + ae_assert(ssize>=0, "MLPGradBatchSparse: SSize<0", _state); + ae_assert(sparseiscrs(xy, _state), "MLPGradBatchSparse: sparse matrix XY must be in CRS format.", _state); + subset0 = 0; + subset1 = ssize; + subsettype = 0; + mlpproperties(network, &nin, &nout, &wcount, _state); + rvectorsetlengthatleast(grad, wcount, _state); + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + sgrad->f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad->g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + mlpgradbatchx(network, &network->dummydxy, xy, ssize, 1, &network->dummyidx, subset0, subset1, subsettype, &network->buf, &network->gradbuf, _state); + *e = 0.0; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = 0.0; + } + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + *e = *e+sgrad->f; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = grad->ptr.p_double[i]+sgrad->g.ptr.p_double[i]; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpgradbatchsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state) +{ + mlpgradbatchsparse(network,xy,ssize,e,grad, _state); +} + + +/************************************************************************* +Batch gradient calculation for a subset of dataset + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t npoints; + ae_int_t subset0; + ae_int_t subset1; + ae_int_t subsettype; + smlpgrad *sgrad; + ae_smart_ptr _sgrad; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_smart_ptr_init(&_sgrad, (void**)&sgrad, _state, ae_true); + + ae_assert(setsize>=0, "MLPGradBatchSubset: SetSize<0", _state); + ae_assert(subsetsize<=idx->cnt, "MLPGradBatchSubset: SubsetSize>Length(Idx)", _state); + npoints = setsize; + if( subsetsize<0 ) + { + subset0 = 0; + subset1 = setsize; + subsettype = 0; + } + else + { + subset0 = 0; + subset1 = subsetsize; + subsettype = 1; + for(i=0; i<=subsetsize-1; i++) + { + ae_assert(idx->ptr.p_int[i]>=0, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]<0)", _state); + ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state); + } + } + mlpproperties(network, &nin, &nout, &wcount, _state); + rvectorsetlengthatleast(grad, wcount, _state); + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + sgrad->f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad->g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + mlpgradbatchx(network, xy, &network->dummysxy, setsize, 0, idx, subset0, subset1, subsettype, &network->buf, &network->gradbuf, _state); + *e = 0.0; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = 0.0; + } + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + *e = *e+sgrad->f; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = grad->ptr.p_double[i]+sgrad->g.ptr.p_double[i]; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpgradbatchsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state) +{ + mlpgradbatchsubset(network,xy,setsize,idx,subsetsize,e,grad, _state); +} + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs for a subset of +dataset given by set of indexes. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatchSparse + function. + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t npoints; + ae_int_t subset0; + ae_int_t subset1; + ae_int_t subsettype; + smlpgrad *sgrad; + ae_smart_ptr _sgrad; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_smart_ptr_init(&_sgrad, (void**)&sgrad, _state, ae_true); + + ae_assert(setsize>=0, "MLPGradBatchSparseSubset: SetSize<0", _state); + ae_assert(subsetsize<=idx->cnt, "MLPGradBatchSparseSubset: SubsetSize>Length(Idx)", _state); + ae_assert(sparseiscrs(xy, _state), "MLPGradBatchSparseSubset: sparse matrix XY must be in CRS format.", _state); + npoints = setsize; + if( subsetsize<0 ) + { + subset0 = 0; + subset1 = setsize; + subsettype = 0; + } + else + { + subset0 = 0; + subset1 = subsetsize; + subsettype = 1; + for(i=0; i<=subsetsize-1; i++) + { + ae_assert(idx->ptr.p_int[i]>=0, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]<0)", _state); + ae_assert(idx->ptr.p_int[i]<=npoints-1, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)", _state); + } + } + mlpproperties(network, &nin, &nout, &wcount, _state); + rvectorsetlengthatleast(grad, wcount, _state); + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + sgrad->f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad->g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + mlpgradbatchx(network, &network->dummydxy, xy, setsize, 1, idx, subset0, subset1, subsettype, &network->buf, &network->gradbuf, _state); + *e = 0.0; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = 0.0; + } + ae_shared_pool_first_recycled(&network->gradbuf, &_sgrad, _state); + while(sgrad!=NULL) + { + *e = *e+sgrad->f; + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = grad->ptr.p_double[i]+sgrad->g.ptr.p_double[i]; + } + ae_shared_pool_next_recycled(&network->gradbuf, &_sgrad, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpgradbatchsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state) +{ + mlpgradbatchsparsesubset(network,xy,setsize,idx,subsetsize,e,grad, _state); +} + + +void mlpgradbatchx(multilayerperceptron* network, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + ae_shared_pool* gradbuf, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t rowsize; + ae_int_t srcidx; + ae_int_t cstart; + ae_int_t csize; + ae_int_t j; + double problemcost; + mlpbuffers *buf2; + ae_smart_ptr _buf2; + ae_int_t len0; + ae_int_t len1; + mlpbuffers *pbuf; + ae_smart_ptr _pbuf; + smlpgrad *sgrad; + ae_smart_ptr _sgrad; + + ae_frame_make(_state, &_frame_block); + ae_smart_ptr_init(&_buf2, (void**)&buf2, _state, ae_true); + ae_smart_ptr_init(&_pbuf, (void**)&pbuf, _state, ae_true); + ae_smart_ptr_init(&_sgrad, (void**)&sgrad, _state, ae_true); + + ae_assert(datasetsize>=0, "MLPGradBatchX: SetSize<0", _state); + ae_assert(datasettype==0||datasettype==1, "MLPGradBatchX: DatasetType is incorrect", _state); + ae_assert(subsettype==0||subsettype==1, "MLPGradBatchX: SubsetType is incorrect", _state); + + /* + * Determine network and dataset properties + */ + mlpproperties(network, &nin, &nout, &wcount, _state); + if( mlpissoftmax(network, _state) ) + { + rowsize = nin+1; + } + else + { + rowsize = nin+nout; + } + + /* + * Split problem. + * + * Splitting problem allows us to reduce effect of single-precision + * arithmetics (SSE-optimized version of MLPChunkedGradient uses single + * precision internally, but converts them to double precision after + * results are exported from HPC buffer to network). Small batches are + * calculated in single precision, results are aggregated in double + * precision, and it allows us to avoid accumulation of errors when + * we process very large batches (tens of thousands of items). + * + * NOTE: it is important to use real arithmetics for ProblemCost + * because ProblemCost may be larger than MAXINT. + */ + problemcost = subset1-subset0; + problemcost = problemcost*wcount; + if( subset1-subset0>=2*mlpbase_microbatchsize&&ae_fp_greater(problemcost,mlpbase_gradbasecasecost) ) + { + splitlength(subset1-subset0, mlpbase_microbatchsize, &len0, &len1, _state); + mlpgradbatchx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0, subset0+len0, subsettype, buf, gradbuf, _state); + mlpgradbatchx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0+len0, subset1, subsettype, buf, gradbuf, _state); + ae_frame_leave(_state); + return; + } + + /* + * Chunked processing + */ + ae_shared_pool_retrieve(gradbuf, &_sgrad, _state); + ae_shared_pool_retrieve(buf, &_pbuf, _state); + hpcpreparechunkedgradient(&network->weights, wcount, mlpntotal(network, _state), nin, nout, pbuf, _state); + cstart = subset0; + while(cstartchunksize, _state)-cstart; + for(j=0; j<=csize-1; j++) + { + srcidx = -1; + if( subsettype==0 ) + { + srcidx = cstart+j; + } + if( subsettype==1 ) + { + srcidx = idx->ptr.p_int[cstart+j]; + } + ae_assert(srcidx>=0, "MLPGradBatchX: internal error", _state); + if( datasettype==0 ) + { + ae_v_move(&pbuf->xy.ptr.pp_double[j][0], 1, &densexy->ptr.pp_double[srcidx][0], 1, ae_v_len(0,rowsize-1)); + } + if( datasettype==1 ) + { + sparsegetrow(sparsexy, srcidx, &pbuf->xyrow, _state); + ae_v_move(&pbuf->xy.ptr.pp_double[j][0], 1, &pbuf->xyrow.ptr.p_double[0], 1, ae_v_len(0,rowsize-1)); + } + } + + /* + * Process chunk and advance line pointer + */ + mlpbase_mlpchunkedgradient(network, &pbuf->xy, 0, csize, &pbuf->batch4buf, &pbuf->hpcbuf, &sgrad->f, ae_false, _state); + cstart = cstart+pbuf->chunksize; + } + hpcfinalizechunkedgradient(pbuf, &sgrad->g, _state); + ae_shared_pool_recycle(buf, &_pbuf, _state); + ae_shared_pool_recycle(gradbuf, &_sgrad, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs +(natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - set of inputs/outputs; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradnbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + mlpbuffers *pbuf; + ae_smart_ptr _pbuf; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_smart_ptr_init(&_pbuf, (void**)&pbuf, _state, ae_true); + + + /* + * Alloc + */ + mlpproperties(network, &nin, &nout, &wcount, _state); + ae_shared_pool_retrieve(&network->buf, &_pbuf, _state); + hpcpreparechunkedgradient(&network->weights, wcount, mlpntotal(network, _state), nin, nout, pbuf, _state); + rvectorsetlengthatleast(grad, wcount, _state); + for(i=0; i<=wcount-1; i++) + { + grad->ptr.p_double[i] = 0; + } + *e = 0; + i = 0; + while(i<=ssize-1) + { + mlpbase_mlpchunkedgradient(network, xy, i, ae_minint(ssize, i+pbuf->chunksize, _state)-i, &pbuf->batch4buf, &pbuf->hpcbuf, e, ae_true, _state); + i = i+pbuf->chunksize; + } + hpcfinalizechunkedgradient(pbuf, grad, _state); + ae_shared_pool_recycle(&network->buf, &_pbuf, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Batch Hessian calculation (natural error function) using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessiannbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state) +{ + + *e = 0; + + mlpbase_mlphessianbatchinternal(network, xy, ssize, ae_true, e, grad, h, _state); +} + + +/************************************************************************* +Batch Hessian calculation using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessianbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state) +{ + + *e = 0; + + mlpbase_mlphessianbatchinternal(network, xy, ssize, ae_false, e, grad, h, _state); +} + + +/************************************************************************* +Internal subroutine, shouldn't be called by user. +*************************************************************************/ +void mlpinternalprocessvector(/* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* weights, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_vector* neurons, + /* Real */ ae_vector* dfdnet, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n1; + ae_int_t n2; + ae_int_t w1; + ae_int_t w2; + ae_int_t ntotal; + ae_int_t nin; + ae_int_t nout; + ae_int_t istart; + ae_int_t offs; + double net; + double f; + double df; + double d2f; + double mx; + ae_bool perr; + + + + /* + * Read network geometry + */ + nin = structinfo->ptr.p_int[1]; + nout = structinfo->ptr.p_int[2]; + ntotal = structinfo->ptr.p_int[3]; + istart = structinfo->ptr.p_int[5]; + + /* + * Inputs standartisation and putting in the network + */ + for(i=0; i<=nin-1; i++) + { + if( ae_fp_neq(columnsigmas->ptr.p_double[i],0) ) + { + neurons->ptr.p_double[i] = (x->ptr.p_double[i]-columnmeans->ptr.p_double[i])/columnsigmas->ptr.p_double[i]; + } + else + { + neurons->ptr.p_double[i] = x->ptr.p_double[i]-columnmeans->ptr.p_double[i]; + } + } + + /* + * Process network + */ + for(i=0; i<=ntotal-1; i++) + { + offs = istart+i*mlpbase_nfieldwidth; + if( structinfo->ptr.p_int[offs+0]>0||structinfo->ptr.p_int[offs+0]==-5 ) + { + + /* + * Activation function + */ + mlpactivationfunction(neurons->ptr.p_double[structinfo->ptr.p_int[offs+2]], structinfo->ptr.p_int[offs+0], &f, &df, &d2f, _state); + neurons->ptr.p_double[i] = f; + dfdnet->ptr.p_double[i] = df; + continue; + } + if( structinfo->ptr.p_int[offs+0]==0 ) + { + + /* + * Adaptive summator + */ + n1 = structinfo->ptr.p_int[offs+2]; + n2 = n1+structinfo->ptr.p_int[offs+1]-1; + w1 = structinfo->ptr.p_int[offs+3]; + w2 = w1+structinfo->ptr.p_int[offs+1]-1; + net = ae_v_dotproduct(&weights->ptr.p_double[w1], 1, &neurons->ptr.p_double[n1], 1, ae_v_len(w1,w2)); + neurons->ptr.p_double[i] = net; + dfdnet->ptr.p_double[i] = 1.0; + touchint(&n2, _state); + continue; + } + if( structinfo->ptr.p_int[offs+0]<0 ) + { + perr = ae_true; + if( structinfo->ptr.p_int[offs+0]==-2 ) + { + + /* + * input neuron, left unchanged + */ + perr = ae_false; + } + if( structinfo->ptr.p_int[offs+0]==-3 ) + { + + /* + * "-1" neuron + */ + neurons->ptr.p_double[i] = -1; + perr = ae_false; + } + if( structinfo->ptr.p_int[offs+0]==-4 ) + { + + /* + * "0" neuron + */ + neurons->ptr.p_double[i] = 0; + perr = ae_false; + } + ae_assert(!perr, "MLPInternalProcessVector: internal error - unknown neuron type!", _state); + continue; + } + } + + /* + * Extract result + */ + ae_v_move(&y->ptr.p_double[0], 1, &neurons->ptr.p_double[ntotal-nout], 1, ae_v_len(0,nout-1)); + + /* + * Softmax post-processing or standardisation if needed + */ + ae_assert(structinfo->ptr.p_int[6]==0||structinfo->ptr.p_int[6]==1, "MLPInternalProcessVector: unknown normalization type!", _state); + if( structinfo->ptr.p_int[6]==1 ) + { + + /* + * Softmax + */ + mx = y->ptr.p_double[0]; + for(i=1; i<=nout-1; i++) + { + mx = ae_maxreal(mx, y->ptr.p_double[i], _state); + } + net = 0; + for(i=0; i<=nout-1; i++) + { + y->ptr.p_double[i] = ae_exp(y->ptr.p_double[i]-mx, _state); + net = net+y->ptr.p_double[i]; + } + for(i=0; i<=nout-1; i++) + { + y->ptr.p_double[i] = y->ptr.p_double[i]/net; + } + } + else + { + + /* + * Standardisation + */ + for(i=0; i<=nout-1; i++) + { + y->ptr.p_double[i] = y->ptr.p_double[i]*columnsigmas->ptr.p_double[nin+i]+columnmeans->ptr.p_double[nin+i]; + } + } +} + + +/************************************************************************* +Serializer: allocation + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpalloc(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t fkind; + double threshold; + double v0; + double v1; + ae_int_t nin; + ae_int_t nout; + + + nin = network->hllayersizes.ptr.p_int[0]; + nout = network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1]; + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + allocintegerarray(s, &network->hllayersizes, -1, _state); + for(i=1; i<=network->hllayersizes.cnt-1; i++) + { + for(j=0; j<=network->hllayersizes.ptr.p_int[i]-1; j++) + { + mlpgetneuroninfo(network, i, j, &fkind, &threshold, _state); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + for(k=0; k<=network->hllayersizes.ptr.p_int[i-1]-1; k++) + { + ae_serializer_alloc_entry(s); + } + } + } + for(j=0; j<=nin-1; j++) + { + mlpgetinputscaling(network, j, &v0, &v1, _state); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + } + for(j=0; j<=nout-1; j++) + { + mlpgetoutputscaling(network, j, &v0, &v1, _state); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + } +} + + +/************************************************************************* +Serializer: serialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpserialize(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t fkind; + double threshold; + double v0; + double v1; + ae_int_t nin; + ae_int_t nout; + + + nin = network->hllayersizes.ptr.p_int[0]; + nout = network->hllayersizes.ptr.p_int[network->hllayersizes.cnt-1]; + ae_serializer_serialize_int(s, getmlpserializationcode(_state), _state); + ae_serializer_serialize_int(s, mlpbase_mlpfirstversion, _state); + ae_serializer_serialize_bool(s, mlpissoftmax(network, _state), _state); + serializeintegerarray(s, &network->hllayersizes, -1, _state); + for(i=1; i<=network->hllayersizes.cnt-1; i++) + { + for(j=0; j<=network->hllayersizes.ptr.p_int[i]-1; j++) + { + mlpgetneuroninfo(network, i, j, &fkind, &threshold, _state); + ae_serializer_serialize_int(s, fkind, _state); + ae_serializer_serialize_double(s, threshold, _state); + for(k=0; k<=network->hllayersizes.ptr.p_int[i-1]-1; k++) + { + ae_serializer_serialize_double(s, mlpgetweight(network, i-1, k, i, j, _state), _state); + } + } + } + for(j=0; j<=nin-1; j++) + { + mlpgetinputscaling(network, j, &v0, &v1, _state); + ae_serializer_serialize_double(s, v0, _state); + ae_serializer_serialize_double(s, v1, _state); + } + for(j=0; j<=nout-1; j++) + { + mlpgetoutputscaling(network, j, &v0, &v1, _state); + ae_serializer_serialize_double(s, v0, _state); + ae_serializer_serialize_double(s, v1, _state); + } +} + + +/************************************************************************* +Serializer: unserialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpunserialize(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i0; + ae_int_t i1; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t fkind; + double threshold; + double v0; + double v1; + ae_int_t nin; + ae_int_t nout; + ae_bool issoftmax; + ae_vector layersizes; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&layersizes, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of header + */ + ae_serializer_unserialize_int(s, &i0, _state); + ae_assert(i0==getmlpserializationcode(_state), "MLPUnserialize: stream header corrupted", _state); + ae_serializer_unserialize_int(s, &i1, _state); + ae_assert(i1==mlpbase_mlpfirstversion, "MLPUnserialize: stream header corrupted", _state); + + /* + * Create network + */ + ae_serializer_unserialize_bool(s, &issoftmax, _state); + unserializeintegerarray(s, &layersizes, _state); + ae_assert((layersizes.cnt==2||layersizes.cnt==3)||layersizes.cnt==4, "MLPUnserialize: too many hidden layers!", _state); + nin = layersizes.ptr.p_int[0]; + nout = layersizes.ptr.p_int[layersizes.cnt-1]; + if( layersizes.cnt==2 ) + { + if( issoftmax ) + { + mlpcreatec0(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], network, _state); + } + else + { + mlpcreate0(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], network, _state); + } + } + if( layersizes.cnt==3 ) + { + if( issoftmax ) + { + mlpcreatec1(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], network, _state); + } + else + { + mlpcreate1(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], network, _state); + } + } + if( layersizes.cnt==4 ) + { + if( issoftmax ) + { + mlpcreatec2(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], layersizes.ptr.p_int[3], network, _state); + } + else + { + mlpcreate2(layersizes.ptr.p_int[0], layersizes.ptr.p_int[1], layersizes.ptr.p_int[2], layersizes.ptr.p_int[3], network, _state); + } + } + + /* + * Load neurons and weights + */ + for(i=1; i<=layersizes.cnt-1; i++) + { + for(j=0; j<=layersizes.ptr.p_int[i]-1; j++) + { + ae_serializer_unserialize_int(s, &fkind, _state); + ae_serializer_unserialize_double(s, &threshold, _state); + mlpsetneuroninfo(network, i, j, fkind, threshold, _state); + for(k=0; k<=layersizes.ptr.p_int[i-1]-1; k++) + { + ae_serializer_unserialize_double(s, &v0, _state); + mlpsetweight(network, i-1, k, i, j, v0, _state); + } + } + } + + /* + * Load standartizator + */ + for(j=0; j<=nin-1; j++) + { + ae_serializer_unserialize_double(s, &v0, _state); + ae_serializer_unserialize_double(s, &v1, _state); + mlpsetinputscaling(network, j, v0, v1, _state); + } + for(j=0; j<=nout-1; j++) + { + ae_serializer_unserialize_double(s, &v0, _state); + ae_serializer_unserialize_double(s, &v1, _state); + mlpsetoutputscaling(network, j, v0, v1, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Calculation of all types of errors. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, + ae_state *_state) +{ + ae_int_t idx0; + ae_int_t idx1; + ae_int_t idxtype; + + _modelerrors_clear(rep); + + ae_assert(xy->rows>=setsize, "MLPAllErrorsSubset: XY has less than SetSize rows", _state); + if( setsize>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPAllErrorsSubset: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAllErrorsSubset: XY has less than NIn+NOut columns", _state); + } + } + if( subsetsize>=0 ) + { + idx0 = 0; + idx1 = subsetsize; + idxtype = 1; + } + else + { + idx0 = 0; + idx1 = setsize; + idxtype = 0; + } + mlpallerrorsx(network, xy, &network->dummysxy, setsize, 0, subset, idx0, idx1, idxtype, &network->buf, rep, _state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpallerrorssubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, ae_state *_state) +{ + mlpallerrorssubset(network,xy,setsize,subset,subsetsize,rep, _state); +} + + +/************************************************************************* +Calculation of all types of errors on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset given by sparse matrix; + one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, + ae_state *_state) +{ + ae_int_t idx0; + ae_int_t idx1; + ae_int_t idxtype; + + _modelerrors_clear(rep); + + ae_assert(sparseiscrs(xy, _state), "MLPAllErrorsSparseSubset: XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=setsize, "MLPAllErrorsSparseSubset: XY has less than SetSize rows", _state); + if( setsize>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPAllErrorsSparseSubset: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPAllErrorsSparseSubset: XY has less than NIn+NOut columns", _state); + } + } + if( subsetsize>=0 ) + { + idx0 = 0; + idx1 = subsetsize; + idxtype = 1; + } + else + { + idx0 = 0; + idx1 = setsize; + idxtype = 0; + } + mlpallerrorsx(network, &network->dummydxy, xy, setsize, 1, subset, idx0, idx1, idxtype, &network->buf, rep, _state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpallerrorssparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, ae_state *_state) +{ + mlpallerrorssparsesubset(network,xy,setsize,subset,subsetsize,rep, _state); +} + + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_state *_state) +{ + ae_int_t idx0; + ae_int_t idx1; + ae_int_t idxtype; + double result; + + + ae_assert(xy->rows>=setsize, "MLPErrorSubset: XY has less than SetSize rows", _state); + if( setsize>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+1, "MLPErrorSubset: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(xy->cols>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPErrorSubset: XY has less than NIn+NOut columns", _state); + } + } + if( subsetsize>=0 ) + { + idx0 = 0; + idx1 = subsetsize; + idxtype = 1; + } + else + { + idx0 = 0; + idx1 = setsize; + idxtype = 0; + } + mlpallerrorsx(network, xy, &network->dummysxy, setsize, 0, subset, idx0, idx1, idxtype, &network->buf, &network->err, _state); + result = ae_sqr(network->err.rmserror, _state)*(idx1-idx0)*mlpgetoutputscount(network, _state)/2; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlperrorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, ae_state *_state) +{ + return mlperrorsubset(network,xy,setsize,subset,subsetsize, _state); +} + + +/************************************************************************* +Error of the neural network on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + SetSize - real size of XY, SetSize>=0; + it is used when SubsetSize<0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_state *_state) +{ + ae_int_t idx0; + ae_int_t idx1; + ae_int_t idxtype; + double result; + + + ae_assert(sparseiscrs(xy, _state), "MLPErrorSparseSubset: XY is not in CRS format.", _state); + ae_assert(sparsegetnrows(xy, _state)>=setsize, "MLPErrorSparseSubset: XY has less than SetSize rows", _state); + if( setsize>0 ) + { + if( mlpissoftmax(network, _state) ) + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+1, "MLPErrorSparseSubset: XY has less than NIn+1 columns", _state); + } + else + { + ae_assert(sparsegetncols(xy, _state)>=mlpgetinputscount(network, _state)+mlpgetoutputscount(network, _state), "MLPErrorSparseSubset: XY has less than NIn+NOut columns", _state); + } + } + if( subsetsize>=0 ) + { + idx0 = 0; + idx1 = subsetsize; + idxtype = 1; + } + else + { + idx0 = 0; + idx1 = setsize; + idxtype = 0; + } + mlpallerrorsx(network, &network->dummydxy, xy, setsize, 1, subset, idx0, idx1, idxtype, &network->buf, &network->err, _state); + result = ae_sqr(network->err.rmserror, _state)*(idx1-idx0)*mlpgetoutputscount(network, _state)/2; + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +double _pexec_mlperrorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, ae_state *_state) +{ + return mlperrorsparsesubset(network,xy,setsize,subset,subsetsize, _state); +} + + +void mlpallerrorsx(multilayerperceptron* network, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + modelerrors* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t rowsize; + ae_bool iscls; + ae_int_t srcidx; + ae_int_t cstart; + ae_int_t csize; + ae_int_t j; + mlpbuffers *pbuf; + ae_smart_ptr _pbuf; + ae_int_t len0; + ae_int_t len1; + modelerrors rep0; + modelerrors rep1; + + ae_frame_make(_state, &_frame_block); + ae_smart_ptr_init(&_pbuf, (void**)&pbuf, _state, ae_true); + _modelerrors_init(&rep0, _state, ae_true); + _modelerrors_init(&rep1, _state, ae_true); + + ae_assert(datasetsize>=0, "MLPAllErrorsX: SetSize<0", _state); + ae_assert(datasettype==0||datasettype==1, "MLPAllErrorsX: DatasetType is incorrect", _state); + ae_assert(subsettype==0||subsettype==1, "MLPAllErrorsX: SubsetType is incorrect", _state); + + /* + * Determine network properties + */ + mlpproperties(network, &nin, &nout, &wcount, _state); + iscls = mlpissoftmax(network, _state); + + /* + * Split problem. + * + * Splitting problem allows us to reduce effect of single-precision + * arithmetics (SSE-optimized version of MLPChunkedProcess uses single + * precision internally, but converts them to double precision after + * results are exported from HPC buffer to network). Small batches are + * calculated in single precision, results are aggregated in double + * precision, and it allows us to avoid accumulation of errors when + * we process very large batches (tens of thousands of items). + * + * NOTE: it is important to use real arithmetics for ProblemCost + * because ProblemCost may be larger than MAXINT. + */ + if( subset1-subset0>=2*mlpbase_microbatchsize&&ae_fp_greater(inttoreal(subset1-subset0, _state)*inttoreal(wcount, _state),mlpbase_gradbasecasecost) ) + { + splitlength(subset1-subset0, mlpbase_microbatchsize, &len0, &len1, _state); + mlpallerrorsx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0, subset0+len0, subsettype, buf, &rep0, _state); + mlpallerrorsx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0+len0, subset1, subsettype, buf, &rep1, _state); + rep->relclserror = (len0*rep0.relclserror+len1*rep1.relclserror)/(len0+len1); + rep->avgce = (len0*rep0.avgce+len1*rep1.avgce)/(len0+len1); + rep->rmserror = ae_sqrt((len0*ae_sqr(rep0.rmserror, _state)+len1*ae_sqr(rep1.rmserror, _state))/(len0+len1), _state); + rep->avgerror = (len0*rep0.avgerror+len1*rep1.avgerror)/(len0+len1); + rep->avgrelerror = (len0*rep0.avgrelerror+len1*rep1.avgrelerror)/(len0+len1); + ae_frame_leave(_state); + return; + } + + /* + * Retrieve and prepare + */ + ae_shared_pool_retrieve(buf, &_pbuf, _state); + if( iscls ) + { + rowsize = nin+1; + dserrallocate(nout, &pbuf->tmp0, _state); + } + else + { + rowsize = nin+nout; + dserrallocate(-nout, &pbuf->tmp0, _state); + } + + /* + * Processing + */ + hpcpreparechunkedgradient(&network->weights, wcount, mlpntotal(network, _state), nin, nout, pbuf, _state); + cstart = subset0; + while(cstartchunksize, _state)-cstart; + for(j=0; j<=csize-1; j++) + { + srcidx = -1; + if( subsettype==0 ) + { + srcidx = cstart+j; + } + if( subsettype==1 ) + { + srcidx = idx->ptr.p_int[cstart+j]; + } + ae_assert(srcidx>=0, "MLPAllErrorsX: internal error", _state); + if( datasettype==0 ) + { + ae_v_move(&pbuf->xy.ptr.pp_double[j][0], 1, &densexy->ptr.pp_double[srcidx][0], 1, ae_v_len(0,rowsize-1)); + } + if( datasettype==1 ) + { + sparsegetrow(sparsexy, srcidx, &pbuf->xyrow, _state); + ae_v_move(&pbuf->xy.ptr.pp_double[j][0], 1, &pbuf->xyrow.ptr.p_double[0], 1, ae_v_len(0,rowsize-1)); + } + } + + /* + * Unpack XY and process (temporary code, to be replaced by chunked processing) + */ + for(j=0; j<=csize-1; j++) + { + ae_v_move(&pbuf->xy2.ptr.pp_double[j][0], 1, &pbuf->xy.ptr.pp_double[j][0], 1, ae_v_len(0,rowsize-1)); + } + mlpbase_mlpchunkedprocess(network, &pbuf->xy2, 0, csize, &pbuf->batch4buf, &pbuf->hpcbuf, _state); + for(j=0; j<=csize-1; j++) + { + ae_v_move(&pbuf->x.ptr.p_double[0], 1, &pbuf->xy2.ptr.pp_double[j][0], 1, ae_v_len(0,nin-1)); + ae_v_move(&pbuf->y.ptr.p_double[0], 1, &pbuf->xy2.ptr.pp_double[j][nin], 1, ae_v_len(0,nout-1)); + if( iscls ) + { + pbuf->desiredy.ptr.p_double[0] = pbuf->xy.ptr.pp_double[j][nin]; + } + else + { + ae_v_move(&pbuf->desiredy.ptr.p_double[0], 1, &pbuf->xy.ptr.pp_double[j][nin], 1, ae_v_len(0,nout-1)); + } + dserraccumulate(&pbuf->tmp0, &pbuf->y, &pbuf->desiredy, _state); + } + + /* + * Process chunk and advance line pointer + */ + cstart = cstart+pbuf->chunksize; + } + dserrfinish(&pbuf->tmp0, _state); + rep->relclserror = pbuf->tmp0.ptr.p_double[0]; + rep->avgce = pbuf->tmp0.ptr.p_double[1]/ae_log(2, _state); + rep->rmserror = pbuf->tmp0.ptr.p_double[2]; + rep->avgerror = pbuf->tmp0.ptr.p_double[3]; + rep->avgrelerror = pbuf->tmp0.ptr.p_double[4]; + + /* + * Recycle + */ + ae_shared_pool_recycle(buf, &_pbuf, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine: adding new input layer to network +*************************************************************************/ +static void mlpbase_addinputlayer(ae_int_t ncount, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state) +{ + + + lsizes->ptr.p_int[0] = ncount; + ltypes->ptr.p_int[0] = -2; + lconnfirst->ptr.p_int[0] = 0; + lconnlast->ptr.p_int[0] = 0; + *lastproc = 0; +} + + +/************************************************************************* +Internal subroutine: adding new summator layer to network +*************************************************************************/ +static void mlpbase_addbiasedsummatorlayer(ae_int_t ncount, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state) +{ + + + lsizes->ptr.p_int[*lastproc+1] = 1; + ltypes->ptr.p_int[*lastproc+1] = -3; + lconnfirst->ptr.p_int[*lastproc+1] = 0; + lconnlast->ptr.p_int[*lastproc+1] = 0; + lsizes->ptr.p_int[*lastproc+2] = ncount; + ltypes->ptr.p_int[*lastproc+2] = 0; + lconnfirst->ptr.p_int[*lastproc+2] = *lastproc; + lconnlast->ptr.p_int[*lastproc+2] = *lastproc+1; + *lastproc = *lastproc+2; +} + + +/************************************************************************* +Internal subroutine: adding new summator layer to network +*************************************************************************/ +static void mlpbase_addactivationlayer(ae_int_t functype, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state) +{ + + + ae_assert(functype>0||functype==-5, "AddActivationLayer: incorrect function type", _state); + lsizes->ptr.p_int[*lastproc+1] = lsizes->ptr.p_int[*lastproc]; + ltypes->ptr.p_int[*lastproc+1] = functype; + lconnfirst->ptr.p_int[*lastproc+1] = *lastproc; + lconnlast->ptr.p_int[*lastproc+1] = *lastproc; + *lastproc = *lastproc+1; +} + + +/************************************************************************* +Internal subroutine: adding new zero layer to network +*************************************************************************/ +static void mlpbase_addzerolayer(/* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t* lastproc, + ae_state *_state) +{ + + + lsizes->ptr.p_int[*lastproc+1] = 1; + ltypes->ptr.p_int[*lastproc+1] = -4; + lconnfirst->ptr.p_int[*lastproc+1] = 0; + lconnlast->ptr.p_int[*lastproc+1] = 0; + *lastproc = *lastproc+1; +} + + +/************************************************************************* +This routine adds input layer to the high-level description of the network. + +It modifies Network.HLConnections and Network.HLNeurons and assumes that +these arrays have enough place to store data. It accepts following +parameters: + Network - network + ConnIdx - index of the first free entry in the HLConnections + NeuroIdx - index of the first free entry in the HLNeurons + StructInfoIdx- index of the first entry in the low level description + of the current layer (in the StructInfo array) + NIn - number of inputs + +It modified Network and indices. +*************************************************************************/ +static void mlpbase_hladdinputlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t nin, + ae_state *_state) +{ + ae_int_t i; + ae_int_t offs; + + + offs = mlpbase_hlnfieldwidth*(*neuroidx); + for(i=0; i<=nin-1; i++) + { + network->hlneurons.ptr.p_int[offs+0] = 0; + network->hlneurons.ptr.p_int[offs+1] = i; + network->hlneurons.ptr.p_int[offs+2] = -1; + network->hlneurons.ptr.p_int[offs+3] = -1; + offs = offs+mlpbase_hlnfieldwidth; + } + *neuroidx = *neuroidx+nin; + *structinfoidx = *structinfoidx+nin; +} + + +/************************************************************************* +This routine adds output layer to the high-level description of +the network. + +It modifies Network.HLConnections and Network.HLNeurons and assumes that +these arrays have enough place to store data. It accepts following +parameters: + Network - network + ConnIdx - index of the first free entry in the HLConnections + NeuroIdx - index of the first free entry in the HLNeurons + StructInfoIdx- index of the first entry in the low level description + of the current layer (in the StructInfo array) + WeightsIdx - index of the first entry in the Weights array which + corresponds to the current layer + K - current layer index + NPrev - number of neurons in the previous layer + NOut - number of outputs + IsCls - is it classifier network? + IsLinear - is it network with linear output? + +It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx. +*************************************************************************/ +static void mlpbase_hladdoutputlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t* weightsidx, + ae_int_t k, + ae_int_t nprev, + ae_int_t nout, + ae_bool iscls, + ae_bool islinearout, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t neurooffs; + ae_int_t connoffs; + + + ae_assert((iscls&&islinearout)||!iscls, "HLAddOutputLayer: internal error", _state); + neurooffs = mlpbase_hlnfieldwidth*(*neuroidx); + connoffs = mlpbase_hlconnfieldwidth*(*connidx); + if( !iscls ) + { + + /* + * Regression network + */ + for(i=0; i<=nout-1; i++) + { + network->hlneurons.ptr.p_int[neurooffs+0] = k; + network->hlneurons.ptr.p_int[neurooffs+1] = i; + network->hlneurons.ptr.p_int[neurooffs+2] = *structinfoidx+1+nout+i; + network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i; + neurooffs = neurooffs+mlpbase_hlnfieldwidth; + } + for(i=0; i<=nprev-1; i++) + { + for(j=0; j<=nout-1; j++) + { + network->hlconnections.ptr.p_int[connoffs+0] = k-1; + network->hlconnections.ptr.p_int[connoffs+1] = i; + network->hlconnections.ptr.p_int[connoffs+2] = k; + network->hlconnections.ptr.p_int[connoffs+3] = j; + network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1); + connoffs = connoffs+mlpbase_hlconnfieldwidth; + } + } + *connidx = *connidx+nprev*nout; + *neuroidx = *neuroidx+nout; + *structinfoidx = *structinfoidx+2*nout+1; + *weightsidx = *weightsidx+nout*(nprev+1); + } + else + { + + /* + * Classification network + */ + for(i=0; i<=nout-2; i++) + { + network->hlneurons.ptr.p_int[neurooffs+0] = k; + network->hlneurons.ptr.p_int[neurooffs+1] = i; + network->hlneurons.ptr.p_int[neurooffs+2] = -1; + network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i; + neurooffs = neurooffs+mlpbase_hlnfieldwidth; + } + network->hlneurons.ptr.p_int[neurooffs+0] = k; + network->hlneurons.ptr.p_int[neurooffs+1] = i; + network->hlneurons.ptr.p_int[neurooffs+2] = -1; + network->hlneurons.ptr.p_int[neurooffs+3] = -1; + for(i=0; i<=nprev-1; i++) + { + for(j=0; j<=nout-2; j++) + { + network->hlconnections.ptr.p_int[connoffs+0] = k-1; + network->hlconnections.ptr.p_int[connoffs+1] = i; + network->hlconnections.ptr.p_int[connoffs+2] = k; + network->hlconnections.ptr.p_int[connoffs+3] = j; + network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1); + connoffs = connoffs+mlpbase_hlconnfieldwidth; + } + } + *connidx = *connidx+nprev*(nout-1); + *neuroidx = *neuroidx+nout; + *structinfoidx = *structinfoidx+nout+2; + *weightsidx = *weightsidx+(nout-1)*(nprev+1); + } +} + + +/************************************************************************* +This routine adds hidden layer to the high-level description of +the network. + +It modifies Network.HLConnections and Network.HLNeurons and assumes that +these arrays have enough place to store data. It accepts following +parameters: + Network - network + ConnIdx - index of the first free entry in the HLConnections + NeuroIdx - index of the first free entry in the HLNeurons + StructInfoIdx- index of the first entry in the low level description + of the current layer (in the StructInfo array) + WeightsIdx - index of the first entry in the Weights array which + corresponds to the current layer + K - current layer index + NPrev - number of neurons in the previous layer + NCur - number of neurons in the current layer + +It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx. +*************************************************************************/ +static void mlpbase_hladdhiddenlayer(multilayerperceptron* network, + ae_int_t* connidx, + ae_int_t* neuroidx, + ae_int_t* structinfoidx, + ae_int_t* weightsidx, + ae_int_t k, + ae_int_t nprev, + ae_int_t ncur, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t neurooffs; + ae_int_t connoffs; + + + neurooffs = mlpbase_hlnfieldwidth*(*neuroidx); + connoffs = mlpbase_hlconnfieldwidth*(*connidx); + for(i=0; i<=ncur-1; i++) + { + network->hlneurons.ptr.p_int[neurooffs+0] = k; + network->hlneurons.ptr.p_int[neurooffs+1] = i; + network->hlneurons.ptr.p_int[neurooffs+2] = *structinfoidx+1+ncur+i; + network->hlneurons.ptr.p_int[neurooffs+3] = *weightsidx+nprev+(nprev+1)*i; + neurooffs = neurooffs+mlpbase_hlnfieldwidth; + } + for(i=0; i<=nprev-1; i++) + { + for(j=0; j<=ncur-1; j++) + { + network->hlconnections.ptr.p_int[connoffs+0] = k-1; + network->hlconnections.ptr.p_int[connoffs+1] = i; + network->hlconnections.ptr.p_int[connoffs+2] = k; + network->hlconnections.ptr.p_int[connoffs+3] = j; + network->hlconnections.ptr.p_int[connoffs+4] = *weightsidx+i+j*(nprev+1); + connoffs = connoffs+mlpbase_hlconnfieldwidth; + } + } + *connidx = *connidx+nprev*ncur; + *neuroidx = *neuroidx+ncur; + *structinfoidx = *structinfoidx+2*ncur+1; + *weightsidx = *weightsidx+ncur*(nprev+1); +} + + +/************************************************************************* +This function fills high level information about network created using +internal MLPCreate() function. + +This function does NOT examine StructInfo for low level information, it +just expects that network has following structure: + + input neuron \ + ... | input layer + input neuron / + + "-1" neuron \ + biased summator | + ... | + biased summator | hidden layer(s), if there are exists any + activation function | + ... | + activation function / + + "-1" neuron \ + biased summator | output layer: + ... | + biased summator | * we have NOut summators/activators for regression networks + activation function | * we have only NOut-1 summators and no activators for classifiers + ... | * we have "0" neuron only when we have classifier + activation function | + "0" neuron / + + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +static void mlpbase_fillhighlevelinformation(multilayerperceptron* network, + ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_bool iscls, + ae_bool islinearout, + ae_state *_state) +{ + ae_int_t idxweights; + ae_int_t idxstruct; + ae_int_t idxneuro; + ae_int_t idxconn; + + + ae_assert((iscls&&islinearout)||!iscls, "FillHighLevelInformation: internal error", _state); + + /* + * Preparations common to all types of networks + */ + idxweights = 0; + idxneuro = 0; + idxstruct = 0; + idxconn = 0; + network->hlnetworktype = 0; + + /* + * network without hidden layers + */ + if( nhid1==0 ) + { + ae_vector_set_length(&network->hllayersizes, 2, _state); + network->hllayersizes.ptr.p_int[0] = nin; + network->hllayersizes.ptr.p_int[1] = nout; + if( !iscls ) + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*nin*nout, _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nout), _state); + network->hlnormtype = 0; + } + else + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*nin*(nout-1), _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nout), _state); + network->hlnormtype = 1; + } + mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state); + mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nout, iscls, islinearout, _state); + return; + } + + /* + * network with one hidden layers + */ + if( nhid2==0 ) + { + ae_vector_set_length(&network->hllayersizes, 3, _state); + network->hllayersizes.ptr.p_int[0] = nin; + network->hllayersizes.ptr.p_int[1] = nhid1; + network->hllayersizes.ptr.p_int[2] = nout; + if( !iscls ) + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nout), _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nout), _state); + network->hlnormtype = 0; + } + else + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*(nout-1)), _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nout), _state); + network->hlnormtype = 1; + } + mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state); + mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nhid1, _state); + mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 2, nhid1, nout, iscls, islinearout, _state); + return; + } + + /* + * Two hidden layers + */ + ae_vector_set_length(&network->hllayersizes, 4, _state); + network->hllayersizes.ptr.p_int[0] = nin; + network->hllayersizes.ptr.p_int[1] = nhid1; + network->hllayersizes.ptr.p_int[2] = nhid2; + network->hllayersizes.ptr.p_int[3] = nout; + if( !iscls ) + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout), _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nhid2+nout), _state); + network->hlnormtype = 0; + } + else + { + ae_vector_set_length(&network->hlconnections, mlpbase_hlconnfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1)), _state); + ae_vector_set_length(&network->hlneurons, mlpbase_hlnfieldwidth*(nin+nhid1+nhid2+nout), _state); + network->hlnormtype = 1; + } + mlpbase_hladdinputlayer(network, &idxconn, &idxneuro, &idxstruct, nin, _state); + mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 1, nin, nhid1, _state); + mlpbase_hladdhiddenlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 2, nhid1, nhid2, _state); + mlpbase_hladdoutputlayer(network, &idxconn, &idxneuro, &idxstruct, &idxweights, 3, nhid2, nout, iscls, islinearout, _state); +} + + +/************************************************************************* +Internal subroutine. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +static void mlpbase_mlpcreate(ae_int_t nin, + ae_int_t nout, + /* Integer */ ae_vector* lsizes, + /* Integer */ ae_vector* ltypes, + /* Integer */ ae_vector* lconnfirst, + /* Integer */ ae_vector* lconnlast, + ae_int_t layerscount, + ae_bool isclsnet, + multilayerperceptron* network, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t ssize; + ae_int_t ntotal; + ae_int_t wcount; + ae_int_t offs; + ae_int_t nprocessed; + ae_int_t wallocated; + ae_vector localtemp; + ae_vector lnfirst; + ae_vector lnsyn; + mlpbuffers buf; + smlpgrad sgrad; + + ae_frame_make(_state, &_frame_block); + _multilayerperceptron_clear(network); + ae_vector_init(&localtemp, 0, DT_INT, _state, ae_true); + ae_vector_init(&lnfirst, 0, DT_INT, _state, ae_true); + ae_vector_init(&lnsyn, 0, DT_INT, _state, ae_true); + _mlpbuffers_init(&buf, _state, ae_true); + _smlpgrad_init(&sgrad, _state, ae_true); + + + /* + * Check + */ + ae_assert(layerscount>0, "MLPCreate: wrong parameters!", _state); + ae_assert(ltypes->ptr.p_int[0]==-2, "MLPCreate: wrong LTypes[0] (must be -2)!", _state); + for(i=0; i<=layerscount-1; i++) + { + ae_assert(lsizes->ptr.p_int[i]>0, "MLPCreate: wrong LSizes!", _state); + ae_assert(lconnfirst->ptr.p_int[i]>=0&&(lconnfirst->ptr.p_int[i]ptr.p_int[i]>=lconnfirst->ptr.p_int[i]&&(lconnlast->ptr.p_int[i]ptr.p_int[i]>=0||ltypes->ptr.p_int[i]==-5 ) + { + lnsyn.ptr.p_int[i] = 0; + for(j=lconnfirst->ptr.p_int[i]; j<=lconnlast->ptr.p_int[i]; j++) + { + lnsyn.ptr.p_int[i] = lnsyn.ptr.p_int[i]+lsizes->ptr.p_int[j]; + } + } + else + { + if( (ltypes->ptr.p_int[i]==-2||ltypes->ptr.p_int[i]==-3)||ltypes->ptr.p_int[i]==-4 ) + { + lnsyn.ptr.p_int[i] = 0; + } + } + ae_assert(lnsyn.ptr.p_int[i]>=0, "MLPCreate: internal error #0!", _state); + + /* + * Other info + */ + lnfirst.ptr.p_int[i] = ntotal; + ntotal = ntotal+lsizes->ptr.p_int[i]; + if( ltypes->ptr.p_int[i]==0 ) + { + wcount = wcount+lnsyn.ptr.p_int[i]*lsizes->ptr.p_int[i]; + } + } + ssize = 7+ntotal*mlpbase_nfieldwidth; + + /* + * Allocate + */ + ae_vector_set_length(&network->structinfo, ssize-1+1, _state); + ae_vector_set_length(&network->weights, wcount-1+1, _state); + if( isclsnet ) + { + ae_vector_set_length(&network->columnmeans, nin-1+1, _state); + ae_vector_set_length(&network->columnsigmas, nin-1+1, _state); + } + else + { + ae_vector_set_length(&network->columnmeans, nin+nout-1+1, _state); + ae_vector_set_length(&network->columnsigmas, nin+nout-1+1, _state); + } + ae_vector_set_length(&network->neurons, ntotal-1+1, _state); + ae_vector_set_length(&network->nwbuf, ae_maxint(wcount, 2*nout, _state)-1+1, _state); + ae_vector_set_length(&network->integerbuf, 3+1, _state); + ae_vector_set_length(&network->dfdnet, ntotal-1+1, _state); + ae_vector_set_length(&network->x, nin-1+1, _state); + ae_vector_set_length(&network->y, nout-1+1, _state); + ae_vector_set_length(&network->derror, ntotal-1+1, _state); + + /* + * Fill structure: global info + */ + network->structinfo.ptr.p_int[0] = ssize; + network->structinfo.ptr.p_int[1] = nin; + network->structinfo.ptr.p_int[2] = nout; + network->structinfo.ptr.p_int[3] = ntotal; + network->structinfo.ptr.p_int[4] = wcount; + network->structinfo.ptr.p_int[5] = 7; + if( isclsnet ) + { + network->structinfo.ptr.p_int[6] = 1; + } + else + { + network->structinfo.ptr.p_int[6] = 0; + } + + /* + * Fill structure: neuron connections + */ + nprocessed = 0; + wallocated = 0; + for(i=0; i<=layerscount-1; i++) + { + for(j=0; j<=lsizes->ptr.p_int[i]-1; j++) + { + offs = network->structinfo.ptr.p_int[5]+nprocessed*mlpbase_nfieldwidth; + network->structinfo.ptr.p_int[offs+0] = ltypes->ptr.p_int[i]; + if( ltypes->ptr.p_int[i]==0 ) + { + + /* + * Adaptive summator: + * * connections with weights to previous neurons + */ + network->structinfo.ptr.p_int[offs+1] = lnsyn.ptr.p_int[i]; + network->structinfo.ptr.p_int[offs+2] = lnfirst.ptr.p_int[lconnfirst->ptr.p_int[i]]; + network->structinfo.ptr.p_int[offs+3] = wallocated; + wallocated = wallocated+lnsyn.ptr.p_int[i]; + nprocessed = nprocessed+1; + } + if( ltypes->ptr.p_int[i]>0||ltypes->ptr.p_int[i]==-5 ) + { + + /* + * Activation layer: + * * each neuron connected to one (only one) of previous neurons. + * * no weights + */ + network->structinfo.ptr.p_int[offs+1] = 1; + network->structinfo.ptr.p_int[offs+2] = lnfirst.ptr.p_int[lconnfirst->ptr.p_int[i]]+j; + network->structinfo.ptr.p_int[offs+3] = -1; + nprocessed = nprocessed+1; + } + if( (ltypes->ptr.p_int[i]==-2||ltypes->ptr.p_int[i]==-3)||ltypes->ptr.p_int[i]==-4 ) + { + nprocessed = nprocessed+1; + } + } + } + ae_assert(wallocated==wcount, "MLPCreate: internal error #1!", _state); + ae_assert(nprocessed==ntotal, "MLPCreate: internal error #2!", _state); + + /* + * Fill weights by small random values + * Initialize means and sigmas + */ + for(i=0; i<=nin-1; i++) + { + network->columnmeans.ptr.p_double[i] = 0; + network->columnsigmas.ptr.p_double[i] = 1; + } + if( !isclsnet ) + { + for(i=0; i<=nout-1; i++) + { + network->columnmeans.ptr.p_double[nin+i] = 0; + network->columnsigmas.ptr.p_double[nin+i] = 1; + } + } + mlprandomize(network, _state); + + /* + * Seed buffers + */ + ae_shared_pool_set_seed(&network->buf, &buf, sizeof(buf), _mlpbuffers_init, _mlpbuffers_init_copy, _mlpbuffers_destroy, _state); + ae_vector_set_length(&sgrad.g, wcount, _state); + sgrad.f = 0.0; + for(i=0; i<=wcount-1; i++) + { + sgrad.g.ptr.p_double[i] = 0.0; + } + ae_shared_pool_set_seed(&network->gradbuf, &sgrad, sizeof(sgrad), _smlpgrad_init, _smlpgrad_init_copy, _smlpgrad_destroy, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine for Hessian calculation. + +WARNING! Unspeakable math far beyong human capabilities :) +*************************************************************************/ +static void mlpbase_mlphessianbatchinternal(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_bool naturalerr, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t kl; + ae_int_t offs; + ae_int_t n1; + ae_int_t n2; + ae_int_t w1; + ae_int_t w2; + double s; + double t; + double v; + double et; + ae_bool bflag; + double f; + double df; + double d2f; + double deidyj; + double mx; + double q; + double z; + double s2; + double expi; + double expj; + ae_vector x; + ae_vector desiredy; + ae_vector gt; + ae_vector zeros; + ae_matrix rx; + ae_matrix ry; + ae_matrix rdx; + ae_matrix rdy; + + ae_frame_make(_state, &_frame_block); + *e = 0; + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&desiredy, 0, DT_REAL, _state, ae_true); + ae_vector_init(>, 0, DT_REAL, _state, ae_true); + ae_vector_init(&zeros, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&rx, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ry, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&rdx, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&rdy, 0, 0, DT_REAL, _state, ae_true); + + mlpproperties(network, &nin, &nout, &wcount, _state); + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Prepare + */ + ae_vector_set_length(&x, nin-1+1, _state); + ae_vector_set_length(&desiredy, nout-1+1, _state); + ae_vector_set_length(&zeros, wcount-1+1, _state); + ae_vector_set_length(>, wcount-1+1, _state); + ae_matrix_set_length(&rx, ntotal+nout-1+1, wcount-1+1, _state); + ae_matrix_set_length(&ry, ntotal+nout-1+1, wcount-1+1, _state); + ae_matrix_set_length(&rdx, ntotal+nout-1+1, wcount-1+1, _state); + ae_matrix_set_length(&rdy, ntotal+nout-1+1, wcount-1+1, _state); + *e = 0; + for(i=0; i<=wcount-1; i++) + { + zeros.ptr.p_double[i] = 0; + } + ae_v_move(&grad->ptr.p_double[0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + for(i=0; i<=wcount-1; i++) + { + ae_v_move(&h->ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + } + + /* + * Process + */ + for(k=0; k<=ssize-1; k++) + { + + /* + * Process vector with MLPGradN. + * Now Neurons, DFDNET and DError contains results of the last run. + */ + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[k][0], 1, ae_v_len(0,nin-1)); + if( mlpissoftmax(network, _state) ) + { + + /* + * class labels outputs + */ + kl = ae_round(xy->ptr.pp_double[k][nin], _state); + for(i=0; i<=nout-1; i++) + { + if( i==kl ) + { + desiredy.ptr.p_double[i] = 1; + } + else + { + desiredy.ptr.p_double[i] = 0; + } + } + } + else + { + + /* + * real outputs + */ + ae_v_move(&desiredy.ptr.p_double[0], 1, &xy->ptr.pp_double[k][nin], 1, ae_v_len(0,nout-1)); + } + if( naturalerr ) + { + mlpgradn(network, &x, &desiredy, &et, >, _state); + } + else + { + mlpgrad(network, &x, &desiredy, &et, >, _state); + } + + /* + * grad, error + */ + *e = *e+et; + ae_v_add(&grad->ptr.p_double[0], 1, >.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + + /* + * Hessian. + * Forward pass of the R-algorithm + */ + for(i=0; i<=ntotal-1; i++) + { + offs = istart+i*mlpbase_nfieldwidth; + ae_v_move(&rx.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ae_v_move(&ry.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 ) + { + + /* + * Activation function + */ + n1 = network->structinfo.ptr.p_int[offs+2]; + ae_v_move(&rx.ptr.pp_double[i][0], 1, &ry.ptr.pp_double[n1][0], 1, ae_v_len(0,wcount-1)); + v = network->dfdnet.ptr.p_double[i]; + ae_v_moved(&ry.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v); + continue; + } + if( network->structinfo.ptr.p_int[offs+0]==0 ) + { + + /* + * Adaptive summator + */ + n1 = network->structinfo.ptr.p_int[offs+2]; + n2 = n1+network->structinfo.ptr.p_int[offs+1]-1; + w1 = network->structinfo.ptr.p_int[offs+3]; + w2 = w1+network->structinfo.ptr.p_int[offs+1]-1; + for(j=n1; j<=n2; j++) + { + v = network->weights.ptr.p_double[w1+j-n1]; + ae_v_addd(&rx.ptr.pp_double[i][0], 1, &ry.ptr.pp_double[j][0], 1, ae_v_len(0,wcount-1), v); + rx.ptr.pp_double[i][w1+j-n1] = rx.ptr.pp_double[i][w1+j-n1]+network->neurons.ptr.p_double[j]; + } + ae_v_move(&ry.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1)); + continue; + } + if( network->structinfo.ptr.p_int[offs+0]<0 ) + { + bflag = ae_true; + if( network->structinfo.ptr.p_int[offs+0]==-2 ) + { + + /* + * input neuron, left unchanged + */ + bflag = ae_false; + } + if( network->structinfo.ptr.p_int[offs+0]==-3 ) + { + + /* + * "-1" neuron, left unchanged + */ + bflag = ae_false; + } + if( network->structinfo.ptr.p_int[offs+0]==-4 ) + { + + /* + * "0" neuron, left unchanged + */ + bflag = ae_false; + } + ae_assert(!bflag, "MLPHessianNBatch: internal error - unknown neuron type!", _state); + continue; + } + } + + /* + * Hessian. Backward pass of the R-algorithm. + * + * Stage 1. Initialize RDY + */ + for(i=0; i<=ntotal+nout-1; i++) + { + ae_v_move(&rdy.ptr.pp_double[i][0], 1, &zeros.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + } + if( network->structinfo.ptr.p_int[6]==0 ) + { + + /* + * Standardisation. + * + * In context of the Hessian calculation standardisation + * is considered as additional layer with weightless + * activation function: + * + * F(NET) := Sigma*NET + * + * So we add one more layer to forward pass, and + * make forward/backward pass through this layer. + */ + for(i=0; i<=nout-1; i++) + { + n1 = ntotal-nout+i; + n2 = ntotal+i; + + /* + * Forward pass from N1 to N2 + */ + ae_v_move(&rx.ptr.pp_double[n2][0], 1, &ry.ptr.pp_double[n1][0], 1, ae_v_len(0,wcount-1)); + v = network->columnsigmas.ptr.p_double[nin+i]; + ae_v_moved(&ry.ptr.pp_double[n2][0], 1, &rx.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1), v); + + /* + * Initialization of RDY + */ + ae_v_move(&rdy.ptr.pp_double[n2][0], 1, &ry.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1)); + + /* + * Backward pass from N2 to N1: + * 1. Calculate R(dE/dX). + * 2. No R(dE/dWij) is needed since weight of activation neuron + * is fixed to 1. So we can update R(dE/dY) for + * the connected neuron (note that Vij=0, Wij=1) + */ + df = network->columnsigmas.ptr.p_double[nin+i]; + ae_v_moved(&rdx.ptr.pp_double[n2][0], 1, &rdy.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1), df); + ae_v_add(&rdy.ptr.pp_double[n1][0], 1, &rdx.ptr.pp_double[n2][0], 1, ae_v_len(0,wcount-1)); + } + } + else + { + + /* + * Softmax. + * + * Initialize RDY using generalized expression for ei'(yi) + * (see expression (9) from p. 5 of "Fast Exact Multiplication by the Hessian"). + * + * When we are working with softmax network, generalized + * expression for ei'(yi) is used because softmax + * normalization leads to ei, which depends on all y's + */ + if( naturalerr ) + { + + /* + * softmax + cross-entropy. + * We have: + * + * S = sum(exp(yk)), + * ei = sum(trn)*exp(yi)/S-trn_i + * + * j=i: d(ei)/d(yj) = T*exp(yi)*(S-exp(yi))/S^2 + * j<>i: d(ei)/d(yj) = -T*exp(yi)*exp(yj)/S^2 + */ + t = 0; + for(i=0; i<=nout-1; i++) + { + t = t+desiredy.ptr.p_double[i]; + } + mx = network->neurons.ptr.p_double[ntotal-nout]; + for(i=0; i<=nout-1; i++) + { + mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state); + } + s = 0; + for(i=0; i<=nout-1; i++) + { + network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state); + s = s+network->nwbuf.ptr.p_double[i]; + } + for(i=0; i<=nout-1; i++) + { + for(j=0; j<=nout-1; j++) + { + if( j==i ) + { + deidyj = t*network->nwbuf.ptr.p_double[i]*(s-network->nwbuf.ptr.p_double[i])/ae_sqr(s, _state); + ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+i][0], 1, ae_v_len(0,wcount-1), deidyj); + } + else + { + deidyj = -t*network->nwbuf.ptr.p_double[i]*network->nwbuf.ptr.p_double[j]/ae_sqr(s, _state); + ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+j][0], 1, ae_v_len(0,wcount-1), deidyj); + } + } + } + } + else + { + + /* + * For a softmax + squared error we have expression + * far beyond human imagination so we dont even try + * to comment on it. Just enjoy the code... + * + * P.S. That's why "natural error" is called "natural" - + * compact beatiful expressions, fast code.... + */ + mx = network->neurons.ptr.p_double[ntotal-nout]; + for(i=0; i<=nout-1; i++) + { + mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state); + } + s = 0; + s2 = 0; + for(i=0; i<=nout-1; i++) + { + network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state); + s = s+network->nwbuf.ptr.p_double[i]; + s2 = s2+ae_sqr(network->nwbuf.ptr.p_double[i], _state); + } + q = 0; + for(i=0; i<=nout-1; i++) + { + q = q+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])*network->nwbuf.ptr.p_double[i]; + } + for(i=0; i<=nout-1; i++) + { + z = -q+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])*s; + expi = network->nwbuf.ptr.p_double[i]; + for(j=0; j<=nout-1; j++) + { + expj = network->nwbuf.ptr.p_double[j]; + if( j==i ) + { + deidyj = expi/ae_sqr(s, _state)*((z+expi)*(s-2*expi)/s+expi*s2/ae_sqr(s, _state)); + } + else + { + deidyj = expi*expj/ae_sqr(s, _state)*(s2/ae_sqr(s, _state)-2*z/s-(expi+expj)/s+(network->y.ptr.p_double[i]-desiredy.ptr.p_double[i])-(network->y.ptr.p_double[j]-desiredy.ptr.p_double[j])); + } + ae_v_addd(&rdy.ptr.pp_double[ntotal-nout+i][0], 1, &ry.ptr.pp_double[ntotal-nout+j][0], 1, ae_v_len(0,wcount-1), deidyj); + } + } + } + } + + /* + * Hessian. Backward pass of the R-algorithm + * + * Stage 2. Process. + */ + for(i=ntotal-1; i>=0; i--) + { + + /* + * Possible variants: + * 1. Activation function + * 2. Adaptive summator + * 3. Special neuron + */ + offs = istart+i*mlpbase_nfieldwidth; + if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 ) + { + n1 = network->structinfo.ptr.p_int[offs+2]; + + /* + * First, calculate R(dE/dX). + */ + mlpactivationfunction(network->neurons.ptr.p_double[n1], network->structinfo.ptr.p_int[offs+0], &f, &df, &d2f, _state); + v = d2f*network->derror.ptr.p_double[i]; + ae_v_moved(&rdx.ptr.pp_double[i][0], 1, &rdy.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), df); + ae_v_addd(&rdx.ptr.pp_double[i][0], 1, &rx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v); + + /* + * No R(dE/dWij) is needed since weight of activation neuron + * is fixed to 1. + * + * So we can update R(dE/dY) for the connected neuron. + * (note that Vij=0, Wij=1) + */ + ae_v_add(&rdy.ptr.pp_double[n1][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1)); + continue; + } + if( network->structinfo.ptr.p_int[offs+0]==0 ) + { + + /* + * Adaptive summator + */ + n1 = network->structinfo.ptr.p_int[offs+2]; + n2 = n1+network->structinfo.ptr.p_int[offs+1]-1; + w1 = network->structinfo.ptr.p_int[offs+3]; + w2 = w1+network->structinfo.ptr.p_int[offs+1]-1; + + /* + * First, calculate R(dE/dX). + */ + ae_v_move(&rdx.ptr.pp_double[i][0], 1, &rdy.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1)); + + /* + * Then, calculate R(dE/dWij) + */ + for(j=w1; j<=w2; j++) + { + v = network->neurons.ptr.p_double[n1+j-w1]; + ae_v_addd(&h->ptr.pp_double[j][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v); + v = network->derror.ptr.p_double[i]; + ae_v_addd(&h->ptr.pp_double[j][0], 1, &ry.ptr.pp_double[n1+j-w1][0], 1, ae_v_len(0,wcount-1), v); + } + + /* + * And finally, update R(dE/dY) for connected neurons. + */ + for(j=w1; j<=w2; j++) + { + v = network->weights.ptr.p_double[j]; + ae_v_addd(&rdy.ptr.pp_double[n1+j-w1][0], 1, &rdx.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1), v); + rdy.ptr.pp_double[n1+j-w1][j] = rdy.ptr.pp_double[n1+j-w1][j]+network->derror.ptr.p_double[i]; + } + continue; + } + if( network->structinfo.ptr.p_int[offs+0]<0 ) + { + bflag = ae_false; + if( (network->structinfo.ptr.p_int[offs+0]==-2||network->structinfo.ptr.p_int[offs+0]==-3)||network->structinfo.ptr.p_int[offs+0]==-4 ) + { + + /* + * Special neuron type, no back-propagation required + */ + bflag = ae_true; + } + ae_assert(bflag, "MLPHessianNBatch: unknown neuron type!", _state); + continue; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine + +Network must be processed by MLPProcess on X +*************************************************************************/ +static void mlpbase_mlpinternalcalculategradient(multilayerperceptron* network, + /* Real */ ae_vector* neurons, + /* Real */ ae_vector* weights, + /* Real */ ae_vector* derror, + /* Real */ ae_vector* grad, + ae_bool naturalerrorfunc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n1; + ae_int_t n2; + ae_int_t w1; + ae_int_t w2; + ae_int_t ntotal; + ae_int_t istart; + ae_int_t nin; + ae_int_t nout; + ae_int_t offs; + double dedf; + double dfdnet; + double v; + double fown; + double deown; + double net; + double mx; + ae_bool bflag; + + + + /* + * Read network geometry + */ + nin = network->structinfo.ptr.p_int[1]; + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + + /* + * Pre-processing of dError/dOut: + * from dError/dOut(normalized) to dError/dOut(non-normalized) + */ + ae_assert(network->structinfo.ptr.p_int[6]==0||network->structinfo.ptr.p_int[6]==1, "MLPInternalCalculateGradient: unknown normalization type!", _state); + if( network->structinfo.ptr.p_int[6]==1 ) + { + + /* + * Softmax + */ + if( !naturalerrorfunc ) + { + mx = network->neurons.ptr.p_double[ntotal-nout]; + for(i=0; i<=nout-1; i++) + { + mx = ae_maxreal(mx, network->neurons.ptr.p_double[ntotal-nout+i], _state); + } + net = 0; + for(i=0; i<=nout-1; i++) + { + network->nwbuf.ptr.p_double[i] = ae_exp(network->neurons.ptr.p_double[ntotal-nout+i]-mx, _state); + net = net+network->nwbuf.ptr.p_double[i]; + } + v = ae_v_dotproduct(&network->derror.ptr.p_double[ntotal-nout], 1, &network->nwbuf.ptr.p_double[0], 1, ae_v_len(ntotal-nout,ntotal-1)); + for(i=0; i<=nout-1; i++) + { + fown = network->nwbuf.ptr.p_double[i]; + deown = network->derror.ptr.p_double[ntotal-nout+i]; + network->nwbuf.ptr.p_double[nout+i] = (-v+deown*fown+deown*(net-fown))*fown/ae_sqr(net, _state); + } + for(i=0; i<=nout-1; i++) + { + network->derror.ptr.p_double[ntotal-nout+i] = network->nwbuf.ptr.p_double[nout+i]; + } + } + } + else + { + + /* + * Un-standardisation + */ + for(i=0; i<=nout-1; i++) + { + network->derror.ptr.p_double[ntotal-nout+i] = network->derror.ptr.p_double[ntotal-nout+i]*network->columnsigmas.ptr.p_double[nin+i]; + } + } + + /* + * Backpropagation + */ + for(i=ntotal-1; i>=0; i--) + { + + /* + * Extract info + */ + offs = istart+i*mlpbase_nfieldwidth; + if( network->structinfo.ptr.p_int[offs+0]>0||network->structinfo.ptr.p_int[offs+0]==-5 ) + { + + /* + * Activation function + */ + dedf = network->derror.ptr.p_double[i]; + dfdnet = network->dfdnet.ptr.p_double[i]; + derror->ptr.p_double[network->structinfo.ptr.p_int[offs+2]] = derror->ptr.p_double[network->structinfo.ptr.p_int[offs+2]]+dedf*dfdnet; + continue; + } + if( network->structinfo.ptr.p_int[offs+0]==0 ) + { + + /* + * Adaptive summator + */ + n1 = network->structinfo.ptr.p_int[offs+2]; + n2 = n1+network->structinfo.ptr.p_int[offs+1]-1; + w1 = network->structinfo.ptr.p_int[offs+3]; + w2 = w1+network->structinfo.ptr.p_int[offs+1]-1; + dedf = network->derror.ptr.p_double[i]; + dfdnet = 1.0; + v = dedf*dfdnet; + ae_v_moved(&grad->ptr.p_double[w1], 1, &neurons->ptr.p_double[n1], 1, ae_v_len(w1,w2), v); + ae_v_addd(&derror->ptr.p_double[n1], 1, &weights->ptr.p_double[w1], 1, ae_v_len(n1,n2), v); + continue; + } + if( network->structinfo.ptr.p_int[offs+0]<0 ) + { + bflag = ae_false; + if( (network->structinfo.ptr.p_int[offs+0]==-2||network->structinfo.ptr.p_int[offs+0]==-3)||network->structinfo.ptr.p_int[offs+0]==-4 ) + { + + /* + * Special neuron type, no back-propagation required + */ + bflag = ae_true; + } + ae_assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!", _state); + continue; + } + } +} + + +static void mlpbase_mlpchunkedgradient(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + double* e, + ae_bool naturalerrorfunc, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t kl; + ae_int_t ntotal; + ae_int_t nin; + ae_int_t nout; + ae_int_t offs; + double f; + double df; + double d2f; + double v; + double vv; + double s; + double fown; + double deown; + ae_bool bflag; + ae_int_t istart; + ae_int_t entrysize; + ae_int_t dfoffs; + ae_int_t derroroffs; + ae_int_t entryoffs; + ae_int_t neuronidx; + ae_int_t srcentryoffs; + ae_int_t srcneuronidx; + ae_int_t srcweightidx; + ae_int_t neurontype; + ae_int_t nweights; + ae_int_t offs0; + ae_int_t offs1; + ae_int_t offs2; + double v0; + double v1; + double v2; + double v3; + double s0; + double s1; + double s2; + double s3; + ae_int_t chunksize; + + + chunksize = 4; + ae_assert(csize<=chunksize, "MLPChunkedGradient: internal error (CSize>ChunkSize)", _state); + + /* + * Try to use HPC core, if possible + */ + if( hpcchunkedgradient(&network->weights, &network->structinfo, &network->columnmeans, &network->columnsigmas, xy, cstart, csize, batch4buf, hpcbuf, e, naturalerrorfunc, _state) ) + { + return; + } + + /* + * Read network geometry, prepare data + */ + nin = network->structinfo.ptr.p_int[1]; + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + entrysize = 12; + dfoffs = 4; + derroroffs = 8; + + /* + * Fill Batch4Buf by zeros. + * + * THIS STAGE IS VERY IMPORTANT! + * + * We fill all components of entry - neuron values, dF/dNET, dError/dF. + * It allows us to easily handle situations when CSizeptr.p_double[i] = 0; + } + + /* + * Forward pass: + * 1. Load data into Batch4Buf. If CSizecolumnsigmas.ptr.p_double[i],0) ) + { + batch4buf->ptr.p_double[entryoffs+j] = (xy->ptr.pp_double[cstart+j][i]-network->columnmeans.ptr.p_double[i])/network->columnsigmas.ptr.p_double[i]; + } + else + { + batch4buf->ptr.p_double[entryoffs+j] = xy->ptr.pp_double[cstart+j][i]-network->columnmeans.ptr.p_double[i]; + } + } + } + for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++) + { + entryoffs = entrysize*neuronidx; + offs = istart+neuronidx*mlpbase_nfieldwidth; + neurontype = network->structinfo.ptr.p_int[offs+0]; + if( neurontype>0||neurontype==-5 ) + { + + /* + * "activation function" neuron, which takes value of neuron SrcNeuronIdx + * and applies activation function to it. + * + * This neuron has no weights and no tunable parameters. + */ + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+0], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+0] = f; + batch4buf->ptr.p_double[entryoffs+0+dfoffs] = df; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+1], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+1] = f; + batch4buf->ptr.p_double[entryoffs+1+dfoffs] = df; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+2], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+2] = f; + batch4buf->ptr.p_double[entryoffs+2+dfoffs] = df; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+3], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+3] = f; + batch4buf->ptr.p_double[entryoffs+3+dfoffs] = df; + continue; + } + if( neurontype==0 ) + { + + /* + * "adaptive summator" neuron, whose output is a weighted sum of inputs. + * It has weights, but has no activation function. + */ + nweights = network->structinfo.ptr.p_int[offs+1]; + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + srcweightidx = network->structinfo.ptr.p_int[offs+3]; + v0 = 0; + v1 = 0; + v2 = 0; + v3 = 0; + for(j=0; j<=nweights-1; j++) + { + v = network->weights.ptr.p_double[srcweightidx]; + srcweightidx = srcweightidx+1; + v0 = v0+v*batch4buf->ptr.p_double[srcentryoffs+0]; + v1 = v1+v*batch4buf->ptr.p_double[srcentryoffs+1]; + v2 = v2+v*batch4buf->ptr.p_double[srcentryoffs+2]; + v3 = v3+v*batch4buf->ptr.p_double[srcentryoffs+3]; + srcentryoffs = srcentryoffs+entrysize; + } + batch4buf->ptr.p_double[entryoffs+0] = v0; + batch4buf->ptr.p_double[entryoffs+1] = v1; + batch4buf->ptr.p_double[entryoffs+2] = v2; + batch4buf->ptr.p_double[entryoffs+3] = v3; + batch4buf->ptr.p_double[entryoffs+0+dfoffs] = 1; + batch4buf->ptr.p_double[entryoffs+1+dfoffs] = 1; + batch4buf->ptr.p_double[entryoffs+2+dfoffs] = 1; + batch4buf->ptr.p_double[entryoffs+3+dfoffs] = 1; + continue; + } + if( neurontype<0 ) + { + bflag = ae_false; + if( neurontype==-2 ) + { + + /* + * Input neuron, left unchanged + */ + bflag = ae_true; + } + if( neurontype==-3 ) + { + + /* + * "-1" neuron + */ + batch4buf->ptr.p_double[entryoffs+0] = -1; + batch4buf->ptr.p_double[entryoffs+1] = -1; + batch4buf->ptr.p_double[entryoffs+2] = -1; + batch4buf->ptr.p_double[entryoffs+3] = -1; + batch4buf->ptr.p_double[entryoffs+0+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+1+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+2+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+3+dfoffs] = 0; + bflag = ae_true; + } + if( neurontype==-4 ) + { + + /* + * "0" neuron + */ + batch4buf->ptr.p_double[entryoffs+0] = 0; + batch4buf->ptr.p_double[entryoffs+1] = 0; + batch4buf->ptr.p_double[entryoffs+2] = 0; + batch4buf->ptr.p_double[entryoffs+3] = 0; + batch4buf->ptr.p_double[entryoffs+0+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+1+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+2+dfoffs] = 0; + batch4buf->ptr.p_double[entryoffs+3+dfoffs] = 0; + bflag = ae_true; + } + ae_assert(bflag, "MLPChunkedGradient: internal error - unknown neuron type!", _state); + continue; + } + } + + /* + * Intermediate phase between forward and backward passes. + * + * For regression networks: + * * forward pass is completely done (no additional post-processing is + * needed). + * * before starting backward pass, we have to calculate dError/dOut + * for output neurons. We also update error at this phase. + * + * For classification networks: + * * in addition to forward pass we apply SOFTMAX normalization to + * output neurons. + * * after applying normalization, we have to calculate dError/dOut, + * which is calculated in two steps: + * * first, we calculate derivative of error with respect to SOFTMAX + * normalized outputs (normalized dError) + * * then, we calculate derivative of error with respect to values + * of outputs BEFORE normalization was applied to them + */ + ae_assert(network->structinfo.ptr.p_int[6]==0||network->structinfo.ptr.p_int[6]==1, "MLPChunkedGradient: unknown normalization type!", _state); + if( network->structinfo.ptr.p_int[6]==1 ) + { + + /* + * SOFTMAX-normalized network. + * + * First, calculate (V0,V1,V2,V3) - component-wise maximum + * of output neurons. This vector of maximum values will be + * used for normalization of outputs prior to calculating + * exponentials. + * + * NOTE: the only purpose of this stage is to prevent overflow + * during calculation of exponentials. With this stage + * we make sure that all exponentials are calculated + * with non-positive argument. If you load (0,0,0,0) to + * (V0,V1,V2,V3), your program will continue working - + * although with less robustness. + */ + entryoffs = entrysize*(ntotal-nout); + v0 = batch4buf->ptr.p_double[entryoffs+0]; + v1 = batch4buf->ptr.p_double[entryoffs+1]; + v2 = batch4buf->ptr.p_double[entryoffs+2]; + v3 = batch4buf->ptr.p_double[entryoffs+3]; + entryoffs = entryoffs+entrysize; + for(i=1; i<=nout-1; i++) + { + v = batch4buf->ptr.p_double[entryoffs+0]; + if( v>v0 ) + { + v0 = v; + } + v = batch4buf->ptr.p_double[entryoffs+1]; + if( v>v1 ) + { + v1 = v; + } + v = batch4buf->ptr.p_double[entryoffs+2]; + if( v>v2 ) + { + v2 = v; + } + v = batch4buf->ptr.p_double[entryoffs+3]; + if( v>v3 ) + { + v3 = v; + } + entryoffs = entryoffs+entrysize; + } + + /* + * Then, calculate exponentials and place them to part of the + * array which is located past the last entry. We also + * calculate sum of exponentials which will be stored past the + * exponentials. + */ + entryoffs = entrysize*(ntotal-nout); + offs0 = entrysize*ntotal; + s0 = 0; + s1 = 0; + s2 = 0; + s3 = 0; + for(i=0; i<=nout-1; i++) + { + v = ae_exp(batch4buf->ptr.p_double[entryoffs+0]-v0, _state); + s0 = s0+v; + batch4buf->ptr.p_double[offs0+0] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+1]-v1, _state); + s1 = s1+v; + batch4buf->ptr.p_double[offs0+1] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+2]-v2, _state); + s2 = s2+v; + batch4buf->ptr.p_double[offs0+2] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+3]-v3, _state); + s3 = s3+v; + batch4buf->ptr.p_double[offs0+3] = v; + entryoffs = entryoffs+entrysize; + offs0 = offs0+chunksize; + } + offs0 = entrysize*ntotal+2*nout*chunksize; + batch4buf->ptr.p_double[offs0+0] = s0; + batch4buf->ptr.p_double[offs0+1] = s1; + batch4buf->ptr.p_double[offs0+2] = s2; + batch4buf->ptr.p_double[offs0+3] = s3; + + /* + * Now we have: + * * Batch4Buf[0...EntrySize*NTotal-1] stores: + * * NTotal*ChunkSize neuron output values (SOFTMAX normalization + * was not applied to these values), + * * NTotal*ChunkSize values of dF/dNET (derivative of neuron + * output with respect to its input) + * * NTotal*ChunkSize zeros in the elements which correspond to + * dError/dOut (derivative of error with respect to neuron output). + * * Batch4Buf[EntrySize*NTotal...EntrySize*NTotal+ChunkSize*NOut-1] - + * stores exponentials of last NOut neurons. + * * Batch4Buf[EntrySize*NTotal+ChunkSize*NOut-1...EntrySize*NTotal+ChunkSize*2*NOut-1] + * - can be used for temporary calculations + * * Batch4Buf[EntrySize*NTotal+ChunkSize*2*NOut...EntrySize*NTotal+ChunkSize*2*NOut+ChunkSize-1] + * - stores sum-of-exponentials + * + * Block below calculates derivatives of error function with respect + * to non-SOFTMAX-normalized output values of last NOut neurons. + * + * It is quite complicated; we do not describe algebra behind it, + * but if you want you may check it yourself :) + */ + if( naturalerrorfunc ) + { + + /* + * Calculate derivative of error with respect to values of + * output neurons PRIOR TO SOFTMAX NORMALIZATION. Because we + * use natural error function (cross-entropy), we can do so + * very easy. + */ + offs0 = entrysize*ntotal+2*nout*chunksize; + for(k=0; k<=csize-1; k++) + { + s = batch4buf->ptr.p_double[offs0+k]; + kl = ae_round(xy->ptr.pp_double[cstart+k][nin], _state); + offs1 = (ntotal-nout)*entrysize+derroroffs+k; + offs2 = entrysize*ntotal+k; + for(i=0; i<=nout-1; i++) + { + if( i==kl ) + { + v = 1; + } + else + { + v = 0; + } + vv = batch4buf->ptr.p_double[offs2]; + batch4buf->ptr.p_double[offs1] = vv/s-v; + *e = *e+mlpbase_safecrossentropy(v, vv/s, _state); + offs1 = offs1+entrysize; + offs2 = offs2+chunksize; + } + } + } + else + { + + /* + * SOFTMAX normalization makes things very difficult. + * Sorry, we do not dare to describe this esoteric math + * in details. + */ + offs0 = entrysize*ntotal+chunksize*2*nout; + for(k=0; k<=csize-1; k++) + { + s = batch4buf->ptr.p_double[offs0+k]; + kl = ae_round(xy->ptr.pp_double[cstart+k][nin], _state); + vv = 0; + offs1 = entrysize*ntotal+k; + offs2 = entrysize*ntotal+nout*chunksize+k; + for(i=0; i<=nout-1; i++) + { + fown = batch4buf->ptr.p_double[offs1]; + if( i==kl ) + { + deown = fown/s-1; + } + else + { + deown = fown/s; + } + batch4buf->ptr.p_double[offs2] = deown; + vv = vv+deown*fown; + *e = *e+deown*deown/2; + offs1 = offs1+chunksize; + offs2 = offs2+chunksize; + } + offs1 = entrysize*ntotal+k; + offs2 = entrysize*ntotal+nout*chunksize+k; + for(i=0; i<=nout-1; i++) + { + fown = batch4buf->ptr.p_double[offs1]; + deown = batch4buf->ptr.p_double[offs2]; + batch4buf->ptr.p_double[(ntotal-nout+i)*entrysize+derroroffs+k] = (-vv+deown*fown+deown*(s-fown))*fown/ae_sqr(s, _state); + offs1 = offs1+chunksize; + offs2 = offs2+chunksize; + } + } + } + } + else + { + + /* + * Regression network with sum-of-squares function. + * + * For each NOut of last neurons: + * * calculate difference between actual and desired output + * * calculate dError/dOut for this neuron (proportional to difference) + * * store in in last 4 components of entry (these values are used + * to start backpropagation) + * * update error + */ + for(i=0; i<=nout-1; i++) + { + v0 = network->columnsigmas.ptr.p_double[nin+i]; + v1 = network->columnmeans.ptr.p_double[nin+i]; + entryoffs = entrysize*(ntotal-nout+i); + offs0 = entryoffs; + offs1 = entryoffs+derroroffs; + for(j=0; j<=csize-1; j++) + { + v = batch4buf->ptr.p_double[offs0+j]*v0+v1-xy->ptr.pp_double[cstart+j][nin+i]; + batch4buf->ptr.p_double[offs1+j] = v*v0; + *e = *e+v*v/2; + } + } + } + + /* + * Backpropagation + */ + for(neuronidx=ntotal-1; neuronidx>=0; neuronidx--) + { + entryoffs = entrysize*neuronidx; + offs = istart+neuronidx*mlpbase_nfieldwidth; + neurontype = network->structinfo.ptr.p_int[offs+0]; + if( neurontype>0||neurontype==-5 ) + { + + /* + * Activation function + */ + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + offs0 = srcentryoffs+derroroffs; + offs1 = entryoffs+derroroffs; + offs2 = entryoffs+dfoffs; + batch4buf->ptr.p_double[offs0+0] = batch4buf->ptr.p_double[offs0+0]+batch4buf->ptr.p_double[offs1+0]*batch4buf->ptr.p_double[offs2+0]; + batch4buf->ptr.p_double[offs0+1] = batch4buf->ptr.p_double[offs0+1]+batch4buf->ptr.p_double[offs1+1]*batch4buf->ptr.p_double[offs2+1]; + batch4buf->ptr.p_double[offs0+2] = batch4buf->ptr.p_double[offs0+2]+batch4buf->ptr.p_double[offs1+2]*batch4buf->ptr.p_double[offs2+2]; + batch4buf->ptr.p_double[offs0+3] = batch4buf->ptr.p_double[offs0+3]+batch4buf->ptr.p_double[offs1+3]*batch4buf->ptr.p_double[offs2+3]; + continue; + } + if( neurontype==0 ) + { + + /* + * Adaptive summator + */ + nweights = network->structinfo.ptr.p_int[offs+1]; + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + srcweightidx = network->structinfo.ptr.p_int[offs+3]; + v0 = batch4buf->ptr.p_double[entryoffs+derroroffs+0]; + v1 = batch4buf->ptr.p_double[entryoffs+derroroffs+1]; + v2 = batch4buf->ptr.p_double[entryoffs+derroroffs+2]; + v3 = batch4buf->ptr.p_double[entryoffs+derroroffs+3]; + for(j=0; j<=nweights-1; j++) + { + offs0 = srcentryoffs; + offs1 = srcentryoffs+derroroffs; + v = network->weights.ptr.p_double[srcweightidx]; + hpcbuf->ptr.p_double[srcweightidx] = hpcbuf->ptr.p_double[srcweightidx]+batch4buf->ptr.p_double[offs0+0]*v0+batch4buf->ptr.p_double[offs0+1]*v1+batch4buf->ptr.p_double[offs0+2]*v2+batch4buf->ptr.p_double[offs0+3]*v3; + batch4buf->ptr.p_double[offs1+0] = batch4buf->ptr.p_double[offs1+0]+v*v0; + batch4buf->ptr.p_double[offs1+1] = batch4buf->ptr.p_double[offs1+1]+v*v1; + batch4buf->ptr.p_double[offs1+2] = batch4buf->ptr.p_double[offs1+2]+v*v2; + batch4buf->ptr.p_double[offs1+3] = batch4buf->ptr.p_double[offs1+3]+v*v3; + srcentryoffs = srcentryoffs+entrysize; + srcweightidx = srcweightidx+1; + } + continue; + } + if( neurontype<0 ) + { + bflag = ae_false; + if( (neurontype==-2||neurontype==-3)||neurontype==-4 ) + { + + /* + * Special neuron type, no back-propagation required + */ + bflag = ae_true; + } + ae_assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!", _state); + continue; + } + } +} + + +static void mlpbase_mlpchunkedprocess(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t cstart, + ae_int_t csize, + /* Real */ ae_vector* batch4buf, + /* Real */ ae_vector* hpcbuf, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t ntotal; + ae_int_t nin; + ae_int_t nout; + ae_int_t offs; + double f; + double df; + double d2f; + double v; + ae_bool bflag; + ae_int_t istart; + ae_int_t entrysize; + ae_int_t entryoffs; + ae_int_t neuronidx; + ae_int_t srcentryoffs; + ae_int_t srcneuronidx; + ae_int_t srcweightidx; + ae_int_t neurontype; + ae_int_t nweights; + ae_int_t offs0; + double v0; + double v1; + double v2; + double v3; + double s0; + double s1; + double s2; + double s3; + ae_int_t chunksize; + + + chunksize = 4; + ae_assert(csize<=chunksize, "MLPChunkedProcess: internal error (CSize>ChunkSize)", _state); + + /* + * Try to use HPC core, if possible + */ + if( hpcchunkedprocess(&network->weights, &network->structinfo, &network->columnmeans, &network->columnsigmas, xy, cstart, csize, batch4buf, hpcbuf, _state) ) + { + return; + } + + /* + * Read network geometry, prepare data + */ + nin = network->structinfo.ptr.p_int[1]; + nout = network->structinfo.ptr.p_int[2]; + ntotal = network->structinfo.ptr.p_int[3]; + istart = network->structinfo.ptr.p_int[5]; + entrysize = 4; + + /* + * Fill Batch4Buf by zeros. + * + * THIS STAGE IS VERY IMPORTANT! + * + * We fill all components of entry - neuron values, dF/dNET, dError/dF. + * It allows us to easily handle situations when CSizeptr.p_double[i] = 0; + } + + /* + * Forward pass: + * 1. Load data into Batch4Buf. If CSizecolumnsigmas.ptr.p_double[i],0) ) + { + batch4buf->ptr.p_double[entryoffs+j] = (xy->ptr.pp_double[cstart+j][i]-network->columnmeans.ptr.p_double[i])/network->columnsigmas.ptr.p_double[i]; + } + else + { + batch4buf->ptr.p_double[entryoffs+j] = xy->ptr.pp_double[cstart+j][i]-network->columnmeans.ptr.p_double[i]; + } + } + } + for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++) + { + entryoffs = entrysize*neuronidx; + offs = istart+neuronidx*mlpbase_nfieldwidth; + neurontype = network->structinfo.ptr.p_int[offs+0]; + if( neurontype>0||neurontype==-5 ) + { + + /* + * "activation function" neuron, which takes value of neuron SrcNeuronIdx + * and applies activation function to it. + * + * This neuron has no weights and no tunable parameters. + */ + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+0], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+0] = f; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+1], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+1] = f; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+2], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+2] = f; + mlpactivationfunction(batch4buf->ptr.p_double[srcentryoffs+3], neurontype, &f, &df, &d2f, _state); + batch4buf->ptr.p_double[entryoffs+3] = f; + continue; + } + if( neurontype==0 ) + { + + /* + * "adaptive summator" neuron, whose output is a weighted sum of inputs. + * It has weights, but has no activation function. + */ + nweights = network->structinfo.ptr.p_int[offs+1]; + srcneuronidx = network->structinfo.ptr.p_int[offs+2]; + srcentryoffs = entrysize*srcneuronidx; + srcweightidx = network->structinfo.ptr.p_int[offs+3]; + v0 = 0; + v1 = 0; + v2 = 0; + v3 = 0; + for(j=0; j<=nweights-1; j++) + { + v = network->weights.ptr.p_double[srcweightidx]; + srcweightidx = srcweightidx+1; + v0 = v0+v*batch4buf->ptr.p_double[srcentryoffs+0]; + v1 = v1+v*batch4buf->ptr.p_double[srcentryoffs+1]; + v2 = v2+v*batch4buf->ptr.p_double[srcentryoffs+2]; + v3 = v3+v*batch4buf->ptr.p_double[srcentryoffs+3]; + srcentryoffs = srcentryoffs+entrysize; + } + batch4buf->ptr.p_double[entryoffs+0] = v0; + batch4buf->ptr.p_double[entryoffs+1] = v1; + batch4buf->ptr.p_double[entryoffs+2] = v2; + batch4buf->ptr.p_double[entryoffs+3] = v3; + continue; + } + if( neurontype<0 ) + { + bflag = ae_false; + if( neurontype==-2 ) + { + + /* + * Input neuron, left unchanged + */ + bflag = ae_true; + } + if( neurontype==-3 ) + { + + /* + * "-1" neuron + */ + batch4buf->ptr.p_double[entryoffs+0] = -1; + batch4buf->ptr.p_double[entryoffs+1] = -1; + batch4buf->ptr.p_double[entryoffs+2] = -1; + batch4buf->ptr.p_double[entryoffs+3] = -1; + bflag = ae_true; + } + if( neurontype==-4 ) + { + + /* + * "0" neuron + */ + batch4buf->ptr.p_double[entryoffs+0] = 0; + batch4buf->ptr.p_double[entryoffs+1] = 0; + batch4buf->ptr.p_double[entryoffs+2] = 0; + batch4buf->ptr.p_double[entryoffs+3] = 0; + bflag = ae_true; + } + ae_assert(bflag, "MLPChunkedProcess: internal error - unknown neuron type!", _state); + continue; + } + } + + /* + * SOFTMAX normalization or scaling. + */ + ae_assert(network->structinfo.ptr.p_int[6]==0||network->structinfo.ptr.p_int[6]==1, "MLPChunkedProcess: unknown normalization type!", _state); + if( network->structinfo.ptr.p_int[6]==1 ) + { + + /* + * SOFTMAX-normalized network. + * + * First, calculate (V0,V1,V2,V3) - component-wise maximum + * of output neurons. This vector of maximum values will be + * used for normalization of outputs prior to calculating + * exponentials. + * + * NOTE: the only purpose of this stage is to prevent overflow + * during calculation of exponentials. With this stage + * we make sure that all exponentials are calculated + * with non-positive argument. If you load (0,0,0,0) to + * (V0,V1,V2,V3), your program will continue working - + * although with less robustness. + */ + entryoffs = entrysize*(ntotal-nout); + v0 = batch4buf->ptr.p_double[entryoffs+0]; + v1 = batch4buf->ptr.p_double[entryoffs+1]; + v2 = batch4buf->ptr.p_double[entryoffs+2]; + v3 = batch4buf->ptr.p_double[entryoffs+3]; + entryoffs = entryoffs+entrysize; + for(i=1; i<=nout-1; i++) + { + v = batch4buf->ptr.p_double[entryoffs+0]; + if( v>v0 ) + { + v0 = v; + } + v = batch4buf->ptr.p_double[entryoffs+1]; + if( v>v1 ) + { + v1 = v; + } + v = batch4buf->ptr.p_double[entryoffs+2]; + if( v>v2 ) + { + v2 = v; + } + v = batch4buf->ptr.p_double[entryoffs+3]; + if( v>v3 ) + { + v3 = v; + } + entryoffs = entryoffs+entrysize; + } + + /* + * Then, calculate exponentials and place them to part of the + * array which is located past the last entry. We also + * calculate sum of exponentials. + */ + entryoffs = entrysize*(ntotal-nout); + offs0 = entrysize*ntotal; + s0 = 0; + s1 = 0; + s2 = 0; + s3 = 0; + for(i=0; i<=nout-1; i++) + { + v = ae_exp(batch4buf->ptr.p_double[entryoffs+0]-v0, _state); + s0 = s0+v; + batch4buf->ptr.p_double[offs0+0] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+1]-v1, _state); + s1 = s1+v; + batch4buf->ptr.p_double[offs0+1] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+2]-v2, _state); + s2 = s2+v; + batch4buf->ptr.p_double[offs0+2] = v; + v = ae_exp(batch4buf->ptr.p_double[entryoffs+3]-v3, _state); + s3 = s3+v; + batch4buf->ptr.p_double[offs0+3] = v; + entryoffs = entryoffs+entrysize; + offs0 = offs0+chunksize; + } + + /* + * Write SOFTMAX-normalized values to the output array. + */ + offs0 = entrysize*ntotal; + for(i=0; i<=nout-1; i++) + { + if( csize>0 ) + { + xy->ptr.pp_double[cstart+0][nin+i] = batch4buf->ptr.p_double[offs0+0]/s0; + } + if( csize>1 ) + { + xy->ptr.pp_double[cstart+1][nin+i] = batch4buf->ptr.p_double[offs0+1]/s1; + } + if( csize>2 ) + { + xy->ptr.pp_double[cstart+2][nin+i] = batch4buf->ptr.p_double[offs0+2]/s2; + } + if( csize>3 ) + { + xy->ptr.pp_double[cstart+3][nin+i] = batch4buf->ptr.p_double[offs0+3]/s3; + } + offs0 = offs0+chunksize; + } + } + else + { + + /* + * Regression network with sum-of-squares function. + * + * For each NOut of last neurons: + * * calculate difference between actual and desired output + * * calculate dError/dOut for this neuron (proportional to difference) + * * store in in last 4 components of entry (these values are used + * to start backpropagation) + * * update error + */ + for(i=0; i<=nout-1; i++) + { + v0 = network->columnsigmas.ptr.p_double[nin+i]; + v1 = network->columnmeans.ptr.p_double[nin+i]; + entryoffs = entrysize*(ntotal-nout+i); + for(j=0; j<=csize-1; j++) + { + xy->ptr.pp_double[cstart+j][nin+i] = batch4buf->ptr.p_double[entryoffs+j]*v0+v1; + } + } + } +} + + +/************************************************************************* +Returns T*Ln(T/Z), guarded against overflow/underflow. +Internal subroutine. +*************************************************************************/ +static double mlpbase_safecrossentropy(double t, + double z, + ae_state *_state) +{ + double r; + double result; + + + if( ae_fp_eq(t,0) ) + { + result = 0; + } + else + { + if( ae_fp_greater(ae_fabs(z, _state),1) ) + { + + /* + * Shouldn't be the case with softmax, + * but we just want to be sure. + */ + if( ae_fp_eq(t/z,0) ) + { + r = ae_minrealnumber; + } + else + { + r = t/z; + } + } + else + { + + /* + * Normal case + */ + if( ae_fp_eq(z,0)||ae_fp_greater_eq(ae_fabs(t, _state),ae_maxrealnumber*ae_fabs(z, _state)) ) + { + r = ae_maxrealnumber; + } + else + { + r = t/z; + } + } + result = t*ae_log(r, _state); + } + return result; +} + + +/************************************************************************* +This function performs backward pass of neural network randimization: +* it assumes that Network.Weights stores standard deviation of weights + (weights are not generated yet, only their deviations are present) +* it sets deviations of weights which feed NeuronIdx-th neuron to specified value +* it recursively passes to deeper neuron and modifies their weights +* it stops after encountering nonlinear neurons, linear activation function, + input neurons, "0" and "-1" neurons + + -- ALGLIB -- + Copyright 27.06.2013 by Bochkanov Sergey +*************************************************************************/ +static void mlpbase_randomizebackwardpass(multilayerperceptron* network, + ae_int_t neuronidx, + double v, + ae_state *_state) +{ + ae_int_t istart; + ae_int_t neurontype; + ae_int_t n1; + ae_int_t n2; + ae_int_t w1; + ae_int_t w2; + ae_int_t offs; + ae_int_t i; + + + istart = network->structinfo.ptr.p_int[5]; + neurontype = network->structinfo.ptr.p_int[istart+neuronidx*mlpbase_nfieldwidth+0]; + if( neurontype==-2 ) + { + + /* + * Input neuron - stop + */ + return; + } + if( neurontype==-3 ) + { + + /* + * "-1" neuron: stop + */ + return; + } + if( neurontype==-4 ) + { + + /* + * "0" neuron: stop + */ + return; + } + if( neurontype==0 ) + { + + /* + * Adaptive summator neuron: + * * modify deviations of its weights + * * recursively call this function for its inputs + */ + offs = istart+neuronidx*mlpbase_nfieldwidth; + n1 = network->structinfo.ptr.p_int[offs+2]; + n2 = n1+network->structinfo.ptr.p_int[offs+1]-1; + w1 = network->structinfo.ptr.p_int[offs+3]; + w2 = w1+network->structinfo.ptr.p_int[offs+1]-1; + for(i=w1; i<=w2; i++) + { + network->weights.ptr.p_double[i] = v; + } + for(i=n1; i<=n2; i++) + { + mlpbase_randomizebackwardpass(network, i, v, _state); + } + return; + } + if( neurontype==-5 ) + { + + /* + * Linear activation function: stop + */ + return; + } + if( neurontype>0 ) + { + + /* + * Nonlinear activation function: stop + */ + return; + } + ae_assert(ae_false, "RandomizeBackwardPass: unexpected neuron type", _state); +} + + +ae_bool _modelerrors_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + modelerrors *p = (modelerrors*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _modelerrors_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + modelerrors *dst = (modelerrors*)_dst; + modelerrors *src = (modelerrors*)_src; + dst->relclserror = src->relclserror; + dst->avgce = src->avgce; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + return ae_true; +} + + +void _modelerrors_clear(void* _p) +{ + modelerrors *p = (modelerrors*)_p; + ae_touch_ptr((void*)p); +} + + +void _modelerrors_destroy(void* _p) +{ + modelerrors *p = (modelerrors*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _smlpgrad_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + smlpgrad *p = (smlpgrad*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _smlpgrad_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + smlpgrad *dst = (smlpgrad*)_dst; + smlpgrad *src = (smlpgrad*)_src; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _smlpgrad_clear(void* _p) +{ + smlpgrad *p = (smlpgrad*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->g); +} + + +void _smlpgrad_destroy(void* _p) +{ + smlpgrad *p = (smlpgrad*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->g); +} + + +ae_bool _multilayerperceptron_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + multilayerperceptron *p = (multilayerperceptron*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->hllayersizes, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hlconnections, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hlneurons, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->structinfo, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->weights, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->columnmeans, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->columnsigmas, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->neurons, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dfdnet, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->derror, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->xy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xyrow, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->nwbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->integerbuf, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !_modelerrors_init(&p->err, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rndbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->buf, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->gradbuf, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->dummydxy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init(&p->dummysxy, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dummyidx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->dummypool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _multilayerperceptron_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + multilayerperceptron *dst = (multilayerperceptron*)_dst; + multilayerperceptron *src = (multilayerperceptron*)_src; + dst->hlnetworktype = src->hlnetworktype; + dst->hlnormtype = src->hlnormtype; + if( !ae_vector_init_copy(&dst->hllayersizes, &src->hllayersizes, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->hlconnections, &src->hlconnections, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->hlneurons, &src->hlneurons, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->structinfo, &src->structinfo, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->weights, &src->weights, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->columnmeans, &src->columnmeans, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->columnsigmas, &src->columnsigmas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->neurons, &src->neurons, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dfdnet, &src->dfdnet, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->derror, &src->derror, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->xy, &src->xy, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xyrow, &src->xyrow, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->nwbuf, &src->nwbuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->integerbuf, &src->integerbuf, _state, make_automatic) ) + return ae_false; + if( !_modelerrors_init_copy(&dst->err, &src->err, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rndbuf, &src->rndbuf, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init_copy(&dst->buf, &src->buf, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init_copy(&dst->gradbuf, &src->gradbuf, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->dummydxy, &src->dummydxy, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init_copy(&dst->dummysxy, &src->dummysxy, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dummyidx, &src->dummyidx, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init_copy(&dst->dummypool, &src->dummypool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _multilayerperceptron_clear(void* _p) +{ + multilayerperceptron *p = (multilayerperceptron*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->hllayersizes); + ae_vector_clear(&p->hlconnections); + ae_vector_clear(&p->hlneurons); + ae_vector_clear(&p->structinfo); + ae_vector_clear(&p->weights); + ae_vector_clear(&p->columnmeans); + ae_vector_clear(&p->columnsigmas); + ae_vector_clear(&p->neurons); + ae_vector_clear(&p->dfdnet); + ae_vector_clear(&p->derror); + ae_vector_clear(&p->x); + ae_vector_clear(&p->y); + ae_matrix_clear(&p->xy); + ae_vector_clear(&p->xyrow); + ae_vector_clear(&p->nwbuf); + ae_vector_clear(&p->integerbuf); + _modelerrors_clear(&p->err); + ae_vector_clear(&p->rndbuf); + ae_shared_pool_clear(&p->buf); + ae_shared_pool_clear(&p->gradbuf); + ae_matrix_clear(&p->dummydxy); + _sparsematrix_clear(&p->dummysxy); + ae_vector_clear(&p->dummyidx); + ae_shared_pool_clear(&p->dummypool); +} + + +void _multilayerperceptron_destroy(void* _p) +{ + multilayerperceptron *p = (multilayerperceptron*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->hllayersizes); + ae_vector_destroy(&p->hlconnections); + ae_vector_destroy(&p->hlneurons); + ae_vector_destroy(&p->structinfo); + ae_vector_destroy(&p->weights); + ae_vector_destroy(&p->columnmeans); + ae_vector_destroy(&p->columnsigmas); + ae_vector_destroy(&p->neurons); + ae_vector_destroy(&p->dfdnet); + ae_vector_destroy(&p->derror); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->y); + ae_matrix_destroy(&p->xy); + ae_vector_destroy(&p->xyrow); + ae_vector_destroy(&p->nwbuf); + ae_vector_destroy(&p->integerbuf); + _modelerrors_destroy(&p->err); + ae_vector_destroy(&p->rndbuf); + ae_shared_pool_destroy(&p->buf); + ae_shared_pool_destroy(&p->gradbuf); + ae_matrix_destroy(&p->dummydxy); + _sparsematrix_destroy(&p->dummysxy); + ae_vector_destroy(&p->dummyidx); + ae_shared_pool_destroy(&p->dummypool); +} + + + + +/************************************************************************* +This subroutine trains logit model. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars] + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPointsptr.pp_double[i][nvars], _state)<0||ae_round(xy->ptr.pp_double[i][nvars], _state)>=nclasses ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * Initialize data + */ + rep->ngrad = 0; + rep->nhess = 0; + + /* + * Allocate array + */ + offs = 5; + ssize = 5+(nvars+1)*(nclasses-1)+nclasses; + ae_vector_set_length(&lm->w, ssize-1+1, _state); + lm->w.ptr.p_double[0] = ssize; + lm->w.ptr.p_double[1] = logit_logitvnum; + lm->w.ptr.p_double[2] = nvars; + lm->w.ptr.p_double[3] = nclasses; + lm->w.ptr.p_double[4] = offs; + + /* + * Degenerate case: all outputs are equal + */ + allsame = ae_true; + for(i=1; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nvars], _state)!=ae_round(xy->ptr.pp_double[i-1][nvars], _state) ) + { + allsame = ae_false; + } + } + if( allsame ) + { + for(i=0; i<=(nvars+1)*(nclasses-1)-1; i++) + { + lm->w.ptr.p_double[offs+i] = 0; + } + v = -2*ae_log(ae_minrealnumber, _state); + k = ae_round(xy->ptr.pp_double[0][nvars], _state); + if( k==nclasses-1 ) + { + for(i=0; i<=nclasses-2; i++) + { + lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = -v; + } + } + else + { + for(i=0; i<=nclasses-2; i++) + { + if( i==k ) + { + lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = v; + } + else + { + lm->w.ptr.p_double[offs+i*(nvars+1)+nvars] = 0; + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * General case. + * Prepare task and network. Allocate space. + */ + mlpcreatec0(nvars, nclasses, &network, _state); + mlpinitpreprocessor(&network, xy, npoints, _state); + mlpproperties(&network, &nin, &nout, &wcount, _state); + for(i=0; i<=wcount-1; i++) + { + network.weights.ptr.p_double[i] = (2*ae_randomreal(_state)-1)/nvars; + } + ae_vector_set_length(&g, wcount-1+1, _state); + ae_matrix_set_length(&h, wcount-1+1, wcount-1+1, _state); + ae_vector_set_length(&wbase, wcount-1+1, _state); + ae_vector_set_length(&wdir, wcount-1+1, _state); + ae_vector_set_length(&work, wcount-1+1, _state); + + /* + * First stage: optimize in gradient direction. + */ + for(k=0; k<=wcount/3+10; k++) + { + + /* + * Calculate gradient in starting point + */ + mlpgradnbatch(&network, xy, npoints, &e, &g, _state); + v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + rep->ngrad = rep->ngrad+1; + + /* + * Setup optimization scheme + */ + ae_v_moveneg(&wdir.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + v = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + wstep = ae_sqrt(v, _state); + v = 1/ae_sqrt(v, _state); + ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), v); + mcstage = 0; + logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state); + while(mcstage!=0) + { + mlpgradnbatch(&network, xy, npoints, &e, &g, _state); + v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + rep->ngrad = rep->ngrad+1; + logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state); + } + } + + /* + * Second stage: use Hessian when we are close to the minimum + */ + for(;;) + { + + /* + * Calculate and update E/G/H + */ + mlphessiannbatch(&network, xy, npoints, &e, &g, &h, _state); + v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + for(k=0; k<=wcount-1; k++) + { + h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay; + } + rep->nhess = rep->nhess+1; + + /* + * Select step direction + * NOTE: it is important to use lower-triangle Cholesky + * factorization since it is much faster than higher-triangle version. + */ + spd = spdmatrixcholesky(&h, wcount, ae_false, _state); + spdmatrixcholeskysolve(&h, wcount, ae_false, &g, &solverinfo, &solverrep, &wdir, _state); + spd = solverinfo>0; + if( spd ) + { + + /* + * H is positive definite. + * Step in Newton direction. + */ + ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), -1); + spd = ae_true; + } + else + { + + /* + * H is indefinite. + * Step in gradient direction. + */ + ae_v_moveneg(&wdir.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + spd = ae_false; + } + + /* + * Optimize in WDir direction + */ + v = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + wstep = ae_sqrt(v, _state); + v = 1/ae_sqrt(v, _state); + ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), v); + mcstage = 0; + logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state); + while(mcstage!=0) + { + mlpgradnbatch(&network, xy, npoints, &e, &g, _state); + v = ae_v_dotproduct(&network.weights.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + rep->ngrad = rep->ngrad+1; + logit_mnlmcsrch(wcount, &network.weights, &e, &g, &wdir, &wstep, &mcinfo, &mcnfev, &work, &mcstate, &mcstage, _state); + } + if( spd&&((mcinfo==2||mcinfo==4)||mcinfo==6) ) + { + break; + } + } + + /* + * Convert from NN format to MNL format + */ + ae_v_move(&lm->w.ptr.p_double[offs], 1, &network.weights.ptr.p_double[0], 1, ae_v_len(offs,offs+wcount-1)); + for(k=0; k<=nvars-1; k++) + { + for(i=0; i<=nclasses-2; i++) + { + s = network.columnsigmas.ptr.p_double[k]; + if( ae_fp_eq(s,0) ) + { + s = 1; + } + j = offs+(nvars+1)*i; + v = lm->w.ptr.p_double[j+k]; + lm->w.ptr.p_double[j+k] = v/s; + lm->w.ptr.p_double[j+nvars] = lm->w.ptr.p_double[j+nvars]+v*network.columnmeans.ptr.p_double[k]/s; + } + } + for(k=0; k<=nclasses-2; k++) + { + lm->w.ptr.p_double[offs+(nvars+1)*k+nvars] = -lm->w.ptr.p_double[offs+(nvars+1)*k+nvars]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + LM - logit model, passed by non-constant reference + (some fields of structure are used as temporaries + when calculating model output). + X - input vector, array[0..NVars-1]. + Y - (possibly) preallocated buffer; if size of Y is less than + NClasses, it will be reallocated.If it is large enough, it + is NOT reallocated, so we can save some time on reallocation. + +OUTPUT PARAMETERS: + Y - result, array[0..NClasses-1] + Vector of posterior probabilities for classification task. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlprocess(logitmodel* lm, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t offs; + ae_int_t i; + ae_int_t i1; + double s; + + + ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLProcess: unexpected model version", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + nclasses = ae_round(lm->w.ptr.p_double[3], _state); + offs = ae_round(lm->w.ptr.p_double[4], _state); + logit_mnliexp(&lm->w, x, _state); + s = 0; + i1 = offs+(nvars+1)*(nclasses-1); + for(i=i1; i<=i1+nclasses-1; i++) + { + s = s+lm->w.ptr.p_double[i]; + } + if( y->cntptr.p_double[i] = lm->w.ptr.p_double[i1+i]/s; + } +} + + +/************************************************************************* +'interactive' variant of MNLProcess for languages like Python which +support constructs like "Y = MNLProcess(LM,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlprocessi(logitmodel* lm, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + ae_vector_clear(y); + + mnlprocess(lm, x, y, _state); +} + + +/************************************************************************* +Unpacks coefficients of logit model. Logit model have form: + + P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1)) + S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]), when iw.ptr.p_double[1],logit_logitvnum), "MNLUnpack: unexpected model version", _state); + *nvars = ae_round(lm->w.ptr.p_double[2], _state); + *nclasses = ae_round(lm->w.ptr.p_double[3], _state); + offs = ae_round(lm->w.ptr.p_double[4], _state); + ae_matrix_set_length(a, *nclasses-2+1, *nvars+1, _state); + for(i=0; i<=*nclasses-2; i++) + { + ae_v_move(&a->ptr.pp_double[i][0], 1, &lm->w.ptr.p_double[offs+i*(*nvars+1)], 1, ae_v_len(0,*nvars)); + } +} + + +/************************************************************************* +"Packs" coefficients and creates logit model in ALGLIB format (MNLUnpack +reversed). + +INPUT PARAMETERS: + A - model (see MNLUnpack) + NVars - number of independent variables + NClasses - number of classes + +OUTPUT PARAMETERS: + LM - logit model. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +void mnlpack(/* Real */ ae_matrix* a, + ae_int_t nvars, + ae_int_t nclasses, + logitmodel* lm, + ae_state *_state) +{ + ae_int_t offs; + ae_int_t i; + ae_int_t ssize; + + _logitmodel_clear(lm); + + offs = 5; + ssize = 5+(nvars+1)*(nclasses-1)+nclasses; + ae_vector_set_length(&lm->w, ssize-1+1, _state); + lm->w.ptr.p_double[0] = ssize; + lm->w.ptr.p_double[1] = logit_logitvnum; + lm->w.ptr.p_double[2] = nvars; + lm->w.ptr.p_double[3] = nclasses; + lm->w.ptr.p_double[4] = offs; + for(i=0; i<=nclasses-2; i++) + { + ae_v_move(&lm->w.ptr.p_double[offs+i*(nvars+1)], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(offs+i*(nvars+1),offs+i*(nvars+1)+nvars)); + } +} + + +/************************************************************************* +Copying of LogitModel strucure + +INPUT PARAMETERS: + LM1 - original + +OUTPUT PARAMETERS: + LM2 - copy + + -- ALGLIB -- + Copyright 15.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mnlcopy(logitmodel* lm1, logitmodel* lm2, ae_state *_state) +{ + ae_int_t k; + + _logitmodel_clear(lm2); + + k = ae_round(lm1->w.ptr.p_double[0], _state); + ae_vector_set_length(&lm2->w, k-1+1, _state); + ae_v_move(&lm2->w.ptr.p_double[0], 1, &lm1->w.ptr.p_double[0], 1, ae_v_len(0,k-1)); +} + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*ln(2)). + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgce(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t i; + ae_vector workx; + ae_vector worky; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&workx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&worky, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLClsError: unexpected model version", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + nclasses = ae_round(lm->w.ptr.p_double[3], _state); + ae_vector_set_length(&workx, nvars-1+1, _state); + ae_vector_set_length(&worky, nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + ae_assert(ae_round(xy->ptr.pp_double[i][nvars], _state)>=0&&ae_round(xy->ptr.pp_double[i][nvars], _state)ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + mnlprocess(lm, &workx, &worky, _state); + if( ae_fp_greater(worky.ptr.p_double[ae_round(xy->ptr.pp_double[i][nvars], _state)],0) ) + { + result = result-ae_log(worky.ptr.p_double[ae_round(xy->ptr.pp_double[i][nvars], _state)], _state); + } + else + { + result = result-ae_log(ae_minrealnumber, _state); + } + } + result = result/(npoints*ae_log(2, _state)); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlrelclserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double result; + + + result = (double)mnlclserror(lm, xy, npoints, _state)/(double)npoints; + return result; +} + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + root mean square error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlrmserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double relcls; + double avgce; + double rms; + double avg; + double avgrel; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state); + logit_mnlallerrors(lm, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state); + result = rms; + return result; +} + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + average error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgerror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double relcls; + double avgce; + double rms; + double avg; + double avgrel; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state); + logit_mnlallerrors(lm, xy, npoints, &relcls, &avgce, &rms, &avg, &avgrel, _state); + result = avg; + return result; +} + + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + LM - logit model + XY - test set + NPoints - test set size + +RESULT: + average relative error (error when estimating posterior probabilities). + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +double mnlavgrelerror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state) +{ + double relcls; + double avgce; + double rms; + double avg; + double avgrel; + double result; + + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNLRMSError: Incorrect MNL version!", _state); + logit_mnlallerrors(lm, xy, ssize, &relcls, &avgce, &rms, &avg, &avgrel, _state); + result = avgrel; + return result; +} + + +/************************************************************************* +Classification error on test set = MNLRelClsError*NPoints + + -- ALGLIB -- + Copyright 10.09.2008 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mnlclserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t i; + ae_int_t j; + ae_vector workx; + ae_vector worky; + ae_int_t nmax; + ae_int_t result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&workx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&worky, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_eq(lm->w.ptr.p_double[1],logit_logitvnum), "MNLClsError: unexpected model version", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + nclasses = ae_round(lm->w.ptr.p_double[3], _state); + ae_vector_set_length(&workx, nvars-1+1, _state); + ae_vector_set_length(&worky, nclasses-1+1, _state); + result = 0; + for(i=0; i<=npoints-1; i++) + { + + /* + * Process + */ + ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + mnlprocess(lm, &workx, &worky, _state); + + /* + * Logit version of the answer + */ + nmax = 0; + for(j=0; j<=nclasses-1; j++) + { + if( ae_fp_greater(worky.ptr.p_double[j],worky.ptr.p_double[nmax]) ) + { + nmax = j; + } + } + + /* + * compare + */ + if( nmax!=ae_round(xy->ptr.pp_double[i][nvars], _state) ) + { + result = result+1; + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Internal subroutine. Places exponents of the anti-overflow shifted +internal linear outputs into the service part of the W array. +*************************************************************************/ +static void logit_mnliexp(/* Real */ ae_vector* w, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t offs; + ae_int_t i; + ae_int_t i1; + double v; + double mx; + + + ae_assert(ae_fp_eq(w->ptr.p_double[1],logit_logitvnum), "LOGIT: unexpected model version", _state); + nvars = ae_round(w->ptr.p_double[2], _state); + nclasses = ae_round(w->ptr.p_double[3], _state); + offs = ae_round(w->ptr.p_double[4], _state); + i1 = offs+(nvars+1)*(nclasses-1); + for(i=0; i<=nclasses-2; i++) + { + v = ae_v_dotproduct(&w->ptr.p_double[offs+i*(nvars+1)], 1, &x->ptr.p_double[0], 1, ae_v_len(offs+i*(nvars+1),offs+i*(nvars+1)+nvars-1)); + w->ptr.p_double[i1+i] = v+w->ptr.p_double[offs+i*(nvars+1)+nvars]; + } + w->ptr.p_double[i1+nclasses-1] = 0; + mx = 0; + for(i=i1; i<=i1+nclasses-1; i++) + { + mx = ae_maxreal(mx, w->ptr.p_double[i], _state); + } + for(i=i1; i<=i1+nclasses-1; i++) + { + w->ptr.p_double[i] = ae_exp(w->ptr.p_double[i]-mx, _state); + } +} + + +/************************************************************************* +Calculation of all types of errors + + -- ALGLIB -- + Copyright 30.08.2008 by Bochkanov Sergey +*************************************************************************/ +static void logit_mnlallerrors(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double* relcls, + double* avgce, + double* rms, + double* avg, + double* avgrel, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t i; + ae_vector buf; + ae_vector workx; + ae_vector y; + ae_vector dy; + + ae_frame_make(_state, &_frame_block); + *relcls = 0; + *avgce = 0; + *rms = 0; + *avg = 0; + *avgrel = 0; + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&workx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dy, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_round(lm->w.ptr.p_double[1], _state)==logit_logitvnum, "MNL unit: Incorrect MNL version!", _state); + nvars = ae_round(lm->w.ptr.p_double[2], _state); + nclasses = ae_round(lm->w.ptr.p_double[3], _state); + ae_vector_set_length(&workx, nvars-1+1, _state); + ae_vector_set_length(&y, nclasses-1+1, _state); + ae_vector_set_length(&dy, 0+1, _state); + dserrallocate(nclasses, &buf, _state); + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&workx.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + mnlprocess(lm, &workx, &y, _state); + dy.ptr.p_double[0] = xy->ptr.pp_double[i][nvars]; + dserraccumulate(&buf, &y, &dy, _state); + } + dserrfinish(&buf, _state); + *relcls = buf.ptr.p_double[0]; + *avgce = buf.ptr.p_double[1]; + *rms = buf.ptr.p_double[2]; + *avg = buf.ptr.p_double[3]; + *avgrel = buf.ptr.p_double[4]; + ae_frame_leave(_state); +} + + +/************************************************************************* +THE PURPOSE OF MCSRCH IS TO FIND A STEP WHICH SATISFIES A SUFFICIENT +DECREASE CONDITION AND A CURVATURE CONDITION. + +AT EACH STAGE THE SUBROUTINE UPDATES AN INTERVAL OF UNCERTAINTY WITH +ENDPOINTS STX AND STY. THE INTERVAL OF UNCERTAINTY IS INITIALLY CHOSEN +SO THAT IT CONTAINS A MINIMIZER OF THE MODIFIED FUNCTION + + F(X+STP*S) - F(X) - FTOL*STP*(GRADF(X)'S). + +IF A STEP IS OBTAINED FOR WHICH THE MODIFIED FUNCTION HAS A NONPOSITIVE +FUNCTION VALUE AND NONNEGATIVE DERIVATIVE, THEN THE INTERVAL OF +UNCERTAINTY IS CHOSEN SO THAT IT CONTAINS A MINIMIZER OF F(X+STP*S). + +THE ALGORITHM IS DESIGNED TO FIND A STEP WHICH SATISFIES THE SUFFICIENT +DECREASE CONDITION + + F(X+STP*S) .LE. F(X) + FTOL*STP*(GRADF(X)'S), + +AND THE CURVATURE CONDITION + + ABS(GRADF(X+STP*S)'S)) .LE. GTOL*ABS(GRADF(X)'S). + +IF FTOL IS LESS THAN GTOL AND IF, FOR EXAMPLE, THE FUNCTION IS BOUNDED +BELOW, THEN THERE IS ALWAYS A STEP WHICH SATISFIES BOTH CONDITIONS. +IF NO STEP CAN BE FOUND WHICH SATISFIES BOTH CONDITIONS, THEN THE +ALGORITHM USUALLY STOPS WHEN ROUNDING ERRORS PREVENT FURTHER PROGRESS. +IN THIS CASE STP ONLY SATISFIES THE SUFFICIENT DECREASE CONDITION. + +PARAMETERS DESCRIPRION + +N IS A POSITIVE INTEGER INPUT VARIABLE SET TO THE NUMBER OF VARIABLES. + +X IS AN ARRAY OF LENGTH N. ON INPUT IT MUST CONTAIN THE BASE POINT FOR +THE LINE SEARCH. ON OUTPUT IT CONTAINS X+STP*S. + +F IS A VARIABLE. ON INPUT IT MUST CONTAIN THE VALUE OF F AT X. ON OUTPUT +IT CONTAINS THE VALUE OF F AT X + STP*S. + +G IS AN ARRAY OF LENGTH N. ON INPUT IT MUST CONTAIN THE GRADIENT OF F AT X. +ON OUTPUT IT CONTAINS THE GRADIENT OF F AT X + STP*S. + +S IS AN INPUT ARRAY OF LENGTH N WHICH SPECIFIES THE SEARCH DIRECTION. + +STP IS A NONNEGATIVE VARIABLE. ON INPUT STP CONTAINS AN INITIAL ESTIMATE +OF A SATISFACTORY STEP. ON OUTPUT STP CONTAINS THE FINAL ESTIMATE. + +FTOL AND GTOL ARE NONNEGATIVE INPUT VARIABLES. TERMINATION OCCURS WHEN THE +SUFFICIENT DECREASE CONDITION AND THE DIRECTIONAL DERIVATIVE CONDITION ARE +SATISFIED. + +XTOL IS A NONNEGATIVE INPUT VARIABLE. TERMINATION OCCURS WHEN THE RELATIVE +WIDTH OF THE INTERVAL OF UNCERTAINTY IS AT MOST XTOL. + +STPMIN AND STPMAX ARE NONNEGATIVE INPUT VARIABLES WHICH SPECIFY LOWER AND +UPPER BOUNDS FOR THE STEP. + +MAXFEV IS A POSITIVE INTEGER INPUT VARIABLE. TERMINATION OCCURS WHEN THE +NUMBER OF CALLS TO FCN IS AT LEAST MAXFEV BY THE END OF AN ITERATION. + +INFO IS AN INTEGER OUTPUT VARIABLE SET AS FOLLOWS: + INFO = 0 IMPROPER INPUT PARAMETERS. + + INFO = 1 THE SUFFICIENT DECREASE CONDITION AND THE + DIRECTIONAL DERIVATIVE CONDITION HOLD. + + INFO = 2 RELATIVE WIDTH OF THE INTERVAL OF UNCERTAINTY + IS AT MOST XTOL. + + INFO = 3 NUMBER OF CALLS TO FCN HAS REACHED MAXFEV. + + INFO = 4 THE STEP IS AT THE LOWER BOUND STPMIN. + + INFO = 5 THE STEP IS AT THE UPPER BOUND STPMAX. + + INFO = 6 ROUNDING ERRORS PREVENT FURTHER PROGRESS. + THERE MAY NOT BE A STEP WHICH SATISFIES THE + SUFFICIENT DECREASE AND CURVATURE CONDITIONS. + TOLERANCES MAY BE TOO SMALL. + +NFEV IS AN INTEGER OUTPUT VARIABLE SET TO THE NUMBER OF CALLS TO FCN. + +WA IS A WORK ARRAY OF LENGTH N. + +ARGONNE NATIONAL LABORATORY. MINPACK PROJECT. JUNE 1983 +JORGE J. MORE', DAVID J. THUENTE +*************************************************************************/ +static void logit_mnlmcsrch(ae_int_t n, + /* Real */ ae_vector* x, + double* f, + /* Real */ ae_vector* g, + /* Real */ ae_vector* s, + double* stp, + ae_int_t* info, + ae_int_t* nfev, + /* Real */ ae_vector* wa, + logitmcstate* state, + ae_int_t* stage, + ae_state *_state) +{ + double v; + double p5; + double p66; + double zero; + + + + /* + * init + */ + p5 = 0.5; + p66 = 0.66; + state->xtrapf = 4.0; + zero = 0; + + /* + * Main cycle + */ + for(;;) + { + if( *stage==0 ) + { + + /* + * NEXT + */ + *stage = 2; + continue; + } + if( *stage==2 ) + { + state->infoc = 1; + *info = 0; + + /* + * CHECK THE INPUT PARAMETERS FOR ERRORS. + */ + if( ((((((n<=0||ae_fp_less_eq(*stp,0))||ae_fp_less(logit_ftol,0))||ae_fp_less(logit_gtol,zero))||ae_fp_less(logit_xtol,zero))||ae_fp_less(logit_stpmin,zero))||ae_fp_less(logit_stpmax,logit_stpmin))||logit_maxfev<=0 ) + { + *stage = 0; + return; + } + + /* + * COMPUTE THE INITIAL GRADIENT IN THE SEARCH DIRECTION + * AND CHECK THAT S IS A DESCENT DIRECTION. + */ + v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->dginit = v; + if( ae_fp_greater_eq(state->dginit,0) ) + { + *stage = 0; + return; + } + + /* + * INITIALIZE LOCAL VARIABLES. + */ + state->brackt = ae_false; + state->stage1 = ae_true; + *nfev = 0; + state->finit = *f; + state->dgtest = logit_ftol*state->dginit; + state->width = logit_stpmax-logit_stpmin; + state->width1 = state->width/p5; + ae_v_move(&wa->ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * THE VARIABLES STX, FX, DGX CONTAIN THE VALUES OF THE STEP, + * FUNCTION, AND DIRECTIONAL DERIVATIVE AT THE BEST STEP. + * THE VARIABLES STY, FY, DGY CONTAIN THE VALUE OF THE STEP, + * FUNCTION, AND DERIVATIVE AT THE OTHER ENDPOINT OF + * THE INTERVAL OF UNCERTAINTY. + * THE VARIABLES STP, F, DG CONTAIN THE VALUES OF THE STEP, + * FUNCTION, AND DERIVATIVE AT THE CURRENT STEP. + */ + state->stx = 0; + state->fx = state->finit; + state->dgx = state->dginit; + state->sty = 0; + state->fy = state->finit; + state->dgy = state->dginit; + + /* + * NEXT + */ + *stage = 3; + continue; + } + if( *stage==3 ) + { + + /* + * START OF ITERATION. + * + * SET THE MINIMUM AND MAXIMUM STEPS TO CORRESPOND + * TO THE PRESENT INTERVAL OF UNCERTAINTY. + */ + if( state->brackt ) + { + if( ae_fp_less(state->stx,state->sty) ) + { + state->stmin = state->stx; + state->stmax = state->sty; + } + else + { + state->stmin = state->sty; + state->stmax = state->stx; + } + } + else + { + state->stmin = state->stx; + state->stmax = *stp+state->xtrapf*(*stp-state->stx); + } + + /* + * FORCE THE STEP TO BE WITHIN THE BOUNDS STPMAX AND STPMIN. + */ + if( ae_fp_greater(*stp,logit_stpmax) ) + { + *stp = logit_stpmax; + } + if( ae_fp_less(*stp,logit_stpmin) ) + { + *stp = logit_stpmin; + } + + /* + * IF AN UNUSUAL TERMINATION IS TO OCCUR THEN LET + * STP BE THE LOWEST POINT OBTAINED SO FAR. + */ + if( (((state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||*nfev>=logit_maxfev-1)||state->infoc==0)||(state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,logit_xtol*state->stmax)) ) + { + *stp = state->stx; + } + + /* + * EVALUATE THE FUNCTION AND GRADIENT AT STP + * AND COMPUTE THE DIRECTIONAL DERIVATIVE. + */ + ae_v_move(&x->ptr.p_double[0], 1, &wa->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&x->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1), *stp); + + /* + * NEXT + */ + *stage = 4; + return; + } + if( *stage==4 ) + { + *info = 0; + *nfev = *nfev+1; + v = ae_v_dotproduct(&g->ptr.p_double[0], 1, &s->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->dg = v; + state->ftest1 = state->finit+*stp*state->dgtest; + + /* + * TEST FOR CONVERGENCE. + */ + if( (state->brackt&&(ae_fp_less_eq(*stp,state->stmin)||ae_fp_greater_eq(*stp,state->stmax)))||state->infoc==0 ) + { + *info = 6; + } + if( (ae_fp_eq(*stp,logit_stpmax)&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_less_eq(state->dg,state->dgtest) ) + { + *info = 5; + } + if( ae_fp_eq(*stp,logit_stpmin)&&(ae_fp_greater(*f,state->ftest1)||ae_fp_greater_eq(state->dg,state->dgtest)) ) + { + *info = 4; + } + if( *nfev>=logit_maxfev ) + { + *info = 3; + } + if( state->brackt&&ae_fp_less_eq(state->stmax-state->stmin,logit_xtol*state->stmax) ) + { + *info = 2; + } + if( ae_fp_less_eq(*f,state->ftest1)&&ae_fp_less_eq(ae_fabs(state->dg, _state),-logit_gtol*state->dginit) ) + { + *info = 1; + } + + /* + * CHECK FOR TERMINATION. + */ + if( *info!=0 ) + { + *stage = 0; + return; + } + + /* + * IN THE FIRST STAGE WE SEEK A STEP FOR WHICH THE MODIFIED + * FUNCTION HAS A NONPOSITIVE VALUE AND NONNEGATIVE DERIVATIVE. + */ + if( (state->stage1&&ae_fp_less_eq(*f,state->ftest1))&&ae_fp_greater_eq(state->dg,ae_minreal(logit_ftol, logit_gtol, _state)*state->dginit) ) + { + state->stage1 = ae_false; + } + + /* + * A MODIFIED FUNCTION IS USED TO PREDICT THE STEP ONLY IF + * WE HAVE NOT OBTAINED A STEP FOR WHICH THE MODIFIED + * FUNCTION HAS A NONPOSITIVE FUNCTION VALUE AND NONNEGATIVE + * DERIVATIVE, AND IF A LOWER FUNCTION VALUE HAS BEEN + * OBTAINED BUT THE DECREASE IS NOT SUFFICIENT. + */ + if( (state->stage1&&ae_fp_less_eq(*f,state->fx))&&ae_fp_greater(*f,state->ftest1) ) + { + + /* + * DEFINE THE MODIFIED FUNCTION AND DERIVATIVE VALUES. + */ + state->fm = *f-*stp*state->dgtest; + state->fxm = state->fx-state->stx*state->dgtest; + state->fym = state->fy-state->sty*state->dgtest; + state->dgm = state->dg-state->dgtest; + state->dgxm = state->dgx-state->dgtest; + state->dgym = state->dgy-state->dgtest; + + /* + * CALL CSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY + * AND TO COMPUTE THE NEW STEP. + */ + logit_mnlmcstep(&state->stx, &state->fxm, &state->dgxm, &state->sty, &state->fym, &state->dgym, stp, state->fm, state->dgm, &state->brackt, state->stmin, state->stmax, &state->infoc, _state); + + /* + * RESET THE FUNCTION AND GRADIENT VALUES FOR F. + */ + state->fx = state->fxm+state->stx*state->dgtest; + state->fy = state->fym+state->sty*state->dgtest; + state->dgx = state->dgxm+state->dgtest; + state->dgy = state->dgym+state->dgtest; + } + else + { + + /* + * CALL MCSTEP TO UPDATE THE INTERVAL OF UNCERTAINTY + * AND TO COMPUTE THE NEW STEP. + */ + logit_mnlmcstep(&state->stx, &state->fx, &state->dgx, &state->sty, &state->fy, &state->dgy, stp, *f, state->dg, &state->brackt, state->stmin, state->stmax, &state->infoc, _state); + } + + /* + * FORCE A SUFFICIENT DECREASE IN THE SIZE OF THE + * INTERVAL OF UNCERTAINTY. + */ + if( state->brackt ) + { + if( ae_fp_greater_eq(ae_fabs(state->sty-state->stx, _state),p66*state->width1) ) + { + *stp = state->stx+p5*(state->sty-state->stx); + } + state->width1 = state->width; + state->width = ae_fabs(state->sty-state->stx, _state); + } + + /* + * NEXT. + */ + *stage = 3; + continue; + } + } +} + + +static void logit_mnlmcstep(double* stx, + double* fx, + double* dx, + double* sty, + double* fy, + double* dy, + double* stp, + double fp, + double dp, + ae_bool* brackt, + double stmin, + double stmax, + ae_int_t* info, + ae_state *_state) +{ + ae_bool bound; + double gamma; + double p; + double q; + double r; + double s; + double sgnd; + double stpc; + double stpf; + double stpq; + double theta; + + + *info = 0; + + /* + * CHECK THE INPUT PARAMETERS FOR ERRORS. + */ + if( ((*brackt&&(ae_fp_less_eq(*stp,ae_minreal(*stx, *sty, _state))||ae_fp_greater_eq(*stp,ae_maxreal(*stx, *sty, _state))))||ae_fp_greater_eq(*dx*(*stp-(*stx)),0))||ae_fp_less(stmax,stmin) ) + { + return; + } + + /* + * DETERMINE IF THE DERIVATIVES HAVE OPPOSITE SIGN. + */ + sgnd = dp*(*dx/ae_fabs(*dx, _state)); + + /* + * FIRST CASE. A HIGHER FUNCTION VALUE. + * THE MINIMUM IS BRACKETED. IF THE CUBIC STEP IS CLOSER + * TO STX THAN THE QUADRATIC STEP, THE CUBIC STEP IS TAKEN, + * ELSE THE AVERAGE OF THE CUBIC AND QUADRATIC STEPS IS TAKEN. + */ + if( ae_fp_greater(fp,*fx) ) + { + *info = 1; + bound = ae_true; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state); + if( ae_fp_less(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-(*dx)+theta; + q = gamma-(*dx)+gamma+dp; + r = p/q; + stpc = *stx+r*(*stp-(*stx)); + stpq = *stx+*dx/((*fx-fp)/(*stp-(*stx))+(*dx))/2*(*stp-(*stx)); + if( ae_fp_less(ae_fabs(stpc-(*stx), _state),ae_fabs(stpq-(*stx), _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpc+(stpq-stpc)/2; + } + *brackt = ae_true; + } + else + { + if( ae_fp_less(sgnd,0) ) + { + + /* + * SECOND CASE. A LOWER FUNCTION VALUE AND DERIVATIVES OF + * OPPOSITE SIGN. THE MINIMUM IS BRACKETED. IF THE CUBIC + * STEP IS CLOSER TO STX THAN THE QUADRATIC (SECANT) STEP, + * THE CUBIC STEP IS TAKEN, ELSE THE QUADRATIC STEP IS TAKEN. + */ + *info = 2; + bound = ae_false; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state); + if( ae_fp_greater(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma-dp+gamma+(*dx); + r = p/q; + stpc = *stp+r*(*stx-(*stp)); + stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp)); + if( ae_fp_greater(ae_fabs(stpc-(*stp), _state),ae_fabs(stpq-(*stp), _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + *brackt = ae_true; + } + else + { + if( ae_fp_less(ae_fabs(dp, _state),ae_fabs(*dx, _state)) ) + { + + /* + * THIRD CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE + * SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DECREASES. + * THE CUBIC STEP IS ONLY USED IF THE CUBIC TENDS TO INFINITY + * IN THE DIRECTION OF THE STEP OR IF THE MINIMUM OF THE CUBIC + * IS BEYOND STP. OTHERWISE THE CUBIC STEP IS DEFINED TO BE + * EITHER STPMIN OR STPMAX. THE QUADRATIC (SECANT) STEP IS ALSO + * COMPUTED AND IF THE MINIMUM IS BRACKETED THEN THE THE STEP + * CLOSEST TO STX IS TAKEN, ELSE THE STEP FARTHEST AWAY IS TAKEN. + */ + *info = 3; + bound = ae_true; + theta = 3*(*fx-fp)/(*stp-(*stx))+(*dx)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dx, _state), ae_fabs(dp, _state), _state), _state); + + /* + * THE CASE GAMMA = 0 ONLY ARISES IF THE CUBIC DOES NOT TEND + * TO INFINITY IN THE DIRECTION OF THE STEP. + */ + gamma = s*ae_sqrt(ae_maxreal(0, ae_sqr(theta/s, _state)-*dx/s*(dp/s), _state), _state); + if( ae_fp_greater(*stp,*stx) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma+(*dx-dp)+gamma; + r = p/q; + if( ae_fp_less(r,0)&&ae_fp_neq(gamma,0) ) + { + stpc = *stp+r*(*stx-(*stp)); + } + else + { + if( ae_fp_greater(*stp,*stx) ) + { + stpc = stmax; + } + else + { + stpc = stmin; + } + } + stpq = *stp+dp/(dp-(*dx))*(*stx-(*stp)); + if( *brackt ) + { + if( ae_fp_less(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + else + { + if( ae_fp_greater(ae_fabs(*stp-stpc, _state),ae_fabs(*stp-stpq, _state)) ) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + } + else + { + + /* + * FOURTH CASE. A LOWER FUNCTION VALUE, DERIVATIVES OF THE + * SAME SIGN, AND THE MAGNITUDE OF THE DERIVATIVE DOES + * NOT DECREASE. IF THE MINIMUM IS NOT BRACKETED, THE STEP + * IS EITHER STPMIN OR STPMAX, ELSE THE CUBIC STEP IS TAKEN. + */ + *info = 4; + bound = ae_false; + if( *brackt ) + { + theta = 3*(fp-(*fy))/(*sty-(*stp))+(*dy)+dp; + s = ae_maxreal(ae_fabs(theta, _state), ae_maxreal(ae_fabs(*dy, _state), ae_fabs(dp, _state), _state), _state); + gamma = s*ae_sqrt(ae_sqr(theta/s, _state)-*dy/s*(dp/s), _state); + if( ae_fp_greater(*stp,*sty) ) + { + gamma = -gamma; + } + p = gamma-dp+theta; + q = gamma-dp+gamma+(*dy); + r = p/q; + stpc = *stp+r*(*sty-(*stp)); + stpf = stpc; + } + else + { + if( ae_fp_greater(*stp,*stx) ) + { + stpf = stmax; + } + else + { + stpf = stmin; + } + } + } + } + } + + /* + * UPDATE THE INTERVAL OF UNCERTAINTY. THIS UPDATE DOES NOT + * DEPEND ON THE NEW STEP OR THE CASE ANALYSIS ABOVE. + */ + if( ae_fp_greater(fp,*fx) ) + { + *sty = *stp; + *fy = fp; + *dy = dp; + } + else + { + if( ae_fp_less(sgnd,0.0) ) + { + *sty = *stx; + *fy = *fx; + *dy = *dx; + } + *stx = *stp; + *fx = fp; + *dx = dp; + } + + /* + * COMPUTE THE NEW STEP AND SAFEGUARD IT. + */ + stpf = ae_minreal(stmax, stpf, _state); + stpf = ae_maxreal(stmin, stpf, _state); + *stp = stpf; + if( *brackt&&bound ) + { + if( ae_fp_greater(*sty,*stx) ) + { + *stp = ae_minreal(*stx+0.66*(*sty-(*stx)), *stp, _state); + } + else + { + *stp = ae_maxreal(*stx+0.66*(*sty-(*stx)), *stp, _state); + } + } +} + + +ae_bool _logitmodel_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + logitmodel *p = (logitmodel*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->w, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _logitmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + logitmodel *dst = (logitmodel*)_dst; + logitmodel *src = (logitmodel*)_src; + if( !ae_vector_init_copy(&dst->w, &src->w, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _logitmodel_clear(void* _p) +{ + logitmodel *p = (logitmodel*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->w); +} + + +void _logitmodel_destroy(void* _p) +{ + logitmodel *p = (logitmodel*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->w); +} + + +ae_bool _logitmcstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + logitmcstate *p = (logitmcstate*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _logitmcstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + logitmcstate *dst = (logitmcstate*)_dst; + logitmcstate *src = (logitmcstate*)_src; + dst->brackt = src->brackt; + dst->stage1 = src->stage1; + dst->infoc = src->infoc; + dst->dg = src->dg; + dst->dgm = src->dgm; + dst->dginit = src->dginit; + dst->dgtest = src->dgtest; + dst->dgx = src->dgx; + dst->dgxm = src->dgxm; + dst->dgy = src->dgy; + dst->dgym = src->dgym; + dst->finit = src->finit; + dst->ftest1 = src->ftest1; + dst->fm = src->fm; + dst->fx = src->fx; + dst->fxm = src->fxm; + dst->fy = src->fy; + dst->fym = src->fym; + dst->stx = src->stx; + dst->sty = src->sty; + dst->stmin = src->stmin; + dst->stmax = src->stmax; + dst->width = src->width; + dst->width1 = src->width1; + dst->xtrapf = src->xtrapf; + return ae_true; +} + + +void _logitmcstate_clear(void* _p) +{ + logitmcstate *p = (logitmcstate*)_p; + ae_touch_ptr((void*)p); +} + + +void _logitmcstate_destroy(void* _p) +{ + logitmcstate *p = (logitmcstate*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _mnlreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mnlreport *p = (mnlreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _mnlreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mnlreport *dst = (mnlreport*)_dst; + mnlreport *src = (mnlreport*)_src; + dst->ngrad = src->ngrad; + dst->nhess = src->nhess; + return ae_true; +} + + +void _mnlreport_clear(void* _p) +{ + mnlreport *p = (mnlreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _mnlreport_destroy(void* _p) +{ + mnlreport *p = (mnlreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +DESCRIPTION: + +This function creates MCPD (Markov Chains for Population Data) solver. + +This solver can be used to find transition matrix P for N-dimensional +prediction problem where transition from X[i] to X[i+1] is modelled as + X[i+1] = P*X[i] +where X[i] and X[i+1] are N-dimensional population vectors (components of +each X are non-negative), and P is a N*N transition matrix (elements of P +are non-negative, each column sums to 1.0). + +Such models arise when when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is constant, i.e. there is no new individuals and no one + leaves population +* you want to model transitions of individuals from one state into another + +USAGE: + +Here we give very brief outline of the MCPD. We strongly recommend you to +read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on data analysis which is available at http://www.alglib.net/dataanalysis/ + +1. User initializes algorithm state with MCPDCreate() call + +2. User adds one or more tracks - sequences of states which describe + evolution of a system being modelled from different starting conditions + +3. User may add optional boundary, equality and/or linear constraints on + the coefficients of P by calling one of the following functions: + * MCPDSetEC() to set equality constraints + * MCPDSetBC() to set bound constraints + * MCPDSetLC() to set linear constraints + +4. Optionally, user may set custom weights for prediction errors (by + default, algorithm assigns non-equal, automatically chosen weights for + errors in the prediction of different components of X). It can be done + with a call of MCPDSetPredictionWeights() function. + +5. User calls MCPDSolve() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +6. User calls MCPDResults() to get solution + +INPUT PARAMETERS: + N - problem dimension, N>=1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreate(ae_int_t n, mcpdstate* s, ae_state *_state) +{ + + _mcpdstate_clear(s); + + ae_assert(n>=1, "MCPDCreate: N<1", _state); + mcpd_mcpdinit(n, -1, -1, s, _state); +} + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "entry" state and is treated +in a special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +Such conditions basically mean that row of P which corresponds to "entry" +state is zero. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - at every moment of time there is some + (unpredictable) amount of "new" individuals, which can transit into one + of the states at the next turn, but still no one leaves population +* you want to model transitions of individuals from one state into another +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentry(ae_int_t n, + ae_int_t entrystate, + mcpdstate* s, + ae_state *_state) +{ + + _mcpdstate_clear(s); + + ae_assert(n>=2, "MCPDCreateEntry: N<2", _state); + ae_assert(entrystate>=0, "MCPDCreateEntry: EntryState<0", _state); + ae_assert(entrystate=N", _state); + mcpd_mcpdinit(n, entrystate, -1, s, _state); +} + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Exit-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "exit" state and is treated +in a special way: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that column of P which corresponds to +"exit" state is zero. Multiplication by such P may decrease sum of vector +components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - individuals can move into "exit" state + and leave population at the next turn, but there are no new individuals +* amount of individuals which leave population can be predicted +* you want to model transitions of individuals from one state into another + (including transitions into the "exit" state) + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateexit(ae_int_t n, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state) +{ + + _mcpdstate_clear(s); + + ae_assert(n>=2, "MCPDCreateExit: N<2", _state); + ae_assert(exitstate>=0, "MCPDCreateExit: ExitState<0", _state); + ae_assert(exitstate=N", _state); + mcpd_mcpdinit(n, -1, exitstate, s, _state); +} + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-Exit-states" model, i.e. model where transition from X[i] to +X[i+1] is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +one selected component of X[] is called "entry" state and is treated in a +special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +and another one component of X[] is called "exit" state and is treated in +a special way too: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that: + row of P which corresponds to "entry" state is zero + column of P which corresponds to "exit" state is zero +Multiplication by such P may decrease sum of vector components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant +* at every moment of time there is some (unpredictable) amount of "new" + individuals, which can transit into one of the states at the next turn +* some individuals can move (predictably) into "exit" state and leave + population at the next turn +* you want to model transitions of individuals from one state into another, + including transitions from the "entry" state and into the "exit" state. +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentryexit(ae_int_t n, + ae_int_t entrystate, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state) +{ + + _mcpdstate_clear(s); + + ae_assert(n>=2, "MCPDCreateEntryExit: N<2", _state); + ae_assert(entrystate>=0, "MCPDCreateEntryExit: EntryState<0", _state); + ae_assert(entrystate=N", _state); + ae_assert(exitstate>=0, "MCPDCreateEntryExit: ExitState<0", _state); + ae_assert(exitstate=N", _state); + ae_assert(entrystate!=exitstate, "MCPDCreateEntryExit: EntryState=ExitState", _state); + mcpd_mcpdinit(n, entrystate, exitstate, s, _state); +} + + +/************************************************************************* +This function is used to add a track - sequence of system states at the +different moments of its evolution. + +You may add one or several tracks to the MCPD solver. In case you have +several tracks, they won't overwrite each other. For example, if you pass +two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then +solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, +t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it +wont try to model transition from t=A+3 to t=B+1. + +INPUT PARAMETERS: + S - solver + XY - track, array[K,N]: + * I-th row is a state at t=I + * elements of XY must be non-negative (exception will be + thrown on negative elements) + K - number of points in a track + * if given, only leading K rows of XY are used + * if not given, automatically determined from size of XY + +NOTES: + +1. Track may contain either proportional or population data: + * with proportional data all rows of XY must sum to 1.0, i.e. we have + proportions instead of absolute population values + * with population data rows of XY contain population counts and generally + do not sum to 1.0 (although they still must be non-negative) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddtrack(mcpdstate* s, + /* Real */ ae_matrix* xy, + ae_int_t k, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + double s0; + double s1; + + + n = s->n; + ae_assert(k>=0, "MCPDAddTrack: K<0", _state); + ae_assert(xy->cols>=n, "MCPDAddTrack: Cols(XY)rows>=k, "MCPDAddTrack: Rows(XY)ptr.pp_double[i][j],0), "MCPDAddTrack: XY contains negative elements", _state); + } + } + if( k<2 ) + { + return; + } + if( s->data.rowsnpairs+k-1 ) + { + rmatrixresize(&s->data, ae_maxint(2*s->data.rows, s->npairs+k-1, _state), 2*n, _state); + } + for(i=0; i<=k-2; i++) + { + s0 = 0; + s1 = 0; + for(j=0; j<=n-1; j++) + { + if( s->states.ptr.p_int[j]>=0 ) + { + s0 = s0+xy->ptr.pp_double[i][j]; + } + if( s->states.ptr.p_int[j]<=0 ) + { + s1 = s1+xy->ptr.pp_double[i+1][j]; + } + } + if( ae_fp_greater(s0,0)&&ae_fp_greater(s1,0) ) + { + for(j=0; j<=n-1; j++) + { + if( s->states.ptr.p_int[j]>=0 ) + { + s->data.ptr.pp_double[s->npairs][j] = xy->ptr.pp_double[i][j]/s0; + } + else + { + s->data.ptr.pp_double[s->npairs][j] = 0.0; + } + if( s->states.ptr.p_int[j]<=0 ) + { + s->data.ptr.pp_double[s->npairs][n+j] = xy->ptr.pp_double[i+1][j]/s1; + } + else + { + s->data.ptr.pp_double[s->npairs][n+j] = 0.0; + } + } + s->npairs = s->npairs+1; + } + } +} + + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place equality constraints on arbitrary +subset of elements of P. Set of constraints is specified by EC, which may +contain either NAN's or finite numbers from [0,1]. NAN denotes absence of +constraint, finite number denotes equality constraint on specific element +of P. + +You can also use MCPDAddEC() function which allows to ADD equality +constraint for one element of P without changing constraints for other +elements. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + EC - equality constraints, array[N,N]. Elements of EC can be + either NAN's or finite numbers from [0,1]. NAN denotes + absence of constraints, while finite value denotes + equality constraint on the corresponding element of P. + +NOTES: + +1. infinite values of EC will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetec(mcpdstate* s, + /* Real */ ae_matrix* ec, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + + + n = s->n; + ae_assert(ec->cols>=n, "MCPDSetEC: Cols(EC)rows>=n, "MCPDSetEC: Rows(EC)ptr.pp_double[i][j], _state)||ae_isnan(ec->ptr.pp_double[i][j], _state), "MCPDSetEC: EC containts infinite elements", _state); + s->ec.ptr.pp_double[i][j] = ec->ptr.pp_double[i][j]; + } + } +} + + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD equality constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetEC() function which allows you to specify +arbitrary set of equality constraints in one call. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + I - row index of element being constrained + J - column index of element being constrained + C - value (constraint for P[I,J]). Can be either NAN (no + constraint) or finite value from [0,1]. + +NOTES: + +1. infinite values of C will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddec(mcpdstate* s, + ae_int_t i, + ae_int_t j, + double c, + ae_state *_state) +{ + + + ae_assert(i>=0, "MCPDAddEC: I<0", _state); + ae_assert(in, "MCPDAddEC: I>=N", _state); + ae_assert(j>=0, "MCPDAddEC: J<0", _state); + ae_assert(jn, "MCPDAddEC: J>=N", _state); + ae_assert(ae_isnan(c, _state)||ae_isfinite(c, _state), "MCPDAddEC: C is not finite number or NAN", _state); + s->ec.ptr.pp_double[i][j] = c; +} + + +/************************************************************************* +This function is used to add bound constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place bound constraints on arbitrary +subset of elements of P. Set of constraints is specified by BndL/BndU +matrices, which may contain arbitrary combination of finite numbers or +infinities (like -INFn; + ae_assert(bndl->cols>=n, "MCPDSetBC: Cols(BndL)rows>=n, "MCPDSetBC: Rows(BndL)cols>=n, "MCPDSetBC: Cols(BndU)rows>=n, "MCPDSetBC: Rows(BndU)ptr.pp_double[i][j], _state)||ae_isneginf(bndl->ptr.pp_double[i][j], _state), "MCPDSetBC: BndL containts NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.pp_double[i][j], _state)||ae_isposinf(bndu->ptr.pp_double[i][j], _state), "MCPDSetBC: BndU containts NAN or -INF", _state); + s->bndl.ptr.pp_double[i][j] = bndl->ptr.pp_double[i][j]; + s->bndu.ptr.pp_double[i][j] = bndu->ptr.pp_double[i][j]; + } + } +} + + +/************************************************************************* +This function is used to add bound constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD bound constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetBC() function which allows to place bound +constraints on arbitrary subset of elements of P. Set of constraints is +specified by BndL/BndU matrices, which may contain arbitrary combination +of finite numbers or infinities (like -INF=0, "MCPDAddBC: I<0", _state); + ae_assert(in, "MCPDAddBC: I>=N", _state); + ae_assert(j>=0, "MCPDAddBC: J<0", _state); + ae_assert(jn, "MCPDAddBC: J>=N", _state); + ae_assert(ae_isfinite(bndl, _state)||ae_isneginf(bndl, _state), "MCPDAddBC: BndL is NAN or +INF", _state); + ae_assert(ae_isfinite(bndu, _state)||ae_isposinf(bndu, _state), "MCPDAddBC: BndU is NAN or -INF", _state); + s->bndl.ptr.pp_double[i][j] = bndl; + s->bndu.ptr.pp_double[i][j] = bndu; +} + + +/************************************************************************* +This function is used to set linear equality/inequality constraints on the +elements of the transition matrix P. + +This function can be used to set one or several general linear constraints +on the elements of P. Two types of constraints are supported: +* equality constraints +* inequality constraints (both less-or-equal and greater-or-equal) + +Coefficients of constraints are specified by matrix C (one of the +parameters). One row of C corresponds to one constraint. Because +transition matrix P has N*N elements, we need N*N columns to store all +coefficients (they are stored row by row), and one more column to store +right part - hence C has N*N+1 columns. Constraint kind is stored in the +CT array. + +Thus, I-th linear constraint is + P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] + + + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... + + + P[N-1,N-1]*C[I,N*N-1] ?=? C[I,N*N] +where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" (CT[i]>0). + +Your constraint may involve only some subset of P (less than N*N elements). +For example it can be something like + P[0,0] + P[0,1] = 0.5 +In this case you still should pass matrix with N*N+1 columns, but all its +elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero. + +INPUT PARAMETERS: + S - solver + C - array[K,N*N+1] - coefficients of constraints + (see above for complete description) + CT - array[K] - constraint types + (see above for complete description) + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetlc(mcpdstate* s, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + + + n = s->n; + ae_assert(c->cols>=n*n+1, "MCPDSetLC: Cols(C)rows>=k, "MCPDSetLC: Rows(C)cnt>=k, "MCPDSetLC: Len(CT)c, k, n*n+1, _state); + ivectorsetlengthatleast(&s->ct, k, _state); + for(i=0; i<=k-1; i++) + { + for(j=0; j<=n*n; j++) + { + s->c.ptr.pp_double[i][j] = c->ptr.pp_double[i][j]; + } + s->ct.ptr.p_int[i] = ct->ptr.p_int[i]; + } + s->ccnt = k; +} + + +/************************************************************************* +This function allows to tune amount of Tikhonov regularization being +applied to your problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change coefficient r. You can also change +prior values with MCPDSetPrior() function. + +INPUT PARAMETERS: + S - solver + V - regularization coefficient, finite non-negative value. It + is not recommended to specify zero value unless you are + pretty sure that you want it. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsettikhonovregularizer(mcpdstate* s, double v, ae_state *_state) +{ + + + ae_assert(ae_isfinite(v, _state), "MCPDSetTikhonovRegularizer: V is infinite or NAN", _state); + ae_assert(ae_fp_greater_eq(v,0.0), "MCPDSetTikhonovRegularizer: V is less than zero", _state); + s->regterm = v; +} + + +/************************************************************************* +This function allows to set prior values used for regularization of your +problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change prior values prior_P. You can also +change r with MCPDSetTikhonovRegularizer() function. + +INPUT PARAMETERS: + S - solver + PP - array[N,N], matrix of prior values: + 1. elements must be real numbers from [0,1] + 2. columns must sum to 1.0. + First property is checked (exception is thrown otherwise), + while second one is not checked/enforced. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetprior(mcpdstate* s, + /* Real */ ae_matrix* pp, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _pp; + ae_int_t i; + ae_int_t j; + ae_int_t n; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_pp, pp, _state, ae_true); + pp = &_pp; + + n = s->n; + ae_assert(pp->cols>=n, "MCPDSetPrior: Cols(PP)rows>=n, "MCPDSetPrior: Rows(PP)ptr.pp_double[i][j], _state), "MCPDSetPrior: PP containts infinite elements", _state); + ae_assert(ae_fp_greater_eq(pp->ptr.pp_double[i][j],0.0)&&ae_fp_less_eq(pp->ptr.pp_double[i][j],1.0), "MCPDSetPrior: PP[i,j] is less than 0.0 or greater than 1.0", _state); + s->priorp.ptr.pp_double[i][j] = pp->ptr.pp_double[i][j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function is used to change prediction weights + +MCPD solver scales prediction errors as follows + Error(P) = ||W*(y-P*x)||^2 +where + x is a system state at time t + y is a system state at time t+1 + P is a transition matrix + W is a diagonal scaling matrix + +By default, weights are chosen in order to minimize relative prediction +error instead of absolute one. For example, if one component of state is +about 0.5 in magnitude and another one is about 0.05, then algorithm will +make corresponding weights equal to 2.0 and 20.0. + +INPUT PARAMETERS: + S - solver + PW - array[N], weights: + * must be non-negative values (exception will be thrown otherwise) + * zero values will be replaced by automatically chosen values + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetpredictionweights(mcpdstate* s, + /* Real */ ae_vector* pw, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + + + n = s->n; + ae_assert(pw->cnt>=n, "MCPDSetPredictionWeights: Length(PW)ptr.p_double[i], _state), "MCPDSetPredictionWeights: PW containts infinite or NAN elements", _state); + ae_assert(ae_fp_greater_eq(pw->ptr.p_double[i],0), "MCPDSetPredictionWeights: PW containts negative elements", _state); + s->pw.ptr.p_double[i] = pw->ptr.p_double[i]; + } +} + + +/************************************************************************* +This function is used to start solution of the MCPD problem. + +After return from this function, you can use MCPDResults() to get solution +and completion code. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsolve(mcpdstate* s, ae_state *_state) +{ + ae_int_t n; + ae_int_t npairs; + ae_int_t ccnt; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t k2; + double v; + double vv; + + + n = s->n; + npairs = s->npairs; + + /* + * init fields of S + */ + s->repterminationtype = 0; + s->repinneriterationscount = 0; + s->repouteriterationscount = 0; + s->repnfev = 0; + for(k=0; k<=n-1; k++) + { + for(k2=0; k2<=n-1; k2++) + { + s->p.ptr.pp_double[k][k2] = _state->v_nan; + } + } + + /* + * Generate "effective" weights for prediction and calculate preconditioner + */ + for(i=0; i<=n-1; i++) + { + if( ae_fp_eq(s->pw.ptr.p_double[i],0) ) + { + v = 0; + k = 0; + for(j=0; j<=npairs-1; j++) + { + if( ae_fp_neq(s->data.ptr.pp_double[j][n+i],0) ) + { + v = v+s->data.ptr.pp_double[j][n+i]; + k = k+1; + } + } + if( k!=0 ) + { + s->effectivew.ptr.p_double[i] = k/v; + } + else + { + s->effectivew.ptr.p_double[i] = 1.0; + } + } + else + { + s->effectivew.ptr.p_double[i] = s->pw.ptr.p_double[i]; + } + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->h.ptr.p_double[i*n+j] = 2*s->regterm; + } + } + for(k=0; k<=npairs-1; k++) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->h.ptr.p_double[i*n+j] = s->h.ptr.p_double[i*n+j]+2*ae_sqr(s->effectivew.ptr.p_double[i], _state)*ae_sqr(s->data.ptr.pp_double[k][j], _state); + } + } + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( ae_fp_eq(s->h.ptr.p_double[i*n+j],0) ) + { + s->h.ptr.p_double[i*n+j] = 1; + } + } + } + + /* + * Generate "effective" BndL/BndU + */ + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + + /* + * Set default boundary constraints. + * Lower bound is always zero, upper bound is calculated + * with respect to entry/exit states. + */ + s->effectivebndl.ptr.p_double[i*n+j] = 0.0; + if( s->states.ptr.p_int[i]>0||s->states.ptr.p_int[j]<0 ) + { + s->effectivebndu.ptr.p_double[i*n+j] = 0.0; + } + else + { + s->effectivebndu.ptr.p_double[i*n+j] = 1.0; + } + + /* + * Calculate intersection of the default and user-specified bound constraints. + * This code checks consistency of such combination. + */ + if( ae_isfinite(s->bndl.ptr.pp_double[i][j], _state)&&ae_fp_greater(s->bndl.ptr.pp_double[i][j],s->effectivebndl.ptr.p_double[i*n+j]) ) + { + s->effectivebndl.ptr.p_double[i*n+j] = s->bndl.ptr.pp_double[i][j]; + } + if( ae_isfinite(s->bndu.ptr.pp_double[i][j], _state)&&ae_fp_less(s->bndu.ptr.pp_double[i][j],s->effectivebndu.ptr.p_double[i*n+j]) ) + { + s->effectivebndu.ptr.p_double[i*n+j] = s->bndu.ptr.pp_double[i][j]; + } + if( ae_fp_greater(s->effectivebndl.ptr.p_double[i*n+j],s->effectivebndu.ptr.p_double[i*n+j]) ) + { + s->repterminationtype = -3; + return; + } + + /* + * Calculate intersection of the effective bound constraints + * and user-specified equality constraints. + * This code checks consistency of such combination. + */ + if( ae_isfinite(s->ec.ptr.pp_double[i][j], _state) ) + { + if( ae_fp_less(s->ec.ptr.pp_double[i][j],s->effectivebndl.ptr.p_double[i*n+j])||ae_fp_greater(s->ec.ptr.pp_double[i][j],s->effectivebndu.ptr.p_double[i*n+j]) ) + { + s->repterminationtype = -3; + return; + } + s->effectivebndl.ptr.p_double[i*n+j] = s->ec.ptr.pp_double[i][j]; + s->effectivebndu.ptr.p_double[i*n+j] = s->ec.ptr.pp_double[i][j]; + } + } + } + + /* + * Generate linear constraints: + * * "default" sums-to-one constraints (not generated for "exit" states) + */ + rmatrixsetlengthatleast(&s->effectivec, s->ccnt+n, n*n+1, _state); + ivectorsetlengthatleast(&s->effectivect, s->ccnt+n, _state); + ccnt = s->ccnt; + for(i=0; i<=s->ccnt-1; i++) + { + for(j=0; j<=n*n; j++) + { + s->effectivec.ptr.pp_double[i][j] = s->c.ptr.pp_double[i][j]; + } + s->effectivect.ptr.p_int[i] = s->ct.ptr.p_int[i]; + } + for(i=0; i<=n-1; i++) + { + if( s->states.ptr.p_int[i]>=0 ) + { + for(k=0; k<=n*n-1; k++) + { + s->effectivec.ptr.pp_double[ccnt][k] = 0; + } + for(k=0; k<=n-1; k++) + { + s->effectivec.ptr.pp_double[ccnt][k*n+i] = 1; + } + s->effectivec.ptr.pp_double[ccnt][n*n] = 1.0; + s->effectivect.ptr.p_int[ccnt] = 0; + ccnt = ccnt+1; + } + } + + /* + * create optimizer + */ + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->tmpp.ptr.p_double[i*n+j] = (double)1/(double)n; + } + } + minbleicrestartfrom(&s->bs, &s->tmpp, _state); + minbleicsetbc(&s->bs, &s->effectivebndl, &s->effectivebndu, _state); + minbleicsetlc(&s->bs, &s->effectivec, &s->effectivect, ccnt, _state); + minbleicsetcond(&s->bs, 0.0, 0.0, mcpd_xtol, 0, _state); + minbleicsetprecdiag(&s->bs, &s->h, _state); + + /* + * solve problem + */ + while(minbleiciteration(&s->bs, _state)) + { + ae_assert(s->bs.needfg, "MCPDSolve: internal error", _state); + if( s->bs.needfg ) + { + + /* + * Calculate regularization term + */ + s->bs.f = 0.0; + vv = s->regterm; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->bs.f = s->bs.f+vv*ae_sqr(s->bs.x.ptr.p_double[i*n+j]-s->priorp.ptr.pp_double[i][j], _state); + s->bs.g.ptr.p_double[i*n+j] = 2*vv*(s->bs.x.ptr.p_double[i*n+j]-s->priorp.ptr.pp_double[i][j]); + } + } + + /* + * calculate prediction error/gradient for K-th pair + */ + for(k=0; k<=npairs-1; k++) + { + for(i=0; i<=n-1; i++) + { + v = ae_v_dotproduct(&s->bs.x.ptr.p_double[i*n], 1, &s->data.ptr.pp_double[k][0], 1, ae_v_len(i*n,i*n+n-1)); + vv = s->effectivew.ptr.p_double[i]; + s->bs.f = s->bs.f+ae_sqr(vv*(v-s->data.ptr.pp_double[k][n+i]), _state); + for(j=0; j<=n-1; j++) + { + s->bs.g.ptr.p_double[i*n+j] = s->bs.g.ptr.p_double[i*n+j]+2*vv*vv*(v-s->data.ptr.pp_double[k][n+i])*s->data.ptr.pp_double[k][j]; + } + } + } + + /* + * continue + */ + continue; + } + } + minbleicresultsbuf(&s->bs, &s->tmpp, &s->br, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->p.ptr.pp_double[i][j] = s->tmpp.ptr.p_double[i*n+j]; + } + } + s->repterminationtype = s->br.terminationtype; + s->repinneriterationscount = s->br.inneriterationscount; + s->repouteriterationscount = s->br.outeriterationscount; + s->repnfev = s->br.nfev; +} + + +/************************************************************************* +MCPD results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + P - array[N,N], transition matrix + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one. Speaking short, positive values denote + success, negative ones are failures. + More information about fields of this structure can be + found in the comments on MCPDReport datatype. + + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdresults(mcpdstate* s, + /* Real */ ae_matrix* p, + mcpdreport* rep, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(p); + _mcpdreport_clear(rep); + + ae_matrix_set_length(p, s->n, s->n, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=s->n-1; j++) + { + p->ptr.pp_double[i][j] = s->p.ptr.pp_double[i][j]; + } + } + rep->terminationtype = s->repterminationtype; + rep->inneriterationscount = s->repinneriterationscount; + rep->outeriterationscount = s->repouteriterationscount; + rep->nfev = s->repnfev; +} + + +/************************************************************************* +Internal initialization function + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +static void mcpd_mcpdinit(ae_int_t n, + ae_int_t entrystate, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + ae_assert(n>=1, "MCPDCreate: N<1", _state); + s->n = n; + ae_vector_set_length(&s->states, n, _state); + for(i=0; i<=n-1; i++) + { + s->states.ptr.p_int[i] = 0; + } + if( entrystate>=0 ) + { + s->states.ptr.p_int[entrystate] = 1; + } + if( exitstate>=0 ) + { + s->states.ptr.p_int[exitstate] = -1; + } + s->npairs = 0; + s->regterm = 1.0E-8; + s->ccnt = 0; + ae_matrix_set_length(&s->p, n, n, _state); + ae_matrix_set_length(&s->ec, n, n, _state); + ae_matrix_set_length(&s->bndl, n, n, _state); + ae_matrix_set_length(&s->bndu, n, n, _state); + ae_vector_set_length(&s->pw, n, _state); + ae_matrix_set_length(&s->priorp, n, n, _state); + ae_vector_set_length(&s->tmpp, n*n, _state); + ae_vector_set_length(&s->effectivew, n, _state); + ae_vector_set_length(&s->effectivebndl, n*n, _state); + ae_vector_set_length(&s->effectivebndu, n*n, _state); + ae_vector_set_length(&s->h, n*n, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->p.ptr.pp_double[i][j] = 0.0; + s->priorp.ptr.pp_double[i][j] = 0.0; + s->bndl.ptr.pp_double[i][j] = _state->v_neginf; + s->bndu.ptr.pp_double[i][j] = _state->v_posinf; + s->ec.ptr.pp_double[i][j] = _state->v_nan; + } + s->pw.ptr.p_double[i] = 0.0; + s->priorp.ptr.pp_double[i][i] = 1.0; + } + ae_matrix_set_length(&s->data, 1, 2*n, _state); + for(i=0; i<=2*n-1; i++) + { + s->data.ptr.pp_double[0][i] = 0.0; + } + for(i=0; i<=n*n-1; i++) + { + s->tmpp.ptr.p_double[i] = 0.0; + } + minbleiccreate(n*n, &s->tmpp, &s->bs, _state); +} + + +ae_bool _mcpdstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mcpdstate *p = (mcpdstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->states, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->data, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->ec, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->bndl, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->bndu, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->c, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ct, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->pw, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->priorp, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_minbleicstate_init(&p->bs, _state, make_automatic) ) + return ae_false; + if( !_minbleicreport_init(&p->br, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->effectivew, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->effectivebndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->effectivebndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->effectivec, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->effectivect, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->h, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->p, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mcpdstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mcpdstate *dst = (mcpdstate*)_dst; + mcpdstate *src = (mcpdstate*)_src; + dst->n = src->n; + if( !ae_vector_init_copy(&dst->states, &src->states, _state, make_automatic) ) + return ae_false; + dst->npairs = src->npairs; + if( !ae_matrix_init_copy(&dst->data, &src->data, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->ec, &src->ec, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->c, &src->c, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ct, &src->ct, _state, make_automatic) ) + return ae_false; + dst->ccnt = src->ccnt; + if( !ae_vector_init_copy(&dst->pw, &src->pw, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->priorp, &src->priorp, _state, make_automatic) ) + return ae_false; + dst->regterm = src->regterm; + if( !_minbleicstate_init_copy(&dst->bs, &src->bs, _state, make_automatic) ) + return ae_false; + dst->repinneriterationscount = src->repinneriterationscount; + dst->repouteriterationscount = src->repouteriterationscount; + dst->repnfev = src->repnfev; + dst->repterminationtype = src->repterminationtype; + if( !_minbleicreport_init_copy(&dst->br, &src->br, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpp, &src->tmpp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->effectivew, &src->effectivew, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->effectivebndl, &src->effectivebndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->effectivebndu, &src->effectivebndu, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->effectivec, &src->effectivec, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->effectivect, &src->effectivect, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->h, &src->h, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->p, &src->p, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mcpdstate_clear(void* _p) +{ + mcpdstate *p = (mcpdstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->states); + ae_matrix_clear(&p->data); + ae_matrix_clear(&p->ec); + ae_matrix_clear(&p->bndl); + ae_matrix_clear(&p->bndu); + ae_matrix_clear(&p->c); + ae_vector_clear(&p->ct); + ae_vector_clear(&p->pw); + ae_matrix_clear(&p->priorp); + _minbleicstate_clear(&p->bs); + _minbleicreport_clear(&p->br); + ae_vector_clear(&p->tmpp); + ae_vector_clear(&p->effectivew); + ae_vector_clear(&p->effectivebndl); + ae_vector_clear(&p->effectivebndu); + ae_matrix_clear(&p->effectivec); + ae_vector_clear(&p->effectivect); + ae_vector_clear(&p->h); + ae_matrix_clear(&p->p); +} + + +void _mcpdstate_destroy(void* _p) +{ + mcpdstate *p = (mcpdstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->states); + ae_matrix_destroy(&p->data); + ae_matrix_destroy(&p->ec); + ae_matrix_destroy(&p->bndl); + ae_matrix_destroy(&p->bndu); + ae_matrix_destroy(&p->c); + ae_vector_destroy(&p->ct); + ae_vector_destroy(&p->pw); + ae_matrix_destroy(&p->priorp); + _minbleicstate_destroy(&p->bs); + _minbleicreport_destroy(&p->br); + ae_vector_destroy(&p->tmpp); + ae_vector_destroy(&p->effectivew); + ae_vector_destroy(&p->effectivebndl); + ae_vector_destroy(&p->effectivebndu); + ae_matrix_destroy(&p->effectivec); + ae_vector_destroy(&p->effectivect); + ae_vector_destroy(&p->h); + ae_matrix_destroy(&p->p); +} + + +ae_bool _mcpdreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mcpdreport *p = (mcpdreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _mcpdreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mcpdreport *dst = (mcpdreport*)_dst; + mcpdreport *src = (mcpdreport*)_src; + dst->inneriterationscount = src->inneriterationscount; + dst->outeriterationscount = src->outeriterationscount; + dst->nfev = src->nfev; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _mcpdreport_clear(void* _p) +{ + mcpdreport *p = (mcpdreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _mcpdreport_destroy(void* _p) +{ + mcpdreport *p = (mcpdreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +Like MLPCreate0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate0(ae_int_t nin, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreate0(nin, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreate1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreate1(nin, nhid, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreate2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreate2(nin, nhid1, nhid2, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateB0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb0(ae_int_t nin, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreateb0(nin, nout, b, d, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateB1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreateb1(nin, nhid, nout, b, d, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateB2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreateb2(nin, nhid1, nhid2, nout, b, d, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateR0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater0(ae_int_t nin, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreater0(nin, nout, a, b, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateR1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreater1(nin, nhid, nout, a, b, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateR2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreater2(nin, nhid1, nhid2, nout, a, b, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateC0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec0(ae_int_t nin, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreatec0(nin, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateC1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreatec1(nin, nhid, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Like MLPCreateC2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_frame _frame_block; + multilayerperceptron net; + + ae_frame_make(_state, &_frame_block); + _mlpensemble_clear(ensemble); + _multilayerperceptron_init(&net, _state, ae_true); + + mlpcreatec2(nin, nhid1, nhid2, nout, &net, _state); + mlpecreatefromnetwork(&net, ensemblesize, ensemble, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Creates ensemble from network. Only network geometry is copied. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatefromnetwork(multilayerperceptron* network, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_int_t i; + ae_int_t ccount; + ae_int_t wcount; + + _mlpensemble_clear(ensemble); + + ae_assert(ensemblesize>0, "MLPECreate: incorrect ensemble size!", _state); + + /* + * Copy network + */ + mlpcopy(network, &ensemble->network, _state); + + /* + * network properties + */ + if( mlpissoftmax(network, _state) ) + { + ccount = mlpgetinputscount(&ensemble->network, _state); + } + else + { + ccount = mlpgetinputscount(&ensemble->network, _state)+mlpgetoutputscount(&ensemble->network, _state); + } + wcount = mlpgetweightscount(&ensemble->network, _state); + ensemble->ensemblesize = ensemblesize; + + /* + * weights, means, sigmas + */ + ae_vector_set_length(&ensemble->weights, ensemblesize*wcount, _state); + ae_vector_set_length(&ensemble->columnmeans, ensemblesize*ccount, _state); + ae_vector_set_length(&ensemble->columnsigmas, ensemblesize*ccount, _state); + for(i=0; i<=ensemblesize*wcount-1; i++) + { + ensemble->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5; + } + for(i=0; i<=ensemblesize-1; i++) + { + ae_v_move(&ensemble->columnmeans.ptr.p_double[i*ccount], 1, &network->columnmeans.ptr.p_double[0], 1, ae_v_len(i*ccount,(i+1)*ccount-1)); + ae_v_move(&ensemble->columnsigmas.ptr.p_double[i*ccount], 1, &network->columnsigmas.ptr.p_double[0], 1, ae_v_len(i*ccount,(i+1)*ccount-1)); + } + + /* + * temporaries, internal buffers + */ + ae_vector_set_length(&ensemble->y, mlpgetoutputscount(&ensemble->network, _state), _state); +} + + +/************************************************************************* +Copying of MLPEnsemble strucure + +INPUT PARAMETERS: + Ensemble1 - original + +OUTPUT PARAMETERS: + Ensemble2 - copy + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecopy(mlpensemble* ensemble1, + mlpensemble* ensemble2, + ae_state *_state) +{ + ae_int_t ccount; + ae_int_t wcount; + + _mlpensemble_clear(ensemble2); + + + /* + * Unload info + */ + if( mlpissoftmax(&ensemble1->network, _state) ) + { + ccount = mlpgetinputscount(&ensemble1->network, _state); + } + else + { + ccount = mlpgetinputscount(&ensemble1->network, _state)+mlpgetoutputscount(&ensemble1->network, _state); + } + wcount = mlpgetweightscount(&ensemble1->network, _state); + + /* + * Allocate space + */ + ae_vector_set_length(&ensemble2->weights, ensemble1->ensemblesize*wcount, _state); + ae_vector_set_length(&ensemble2->columnmeans, ensemble1->ensemblesize*ccount, _state); + ae_vector_set_length(&ensemble2->columnsigmas, ensemble1->ensemblesize*ccount, _state); + ae_vector_set_length(&ensemble2->y, mlpgetoutputscount(&ensemble1->network, _state), _state); + + /* + * Copy + */ + ensemble2->ensemblesize = ensemble1->ensemblesize; + ae_v_move(&ensemble2->weights.ptr.p_double[0], 1, &ensemble1->weights.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*wcount-1)); + ae_v_move(&ensemble2->columnmeans.ptr.p_double[0], 1, &ensemble1->columnmeans.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*ccount-1)); + ae_v_move(&ensemble2->columnsigmas.ptr.p_double[0], 1, &ensemble1->columnsigmas.ptr.p_double[0], 1, ae_v_len(0,ensemble1->ensemblesize*ccount-1)); + mlpcopy(&ensemble1->network, &ensemble2->network, _state); +} + + +/************************************************************************* +Randomization of MLP ensemble + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlperandomize(mlpensemble* ensemble, ae_state *_state) +{ + ae_int_t i; + ae_int_t wcount; + + + wcount = mlpgetweightscount(&ensemble->network, _state); + for(i=0; i<=ensemble->ensemblesize*wcount-1; i++) + { + ensemble->weights.ptr.p_double[i] = ae_randomreal(_state)-0.5; + } +} + + +/************************************************************************* +Return ensemble properties (number of inputs and outputs). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeproperties(mlpensemble* ensemble, + ae_int_t* nin, + ae_int_t* nout, + ae_state *_state) +{ + + *nin = 0; + *nout = 0; + + *nin = mlpgetinputscount(&ensemble->network, _state); + *nout = mlpgetoutputscount(&ensemble->network, _state); +} + + +/************************************************************************* +Return normalization type (whether ensemble is SOFTMAX-normalized or not). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool mlpeissoftmax(mlpensemble* ensemble, ae_state *_state) +{ + ae_bool result; + + + result = mlpissoftmax(&ensemble->network, _state); + return result; +} + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Ensemble- neural networks ensemble + X - input vector, array[0..NIn-1]. + Y - (possibly) preallocated buffer; if size of Y is less than + NOut, it will be reallocated. If it is large enough, it + is NOT reallocated, so we can save some time on reallocation. + + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocess(mlpensemble* ensemble, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t es; + ae_int_t wc; + ae_int_t cc; + double v; + ae_int_t nout; + + + if( y->cntnetwork, _state) ) + { + ae_vector_set_length(y, mlpgetoutputscount(&ensemble->network, _state), _state); + } + es = ensemble->ensemblesize; + wc = mlpgetweightscount(&ensemble->network, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + cc = mlpgetinputscount(&ensemble->network, _state); + } + else + { + cc = mlpgetinputscount(&ensemble->network, _state)+mlpgetoutputscount(&ensemble->network, _state); + } + v = (double)1/(double)es; + nout = mlpgetoutputscount(&ensemble->network, _state); + for(i=0; i<=nout-1; i++) + { + y->ptr.p_double[i] = 0; + } + for(i=0; i<=es-1; i++) + { + ae_v_move(&ensemble->network.weights.ptr.p_double[0], 1, &ensemble->weights.ptr.p_double[i*wc], 1, ae_v_len(0,wc-1)); + ae_v_move(&ensemble->network.columnmeans.ptr.p_double[0], 1, &ensemble->columnmeans.ptr.p_double[i*cc], 1, ae_v_len(0,cc-1)); + ae_v_move(&ensemble->network.columnsigmas.ptr.p_double[0], 1, &ensemble->columnsigmas.ptr.p_double[i*cc], 1, ae_v_len(0,cc-1)); + mlpprocess(&ensemble->network, x, &ensemble->y, _state); + ae_v_addd(&y->ptr.p_double[0], 1, &ensemble->y.ptr.p_double[0], 1, ae_v_len(0,nout-1), v); + } +} + + +/************************************************************************* +'interactive' variant of MLPEProcess for languages like Python which +support constructs like "Y = MLPEProcess(LM,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocessi(mlpensemble* ensemble, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + ae_vector_clear(y); + + mlpeprocess(ensemble, x, y, _state); +} + + +/************************************************************************* +Calculation of all types of errors + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeallerrorsx(mlpensemble* ensemble, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + modelerrors* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t nin; + ae_int_t nout; + ae_bool iscls; + ae_int_t srcidx; + mlpbuffers *pbuf; + ae_smart_ptr _pbuf; + modelerrors rep0; + modelerrors rep1; + + ae_frame_make(_state, &_frame_block); + ae_smart_ptr_init(&_pbuf, (void**)&pbuf, _state, ae_true); + _modelerrors_init(&rep0, _state, ae_true); + _modelerrors_init(&rep1, _state, ae_true); + + + /* + * Get network information + */ + nin = mlpgetinputscount(&ensemble->network, _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + iscls = mlpissoftmax(&ensemble->network, _state); + + /* + * Retrieve buffer, prepare, process data, recycle buffer + */ + ae_shared_pool_retrieve(buf, &_pbuf, _state); + if( iscls ) + { + dserrallocate(nout, &pbuf->tmp0, _state); + } + else + { + dserrallocate(-nout, &pbuf->tmp0, _state); + } + rvectorsetlengthatleast(&pbuf->x, nin, _state); + rvectorsetlengthatleast(&pbuf->y, nout, _state); + rvectorsetlengthatleast(&pbuf->desiredy, nout, _state); + for(i=subset0; i<=subset1-1; i++) + { + srcidx = -1; + if( subsettype==0 ) + { + srcidx = i; + } + if( subsettype==1 ) + { + srcidx = idx->ptr.p_int[i]; + } + ae_assert(srcidx>=0, "MLPEAllErrorsX: internal error", _state); + if( datasettype==0 ) + { + ae_v_move(&pbuf->x.ptr.p_double[0], 1, &densexy->ptr.pp_double[srcidx][0], 1, ae_v_len(0,nin-1)); + } + if( datasettype==1 ) + { + sparsegetrow(sparsexy, srcidx, &pbuf->x, _state); + } + mlpeprocess(ensemble, &pbuf->x, &pbuf->y, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + if( datasettype==0 ) + { + pbuf->desiredy.ptr.p_double[0] = densexy->ptr.pp_double[srcidx][nin]; + } + if( datasettype==1 ) + { + pbuf->desiredy.ptr.p_double[0] = sparseget(sparsexy, srcidx, nin, _state); + } + } + else + { + if( datasettype==0 ) + { + ae_v_move(&pbuf->desiredy.ptr.p_double[0], 1, &densexy->ptr.pp_double[srcidx][nin], 1, ae_v_len(0,nout-1)); + } + if( datasettype==1 ) + { + for(j=0; j<=nout-1; j++) + { + pbuf->desiredy.ptr.p_double[j] = sparseget(sparsexy, srcidx, nin+j, _state); + } + } + } + dserraccumulate(&pbuf->tmp0, &pbuf->y, &pbuf->desiredy, _state); + } + dserrfinish(&pbuf->tmp0, _state); + rep->relclserror = pbuf->tmp0.ptr.p_double[0]; + rep->avgce = pbuf->tmp0.ptr.p_double[1]/ae_log(2, _state); + rep->rmserror = pbuf->tmp0.ptr.p_double[2]; + rep->avgerror = pbuf->tmp0.ptr.p_double[3]; + rep->avgrelerror = pbuf->tmp0.ptr.p_double[4]; + ae_shared_pool_recycle(buf, &_pbuf, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Calculation of all types of errors on dataset given by sparse matrix + + -- ALGLIB -- + Copyright 10.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpeallerrorssparse(mlpensemble* ensemble, + sparsematrix* xy, + ae_int_t npoints, + double* relcls, + double* avgce, + double* rms, + double* avg, + double* avgrel, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector buf; + ae_vector workx; + ae_vector y; + ae_vector dy; + ae_int_t nin; + ae_int_t nout; + + ae_frame_make(_state, &_frame_block); + *relcls = 0; + *avgce = 0; + *rms = 0; + *avg = 0; + *avgrel = 0; + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&workx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dy, 0, DT_REAL, _state, ae_true); + + nin = mlpgetinputscount(&ensemble->network, _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + ae_vector_set_length(&dy, 1, _state); + dserrallocate(nout, &buf, _state); + } + else + { + ae_vector_set_length(&dy, nout, _state); + dserrallocate(-nout, &buf, _state); + } + for(i=0; i<=npoints-1; i++) + { + sparsegetrow(xy, i, &workx, _state); + mlpeprocess(ensemble, &workx, &y, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + dy.ptr.p_double[0] = workx.ptr.p_double[nin]; + } + else + { + ae_v_move(&dy.ptr.p_double[0], 1, &workx.ptr.p_double[nin], 1, ae_v_len(0,nout-1)); + } + dserraccumulate(&buf, &y, &dy, _state); + } + dserrfinish(&buf, _state); + *relcls = buf.ptr.p_double[0]; + *avgce = buf.ptr.p_double[1]; + *rms = buf.ptr.p_double[2]; + *avg = buf.ptr.p_double[3]; + *avgrel = buf.ptr.p_double[4]; + ae_frame_leave(_state); +} + + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Works both for classifier betwork and for regression networks which +are used as classifiers. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlperelclserror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors rep; + double result; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&rep, _state, ae_true); + + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &rep, _state); + result = rep.relclserror; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if ensemble solves regression task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgce(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors rep; + double result; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&rep, _state, ae_true); + + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &rep, _state); + result = rep.avgce; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for classification task +RMS error means error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpermserror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors rep; + double result; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&rep, _state, ae_true); + + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &rep, _state); + result = rep.rmserror; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgerror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors rep; + double result; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&rep, _state, ae_true); + + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &rep, _state); + result = rep.avgerror; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average relative error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgrelerror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors rep; + double result; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&rep, _state, ae_true); + + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &rep, _state); + result = rep.avgrelerror; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Serializer: allocation + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpealloc(ae_serializer* s, mlpensemble* ensemble, ae_state *_state) +{ + + + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + allocrealarray(s, &ensemble->weights, -1, _state); + allocrealarray(s, &ensemble->columnmeans, -1, _state); + allocrealarray(s, &ensemble->columnsigmas, -1, _state); + mlpalloc(s, &ensemble->network, _state); +} + + +/************************************************************************* +Serializer: serialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpeserialize(ae_serializer* s, + mlpensemble* ensemble, + ae_state *_state) +{ + + + ae_serializer_serialize_int(s, getmlpeserializationcode(_state), _state); + ae_serializer_serialize_int(s, mlpe_mlpefirstversion, _state); + ae_serializer_serialize_int(s, ensemble->ensemblesize, _state); + serializerealarray(s, &ensemble->weights, -1, _state); + serializerealarray(s, &ensemble->columnmeans, -1, _state); + serializerealarray(s, &ensemble->columnsigmas, -1, _state); + mlpserialize(s, &ensemble->network, _state); +} + + +/************************************************************************* +Serializer: unserialization + + -- ALGLIB -- + Copyright 14.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpeunserialize(ae_serializer* s, + mlpensemble* ensemble, + ae_state *_state) +{ + ae_int_t i0; + ae_int_t i1; + + _mlpensemble_clear(ensemble); + + + /* + * check correctness of header + */ + ae_serializer_unserialize_int(s, &i0, _state); + ae_assert(i0==getmlpeserializationcode(_state), "MLPEUnserialize: stream header corrupted", _state); + ae_serializer_unserialize_int(s, &i1, _state); + ae_assert(i1==mlpe_mlpefirstversion, "MLPEUnserialize: stream header corrupted", _state); + + /* + * Create network + */ + ae_serializer_unserialize_int(s, &ensemble->ensemblesize, _state); + unserializerealarray(s, &ensemble->weights, _state); + unserializerealarray(s, &ensemble->columnmeans, _state); + unserializerealarray(s, &ensemble->columnsigmas, _state); + mlpunserialize(s, &ensemble->network, _state); + + /* + * Allocate termoraries + */ + ae_vector_set_length(&ensemble->y, mlpgetoutputscount(&ensemble->network, _state), _state); +} + + +ae_bool _mlpensemble_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpensemble *p = (mlpensemble*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->weights, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->columnmeans, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->columnsigmas, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_multilayerperceptron_init(&p->network, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mlpensemble_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpensemble *dst = (mlpensemble*)_dst; + mlpensemble *src = (mlpensemble*)_src; + dst->ensemblesize = src->ensemblesize; + if( !ae_vector_init_copy(&dst->weights, &src->weights, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->columnmeans, &src->columnmeans, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->columnsigmas, &src->columnsigmas, _state, make_automatic) ) + return ae_false; + if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mlpensemble_clear(void* _p) +{ + mlpensemble *p = (mlpensemble*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->weights); + ae_vector_clear(&p->columnmeans); + ae_vector_clear(&p->columnsigmas); + _multilayerperceptron_clear(&p->network); + ae_vector_clear(&p->y); +} + + +void _mlpensemble_destroy(void* _p) +{ + mlpensemble *p = (mlpensemble*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->weights); + ae_vector_destroy(&p->columnmeans); + ae_vector_destroy(&p->columnsigmas); + _multilayerperceptron_destroy(&p->network); + ae_vector_destroy(&p->y); +} + + + + +/************************************************************************* +Neural network training using modified Levenberg-Marquardt with exact +Hessian calculation and regularization. Subroutine trains neural network +with restarts from random positions. Algorithm is well suited for small +and medium scale problems (hundreds of weights). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -9, if internal matrix inverse subroutine failed + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlm(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + double lmsteptol; + ae_int_t i; + ae_int_t k; + double v; + double e; + double enew; + double xnorm2; + double stepnorm; + ae_vector g; + ae_vector d; + ae_matrix h; + ae_matrix hmod; + ae_matrix z; + ae_bool spd; + double nu; + double lambdav; + double lambdaup; + double lambdadown; + minlbfgsreport internalrep; + minlbfgsstate state; + ae_vector x; + ae_vector y; + ae_vector wbase; + ae_vector wdir; + ae_vector wt; + ae_vector wx; + ae_int_t pass; + ae_vector wbest; + double ebest; + ae_int_t invinfo; + matinvreport invrep; + ae_int_t solverinfo; + densesolverreport solverrep; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + ae_vector_init(&g, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&h, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&hmod, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true); + _minlbfgsreport_init(&internalrep, _state, ae_true); + _minlbfgsstate_init(&state, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wbase, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wdir, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true); + _matinvreport_init(&invrep, _state, ae_true); + _densesolverreport_init(&solverrep, _state, ae_true); + + mlpproperties(network, &nin, &nout, &wcount, _state); + lambdaup = 10; + lambdadown = 0.3; + lmsteptol = 0.001; + + /* + * Test for inputs + */ + if( npoints<=0||restarts<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( mlpissoftmax(network, _state) ) + { + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + decay = ae_maxreal(decay, mlptrain_mindecay, _state); + *info = 2; + + /* + * Initialize data + */ + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + + /* + * General case. + * Prepare task and network. Allocate space. + */ + mlpinitpreprocessor(network, xy, npoints, _state); + ae_vector_set_length(&g, wcount-1+1, _state); + ae_matrix_set_length(&h, wcount-1+1, wcount-1+1, _state); + ae_matrix_set_length(&hmod, wcount-1+1, wcount-1+1, _state); + ae_vector_set_length(&wbase, wcount-1+1, _state); + ae_vector_set_length(&wdir, wcount-1+1, _state); + ae_vector_set_length(&wbest, wcount-1+1, _state); + ae_vector_set_length(&wt, wcount-1+1, _state); + ae_vector_set_length(&wx, wcount-1+1, _state); + ebest = ae_maxrealnumber; + + /* + * Multiple passes + */ + for(pass=1; pass<=restarts; pass++) + { + + /* + * Initialize weights + */ + mlprandomize(network, _state); + + /* + * First stage of the hybrid algorithm: LBFGS + */ + ae_v_move(&wbase.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + minlbfgscreate(wcount, ae_minint(wcount, 5, _state), &wbase, &state, _state); + minlbfgssetcond(&state, 0, 0, 0, ae_maxint(25, wcount, _state), _state); + while(minlbfgsiteration(&state, _state)) + { + + /* + * gradient + */ + ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + mlpgradbatch(network, xy, npoints, &state.f, &state.g, _state); + + /* + * weight decay + */ + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + state.f = state.f+0.5*decay*v; + ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + + /* + * next iteration + */ + rep->ngrad = rep->ngrad+1; + } + minlbfgsresults(&state, &wbase, &internalrep, _state); + ae_v_move(&network->weights.ptr.p_double[0], 1, &wbase.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + + /* + * Second stage of the hybrid algorithm: LM + * + * Initialize H with identity matrix, + * G with gradient, + * E with regularized error. + */ + mlphessianbatch(network, xy, npoints, &e, &g, &h, _state); + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + for(k=0; k<=wcount-1; k++) + { + h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay; + } + rep->nhess = rep->nhess+1; + lambdav = 0.001; + nu = 2; + for(;;) + { + + /* + * 1. HMod = H+lambda*I + * 2. Try to solve (H+Lambda*I)*dx = -g. + * Increase lambda if left part is not positive definite. + */ + for(i=0; i<=wcount-1; i++) + { + ae_v_move(&hmod.ptr.pp_double[i][0], 1, &h.ptr.pp_double[i][0], 1, ae_v_len(0,wcount-1)); + hmod.ptr.pp_double[i][i] = hmod.ptr.pp_double[i][i]+lambdav; + } + spd = spdmatrixcholesky(&hmod, wcount, ae_true, _state); + rep->ncholesky = rep->ncholesky+1; + if( !spd ) + { + lambdav = lambdav*lambdaup*nu; + nu = nu*2; + continue; + } + spdmatrixcholeskysolve(&hmod, wcount, ae_true, &g, &solverinfo, &solverrep, &wdir, _state); + if( solverinfo<0 ) + { + lambdav = lambdav*lambdaup*nu; + nu = nu*2; + continue; + } + ae_v_muld(&wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1), -1); + + /* + * Lambda found. + * 1. Save old w in WBase + * 1. Test some stopping criterions + * 2. If error(w+wdir)>error(w), increase lambda + */ + ae_v_add(&network->weights.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + xnorm2 = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + stepnorm = ae_v_dotproduct(&wdir.ptr.p_double[0], 1, &wdir.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + stepnorm = ae_sqrt(stepnorm, _state); + enew = mlperror(network, xy, npoints, _state)+0.5*decay*xnorm2; + if( ae_fp_less(stepnorm,lmsteptol*(1+ae_sqrt(xnorm2, _state))) ) + { + break; + } + if( ae_fp_greater(enew,e) ) + { + lambdav = lambdav*lambdaup*nu; + nu = nu*2; + continue; + } + + /* + * Optimize using inv(cholesky(H)) as preconditioner + */ + rmatrixtrinverse(&hmod, wcount, ae_true, ae_false, &invinfo, &invrep, _state); + if( invinfo<=0 ) + { + + /* + * if matrix can't be inverted then exit with errors + * TODO: make WCount steps in direction suggested by HMod + */ + *info = -9; + ae_frame_leave(_state); + return; + } + ae_v_move(&wbase.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + for(i=0; i<=wcount-1; i++) + { + wt.ptr.p_double[i] = 0; + } + minlbfgscreatex(wcount, wcount, &wt, 1, 0.0, &state, _state); + minlbfgssetcond(&state, 0, 0, 0, 5, _state); + while(minlbfgsiteration(&state, _state)) + { + + /* + * gradient + */ + for(i=0; i<=wcount-1; i++) + { + v = ae_v_dotproduct(&state.x.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1)); + network->weights.ptr.p_double[i] = wbase.ptr.p_double[i]+v; + } + mlpgradbatch(network, xy, npoints, &state.f, &g, _state); + for(i=0; i<=wcount-1; i++) + { + state.g.ptr.p_double[i] = 0; + } + for(i=0; i<=wcount-1; i++) + { + v = g.ptr.p_double[i]; + ae_v_addd(&state.g.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1), v); + } + + /* + * weight decay + * grad(x'*x) = A'*(x0+A*t) + */ + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + state.f = state.f+0.5*decay*v; + for(i=0; i<=wcount-1; i++) + { + v = decay*network->weights.ptr.p_double[i]; + ae_v_addd(&state.g.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1), v); + } + + /* + * next iteration + */ + rep->ngrad = rep->ngrad+1; + } + minlbfgsresults(&state, &wt, &internalrep, _state); + + /* + * Accept new position. + * Calculate Hessian + */ + for(i=0; i<=wcount-1; i++) + { + v = ae_v_dotproduct(&wt.ptr.p_double[i], 1, &hmod.ptr.pp_double[i][i], 1, ae_v_len(i,wcount-1)); + network->weights.ptr.p_double[i] = wbase.ptr.p_double[i]+v; + } + mlphessianbatch(network, xy, npoints, &e, &g, &h, _state); + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = e+0.5*decay*v; + ae_v_addd(&g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + for(k=0; k<=wcount-1; k++) + { + h.ptr.pp_double[k][k] = h.ptr.pp_double[k][k]+decay; + } + rep->nhess = rep->nhess+1; + + /* + * Update lambda + */ + lambdav = lambdav*lambdadown; + nu = 2; + } + + /* + * update WBest + */ + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = 0.5*decay*v+mlperror(network, xy, npoints, _state); + if( ae_fp_less(e,ebest) ) + { + ebest = e; + ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + } + } + + /* + * copy WBest to output + */ + ae_v_move(&network->weights.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Neural network training using L-BFGS algorithm with regularization. +Subroutine trains neural network with restarts from random positions. +Algorithm is well suited for problems of any dimensionality (memory +requirements and step complexity are linear by weights number). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + MaxIts - stopping criterion. Algorithm stops after MaxIts + iterations (NOT gradient calculations). Zero MaxIts + means stopping when step is sufficiently small. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlbfgs(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t pass; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_vector w; + ae_vector wbest; + double e; + double v; + double ebest; + minlbfgsreport internalrep; + minlbfgsstate state; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true); + _minlbfgsreport_init(&internalrep, _state, ae_true); + _minlbfgsstate_init(&state, _state, ae_true); + + + /* + * Test inputs, parse flags, read network geometry + */ + if( ae_fp_eq(wstep,0)&&maxits==0 ) + { + *info = -8; + ae_frame_leave(_state); + return; + } + if( ((npoints<=0||restarts<1)||ae_fp_less(wstep,0))||maxits<0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + mlpproperties(network, &nin, &nout, &wcount, _state); + if( mlpissoftmax(network, _state) ) + { + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + decay = ae_maxreal(decay, mlptrain_mindecay, _state); + *info = 2; + + /* + * Prepare + */ + mlpinitpreprocessor(network, xy, npoints, _state); + ae_vector_set_length(&w, wcount-1+1, _state); + ae_vector_set_length(&wbest, wcount-1+1, _state); + ebest = ae_maxrealnumber; + + /* + * Multiple starts + */ + rep->ncholesky = 0; + rep->nhess = 0; + rep->ngrad = 0; + for(pass=1; pass<=restarts; pass++) + { + + /* + * Process + */ + mlprandomize(network, _state); + ae_v_move(&w.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + minlbfgscreate(wcount, ae_minint(wcount, 10, _state), &w, &state, _state); + minlbfgssetcond(&state, 0.0, 0.0, wstep, maxits, _state); + while(minlbfgsiteration(&state, _state)) + { + ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + mlpgradnbatch(network, xy, npoints, &state.f, &state.g, _state); + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + state.f = state.f+0.5*decay*v; + ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + rep->ngrad = rep->ngrad+1; + } + minlbfgsresults(&state, &w, &internalrep, _state); + ae_v_move(&network->weights.ptr.p_double[0], 1, &w.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + + /* + * Compare with best + */ + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = mlperrorn(network, xy, npoints, _state)+0.5*decay*v; + if( ae_fp_less(e,ebest) ) + { + ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ebest = e; + } + } + + /* + * The best network + */ + ae_v_move(&network->weights.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Neural network training using early stopping (base algorithm - L-BFGS with +regularization). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + TrnXY - training set + TrnSize - training set size, TrnSize>0 + ValXY - validation set + ValSize - validation set size, ValSize>0 + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts, either: + * strictly positive number - algorithm make specified + number of restarts from random position. + * -1, in which case algorithm makes exactly one run + from the initial state of the network (no randomization). + If you don't know what Restarts to choose, choose one + one the following: + * -1 (deterministic start) + * +1 (one random restart) + * +5 (moderate amount of random restarts) + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1, ...). + * 2, task has been solved, stopping criterion met - + sufficiently small step size. Not expected (we + use EARLY stopping) but possible and not an + error. + * 6, task has been solved, stopping criterion met - + increasing of validation set error. + Rep - training report + +NOTE: + +Algorithm stops if validation set error increases for a long enough or +step size is small enought (there are task where validation set may +decrease for eternity). In any case solution returned corresponds to the +minimum of validation set error. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptraines(multilayerperceptron* network, + /* Real */ ae_matrix* trnxy, + ae_int_t trnsize, + /* Real */ ae_matrix* valxy, + ae_int_t valsize, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t pass; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_vector w; + ae_vector wbest; + double e; + double v; + double ebest; + ae_vector wfinal; + double efinal; + ae_int_t itcnt; + ae_int_t itbest; + minlbfgsreport internalrep; + minlbfgsstate state; + double wstep; + ae_bool needrandomization; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wbest, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wfinal, 0, DT_REAL, _state, ae_true); + _minlbfgsreport_init(&internalrep, _state, ae_true); + _minlbfgsstate_init(&state, _state, ae_true); + + wstep = 0.001; + + /* + * Test inputs, parse flags, read network geometry + */ + if( ((trnsize<=0||valsize<=0)||(restarts<1&&restarts!=-1))||ae_fp_less(decay,0) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( restarts==-1 ) + { + needrandomization = ae_false; + restarts = 1; + } + else + { + needrandomization = ae_true; + } + mlpproperties(network, &nin, &nout, &wcount, _state); + if( mlpissoftmax(network, _state) ) + { + for(i=0; i<=trnsize-1; i++) + { + if( ae_round(trnxy->ptr.pp_double[i][nin], _state)<0||ae_round(trnxy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + for(i=0; i<=valsize-1; i++) + { + if( ae_round(valxy->ptr.pp_double[i][nin], _state)<0||ae_round(valxy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + *info = 2; + + /* + * Prepare + */ + mlpinitpreprocessor(network, trnxy, trnsize, _state); + ae_vector_set_length(&w, wcount-1+1, _state); + ae_vector_set_length(&wbest, wcount-1+1, _state); + ae_vector_set_length(&wfinal, wcount-1+1, _state); + efinal = ae_maxrealnumber; + for(i=0; i<=wcount-1; i++) + { + wfinal.ptr.p_double[i] = 0; + } + + /* + * Multiple starts + */ + rep->ncholesky = 0; + rep->nhess = 0; + rep->ngrad = 0; + for(pass=1; pass<=restarts; pass++) + { + + /* + * Process + */ + if( needrandomization ) + { + mlprandomize(network, _state); + } + ebest = mlperror(network, valxy, valsize, _state); + ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + itbest = 0; + itcnt = 0; + ae_v_move(&w.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + minlbfgscreate(wcount, ae_minint(wcount, 10, _state), &w, &state, _state); + minlbfgssetcond(&state, 0.0, 0.0, wstep, 0, _state); + minlbfgssetxrep(&state, ae_true, _state); + while(minlbfgsiteration(&state, _state)) + { + + /* + * Calculate gradient + */ + if( state.needfg ) + { + ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + mlpgradnbatch(network, trnxy, trnsize, &state.f, &state.g, _state); + v = ae_v_dotproduct(&network->weights.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + state.f = state.f+0.5*decay*v; + ae_v_addd(&state.g.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + rep->ngrad = rep->ngrad+1; + } + + /* + * Validation set + */ + if( state.xupdated ) + { + ae_v_move(&network->weights.ptr.p_double[0], 1, &state.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + e = mlperror(network, valxy, valsize, _state); + if( ae_fp_less(e,ebest) ) + { + ebest = e; + ae_v_move(&wbest.ptr.p_double[0], 1, &network->weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + itbest = itcnt; + } + if( itcnt>30&&ae_fp_greater(itcnt,1.5*itbest) ) + { + *info = 6; + break; + } + itcnt = itcnt+1; + } + } + minlbfgsresults(&state, &w, &internalrep, _state); + + /* + * Compare with final answer + */ + if( ae_fp_less(ebest,efinal) ) + { + ae_v_move(&wfinal.ptr.p_double[0], 1, &wbest.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + efinal = ebest; + } + } + + /* + * The best network + */ + ae_v_move(&network->weights.ptr.p_double[0], 1, &wfinal.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - L-BFGS. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlbfgs(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t foldscount, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state) +{ + + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(cvrep); + + mlptrain_mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, ae_false, wstep, maxits, info, rep, cvrep, _state); +} + + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - Levenberg-Marquardt. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlm(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t foldscount, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state) +{ + + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(cvrep); + + mlptrain_mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, ae_true, 0.0, 0, info, rep, cvrep, _state); +} + + +/************************************************************************* +This function estimates generalization error using cross-validation on the +current dataset with current training settings. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * FoldsCount cross-validation rounds (always) + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. Network is not changed during cross- + validation and is not trained - it is used only as + representative of its architecture. I.e., we estimate + generalization properties of ARCHITECTURE, not some + specific network. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that for each cross-validation + round specified number of random restarts is + performed, with best network being chosen after + training. + * NRestarts=0 is same as NRestarts=1 + FoldsCount - number of folds in k-fold cross-validation: + * 2<=FoldsCount<=size of dataset + * recommended value: 10. + * values larger than dataset size will be silently + truncated down to dataset size + +OUTPUT PARAMETERS: + Rep - structure which contains cross-validation estimates: + * Rep.RelCLSError - fraction of misclassified cases. + * Rep.AvgCE - acerage cross-entropy + * Rep.RMSError - root-mean-square error + * Rep.AvgError - average error + * Rep.AvgRelError - average relative error + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or subset with only one point was given, zeros are returned as + estimates. + +NOTE: this method performs FoldsCount cross-validation rounds, each one + with NRestarts random starts. Thus, FoldsCount*NRestarts networks + are trained in total. + +NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems. + +NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError + contain errors in prediction of posterior probabilities. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcv(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + ae_int_t foldscount, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_shared_pool pooldatacv; + mlpparallelizationcv datacv; + mlpparallelizationcv *sdatacv; + ae_smart_ptr _sdatacv; + ae_matrix cvy; + ae_vector folds; + ae_vector buf; + ae_vector dy; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t rowsize; + ae_int_t ntype; + ae_int_t ttype; + ae_int_t i; + ae_int_t j; + ae_int_t k; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + _mlpreport_clear(rep); + ae_shared_pool_init(&pooldatacv, _state, ae_true); + _mlpparallelizationcv_init(&datacv, _state, ae_true); + ae_smart_ptr_init(&_sdatacv, (void**)&sdatacv, _state, ae_true); + ae_matrix_init(&cvy, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&folds, 0, DT_INT, _state, ae_true); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dy, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&rs, _state, ae_true); + + if( !mlpissoftmax(network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + ae_assert(ntype==ttype, "MLPKFoldCV: type of input network is not similar to network type in trainer object", _state); + ae_assert(s->npoints>=0, "MLPKFoldCV: possible trainer S is not initialized(S.NPoints<0)", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPKFoldCV: number of inputs in trainer is not equal to number of inputs in network", _state); + ae_assert(s->nout==nout, "MLPKFoldCV: number of outputs in trainer is not equal to number of outputs in network", _state); + ae_assert(nrestarts>=0, "MLPKFoldCV: NRestarts<0", _state); + ae_assert(foldscount>=2, "MLPKFoldCV: FoldsCount<2", _state); + if( foldscount>s->npoints ) + { + foldscount = s->npoints; + } + rep->relclserror = 0; + rep->avgce = 0; + rep->rmserror = 0; + rep->avgerror = 0; + rep->avgrelerror = 0; + hqrndrandomize(&rs, _state); + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + if( s->npoints==0||s->npoints==1 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Read network geometry, test parameters + */ + if( s->rcpar ) + { + rowsize = nin+nout; + ae_vector_set_length(&dy, nout, _state); + dserrallocate(-nout, &buf, _state); + } + else + { + rowsize = nin+1; + ae_vector_set_length(&dy, 1, _state); + dserrallocate(nout, &buf, _state); + } + + /* + * Folds + */ + ae_vector_set_length(&folds, s->npoints, _state); + for(i=0; i<=s->npoints-1; i++) + { + folds.ptr.p_int[i] = i*foldscount/s->npoints; + } + for(i=0; i<=s->npoints-2; i++) + { + j = i+hqrnduniformi(&rs, s->npoints-i, _state); + if( j!=i ) + { + k = folds.ptr.p_int[i]; + folds.ptr.p_int[i] = folds.ptr.p_int[j]; + folds.ptr.p_int[j] = k; + } + } + ae_matrix_set_length(&cvy, s->npoints, nout, _state); + + /* + * Initialize SEED-value for shared pool + */ + datacv.ngrad = 0; + mlpcopy(network, &datacv.network, _state); + ae_vector_set_length(&datacv.subset, s->npoints, _state); + ae_vector_set_length(&datacv.xyrow, rowsize, _state); + ae_vector_set_length(&datacv.y, nout, _state); + + /* + * Create shared pool + */ + ae_shared_pool_set_seed(&pooldatacv, &datacv, sizeof(datacv), _mlpparallelizationcv_init, _mlpparallelizationcv_init_copy, _mlpparallelizationcv_destroy, _state); + + /* + * Parallelization + */ + mlptrain_mthreadcv(s, rowsize, nrestarts, &folds, 0, foldscount, &cvy, &pooldatacv, _state); + + /* + * Calculate value for NGrad + */ + ae_shared_pool_first_recycled(&pooldatacv, &_sdatacv, _state); + while(sdatacv!=NULL) + { + rep->ngrad = rep->ngrad+sdatacv->ngrad; + ae_shared_pool_next_recycled(&pooldatacv, &_sdatacv, _state); + } + + /* + * Connect of results and calculate cross-validation error + */ + for(i=0; i<=s->npoints-1; i++) + { + if( s->datatype==0 ) + { + ae_v_move(&datacv.xyrow.ptr.p_double[0], 1, &s->densexy.ptr.pp_double[i][0], 1, ae_v_len(0,rowsize-1)); + } + if( s->datatype==1 ) + { + sparsegetrow(&s->sparsexy, i, &datacv.xyrow, _state); + } + ae_v_move(&datacv.y.ptr.p_double[0], 1, &cvy.ptr.pp_double[i][0], 1, ae_v_len(0,nout-1)); + if( s->rcpar ) + { + ae_v_move(&dy.ptr.p_double[0], 1, &datacv.xyrow.ptr.p_double[nin], 1, ae_v_len(0,nout-1)); + } + else + { + dy.ptr.p_double[0] = datacv.xyrow.ptr.p_double[nin]; + } + dserraccumulate(&buf, &datacv.y, &dy, _state); + } + dserrfinish(&buf, _state); + rep->relclserror = buf.ptr.p_double[0]; + rep->avgce = buf.ptr.p_double[1]; + rep->rmserror = buf.ptr.p_double[2]; + rep->avgerror = buf.ptr.p_double[3]; + rep->avgrelerror = buf.ptr.p_double[4]; + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlpkfoldcv(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + ae_int_t foldscount, + mlpreport* rep, ae_state *_state) +{ + mlpkfoldcv(s,network,nrestarts,foldscount,rep, _state); +} + + +/************************************************************************* +Creation of the network trainer object for regression networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NOut - number of outputs, NOut>=1 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any regression + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainer(ae_int_t nin, + ae_int_t nout, + mlptrainer* s, + ae_state *_state) +{ + + _mlptrainer_clear(s); + + ae_assert(nin>=1, "MLPCreateTrainer: NIn<1.", _state); + ae_assert(nout>=1, "MLPCreateTrainer: NOut<1.", _state); + s->nin = nin; + s->nout = nout; + s->rcpar = ae_true; + s->lbfgsfactor = mlptrain_defaultlbfgsfactor; + s->decay = 1.0E-6; + mlpsetcond(s, 0, 0, _state); + s->datatype = 0; + s->npoints = 0; + mlpsetalgobatch(s, _state); +} + + +/************************************************************************* +Creation of the network trainer object for classification networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any classification + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainercls(ae_int_t nin, + ae_int_t nclasses, + mlptrainer* s, + ae_state *_state) +{ + + _mlptrainer_clear(s); + + ae_assert(nin>=1, "MLPCreateTrainerCls: NIn<1.", _state); + ae_assert(nclasses>=2, "MLPCreateTrainerCls: NClasses<2.", _state); + s->nin = nin; + s->nout = nclasses; + s->rcpar = ae_false; + s->lbfgsfactor = mlptrain_defaultlbfgsfactor; + s->decay = 1.0E-6; + mlpsetcond(s, 0, 0, _state); + s->datatype = 0; + s->npoints = 0; + mlpsetalgobatch(s, _state); +} + + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user. + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. + NPoints - points count, >=0. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdataset(mlptrainer* s, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + ae_int_t ndim; + ae_int_t i; + ae_int_t j; + + + ae_assert(s->nin>=1, "MLPSetDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).", _state); + ae_assert(npoints>=0, "MLPSetDataset: NPoint<0", _state); + ae_assert(npoints<=xy->rows, "MLPSetDataset: invalid size of matrix XY(NPoint more then rows of matrix XY)", _state); + s->datatype = 0; + s->npoints = npoints; + if( npoints==0 ) + { + return; + } + if( s->rcpar ) + { + ae_assert(s->nout>=1, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).", _state); + ndim = s->nin+s->nout; + ae_assert(ndim<=xy->cols, "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).", _state); + ae_assert(apservisfinitematrix(xy, npoints, ndim, _state), "MLPSetDataset: parameter XY contains Infinite or NaN.", _state); + } + else + { + ae_assert(s->nout>=2, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).", _state); + ndim = s->nin+1; + ae_assert(ndim<=xy->cols, "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).", _state); + ae_assert(apservisfinitematrix(xy, npoints, ndim, _state), "MLPSetDataset: parameter XY contains Infinite or NaN.", _state); + for(i=0; i<=npoints-1; i++) + { + ae_assert(ae_round(xy->ptr.pp_double[i][s->nin], _state)>=0&&ae_round(xy->ptr.pp_double[i][s->nin], _state)nout, "MLPSetDataset: invalid parameter XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state); + } + } + rmatrixsetlengthatleast(&s->densexy, npoints, ndim, _state); + for(i=0; i<=npoints-1; i++) + { + for(j=0; j<=ndim-1; j++) + { + s->densexy.ptr.pp_double[i][j] = xy->ptr.pp_double[i][j]; + } + } +} + + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user (sparse matrix is used to store dataset). + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Any sparse storage format can be used: + Hash-table, CRS... + NPoints - points count, >=0 + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetsparsedataset(mlptrainer* s, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state) +{ + double v; + ae_int_t t0; + ae_int_t t1; + ae_int_t i; + ae_int_t j; + + + + /* + * Check correctness of the data + */ + ae_assert(s->nin>0, "MLPSetSparseDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).", _state); + ae_assert(npoints>=0, "MLPSetSparseDataset: NPoint<0", _state); + ae_assert(npoints<=sparsegetnrows(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(NPoint more then rows of matrix XY)", _state); + if( npoints>0 ) + { + t0 = 0; + t1 = 0; + if( s->rcpar ) + { + ae_assert(s->nout>=1, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).", _state); + ae_assert(s->nin+s->nout<=sparsegetncols(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).", _state); + while(sparseenumerate(xy, &t0, &t1, &i, &j, &v, _state)) + { + if( inin+s->nout ) + { + ae_assert(ae_isfinite(v, _state), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.", _state); + } + } + } + else + { + ae_assert(s->nout>=2, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).", _state); + ae_assert(s->nin+1<=sparsegetncols(xy, _state), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).", _state); + while(sparseenumerate(xy, &t0, &t1, &i, &j, &v, _state)) + { + if( inin ) + { + if( j!=s->nin ) + { + ae_assert(ae_isfinite(v, _state), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.", _state); + } + else + { + ae_assert((ae_isfinite(v, _state)&&ae_round(v, _state)>=0)&&ae_round(v, _state)nout, "MLPSetSparseDataset: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).", _state); + } + } + } + } + } + + /* + * Set dataset + */ + s->datatype = 1; + s->npoints = npoints; + sparsecopytocrs(xy, &s->sparsexy, _state); +} + + +/************************************************************************* +This function sets weight decay coefficient which is used for training. + +INPUT PARAMETERS: + S - trainer object + Decay - weight decay coefficient, >=0. Weight decay term + 'Decay*||Weights||^2' is added to error function. If + you don't know what Decay to choose, use 1.0E-3. + Weight decay can be set to zero, in this case network + is trained without weight decay. + +NOTE: by default network uses some small nonzero value for weight decay. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdecay(mlptrainer* s, double decay, ae_state *_state) +{ + + + ae_assert(ae_isfinite(decay, _state), "MLPSetDecay: parameter Decay contains Infinite or NaN.", _state); + ae_assert(ae_fp_greater_eq(decay,0), "MLPSetDecay: Decay<0.", _state); + s->decay = decay; +} + + +/************************************************************************* +This function sets stopping criteria for the optimizer. + +INPUT PARAMETERS: + S - trainer object + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + WStep>=0. + MaxIts - stopping criterion. Algorithm stops after MaxIts + epochs (full passes over entire dataset). Zero MaxIts + means stopping when step is sufficiently small. + MaxIts>=0. + +NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values are also + used when MLPSetCond() is called with WStep=0 and MaxIts=0. + +NOTE: these stopping criteria are used for all kinds of neural training - + from "conventional" networks to early stopping ensembles. When used + for "conventional" networks, they are used as the only stopping + criteria. When combined with early stopping, they used as ADDITIONAL + stopping criteria which can terminate early stopping algorithm. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetcond(mlptrainer* s, + double wstep, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(wstep, _state), "MLPSetCond: parameter WStep contains Infinite or NaN.", _state); + ae_assert(ae_fp_greater_eq(wstep,0), "MLPSetCond: WStep<0.", _state); + ae_assert(maxits>=0, "MLPSetCond: MaxIts<0.", _state); + if( ae_fp_neq(wstep,0)||maxits!=0 ) + { + s->wstep = wstep; + s->maxits = maxits; + } + else + { + s->wstep = 0.005; + s->maxits = 0; + } +} + + +/************************************************************************* +This function sets training algorithm: batch training using L-BFGS will be +used. + +This algorithm: +* the most robust for small-scale problems, but may be too slow for large + scale ones. +* perfoms full pass through the dataset before performing step +* uses conditions specified by MLPSetCond() for stopping +* is default one used by trainer object + +INPUT PARAMETERS: + S - trainer object + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetalgobatch(mlptrainer* s, ae_state *_state) +{ + + + s->algokind = 0; +} + + +/************************************************************************* +This function trains neural network passed to this function, using current +dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset()) +and current training settings. Training from NRestarts random starting +positions is performed, best network is chosen. + +Training is performed using current training algorithm. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed, best network is chosen after + training + * NRestarts=0 means that current state of the network + is used for training. + +OUTPUT PARAMETERS: + Network - trained network + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + network is filled by zero values. Same behavior for functions + MLPStartTraining and MLPContinueTraining. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainnetwork(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntype; + ae_int_t ttype; + ae_shared_pool trnpool; + + ae_frame_make(_state, &_frame_block); + _mlpreport_clear(rep); + ae_shared_pool_init(&trnpool, _state, ae_true); + + ae_assert(s->npoints>=0, "MLPTrainNetwork: parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + if( !mlpissoftmax(network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + ae_assert(ntype==ttype, "MLPTrainNetwork: type of input network is not similar to network type in trainer object", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPTrainNetwork: number of inputs in trainer is not equal to number of inputs in network", _state); + ae_assert(s->nout==nout, "MLPTrainNetwork: number of outputs in trainer is not equal to number of outputs in network", _state); + ae_assert(nrestarts>=0, "MLPTrainNetwork: NRestarts<0.", _state); + + /* + * Train + */ + mlptrain_mlptrainnetworkx(s, nrestarts, -1, &s->subset, -1, &s->subset, 0, network, rep, ae_true, &trnpool, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlptrainnetwork(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + mlpreport* rep, ae_state *_state) +{ + mlptrainnetwork(s,network,nrestarts,rep, _state); +} + + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +After call to this function trainer object remembers network and is ready +to train it. However, no training is performed until first call to +MLPContinueTraining() function. Subsequent calls to MLPContinueTraining() +will advance training progress one iteration further. + +EXAMPLE: + > + > ...initialize network and trainer object.... + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > ...visualize training progress... + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + RandomStart - randomize network before training or not: + * True means that network is randomized and its + initial state (one which was passed to the trainer + object) is lost. + * False means that training is started from the + current state of the network + +OUTPUT PARAMETERS: + Network - neural network which is ready to training (weights are + initialized, preprocessor is initialized using current + training set) + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpstarttraining(mlptrainer* s, + multilayerperceptron* network, + ae_bool randomstart, + ae_state *_state) +{ + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntype; + ae_int_t ttype; + + + ae_assert(s->npoints>=0, "MLPStartTraining: parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + if( !mlpissoftmax(network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + ae_assert(ntype==ttype, "MLPStartTraining: type of input network is not similar to network type in trainer object", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPStartTraining: number of inputs in trainer is not equal to number of inputs in the network.", _state); + ae_assert(s->nout==nout, "MLPStartTraining: number of outputs in trainer is not equal to number of outputs in the network.", _state); + + /* + * Initialize temporaries + */ + mlptrain_initmlptrnsession(network, randomstart, s, &s->session, _state); + + /* + * Train network + */ + mlptrain_mlpstarttrainingx(s, randomstart, -1, &s->subset, -1, &s->session, _state); + + /* + * Update network + */ + mlpcopytunableparameters(&s->session.network, network, _state); +} + + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +This function performs one more iteration of the training and returns +either True (training continues) or False (training stopped). In case True +was returned, Network weights are updated according to the current state +of the optimization progress. In case False was returned, no additional +updates is performed (previous update of the network weights moved us to +the final point, and no additional updates is needed). + +EXAMPLE: + > + > [initialize network and trainer object] + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > [visualize training progress] + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network structure, which is used to store + current state of the training process. + +OUTPUT PARAMETERS: + Network - weights of the neural network are rewritten by the + current approximation. + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + +NOTE: It is expected that Network is the same one which was passed to + MLPStartTraining() function. However, THIS function checks only + following: + * that number of network inputs is consistent with trainer object + settings + * that number of network outputs/classes is consistent with trainer + object settings + * that number of network weights is the same as number of weights in + the network passed to MLPStartTraining() function + Exception is thrown when these conditions are violated. + + It is also expected that you do not change state of the network on + your own - the only party who has right to change network during its + training is a trainer object. Any attempt to interfere with trainer + may lead to unpredictable results. + + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool mlpcontinuetraining(mlptrainer* s, + multilayerperceptron* network, + ae_state *_state) +{ + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntype; + ae_int_t ttype; + ae_bool result; + + + ae_assert(s->npoints>=0, "MLPContinueTraining: parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + if( !mlpissoftmax(network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + ae_assert(ntype==ttype, "MLPContinueTraining: type of input network is not similar to network type in trainer object.", _state); + mlpproperties(network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPContinueTraining: number of inputs in trainer is not equal to number of inputs in the network.", _state); + ae_assert(s->nout==nout, "MLPContinueTraining: number of outputs in trainer is not equal to number of outputs in the network.", _state); + result = mlptrain_mlpcontinuetrainingx(s, &s->subset, -1, &s->ngradbatch, &s->session, _state); + if( result ) + { + ae_v_move(&network->weights.ptr.p_double[0], 1, &s->session.network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + } + return result; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +ae_bool _pexec_mlpcontinuetraining(mlptrainer* s, + multilayerperceptron* network, ae_state *_state) +{ + return mlpcontinuetraining(s,network, _state); +} + + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +Modified Levenberg-Marquardt algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglm(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state) +{ + + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(ooberrors); + + mlptrain_mlpebagginginternal(ensemble, xy, npoints, decay, restarts, 0.0, 0, ae_true, info, rep, ooberrors, _state); +} + + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +L-BFGS algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglbfgs(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state) +{ + + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(ooberrors); + + mlptrain_mlpebagginginternal(ensemble, xy, npoints, decay, restarts, wstep, maxits, ae_false, info, rep, ooberrors, _state); +} + + +/************************************************************************* +Training neural networks ensemble using early stopping. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 6, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpetraines(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + ae_int_t ccount; + ae_int_t pcount; + ae_matrix trnxy; + ae_matrix valxy; + ae_int_t trnsize; + ae_int_t valsize; + ae_int_t tmpinfo; + mlpreport tmprep; + modelerrors moderr; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + ae_matrix_init(&trnxy, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&valxy, 0, 0, DT_REAL, _state, ae_true); + _mlpreport_init(&tmprep, _state, ae_true); + _modelerrors_init(&moderr, _state, ae_true); + + nin = mlpgetinputscount(&ensemble->network, _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + wcount = mlpgetweightscount(&ensemble->network, _state); + if( (npoints<2||restarts<1)||ae_fp_less(decay,0) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( mlpissoftmax(&ensemble->network, _state) ) + { + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + *info = 6; + + /* + * allocate + */ + if( mlpissoftmax(&ensemble->network, _state) ) + { + ccount = nin+1; + pcount = nin; + } + else + { + ccount = nin+nout; + pcount = nin+nout; + } + ae_matrix_set_length(&trnxy, npoints, ccount, _state); + ae_matrix_set_length(&valxy, npoints, ccount, _state); + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + + /* + * train networks + */ + for(k=0; k<=ensemble->ensemblesize-1; k++) + { + + /* + * Split set + */ + do + { + trnsize = 0; + valsize = 0; + for(i=0; i<=npoints-1; i++) + { + if( ae_fp_less(ae_randomreal(_state),0.66) ) + { + + /* + * Assign sample to training set + */ + ae_v_move(&trnxy.ptr.pp_double[trnsize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,ccount-1)); + trnsize = trnsize+1; + } + else + { + + /* + * Assign sample to validation set + */ + ae_v_move(&valxy.ptr.pp_double[valsize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,ccount-1)); + valsize = valsize+1; + } + } + } + while(!(trnsize!=0&&valsize!=0)); + + /* + * Train + */ + mlptraines(&ensemble->network, &trnxy, trnsize, &valxy, valsize, decay, restarts, &tmpinfo, &tmprep, _state); + if( tmpinfo<0 ) + { + *info = tmpinfo; + ae_frame_leave(_state); + return; + } + + /* + * save results + */ + ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &ensemble->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1)); + ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcount], 1, &ensemble->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1)); + ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcount], 1, &ensemble->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1)); + rep->ngrad = rep->ngrad+tmprep.ngrad; + rep->nhess = rep->nhess+tmprep.nhess; + rep->ncholesky = rep->ncholesky+tmprep.ncholesky; + } + mlpeallerrorsx(ensemble, xy, &ensemble->network.dummysxy, npoints, 0, &ensemble->network.dummyidx, 0, npoints, 0, &ensemble->network.buf, &moderr, _state); + rep->relclserror = moderr.relclserror; + rep->avgce = moderr.avgce; + rep->rmserror = moderr.rmserror; + rep->avgerror = moderr.avgerror; + rep->avgrelerror = moderr.avgrelerror; + ae_frame_leave(_state); +} + + +/************************************************************************* +This function trains neural network ensemble passed to this function using +current dataset and early stopping training algorithm. Each early stopping +round performs NRestarts random restarts (thus, EnsembleSize*NRestarts +training rounds is performed in total). + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * EnsembleSize training sessions performed for each of ensemble + ! members (always parallelized) + ! * NRestarts training sessions performed within each of training + ! sessions (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object; + Ensemble - neural network ensemble. It must have same number of + inputs and outputs/classes as was specified during + creation of the trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed during each ES round; + * NRestarts=0 is silently replaced by 1. + +OUTPUT PARAMETERS: + Ensemble - trained ensemble; + Rep - it contains all type of errors. + +NOTE: this training method uses BOTH early stopping and weight decay! So, + you should select weight decay before starting training just as you + select it before training "conventional" networks. + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or single-point dataset was passed, ensemble is filled by zero + values. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 22.08.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainensemblees(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t nrestarts, + mlpreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t ntype; + ae_int_t ttype; + ae_shared_pool esessions; + sinteger sgrad; + modelerrors tmprep; + + ae_frame_make(_state, &_frame_block); + _mlpreport_clear(rep); + ae_shared_pool_init(&esessions, _state, ae_true); + _sinteger_init(&sgrad, _state, ae_true); + _modelerrors_init(&tmprep, _state, ae_true); + + ae_assert(s->npoints>=0, "MLPTrainEnsembleES: parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + if( !mlpeissoftmax(ensemble, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + ae_assert(ntype==ttype, "MLPTrainEnsembleES: internal error - type of input network is not similar to network type in trainer object", _state); + nin = mlpgetinputscount(&ensemble->network, _state); + ae_assert(s->nin==nin, "MLPTrainEnsembleES: number of inputs in trainer is not equal to number of inputs in ensemble network", _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + ae_assert(s->nout==nout, "MLPTrainEnsembleES: number of outputs in trainer is not equal to number of outputs in ensemble network", _state); + ae_assert(nrestarts>=0, "MLPTrainEnsembleES: NRestarts<0.", _state); + + /* + * Initialize parameter Rep + */ + rep->relclserror = 0; + rep->avgce = 0; + rep->rmserror = 0; + rep->avgerror = 0; + rep->avgrelerror = 0; + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + + /* + * Allocate + */ + ivectorsetlengthatleast(&s->subset, s->npoints, _state); + ivectorsetlengthatleast(&s->valsubset, s->npoints, _state); + + /* + * Start training + * + * NOTE: ESessions is not initialized because MLPTrainEnsembleX + * needs uninitialized pool. + */ + sgrad.val = 0; + mlptrain_mlptrainensemblex(s, ensemble, 0, ensemble->ensemblesize, nrestarts, 0, &sgrad, ae_true, &esessions, _state); + rep->ngrad = sgrad.val; + + /* + * Calculate errors. + */ + if( s->datatype==0 ) + { + mlpeallerrorsx(ensemble, &s->densexy, &s->sparsexy, s->npoints, 0, &ensemble->network.dummyidx, 0, s->npoints, 0, &ensemble->network.buf, &tmprep, _state); + } + if( s->datatype==1 ) + { + mlpeallerrorsx(ensemble, &s->densexy, &s->sparsexy, s->npoints, 1, &ensemble->network.dummyidx, 0, s->npoints, 0, &ensemble->network.buf, &tmprep, _state); + } + rep->relclserror = tmprep.relclserror; + rep->avgce = tmprep.avgce; + rep->rmserror = tmprep.rmserror; + rep->avgerror = tmprep.avgerror; + rep->avgrelerror = tmprep.avgrelerror; + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_mlptrainensemblees(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t nrestarts, + mlpreport* rep, ae_state *_state) +{ + mlptrainensemblees(s,ensemble,nrestarts,rep, _state); +} + + +/************************************************************************* +Internal cross-validation subroutine +*************************************************************************/ +static void mlptrain_mlpkfoldcvgeneral(multilayerperceptron* n, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t foldscount, + ae_bool lmalgorithm, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t fold; + ae_int_t j; + ae_int_t k; + multilayerperceptron network; + ae_int_t nin; + ae_int_t nout; + ae_int_t rowlen; + ae_int_t wcount; + ae_int_t nclasses; + ae_int_t tssize; + ae_int_t cvssize; + ae_matrix cvset; + ae_matrix testset; + ae_vector folds; + ae_int_t relcnt; + mlpreport internalrep; + ae_vector x; + ae_vector y; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(cvrep); + _multilayerperceptron_init(&network, _state, ae_true); + ae_matrix_init(&cvset, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&testset, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&folds, 0, DT_INT, _state, ae_true); + _mlpreport_init(&internalrep, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + + /* + * Read network geometry, test parameters + */ + mlpproperties(n, &nin, &nout, &wcount, _state); + if( mlpissoftmax(n, _state) ) + { + nclasses = nout; + rowlen = nin+1; + } + else + { + nclasses = -nout; + rowlen = nin+nout; + } + if( (npoints<=0||foldscount<2)||foldscount>npoints ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + mlpcopy(n, &network, _state); + + /* + * K-fold out cross-validation. + * First, estimate generalization error + */ + ae_matrix_set_length(&testset, npoints-1+1, rowlen-1+1, _state); + ae_matrix_set_length(&cvset, npoints-1+1, rowlen-1+1, _state); + ae_vector_set_length(&x, nin-1+1, _state); + ae_vector_set_length(&y, nout-1+1, _state); + mlptrain_mlpkfoldsplit(xy, npoints, nclasses, foldscount, ae_false, &folds, _state); + cvrep->relclserror = 0; + cvrep->avgce = 0; + cvrep->rmserror = 0; + cvrep->avgerror = 0; + cvrep->avgrelerror = 0; + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + relcnt = 0; + for(fold=0; fold<=foldscount-1; fold++) + { + + /* + * Separate set + */ + tssize = 0; + cvssize = 0; + for(i=0; i<=npoints-1; i++) + { + if( folds.ptr.p_int[i]==fold ) + { + ae_v_move(&testset.ptr.pp_double[tssize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,rowlen-1)); + tssize = tssize+1; + } + else + { + ae_v_move(&cvset.ptr.pp_double[cvssize][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,rowlen-1)); + cvssize = cvssize+1; + } + } + + /* + * Train on CV training set + */ + if( lmalgorithm ) + { + mlptrainlm(&network, &cvset, cvssize, decay, restarts, info, &internalrep, _state); + } + else + { + mlptrainlbfgs(&network, &cvset, cvssize, decay, restarts, wstep, maxits, info, &internalrep, _state); + } + if( *info<0 ) + { + cvrep->relclserror = 0; + cvrep->avgce = 0; + cvrep->rmserror = 0; + cvrep->avgerror = 0; + cvrep->avgrelerror = 0; + ae_frame_leave(_state); + return; + } + rep->ngrad = rep->ngrad+internalrep.ngrad; + rep->nhess = rep->nhess+internalrep.nhess; + rep->ncholesky = rep->ncholesky+internalrep.ncholesky; + + /* + * Estimate error using CV test set + */ + if( mlpissoftmax(&network, _state) ) + { + + /* + * classification-only code + */ + cvrep->relclserror = cvrep->relclserror+mlpclserror(&network, &testset, tssize, _state); + cvrep->avgce = cvrep->avgce+mlperrorn(&network, &testset, tssize, _state); + } + for(i=0; i<=tssize-1; i++) + { + ae_v_move(&x.ptr.p_double[0], 1, &testset.ptr.pp_double[i][0], 1, ae_v_len(0,nin-1)); + mlpprocess(&network, &x, &y, _state); + if( mlpissoftmax(&network, _state) ) + { + + /* + * Classification-specific code + */ + k = ae_round(testset.ptr.pp_double[i][nin], _state); + for(j=0; j<=nout-1; j++) + { + if( j==k ) + { + cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j]-1, _state); + cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j]-1, _state); + cvrep->avgrelerror = cvrep->avgrelerror+ae_fabs(y.ptr.p_double[j]-1, _state); + relcnt = relcnt+1; + } + else + { + cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j], _state); + cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j], _state); + } + } + } + else + { + + /* + * Regression-specific code + */ + for(j=0; j<=nout-1; j++) + { + cvrep->rmserror = cvrep->rmserror+ae_sqr(y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j], _state); + cvrep->avgerror = cvrep->avgerror+ae_fabs(y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j], _state); + if( ae_fp_neq(testset.ptr.pp_double[i][nin+j],0) ) + { + cvrep->avgrelerror = cvrep->avgrelerror+ae_fabs((y.ptr.p_double[j]-testset.ptr.pp_double[i][nin+j])/testset.ptr.pp_double[i][nin+j], _state); + relcnt = relcnt+1; + } + } + } + } + } + if( mlpissoftmax(&network, _state) ) + { + cvrep->relclserror = cvrep->relclserror/npoints; + cvrep->avgce = cvrep->avgce/(ae_log(2, _state)*npoints); + } + cvrep->rmserror = ae_sqrt(cvrep->rmserror/(npoints*nout), _state); + cvrep->avgerror = cvrep->avgerror/(npoints*nout); + if( relcnt>0 ) + { + cvrep->avgrelerror = cvrep->avgrelerror/relcnt; + } + *info = 1; + ae_frame_leave(_state); +} + + +/************************************************************************* +Subroutine prepares K-fold split of the training set. + +NOTES: + "NClasses>0" means that we have classification task. + "NClasses<0" means regression task with -NClasses real outputs. +*************************************************************************/ +static void mlptrain_mlpkfoldsplit(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nclasses, + ae_int_t foldscount, + ae_bool stratifiedsplits, + /* Integer */ ae_vector* folds, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(folds); + _hqrndstate_init(&rs, _state, ae_true); + + + /* + * test parameters + */ + ae_assert(npoints>0, "MLPKFoldSplit: wrong NPoints!", _state); + ae_assert(nclasses>1||nclasses<0, "MLPKFoldSplit: wrong NClasses!", _state); + ae_assert(foldscount>=2&&foldscount<=npoints, "MLPKFoldSplit: wrong FoldsCount!", _state); + ae_assert(!stratifiedsplits, "MLPKFoldSplit: stratified splits are not supported!", _state); + + /* + * Folds + */ + hqrndrandomize(&rs, _state); + ae_vector_set_length(folds, npoints-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + folds->ptr.p_int[i] = i*foldscount/npoints; + } + for(i=0; i<=npoints-2; i++) + { + j = i+hqrnduniformi(&rs, npoints-i, _state); + if( j!=i ) + { + k = folds->ptr.p_int[i]; + folds->ptr.p_int[i] = folds->ptr.p_int[j]; + folds->ptr.p_int[j] = k; + } + } + ae_frame_leave(_state); +} + + +static void mlptrain_mthreadcv(mlptrainer* s, + ae_int_t rowsize, + ae_int_t nrestarts, + /* Integer */ ae_vector* folds, + ae_int_t fold, + ae_int_t dfold, + /* Real */ ae_matrix* cvy, + ae_shared_pool* pooldatacv, + ae_state *_state) +{ + ae_frame _frame_block; + mlpparallelizationcv *datacv; + ae_smart_ptr _datacv; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_smart_ptr_init(&_datacv, (void**)&datacv, _state, ae_true); + + if( fold==dfold-1 ) + { + + /* + * Separate set + */ + ae_shared_pool_retrieve(pooldatacv, &_datacv, _state); + datacv->subsetsize = 0; + for(i=0; i<=s->npoints-1; i++) + { + if( folds->ptr.p_int[i]!=fold ) + { + datacv->subset.ptr.p_int[datacv->subsetsize] = i; + datacv->subsetsize = datacv->subsetsize+1; + } + } + + /* + * Train on CV training set + */ + mlptrain_mlptrainnetworkx(s, nrestarts, -1, &datacv->subset, datacv->subsetsize, &datacv->subset, 0, &datacv->network, &datacv->rep, ae_true, &datacv->trnpool, _state); + datacv->ngrad = datacv->ngrad+datacv->rep.ngrad; + + /* + * Estimate error using CV test set + */ + for(i=0; i<=s->npoints-1; i++) + { + if( folds->ptr.p_int[i]==fold ) + { + if( s->datatype==0 ) + { + ae_v_move(&datacv->xyrow.ptr.p_double[0], 1, &s->densexy.ptr.pp_double[i][0], 1, ae_v_len(0,rowsize-1)); + } + if( s->datatype==1 ) + { + sparsegetrow(&s->sparsexy, i, &datacv->xyrow, _state); + } + mlpprocess(&datacv->network, &datacv->xyrow, &datacv->y, _state); + ae_v_move(&cvy->ptr.pp_double[i][0], 1, &datacv->y.ptr.p_double[0], 1, ae_v_len(0,s->nout-1)); + } + } + ae_shared_pool_recycle(pooldatacv, &_datacv, _state); + } + else + { + ae_assert(foldDFold-1).", _state); + mlptrain_mthreadcv(s, rowsize, nrestarts, folds, fold, (fold+dfold)/2, cvy, pooldatacv, _state); + mlptrain_mthreadcv(s, rowsize, nrestarts, folds, (fold+dfold)/2, dfold, cvy, pooldatacv, _state); + } + ae_frame_leave(_state); +} + + +static void mlptrain_mlptrainnetworkx(mlptrainer* s, + ae_int_t nrestarts, + ae_int_t algokind, + /* Integer */ ae_vector* trnsubset, + ae_int_t trnsubsetsize, + /* Integer */ ae_vector* valsubset, + ae_int_t valsubsetsize, + multilayerperceptron* network, + mlpreport* rep, + ae_bool isrootcall, + ae_shared_pool* sessions, + ae_state *_state) +{ + ae_frame _frame_block; + modelerrors modrep; + double eval; + double ebest; + ae_int_t ngradbatch; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t pcount; + ae_int_t itbest; + ae_int_t itcnt; + ae_int_t ntype; + ae_int_t ttype; + ae_bool rndstart; + ae_int_t i; + ae_int_t nr0; + ae_int_t nr1; + mlpreport rep0; + mlpreport rep1; + ae_bool randomizenetwork; + double bestrmserror; + smlptrnsession *psession; + ae_smart_ptr _psession; + + ae_frame_make(_state, &_frame_block); + _modelerrors_init(&modrep, _state, ae_true); + _mlpreport_init(&rep0, _state, ae_true); + _mlpreport_init(&rep1, _state, ae_true); + ae_smart_ptr_init(&_psession, (void**)&psession, _state, ae_true); + + mlpproperties(network, &nin, &nout, &wcount, _state); + + /* + * Process root call + */ + if( isrootcall ) + { + + /* + * Check correctness of parameters + */ + ae_assert(algokind==0||algokind==-1, "MLPTrainNetworkX: unexpected AlgoKind", _state); + ae_assert(s->npoints>=0, "MLPTrainNetworkX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + if( !mlpissoftmax(network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + ae_assert(ntype==ttype, "MLPTrainNetworkX: internal error - type of the training network is not similar to network type in trainer object", _state); + ae_assert(s->nin==nin, "MLPTrainNetworkX: internal error - number of inputs in trainer is not equal to number of inputs in the training network.", _state); + ae_assert(s->nout==nout, "MLPTrainNetworkX: internal error - number of outputs in trainer is not equal to number of outputs in the training network.", _state); + ae_assert(nrestarts>=0, "MLPTrainNetworkX: internal error - NRestarts<0.", _state); + ae_assert(trnsubset->cnt>=trnsubsetsize, "MLPTrainNetworkX: internal error - parameter TrnSubsetSize more than input subset size(Length(TrnSubset)ptr.p_int[i]>=0&&trnsubset->ptr.p_int[i]<=s->npoints-1, "MLPTrainNetworkX: internal error - parameter TrnSubset contains incorrect index(TrnSubset[I]<0 or TrnSubset[I]>S.NPoints-1)", _state); + } + ae_assert(valsubset->cnt>=valsubsetsize, "MLPTrainNetworkX: internal error - parameter ValSubsetSize more than input subset size(Length(ValSubset)ptr.p_int[i]>=0&&valsubset->ptr.p_int[i]<=s->npoints-1, "MLPTrainNetworkX: internal error - parameter ValSubset contains incorrect index(ValSubset[I]<0 or ValSubset[I]>S.NPoints-1)", _state); + } + + /* + * Train + */ + randomizenetwork = nrestarts>0; + mlptrain_initmlptrnsessions(network, randomizenetwork, s, sessions, _state); + mlptrain_mlptrainnetworkx(s, nrestarts, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, rep, ae_false, sessions, _state); + + /* + * Choose best network + */ + bestrmserror = ae_maxrealnumber; + ae_shared_pool_first_recycled(sessions, &_psession, _state); + while(psession!=NULL) + { + if( ae_fp_less(psession->bestrmserror,bestrmserror) ) + { + mlpimporttunableparameters(network, &psession->bestparameters, _state); + bestrmserror = psession->bestrmserror; + } + ae_shared_pool_next_recycled(sessions, &_psession, _state); + } + + /* + * Calculate errors + */ + if( s->datatype==0 ) + { + mlpallerrorssubset(network, &s->densexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state); + } + if( s->datatype==1 ) + { + mlpallerrorssparsesubset(network, &s->sparsexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state); + } + rep->relclserror = modrep.relclserror; + rep->avgce = modrep.avgce; + rep->rmserror = modrep.rmserror; + rep->avgerror = modrep.avgerror; + rep->avgrelerror = modrep.avgrelerror; + + /* + * Done + */ + ae_frame_leave(_state); + return; + } + + /* + * Split problem, if we have more than 1 restart + */ + if( nrestarts>=2 ) + { + + /* + * Divide problem with NRestarts into two: NR0 and NR1. + */ + nr0 = nrestarts/2; + nr1 = nrestarts-nr0; + mlptrain_mlptrainnetworkx(s, nr0, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, &rep0, ae_false, sessions, _state); + mlptrain_mlptrainnetworkx(s, nr1, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, &rep1, ae_false, sessions, _state); + + /* + * Aggregate results + */ + rep->ngrad = rep0.ngrad+rep1.ngrad; + rep->nhess = rep0.nhess+rep1.nhess; + rep->ncholesky = rep0.ncholesky+rep1.ncholesky; + + /* + * Done :) + */ + ae_frame_leave(_state); + return; + } + + /* + * Execution with NRestarts=1 or NRestarts=0: + * * NRestarts=1 means that network is restarted from random position + * * NRestarts=0 means that network is not randomized + */ + ae_assert(nrestarts==0||nrestarts==1, "MLPTrainNetworkX: internal error", _state); + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + ae_shared_pool_retrieve(sessions, &_psession, _state); + if( ((s->datatype==0||s->datatype==1)&&s->npoints>0)&&trnsubsetsize!=0 ) + { + + /* + * Train network using combination of early stopping and step-size + * and step-count based criteria. Network state with best value of + * validation set error is stored in WBuf0. When validation set is + * zero, most recent state of network is stored. + */ + rndstart = nrestarts!=0; + ngradbatch = 0; + eval = 0; + ebest = 0; + itbest = 0; + itcnt = 0; + mlptrain_mlpstarttrainingx(s, rndstart, algokind, trnsubset, trnsubsetsize, psession, _state); + if( s->datatype==0 ) + { + ebest = mlperrorsubset(&psession->network, &s->densexy, s->npoints, valsubset, valsubsetsize, _state); + } + if( s->datatype==1 ) + { + ebest = mlperrorsparsesubset(&psession->network, &s->sparsexy, s->npoints, valsubset, valsubsetsize, _state); + } + ae_v_move(&psession->wbuf0.ptr.p_double[0], 1, &psession->network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + while(mlptrain_mlpcontinuetrainingx(s, trnsubset, trnsubsetsize, &ngradbatch, psession, _state)) + { + if( s->datatype==0 ) + { + eval = mlperrorsubset(&psession->network, &s->densexy, s->npoints, valsubset, valsubsetsize, _state); + } + if( s->datatype==1 ) + { + eval = mlperrorsparsesubset(&psession->network, &s->sparsexy, s->npoints, valsubset, valsubsetsize, _state); + } + if( ae_fp_less_eq(eval,ebest)||valsubsetsize==0 ) + { + ae_v_move(&psession->wbuf0.ptr.p_double[0], 1, &psession->network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + ebest = eval; + itbest = itcnt; + } + if( itcnt>30&&ae_fp_greater(itcnt,1.5*itbest) ) + { + break; + } + itcnt = itcnt+1; + } + ae_v_move(&psession->network.weights.ptr.p_double[0], 1, &psession->wbuf0.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + rep->ngrad = ngradbatch; + } + else + { + for(i=0; i<=wcount-1; i++) + { + psession->network.weights.ptr.p_double[i] = 0; + } + } + + /* + * Evaluate network performance and update PSession.BestParameters/BestRMSError + * (if needed). + */ + if( s->datatype==0 ) + { + mlpallerrorssubset(&psession->network, &s->densexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state); + } + if( s->datatype==1 ) + { + mlpallerrorssparsesubset(&psession->network, &s->sparsexy, s->npoints, trnsubset, trnsubsetsize, &modrep, _state); + } + if( ae_fp_less(modrep.rmserror,psession->bestrmserror) ) + { + mlpexporttunableparameters(&psession->network, &psession->bestparameters, &pcount, _state); + psession->bestrmserror = modrep.rmserror; + } + + /* + * Move session back to pool + */ + ae_shared_pool_recycle(sessions, &_psession, _state); + ae_frame_leave(_state); +} + + +static void mlptrain_mlptrainensemblex(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t idx0, + ae_int_t idx1, + ae_int_t nrestarts, + ae_int_t trainingmethod, + sinteger* ngrad, + ae_bool isrootcall, + ae_shared_pool* esessions, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t pcount; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t trnsubsetsize; + ae_int_t valsubsetsize; + ae_int_t k0; + sinteger ngrad0; + sinteger ngrad1; + mlpetrnsession *psession; + ae_smart_ptr _psession; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + _sinteger_init(&ngrad0, _state, ae_true); + _sinteger_init(&ngrad1, _state, ae_true); + ae_smart_ptr_init(&_psession, (void**)&psession, _state, ae_true); + _hqrndstate_init(&rs, _state, ae_true); + + nin = mlpgetinputscount(&ensemble->network, _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + wcount = mlpgetweightscount(&ensemble->network, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + pcount = nin; + } + else + { + pcount = nin+nout; + } + if( nrestarts<=0 ) + { + nrestarts = 1; + } + + /* + * Handle degenerate case + */ + if( s->npoints<2 ) + { + for(i=idx0; i<=idx1-1; i++) + { + for(j=0; j<=wcount-1; j++) + { + ensemble->weights.ptr.p_double[i*wcount+j] = 0.0; + } + for(j=0; j<=pcount-1; j++) + { + ensemble->columnmeans.ptr.p_double[i*pcount+j] = 0.0; + ensemble->columnsigmas.ptr.p_double[i*pcount+j] = 1.0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Process root call + */ + if( isrootcall ) + { + + /* + * Prepare: + * * prepare MLPETrnSessions + * * fill ensemble by zeros (helps to detect errors) + */ + mlptrain_initmlpetrnsessions(&ensemble->network, s, esessions, _state); + for(i=idx0; i<=idx1-1; i++) + { + for(j=0; j<=wcount-1; j++) + { + ensemble->weights.ptr.p_double[i*wcount+j] = 0.0; + } + for(j=0; j<=pcount-1; j++) + { + ensemble->columnmeans.ptr.p_double[i*pcount+j] = 0.0; + ensemble->columnsigmas.ptr.p_double[i*pcount+j] = 0.0; + } + } + + /* + * Train in non-root mode and exit + */ + mlptrain_mlptrainensemblex(s, ensemble, idx0, idx1, nrestarts, trainingmethod, ngrad, ae_false, esessions, _state); + ae_frame_leave(_state); + return; + } + + /* + * Split problem + */ + if( idx1-idx0>=2 ) + { + k0 = (idx1-idx0)/2; + ngrad0.val = 0; + ngrad1.val = 0; + mlptrain_mlptrainensemblex(s, ensemble, idx0, idx0+k0, nrestarts, trainingmethod, &ngrad0, ae_false, esessions, _state); + mlptrain_mlptrainensemblex(s, ensemble, idx0+k0, idx1, nrestarts, trainingmethod, &ngrad1, ae_false, esessions, _state); + ngrad->val = ngrad0.val+ngrad1.val; + ae_frame_leave(_state); + return; + } + + /* + * Retrieve and prepare session + */ + ae_shared_pool_retrieve(esessions, &_psession, _state); + + /* + * Train + */ + hqrndrandomize(&rs, _state); + for(k=idx0; k<=idx1-1; k++) + { + + /* + * Split set + */ + trnsubsetsize = 0; + valsubsetsize = 0; + if( trainingmethod==0 ) + { + do + { + trnsubsetsize = 0; + valsubsetsize = 0; + for(i=0; i<=s->npoints-1; i++) + { + if( ae_fp_less(ae_randomreal(_state),0.66) ) + { + + /* + * Assign sample to training set + */ + psession->trnsubset.ptr.p_int[trnsubsetsize] = i; + trnsubsetsize = trnsubsetsize+1; + } + else + { + + /* + * Assign sample to validation set + */ + psession->valsubset.ptr.p_int[valsubsetsize] = i; + valsubsetsize = valsubsetsize+1; + } + } + } + while(!(trnsubsetsize!=0&&valsubsetsize!=0)); + } + if( trainingmethod==1 ) + { + valsubsetsize = 0; + trnsubsetsize = s->npoints; + for(i=0; i<=s->npoints-1; i++) + { + psession->trnsubset.ptr.p_int[i] = hqrnduniformi(&rs, s->npoints, _state); + } + } + + /* + * Train + */ + mlptrain_mlptrainnetworkx(s, nrestarts, -1, &psession->trnsubset, trnsubsetsize, &psession->valsubset, valsubsetsize, &psession->network, &psession->mlprep, ae_true, &psession->mlpsessions, _state); + ngrad->val = ngrad->val+psession->mlprep.ngrad; + + /* + * Save results + */ + ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &psession->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1)); + ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcount], 1, &psession->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1)); + ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcount], 1, &psession->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcount,(k+1)*pcount-1)); + } + + /* + * Recycle session + */ + ae_shared_pool_recycle(esessions, &_psession, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTrainingX call, +and then user subsequently calls MLPContinueTrainingX to perform one more +iteration of the training. + +After call to this function trainer object remembers network and is ready +to train it. However, no training is performed until first call to +MLPContinueTraining() function. Subsequent calls to MLPContinueTraining() +will advance traing progress one iteration further. + + + -- ALGLIB -- + Copyright 13.08.2012 by Bochkanov Sergey +*************************************************************************/ +static void mlptrain_mlpstarttrainingx(mlptrainer* s, + ae_bool randomstart, + ae_int_t algokind, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + smlptrnsession* session, + ae_state *_state) +{ + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t ntype; + ae_int_t ttype; + ae_int_t i; + + + + /* + * Check parameters + */ + ae_assert(s->npoints>=0, "MLPStartTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)", _state); + ae_assert(algokind==0||algokind==-1, "MLPStartTrainingX: unexpected AlgoKind", _state); + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + if( !mlpissoftmax(&session->network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + ae_assert(ntype==ttype, "MLPStartTrainingX: internal error - type of the resulting network is not similar to network type in trainer object", _state); + mlpproperties(&session->network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPStartTrainingX: number of inputs in trainer is not equal to number of inputs in the network.", _state); + ae_assert(s->nout==nout, "MLPStartTrainingX: number of outputs in trainer is not equal to number of outputs in the network.", _state); + ae_assert(subset->cnt>=subsetsize, "MLPStartTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)ptr.p_int[i]>=0&&subset->ptr.p_int[i]<=s->npoints-1, "MLPStartTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)", _state); + } + + /* + * Prepare session + */ + minlbfgssetcond(&session->optimizer, 0.0, 0.0, s->wstep, s->maxits, _state); + if( s->npoints>0&&subsetsize!=0 ) + { + if( randomstart ) + { + mlprandomize(&session->network, _state); + } + minlbfgsrestartfrom(&session->optimizer, &session->network.weights, _state); + } + else + { + for(i=0; i<=wcount-1; i++) + { + session->network.weights.ptr.p_double[i] = 0; + } + } + if( algokind==-1 ) + { + session->algoused = s->algokind; + if( s->algokind==1 ) + { + session->minibatchsize = s->minibatchsize; + } + } + else + { + session->algoused = 0; + } + hqrndrandomize(&session->generator, _state); + ae_vector_set_length(&session->rstate.ia, 15+1, _state); + ae_vector_set_length(&session->rstate.ra, 1+1, _state); + session->rstate.stage = -1; +} + + +/************************************************************************* +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTrainingX call, +and then user subsequently calls MLPContinueTrainingX to perform one more +iteration of the training. + +This function performs one more iteration of the training and returns +either True (training continues) or False (training stopped). In case True +was returned, Network weights are updated according to the current state +of the optimization progress. In case False was returned, no additional +updates is performed (previous update of the network weights moved us to +the final point, and no additional updates is needed). + +EXAMPLE: + > + > [initialize network and trainer object] + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > [visualize training progress] + > + + + -- ALGLIB -- + Copyright 13.08.2012 by Bochkanov Sergey +*************************************************************************/ +static ae_bool mlptrain_mlpcontinuetrainingx(mlptrainer* s, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_int_t* ngradbatch, + smlptrnsession* session, + ae_state *_state) +{ + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t twcount; + ae_int_t ntype; + ae_int_t ttype; + double decay; + double v; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t trnsetsize; + ae_int_t epoch; + ae_int_t minibatchcount; + ae_int_t minibatchidx; + ae_int_t cursize; + ae_int_t idx0; + ae_int_t idx1; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( session->rstate.stage>=0 ) + { + nin = session->rstate.ia.ptr.p_int[0]; + nout = session->rstate.ia.ptr.p_int[1]; + wcount = session->rstate.ia.ptr.p_int[2]; + twcount = session->rstate.ia.ptr.p_int[3]; + ntype = session->rstate.ia.ptr.p_int[4]; + ttype = session->rstate.ia.ptr.p_int[5]; + i = session->rstate.ia.ptr.p_int[6]; + j = session->rstate.ia.ptr.p_int[7]; + k = session->rstate.ia.ptr.p_int[8]; + trnsetsize = session->rstate.ia.ptr.p_int[9]; + epoch = session->rstate.ia.ptr.p_int[10]; + minibatchcount = session->rstate.ia.ptr.p_int[11]; + minibatchidx = session->rstate.ia.ptr.p_int[12]; + cursize = session->rstate.ia.ptr.p_int[13]; + idx0 = session->rstate.ia.ptr.p_int[14]; + idx1 = session->rstate.ia.ptr.p_int[15]; + decay = session->rstate.ra.ptr.p_double[0]; + v = session->rstate.ra.ptr.p_double[1]; + } + else + { + nin = -983; + nout = -989; + wcount = -834; + twcount = 900; + ntype = -287; + ttype = 364; + i = 214; + j = -338; + k = -686; + trnsetsize = 912; + epoch = 585; + minibatchcount = 497; + minibatchidx = -271; + cursize = -581; + idx0 = 745; + idx1 = -533; + decay = -77; + v = 678; + } + if( session->rstate.stage==0 ) + { + goto lbl_0; + } + + /* + * Routine body + */ + + /* + * Check correctness of inputs + */ + ae_assert(s->npoints>=0, "MLPContinueTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0).", _state); + if( s->rcpar ) + { + ttype = 0; + } + else + { + ttype = 1; + } + if( !mlpissoftmax(&session->network, _state) ) + { + ntype = 0; + } + else + { + ntype = 1; + } + ae_assert(ntype==ttype, "MLPContinueTrainingX: internal error - type of the resulting network is not similar to network type in trainer object.", _state); + mlpproperties(&session->network, &nin, &nout, &wcount, _state); + ae_assert(s->nin==nin, "MLPContinueTrainingX: internal error - number of inputs in trainer is not equal to number of inputs in the network.", _state); + ae_assert(s->nout==nout, "MLPContinueTrainingX: internal error - number of outputs in trainer is not equal to number of outputs in the network.", _state); + ae_assert(subset->cnt>=subsetsize, "MLPContinueTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)ptr.p_int[i]>=0&&subset->ptr.p_int[i]<=s->npoints-1, "MLPContinueTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1).", _state); + } + + /* + * Quick exit on empty training set + */ + if( s->npoints==0||subsetsize==0 ) + { + result = ae_false; + return result; + } + + /* + * Minibatch training + */ + if( session->algoused==1 ) + { + ae_assert(ae_false, "MINIBATCH TRAINING IS NOT IMPLEMENTED YET", _state); + } + + /* + * Last option: full batch training + */ + decay = s->decay; +lbl_1: + if( !minlbfgsiteration(&session->optimizer, _state) ) + { + goto lbl_2; + } + if( !session->optimizer.xupdated ) + { + goto lbl_3; + } + ae_v_move(&session->network.weights.ptr.p_double[0], 1, &session->optimizer.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + session->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: +lbl_3: + ae_v_move(&session->network.weights.ptr.p_double[0], 1, &session->optimizer.x.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + if( s->datatype==0 ) + { + mlpgradbatchsubset(&session->network, &s->densexy, s->npoints, subset, subsetsize, &session->optimizer.f, &session->optimizer.g, _state); + } + if( s->datatype==1 ) + { + mlpgradbatchsparsesubset(&session->network, &s->sparsexy, s->npoints, subset, subsetsize, &session->optimizer.f, &session->optimizer.g, _state); + } + + /* + * Increment number of operations performed on batch gradient + */ + *ngradbatch = *ngradbatch+1; + v = ae_v_dotproduct(&session->network.weights.ptr.p_double[0], 1, &session->network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1)); + session->optimizer.f = session->optimizer.f+0.5*decay*v; + ae_v_addd(&session->optimizer.g.ptr.p_double[0], 1, &session->network.weights.ptr.p_double[0], 1, ae_v_len(0,wcount-1), decay); + goto lbl_1; +lbl_2: + minlbfgsresultsbuf(&session->optimizer, &session->network.weights, &session->optimizerrep, _state); + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + session->rstate.ia.ptr.p_int[0] = nin; + session->rstate.ia.ptr.p_int[1] = nout; + session->rstate.ia.ptr.p_int[2] = wcount; + session->rstate.ia.ptr.p_int[3] = twcount; + session->rstate.ia.ptr.p_int[4] = ntype; + session->rstate.ia.ptr.p_int[5] = ttype; + session->rstate.ia.ptr.p_int[6] = i; + session->rstate.ia.ptr.p_int[7] = j; + session->rstate.ia.ptr.p_int[8] = k; + session->rstate.ia.ptr.p_int[9] = trnsetsize; + session->rstate.ia.ptr.p_int[10] = epoch; + session->rstate.ia.ptr.p_int[11] = minibatchcount; + session->rstate.ia.ptr.p_int[12] = minibatchidx; + session->rstate.ia.ptr.p_int[13] = cursize; + session->rstate.ia.ptr.p_int[14] = idx0; + session->rstate.ia.ptr.p_int[15] = idx1; + session->rstate.ra.ptr.p_double[0] = decay; + session->rstate.ra.ptr.p_double[1] = v; + return result; +} + + +/************************************************************************* +Internal bagging subroutine. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +static void mlptrain_mlpebagginginternal(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_bool lmalgorithm, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix xys; + ae_vector s; + ae_matrix oobbuf; + ae_vector oobcntbuf; + ae_vector x; + ae_vector y; + ae_vector dy; + ae_vector dsbuf; + ae_int_t ccnt; + ae_int_t pcnt; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + mlpreport tmprep; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _mlpreport_clear(rep); + _mlpcvreport_clear(ooberrors); + ae_matrix_init(&xys, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&s, 0, DT_BOOL, _state, ae_true); + ae_matrix_init(&oobbuf, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&oobcntbuf, 0, DT_INT, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dsbuf, 0, DT_REAL, _state, ae_true); + _mlpreport_init(&tmprep, _state, ae_true); + _hqrndstate_init(&rs, _state, ae_true); + + nin = mlpgetinputscount(&ensemble->network, _state); + nout = mlpgetoutputscount(&ensemble->network, _state); + wcount = mlpgetweightscount(&ensemble->network, _state); + + /* + * Test for inputs + */ + if( (!lmalgorithm&&ae_fp_eq(wstep,0))&&maxits==0 ) + { + *info = -8; + ae_frame_leave(_state); + return; + } + if( ((npoints<=0||restarts<1)||ae_fp_less(wstep,0))||maxits<0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( mlpissoftmax(&ensemble->network, _state) ) + { + for(i=0; i<=npoints-1; i++) + { + if( ae_round(xy->ptr.pp_double[i][nin], _state)<0||ae_round(xy->ptr.pp_double[i][nin], _state)>=nout ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + } + + /* + * allocate temporaries + */ + *info = 2; + rep->ngrad = 0; + rep->nhess = 0; + rep->ncholesky = 0; + ooberrors->relclserror = 0; + ooberrors->avgce = 0; + ooberrors->rmserror = 0; + ooberrors->avgerror = 0; + ooberrors->avgrelerror = 0; + if( mlpissoftmax(&ensemble->network, _state) ) + { + ccnt = nin+1; + pcnt = nin; + } + else + { + ccnt = nin+nout; + pcnt = nin+nout; + } + ae_matrix_set_length(&xys, npoints, ccnt, _state); + ae_vector_set_length(&s, npoints, _state); + ae_matrix_set_length(&oobbuf, npoints, nout, _state); + ae_vector_set_length(&oobcntbuf, npoints, _state); + ae_vector_set_length(&x, nin, _state); + ae_vector_set_length(&y, nout, _state); + if( mlpissoftmax(&ensemble->network, _state) ) + { + ae_vector_set_length(&dy, 1, _state); + } + else + { + ae_vector_set_length(&dy, nout, _state); + } + for(i=0; i<=npoints-1; i++) + { + for(j=0; j<=nout-1; j++) + { + oobbuf.ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=npoints-1; i++) + { + oobcntbuf.ptr.p_int[i] = 0; + } + + /* + * main bagging cycle + */ + hqrndrandomize(&rs, _state); + for(k=0; k<=ensemble->ensemblesize-1; k++) + { + + /* + * prepare dataset + */ + for(i=0; i<=npoints-1; i++) + { + s.ptr.p_bool[i] = ae_false; + } + for(i=0; i<=npoints-1; i++) + { + j = hqrnduniformi(&rs, npoints, _state); + s.ptr.p_bool[j] = ae_true; + ae_v_move(&xys.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[j][0], 1, ae_v_len(0,ccnt-1)); + } + + /* + * train + */ + if( lmalgorithm ) + { + mlptrainlm(&ensemble->network, &xys, npoints, decay, restarts, info, &tmprep, _state); + } + else + { + mlptrainlbfgs(&ensemble->network, &xys, npoints, decay, restarts, wstep, maxits, info, &tmprep, _state); + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * save results + */ + rep->ngrad = rep->ngrad+tmprep.ngrad; + rep->nhess = rep->nhess+tmprep.nhess; + rep->ncholesky = rep->ncholesky+tmprep.ncholesky; + ae_v_move(&ensemble->weights.ptr.p_double[k*wcount], 1, &ensemble->network.weights.ptr.p_double[0], 1, ae_v_len(k*wcount,(k+1)*wcount-1)); + ae_v_move(&ensemble->columnmeans.ptr.p_double[k*pcnt], 1, &ensemble->network.columnmeans.ptr.p_double[0], 1, ae_v_len(k*pcnt,(k+1)*pcnt-1)); + ae_v_move(&ensemble->columnsigmas.ptr.p_double[k*pcnt], 1, &ensemble->network.columnsigmas.ptr.p_double[0], 1, ae_v_len(k*pcnt,(k+1)*pcnt-1)); + + /* + * OOB estimates + */ + for(i=0; i<=npoints-1; i++) + { + if( !s.ptr.p_bool[i] ) + { + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nin-1)); + mlpprocess(&ensemble->network, &x, &y, _state); + ae_v_add(&oobbuf.ptr.pp_double[i][0], 1, &y.ptr.p_double[0], 1, ae_v_len(0,nout-1)); + oobcntbuf.ptr.p_int[i] = oobcntbuf.ptr.p_int[i]+1; + } + } + } + + /* + * OOB estimates + */ + if( mlpissoftmax(&ensemble->network, _state) ) + { + dserrallocate(nout, &dsbuf, _state); + } + else + { + dserrallocate(-nout, &dsbuf, _state); + } + for(i=0; i<=npoints-1; i++) + { + if( oobcntbuf.ptr.p_int[i]!=0 ) + { + v = (double)1/(double)oobcntbuf.ptr.p_int[i]; + ae_v_moved(&y.ptr.p_double[0], 1, &oobbuf.ptr.pp_double[i][0], 1, ae_v_len(0,nout-1), v); + if( mlpissoftmax(&ensemble->network, _state) ) + { + dy.ptr.p_double[0] = xy->ptr.pp_double[i][nin]; + } + else + { + ae_v_moved(&dy.ptr.p_double[0], 1, &xy->ptr.pp_double[i][nin], 1, ae_v_len(0,nout-1), v); + } + dserraccumulate(&dsbuf, &y, &dy, _state); + } + } + dserrfinish(&dsbuf, _state); + ooberrors->relclserror = dsbuf.ptr.p_double[0]; + ooberrors->avgce = dsbuf.ptr.p_double[1]; + ooberrors->rmserror = dsbuf.ptr.p_double[2]; + ooberrors->avgerror = dsbuf.ptr.p_double[3]; + ooberrors->avgrelerror = dsbuf.ptr.p_double[4]; + ae_frame_leave(_state); +} + + +/************************************************************************* +This function initializes temporaries needed for training session. + + + -- ALGLIB -- + Copyright 01.07.2013 by Bochkanov Sergey +*************************************************************************/ +static void mlptrain_initmlptrnsession(multilayerperceptron* networktrained, + ae_bool randomizenetwork, + mlptrainer* trainer, + smlptrnsession* session, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t nin; + ae_int_t nout; + ae_int_t wcount; + ae_int_t pcount; + ae_vector dummysubset; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&dummysubset, 0, DT_INT, _state, ae_true); + + + /* + * Prepare network: + * * copy input network to Session.Network + * * re-initialize preprocessor and weights if RandomizeNetwork=True + */ + mlpcopy(networktrained, &session->network, _state); + if( randomizenetwork ) + { + ae_assert(trainer->datatype==0||trainer->datatype==1, "InitTemporaries: unexpected Trainer.DataType", _state); + if( trainer->datatype==0 ) + { + mlpinitpreprocessorsubset(&session->network, &trainer->densexy, trainer->npoints, &dummysubset, -1, _state); + } + if( trainer->datatype==1 ) + { + mlpinitpreprocessorsparsesubset(&session->network, &trainer->sparsexy, trainer->npoints, &dummysubset, -1, _state); + } + mlprandomize(&session->network, _state); + session->randomizenetwork = ae_true; + } + else + { + session->randomizenetwork = ae_false; + } + + /* + * Determine network geometry and initialize optimizer + */ + mlpproperties(&session->network, &nin, &nout, &wcount, _state); + minlbfgscreate(wcount, ae_minint(wcount, trainer->lbfgsfactor, _state), &session->network.weights, &session->optimizer, _state); + minlbfgssetxrep(&session->optimizer, ae_true, _state); + + /* + * Create buffers + */ + ae_vector_set_length(&session->wbuf0, wcount, _state); + ae_vector_set_length(&session->wbuf1, wcount, _state); + + /* + * Initialize session result + */ + mlpexporttunableparameters(&session->network, &session->bestparameters, &pcount, _state); + session->bestrmserror = ae_maxrealnumber; + ae_frame_leave(_state); +} + + +/************************************************************************* +This function initializes temporaries needed for training session. + +*************************************************************************/ +static void mlptrain_initmlptrnsessions(multilayerperceptron* networktrained, + ae_bool randomizenetwork, + mlptrainer* trainer, + ae_shared_pool* sessions, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector dummysubset; + smlptrnsession t; + smlptrnsession *p; + ae_smart_ptr _p; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&dummysubset, 0, DT_INT, _state, ae_true); + _smlptrnsession_init(&t, _state, ae_true); + ae_smart_ptr_init(&_p, (void**)&p, _state, ae_true); + + if( ae_shared_pool_is_initialized(sessions) ) + { + + /* + * Pool was already initialized. + * Clear sessions stored in the pool. + */ + ae_shared_pool_first_recycled(sessions, &_p, _state); + while(p!=NULL) + { + ae_assert(mlpsamearchitecture(&p->network, networktrained, _state), "InitMLPTrnSessions: internal consistency error", _state); + p->bestrmserror = ae_maxrealnumber; + ae_shared_pool_next_recycled(sessions, &_p, _state); + } + } + else + { + + /* + * Prepare session and seed pool + */ + mlptrain_initmlptrnsession(networktrained, randomizenetwork, trainer, &t, _state); + ae_shared_pool_set_seed(sessions, &t, sizeof(t), _smlptrnsession_init, _smlptrnsession_init_copy, _smlptrnsession_destroy, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function initializes temporaries needed for ensemble training. + +*************************************************************************/ +static void mlptrain_initmlpetrnsession(multilayerperceptron* individualnetwork, + mlptrainer* trainer, + mlpetrnsession* session, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector dummysubset; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&dummysubset, 0, DT_INT, _state, ae_true); + + + /* + * Prepare network: + * * copy input network to Session.Network + * * re-initialize preprocessor and weights if RandomizeNetwork=True + */ + mlpcopy(individualnetwork, &session->network, _state); + mlptrain_initmlptrnsessions(individualnetwork, ae_true, trainer, &session->mlpsessions, _state); + ivectorsetlengthatleast(&session->trnsubset, trainer->npoints, _state); + ivectorsetlengthatleast(&session->valsubset, trainer->npoints, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This function initializes temporaries needed for training session. + +*************************************************************************/ +static void mlptrain_initmlpetrnsessions(multilayerperceptron* individualnetwork, + mlptrainer* trainer, + ae_shared_pool* sessions, + ae_state *_state) +{ + ae_frame _frame_block; + mlpetrnsession t; + + ae_frame_make(_state, &_frame_block); + _mlpetrnsession_init(&t, _state, ae_true); + + if( !ae_shared_pool_is_initialized(sessions) ) + { + mlptrain_initmlpetrnsession(individualnetwork, trainer, &t, _state); + ae_shared_pool_set_seed(sessions, &t, sizeof(t), _mlpetrnsession_init, _mlpetrnsession_init_copy, _mlpetrnsession_destroy, _state); + } + ae_frame_leave(_state); +} + + +ae_bool _mlpreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpreport *p = (mlpreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _mlpreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpreport *dst = (mlpreport*)_dst; + mlpreport *src = (mlpreport*)_src; + dst->relclserror = src->relclserror; + dst->avgce = src->avgce; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->ngrad = src->ngrad; + dst->nhess = src->nhess; + dst->ncholesky = src->ncholesky; + return ae_true; +} + + +void _mlpreport_clear(void* _p) +{ + mlpreport *p = (mlpreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _mlpreport_destroy(void* _p) +{ + mlpreport *p = (mlpreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _mlpcvreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpcvreport *p = (mlpcvreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _mlpcvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpcvreport *dst = (mlpcvreport*)_dst; + mlpcvreport *src = (mlpcvreport*)_src; + dst->relclserror = src->relclserror; + dst->avgce = src->avgce; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + return ae_true; +} + + +void _mlpcvreport_clear(void* _p) +{ + mlpcvreport *p = (mlpcvreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _mlpcvreport_destroy(void* _p) +{ + mlpcvreport *p = (mlpcvreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _smlptrnsession_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + smlptrnsession *p = (smlptrnsession*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->bestparameters, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_multilayerperceptron_init(&p->network, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsstate_init(&p->optimizer, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsreport_init(&p->optimizerrep, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wbuf0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wbuf1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->allminibatches, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->currentminibatch, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !_hqrndstate_init(&p->generator, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _smlptrnsession_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + smlptrnsession *dst = (smlptrnsession*)_dst; + smlptrnsession *src = (smlptrnsession*)_src; + if( !ae_vector_init_copy(&dst->bestparameters, &src->bestparameters, _state, make_automatic) ) + return ae_false; + dst->bestrmserror = src->bestrmserror; + dst->randomizenetwork = src->randomizenetwork; + if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsstate_init_copy(&dst->optimizer, &src->optimizer, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsreport_init_copy(&dst->optimizerrep, &src->optimizerrep, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wbuf0, &src->wbuf0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wbuf1, &src->wbuf1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->allminibatches, &src->allminibatches, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->currentminibatch, &src->currentminibatch, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->algoused = src->algoused; + dst->minibatchsize = src->minibatchsize; + if( !_hqrndstate_init_copy(&dst->generator, &src->generator, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _smlptrnsession_clear(void* _p) +{ + smlptrnsession *p = (smlptrnsession*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->bestparameters); + _multilayerperceptron_clear(&p->network); + _minlbfgsstate_clear(&p->optimizer); + _minlbfgsreport_clear(&p->optimizerrep); + ae_vector_clear(&p->wbuf0); + ae_vector_clear(&p->wbuf1); + ae_vector_clear(&p->allminibatches); + ae_vector_clear(&p->currentminibatch); + _rcommstate_clear(&p->rstate); + _hqrndstate_clear(&p->generator); +} + + +void _smlptrnsession_destroy(void* _p) +{ + smlptrnsession *p = (smlptrnsession*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->bestparameters); + _multilayerperceptron_destroy(&p->network); + _minlbfgsstate_destroy(&p->optimizer); + _minlbfgsreport_destroy(&p->optimizerrep); + ae_vector_destroy(&p->wbuf0); + ae_vector_destroy(&p->wbuf1); + ae_vector_destroy(&p->allminibatches); + ae_vector_destroy(&p->currentminibatch); + _rcommstate_destroy(&p->rstate); + _hqrndstate_destroy(&p->generator); +} + + +ae_bool _mlpetrnsession_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpetrnsession *p = (mlpetrnsession*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->trnsubset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->valsubset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->mlpsessions, _state, make_automatic) ) + return ae_false; + if( !_mlpreport_init(&p->mlprep, _state, make_automatic) ) + return ae_false; + if( !_multilayerperceptron_init(&p->network, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mlpetrnsession_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpetrnsession *dst = (mlpetrnsession*)_dst; + mlpetrnsession *src = (mlpetrnsession*)_src; + if( !ae_vector_init_copy(&dst->trnsubset, &src->trnsubset, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->valsubset, &src->valsubset, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init_copy(&dst->mlpsessions, &src->mlpsessions, _state, make_automatic) ) + return ae_false; + if( !_mlpreport_init_copy(&dst->mlprep, &src->mlprep, _state, make_automatic) ) + return ae_false; + if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mlpetrnsession_clear(void* _p) +{ + mlpetrnsession *p = (mlpetrnsession*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->trnsubset); + ae_vector_clear(&p->valsubset); + ae_shared_pool_clear(&p->mlpsessions); + _mlpreport_clear(&p->mlprep); + _multilayerperceptron_clear(&p->network); +} + + +void _mlpetrnsession_destroy(void* _p) +{ + mlpetrnsession *p = (mlpetrnsession*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->trnsubset); + ae_vector_destroy(&p->valsubset); + ae_shared_pool_destroy(&p->mlpsessions); + _mlpreport_destroy(&p->mlprep); + _multilayerperceptron_destroy(&p->network); +} + + +ae_bool _mlptrainer_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlptrainer *p = (mlptrainer*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->densexy, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init(&p->sparsexy, _state, make_automatic) ) + return ae_false; + if( !_smlptrnsession_init(&p->session, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->subset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->valsubset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mlptrainer_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlptrainer *dst = (mlptrainer*)_dst; + mlptrainer *src = (mlptrainer*)_src; + dst->nin = src->nin; + dst->nout = src->nout; + dst->rcpar = src->rcpar; + dst->lbfgsfactor = src->lbfgsfactor; + dst->decay = src->decay; + dst->wstep = src->wstep; + dst->maxits = src->maxits; + dst->datatype = src->datatype; + dst->npoints = src->npoints; + if( !ae_matrix_init_copy(&dst->densexy, &src->densexy, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init_copy(&dst->sparsexy, &src->sparsexy, _state, make_automatic) ) + return ae_false; + if( !_smlptrnsession_init_copy(&dst->session, &src->session, _state, make_automatic) ) + return ae_false; + dst->ngradbatch = src->ngradbatch; + if( !ae_vector_init_copy(&dst->subset, &src->subset, _state, make_automatic) ) + return ae_false; + dst->subsetsize = src->subsetsize; + if( !ae_vector_init_copy(&dst->valsubset, &src->valsubset, _state, make_automatic) ) + return ae_false; + dst->valsubsetsize = src->valsubsetsize; + dst->algokind = src->algokind; + dst->minibatchsize = src->minibatchsize; + return ae_true; +} + + +void _mlptrainer_clear(void* _p) +{ + mlptrainer *p = (mlptrainer*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->densexy); + _sparsematrix_clear(&p->sparsexy); + _smlptrnsession_clear(&p->session); + ae_vector_clear(&p->subset); + ae_vector_clear(&p->valsubset); +} + + +void _mlptrainer_destroy(void* _p) +{ + mlptrainer *p = (mlptrainer*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->densexy); + _sparsematrix_destroy(&p->sparsexy); + _smlptrnsession_destroy(&p->session); + ae_vector_destroy(&p->subset); + ae_vector_destroy(&p->valsubset); +} + + +ae_bool _mlpparallelizationcv_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mlpparallelizationcv *p = (mlpparallelizationcv*)_p; + ae_touch_ptr((void*)p); + if( !_multilayerperceptron_init(&p->network, _state, make_automatic) ) + return ae_false; + if( !_mlpreport_init(&p->rep, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->subset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xyrow, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_shared_pool_init(&p->trnpool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mlpparallelizationcv_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mlpparallelizationcv *dst = (mlpparallelizationcv*)_dst; + mlpparallelizationcv *src = (mlpparallelizationcv*)_src; + if( !_multilayerperceptron_init_copy(&dst->network, &src->network, _state, make_automatic) ) + return ae_false; + if( !_mlpreport_init_copy(&dst->rep, &src->rep, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->subset, &src->subset, _state, make_automatic) ) + return ae_false; + dst->subsetsize = src->subsetsize; + if( !ae_vector_init_copy(&dst->xyrow, &src->xyrow, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + dst->ngrad = src->ngrad; + if( !ae_shared_pool_init_copy(&dst->trnpool, &src->trnpool, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mlpparallelizationcv_clear(void* _p) +{ + mlpparallelizationcv *p = (mlpparallelizationcv*)_p; + ae_touch_ptr((void*)p); + _multilayerperceptron_clear(&p->network); + _mlpreport_clear(&p->rep); + ae_vector_clear(&p->subset); + ae_vector_clear(&p->xyrow); + ae_vector_clear(&p->y); + ae_shared_pool_clear(&p->trnpool); +} + + +void _mlpparallelizationcv_destroy(void* _p) +{ + mlpparallelizationcv *p = (mlpparallelizationcv*)_p; + ae_touch_ptr((void*)p); + _multilayerperceptron_destroy(&p->network); + _mlpreport_destroy(&p->rep); + ae_vector_destroy(&p->subset); + ae_vector_destroy(&p->xyrow); + ae_vector_destroy(&p->y); + ae_shared_pool_destroy(&p->trnpool); +} + + + + +/************************************************************************* +Principal components analysis + +Subroutine builds orthogonal basis where first axis corresponds to +direction with maximum variance, second axis maximizes variance in subspace +orthogonal to first axis and so on. + +It should be noted that, unlike LDA, PCA does not use class labels. + +INPUT PARAMETERS: + X - dataset, array[0..NPoints-1,0..NVars-1]. + matrix contains ONLY INDEPENDENT VARIABLES. + NPoints - dataset size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + +ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ: + Info - return code: + * -4, if SVD subroutine haven't converged + * -1, if wrong parameters has been passed (NPoints<0, + NVars<1) + * 1, if task is solved + S2 - array[0..NVars-1]. variance values corresponding + to basis vectors. + V - array[0..NVars-1,0..NVars-1] + matrix, whose columns store basis vectors. + + -- ALGLIB -- + Copyright 25.08.2008 by Bochkanov Sergey +*************************************************************************/ +void pcabuildbasis(/* Real */ ae_matrix* x, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* s2, + /* Real */ ae_matrix* v, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix a; + ae_matrix u; + ae_matrix vt; + ae_vector m; + ae_vector t; + ae_int_t i; + ae_int_t j; + double mean; + double variance; + double skewness; + double kurtosis; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(s2); + ae_matrix_clear(v); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&m, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + + /* + * Check input data + */ + if( npoints<0||nvars<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Special case: NPoints=0 + */ + if( npoints==0 ) + { + ae_vector_set_length(s2, nvars-1+1, _state); + ae_matrix_set_length(v, nvars-1+1, nvars-1+1, _state); + for(i=0; i<=nvars-1; i++) + { + s2->ptr.p_double[i] = 0; + } + for(i=0; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + if( i==j ) + { + v->ptr.pp_double[i][j] = 1; + } + else + { + v->ptr.pp_double[i][j] = 0; + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * Calculate means + */ + ae_vector_set_length(&m, nvars-1+1, _state); + ae_vector_set_length(&t, npoints-1+1, _state); + for(j=0; j<=nvars-1; j++) + { + ae_v_move(&t.ptr.p_double[0], 1, &x->ptr.pp_double[0][j], x->stride, ae_v_len(0,npoints-1)); + samplemoments(&t, npoints, &mean, &variance, &skewness, &kurtosis, _state); + m.ptr.p_double[j] = mean; + } + + /* + * Center, apply SVD, prepare output + */ + ae_matrix_set_length(&a, ae_maxint(npoints, nvars, _state)-1+1, nvars-1+1, _state); + for(i=0; i<=npoints-1; i++) + { + ae_v_move(&a.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,nvars-1)); + ae_v_sub(&a.ptr.pp_double[i][0], 1, &m.ptr.p_double[0], 1, ae_v_len(0,nvars-1)); + } + for(i=npoints; i<=nvars-1; i++) + { + for(j=0; j<=nvars-1; j++) + { + a.ptr.pp_double[i][j] = 0; + } + } + if( !rmatrixsvd(&a, ae_maxint(npoints, nvars, _state), nvars, 0, 1, 2, s2, &u, &vt, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + if( npoints!=1 ) + { + for(i=0; i<=nvars-1; i++) + { + s2->ptr.p_double[i] = ae_sqr(s2->ptr.p_double[i], _state)/(npoints-1); + } + } + ae_matrix_set_length(v, nvars-1+1, nvars-1+1, _state); + copyandtranspose(&vt, 0, nvars-1, 0, nvars-1, v, 0, nvars-1, 0, nvars-1, _state); + ae_frame_leave(_state); +} + + + +} + diff --git a/src/inc/alglib/dataanalysis.h b/src/inc/alglib/dataanalysis.h new file mode 100644 index 0000000..4aed876 --- /dev/null +++ b/src/inc/alglib/dataanalysis.h @@ -0,0 +1,7394 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _dataanalysis_pkg_h +#define _dataanalysis_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "linalg.h" +#include "statistics.h" +#include "alglibmisc.h" +#include "specialfunctions.h" +#include "solvers.h" +#include "optimization.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + double relclserror; + double avgce; + double rmserror; + double avgerror; + double avgrelerror; +} cvreport; +typedef struct +{ + ae_int_t npoints; + ae_int_t nfeatures; + ae_int_t disttype; + ae_matrix xy; + ae_matrix d; + ae_int_t ahcalgo; + ae_int_t kmeansrestarts; + ae_int_t kmeansmaxits; +} clusterizerstate; +typedef struct +{ + ae_int_t npoints; + ae_vector p; + ae_matrix z; + ae_matrix pz; + ae_matrix pm; + ae_vector mergedist; +} ahcreport; +typedef struct +{ + ae_int_t npoints; + ae_int_t nfeatures; + ae_int_t terminationtype; + ae_int_t k; + ae_matrix c; + ae_vector cidx; +} kmeansreport; +typedef struct +{ + ae_int_t nvars; + ae_int_t nclasses; + ae_int_t ntrees; + ae_int_t bufsize; + ae_vector trees; +} decisionforest; +typedef struct +{ + double relclserror; + double avgce; + double rmserror; + double avgerror; + double avgrelerror; + double oobrelclserror; + double oobavgce; + double oobrmserror; + double oobavgerror; + double oobavgrelerror; +} dfreport; +typedef struct +{ + ae_vector treebuf; + ae_vector idxbuf; + ae_vector tmpbufr; + ae_vector tmpbufr2; + ae_vector tmpbufi; + ae_vector classibuf; + ae_vector sortrbuf; + ae_vector sortrbuf2; + ae_vector sortibuf; + ae_vector varpool; + ae_vector evsbin; + ae_vector evssplits; +} dfinternalbuffers; +typedef struct +{ + ae_vector w; +} linearmodel; +typedef struct +{ + ae_matrix c; + double rmserror; + double avgerror; + double avgrelerror; + double cvrmserror; + double cvavgerror; + double cvavgrelerror; + ae_int_t ncvdefects; + ae_vector cvdefects; +} lrreport; +typedef struct +{ + double relclserror; + double avgce; + double rmserror; + double avgerror; + double avgrelerror; +} modelerrors; +typedef struct +{ + double f; + ae_vector g; +} smlpgrad; +typedef struct +{ + ae_int_t hlnetworktype; + ae_int_t hlnormtype; + ae_vector hllayersizes; + ae_vector hlconnections; + ae_vector hlneurons; + ae_vector structinfo; + ae_vector weights; + ae_vector columnmeans; + ae_vector columnsigmas; + ae_vector neurons; + ae_vector dfdnet; + ae_vector derror; + ae_vector x; + ae_vector y; + ae_matrix xy; + ae_vector xyrow; + ae_vector nwbuf; + ae_vector integerbuf; + modelerrors err; + ae_vector rndbuf; + ae_shared_pool buf; + ae_shared_pool gradbuf; + ae_matrix dummydxy; + sparsematrix dummysxy; + ae_vector dummyidx; + ae_shared_pool dummypool; +} multilayerperceptron; +typedef struct +{ + ae_vector w; +} logitmodel; +typedef struct +{ + ae_bool brackt; + ae_bool stage1; + ae_int_t infoc; + double dg; + double dgm; + double dginit; + double dgtest; + double dgx; + double dgxm; + double dgy; + double dgym; + double finit; + double ftest1; + double fm; + double fx; + double fxm; + double fy; + double fym; + double stx; + double sty; + double stmin; + double stmax; + double width; + double width1; + double xtrapf; +} logitmcstate; +typedef struct +{ + ae_int_t ngrad; + ae_int_t nhess; +} mnlreport; +typedef struct +{ + ae_int_t n; + ae_vector states; + ae_int_t npairs; + ae_matrix data; + ae_matrix ec; + ae_matrix bndl; + ae_matrix bndu; + ae_matrix c; + ae_vector ct; + ae_int_t ccnt; + ae_vector pw; + ae_matrix priorp; + double regterm; + minbleicstate bs; + ae_int_t repinneriterationscount; + ae_int_t repouteriterationscount; + ae_int_t repnfev; + ae_int_t repterminationtype; + minbleicreport br; + ae_vector tmpp; + ae_vector effectivew; + ae_vector effectivebndl; + ae_vector effectivebndu; + ae_matrix effectivec; + ae_vector effectivect; + ae_vector h; + ae_matrix p; +} mcpdstate; +typedef struct +{ + ae_int_t inneriterationscount; + ae_int_t outeriterationscount; + ae_int_t nfev; + ae_int_t terminationtype; +} mcpdreport; +typedef struct +{ + ae_int_t ensemblesize; + ae_vector weights; + ae_vector columnmeans; + ae_vector columnsigmas; + multilayerperceptron network; + ae_vector y; +} mlpensemble; +typedef struct +{ + double relclserror; + double avgce; + double rmserror; + double avgerror; + double avgrelerror; + ae_int_t ngrad; + ae_int_t nhess; + ae_int_t ncholesky; +} mlpreport; +typedef struct +{ + double relclserror; + double avgce; + double rmserror; + double avgerror; + double avgrelerror; +} mlpcvreport; +typedef struct +{ + ae_vector bestparameters; + double bestrmserror; + ae_bool randomizenetwork; + multilayerperceptron network; + minlbfgsstate optimizer; + minlbfgsreport optimizerrep; + ae_vector wbuf0; + ae_vector wbuf1; + ae_vector allminibatches; + ae_vector currentminibatch; + rcommstate rstate; + ae_int_t algoused; + ae_int_t minibatchsize; + hqrndstate generator; +} smlptrnsession; +typedef struct +{ + ae_vector trnsubset; + ae_vector valsubset; + ae_shared_pool mlpsessions; + mlpreport mlprep; + multilayerperceptron network; +} mlpetrnsession; +typedef struct +{ + ae_int_t nin; + ae_int_t nout; + ae_bool rcpar; + ae_int_t lbfgsfactor; + double decay; + double wstep; + ae_int_t maxits; + ae_int_t datatype; + ae_int_t npoints; + ae_matrix densexy; + sparsematrix sparsexy; + smlptrnsession session; + ae_int_t ngradbatch; + ae_vector subset; + ae_int_t subsetsize; + ae_vector valsubset; + ae_int_t valsubsetsize; + ae_int_t algokind; + ae_int_t minibatchsize; +} mlptrainer; +typedef struct +{ + multilayerperceptron network; + mlpreport rep; + ae_vector subset; + ae_int_t subsetsize; + ae_vector xyrow; + ae_vector y; + ae_int_t ngrad; + ae_shared_pool trnpool; +} mlpparallelizationcv; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + + +/************************************************************************* +This structure is a clusterization engine. + +You should not try to access its fields directly. +Use ALGLIB functions in order to work with this object. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +class _clusterizerstate_owner +{ +public: + _clusterizerstate_owner(); + _clusterizerstate_owner(const _clusterizerstate_owner &rhs); + _clusterizerstate_owner& operator=(const _clusterizerstate_owner &rhs); + virtual ~_clusterizerstate_owner(); + alglib_impl::clusterizerstate* c_ptr(); + alglib_impl::clusterizerstate* c_ptr() const; +protected: + alglib_impl::clusterizerstate *p_struct; +}; +class clusterizerstate : public _clusterizerstate_owner +{ +public: + clusterizerstate(); + clusterizerstate(const clusterizerstate &rhs); + clusterizerstate& operator=(const clusterizerstate &rhs); + virtual ~clusterizerstate(); + +}; + + +/************************************************************************* +This structure is used to store results of the agglomerative hierarchical +clustering (AHC). + +Following information is returned: + +* NPoints contains number of points in the original dataset + +* Z contains information about merges performed (see below). Z contains + indexes from the original (unsorted) dataset and it can be used when you + need to know what points were merged. However, it is not convenient when + you want to build a dendrograd (see below). + +* if you want to build dendrogram, you can use Z, but it is not good + option, because Z contains indexes from unsorted dataset. Dendrogram + built from such dataset is likely to have intersections. So, you have to + reorder you points before building dendrogram. + Permutation which reorders point is returned in P. Another representation + of merges, which is more convenient for dendorgram construction, is + returned in PM. + +* more information on format of Z, P and PM can be found below and in the + examples from ALGLIB Reference Manual. + +FORMAL DESCRIPTION OF FIELDS: + NPoints number of points + Z array[NPoints-1,2], contains indexes of clusters + linked in pairs to form clustering tree. I-th row + corresponds to I-th merge: + * Z[I,0] - index of the first cluster to merge + * Z[I,1] - index of the second cluster to merge + * Z[I,0]=0 + NFeatures number of variables, >=1 + TerminationType completion code: + * -5 if distance type is anything different from + Euclidean metric + * -3 for degenerate dataset: a) less than K distinct + points, b) K=0 for non-empty dataset. + * +1 for successful completion + K number of clusters + C array[K,NFeatures], rows of the array store centers + CIdx array[NPoints], which contains cluster indexes + + -- ALGLIB -- + Copyright 27.11.2012 by Bochkanov Sergey +*************************************************************************/ +class _kmeansreport_owner +{ +public: + _kmeansreport_owner(); + _kmeansreport_owner(const _kmeansreport_owner &rhs); + _kmeansreport_owner& operator=(const _kmeansreport_owner &rhs); + virtual ~_kmeansreport_owner(); + alglib_impl::kmeansreport* c_ptr(); + alglib_impl::kmeansreport* c_ptr() const; +protected: + alglib_impl::kmeansreport *p_struct; +}; +class kmeansreport : public _kmeansreport_owner +{ +public: + kmeansreport(); + kmeansreport(const kmeansreport &rhs); + kmeansreport& operator=(const kmeansreport &rhs); + virtual ~kmeansreport(); + ae_int_t &npoints; + ae_int_t &nfeatures; + ae_int_t &terminationtype; + ae_int_t &k; + real_2d_array c; + integer_1d_array cidx; + +}; + + + +/************************************************************************* + +*************************************************************************/ +class _decisionforest_owner +{ +public: + _decisionforest_owner(); + _decisionforest_owner(const _decisionforest_owner &rhs); + _decisionforest_owner& operator=(const _decisionforest_owner &rhs); + virtual ~_decisionforest_owner(); + alglib_impl::decisionforest* c_ptr(); + alglib_impl::decisionforest* c_ptr() const; +protected: + alglib_impl::decisionforest *p_struct; +}; +class decisionforest : public _decisionforest_owner +{ +public: + decisionforest(); + decisionforest(const decisionforest &rhs); + decisionforest& operator=(const decisionforest &rhs); + virtual ~decisionforest(); + +}; + + +/************************************************************************* + +*************************************************************************/ +class _dfreport_owner +{ +public: + _dfreport_owner(); + _dfreport_owner(const _dfreport_owner &rhs); + _dfreport_owner& operator=(const _dfreport_owner &rhs); + virtual ~_dfreport_owner(); + alglib_impl::dfreport* c_ptr(); + alglib_impl::dfreport* c_ptr() const; +protected: + alglib_impl::dfreport *p_struct; +}; +class dfreport : public _dfreport_owner +{ +public: + dfreport(); + dfreport(const dfreport &rhs); + dfreport& operator=(const dfreport &rhs); + virtual ~dfreport(); + double &relclserror; + double &avgce; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &oobrelclserror; + double &oobavgce; + double &oobrmserror; + double &oobavgerror; + double &oobavgrelerror; + +}; + +/************************************************************************* + +*************************************************************************/ +class _linearmodel_owner +{ +public: + _linearmodel_owner(); + _linearmodel_owner(const _linearmodel_owner &rhs); + _linearmodel_owner& operator=(const _linearmodel_owner &rhs); + virtual ~_linearmodel_owner(); + alglib_impl::linearmodel* c_ptr(); + alglib_impl::linearmodel* c_ptr() const; +protected: + alglib_impl::linearmodel *p_struct; +}; +class linearmodel : public _linearmodel_owner +{ +public: + linearmodel(); + linearmodel(const linearmodel &rhs); + linearmodel& operator=(const linearmodel &rhs); + virtual ~linearmodel(); + +}; + + +/************************************************************************* +LRReport structure contains additional information about linear model: +* C - covariation matrix, array[0..NVars,0..NVars]. + C[i,j] = Cov(A[i],A[j]) +* RMSError - root mean square error on a training set +* AvgError - average error on a training set +* AvgRelError - average relative error on a training set (excluding + observations with zero function value). +* CVRMSError - leave-one-out cross-validation estimate of + generalization error. Calculated using fast algorithm + with O(NVars*NPoints) complexity. +* CVAvgError - cross-validation estimate of average error +* CVAvgRelError - cross-validation estimate of average relative error + +All other fields of the structure are intended for internal use and should +not be used outside ALGLIB. +*************************************************************************/ +class _lrreport_owner +{ +public: + _lrreport_owner(); + _lrreport_owner(const _lrreport_owner &rhs); + _lrreport_owner& operator=(const _lrreport_owner &rhs); + virtual ~_lrreport_owner(); + alglib_impl::lrreport* c_ptr(); + alglib_impl::lrreport* c_ptr() const; +protected: + alglib_impl::lrreport *p_struct; +}; +class lrreport : public _lrreport_owner +{ +public: + lrreport(); + lrreport(const lrreport &rhs); + lrreport& operator=(const lrreport &rhs); + virtual ~lrreport(); + real_2d_array c; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &cvrmserror; + double &cvavgerror; + double &cvavgrelerror; + ae_int_t &ncvdefects; + integer_1d_array cvdefects; + +}; + + + + + +/************************************************************************* +Model's errors: + * RelCLSError - fraction of misclassified cases. + * AvgCE - acerage cross-entropy + * RMSError - root-mean-square error + * AvgError - average error + * AvgRelError - average relative error + +NOTE 1: RelCLSError/AvgCE are zero on regression problems. + +NOTE 2: on classification problems RMSError/AvgError/AvgRelError contain + errors in prediction of posterior probabilities +*************************************************************************/ +class _modelerrors_owner +{ +public: + _modelerrors_owner(); + _modelerrors_owner(const _modelerrors_owner &rhs); + _modelerrors_owner& operator=(const _modelerrors_owner &rhs); + virtual ~_modelerrors_owner(); + alglib_impl::modelerrors* c_ptr(); + alglib_impl::modelerrors* c_ptr() const; +protected: + alglib_impl::modelerrors *p_struct; +}; +class modelerrors : public _modelerrors_owner +{ +public: + modelerrors(); + modelerrors(const modelerrors &rhs); + modelerrors& operator=(const modelerrors &rhs); + virtual ~modelerrors(); + double &relclserror; + double &avgce; + double &rmserror; + double &avgerror; + double &avgrelerror; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _multilayerperceptron_owner +{ +public: + _multilayerperceptron_owner(); + _multilayerperceptron_owner(const _multilayerperceptron_owner &rhs); + _multilayerperceptron_owner& operator=(const _multilayerperceptron_owner &rhs); + virtual ~_multilayerperceptron_owner(); + alglib_impl::multilayerperceptron* c_ptr(); + alglib_impl::multilayerperceptron* c_ptr() const; +protected: + alglib_impl::multilayerperceptron *p_struct; +}; +class multilayerperceptron : public _multilayerperceptron_owner +{ +public: + multilayerperceptron(); + multilayerperceptron(const multilayerperceptron &rhs); + multilayerperceptron& operator=(const multilayerperceptron &rhs); + virtual ~multilayerperceptron(); + +}; + +/************************************************************************* + +*************************************************************************/ +class _logitmodel_owner +{ +public: + _logitmodel_owner(); + _logitmodel_owner(const _logitmodel_owner &rhs); + _logitmodel_owner& operator=(const _logitmodel_owner &rhs); + virtual ~_logitmodel_owner(); + alglib_impl::logitmodel* c_ptr(); + alglib_impl::logitmodel* c_ptr() const; +protected: + alglib_impl::logitmodel *p_struct; +}; +class logitmodel : public _logitmodel_owner +{ +public: + logitmodel(); + logitmodel(const logitmodel &rhs); + logitmodel& operator=(const logitmodel &rhs); + virtual ~logitmodel(); + +}; + + +/************************************************************************* +MNLReport structure contains information about training process: +* NGrad - number of gradient calculations +* NHess - number of Hessian calculations +*************************************************************************/ +class _mnlreport_owner +{ +public: + _mnlreport_owner(); + _mnlreport_owner(const _mnlreport_owner &rhs); + _mnlreport_owner& operator=(const _mnlreport_owner &rhs); + virtual ~_mnlreport_owner(); + alglib_impl::mnlreport* c_ptr(); + alglib_impl::mnlreport* c_ptr() const; +protected: + alglib_impl::mnlreport *p_struct; +}; +class mnlreport : public _mnlreport_owner +{ +public: + mnlreport(); + mnlreport(const mnlreport &rhs); + mnlreport& operator=(const mnlreport &rhs); + virtual ~mnlreport(); + ae_int_t &ngrad; + ae_int_t &nhess; + +}; + +/************************************************************************* +This structure is a MCPD (Markov Chains for Population Data) solver. + +You should use ALGLIB functions in order to work with this object. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +class _mcpdstate_owner +{ +public: + _mcpdstate_owner(); + _mcpdstate_owner(const _mcpdstate_owner &rhs); + _mcpdstate_owner& operator=(const _mcpdstate_owner &rhs); + virtual ~_mcpdstate_owner(); + alglib_impl::mcpdstate* c_ptr(); + alglib_impl::mcpdstate* c_ptr() const; +protected: + alglib_impl::mcpdstate *p_struct; +}; +class mcpdstate : public _mcpdstate_owner +{ +public: + mcpdstate(); + mcpdstate(const mcpdstate &rhs); + mcpdstate& operator=(const mcpdstate &rhs); + virtual ~mcpdstate(); + +}; + + +/************************************************************************* +This structure is a MCPD training report: + InnerIterationsCount - number of inner iterations of the + underlying optimization algorithm + OuterIterationsCount - number of outer iterations of the + underlying optimization algorithm + NFEV - number of merit function evaluations + TerminationType - termination type + (same as for MinBLEIC optimizer, positive + values denote success, negative ones - + failure) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +class _mcpdreport_owner +{ +public: + _mcpdreport_owner(); + _mcpdreport_owner(const _mcpdreport_owner &rhs); + _mcpdreport_owner& operator=(const _mcpdreport_owner &rhs); + virtual ~_mcpdreport_owner(); + alglib_impl::mcpdreport* c_ptr(); + alglib_impl::mcpdreport* c_ptr() const; +protected: + alglib_impl::mcpdreport *p_struct; +}; +class mcpdreport : public _mcpdreport_owner +{ +public: + mcpdreport(); + mcpdreport(const mcpdreport &rhs); + mcpdreport& operator=(const mcpdreport &rhs); + virtual ~mcpdreport(); + ae_int_t &inneriterationscount; + ae_int_t &outeriterationscount; + ae_int_t &nfev; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +Neural networks ensemble +*************************************************************************/ +class _mlpensemble_owner +{ +public: + _mlpensemble_owner(); + _mlpensemble_owner(const _mlpensemble_owner &rhs); + _mlpensemble_owner& operator=(const _mlpensemble_owner &rhs); + virtual ~_mlpensemble_owner(); + alglib_impl::mlpensemble* c_ptr(); + alglib_impl::mlpensemble* c_ptr() const; +protected: + alglib_impl::mlpensemble *p_struct; +}; +class mlpensemble : public _mlpensemble_owner +{ +public: + mlpensemble(); + mlpensemble(const mlpensemble &rhs); + mlpensemble& operator=(const mlpensemble &rhs); + virtual ~mlpensemble(); + +}; + +/************************************************************************* +Training report: + * RelCLSError - fraction of misclassified cases. + * AvgCE - acerage cross-entropy + * RMSError - root-mean-square error + * AvgError - average error + * AvgRelError - average relative error + * NGrad - number of gradient calculations + * NHess - number of Hessian calculations + * NCholesky - number of Cholesky decompositions + +NOTE 1: RelCLSError/AvgCE are zero on regression problems. + +NOTE 2: on classification problems RMSError/AvgError/AvgRelError contain + errors in prediction of posterior probabilities +*************************************************************************/ +class _mlpreport_owner +{ +public: + _mlpreport_owner(); + _mlpreport_owner(const _mlpreport_owner &rhs); + _mlpreport_owner& operator=(const _mlpreport_owner &rhs); + virtual ~_mlpreport_owner(); + alglib_impl::mlpreport* c_ptr(); + alglib_impl::mlpreport* c_ptr() const; +protected: + alglib_impl::mlpreport *p_struct; +}; +class mlpreport : public _mlpreport_owner +{ +public: + mlpreport(); + mlpreport(const mlpreport &rhs); + mlpreport& operator=(const mlpreport &rhs); + virtual ~mlpreport(); + double &relclserror; + double &avgce; + double &rmserror; + double &avgerror; + double &avgrelerror; + ae_int_t &ngrad; + ae_int_t &nhess; + ae_int_t &ncholesky; + +}; + + +/************************************************************************* +Cross-validation estimates of generalization error +*************************************************************************/ +class _mlpcvreport_owner +{ +public: + _mlpcvreport_owner(); + _mlpcvreport_owner(const _mlpcvreport_owner &rhs); + _mlpcvreport_owner& operator=(const _mlpcvreport_owner &rhs); + virtual ~_mlpcvreport_owner(); + alglib_impl::mlpcvreport* c_ptr(); + alglib_impl::mlpcvreport* c_ptr() const; +protected: + alglib_impl::mlpcvreport *p_struct; +}; +class mlpcvreport : public _mlpcvreport_owner +{ +public: + mlpcvreport(); + mlpcvreport(const mlpcvreport &rhs); + mlpcvreport& operator=(const mlpcvreport &rhs); + virtual ~mlpcvreport(); + double &relclserror; + double &avgce; + double &rmserror; + double &avgerror; + double &avgrelerror; + +}; + + +/************************************************************************* +Trainer object for neural network. + +You should not try to access fields of this object directly - use ALGLIB +functions to work with this object. +*************************************************************************/ +class _mlptrainer_owner +{ +public: + _mlptrainer_owner(); + _mlptrainer_owner(const _mlptrainer_owner &rhs); + _mlptrainer_owner& operator=(const _mlptrainer_owner &rhs); + virtual ~_mlptrainer_owner(); + alglib_impl::mlptrainer* c_ptr(); + alglib_impl::mlptrainer* c_ptr() const; +protected: + alglib_impl::mlptrainer *p_struct; +}; +class mlptrainer : public _mlptrainer_owner +{ +public: + mlptrainer(); + mlptrainer(const mlptrainer &rhs); + mlptrainer& operator=(const mlptrainer &rhs); + virtual ~mlptrainer(); + +}; + +/************************************************************************* +Optimal binary classification + +Algorithms finds optimal (=with minimal cross-entropy) binary partition. +Internal subroutine. + +INPUT PARAMETERS: + A - array[0..N-1], variable + C - array[0..N-1], class numbers (0 or 1). + N - array size + +OUTPUT PARAMETERS: + Info - completetion code: + * -3, all values of A[] are same (partition is impossible) + * -2, one of C[] is incorrect (<0, >1) + * -1, incorrect pararemets were passed (N<=0). + * 1, OK + Threshold- partiton boundary. Left part contains values which are + strictly less than Threshold. Right part contains values + which are greater than or equal to Threshold. + PAL, PBL- probabilities P(0|v=Threshold) and P(1|v>=Threshold) + CVE - cross-validation estimate of cross-entropy + + -- ALGLIB -- + Copyright 22.05.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2(const real_1d_array &a, const integer_1d_array &c, const ae_int_t n, ae_int_t &info, double &threshold, double &pal, double &pbl, double &par, double &pbr, double &cve); + + +/************************************************************************* +Optimal partition, internal subroutine. Fast version. + +Accepts: + A array[0..N-1] array of attributes array[0..N-1] + C array[0..N-1] array of class labels + TiesBuf array[0..N] temporaries (ties) + CntBuf array[0..2*NC-1] temporaries (counts) + Alpha centering factor (0<=alpha<=1, recommended value - 0.05) + BufR array[0..N-1] temporaries + BufI array[0..N-1] temporaries + +Output: + Info error code (">0"=OK, "<0"=bad) + RMS training set RMS error + CVRMS leave-one-out RMS error + +Note: + content of all arrays is changed by subroutine; + it doesn't allocate temporaries. + + -- ALGLIB -- + Copyright 11.12.2008 by Bochkanov Sergey +*************************************************************************/ +void dsoptimalsplit2fast(real_1d_array &a, integer_1d_array &c, integer_1d_array &tiesbuf, integer_1d_array &cntbuf, real_1d_array &bufr, integer_1d_array &bufi, const ae_int_t n, const ae_int_t nc, const double alpha, ae_int_t &info, double &threshold, double &rms, double &cvrms); + +/************************************************************************* +This function initializes clusterizer object. Newly initialized object is +empty, i.e. it does not contain dataset. You should use it as follows: +1. creation +2. dataset is added with ClusterizerSetPoints() +3. additional parameters are set +3. clusterization is performed with one of the clustering functions + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizercreate(clusterizerstate &s); + + +/************************************************************************* +This function adds dataset to the clusterizer structure. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +NOTE 1: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + +NOTE 2: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric + * k-means++ clustering algorithm may be used only with Euclidean + distance function + Thus, list of specific clustering algorithms you may use depends + on distance function you specify when you set your dataset. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype); +void clusterizersetpoints(const clusterizerstate &s, const real_2d_array &xy, const ae_int_t disttype); + + +/************************************************************************* +This function adds dataset given by distance matrix to the clusterizer +structure. It is important that dataset is not given explicitly - only +distance matrix is given. + +This function overrides all previous calls of ClusterizerSetPoints() or +ClusterizerSetDistances(). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + D - array[NPoints,NPoints], distance matrix given by its upper + or lower triangle (main diagonal is ignored because its + entries are expected to be zero). + NPoints - number of points + IsUpper - whether upper or lower triangle of D is given. + +NOTE 1: different clustering algorithms have different limitations: + * agglomerative hierarchical clustering algorithms may be used with + any kind of distance metric, including one which is given by + distance matrix + * k-means++ clustering algorithm may be used only with Euclidean + distance function and explicitly given points - it can not be + used with dataset given by distance matrix + Thus, if you call this function, you will be unable to use k-means + clustering algorithm to process your problem. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const ae_int_t npoints, const bool isupper); +void clusterizersetdistances(const clusterizerstate &s, const real_2d_array &d, const bool isupper); + + +/************************************************************************* +This function sets agglomerative hierarchical clustering algorithm + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Algo - algorithm type: + * 0 complete linkage (default algorithm) + * 1 single linkage + * 2 unweighted average linkage + * 3 weighted average linkage + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetahcalgo(const clusterizerstate &s, const ae_int_t algo); + + +/************************************************************************* +This function sets k-means++ properties : number of restarts and maximum +number of iterations per one run. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + Restarts- restarts count, >=1. + k-means++ algorithm performs several restarts and chooses + best set of centers (one with minimum squared distance). + MaxIts - maximum number of k-means iterations performed during one + run. >=0, zero value means that algorithm performs unlimited + number of iterations. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizersetkmeanslimits(const clusterizerstate &s, const ae_int_t restarts, const ae_int_t maxits); + + +/************************************************************************* +This function performs agglomerative hierarchical clustering + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + +OUTPUT PARAMETERS: + Rep - clustering results; see description of AHCReport + structure for more information. + +NOTE 1: hierarchical clustering algorithms require large amounts of memory. + In particular, this implementation needs sizeof(double)*NPoints^2 + bytes, which are used to store distance matrix. In case we work + with user-supplied matrix, this amount is multiplied by 2 (we have + to store original matrix and to work with its copy). + + For example, problem with 10000 points would require 800M of RAM, + even when working in a 1-dimensional space. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunahc(const clusterizerstate &s, ahcreport &rep); +void smp_clusterizerrunahc(const clusterizerstate &s, ahcreport &rep); + + +/************************************************************************* +This function performs clustering by k-means++ algorithm. + +You may change algorithm properties like number of restarts or iterations +limit by calling ClusterizerSetKMeansLimits() functions. + +INPUT PARAMETERS: + S - clusterizer state, initialized by ClusterizerCreate() + K - number of clusters, K>=0. + K can be zero only when algorithm is called for empty + dataset, in this case completion code is set to + success (+1). + If K=0 and dataset size is non-zero, we can not + meaningfully assign points to some center (there are no + centers because K=0) and return -3 as completion code + (failure). + +OUTPUT PARAMETERS: + Rep - clustering results; see description of KMeansReport + structure for more information. + +NOTE 1: k-means clustering can be performed only for datasets with + Euclidean distance function. Algorithm will return negative + completion code in Rep.TerminationType in case dataset was added + to clusterizer with DistType other than Euclidean (or dataset was + specified by distance matrix instead of explicitly given points). + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizerrunkmeans(const clusterizerstate &s, const ae_int_t k, kmeansreport &rep); + + +/************************************************************************* +This function returns distance matrix for dataset + +FOR USERS OF SMP EDITION: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Multicore version is pretty efficient on large + ! problems which need more than 1.000.000 operations to be solved, + ! gives moderate speed-up in mid-range (from 100.000 to 1.000.000 CPU + ! cycles), but gives no speed-up for small problems (less than 100.000 + ! operations). + +INPUT PARAMETERS: + XY - array[NPoints,NFeatures], dataset + NPoints - number of points, >=0 + NFeatures- number of features, >=1 + DistType- distance function: + * 0 Chebyshev distance (L-inf norm) + * 1 city block distance (L1 norm) + * 2 Euclidean distance (L2 norm) + * 10 Pearson correlation: + dist(a,b) = 1-corr(a,b) + * 11 Absolute Pearson correlation: + dist(a,b) = 1-|corr(a,b)| + * 12 Uncentered Pearson correlation (cosine of the angle): + dist(a,b) = a'*b/(|a|*|b|) + * 13 Absolute uncentered Pearson correlation + dist(a,b) = |a'*b|/(|a|*|b|) + * 20 Spearman rank correlation: + dist(a,b) = 1-rankcorr(a,b) + * 21 Absolute Spearman rank correlation + dist(a,b) = 1-|rankcorr(a,b)| + +OUTPUT PARAMETERS: + D - array[NPoints,NPoints], distance matrix + (full matrix is returned, with lower and upper triangles) + +NOTES: different distance functions have different performance penalty: + * Euclidean or Pearson correlation distances are the fastest ones + * Spearman correlation distance function is a bit slower + * city block and Chebyshev distances are order of magnitude slower + + The reason behing difference in performance is that correlation-based + distance functions are computed using optimized linear algebra kernels, + while Chebyshev and city block distance functions are computed using + simple nested loops with two branches at each iteration. + + -- ALGLIB -- + Copyright 10.07.2012 by Bochkanov Sergey +*************************************************************************/ +void clusterizergetdistances(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype, real_2d_array &d); +void smp_clusterizergetdistances(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures, const ae_int_t disttype, real_2d_array &d); + + +/************************************************************************* +This function takes as input clusterization report Rep, desired clusters +count K, and builds top K clusters from hierarchical clusterization tree. +It returns assignment of points to clusters (array of cluster indexes). + +INPUT PARAMETERS: + Rep - report from ClusterizerRunAHC() performed on XY + K - desired number of clusters, 1<=K<=NPoints. + K can be zero only when NPoints=0. + +OUTPUT PARAMETERS: + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I]=0 + +OUTPUT PARAMETERS: + K - number of clusters, 1<=K<=NPoints + CIdx - array[NPoints], I-th element contains cluster index (from + 0 to K-1) for I-th point of the dataset. + CZ - array[K]. This array allows to convert cluster indexes + returned by this function to indexes used by Rep.Z. J-th + cluster returned by this function corresponds to CZ[J]-th + cluster stored in Rep.Z/PZ/PM. + It is guaranteed that CZ[I]=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforest(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const double r, ae_int_t &info, decisionforest &df, dfreport &rep); + + +/************************************************************************* +This subroutine builds random decision forest. +This function gives ability to tune number of variables used when choosing +best split. + +INPUT PARAMETERS: + XY - training set + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - task type: + * NClasses=1 - regression task with one + dependent variable + * NClasses>1 - classification task with + NClasses classes. + NTrees - number of trees in a forest, NTrees>=1. + recommended values: 50-100. + NRndVars - number of variables used when choosing best split + R - percent of a training set used to build + individual trees. 01). + * 1, if task has been solved + DF - model built + Rep - training report, contains error on a training set + and out-of-bag estimates of generalization error. + + -- ALGLIB -- + Copyright 19.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfbuildrandomdecisionforestx1(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, const ae_int_t ntrees, const ae_int_t nrndvars, const double r, ae_int_t &info, decisionforest &df, dfreport &rep); + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + DF - decision forest model + X - input vector, array[0..NVars-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also DFProcessI. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +void dfprocess(const decisionforest &df, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +'interactive' variant of DFProcess for languages like Python which support +constructs like "Y = DFProcessI(DF,X)" and interactive mode of interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 28.02.2010 by Bochkanov Sergey +*************************************************************************/ +void dfprocessi(const decisionforest &df, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrelclserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if model solves regression task. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgce(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for + classification task, RMS error means error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfrmserror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average error when estimating posterior + probabilities. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + DF - decision forest model + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for + classification task, it means average relative error when estimating + posterior probability of belonging to the correct class. + + -- ALGLIB -- + Copyright 16.02.2009 by Bochkanov Sergey +*************************************************************************/ +double dfavgrelerror(const decisionforest &df, const real_2d_array &xy, const ae_int_t npoints); + +/************************************************************************* +Linear regression + +Subroutine builds model: + + Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N) + +and model found in ALGLIB format, covariation matrix, training set errors +(rms, average, average relative) and leave-one-out cross-validation +estimate of the generalization error. CV estimate calculated using fast +algorithm with O(NPoints*NVars) complexity. + +When covariation matrix is calculated standard deviations of function +values are assumed to be equal to RMS error on the training set. + +INPUT PARAMETERS: + XY - training set, array [0..NPoints-1,0..NVars]: + * NVars columns - independent variables + * last column - dependent variable + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPoints0. + NPoints - training set size, NPoints>NVars+1 + NVars - number of independent variables + +OUTPUT PARAMETERS: + Info - return code: + * -255, in case of unknown internal error + * -4, if internal SVD subroutine haven't converged + * -1, if incorrect parameters was passed (NPoints=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filtersma(real_1d_array &x, const ae_int_t n, const ae_int_t k); +void filtersma(real_1d_array &x, const ae_int_t k); + + +/************************************************************************* +Filters: exponential moving averages. + +This filter replaces array by results of EMA(alpha) filter. EMA(alpha) is +defined as filter which replaces X[] by S[]: + S[0] = X[0] + S[t] = alpha*X[t] + (1-alpha)*S[t-1] + +INPUT PARAMETERS: + X - array[N], array to process. It can be larger than N, + in this case only first N points are processed. + N - points count, N>=0 + alpha - 0=0 + K - K>=1 (K can be larger than N , such cases will be + correctly handled). Window width. K=1 corresponds to + identity transformation (nothing changes). + +OUTPUT PARAMETERS: + X - array, whose first N elements were processed with SMA(K) + +NOTE 1: this function uses efficient in-place algorithm which does not + allocate temporary arrays. + +NOTE 2: this algorithm makes only one pass through array and uses running + sum to speed-up calculation of the averages. Additional measures + are taken to ensure that running sum on a long sequence of zero + elements will be correctly reset to zero even in the presence of + round-off error. + +NOTE 3: this is unsymmetric version of the algorithm, which does NOT + averages points after the current one. Only X[i], X[i-1], ... are + used when calculating new value of X[i]. We should also note that + this algorithm uses BOTH previous points and current one, i.e. + new value of X[i] depends on BOTH previous point and X[i] itself. + + -- ALGLIB -- + Copyright 25.10.2011 by Bochkanov Sergey +*************************************************************************/ +void filterlrma(real_1d_array &x, const ae_int_t n, const ae_int_t k); +void filterlrma(real_1d_array &x, const ae_int_t k); + +/************************************************************************* +Multiclass Fisher LDA + +Subroutine finds coefficients of linear combination which optimally separates +training set on classes. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - linear combination coefficients, array[0..NVars-1] + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherlda(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_1d_array &w); + + +/************************************************************************* +N-dimensional multiclass Fisher LDA + +Subroutine finds coefficients of linear combinations which optimally separates +training set on classes. It returns N-dimensional basis whose vector are sorted +by quality of training set separation (in descending order). + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars]. + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + + +OUTPUT PARAMETERS: + Info - return code: + * -4, if internal EVD subroutine hasn't converged + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed (NPoints<0, + NVars<1, NClasses<2) + * 1, if task has been solved + * 2, if there was a multicollinearity in training set, + but task has been solved. + W - basis, array[0..NVars-1,0..NVars-1] + columns of matrix stores basis vectors, sorted by + quality of training set separation (in descending order) + + -- ALGLIB -- + Copyright 31.05.2008 by Bochkanov Sergey +*************************************************************************/ +void fisherldan(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nvars, const ae_int_t nclasses, ae_int_t &info, real_2d_array &w); + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void mlpserialize(multilayerperceptron &obj, std::string &s_out); + + +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void mlpunserialize(std::string &s_in, multilayerperceptron &obj); + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers, with linear output layer. Network weights are filled with small +random values. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreate0, but with one hidden layer (NHid neurons) with +non-linear activation function. Output layer is linear. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreate0, but with two hidden layers (NHid1 and NHid2 neurons) +with non-linear activation function. Output layer is linear. + $ALL + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. + +Activation function of the output layer takes values: + + (B, +INF), if D>=0 + +or + + (-INF, B), if D<0. + + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateB0 but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateB0 but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, multilayerperceptron &network); + + +/************************************************************************* +Creates neural network with NIn inputs, NOut outputs, without hidden +layers with non-linear output layer. Network weights are filled with small +random values. Activation function of the output layer takes values [A,B]. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateR0, but with non-linear hidden layer. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateR0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 30.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlpcreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, multilayerperceptron &network); + + +/************************************************************************* +Creates classifier network with NIn inputs and NOut possible classes. +Network contains no hidden layers and linear output layer with SOFTMAX- +normalization (so outputs sums up to 1.0 and converge to posterior +probabilities). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec0(const ae_int_t nin, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateC0, but with one non-linear hidden layer. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Same as MLPCreateC0, but with two non-linear hidden layers. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, multilayerperceptron &network); + + +/************************************************************************* +Randomization of neural network weights + + -- ALGLIB -- + Copyright 06.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlprandomize(const multilayerperceptron &network); + + +/************************************************************************* +Randomization of neural network weights and standartisator + + -- ALGLIB -- + Copyright 10.03.2008 by Bochkanov Sergey +*************************************************************************/ +void mlprandomizefull(const multilayerperceptron &network); + + +/************************************************************************* +Returns information about initialized network: number of inputs, outputs, +weights. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpproperties(const multilayerperceptron &network, ae_int_t &nin, ae_int_t &nout, ae_int_t &wcount); + + +/************************************************************************* +Returns number of inputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetinputscount(const multilayerperceptron &network); + + +/************************************************************************* +Returns number of outputs. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetoutputscount(const multilayerperceptron &network); + + +/************************************************************************* +Returns number of weights. + + -- ALGLIB -- + Copyright 19.10.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetweightscount(const multilayerperceptron &network); + + +/************************************************************************* +Tells whether network is SOFTMAX-normalized (i.e. classifier) or not. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +bool mlpissoftmax(const multilayerperceptron &network); + + +/************************************************************************* +This function returns total number of layers (including input, hidden and +output layers). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayerscount(const multilayerperceptron &network); + + +/************************************************************************* +This function returns size of K-th layer. + +K=0 corresponds to input layer, K=CNT-1 corresponds to output layer. + +Size of the output layer is always equal to the number of outputs, although +when we have softmax-normalized network, last neuron doesn't have any +connections - it is just zero. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpgetlayersize(const multilayerperceptron &network, const ae_int_t k); + + +/************************************************************************* +This function returns offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetinputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma); + + +/************************************************************************* +This function returns offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + +OUTPUT PARAMETERS: + Mean - mean term + Sigma - sigma term, guaranteed to be nonzero. + +I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. In case we have SOFTMAX-normalized network, +we return (Mean,Sigma)=(0.0,1.0). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetoutputscaling(const multilayerperceptron &network, const ae_int_t i, double &mean, double &sigma); + + +/************************************************************************* +This function returns information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + +OUTPUT PARAMETERS: + FKind - activation function type (used by MLPActivationFunction()) + this value is zero for input or linear neurons + Threshold - also called offset, bias + zero for input neurons + +NOTE: this function throws exception if layer or neuron with given index +do not exists. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpgetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, ae_int_t &fkind, double &threshold); + + +/************************************************************************* +This function returns information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + +RESULT: + connection weight (zero for non-existent connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. returns zero if neurons exist, but there is no connection between them + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +double mlpgetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1); + + +/************************************************************************* +This function sets offset/scaling coefficients for I-th input of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +NTE: I-th input is passed through linear transformation + IN[i] = (IN[i]-Mean)/Sigma +before feeding to the network. This function sets Mean and Sigma. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetinputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma); + + +/************************************************************************* +This function sets offset/scaling coefficients for I-th output of the +network. + +INPUT PARAMETERS: + Network - network + I - input index + Mean - mean term + Sigma - sigma term (if zero, will be replaced by 1.0) + +OUTPUT PARAMETERS: + +NOTE: I-th output is passed through linear transformation + OUT[i] = OUT[i]*Sigma+Mean +before returning it to user. This function sets Sigma/Mean. In case we +have SOFTMAX-normalized network, you can not set (Sigma,Mean) to anything +other than(0.0,1.0) - this function will throw exception. + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetoutputscaling(const multilayerperceptron &network, const ae_int_t i, const double mean, const double sigma); + + +/************************************************************************* +This function modifies information about Ith neuron of Kth layer + +INPUT PARAMETERS: + Network - network + K - layer index + I - neuron index (within layer) + FKind - activation function type (used by MLPActivationFunction()) + this value must be zero for input neurons + (you can not set activation function for input neurons) + Threshold - also called offset, bias + this value must be zero for input neurons + (you can not set threshold for input neurons) + +NOTES: +1. this function throws exception if layer or neuron with given index do + not exists. +2. this function also throws exception when you try to set non-linear + activation function for input neurons (any kind of network) or for output + neurons of classifier network. +3. this function throws exception when you try to set non-zero threshold for + input neurons (any kind of network). + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetneuroninfo(const multilayerperceptron &network, const ae_int_t k, const ae_int_t i, const ae_int_t fkind, const double threshold); + + +/************************************************************************* +This function modifies information about connection from I0-th neuron of +K0-th layer to I1-th neuron of K1-th layer. + +INPUT PARAMETERS: + Network - network + K0 - layer index + I0 - neuron index (within layer) + K1 - layer index + I1 - neuron index (within layer) + W - connection weight (must be zero for non-existent + connections) + +This function: +1. throws exception if layer or neuron with given index do not exists. +2. throws exception if you try to set non-zero weight for non-existent + connection + + -- ALGLIB -- + Copyright 25.03.2011 by Bochkanov Sergey +*************************************************************************/ +void mlpsetweight(const multilayerperceptron &network, const ae_int_t k0, const ae_int_t i0, const ae_int_t k1, const ae_int_t i1, const double w); + + +/************************************************************************* +Neural network activation function + +INPUT PARAMETERS: + NET - neuron input + K - function index (zero for linear function) + +OUTPUT PARAMETERS: + F - function + DF - its derivative + D2F - its second derivative + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpactivationfunction(const double net, const ae_int_t k, double &f, double &df, double &d2f); + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Network - neural network + X - input vector, array[0..NIn-1]. + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + +See also MLPProcessI + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpprocess(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +'interactive' variant of MLPProcess for languages like Python which +support constructs like "Y = MLPProcess(NN,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 21.09.2010 by Bochkanov Sergey +*************************************************************************/ +void mlpprocessi(const multilayerperceptron &network, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlperror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Error of the neural network on dataset given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x, depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0 + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlperrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +Natural error function for neural network, internal subroutine. + +NOTE: this function is single-threaded. Unlike other error function, it +receives no speed-up from being executed in SMP mode. + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlperrorn(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize); + + +/************************************************************************* +Classification error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: + classification error (number of misclassified cases) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +ae_int_t mlpclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +ae_int_t smp_mlpclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Relative classification error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 25.12.2008 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlprelclserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Relative classification error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. Sparse matrix must use CRS format + for storage. + NPoints - points count, >=0. + +RESULT: +Percent of incorrectly classified cases. Works both for classifier +networks and general purpose networks used as classifiers. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprelclserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlprelclserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 08.01.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpavgce(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlpavgce(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set given by +sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +CrossEntropy/(NPoints*LN(2)). +Zero if network solves regression task. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 9.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgcesparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlpavgcesparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +RMS error on the test set given. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +double mlprmserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlprmserror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +RMS error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Root mean square error. Its meaning for regression task is obvious. As for +classification task, RMS error means error when estimating posterior +probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlprmserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlprmserrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +Average absolute error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlpavgerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average absolute error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average error when estimating posterior probabilities. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlpavgerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +Average relative error on the test set. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + NPoints - points count. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 11.03.2008 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); +double smp_mlpavgrelerror(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average relative error on the test set given by sparse matrix. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + NPoints - points count, >=0. + +RESULT: +Its meaning for regression task is obvious. As for classification task, it +means average relative error when estimating posterior probability of +belonging to the correct class. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 09.08.2012 by Bochkanov Sergey +*************************************************************************/ +double mlpavgrelerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); +double smp_mlpavgrelerrorsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +Gradient calculation + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgrad(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad); + + +/************************************************************************* +Gradient calculation (natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + X - input vector, length of array must be at least NIn + DesiredY- desired outputs, length of array must be at least NOut + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradn(const multilayerperceptron &network, const real_1d_array &x, const real_1d_array &desiredy, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad); +void smp_mlpgradbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs given by sparse +matrices + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs. + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t ssize, double &e, real_1d_array &grad); +void smp_mlpgradbatchsparse(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t ssize, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch gradient calculation for a subset of dataset + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in dense format; one sample = one row: + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad); +void smp_mlpgradbatchsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs for a subset of +dataset given by set of indexes. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset in sparse format; one sample = one row: + * MATRIX MUST BE STORED IN CRS FORMAT + * first NIn columns contain inputs, + * for regression problem, next NOut columns store + desired outputs. + * for classification problem, next column (just one!) + stores class number. + SetSize - real size of XY, SetSize>=0; + Idx - subset of SubsetSize elements, array[SubsetSize]: + * Idx[I] stores row index in the original dataset which is + given by XY. Gradient is calculated with respect to rows + whose indexes are stored in Idx[]. + * Idx[] must store correct indexes; this function throws + an exception in case incorrect index (less than 0 or + larger than rows(XY)) is given + * Idx[] may store indexes in any order and even with + repetitions. + SubsetSize- number of elements in Idx[] array: + * positive value means that subset given by Idx[] is processed + * zero value results in zero gradient + * negative value means that full dataset is processed + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) + Grad - gradient of E with respect to weights of network, + array[WCount] + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatchSparse + function. + + -- ALGLIB -- + Copyright 26.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpgradbatchsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad); +void smp_mlpgradbatchsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &idx, const ae_int_t subsetsize, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch gradient calculation for a set of inputs/outputs +(natural error function is used) + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - set of inputs/outputs; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SSize - number of elements in XY + Grad - possibly preallocated array. If size of array is smaller + than WCount, it will be reallocated. It is recommended to + reuse previously allocated array to reduce allocation + overhead. + +OUTPUT PARAMETERS: + E - error function, sum-of-squares for regression networks, + cross-entropy for classification networks. + Grad - gradient of E with respect to weights of network, array[WCount] + + -- ALGLIB -- + Copyright 04.11.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpgradnbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad); + + +/************************************************************************* +Batch Hessian calculation (natural error function) using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessiannbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h); + + +/************************************************************************* +Batch Hessian calculation using R-algorithm. +Internal subroutine. + + -- ALGLIB -- + Copyright 26.01.2008 by Bochkanov Sergey. + + Hessian calculation based on R-algorithm described in + "Fast Exact Multiplication by the Hessian", + B. A. Pearlmutter, + Neural Computation, 1994. +*************************************************************************/ +void mlphessianbatch(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t ssize, double &e, real_1d_array &grad, real_2d_array &h); + + +/************************************************************************* +Calculation of all types of errors. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset; one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep); +void smp_mlpallerrorssubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep); + + +/************************************************************************* +Calculation of all types of errors on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - network initialized with one of the network creation funcs + XY - original dataset given by sparse matrix; + one sample = one row; + first NIn columns contain inputs, + next NOut columns - desired outputs. + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +OUTPUT PARAMETERS: + Rep - it contains all type of errors. + +NOTE: when SubsetSize<0 is used full dataset by call MLPGradBatch function. + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpallerrorssparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep); +void smp_mlpallerrorssparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize, modelerrors &rep); + + +/************************************************************************* +Error of the neural network on dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format; + SetSize - real size of XY, SetSize>=0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize); +double smp_mlperrorsubset(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize); + + +/************************************************************************* +Error of the neural network on sparse dataset. + + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support + ! + ! First improvement gives close-to-linear speedup on multicore systems. + ! Second improvement gives constant speedup (2-3x depending on your CPU) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + + +INPUT PARAMETERS: + Network - neural network; + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Sparse matrix must use CRS format for + storage. + SetSize - real size of XY, SetSize>=0; + it is used when SubsetSize<0; + Subset - subset of SubsetSize elements, array[SubsetSize]; + SubsetSize- number of elements in Subset[] array. + +RESULT: + sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +dataset format is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 04.09.2012 by Bochkanov Sergey +*************************************************************************/ +double mlperrorsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize); +double smp_mlperrorsparsesubset(const multilayerperceptron &network, const sparsematrix &xy, const ae_int_t setsize, const integer_1d_array &subset, const ae_int_t subsetsize); + +/************************************************************************* +This subroutine trains logit model. + +INPUT PARAMETERS: + XY - training set, array[0..NPoints-1,0..NVars] + First NVars columns store values of independent + variables, next column stores number of class (from 0 + to NClasses-1) which dataset element belongs to. Fractional + values are rounded to nearest integer. + NPoints - training set size, NPoints>=1 + NVars - number of independent variables, NVars>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints=1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreate(const ae_int_t n, mcpdstate &s); + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "entry" state and is treated +in a special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +Such conditions basically mean that row of P which corresponds to "entry" +state is zero. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - at every moment of time there is some + (unpredictable) amount of "new" individuals, which can transit into one + of the states at the next turn, but still no one leaves population +* you want to model transitions of individuals from one state into another +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentry(const ae_int_t n, const ae_int_t entrystate, mcpdstate &s); + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Exit-state" model, i.e. model where transition from X[i] to X[i+1] +is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +and one selected component of X[] is called "exit" state and is treated +in a special way: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that column of P which corresponds to +"exit" state is zero. Multiplication by such P may decrease sum of vector +components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant - individuals can move into "exit" state + and leave population at the next turn, but there are no new individuals +* amount of individuals which leave population can be predicted +* you want to model transitions of individuals from one state into another + (including transitions into the "exit" state) + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateexit(const ae_int_t n, const ae_int_t exitstate, mcpdstate &s); + + +/************************************************************************* +DESCRIPTION: + +This function is a specialized version of MCPDCreate() function, and we +recommend you to read comments for this function for general information +about MCPD solver. + +This function creates MCPD (Markov Chains for Population Data) solver +for "Entry-Exit-states" model, i.e. model where transition from X[i] to +X[i+1] is modelled as + X[i+1] = P*X[i] +where + X[i] and X[i+1] are N-dimensional state vectors + P is a N*N transition matrix +one selected component of X[] is called "entry" state and is treated in a +special way: + system state always transits from "entry" state to some another state + system state can not transit from any state into "entry" state +and another one component of X[] is called "exit" state and is treated in +a special way too: + system state can transit from any state into "exit" state + system state can not transit from "exit" state into any other state + transition operator discards "exit" state (makes it zero at each turn) +Such conditions basically mean that: + row of P which corresponds to "entry" state is zero + column of P which corresponds to "exit" state is zero +Multiplication by such P may decrease sum of vector components. + +Such models arise when: +* there is some population of individuals +* individuals can have different states +* individuals can transit from one state to another +* population size is NOT constant +* at every moment of time there is some (unpredictable) amount of "new" + individuals, which can transit into one of the states at the next turn +* some individuals can move (predictably) into "exit" state and leave + population at the next turn +* you want to model transitions of individuals from one state into another, + including transitions from the "entry" state and into the "exit" state. +* but you do NOT want to predict amount of "new" individuals because it + does not depends on individuals already present (hence system can not + transit INTO entry state - it can only transit FROM it). + +This model is discussed in more details in the ALGLIB User Guide (see +http://www.alglib.net/dataanalysis/ for more data). + +INPUT PARAMETERS: + N - problem dimension, N>=2 + EntryState- index of entry state, in 0..N-1 + ExitState- index of exit state, in 0..N-1 + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdcreateentryexit(const ae_int_t n, const ae_int_t entrystate, const ae_int_t exitstate, mcpdstate &s); + + +/************************************************************************* +This function is used to add a track - sequence of system states at the +different moments of its evolution. + +You may add one or several tracks to the MCPD solver. In case you have +several tracks, they won't overwrite each other. For example, if you pass +two tracks, A1-A2-A3 (system at t=A+1, t=A+2 and t=A+3) and B1-B2-B3, then +solver will try to model transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, +t=B+1 to t=B+2, t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it +wont try to model transition from t=A+3 to t=B+1. + +INPUT PARAMETERS: + S - solver + XY - track, array[K,N]: + * I-th row is a state at t=I + * elements of XY must be non-negative (exception will be + thrown on negative elements) + K - number of points in a track + * if given, only leading K rows of XY are used + * if not given, automatically determined from size of XY + +NOTES: + +1. Track may contain either proportional or population data: + * with proportional data all rows of XY must sum to 1.0, i.e. we have + proportions instead of absolute population values + * with population data rows of XY contain population counts and generally + do not sum to 1.0 (although they still must be non-negative) + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy, const ae_int_t k); +void mcpdaddtrack(const mcpdstate &s, const real_2d_array &xy); + + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place equality constraints on arbitrary +subset of elements of P. Set of constraints is specified by EC, which may +contain either NAN's or finite numbers from [0,1]. NAN denotes absence of +constraint, finite number denotes equality constraint on specific element +of P. + +You can also use MCPDAddEC() function which allows to ADD equality +constraint for one element of P without changing constraints for other +elements. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + EC - equality constraints, array[N,N]. Elements of EC can be + either NAN's or finite numbers from [0,1]. NAN denotes + absence of constraints, while finite value denotes + equality constraint on the corresponding element of P. + +NOTES: + +1. infinite values of EC will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetec(const mcpdstate &s, const real_2d_array &ec); + + +/************************************************************************* +This function is used to add equality constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD equality constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetEC() function which allows you to specify +arbitrary set of equality constraints in one call. + +These functions (MCPDSetEC and MCPDAddEC) interact as follows: +* there is internal matrix of equality constraints which is stored in the + MCPD solver +* MCPDSetEC() replaces this matrix by another one (SET) +* MCPDAddEC() modifies one element of this matrix and leaves other ones + unchanged (ADD) +* thus MCPDAddEC() call preserves all modifications done by previous + calls, while MCPDSetEC() completely discards all changes done to the + equality constraints. + +INPUT PARAMETERS: + S - solver + I - row index of element being constrained + J - column index of element being constrained + C - value (constraint for P[I,J]). Can be either NAN (no + constraint) or finite value from [0,1]. + +NOTES: + +1. infinite values of C will lead to exception being thrown. Values less +than 0.0 or greater than 1.0 will lead to error code being returned after +call to MCPDSolve(). + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdaddec(const mcpdstate &s, const ae_int_t i, const ae_int_t j, const double c); + + +/************************************************************************* +This function is used to add bound constraints on the elements of the +transition matrix P. + +MCPD solver has four types of constraints which can be placed on P: +* user-specified equality constraints (optional) +* user-specified bound constraints (optional) +* user-specified general linear constraints (optional) +* basic constraints (always present): + * non-negativity: P[i,j]>=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to place bound constraints on arbitrary +subset of elements of P. Set of constraints is specified by BndL/BndU +matrices, which may contain arbitrary combination of finite numbers or +infinities (like -INF=0 + * consistency: every column of P sums to 1.0 + +Final constraints which are passed to the underlying optimizer are +calculated as intersection of all present constraints. For example, you +may specify boundary constraint on P[0,0] and equality one: + 0.1<=P[0,0]<=0.9 + P[0,0]=0.5 +Such combination of constraints will be silently reduced to their +intersection, which is P[0,0]=0.5. + +This function can be used to ADD bound constraint for one element of P +without changing constraints for other elements. + +You can also use MCPDSetBC() function which allows to place bound +constraints on arbitrary subset of elements of P. Set of constraints is +specified by BndL/BndU matrices, which may contain arbitrary combination +of finite numbers or infinities (like -INF=" (CT[i]>0). + +Your constraint may involve only some subset of P (less than N*N elements). +For example it can be something like + P[0,0] + P[0,1] = 0.5 +In this case you still should pass matrix with N*N+1 columns, but all its +elements (except for C[0,0], C[0,1] and C[0,N*N-1]) will be zero. + +INPUT PARAMETERS: + S - solver + C - array[K,N*N+1] - coefficients of constraints + (see above for complete description) + CT - array[K] - constraint types + (see above for complete description) + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k); +void mcpdsetlc(const mcpdstate &s, const real_2d_array &c, const integer_1d_array &ct); + + +/************************************************************************* +This function allows to tune amount of Tikhonov regularization being +applied to your problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change coefficient r. You can also change +prior values with MCPDSetPrior() function. + +INPUT PARAMETERS: + S - solver + V - regularization coefficient, finite non-negative value. It + is not recommended to specify zero value unless you are + pretty sure that you want it. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsettikhonovregularizer(const mcpdstate &s, const double v); + + +/************************************************************************* +This function allows to set prior values used for regularization of your +problem. + +By default, regularizing term is equal to r*||P-prior_P||^2, where r is a +small non-zero value, P is transition matrix, prior_P is identity matrix, +||X||^2 is a sum of squared elements of X. + +This function allows you to change prior values prior_P. You can also +change r with MCPDSetTikhonovRegularizer() function. + +INPUT PARAMETERS: + S - solver + PP - array[N,N], matrix of prior values: + 1. elements must be real numbers from [0,1] + 2. columns must sum to 1.0. + First property is checked (exception is thrown otherwise), + while second one is not checked/enforced. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetprior(const mcpdstate &s, const real_2d_array &pp); + + +/************************************************************************* +This function is used to change prediction weights + +MCPD solver scales prediction errors as follows + Error(P) = ||W*(y-P*x)||^2 +where + x is a system state at time t + y is a system state at time t+1 + P is a transition matrix + W is a diagonal scaling matrix + +By default, weights are chosen in order to minimize relative prediction +error instead of absolute one. For example, if one component of state is +about 0.5 in magnitude and another one is about 0.05, then algorithm will +make corresponding weights equal to 2.0 and 20.0. + +INPUT PARAMETERS: + S - solver + PW - array[N], weights: + * must be non-negative values (exception will be thrown otherwise) + * zero values will be replaced by automatically chosen values + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsetpredictionweights(const mcpdstate &s, const real_1d_array &pw); + + +/************************************************************************* +This function is used to start solution of the MCPD problem. + +After return from this function, you can use MCPDResults() to get solution +and completion code. + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdsolve(const mcpdstate &s); + + +/************************************************************************* +MCPD results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + P - array[N,N], transition matrix + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one. Speaking short, positive values denote + success, negative ones are failures. + More information about fields of this structure can be + found in the comments on MCPDReport datatype. + + + -- ALGLIB -- + Copyright 23.05.2010 by Bochkanov Sergey +*************************************************************************/ +void mcpdresults(const mcpdstate &s, real_2d_array &p, mcpdreport &rep); + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void mlpeserialize(mlpensemble &obj, std::string &s_out); + + +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void mlpeunserialize(std::string &s_in, mlpensemble &obj); + + +/************************************************************************* +Like MLPCreate0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreate1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreate2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreate2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateB0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb0(const ae_int_t nin, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateB1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateB2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreateb2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double b, const double d, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateR0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater0(const ae_int_t nin, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateR1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateR2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreater2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const double a, const double b, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateC0, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec0(const ae_int_t nin, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateC1, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec1(const ae_int_t nin, const ae_int_t nhid, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Like MLPCreateC2, but for ensembles. + + -- ALGLIB -- + Copyright 18.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatec2(const ae_int_t nin, const ae_int_t nhid1, const ae_int_t nhid2, const ae_int_t nout, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Creates ensemble from network. Only network geometry is copied. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpecreatefromnetwork(const multilayerperceptron &network, const ae_int_t ensemblesize, mlpensemble &ensemble); + + +/************************************************************************* +Randomization of MLP ensemble + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlperandomize(const mlpensemble &ensemble); + + +/************************************************************************* +Return ensemble properties (number of inputs and outputs). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeproperties(const mlpensemble &ensemble, ae_int_t &nin, ae_int_t &nout); + + +/************************************************************************* +Return normalization type (whether ensemble is SOFTMAX-normalized or not). + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +bool mlpeissoftmax(const mlpensemble &ensemble); + + +/************************************************************************* +Procesing + +INPUT PARAMETERS: + Ensemble- neural networks ensemble + X - input vector, array[0..NIn-1]. + Y - (possibly) preallocated buffer; if size of Y is less than + NOut, it will be reallocated. If it is large enough, it + is NOT reallocated, so we can save some time on reallocation. + + +OUTPUT PARAMETERS: + Y - result. Regression estimate when solving regression task, + vector of posterior probabilities for classification task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocess(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +'interactive' variant of MLPEProcess for languages like Python which +support constructs like "Y = MLPEProcess(LM,X)" and interactive mode of the +interpreter + +This function allocates new array on each call, so it is significantly +slower than its 'non-interactive' counterpart, but it is more convenient +when you call it from command line. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpeprocessi(const mlpensemble &ensemble, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +Relative classification error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + percent of incorrectly classified cases. + Works both for classifier betwork and for regression networks which +are used as classifiers. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlperelclserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average cross-entropy (in bits per element) on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + CrossEntropy/(NPoints*LN(2)). + Zero if ensemble solves regression task. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgce(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +RMS error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + root mean square error. + Its meaning for regression task is obvious. As for classification task +RMS error means error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpermserror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +Average relative error on the test set + +INPUT PARAMETERS: + Ensemble- ensemble + XY - test set + NPoints - test set size + +RESULT: + Its meaning for regression task is obvious. As for classification task +it means average relative error when estimating posterior probabilities. + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +double mlpeavgrelerror(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints); + +/************************************************************************* +Neural network training using modified Levenberg-Marquardt with exact +Hessian calculation and regularization. Subroutine trains neural network +with restarts from random positions. Algorithm is well suited for small +and medium scale problems (hundreds of weights). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -9, if internal matrix inverse subroutine failed + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep); + + +/************************************************************************* +Neural network training using L-BFGS algorithm with regularization. +Subroutine trains neural network with restarts from random positions. +Algorithm is well suited for problems of any dimensionality (memory +requirements and step complexity are linear by weights number). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts from random position, >0. + If you don't know what Restarts to choose, use 2. + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + MaxIts - stopping criterion. Algorithm stops after MaxIts + iterations (NOT gradient calculations). Zero MaxIts + means stopping when step is sufficiently small. + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlptrainlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep); + + +/************************************************************************* +Neural network training using early stopping (base algorithm - L-BFGS with +regularization). + +INPUT PARAMETERS: + Network - neural network with initialized geometry + TrnXY - training set + TrnSize - training set size, TrnSize>0 + ValXY - validation set + ValSize - validation set size, ValSize>0 + Decay - weight decay constant, >=0.001 + Decay term 'Decay*||Weights||^2' is added to error + function. + If you don't know what Decay to choose, use 0.001. + Restarts - number of restarts, either: + * strictly positive number - algorithm make specified + number of restarts from random position. + * -1, in which case algorithm makes exactly one run + from the initial state of the network (no randomization). + If you don't know what Restarts to choose, choose one + one the following: + * -1 (deterministic start) + * +1 (one random restart) + * +5 (moderate amount of random restarts) + +OUTPUT PARAMETERS: + Network - trained neural network. + Info - return code: + * -2, if there is a point with class number + outside of [0..NOut-1]. + * -1, if wrong parameters specified + (NPoints<0, Restarts<1, ...). + * 2, task has been solved, stopping criterion met - + sufficiently small step size. Not expected (we + use EARLY stopping) but possible and not an + error. + * 6, task has been solved, stopping criterion met - + increasing of validation set error. + Rep - training report + +NOTE: + +Algorithm stops if validation set error increases for a long enough or +step size is small enought (there are task where validation set may +decrease for eternity). In any case solution returned corresponds to the +minimum of validation set error. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlptraines(const multilayerperceptron &network, const real_2d_array &trnxy, const ae_int_t trnsize, const real_2d_array &valxy, const ae_int_t valsize, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep); + + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - L-BFGS. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlbfgs(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep); + + +/************************************************************************* +Cross-validation estimate of generalization error. + +Base algorithm - Levenberg-Marquardt. + +INPUT PARAMETERS: + Network - neural network with initialized geometry. Network is + not changed during cross-validation - it is used only + as a representative of its architecture. + XY - training set. + SSize - training set size + Decay - weight decay, same as in MLPTrainLBFGS + Restarts - number of restarts, >0. + restarts are counted for each partition separately, so + total number of restarts will be Restarts*FoldsCount. + FoldsCount - number of folds in k-fold cross-validation, + 2<=FoldsCount<=SSize. + recommended value: 10. + +OUTPUT PARAMETERS: + Info - return code, same as in MLPTrainLBFGS + Rep - report, same as in MLPTrainLM/MLPTrainLBFGS + CVRep - generalization error estimates + + -- ALGLIB -- + Copyright 09.12.2007 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcvlm(const multilayerperceptron &network, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const ae_int_t foldscount, ae_int_t &info, mlpreport &rep, mlpcvreport &cvrep); + + +/************************************************************************* +This function estimates generalization error using cross-validation on the +current dataset with current training settings. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * FoldsCount cross-validation rounds (always) + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. Network is not changed during cross- + validation and is not trained - it is used only as + representative of its architecture. I.e., we estimate + generalization properties of ARCHITECTURE, not some + specific network. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that for each cross-validation + round specified number of random restarts is + performed, with best network being chosen after + training. + * NRestarts=0 is same as NRestarts=1 + FoldsCount - number of folds in k-fold cross-validation: + * 2<=FoldsCount<=size of dataset + * recommended value: 10. + * values larger than dataset size will be silently + truncated down to dataset size + +OUTPUT PARAMETERS: + Rep - structure which contains cross-validation estimates: + * Rep.RelCLSError - fraction of misclassified cases. + * Rep.AvgCE - acerage cross-entropy + * Rep.RMSError - root-mean-square error + * Rep.AvgError - average error + * Rep.AvgRelError - average relative error + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or subset with only one point was given, zeros are returned as + estimates. + +NOTE: this method performs FoldsCount cross-validation rounds, each one + with NRestarts random starts. Thus, FoldsCount*NRestarts networks + are trained in total. + +NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems. + +NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError + contain errors in prediction of posterior probabilities. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep); +void smp_mlpkfoldcv(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, const ae_int_t foldscount, mlpreport &rep); + + +/************************************************************************* +Creation of the network trainer object for regression networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NOut - number of outputs, NOut>=1 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any regression + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainer(const ae_int_t nin, const ae_int_t nout, mlptrainer &s); + + +/************************************************************************* +Creation of the network trainer object for classification networks + +INPUT PARAMETERS: + NIn - number of inputs, NIn>=1 + NClasses - number of classes, NClasses>=2 + +OUTPUT PARAMETERS: + S - neural network trainer object. + This structure can be used to train any classification + network with NIn inputs and NOut outputs. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpcreatetrainercls(const ae_int_t nin, const ae_int_t nclasses, mlptrainer &s); + + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user. + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. + NPoints - points count, >=0. + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdataset(const mlptrainer &s, const real_2d_array &xy, const ae_int_t npoints); + + +/************************************************************************* +This function sets "current dataset" of the trainer object to one passed +by user (sparse matrix is used to store dataset). + +INPUT PARAMETERS: + S - trainer object + XY - training set, see below for information on the + training set format. This function checks correctness + of the dataset (no NANs/INFs, class numbers are + correct) and throws exception when incorrect dataset + is passed. Any sparse storage format can be used: + Hash-table, CRS... + NPoints - points count, >=0 + +DATASET FORMAT: + +This function uses two different dataset formats - one for regression +networks, another one for classification networks. + +For regression networks with NIn inputs and NOut outputs following dataset +format is used: +* dataset is given by NPoints*(NIn+NOut) matrix +* each row corresponds to one example +* first NIn columns are inputs, next NOut columns are outputs + +For classification networks with NIn inputs and NClasses clases following +datasetformat is used: +* dataset is given by NPoints*(NIn+1) matrix +* each row corresponds to one example +* first NIn columns are inputs, last column stores class number (from 0 to + NClasses-1). + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetsparsedataset(const mlptrainer &s, const sparsematrix &xy, const ae_int_t npoints); + + +/************************************************************************* +This function sets weight decay coefficient which is used for training. + +INPUT PARAMETERS: + S - trainer object + Decay - weight decay coefficient, >=0. Weight decay term + 'Decay*||Weights||^2' is added to error function. If + you don't know what Decay to choose, use 1.0E-3. + Weight decay can be set to zero, in this case network + is trained without weight decay. + +NOTE: by default network uses some small nonzero value for weight decay. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetdecay(const mlptrainer &s, const double decay); + + +/************************************************************************* +This function sets stopping criteria for the optimizer. + +INPUT PARAMETERS: + S - trainer object + WStep - stopping criterion. Algorithm stops if step size is + less than WStep. Recommended value - 0.01. Zero step + size means stopping after MaxIts iterations. + WStep>=0. + MaxIts - stopping criterion. Algorithm stops after MaxIts + epochs (full passes over entire dataset). Zero MaxIts + means stopping when step is sufficiently small. + MaxIts>=0. + +NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values are also + used when MLPSetCond() is called with WStep=0 and MaxIts=0. + +NOTE: these stopping criteria are used for all kinds of neural training - + from "conventional" networks to early stopping ensembles. When used + for "conventional" networks, they are used as the only stopping + criteria. When combined with early stopping, they used as ADDITIONAL + stopping criteria which can terminate early stopping algorithm. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetcond(const mlptrainer &s, const double wstep, const ae_int_t maxits); + + +/************************************************************************* +This function sets training algorithm: batch training using L-BFGS will be +used. + +This algorithm: +* the most robust for small-scale problems, but may be too slow for large + scale ones. +* perfoms full pass through the dataset before performing step +* uses conditions specified by MLPSetCond() for stopping +* is default one used by trainer object + +INPUT PARAMETERS: + S - trainer object + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpsetalgobatch(const mlptrainer &s); + + +/************************************************************************* +This function trains neural network passed to this function, using current +dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset()) +and current training settings. Training from NRestarts random starting +positions is performed, best network is chosen. + +Training is performed using current training algorithm. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * NRestarts training sessions performed within each of + ! cross-validation rounds (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed, best network is chosen after + training + * NRestarts=0 means that current state of the network + is used for training. + +OUTPUT PARAMETERS: + Network - trained network + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + network is filled by zero values. Same behavior for functions + MLPStartTraining and MLPContinueTraining. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainnetwork(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, mlpreport &rep); +void smp_mlptrainnetwork(const mlptrainer &s, const multilayerperceptron &network, const ae_int_t nrestarts, mlpreport &rep); + + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +After call to this function trainer object remembers network and is ready +to train it. However, no training is performed until first call to +MLPContinueTraining() function. Subsequent calls to MLPContinueTraining() +will advance training progress one iteration further. + +EXAMPLE: + > + > ...initialize network and trainer object.... + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > ...visualize training progress... + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network. It must have same number of inputs and + output/classes as was specified during creation of the + trainer object. + RandomStart - randomize network before training or not: + * True means that network is randomized and its + initial state (one which was passed to the trainer + object) is lost. + * False means that training is started from the + current state of the network + +OUTPUT PARAMETERS: + Network - neural network which is ready to training (weights are + initialized, preprocessor is initialized using current + training set) + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +void mlpstarttraining(const mlptrainer &s, const multilayerperceptron &network, const bool randomstart); + + +/************************************************************************* +IMPORTANT: this is an "expert" version of the MLPTrain() function. We do + not recommend you to use it unless you are pretty sure that you + need ability to monitor training progress. + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +This function performs step-by-step training of the neural network. Here +"step-by-step" means that training starts with MLPStartTraining() call, +and then user subsequently calls MLPContinueTraining() to perform one more +iteration of the training. + +This function performs one more iteration of the training and returns +either True (training continues) or False (training stopped). In case True +was returned, Network weights are updated according to the current state +of the optimization progress. In case False was returned, no additional +updates is performed (previous update of the network weights moved us to +the final point, and no additional updates is needed). + +EXAMPLE: + > + > [initialize network and trainer object] + > + > MLPStartTraining(Trainer, Network, True) + > while MLPContinueTraining(Trainer, Network) do + > [visualize training progress] + > + +INPUT PARAMETERS: + S - trainer object + Network - neural network structure, which is used to store + current state of the training process. + +OUTPUT PARAMETERS: + Network - weights of the neural network are rewritten by the + current approximation. + +NOTE: this method uses sum-of-squares error function for training. + +NOTE: it is expected that trainer object settings are NOT changed during + step-by-step training, i.e. no one changes stopping criteria or + training set during training. It is possible and there is no defense + against such actions, but algorithm behavior in such cases is + undefined and can be unpredictable. + +NOTE: It is expected that Network is the same one which was passed to + MLPStartTraining() function. However, THIS function checks only + following: + * that number of network inputs is consistent with trainer object + settings + * that number of network outputs/classes is consistent with trainer + object settings + * that number of network weights is the same as number of weights in + the network passed to MLPStartTraining() function + Exception is thrown when these conditions are violated. + + It is also expected that you do not change state of the network on + your own - the only party who has right to change network during its + training is a trainer object. Any attempt to interfere with trainer + may lead to unpredictable results. + + + -- ALGLIB -- + Copyright 23.07.2012 by Bochkanov Sergey +*************************************************************************/ +bool mlpcontinuetraining(const mlptrainer &s, const multilayerperceptron &network); +bool smp_mlpcontinuetraining(const mlptrainer &s, const multilayerperceptron &network); + + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +Modified Levenberg-Marquardt algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglm(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors); + + +/************************************************************************* +Training neural networks ensemble using bootstrap aggregating (bagging). +L-BFGS algorithm is used as base training method. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + WStep - stopping criterion, same as in MLPTrainLBFGS + MaxIts - stopping criterion, same as in MLPTrainLBFGS + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -8, if both WStep=0 and MaxIts=0 + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 2, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 17.02.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpebagginglbfgs(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, const double wstep, const ae_int_t maxits, ae_int_t &info, mlpreport &rep, mlpcvreport &ooberrors); + + +/************************************************************************* +Training neural networks ensemble using early stopping. + +INPUT PARAMETERS: + Ensemble - model with initialized geometry + XY - training set + NPoints - training set size + Decay - weight decay coefficient, >=0.001 + Restarts - restarts, >0. + +OUTPUT PARAMETERS: + Ensemble - trained model + Info - return code: + * -2, if there is a point with class number + outside of [0..NClasses-1]. + * -1, if incorrect parameters was passed + (NPoints<0, Restarts<1). + * 6, if task has been solved. + Rep - training report. + OOBErrors - out-of-bag generalization error estimate + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void mlpetraines(const mlpensemble &ensemble, const real_2d_array &xy, const ae_int_t npoints, const double decay, const ae_int_t restarts, ae_int_t &info, mlpreport &rep); + + +/************************************************************************* +This function trains neural network ensemble passed to this function using +current dataset and early stopping training algorithm. Each early stopping +round performs NRestarts random restarts (thus, EnsembleSize*NRestarts +training rounds is performed in total). + +FOR USERS OF COMMERCIAL EDITION: + + ! Commercial version of ALGLIB includes two important improvements of + ! this function: + ! * multicore support (C++ and C# computational cores) + ! * SSE support (C++ computational core) + ! + ! Second improvement gives constant speedup (2-3X). First improvement + ! gives close-to-linear speedup on multicore systems. Following + ! operations can be executed in parallel: + ! * EnsembleSize training sessions performed for each of ensemble + ! members (always parallelized) + ! * NRestarts training sessions performed within each of training + ! sessions (if NRestarts>1) + ! * gradient calculation over large dataset (if dataset is large enough) + ! + ! In order to use multicore features you have to: + ! * use commercial version of ALGLIB + ! * call this function with "smp_" prefix, which indicates that + ! multicore code will be used (for multicore support) + ! + ! In order to use SSE features you have to: + ! * use commercial version of ALGLIB on Intel processors + ! * use C++ computational core + ! + ! This note is given for users of commercial edition; if you use GPL + ! edition, you still will be able to call smp-version of this function, + ! but all computations will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + +INPUT PARAMETERS: + S - trainer object; + Ensemble - neural network ensemble. It must have same number of + inputs and outputs/classes as was specified during + creation of the trainer object. + NRestarts - number of restarts, >=0: + * NRestarts>0 means that specified number of random + restarts are performed during each ES round; + * NRestarts=0 is silently replaced by 1. + +OUTPUT PARAMETERS: + Ensemble - trained ensemble; + Rep - it contains all type of errors. + +NOTE: this training method uses BOTH early stopping and weight decay! So, + you should select weight decay before starting training just as you + select it before training "conventional" networks. + +NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(), + or single-point dataset was passed, ensemble is filled by zero + values. + +NOTE: this method uses sum-of-squares error function for training. + + -- ALGLIB -- + Copyright 22.08.2012 by Bochkanov Sergey +*************************************************************************/ +void mlptrainensemblees(const mlptrainer &s, const mlpensemble &ensemble, const ae_int_t nrestarts, mlpreport &rep); +void smp_mlptrainensemblees(const mlptrainer &s, const mlpensemble &ensemble, const ae_int_t nrestarts, mlpreport &rep); + +/************************************************************************* +Principal components analysis + +Subroutine builds orthogonal basis where first axis corresponds to +direction with maximum variance, second axis maximizes variance in subspace +orthogonal to first axis and so on. + +It should be noted that, unlike LDA, PCA does not use class labels. + +INPUT PARAMETERS: + X - dataset, array[0..NPoints-1,0..NVars-1]. + matrix contains ONLY INDEPENDENT VARIABLES. + NPoints - dataset size, NPoints>=0 + NVars - number of independent variables, NVars>=1 + +ÂÛÕÎÄÍÛÅ ÏÀÐÀÌÅÒÐÛ: + Info - return code: + * -4, if SVD subroutine haven't converged + * -1, if wrong parameters has been passed (NPoints<0, + NVars<1) + * 1, if task is solved + S2 - array[0..NVars-1]. variance values corresponding + to basis vectors. + V - array[0..NVars-1,0..NVars-1] + matrix, whose columns store basis vectors. + + -- ALGLIB -- + Copyright 25.08.2008 by Bochkanov Sergey +*************************************************************************/ +void pcabuildbasis(const real_2d_array &x, const ae_int_t npoints, const ae_int_t nvars, ae_int_t &info, real_1d_array &s2, real_2d_array &v); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void dserrallocate(ae_int_t nclasses, + /* Real */ ae_vector* buf, + ae_state *_state); +void dserraccumulate(/* Real */ ae_vector* buf, + /* Real */ ae_vector* y, + /* Real */ ae_vector* desiredy, + ae_state *_state); +void dserrfinish(/* Real */ ae_vector* buf, ae_state *_state); +void dsnormalize(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* means, + /* Real */ ae_vector* sigmas, + ae_state *_state); +void dsnormalizec(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* means, + /* Real */ ae_vector* sigmas, + ae_state *_state); +double dsgetmeanmindistance(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_state *_state); +void dstie(/* Real */ ae_vector* a, + ae_int_t n, + /* Integer */ ae_vector* ties, + ae_int_t* tiecount, + /* Integer */ ae_vector* p1, + /* Integer */ ae_vector* p2, + ae_state *_state); +void dstiefasti(/* Real */ ae_vector* a, + /* Integer */ ae_vector* b, + ae_int_t n, + /* Integer */ ae_vector* ties, + ae_int_t* tiecount, + /* Real */ ae_vector* bufr, + /* Integer */ ae_vector* bufi, + ae_state *_state); +void dsoptimalsplit2(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t* info, + double* threshold, + double* pal, + double* pbl, + double* par, + double* pbr, + double* cve, + ae_state *_state); +void dsoptimalsplit2fast(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + /* Integer */ ae_vector* tiesbuf, + /* Integer */ ae_vector* cntbuf, + /* Real */ ae_vector* bufr, + /* Integer */ ae_vector* bufi, + ae_int_t n, + ae_int_t nc, + double alpha, + ae_int_t* info, + double* threshold, + double* rms, + double* cvrms, + ae_state *_state); +void dssplitk(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t nc, + ae_int_t kmax, + ae_int_t* info, + /* Real */ ae_vector* thresholds, + ae_int_t* ni, + double* cve, + ae_state *_state); +void dsoptimalsplitk(/* Real */ ae_vector* a, + /* Integer */ ae_vector* c, + ae_int_t n, + ae_int_t nc, + ae_int_t kmax, + ae_int_t* info, + /* Real */ ae_vector* thresholds, + ae_int_t* ni, + double* cve, + ae_state *_state); +ae_bool _cvreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _cvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _cvreport_clear(void* _p); +void _cvreport_destroy(void* _p); +void clusterizercreate(clusterizerstate* s, ae_state *_state); +void clusterizersetpoints(clusterizerstate* s, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + ae_state *_state); +void clusterizersetdistances(clusterizerstate* s, + /* Real */ ae_matrix* d, + ae_int_t npoints, + ae_bool isupper, + ae_state *_state); +void clusterizersetahcalgo(clusterizerstate* s, + ae_int_t algo, + ae_state *_state); +void clusterizersetkmeanslimits(clusterizerstate* s, + ae_int_t restarts, + ae_int_t maxits, + ae_state *_state); +void clusterizerrunahc(clusterizerstate* s, + ahcreport* rep, + ae_state *_state); +void _pexec_clusterizerrunahc(clusterizerstate* s, + ahcreport* rep, ae_state *_state); +void clusterizerrunkmeans(clusterizerstate* s, + ae_int_t k, + kmeansreport* rep, + ae_state *_state); +void clusterizergetdistances(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, + ae_state *_state); +void _pexec_clusterizergetdistances(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_int_t disttype, + /* Real */ ae_matrix* d, ae_state *_state); +void clusterizergetkclusters(ahcreport* rep, + ae_int_t k, + /* Integer */ ae_vector* cidx, + /* Integer */ ae_vector* cz, + ae_state *_state); +void clusterizerseparatedbydist(ahcreport* rep, + double r, + ae_int_t* k, + /* Integer */ ae_vector* cidx, + /* Integer */ ae_vector* cz, + ae_state *_state); +void clusterizerseparatedbycorr(ahcreport* rep, + double r, + ae_int_t* k, + /* Integer */ ae_vector* cidx, + /* Integer */ ae_vector* cz, + ae_state *_state); +void kmeansgenerateinternal(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t k, + ae_int_t maxits, + ae_int_t restarts, + ae_int_t* info, + /* Real */ ae_matrix* ccol, + ae_bool needccol, + /* Real */ ae_matrix* crow, + ae_bool needcrow, + /* Integer */ ae_vector* xyc, + ae_state *_state); +ae_bool _clusterizerstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _clusterizerstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _clusterizerstate_clear(void* _p); +void _clusterizerstate_destroy(void* _p); +ae_bool _ahcreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _ahcreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _ahcreport_clear(void* _p); +void _ahcreport_destroy(void* _p); +ae_bool _kmeansreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _kmeansreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _kmeansreport_clear(void* _p); +void _kmeansreport_destroy(void* _p); +void kmeansgenerate(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t k, + ae_int_t restarts, + ae_int_t* info, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* xyc, + ae_state *_state); +void dfbuildrandomdecisionforest(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + double r, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state); +void dfbuildrandomdecisionforestx1(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + ae_int_t nrndvars, + double r, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state); +void dfbuildinternal(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t ntrees, + ae_int_t samplesize, + ae_int_t nfeatures, + ae_int_t flags, + ae_int_t* info, + decisionforest* df, + dfreport* rep, + ae_state *_state); +void dfprocess(decisionforest* df, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void dfprocessi(decisionforest* df, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +double dfrelclserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double dfavgce(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double dfrmserror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double dfavgerror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double dfavgrelerror(decisionforest* df, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +void dfcopy(decisionforest* df1, decisionforest* df2, ae_state *_state); +void dfalloc(ae_serializer* s, decisionforest* forest, ae_state *_state); +void dfserialize(ae_serializer* s, + decisionforest* forest, + ae_state *_state); +void dfunserialize(ae_serializer* s, + decisionforest* forest, + ae_state *_state); +ae_bool _decisionforest_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _decisionforest_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _decisionforest_clear(void* _p); +void _decisionforest_destroy(void* _p); +ae_bool _dfreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _dfreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _dfreport_clear(void* _p); +void _dfreport_destroy(void* _p); +ae_bool _dfinternalbuffers_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _dfinternalbuffers_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _dfinternalbuffers_clear(void* _p); +void _dfinternalbuffers_destroy(void* _p); +void lrbuild(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state); +void lrbuilds(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state); +void lrbuildzs(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state); +void lrbuildz(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + linearmodel* lm, + lrreport* ar, + ae_state *_state); +void lrunpack(linearmodel* lm, + /* Real */ ae_vector* v, + ae_int_t* nvars, + ae_state *_state); +void lrpack(/* Real */ ae_vector* v, + ae_int_t nvars, + linearmodel* lm, + ae_state *_state); +double lrprocess(linearmodel* lm, + /* Real */ ae_vector* x, + ae_state *_state); +double lrrmserror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double lravgerror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double lravgrelerror(linearmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +void lrcopy(linearmodel* lm1, linearmodel* lm2, ae_state *_state); +void lrlines(/* Real */ ae_matrix* xy, + /* Real */ ae_vector* s, + ae_int_t n, + ae_int_t* info, + double* a, + double* b, + double* vara, + double* varb, + double* covab, + double* corrab, + double* p, + ae_state *_state); +void lrline(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t* info, + double* a, + double* b, + ae_state *_state); +ae_bool _linearmodel_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _linearmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _linearmodel_clear(void* _p); +void _linearmodel_destroy(void* _p); +ae_bool _lrreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _lrreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _lrreport_clear(void* _p); +void _lrreport_destroy(void* _p); +void filtersma(/* Real */ ae_vector* x, + ae_int_t n, + ae_int_t k, + ae_state *_state); +void filterema(/* Real */ ae_vector* x, + ae_int_t n, + double alpha, + ae_state *_state); +void filterlrma(/* Real */ ae_vector* x, + ae_int_t n, + ae_int_t k, + ae_state *_state); +void fisherlda(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t* info, + /* Real */ ae_vector* w, + ae_state *_state); +void fisherldan(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t* info, + /* Real */ ae_matrix* w, + ae_state *_state); +ae_int_t mlpgradsplitcost(ae_state *_state); +ae_int_t mlpgradsplitsize(ae_state *_state); +void mlpcreate0(ae_int_t nin, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcreate1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcreate2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcreateb0(ae_int_t nin, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state); +void mlpcreateb1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state); +void mlpcreateb2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double b, + double d, + multilayerperceptron* network, + ae_state *_state); +void mlpcreater0(ae_int_t nin, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state); +void mlpcreater1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state); +void mlpcreater2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double a, + double b, + multilayerperceptron* network, + ae_state *_state); +void mlpcreatec0(ae_int_t nin, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcreatec1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcreatec2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + multilayerperceptron* network, + ae_state *_state); +void mlpcopy(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state); +void mlpcopyshared(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state); +ae_bool mlpsamearchitecture(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state); +void mlpcopytunableparameters(multilayerperceptron* network1, + multilayerperceptron* network2, + ae_state *_state); +void mlpexporttunableparameters(multilayerperceptron* network, + /* Real */ ae_vector* p, + ae_int_t* pcount, + ae_state *_state); +void mlpimporttunableparameters(multilayerperceptron* network, + /* Real */ ae_vector* p, + ae_state *_state); +void mlpserializeold(multilayerperceptron* network, + /* Real */ ae_vector* ra, + ae_int_t* rlen, + ae_state *_state); +void mlpunserializeold(/* Real */ ae_vector* ra, + multilayerperceptron* network, + ae_state *_state); +void mlprandomize(multilayerperceptron* network, ae_state *_state); +void mlprandomizefull(multilayerperceptron* network, ae_state *_state); +void mlpinitpreprocessor(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state); +void mlpinitpreprocessorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + ae_state *_state); +void mlpinitpreprocessorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + ae_state *_state); +void mlpinitpreprocessorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + ae_state *_state); +void mlpproperties(multilayerperceptron* network, + ae_int_t* nin, + ae_int_t* nout, + ae_int_t* wcount, + ae_state *_state); +ae_int_t mlpntotal(multilayerperceptron* network, ae_state *_state); +ae_int_t mlpgetinputscount(multilayerperceptron* network, + ae_state *_state); +ae_int_t mlpgetoutputscount(multilayerperceptron* network, + ae_state *_state); +ae_int_t mlpgetweightscount(multilayerperceptron* network, + ae_state *_state); +ae_bool mlpissoftmax(multilayerperceptron* network, ae_state *_state); +ae_int_t mlpgetlayerscount(multilayerperceptron* network, + ae_state *_state); +ae_int_t mlpgetlayersize(multilayerperceptron* network, + ae_int_t k, + ae_state *_state); +void mlpgetinputscaling(multilayerperceptron* network, + ae_int_t i, + double* mean, + double* sigma, + ae_state *_state); +void mlpgetoutputscaling(multilayerperceptron* network, + ae_int_t i, + double* mean, + double* sigma, + ae_state *_state); +void mlpgetneuroninfo(multilayerperceptron* network, + ae_int_t k, + ae_int_t i, + ae_int_t* fkind, + double* threshold, + ae_state *_state); +double mlpgetweight(multilayerperceptron* network, + ae_int_t k0, + ae_int_t i0, + ae_int_t k1, + ae_int_t i1, + ae_state *_state); +void mlpsetinputscaling(multilayerperceptron* network, + ae_int_t i, + double mean, + double sigma, + ae_state *_state); +void mlpsetoutputscaling(multilayerperceptron* network, + ae_int_t i, + double mean, + double sigma, + ae_state *_state); +void mlpsetneuroninfo(multilayerperceptron* network, + ae_int_t k, + ae_int_t i, + ae_int_t fkind, + double threshold, + ae_state *_state); +void mlpsetweight(multilayerperceptron* network, + ae_int_t k0, + ae_int_t i0, + ae_int_t k1, + ae_int_t i1, + double w, + ae_state *_state); +void mlpactivationfunction(double net, + ae_int_t k, + double* f, + double* df, + double* d2f, + ae_state *_state); +void mlpprocess(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mlpprocessi(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +double mlperror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlperror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlperrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlperrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +double mlperrorn(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state); +ae_int_t mlpclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +ae_int_t _pexec_mlpclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlprelclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlprelclserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlprelclserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlprelclserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgce(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgce(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgcesparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgcesparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +double mlprmserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlprmserror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlprmserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlprmserrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgrelerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgrelerror(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, ae_state *_state); +double mlpavgrelerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +double _pexec_mlpavgrelerrorsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t npoints, ae_state *_state); +void mlpgrad(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* desiredy, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void mlpgradn(multilayerperceptron* network, + /* Real */ ae_vector* x, + /* Real */ ae_vector* desiredy, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void mlpgradbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void _pexec_mlpgradbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state); +void mlpgradbatchsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void _pexec_mlpgradbatchsparse(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state); +void mlpgradbatchsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void _pexec_mlpgradbatchsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state); +void mlpgradbatchsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void _pexec_mlpgradbatchsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* idx, + ae_int_t subsetsize, + double* e, + /* Real */ ae_vector* grad, ae_state *_state); +void mlpgradbatchx(multilayerperceptron* network, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + ae_shared_pool* gradbuf, + ae_state *_state); +void mlpgradnbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + ae_state *_state); +void mlphessiannbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state); +void mlphessianbatch(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + double* e, + /* Real */ ae_vector* grad, + /* Real */ ae_matrix* h, + ae_state *_state); +void mlpinternalprocessvector(/* Integer */ ae_vector* structinfo, + /* Real */ ae_vector* weights, + /* Real */ ae_vector* columnmeans, + /* Real */ ae_vector* columnsigmas, + /* Real */ ae_vector* neurons, + /* Real */ ae_vector* dfdnet, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mlpalloc(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state); +void mlpserialize(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state); +void mlpunserialize(ae_serializer* s, + multilayerperceptron* network, + ae_state *_state); +void mlpallerrorssubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, + ae_state *_state); +void _pexec_mlpallerrorssubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, ae_state *_state); +void mlpallerrorssparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, + ae_state *_state); +void _pexec_mlpallerrorssparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + modelerrors* rep, ae_state *_state); +double mlperrorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_state *_state); +double _pexec_mlperrorsubset(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, ae_state *_state); +double mlperrorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, + ae_state *_state); +double _pexec_mlperrorsparsesubset(multilayerperceptron* network, + sparsematrix* xy, + ae_int_t setsize, + /* Integer */ ae_vector* subset, + ae_int_t subsetsize, ae_state *_state); +void mlpallerrorsx(multilayerperceptron* network, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + modelerrors* rep, + ae_state *_state); +ae_bool _modelerrors_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _modelerrors_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _modelerrors_clear(void* _p); +void _modelerrors_destroy(void* _p); +ae_bool _smlpgrad_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _smlpgrad_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _smlpgrad_clear(void* _p); +void _smlpgrad_destroy(void* _p); +ae_bool _multilayerperceptron_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _multilayerperceptron_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _multilayerperceptron_clear(void* _p); +void _multilayerperceptron_destroy(void* _p); +void mnltrainh(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t nclasses, + ae_int_t* info, + logitmodel* lm, + mnlreport* rep, + ae_state *_state); +void mnlprocess(logitmodel* lm, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mnlprocessi(logitmodel* lm, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mnlunpack(logitmodel* lm, + /* Real */ ae_matrix* a, + ae_int_t* nvars, + ae_int_t* nclasses, + ae_state *_state); +void mnlpack(/* Real */ ae_matrix* a, + ae_int_t nvars, + ae_int_t nclasses, + logitmodel* lm, + ae_state *_state); +void mnlcopy(logitmodel* lm1, logitmodel* lm2, ae_state *_state); +double mnlavgce(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mnlrelclserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mnlrmserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mnlavgerror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mnlavgrelerror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t ssize, + ae_state *_state); +ae_int_t mnlclserror(logitmodel* lm, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +ae_bool _logitmodel_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _logitmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _logitmodel_clear(void* _p); +void _logitmodel_destroy(void* _p); +ae_bool _logitmcstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _logitmcstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _logitmcstate_clear(void* _p); +void _logitmcstate_destroy(void* _p); +ae_bool _mnlreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mnlreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mnlreport_clear(void* _p); +void _mnlreport_destroy(void* _p); +void mcpdcreate(ae_int_t n, mcpdstate* s, ae_state *_state); +void mcpdcreateentry(ae_int_t n, + ae_int_t entrystate, + mcpdstate* s, + ae_state *_state); +void mcpdcreateexit(ae_int_t n, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state); +void mcpdcreateentryexit(ae_int_t n, + ae_int_t entrystate, + ae_int_t exitstate, + mcpdstate* s, + ae_state *_state); +void mcpdaddtrack(mcpdstate* s, + /* Real */ ae_matrix* xy, + ae_int_t k, + ae_state *_state); +void mcpdsetec(mcpdstate* s, + /* Real */ ae_matrix* ec, + ae_state *_state); +void mcpdaddec(mcpdstate* s, + ae_int_t i, + ae_int_t j, + double c, + ae_state *_state); +void mcpdsetbc(mcpdstate* s, + /* Real */ ae_matrix* bndl, + /* Real */ ae_matrix* bndu, + ae_state *_state); +void mcpdaddbc(mcpdstate* s, + ae_int_t i, + ae_int_t j, + double bndl, + double bndu, + ae_state *_state); +void mcpdsetlc(mcpdstate* s, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state); +void mcpdsettikhonovregularizer(mcpdstate* s, double v, ae_state *_state); +void mcpdsetprior(mcpdstate* s, + /* Real */ ae_matrix* pp, + ae_state *_state); +void mcpdsetpredictionweights(mcpdstate* s, + /* Real */ ae_vector* pw, + ae_state *_state); +void mcpdsolve(mcpdstate* s, ae_state *_state); +void mcpdresults(mcpdstate* s, + /* Real */ ae_matrix* p, + mcpdreport* rep, + ae_state *_state); +ae_bool _mcpdstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mcpdstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mcpdstate_clear(void* _p); +void _mcpdstate_destroy(void* _p); +ae_bool _mcpdreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mcpdreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mcpdreport_clear(void* _p); +void _mcpdreport_destroy(void* _p); +void mlpecreate0(ae_int_t nin, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreate1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreate2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreateb0(ae_int_t nin, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreateb1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreateb2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double b, + double d, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreater0(ae_int_t nin, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreater1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreater2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + double a, + double b, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreatec0(ae_int_t nin, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreatec1(ae_int_t nin, + ae_int_t nhid, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreatec2(ae_int_t nin, + ae_int_t nhid1, + ae_int_t nhid2, + ae_int_t nout, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecreatefromnetwork(multilayerperceptron* network, + ae_int_t ensemblesize, + mlpensemble* ensemble, + ae_state *_state); +void mlpecopy(mlpensemble* ensemble1, + mlpensemble* ensemble2, + ae_state *_state); +void mlperandomize(mlpensemble* ensemble, ae_state *_state); +void mlpeproperties(mlpensemble* ensemble, + ae_int_t* nin, + ae_int_t* nout, + ae_state *_state); +ae_bool mlpeissoftmax(mlpensemble* ensemble, ae_state *_state); +void mlpeprocess(mlpensemble* ensemble, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mlpeprocessi(mlpensemble* ensemble, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void mlpeallerrorsx(mlpensemble* ensemble, + /* Real */ ae_matrix* densexy, + sparsematrix* sparsexy, + ae_int_t datasetsize, + ae_int_t datasettype, + /* Integer */ ae_vector* idx, + ae_int_t subset0, + ae_int_t subset1, + ae_int_t subsettype, + ae_shared_pool* buf, + modelerrors* rep, + ae_state *_state); +void mlpeallerrorssparse(mlpensemble* ensemble, + sparsematrix* xy, + ae_int_t npoints, + double* relcls, + double* avgce, + double* rms, + double* avg, + double* avgrel, + ae_state *_state); +double mlperelclserror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mlpeavgce(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mlpermserror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mlpeavgerror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +double mlpeavgrelerror(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +void mlpealloc(ae_serializer* s, mlpensemble* ensemble, ae_state *_state); +void mlpeserialize(ae_serializer* s, + mlpensemble* ensemble, + ae_state *_state); +void mlpeunserialize(ae_serializer* s, + mlpensemble* ensemble, + ae_state *_state); +ae_bool _mlpensemble_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpensemble_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpensemble_clear(void* _p); +void _mlpensemble_destroy(void* _p); +void mlptrainlm(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state); +void mlptrainlbfgs(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + ae_state *_state); +void mlptraines(multilayerperceptron* network, + /* Real */ ae_matrix* trnxy, + ae_int_t trnsize, + /* Real */ ae_matrix* valxy, + ae_int_t valsize, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state); +void mlpkfoldcvlbfgs(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t foldscount, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state); +void mlpkfoldcvlm(multilayerperceptron* network, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t foldscount, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* cvrep, + ae_state *_state); +void mlpkfoldcv(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + ae_int_t foldscount, + mlpreport* rep, + ae_state *_state); +void _pexec_mlpkfoldcv(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + ae_int_t foldscount, + mlpreport* rep, ae_state *_state); +void mlpcreatetrainer(ae_int_t nin, + ae_int_t nout, + mlptrainer* s, + ae_state *_state); +void mlpcreatetrainercls(ae_int_t nin, + ae_int_t nclasses, + mlptrainer* s, + ae_state *_state); +void mlpsetdataset(mlptrainer* s, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_state *_state); +void mlpsetsparsedataset(mlptrainer* s, + sparsematrix* xy, + ae_int_t npoints, + ae_state *_state); +void mlpsetdecay(mlptrainer* s, double decay, ae_state *_state); +void mlpsetcond(mlptrainer* s, + double wstep, + ae_int_t maxits, + ae_state *_state); +void mlpsetalgobatch(mlptrainer* s, ae_state *_state); +void mlptrainnetwork(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + mlpreport* rep, + ae_state *_state); +void _pexec_mlptrainnetwork(mlptrainer* s, + multilayerperceptron* network, + ae_int_t nrestarts, + mlpreport* rep, ae_state *_state); +void mlpstarttraining(mlptrainer* s, + multilayerperceptron* network, + ae_bool randomstart, + ae_state *_state); +ae_bool mlpcontinuetraining(mlptrainer* s, + multilayerperceptron* network, + ae_state *_state); +ae_bool _pexec_mlpcontinuetraining(mlptrainer* s, + multilayerperceptron* network, ae_state *_state); +void mlpebagginglm(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state); +void mlpebagginglbfgs(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + double wstep, + ae_int_t maxits, + ae_int_t* info, + mlpreport* rep, + mlpcvreport* ooberrors, + ae_state *_state); +void mlpetraines(mlpensemble* ensemble, + /* Real */ ae_matrix* xy, + ae_int_t npoints, + double decay, + ae_int_t restarts, + ae_int_t* info, + mlpreport* rep, + ae_state *_state); +void mlptrainensemblees(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t nrestarts, + mlpreport* rep, + ae_state *_state); +void _pexec_mlptrainensemblees(mlptrainer* s, + mlpensemble* ensemble, + ae_int_t nrestarts, + mlpreport* rep, ae_state *_state); +ae_bool _mlpreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpreport_clear(void* _p); +void _mlpreport_destroy(void* _p); +ae_bool _mlpcvreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpcvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpcvreport_clear(void* _p); +void _mlpcvreport_destroy(void* _p); +ae_bool _smlptrnsession_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _smlptrnsession_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _smlptrnsession_clear(void* _p); +void _smlptrnsession_destroy(void* _p); +ae_bool _mlpetrnsession_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpetrnsession_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpetrnsession_clear(void* _p); +void _mlpetrnsession_destroy(void* _p); +ae_bool _mlptrainer_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlptrainer_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlptrainer_clear(void* _p); +void _mlptrainer_destroy(void* _p); +ae_bool _mlpparallelizationcv_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mlpparallelizationcv_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mlpparallelizationcv_clear(void* _p); +void _mlpparallelizationcv_destroy(void* _p); +void pcabuildbasis(/* Real */ ae_matrix* x, + ae_int_t npoints, + ae_int_t nvars, + ae_int_t* info, + /* Real */ ae_vector* s2, + /* Real */ ae_matrix* v, + ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/diffequations.cpp b/src/inc/alglib/diffequations.cpp new file mode 100644 index 0000000..268ecd0 --- /dev/null +++ b/src/inc/alglib/diffequations.cpp @@ -0,0 +1,1187 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "diffequations.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* + +*************************************************************************/ +_odesolverstate_owner::_odesolverstate_owner() +{ + p_struct = (alglib_impl::odesolverstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::odesolverstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_odesolverstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_odesolverstate_owner::_odesolverstate_owner(const _odesolverstate_owner &rhs) +{ + p_struct = (alglib_impl::odesolverstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::odesolverstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_odesolverstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_odesolverstate_owner& _odesolverstate_owner::operator=(const _odesolverstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_odesolverstate_clear(p_struct); + if( !alglib_impl::_odesolverstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_odesolverstate_owner::~_odesolverstate_owner() +{ + alglib_impl::_odesolverstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::odesolverstate* _odesolverstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::odesolverstate* _odesolverstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +odesolverstate::odesolverstate() : _odesolverstate_owner() ,needdy(p_struct->needdy),y(&p_struct->y),dy(&p_struct->dy),x(p_struct->x) +{ +} + +odesolverstate::odesolverstate(const odesolverstate &rhs):_odesolverstate_owner(rhs) ,needdy(p_struct->needdy),y(&p_struct->y),dy(&p_struct->dy),x(p_struct->x) +{ +} + +odesolverstate& odesolverstate::operator=(const odesolverstate &rhs) +{ + if( this==&rhs ) + return *this; + _odesolverstate_owner::operator=(rhs); + return *this; +} + +odesolverstate::~odesolverstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_odesolverreport_owner::_odesolverreport_owner() +{ + p_struct = (alglib_impl::odesolverreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::odesolverreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_odesolverreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_odesolverreport_owner::_odesolverreport_owner(const _odesolverreport_owner &rhs) +{ + p_struct = (alglib_impl::odesolverreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::odesolverreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_odesolverreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_odesolverreport_owner& _odesolverreport_owner::operator=(const _odesolverreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_odesolverreport_clear(p_struct); + if( !alglib_impl::_odesolverreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_odesolverreport_owner::~_odesolverreport_owner() +{ + alglib_impl::_odesolverreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::odesolverreport* _odesolverreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::odesolverreport* _odesolverreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +odesolverreport::odesolverreport() : _odesolverreport_owner() ,nfev(p_struct->nfev),terminationtype(p_struct->terminationtype) +{ +} + +odesolverreport::odesolverreport(const odesolverreport &rhs):_odesolverreport_owner(rhs) ,nfev(p_struct->nfev),terminationtype(p_struct->terminationtype) +{ +} + +odesolverreport& odesolverreport::operator=(const odesolverreport &rhs) +{ + if( this==&rhs ) + return *this; + _odesolverreport_owner::operator=(rhs); + return *this; +} + +odesolverreport::~odesolverreport() +{ +} + +/************************************************************************* +Cash-Karp adaptive ODE solver. + +This subroutine solves ODE Y'=f(Y,x) with initial conditions Y(xs)=Ys +(here Y may be single variable or vector of N variables). + +INPUT PARAMETERS: + Y - initial conditions, array[0..N-1]. + contains values of Y[] at X[0] + N - system size + X - points at which Y should be tabulated, array[0..M-1] + integrations starts at X[0], ends at X[M-1], intermediate + values at X[i] are returned too. + SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!! + M - number of intermediate points + first point + last point: + * M>2 means that you need both Y(X[M-1]) and M-2 values at + intermediate points + * M=2 means that you want just to integrate from X[0] to + X[1] and don't interested in intermediate values. + * M=1 means that you don't want to integrate :) + it is degenerate case, but it will be handled correctly. + * M<1 means error + Eps - tolerance (absolute/relative error on each step will be + less than Eps). When passing: + * Eps>0, it means desired ABSOLUTE error + * Eps<0, it means desired RELATIVE error. Relative errors + are calculated with respect to maximum values of Y seen + so far. Be careful to use this criterion when starting + from Y[] that are close to zero. + H - initial step lenth, it will be adjusted automatically + after the first step. If H=0, step will be selected + automatically (usualy it will be equal to 0.001 of + min(x[i]-x[j])). + +OUTPUT PARAMETERS + State - structure which stores algorithm state between subsequent + calls of OdeSolverIteration. Used for reverse communication. + This structure should be passed to the OdeSolverIteration + subroutine. + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults. + + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverrkck(const real_1d_array &y, const ae_int_t n, const real_1d_array &x, const ae_int_t m, const double eps, const double h, odesolverstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::odesolverrkck(const_cast(y.c_ptr()), n, const_cast(x.c_ptr()), m, eps, h, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cash-Karp adaptive ODE solver. + +This subroutine solves ODE Y'=f(Y,x) with initial conditions Y(xs)=Ys +(here Y may be single variable or vector of N variables). + +INPUT PARAMETERS: + Y - initial conditions, array[0..N-1]. + contains values of Y[] at X[0] + N - system size + X - points at which Y should be tabulated, array[0..M-1] + integrations starts at X[0], ends at X[M-1], intermediate + values at X[i] are returned too. + SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!! + M - number of intermediate points + first point + last point: + * M>2 means that you need both Y(X[M-1]) and M-2 values at + intermediate points + * M=2 means that you want just to integrate from X[0] to + X[1] and don't interested in intermediate values. + * M=1 means that you don't want to integrate :) + it is degenerate case, but it will be handled correctly. + * M<1 means error + Eps - tolerance (absolute/relative error on each step will be + less than Eps). When passing: + * Eps>0, it means desired ABSOLUTE error + * Eps<0, it means desired RELATIVE error. Relative errors + are calculated with respect to maximum values of Y seen + so far. Be careful to use this criterion when starting + from Y[] that are close to zero. + H - initial step lenth, it will be adjusted automatically + after the first step. If H=0, step will be selected + automatically (usualy it will be equal to 0.001 of + min(x[i]-x[j])). + +OUTPUT PARAMETERS + State - structure which stores algorithm state between subsequent + calls of OdeSolverIteration. Used for reverse communication. + This structure should be passed to the OdeSolverIteration + subroutine. + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults. + + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverrkck(const real_1d_array &y, const real_1d_array &x, const double eps, const double h, odesolverstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = y.length(); + m = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::odesolverrkck(const_cast(y.c_ptr()), n, const_cast(x.c_ptr()), m, eps, h, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool odesolveriteration(const odesolverstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::odesolveriteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void odesolversolve(odesolverstate &state, + void (*diff)(const real_1d_array &y, double x, real_1d_array &dy, void *ptr), + void *ptr){ + alglib_impl::ae_state _alglib_env_state; + if( diff==NULL ) + throw ap_error("ALGLIB: error in 'odesolversolve()' (diff is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::odesolveriteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needdy ) + { + diff(state.y, state.x, state.dy, ptr); + continue; + } + throw ap_error("ALGLIB: unexpected error in 'odesolversolve'"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +ODE solver results + +Called after OdeSolverIteration returned False. + +INPUT PARAMETERS: + State - algorithm state (used by OdeSolverIteration). + +OUTPUT PARAMETERS: + M - number of tabulated values, M>=1 + XTbl - array[0..M-1], values of X + YTbl - array[0..M-1,0..N-1], values of Y in X[i] + Rep - solver report: + * Rep.TerminationType completetion code: + * -2 X is not ordered by ascending/descending or + there are non-distinct X[], i.e. X[i]=X[i+1] + * -1 incorrect parameters were specified + * 1 task has been solved + * Rep.NFEV contains number of function calculations + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverresults(const odesolverstate &state, ae_int_t &m, real_1d_array &xtbl, real_2d_array &ytbl, odesolverreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::odesolverresults(const_cast(state.c_ptr()), &m, const_cast(xtbl.c_ptr()), const_cast(ytbl.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static double odesolver_odesolvermaxgrow = 3.0; +static double odesolver_odesolvermaxshrink = 10.0; +static void odesolver_odesolverinit(ae_int_t solvertype, + /* Real */ ae_vector* y, + ae_int_t n, + /* Real */ ae_vector* x, + ae_int_t m, + double eps, + double h, + odesolverstate* state, + ae_state *_state); + + + + + +/************************************************************************* +Cash-Karp adaptive ODE solver. + +This subroutine solves ODE Y'=f(Y,x) with initial conditions Y(xs)=Ys +(here Y may be single variable or vector of N variables). + +INPUT PARAMETERS: + Y - initial conditions, array[0..N-1]. + contains values of Y[] at X[0] + N - system size + X - points at which Y should be tabulated, array[0..M-1] + integrations starts at X[0], ends at X[M-1], intermediate + values at X[i] are returned too. + SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!! + M - number of intermediate points + first point + last point: + * M>2 means that you need both Y(X[M-1]) and M-2 values at + intermediate points + * M=2 means that you want just to integrate from X[0] to + X[1] and don't interested in intermediate values. + * M=1 means that you don't want to integrate :) + it is degenerate case, but it will be handled correctly. + * M<1 means error + Eps - tolerance (absolute/relative error on each step will be + less than Eps). When passing: + * Eps>0, it means desired ABSOLUTE error + * Eps<0, it means desired RELATIVE error. Relative errors + are calculated with respect to maximum values of Y seen + so far. Be careful to use this criterion when starting + from Y[] that are close to zero. + H - initial step lenth, it will be adjusted automatically + after the first step. If H=0, step will be selected + automatically (usualy it will be equal to 0.001 of + min(x[i]-x[j])). + +OUTPUT PARAMETERS + State - structure which stores algorithm state between subsequent + calls of OdeSolverIteration. Used for reverse communication. + This structure should be passed to the OdeSolverIteration + subroutine. + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults. + + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverrkck(/* Real */ ae_vector* y, + ae_int_t n, + /* Real */ ae_vector* x, + ae_int_t m, + double eps, + double h, + odesolverstate* state, + ae_state *_state) +{ + + _odesolverstate_clear(state); + + ae_assert(n>=1, "ODESolverRKCK: N<1!", _state); + ae_assert(m>=1, "ODESolverRKCK: M<1!", _state); + ae_assert(y->cnt>=n, "ODESolverRKCK: Length(Y)cnt>=m, "ODESolverRKCK: Length(X)rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + i = state->rstate.ia.ptr.p_int[2]; + j = state->rstate.ia.ptr.p_int[3]; + k = state->rstate.ia.ptr.p_int[4]; + klimit = state->rstate.ia.ptr.p_int[5]; + gridpoint = state->rstate.ba.ptr.p_bool[0]; + xc = state->rstate.ra.ptr.p_double[0]; + v = state->rstate.ra.ptr.p_double[1]; + h = state->rstate.ra.ptr.p_double[2]; + h2 = state->rstate.ra.ptr.p_double[3]; + err = state->rstate.ra.ptr.p_double[4]; + maxgrowpow = state->rstate.ra.ptr.p_double[5]; + } + else + { + n = -983; + m = -989; + i = -834; + j = 900; + k = -287; + klimit = 364; + gridpoint = ae_false; + xc = -338; + v = -686; + h = 912; + h2 = 585; + err = 497; + maxgrowpow = -271; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + + /* + * Routine body + */ + + /* + * prepare + */ + if( state->repterminationtype!=0 ) + { + result = ae_false; + return result; + } + n = state->n; + m = state->m; + h = state->h; + maxgrowpow = ae_pow(odesolver_odesolvermaxgrow, 5, _state); + state->repnfev = 0; + + /* + * some preliminary checks for internal errors + * after this we assume that H>0 and M>1 + */ + ae_assert(ae_fp_greater(state->h,0), "ODESolver: internal error", _state); + ae_assert(m>1, "ODESolverIteration: internal error", _state); + + /* + * choose solver + */ + if( state->solvertype!=0 ) + { + goto lbl_1; + } + + /* + * Cask-Karp solver + * Prepare coefficients table. + * Check it for errors + */ + ae_vector_set_length(&state->rka, 6, _state); + state->rka.ptr.p_double[0] = 0; + state->rka.ptr.p_double[1] = (double)1/(double)5; + state->rka.ptr.p_double[2] = (double)3/(double)10; + state->rka.ptr.p_double[3] = (double)3/(double)5; + state->rka.ptr.p_double[4] = 1; + state->rka.ptr.p_double[5] = (double)7/(double)8; + ae_matrix_set_length(&state->rkb, 6, 5, _state); + state->rkb.ptr.pp_double[1][0] = (double)1/(double)5; + state->rkb.ptr.pp_double[2][0] = (double)3/(double)40; + state->rkb.ptr.pp_double[2][1] = (double)9/(double)40; + state->rkb.ptr.pp_double[3][0] = (double)3/(double)10; + state->rkb.ptr.pp_double[3][1] = -(double)9/(double)10; + state->rkb.ptr.pp_double[3][2] = (double)6/(double)5; + state->rkb.ptr.pp_double[4][0] = -(double)11/(double)54; + state->rkb.ptr.pp_double[4][1] = (double)5/(double)2; + state->rkb.ptr.pp_double[4][2] = -(double)70/(double)27; + state->rkb.ptr.pp_double[4][3] = (double)35/(double)27; + state->rkb.ptr.pp_double[5][0] = (double)1631/(double)55296; + state->rkb.ptr.pp_double[5][1] = (double)175/(double)512; + state->rkb.ptr.pp_double[5][2] = (double)575/(double)13824; + state->rkb.ptr.pp_double[5][3] = (double)44275/(double)110592; + state->rkb.ptr.pp_double[5][4] = (double)253/(double)4096; + ae_vector_set_length(&state->rkc, 6, _state); + state->rkc.ptr.p_double[0] = (double)37/(double)378; + state->rkc.ptr.p_double[1] = 0; + state->rkc.ptr.p_double[2] = (double)250/(double)621; + state->rkc.ptr.p_double[3] = (double)125/(double)594; + state->rkc.ptr.p_double[4] = 0; + state->rkc.ptr.p_double[5] = (double)512/(double)1771; + ae_vector_set_length(&state->rkcs, 6, _state); + state->rkcs.ptr.p_double[0] = (double)2825/(double)27648; + state->rkcs.ptr.p_double[1] = 0; + state->rkcs.ptr.p_double[2] = (double)18575/(double)48384; + state->rkcs.ptr.p_double[3] = (double)13525/(double)55296; + state->rkcs.ptr.p_double[4] = (double)277/(double)14336; + state->rkcs.ptr.p_double[5] = (double)1/(double)4; + ae_matrix_set_length(&state->rkk, 6, n, _state); + + /* + * Main cycle consists of two iterations: + * * outer where we travel from X[i-1] to X[i] + * * inner where we travel inside [X[i-1],X[i]] + */ + ae_matrix_set_length(&state->ytbl, m, n, _state); + ae_vector_set_length(&state->escale, n, _state); + ae_vector_set_length(&state->yn, n, _state); + ae_vector_set_length(&state->yns, n, _state); + xc = state->xg.ptr.p_double[0]; + ae_v_move(&state->ytbl.ptr.pp_double[0][0], 1, &state->yc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(j=0; j<=n-1; j++) + { + state->escale.ptr.p_double[j] = 0; + } + i = 1; +lbl_3: + if( i>m-1 ) + { + goto lbl_5; + } + + /* + * begin inner iteration + */ +lbl_6: + if( ae_false ) + { + goto lbl_7; + } + + /* + * truncate step if needed (beyond right boundary). + * determine should we store X or not + */ + if( ae_fp_greater_eq(xc+h,state->xg.ptr.p_double[i]) ) + { + h = state->xg.ptr.p_double[i]-xc; + gridpoint = ae_true; + } + else + { + gridpoint = ae_false; + } + + /* + * Update error scale maximums + * + * These maximums are initialized by zeros, + * then updated every iterations. + */ + for(j=0; j<=n-1; j++) + { + state->escale.ptr.p_double[j] = ae_maxreal(state->escale.ptr.p_double[j], ae_fabs(state->yc.ptr.p_double[j], _state), _state); + } + + /* + * make one step: + * 1. calculate all info needed to do step + * 2. update errors scale maximums using values/derivatives + * obtained during (1) + * + * Take into account that we use scaling of X to reduce task + * to the form where x[0] < x[1] < ... < x[n-1]. So X is + * replaced by x=xscale*t, and dy/dx=f(y,x) is replaced + * by dy/dt=xscale*f(y,xscale*t). + */ + ae_v_move(&state->yn.ptr.p_double[0], 1, &state->yc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->yns.ptr.p_double[0], 1, &state->yc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + k = 0; +lbl_8: + if( k>5 ) + { + goto lbl_10; + } + + /* + * prepare data for the next update of YN/YNS + */ + state->x = state->xscale*(xc+state->rka.ptr.p_double[k]*h); + ae_v_move(&state->y.ptr.p_double[0], 1, &state->yc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(j=0; j<=k-1; j++) + { + v = state->rkb.ptr.pp_double[k][j]; + ae_v_addd(&state->y.ptr.p_double[0], 1, &state->rkk.ptr.pp_double[j][0], 1, ae_v_len(0,n-1), v); + } + state->needdy = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needdy = ae_false; + state->repnfev = state->repnfev+1; + v = h*state->xscale; + ae_v_moved(&state->rkk.ptr.pp_double[k][0], 1, &state->dy.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + + /* + * update YN/YNS + */ + v = state->rkc.ptr.p_double[k]; + ae_v_addd(&state->yn.ptr.p_double[0], 1, &state->rkk.ptr.pp_double[k][0], 1, ae_v_len(0,n-1), v); + v = state->rkcs.ptr.p_double[k]; + ae_v_addd(&state->yns.ptr.p_double[0], 1, &state->rkk.ptr.pp_double[k][0], 1, ae_v_len(0,n-1), v); + k = k+1; + goto lbl_8; +lbl_10: + + /* + * estimate error + */ + err = 0; + for(j=0; j<=n-1; j++) + { + if( !state->fraceps ) + { + + /* + * absolute error is estimated + */ + err = ae_maxreal(err, ae_fabs(state->yn.ptr.p_double[j]-state->yns.ptr.p_double[j], _state), _state); + } + else + { + + /* + * Relative error is estimated + */ + v = state->escale.ptr.p_double[j]; + if( ae_fp_eq(v,0) ) + { + v = 1; + } + err = ae_maxreal(err, ae_fabs(state->yn.ptr.p_double[j]-state->yns.ptr.p_double[j], _state)/v, _state); + } + } + + /* + * calculate new step, restart if necessary + */ + if( ae_fp_less_eq(maxgrowpow*err,state->eps) ) + { + h2 = odesolver_odesolvermaxgrow*h; + } + else + { + h2 = h*ae_pow(state->eps/err, 0.2, _state); + } + if( ae_fp_less(h2,h/odesolver_odesolvermaxshrink) ) + { + h2 = h/odesolver_odesolvermaxshrink; + } + if( ae_fp_greater(err,state->eps) ) + { + h = h2; + goto lbl_6; + } + + /* + * advance position + */ + xc = xc+h; + ae_v_move(&state->yc.ptr.p_double[0], 1, &state->yn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * update H + */ + h = h2; + + /* + * break on grid point + */ + if( gridpoint ) + { + goto lbl_7; + } + goto lbl_6; +lbl_7: + + /* + * save result + */ + ae_v_move(&state->ytbl.ptr.pp_double[i][0], 1, &state->yc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + i = i+1; + goto lbl_3; +lbl_5: + state->repterminationtype = 1; + result = ae_false; + return result; +lbl_1: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = i; + state->rstate.ia.ptr.p_int[3] = j; + state->rstate.ia.ptr.p_int[4] = k; + state->rstate.ia.ptr.p_int[5] = klimit; + state->rstate.ba.ptr.p_bool[0] = gridpoint; + state->rstate.ra.ptr.p_double[0] = xc; + state->rstate.ra.ptr.p_double[1] = v; + state->rstate.ra.ptr.p_double[2] = h; + state->rstate.ra.ptr.p_double[3] = h2; + state->rstate.ra.ptr.p_double[4] = err; + state->rstate.ra.ptr.p_double[5] = maxgrowpow; + return result; +} + + +/************************************************************************* +ODE solver results + +Called after OdeSolverIteration returned False. + +INPUT PARAMETERS: + State - algorithm state (used by OdeSolverIteration). + +OUTPUT PARAMETERS: + M - number of tabulated values, M>=1 + XTbl - array[0..M-1], values of X + YTbl - array[0..M-1,0..N-1], values of Y in X[i] + Rep - solver report: + * Rep.TerminationType completetion code: + * -2 X is not ordered by ascending/descending or + there are non-distinct X[], i.e. X[i]=X[i+1] + * -1 incorrect parameters were specified + * 1 task has been solved + * Rep.NFEV contains number of function calculations + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverresults(odesolverstate* state, + ae_int_t* m, + /* Real */ ae_vector* xtbl, + /* Real */ ae_matrix* ytbl, + odesolverreport* rep, + ae_state *_state) +{ + double v; + ae_int_t i; + + *m = 0; + ae_vector_clear(xtbl); + ae_matrix_clear(ytbl); + _odesolverreport_clear(rep); + + rep->terminationtype = state->repterminationtype; + if( rep->terminationtype>0 ) + { + *m = state->m; + rep->nfev = state->repnfev; + ae_vector_set_length(xtbl, state->m, _state); + v = state->xscale; + ae_v_moved(&xtbl->ptr.p_double[0], 1, &state->xg.ptr.p_double[0], 1, ae_v_len(0,state->m-1), v); + ae_matrix_set_length(ytbl, state->m, state->n, _state); + for(i=0; i<=state->m-1; i++) + { + ae_v_move(&ytbl->ptr.pp_double[i][0], 1, &state->ytbl.ptr.pp_double[i][0], 1, ae_v_len(0,state->n-1)); + } + } + else + { + rep->nfev = 0; + } +} + + +/************************************************************************* +Internal initialization subroutine +*************************************************************************/ +static void odesolver_odesolverinit(ae_int_t solvertype, + /* Real */ ae_vector* y, + ae_int_t n, + /* Real */ ae_vector* x, + ae_int_t m, + double eps, + double h, + odesolverstate* state, + ae_state *_state) +{ + ae_int_t i; + double v; + + _odesolverstate_clear(state); + + + /* + * Prepare RComm + */ + ae_vector_set_length(&state->rstate.ia, 5+1, _state); + ae_vector_set_length(&state->rstate.ba, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 5+1, _state); + state->rstate.stage = -1; + state->needdy = ae_false; + + /* + * check parameters. + */ + if( (n<=0||m<1)||ae_fp_eq(eps,0) ) + { + state->repterminationtype = -1; + return; + } + if( ae_fp_less(h,0) ) + { + h = -h; + } + + /* + * quick exit if necessary. + * after this block we assume that M>1 + */ + if( m==1 ) + { + state->repnfev = 0; + state->repterminationtype = 1; + ae_matrix_set_length(&state->ytbl, 1, n, _state); + ae_v_move(&state->ytbl.ptr.pp_double[0][0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_vector_set_length(&state->xg, m, _state); + ae_v_move(&state->xg.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,m-1)); + return; + } + + /* + * check again: correct order of X[] + */ + if( ae_fp_eq(x->ptr.p_double[1],x->ptr.p_double[0]) ) + { + state->repterminationtype = -2; + return; + } + for(i=1; i<=m-1; i++) + { + if( (ae_fp_greater(x->ptr.p_double[1],x->ptr.p_double[0])&&ae_fp_less_eq(x->ptr.p_double[i],x->ptr.p_double[i-1]))||(ae_fp_less(x->ptr.p_double[1],x->ptr.p_double[0])&&ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i-1])) ) + { + state->repterminationtype = -2; + return; + } + } + + /* + * auto-select H if necessary + */ + if( ae_fp_eq(h,0) ) + { + v = ae_fabs(x->ptr.p_double[1]-x->ptr.p_double[0], _state); + for(i=2; i<=m-1; i++) + { + v = ae_minreal(v, ae_fabs(x->ptr.p_double[i]-x->ptr.p_double[i-1], _state), _state); + } + h = 0.001*v; + } + + /* + * store parameters + */ + state->n = n; + state->m = m; + state->h = h; + state->eps = ae_fabs(eps, _state); + state->fraceps = ae_fp_less(eps,0); + ae_vector_set_length(&state->xg, m, _state); + ae_v_move(&state->xg.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,m-1)); + if( ae_fp_greater(x->ptr.p_double[1],x->ptr.p_double[0]) ) + { + state->xscale = 1; + } + else + { + state->xscale = -1; + ae_v_muld(&state->xg.ptr.p_double[0], 1, ae_v_len(0,m-1), -1); + } + ae_vector_set_length(&state->yc, n, _state); + ae_v_move(&state->yc.ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->solvertype = solvertype; + state->repterminationtype = 0; + + /* + * Allocate arrays + */ + ae_vector_set_length(&state->y, n, _state); + ae_vector_set_length(&state->dy, n, _state); +} + + +ae_bool _odesolverstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + odesolverstate *p = (odesolverstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->yc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->escale, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dy, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->ytbl, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->yn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->yns, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rka, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rkc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rkcs, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->rkb, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->rkk, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _odesolverstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + odesolverstate *dst = (odesolverstate*)_dst; + odesolverstate *src = (odesolverstate*)_src; + dst->n = src->n; + dst->m = src->m; + dst->xscale = src->xscale; + dst->h = src->h; + dst->eps = src->eps; + dst->fraceps = src->fraceps; + if( !ae_vector_init_copy(&dst->yc, &src->yc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->escale, &src->escale, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xg, &src->xg, _state, make_automatic) ) + return ae_false; + dst->solvertype = src->solvertype; + dst->needdy = src->needdy; + dst->x = src->x; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dy, &src->dy, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->ytbl, &src->ytbl, _state, make_automatic) ) + return ae_false; + dst->repterminationtype = src->repterminationtype; + dst->repnfev = src->repnfev; + if( !ae_vector_init_copy(&dst->yn, &src->yn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->yns, &src->yns, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rka, &src->rka, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rkc, &src->rkc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rkcs, &src->rkcs, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->rkb, &src->rkb, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->rkk, &src->rkk, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _odesolverstate_clear(void* _p) +{ + odesolverstate *p = (odesolverstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->yc); + ae_vector_clear(&p->escale); + ae_vector_clear(&p->xg); + ae_vector_clear(&p->y); + ae_vector_clear(&p->dy); + ae_matrix_clear(&p->ytbl); + ae_vector_clear(&p->yn); + ae_vector_clear(&p->yns); + ae_vector_clear(&p->rka); + ae_vector_clear(&p->rkc); + ae_vector_clear(&p->rkcs); + ae_matrix_clear(&p->rkb); + ae_matrix_clear(&p->rkk); + _rcommstate_clear(&p->rstate); +} + + +void _odesolverstate_destroy(void* _p) +{ + odesolverstate *p = (odesolverstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->yc); + ae_vector_destroy(&p->escale); + ae_vector_destroy(&p->xg); + ae_vector_destroy(&p->y); + ae_vector_destroy(&p->dy); + ae_matrix_destroy(&p->ytbl); + ae_vector_destroy(&p->yn); + ae_vector_destroy(&p->yns); + ae_vector_destroy(&p->rka); + ae_vector_destroy(&p->rkc); + ae_vector_destroy(&p->rkcs); + ae_matrix_destroy(&p->rkb); + ae_matrix_destroy(&p->rkk); + _rcommstate_destroy(&p->rstate); +} + + +ae_bool _odesolverreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + odesolverreport *p = (odesolverreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _odesolverreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + odesolverreport *dst = (odesolverreport*)_dst; + odesolverreport *src = (odesolverreport*)_src; + dst->nfev = src->nfev; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _odesolverreport_clear(void* _p) +{ + odesolverreport *p = (odesolverreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _odesolverreport_destroy(void* _p) +{ + odesolverreport *p = (odesolverreport*)_p; + ae_touch_ptr((void*)p); +} + + + +} + diff --git a/src/inc/alglib/diffequations.h b/src/inc/alglib/diffequations.h new file mode 100644 index 0000000..f288f9b --- /dev/null +++ b/src/inc/alglib/diffequations.h @@ -0,0 +1,267 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _diffequations_pkg_h +#define _diffequations_pkg_h +#include "ap.h" +#include "alglibinternal.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_int_t n; + ae_int_t m; + double xscale; + double h; + double eps; + ae_bool fraceps; + ae_vector yc; + ae_vector escale; + ae_vector xg; + ae_int_t solvertype; + ae_bool needdy; + double x; + ae_vector y; + ae_vector dy; + ae_matrix ytbl; + ae_int_t repterminationtype; + ae_int_t repnfev; + ae_vector yn; + ae_vector yns; + ae_vector rka; + ae_vector rkc; + ae_vector rkcs; + ae_matrix rkb; + ae_matrix rkk; + rcommstate rstate; +} odesolverstate; +typedef struct +{ + ae_int_t nfev; + ae_int_t terminationtype; +} odesolverreport; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + +/************************************************************************* + +*************************************************************************/ +class _odesolverstate_owner +{ +public: + _odesolverstate_owner(); + _odesolverstate_owner(const _odesolverstate_owner &rhs); + _odesolverstate_owner& operator=(const _odesolverstate_owner &rhs); + virtual ~_odesolverstate_owner(); + alglib_impl::odesolverstate* c_ptr(); + alglib_impl::odesolverstate* c_ptr() const; +protected: + alglib_impl::odesolverstate *p_struct; +}; +class odesolverstate : public _odesolverstate_owner +{ +public: + odesolverstate(); + odesolverstate(const odesolverstate &rhs); + odesolverstate& operator=(const odesolverstate &rhs); + virtual ~odesolverstate(); + ae_bool &needdy; + real_1d_array y; + real_1d_array dy; + double &x; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _odesolverreport_owner +{ +public: + _odesolverreport_owner(); + _odesolverreport_owner(const _odesolverreport_owner &rhs); + _odesolverreport_owner& operator=(const _odesolverreport_owner &rhs); + virtual ~_odesolverreport_owner(); + alglib_impl::odesolverreport* c_ptr(); + alglib_impl::odesolverreport* c_ptr() const; +protected: + alglib_impl::odesolverreport *p_struct; +}; +class odesolverreport : public _odesolverreport_owner +{ +public: + odesolverreport(); + odesolverreport(const odesolverreport &rhs); + odesolverreport& operator=(const odesolverreport &rhs); + virtual ~odesolverreport(); + ae_int_t &nfev; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +Cash-Karp adaptive ODE solver. + +This subroutine solves ODE Y'=f(Y,x) with initial conditions Y(xs)=Ys +(here Y may be single variable or vector of N variables). + +INPUT PARAMETERS: + Y - initial conditions, array[0..N-1]. + contains values of Y[] at X[0] + N - system size + X - points at which Y should be tabulated, array[0..M-1] + integrations starts at X[0], ends at X[M-1], intermediate + values at X[i] are returned too. + SHOULD BE ORDERED BY ASCENDING OR BY DESCENDING!!!! + M - number of intermediate points + first point + last point: + * M>2 means that you need both Y(X[M-1]) and M-2 values at + intermediate points + * M=2 means that you want just to integrate from X[0] to + X[1] and don't interested in intermediate values. + * M=1 means that you don't want to integrate :) + it is degenerate case, but it will be handled correctly. + * M<1 means error + Eps - tolerance (absolute/relative error on each step will be + less than Eps). When passing: + * Eps>0, it means desired ABSOLUTE error + * Eps<0, it means desired RELATIVE error. Relative errors + are calculated with respect to maximum values of Y seen + so far. Be careful to use this criterion when starting + from Y[] that are close to zero. + H - initial step lenth, it will be adjusted automatically + after the first step. If H=0, step will be selected + automatically (usualy it will be equal to 0.001 of + min(x[i]-x[j])). + +OUTPUT PARAMETERS + State - structure which stores algorithm state between subsequent + calls of OdeSolverIteration. Used for reverse communication. + This structure should be passed to the OdeSolverIteration + subroutine. + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKIteration, AutoGKResults. + + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverrkck(const real_1d_array &y, const ae_int_t n, const real_1d_array &x, const ae_int_t m, const double eps, const double h, odesolverstate &state); +void odesolverrkck(const real_1d_array &y, const real_1d_array &x, const double eps, const double h, odesolverstate &state); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool odesolveriteration(const odesolverstate &state); + + +/************************************************************************* +This function is used to launcn iterations of ODE solver + +It accepts following parameters: + diff - callback which calculates dy/dx for given y and x + ptr - optional pointer which is passed to diff; can be NULL + + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey + +*************************************************************************/ +void odesolversolve(odesolverstate &state, + void (*diff)(const real_1d_array &y, double x, real_1d_array &dy, void *ptr), + void *ptr = NULL); + + +/************************************************************************* +ODE solver results + +Called after OdeSolverIteration returned False. + +INPUT PARAMETERS: + State - algorithm state (used by OdeSolverIteration). + +OUTPUT PARAMETERS: + M - number of tabulated values, M>=1 + XTbl - array[0..M-1], values of X + YTbl - array[0..M-1,0..N-1], values of Y in X[i] + Rep - solver report: + * Rep.TerminationType completetion code: + * -2 X is not ordered by ascending/descending or + there are non-distinct X[], i.e. X[i]=X[i+1] + * -1 incorrect parameters were specified + * 1 task has been solved + * Rep.NFEV contains number of function calculations + + -- ALGLIB -- + Copyright 01.09.2009 by Bochkanov Sergey +*************************************************************************/ +void odesolverresults(const odesolverstate &state, ae_int_t &m, real_1d_array &xtbl, real_2d_array &ytbl, odesolverreport &rep); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void odesolverrkck(/* Real */ ae_vector* y, + ae_int_t n, + /* Real */ ae_vector* x, + ae_int_t m, + double eps, + double h, + odesolverstate* state, + ae_state *_state); +ae_bool odesolveriteration(odesolverstate* state, ae_state *_state); +void odesolverresults(odesolverstate* state, + ae_int_t* m, + /* Real */ ae_vector* xtbl, + /* Real */ ae_matrix* ytbl, + odesolverreport* rep, + ae_state *_state); +ae_bool _odesolverstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _odesolverstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _odesolverstate_clear(void* _p); +void _odesolverstate_destroy(void* _p); +ae_bool _odesolverreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _odesolverreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _odesolverreport_clear(void* _p); +void _odesolverreport_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/fasttransforms.cpp b/src/inc/alglib/fasttransforms.cpp new file mode 100644 index 0000000..9b7864f --- /dev/null +++ b/src/inc/alglib/fasttransforms.cpp @@ -0,0 +1,3554 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "fasttransforms.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +1-dimensional complex FFT. + +Array size N may be arbitrary number (composite or prime). Composite N's +are handled with cache-oblivious variation of a Cooley-Tukey algorithm. +Small prime-factors are transformed using hard coded codelets (similar to +FFTW codelets, but without low-level optimization), large prime-factors +are handled with Bluestein's algorithm. + +Fastests transforms are for smooth N's (prime factors are 2, 3, 5 only), +most fast for powers of 2. When N have prime factors larger than these, +but orders of magnitude smaller than N, computations will be about 4 times +slower than for nearby highly composite N's. When N itself is prime, speed +will be 6 times lower. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1d(complex_1d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftc1d(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex FFT. + +Array size N may be arbitrary number (composite or prime). Composite N's +are handled with cache-oblivious variation of a Cooley-Tukey algorithm. +Small prime-factors are transformed using hard coded codelets (similar to +FFTW codelets, but without low-level optimization), large prime-factors +are handled with Bluestein's algorithm. + +Fastests transforms are for smooth N's (prime factors are 2, 3, 5 only), +most fast for powers of 2. When N have prime factors larger than these, +but orders of magnitude smaller than N, computations will be about 4 times +slower than for nearby highly composite N's. When N itself is prime, speed +will be 6 times lower. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1d(complex_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = a.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftc1d(const_cast(a.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex inverse FFT. + +Array size N may be arbitrary number (composite or prime). Algorithm has +O(N*logN) complexity for any N (composite or prime). + +See FFTC1D() description for more information about algorithm performance. + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1dinv(complex_1d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftc1dinv(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex inverse FFT. + +Array size N may be arbitrary number (composite or prime). Algorithm has +O(N*logN) complexity for any N (composite or prime). + +See FFTC1D() description for more information about algorithm performance. + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1dinv(complex_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = a.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftc1dinv(const_cast(a.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + F - DFT of a input array, array[0..N-1] + F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + +NOTE: + F[] satisfies symmetry property F[k] = conj(F[N-k]), so just one half +of array is usually needed. But for convinience subroutine returns full +complex array (with frequencies above N/2), so its result may be used by +other FFT-related subroutines. + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1d(const real_1d_array &a, const ae_int_t n, complex_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftr1d(const_cast(a.c_ptr()), n, const_cast(f.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + F - DFT of a input array, array[0..N-1] + F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + +NOTE: + F[] satisfies symmetry property F[k] = conj(F[N-k]), so just one half +of array is usually needed. But for convinience subroutine returns full +complex array (with frequencies above N/2), so its result may be used by +other FFT-related subroutines. + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1d(const real_1d_array &a, complex_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = a.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftr1d(const_cast(a.c_ptr()), n, const_cast(f.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real inverse FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + F - array[0..floor(N/2)] - frequencies from forward real FFT + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + +NOTE: + F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just one +half of frequencies array is needed - elements from 0 to floor(N/2). F[0] +is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd, then +F[floor(N/2)] has no special properties. + +Relying on properties noted above, FFTR1DInv subroutine uses only elements +from 0th to floor(N/2)-th. It ignores imaginary part of F[0], and in case +N is even it ignores imaginary part of F[floor(N/2)] too. + +When you call this function using full arguments list - "FFTR1DInv(F,N,A)" +- you can pass either either frequencies array with N elements or reduced +array with roughly N/2 elements - subroutine will successfully transform +both. + +If you call this function using reduced arguments list - "FFTR1DInv(F,A)" +- you must pass FULL array with N elements (although higher N/2 are still +not used) because array size is used to automatically determine FFT length + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinv(const complex_1d_array &f, const ae_int_t n, real_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftr1dinv(const_cast(f.c_ptr()), n, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real inverse FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + F - array[0..floor(N/2)] - frequencies from forward real FFT + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + +NOTE: + F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just one +half of frequencies array is needed - elements from 0 to floor(N/2). F[0] +is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd, then +F[floor(N/2)] has no special properties. + +Relying on properties noted above, FFTR1DInv subroutine uses only elements +from 0th to floor(N/2)-th. It ignores imaginary part of F[0], and in case +N is even it ignores imaginary part of F[floor(N/2)] too. + +When you call this function using full arguments list - "FFTR1DInv(F,N,A)" +- you can pass either either frequencies array with N elements or reduced +array with roughly N/2 elements - subroutine will successfully transform +both. + +If you call this function using reduced arguments list - "FFTR1DInv(F,A)" +- you must pass FULL array with N elements (although higher N/2 are still +not used) because array size is used to automatically determine FFT length + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinv(const complex_1d_array &f, real_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = f.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fftr1dinv(const_cast(f.c_ptr()), n, const_cast(a.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex convolution. + +For given A/B returns conv(A,B) (non-circular). Subroutine can automatically +choose between three implementations: straightforward O(M*N) formula for +very small N (or M), overlap-add algorithm for cases where max(M,N) is +significantly larger than min(M,N), but O(M*N) algorithm is too slow, and +general FFT-based formula for cases where two previois algorithms are too +slow. + +Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N. + +INPUT PARAMETERS + A - array[0..M-1] - complex function to be transformed + M - problem size + B - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1d(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convc1d(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex non-circular deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length, N<=M + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dinv(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convc1dinv(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional circular complex convolution. + +For given S/R returns conv(S,R) (circular). Algorithm has linearithmic +complexity for any M/N. + +IMPORTANT: normal convolution is commutative, i.e. it is symmetric - +conv(A,B)=conv(B,A). Cyclic convolution IS NOT. One function - S - is a +signal, periodic function, and another - R - is a response, non-periodic +function with limited length. + +INPUT PARAMETERS + S - array[0..M-1] - complex periodic signal + M - problem size + B - array[0..N-1] - complex non-periodic response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircular(const complex_1d_array &s, const ae_int_t m, const complex_1d_array &r, const ae_int_t n, complex_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convc1dcircular(const_cast(s.c_ptr()), m, const_cast(r.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved periodic signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - non-periodic response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-1]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircularinv(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convc1dcircularinv(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real convolution. + +Analogous to ConvC1D(), see ConvC1D() comments for more details. + +INPUT PARAMETERS + A - array[0..M-1] - real function to be transformed + M - problem size + B - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1d(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convr1d(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length, N<=M + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dinv(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convr1dinv(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional circular real convolution. + +Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details. + +INPUT PARAMETERS + S - array[0..M-1] - real signal + M - problem size + B - array[0..N-1] - real response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircular(const real_1d_array &s, const ae_int_t m, const real_1d_array &r, const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convr1dcircular(const_cast(s.c_ptr()), m, const_cast(r.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircularinv(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::convr1dcircularinv(const_cast(a.c_ptr()), m, const_cast(b.c_ptr()), n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(conj(pattern[j])*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(conj(pattern[j])*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1d(const complex_1d_array &signal, const ae_int_t n, const complex_1d_array &pattern, const ae_int_t m, complex_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::corrc1d(const_cast(signal.c_ptr()), n, const_cast(pattern.c_ptr()), m, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional circular complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1dcircular(const complex_1d_array &signal, const ae_int_t m, const complex_1d_array &pattern, const ae_int_t n, complex_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::corrc1dcircular(const_cast(signal.c_ptr()), m, const_cast(pattern.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(pattern[j]*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(pattern[j]*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1d(const real_1d_array &signal, const ae_int_t n, const real_1d_array &pattern, const ae_int_t m, real_1d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::corrr1d(const_cast(signal.c_ptr()), n, const_cast(pattern.c_ptr()), m, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional circular real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1dcircular(const real_1d_array &signal, const ae_int_t m, const real_1d_array &pattern, const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::corrr1dcircular(const_cast(signal.c_ptr()), m, const_cast(pattern.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional Fast Hartley Transform. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - FHT of a input array, array[0..N-1], + A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1) + + + -- ALGLIB -- + Copyright 04.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1d(real_1d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fhtr1d(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional inverse FHT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse FHT of a input array, array[0..N-1] + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1dinv(real_1d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fhtr1dinv(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + + + + + + + + + + + +/************************************************************************* +1-dimensional complex FFT. + +Array size N may be arbitrary number (composite or prime). Composite N's +are handled with cache-oblivious variation of a Cooley-Tukey algorithm. +Small prime-factors are transformed using hard coded codelets (similar to +FFTW codelets, but without low-level optimization), large prime-factors +are handled with Bluestein's algorithm. + +Fastests transforms are for smooth N's (prime factors are 2, 3, 5 only), +most fast for powers of 2. When N have prime factors larger than these, +but orders of magnitude smaller than N, computations will be about 4 times +slower than for nearby highly composite N's. When N itself is prime, speed +will be 6 times lower. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1d(/* Complex */ ae_vector* a, ae_int_t n, ae_state *_state) +{ + ae_frame _frame_block; + fasttransformplan plan; + ae_int_t i; + ae_vector buf; + + ae_frame_make(_state, &_frame_block); + _fasttransformplan_init(&plan, _state, ae_true); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "FFTC1D: incorrect N!", _state); + ae_assert(a->cnt>=n, "FFTC1D: Length(A)ptr.p_complex[i].x; + buf.ptr.p_double[2*i+1] = a->ptr.p_complex[i].y; + } + + /* + * Generate plan and execute it. + * + * Plan is a combination of a successive factorizations of N and + * precomputed data. It is much like a FFTW plan, but is not stored + * between subroutine calls and is much simpler. + */ + ftcomplexfftplan(n, 1, &plan, _state); + ftapplyplan(&plan, &buf, 0, 1, _state); + + /* + * result + */ + for(i=0; i<=n-1; i++) + { + a->ptr.p_complex[i].x = buf.ptr.p_double[2*i+0]; + a->ptr.p_complex[i].y = buf.ptr.p_double[2*i+1]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional complex inverse FFT. + +Array size N may be arbitrary number (composite or prime). Algorithm has +O(N*logN) complexity for any N (composite or prime). + +See FFTC1D() description for more information about algorithm performance. + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1dinv(/* Complex */ ae_vector* a, ae_int_t n, ae_state *_state) +{ + ae_int_t i; + + + ae_assert(n>0, "FFTC1DInv: incorrect N!", _state); + ae_assert(a->cnt>=n, "FFTC1DInv: Length(A)ptr.p_complex[i].y = -a->ptr.p_complex[i].y; + } + fftc1d(a, n, _state); + for(i=0; i<=n-1; i++) + { + a->ptr.p_complex[i].x = a->ptr.p_complex[i].x/n; + a->ptr.p_complex[i].y = -a->ptr.p_complex[i].y/n; + } +} + + +/************************************************************************* +1-dimensional real FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + F - DFT of a input array, array[0..N-1] + F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + +NOTE: + F[] satisfies symmetry property F[k] = conj(F[N-k]), so just one half +of array is usually needed. But for convinience subroutine returns full +complex array (with frequencies above N/2), so its result may be used by +other FFT-related subroutines. + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1d(/* Real */ ae_vector* a, + ae_int_t n, + /* Complex */ ae_vector* f, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t n2; + ae_int_t idx; + ae_complex hn; + ae_complex hmnc; + ae_complex v; + ae_vector buf; + fasttransformplan plan; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(f); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + _fasttransformplan_init(&plan, _state, ae_true); + + ae_assert(n>0, "FFTR1D: incorrect N!", _state); + ae_assert(a->cnt>=n, "FFTR1D: Length(A)ptr.p_complex[0] = ae_complex_from_d(a->ptr.p_double[0]); + ae_frame_leave(_state); + return; + } + if( n==2 ) + { + ae_vector_set_length(f, 2, _state); + f->ptr.p_complex[0].x = a->ptr.p_double[0]+a->ptr.p_double[1]; + f->ptr.p_complex[0].y = 0; + f->ptr.p_complex[1].x = a->ptr.p_double[0]-a->ptr.p_double[1]; + f->ptr.p_complex[1].y = 0; + ae_frame_leave(_state); + return; + } + + /* + * Choose between odd-size and even-size FFTs + */ + if( n%2==0 ) + { + + /* + * even-size real FFT, use reduction to the complex task + */ + n2 = n/2; + ae_vector_set_length(&buf, n, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ftcomplexfftplan(n2, 1, &plan, _state); + ftapplyplan(&plan, &buf, 0, 1, _state); + ae_vector_set_length(f, n, _state); + for(i=0; i<=n2; i++) + { + idx = 2*(i%n2); + hn.x = buf.ptr.p_double[idx+0]; + hn.y = buf.ptr.p_double[idx+1]; + idx = 2*((n2-i)%n2); + hmnc.x = buf.ptr.p_double[idx+0]; + hmnc.y = -buf.ptr.p_double[idx+1]; + v.x = -ae_sin(-2*ae_pi*i/n, _state); + v.y = ae_cos(-2*ae_pi*i/n, _state); + f->ptr.p_complex[i] = ae_c_sub(ae_c_add(hn,hmnc),ae_c_mul(v,ae_c_sub(hn,hmnc))); + f->ptr.p_complex[i].x = 0.5*f->ptr.p_complex[i].x; + f->ptr.p_complex[i].y = 0.5*f->ptr.p_complex[i].y; + } + for(i=n2+1; i<=n-1; i++) + { + f->ptr.p_complex[i] = ae_c_conj(f->ptr.p_complex[n-i], _state); + } + } + else + { + + /* + * use complex FFT + */ + ae_vector_set_length(f, n, _state); + for(i=0; i<=n-1; i++) + { + f->ptr.p_complex[i] = ae_complex_from_d(a->ptr.p_double[i]); + } + fftc1d(f, n, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional real inverse FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + F - array[0..floor(N/2)] - frequencies from forward real FFT + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + +NOTE: + F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just one +half of frequencies array is needed - elements from 0 to floor(N/2). F[0] +is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd, then +F[floor(N/2)] has no special properties. + +Relying on properties noted above, FFTR1DInv subroutine uses only elements +from 0th to floor(N/2)-th. It ignores imaginary part of F[0], and in case +N is even it ignores imaginary part of F[floor(N/2)] too. + +When you call this function using full arguments list - "FFTR1DInv(F,N,A)" +- you can pass either either frequencies array with N elements or reduced +array with roughly N/2 elements - subroutine will successfully transform +both. + +If you call this function using reduced arguments list - "FFTR1DInv(F,A)" +- you must pass FULL array with N elements (although higher N/2 are still +not used) because array size is used to automatically determine FFT length + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinv(/* Complex */ ae_vector* f, + ae_int_t n, + /* Real */ ae_vector* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector h; + ae_vector fh; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(a); + ae_vector_init(&h, 0, DT_REAL, _state, ae_true); + ae_vector_init(&fh, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0, "FFTR1DInv: incorrect N!", _state); + ae_assert(f->cnt>=ae_ifloor((double)n/(double)2, _state)+1, "FFTR1DInv: Length(F)ptr.p_complex[0].x, _state), "FFTR1DInv: F contains infinite or NAN values!", _state); + for(i=1; i<=ae_ifloor((double)n/(double)2, _state)-1; i++) + { + ae_assert(ae_isfinite(f->ptr.p_complex[i].x, _state)&&ae_isfinite(f->ptr.p_complex[i].y, _state), "FFTR1DInv: F contains infinite or NAN values!", _state); + } + ae_assert(ae_isfinite(f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].x, _state), "FFTR1DInv: F contains infinite or NAN values!", _state); + if( n%2!=0 ) + { + ae_assert(ae_isfinite(f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].y, _state), "FFTR1DInv: F contains infinite or NAN values!", _state); + } + + /* + * Special case: N=1, FFT is just identity transform. + * After this block we assume that N is strictly greater than 1. + */ + if( n==1 ) + { + ae_vector_set_length(a, 1, _state); + a->ptr.p_double[0] = f->ptr.p_complex[0].x; + ae_frame_leave(_state); + return; + } + + /* + * inverse real FFT is reduced to the inverse real FHT, + * which is reduced to the forward real FHT, + * which is reduced to the forward real FFT. + * + * Don't worry, it is really compact and efficient reduction :) + */ + ae_vector_set_length(&h, n, _state); + ae_vector_set_length(a, n, _state); + h.ptr.p_double[0] = f->ptr.p_complex[0].x; + for(i=1; i<=ae_ifloor((double)n/(double)2, _state)-1; i++) + { + h.ptr.p_double[i] = f->ptr.p_complex[i].x-f->ptr.p_complex[i].y; + h.ptr.p_double[n-i] = f->ptr.p_complex[i].x+f->ptr.p_complex[i].y; + } + if( n%2==0 ) + { + h.ptr.p_double[ae_ifloor((double)n/(double)2, _state)] = f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].x; + } + else + { + h.ptr.p_double[ae_ifloor((double)n/(double)2, _state)] = f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].x-f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].y; + h.ptr.p_double[ae_ifloor((double)n/(double)2, _state)+1] = f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].x+f->ptr.p_complex[ae_ifloor((double)n/(double)2, _state)].y; + } + fftr1d(&h, n, &fh, _state); + for(i=0; i<=n-1; i++) + { + a->ptr.p_double[i] = (fh.ptr.p_complex[i].x-fh.ptr.p_complex[i].y)/n; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. Never call it directly! + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinternaleven(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* buf, + fasttransformplan* plan, + ae_state *_state) +{ + double x; + double y; + ae_int_t i; + ae_int_t n2; + ae_int_t idx; + ae_complex hn; + ae_complex hmnc; + ae_complex v; + + + ae_assert(n>0&&n%2==0, "FFTR1DEvenInplace: incorrect N!", _state); + + /* + * Special cases: + * * N=2 + * + * After this block we assume that N is strictly greater than 2 + */ + if( n==2 ) + { + x = a->ptr.p_double[0]+a->ptr.p_double[1]; + y = a->ptr.p_double[0]-a->ptr.p_double[1]; + a->ptr.p_double[0] = x; + a->ptr.p_double[1] = y; + return; + } + + /* + * even-size real FFT, use reduction to the complex task + */ + n2 = n/2; + ae_v_move(&buf->ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ftapplyplan(plan, buf, 0, 1, _state); + a->ptr.p_double[0] = buf->ptr.p_double[0]+buf->ptr.p_double[1]; + for(i=1; i<=n2-1; i++) + { + idx = 2*(i%n2); + hn.x = buf->ptr.p_double[idx+0]; + hn.y = buf->ptr.p_double[idx+1]; + idx = 2*(n2-i); + hmnc.x = buf->ptr.p_double[idx+0]; + hmnc.y = -buf->ptr.p_double[idx+1]; + v.x = -ae_sin(-2*ae_pi*i/n, _state); + v.y = ae_cos(-2*ae_pi*i/n, _state); + v = ae_c_sub(ae_c_add(hn,hmnc),ae_c_mul(v,ae_c_sub(hn,hmnc))); + a->ptr.p_double[2*i+0] = 0.5*v.x; + a->ptr.p_double[2*i+1] = 0.5*v.y; + } + a->ptr.p_double[1] = buf->ptr.p_double[0]-buf->ptr.p_double[1]; +} + + +/************************************************************************* +Internal subroutine. Never call it directly! + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinvinternaleven(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* buf, + fasttransformplan* plan, + ae_state *_state) +{ + double x; + double y; + double t; + ae_int_t i; + ae_int_t n2; + + + ae_assert(n>0&&n%2==0, "FFTR1DInvInternalEven: incorrect N!", _state); + + /* + * Special cases: + * * N=2 + * + * After this block we assume that N is strictly greater than 2 + */ + if( n==2 ) + { + x = 0.5*(a->ptr.p_double[0]+a->ptr.p_double[1]); + y = 0.5*(a->ptr.p_double[0]-a->ptr.p_double[1]); + a->ptr.p_double[0] = x; + a->ptr.p_double[1] = y; + return; + } + + /* + * inverse real FFT is reduced to the inverse real FHT, + * which is reduced to the forward real FHT, + * which is reduced to the forward real FFT. + * + * Don't worry, it is really compact and efficient reduction :) + */ + n2 = n/2; + buf->ptr.p_double[0] = a->ptr.p_double[0]; + for(i=1; i<=n2-1; i++) + { + x = a->ptr.p_double[2*i+0]; + y = a->ptr.p_double[2*i+1]; + buf->ptr.p_double[i] = x-y; + buf->ptr.p_double[n-i] = x+y; + } + buf->ptr.p_double[n2] = a->ptr.p_double[1]; + fftr1dinternaleven(buf, n, a, plan, _state); + a->ptr.p_double[0] = buf->ptr.p_double[0]/n; + t = (double)1/(double)n; + for(i=1; i<=n2-1; i++) + { + x = buf->ptr.p_double[2*i+0]; + y = buf->ptr.p_double[2*i+1]; + a->ptr.p_double[i] = t*(x-y); + a->ptr.p_double[n-i] = t*(x+y); + } + a->ptr.p_double[n2] = buf->ptr.p_double[1]/n; +} + + + + +/************************************************************************* +1-dimensional complex convolution. + +For given A/B returns conv(A,B) (non-circular). Subroutine can automatically +choose between three implementations: straightforward O(M*N) formula for +very small N (or M), overlap-add algorithm for cases where max(M,N) is +significantly larger than min(M,N), but O(M*N) algorithm is too slow, and +general FFT-based formula for cases where two previois algorithms are too +slow. + +Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N. + +INPUT PARAMETERS + A - array[0..M-1] - complex function to be transformed + M - problem size + B - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1d(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Complex */ ae_vector* r, + ae_state *_state) +{ + + ae_vector_clear(r); + + ae_assert(n>0&&m>0, "ConvC1D: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer that B. + */ + if( m0&&m>0)&&n<=m, "ConvC1DInv: incorrect N or M!", _state); + p = ftbasefindsmooth(m, _state); + ftcomplexfftplan(p, 1, &plan, _state); + ae_vector_set_length(&buf, 2*p, _state); + for(i=0; i<=m-1; i++) + { + buf.ptr.p_double[2*i+0] = a->ptr.p_complex[i].x; + buf.ptr.p_double[2*i+1] = a->ptr.p_complex[i].y; + } + for(i=m; i<=p-1; i++) + { + buf.ptr.p_double[2*i+0] = 0; + buf.ptr.p_double[2*i+1] = 0; + } + ae_vector_set_length(&buf2, 2*p, _state); + for(i=0; i<=n-1; i++) + { + buf2.ptr.p_double[2*i+0] = b->ptr.p_complex[i].x; + buf2.ptr.p_double[2*i+1] = b->ptr.p_complex[i].y; + } + for(i=n; i<=p-1; i++) + { + buf2.ptr.p_double[2*i+0] = 0; + buf2.ptr.p_double[2*i+1] = 0; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + ftapplyplan(&plan, &buf2, 0, 1, _state); + for(i=0; i<=p-1; i++) + { + c1.x = buf.ptr.p_double[2*i+0]; + c1.y = buf.ptr.p_double[2*i+1]; + c2.x = buf2.ptr.p_double[2*i+0]; + c2.y = buf2.ptr.p_double[2*i+1]; + c3 = ae_c_div(c1,c2); + buf.ptr.p_double[2*i+0] = c3.x; + buf.ptr.p_double[2*i+1] = -c3.y; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + t = (double)1/(double)p; + ae_vector_set_length(r, m-n+1, _state); + for(i=0; i<=m-n; i++) + { + r->ptr.p_complex[i].x = t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i].y = -t*buf.ptr.p_double[2*i+1]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional circular complex convolution. + +For given S/R returns conv(S,R) (circular). Algorithm has linearithmic +complexity for any M/N. + +IMPORTANT: normal convolution is commutative, i.e. it is symmetric - +conv(A,B)=conv(B,A). Cyclic convolution IS NOT. One function - S - is a +signal, periodic function, and another - R - is a response, non-periodic +function with limited length. + +INPUT PARAMETERS + S - array[0..M-1] - complex periodic signal + M - problem size + B - array[0..N-1] - complex non-periodic response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircular(/* Complex */ ae_vector* s, + ae_int_t m, + /* Complex */ ae_vector* r, + ae_int_t n, + /* Complex */ ae_vector* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector buf; + ae_int_t i1; + ae_int_t i2; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(c); + ae_vector_init(&buf, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DCircular: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_complex[i1], 1, "N", ae_v_len(0,j2)); + i1 = i1+m; + } + convc1dcircular(s, m, &buf, m, c, _state); + ae_frame_leave(_state); + return; + } + convc1dx(s, m, r, n, ae_true, -1, 0, c, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved periodic signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - non-periodic response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-1]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircularinv(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Complex */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t i1; + ae_int_t i2; + ae_int_t j2; + ae_vector buf; + ae_vector buf2; + ae_vector cbuf; + fasttransformplan plan; + ae_complex c1; + ae_complex c2; + ae_complex c3; + double t; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&cbuf, 0, DT_COMPLEX, _state, ae_true); + _fasttransformplan_init(&plan, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DCircularInv: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_complex[i1], 1, "N", ae_v_len(0,j2)); + i1 = i1+m; + } + convc1dcircularinv(a, m, &cbuf, m, r, _state); + ae_frame_leave(_state); + return; + } + + /* + * Task is normalized + */ + ftcomplexfftplan(m, 1, &plan, _state); + ae_vector_set_length(&buf, 2*m, _state); + for(i=0; i<=m-1; i++) + { + buf.ptr.p_double[2*i+0] = a->ptr.p_complex[i].x; + buf.ptr.p_double[2*i+1] = a->ptr.p_complex[i].y; + } + ae_vector_set_length(&buf2, 2*m, _state); + for(i=0; i<=n-1; i++) + { + buf2.ptr.p_double[2*i+0] = b->ptr.p_complex[i].x; + buf2.ptr.p_double[2*i+1] = b->ptr.p_complex[i].y; + } + for(i=n; i<=m-1; i++) + { + buf2.ptr.p_double[2*i+0] = 0; + buf2.ptr.p_double[2*i+1] = 0; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + ftapplyplan(&plan, &buf2, 0, 1, _state); + for(i=0; i<=m-1; i++) + { + c1.x = buf.ptr.p_double[2*i+0]; + c1.y = buf.ptr.p_double[2*i+1]; + c2.x = buf2.ptr.p_double[2*i+0]; + c2.y = buf2.ptr.p_double[2*i+1]; + c3 = ae_c_div(c1,c2); + buf.ptr.p_double[2*i+0] = c3.x; + buf.ptr.p_double[2*i+1] = -c3.y; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + t = (double)1/(double)m; + ae_vector_set_length(r, m, _state); + for(i=0; i<=m-1; i++) + { + r->ptr.p_complex[i].x = t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i].y = -t*buf.ptr.p_double[2*i+1]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional real convolution. + +Analogous to ConvC1D(), see ConvC1D() comments for more details. + +INPUT PARAMETERS + A - array[0..M-1] - real function to be transformed + M - problem size + B - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1d(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* r, + ae_state *_state) +{ + + ae_vector_clear(r); + + ae_assert(n>0&&m>0, "ConvR1D: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer that B. + */ + if( m0&&m>0)&&n<=m, "ConvR1DInv: incorrect N or M!", _state); + p = ftbasefindsmootheven(m, _state); + ae_vector_set_length(&buf, p, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1)); + for(i=m; i<=p-1; i++) + { + buf.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf2, p, _state); + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=n; i<=p-1; i++) + { + buf2.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf3, p, _state); + ftcomplexfftplan(p/2, 1, &plan, _state); + fftr1dinternaleven(&buf, p, &buf3, &plan, _state); + fftr1dinternaleven(&buf2, p, &buf3, &plan, _state); + buf.ptr.p_double[0] = buf.ptr.p_double[0]/buf2.ptr.p_double[0]; + buf.ptr.p_double[1] = buf.ptr.p_double[1]/buf2.ptr.p_double[1]; + for(i=1; i<=p/2-1; i++) + { + c1.x = buf.ptr.p_double[2*i+0]; + c1.y = buf.ptr.p_double[2*i+1]; + c2.x = buf2.ptr.p_double[2*i+0]; + c2.y = buf2.ptr.p_double[2*i+1]; + c3 = ae_c_div(c1,c2); + buf.ptr.p_double[2*i+0] = c3.x; + buf.ptr.p_double[2*i+1] = c3.y; + } + fftr1dinvinternaleven(&buf, p, &buf3, &plan, _state); + ae_vector_set_length(r, m-n+1, _state); + ae_v_move(&r->ptr.p_double[0], 1, &buf.ptr.p_double[0], 1, ae_v_len(0,m-n)); + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional circular real convolution. + +Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details. + +INPUT PARAMETERS + S - array[0..M-1] - real signal + M - problem size + B - array[0..N-1] - real response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircular(/* Real */ ae_vector* s, + ae_int_t m, + /* Real */ ae_vector* r, + ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector buf; + ae_int_t i1; + ae_int_t i2; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(c); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DCircular: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_double[i1], 1, ae_v_len(0,j2)); + i1 = i1+m; + } + convr1dcircular(s, m, &buf, m, c, _state); + ae_frame_leave(_state); + return; + } + + /* + * reduce to usual convolution + */ + convr1dx(s, m, r, n, ae_true, -1, 0, c, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional complex deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircularinv(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t i1; + ae_int_t i2; + ae_int_t j2; + ae_vector buf; + ae_vector buf2; + ae_vector buf3; + ae_vector cbuf; + ae_vector cbuf2; + fasttransformplan plan; + ae_complex c1; + ae_complex c2; + ae_complex c3; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&cbuf, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cbuf2, 0, DT_COMPLEX, _state, ae_true); + _fasttransformplan_init(&plan, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvR1DCircularInv: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_double[i1], 1, ae_v_len(0,j2)); + i1 = i1+m; + } + convr1dcircularinv(a, m, &buf, m, r, _state); + ae_frame_leave(_state); + return; + } + + /* + * Task is normalized + */ + if( m%2==0 ) + { + + /* + * size is even, use fast even-size FFT + */ + ae_vector_set_length(&buf, m, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_vector_set_length(&buf2, m, _state); + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=n; i<=m-1; i++) + { + buf2.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf3, m, _state); + ftcomplexfftplan(m/2, 1, &plan, _state); + fftr1dinternaleven(&buf, m, &buf3, &plan, _state); + fftr1dinternaleven(&buf2, m, &buf3, &plan, _state); + buf.ptr.p_double[0] = buf.ptr.p_double[0]/buf2.ptr.p_double[0]; + buf.ptr.p_double[1] = buf.ptr.p_double[1]/buf2.ptr.p_double[1]; + for(i=1; i<=m/2-1; i++) + { + c1.x = buf.ptr.p_double[2*i+0]; + c1.y = buf.ptr.p_double[2*i+1]; + c2.x = buf2.ptr.p_double[2*i+0]; + c2.y = buf2.ptr.p_double[2*i+1]; + c3 = ae_c_div(c1,c2); + buf.ptr.p_double[2*i+0] = c3.x; + buf.ptr.p_double[2*i+1] = c3.y; + } + fftr1dinvinternaleven(&buf, m, &buf3, &plan, _state); + ae_vector_set_length(r, m, _state); + ae_v_move(&r->ptr.p_double[0], 1, &buf.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + else + { + + /* + * odd-size, use general real FFT + */ + fftr1d(a, m, &cbuf, _state); + ae_vector_set_length(&buf2, m, _state); + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=n; i<=m-1; i++) + { + buf2.ptr.p_double[i] = 0; + } + fftr1d(&buf2, m, &cbuf2, _state); + for(i=0; i<=ae_ifloor((double)m/(double)2, _state); i++) + { + cbuf.ptr.p_complex[i] = ae_c_div(cbuf.ptr.p_complex[i],cbuf2.ptr.p_complex[i]); + } + fftr1dinv(&cbuf, m, r, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional complex convolution. + +Extended subroutine which allows to choose convolution algorithm. +Intended for internal use, ALGLIB users should call ConvC1D()/ConvC1DCircular(). + +INPUT PARAMETERS + A - array[0..M-1] - complex function to be transformed + M - problem size + B - array[0..N-1] - complex function to be transformed + N - problem size, N<=M + Alg - algorithm type: + *-2 auto-select Q for overlap-add + *-1 auto-select algorithm and parameters + * 0 straightforward formula for small N's + * 1 general FFT-based code + * 2 overlap-add with length Q + Q - length for overlap-add + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-1]. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dx(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + ae_bool circular, + ae_int_t alg, + ae_int_t q, + /* Complex */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t p; + ae_int_t ptotal; + ae_int_t i1; + ae_int_t i2; + ae_int_t j1; + ae_int_t j2; + ae_vector bbuf; + ae_complex v; + double ax; + double ay; + double bx; + double by; + double t; + double tx; + double ty; + double flopcand; + double flopbest; + ae_int_t algbest; + fasttransformplan plan; + ae_vector buf; + ae_vector buf2; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + ae_vector_init(&bbuf, 0, DT_COMPLEX, _state, ae_true); + _fasttransformplan_init(&plan, _state, ae_true); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf2, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DX: incorrect N or M!", _state); + ae_assert(n<=m, "ConvC1DX: Nptr.p_complex[0]; + ae_v_cmovec(&r->ptr.p_complex[0], 1, &a->ptr.p_complex[0], 1, "N", ae_v_len(0,m-1), v); + ae_frame_leave(_state); + return; + } + + /* + * use straightforward formula + */ + if( circular ) + { + + /* + * circular convolution + */ + ae_vector_set_length(r, m, _state); + v = b->ptr.p_complex[0]; + ae_v_cmovec(&r->ptr.p_complex[0], 1, &a->ptr.p_complex[0], 1, "N", ae_v_len(0,m-1), v); + for(i=1; i<=n-1; i++) + { + v = b->ptr.p_complex[i]; + i1 = 0; + i2 = i-1; + j1 = m-i; + j2 = m-1; + ae_v_caddc(&r->ptr.p_complex[i1], 1, &a->ptr.p_complex[j1], 1, "N", ae_v_len(i1,i2), v); + i1 = i; + i2 = m-1; + j1 = 0; + j2 = m-i-1; + ae_v_caddc(&r->ptr.p_complex[i1], 1, &a->ptr.p_complex[j1], 1, "N", ae_v_len(i1,i2), v); + } + } + else + { + + /* + * non-circular convolution + */ + ae_vector_set_length(r, m+n-1, _state); + for(i=0; i<=m+n-2; i++) + { + r->ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=0; i<=n-1; i++) + { + v = b->ptr.p_complex[i]; + ae_v_caddc(&r->ptr.p_complex[i], 1, &a->ptr.p_complex[0], 1, "N", ae_v_len(i,i+m-1), v); + } + } + ae_frame_leave(_state); + return; + } + + /* + * general FFT-based code for + * circular and non-circular convolutions. + * + * First, if convolution is circular, we test whether M is smooth or not. + * If it is smooth, we just use M-length FFT to calculate convolution. + * If it is not, we calculate non-circular convolution and wrap it arount. + * + * IF convolution is non-circular, we use zero-padding + FFT. + */ + if( alg==1 ) + { + if( circular&&ftbaseissmooth(m, _state) ) + { + + /* + * special code for circular convolution with smooth M + */ + ftcomplexfftplan(m, 1, &plan, _state); + ae_vector_set_length(&buf, 2*m, _state); + for(i=0; i<=m-1; i++) + { + buf.ptr.p_double[2*i+0] = a->ptr.p_complex[i].x; + buf.ptr.p_double[2*i+1] = a->ptr.p_complex[i].y; + } + ae_vector_set_length(&buf2, 2*m, _state); + for(i=0; i<=n-1; i++) + { + buf2.ptr.p_double[2*i+0] = b->ptr.p_complex[i].x; + buf2.ptr.p_double[2*i+1] = b->ptr.p_complex[i].y; + } + for(i=n; i<=m-1; i++) + { + buf2.ptr.p_double[2*i+0] = 0; + buf2.ptr.p_double[2*i+1] = 0; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + ftapplyplan(&plan, &buf2, 0, 1, _state); + for(i=0; i<=m-1; i++) + { + ax = buf.ptr.p_double[2*i+0]; + ay = buf.ptr.p_double[2*i+1]; + bx = buf2.ptr.p_double[2*i+0]; + by = buf2.ptr.p_double[2*i+1]; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*i+0] = tx; + buf.ptr.p_double[2*i+1] = -ty; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + t = (double)1/(double)m; + ae_vector_set_length(r, m, _state); + for(i=0; i<=m-1; i++) + { + r->ptr.p_complex[i].x = t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i].y = -t*buf.ptr.p_double[2*i+1]; + } + } + else + { + + /* + * M is non-smooth, general code (circular/non-circular): + * * first part is the same for circular and non-circular + * convolutions. zero padding, FFTs, inverse FFTs + * * second part differs: + * * for non-circular convolution we just copy array + * * for circular convolution we add array tail to its head + */ + p = ftbasefindsmooth(m+n-1, _state); + ftcomplexfftplan(p, 1, &plan, _state); + ae_vector_set_length(&buf, 2*p, _state); + for(i=0; i<=m-1; i++) + { + buf.ptr.p_double[2*i+0] = a->ptr.p_complex[i].x; + buf.ptr.p_double[2*i+1] = a->ptr.p_complex[i].y; + } + for(i=m; i<=p-1; i++) + { + buf.ptr.p_double[2*i+0] = 0; + buf.ptr.p_double[2*i+1] = 0; + } + ae_vector_set_length(&buf2, 2*p, _state); + for(i=0; i<=n-1; i++) + { + buf2.ptr.p_double[2*i+0] = b->ptr.p_complex[i].x; + buf2.ptr.p_double[2*i+1] = b->ptr.p_complex[i].y; + } + for(i=n; i<=p-1; i++) + { + buf2.ptr.p_double[2*i+0] = 0; + buf2.ptr.p_double[2*i+1] = 0; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + ftapplyplan(&plan, &buf2, 0, 1, _state); + for(i=0; i<=p-1; i++) + { + ax = buf.ptr.p_double[2*i+0]; + ay = buf.ptr.p_double[2*i+1]; + bx = buf2.ptr.p_double[2*i+0]; + by = buf2.ptr.p_double[2*i+1]; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*i+0] = tx; + buf.ptr.p_double[2*i+1] = -ty; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + t = (double)1/(double)p; + if( circular ) + { + + /* + * circular, add tail to head + */ + ae_vector_set_length(r, m, _state); + for(i=0; i<=m-1; i++) + { + r->ptr.p_complex[i].x = t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i].y = -t*buf.ptr.p_double[2*i+1]; + } + for(i=m; i<=m+n-2; i++) + { + r->ptr.p_complex[i-m].x = r->ptr.p_complex[i-m].x+t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i-m].y = r->ptr.p_complex[i-m].y-t*buf.ptr.p_double[2*i+1]; + } + } + else + { + + /* + * non-circular, just copy + */ + ae_vector_set_length(r, m+n-1, _state); + for(i=0; i<=m+n-2; i++) + { + r->ptr.p_complex[i].x = t*buf.ptr.p_double[2*i+0]; + r->ptr.p_complex[i].y = -t*buf.ptr.p_double[2*i+1]; + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * overlap-add method for + * circular and non-circular convolutions. + * + * First part of code (separate FFTs of input blocks) is the same + * for all types of convolution. Second part (overlapping outputs) + * differs for different types of convolution. We just copy output + * when convolution is non-circular. We wrap it around, if it is + * circular. + */ + if( alg==2 ) + { + ae_vector_set_length(&buf, 2*(q+n-1), _state); + + /* + * prepare R + */ + if( circular ) + { + ae_vector_set_length(r, m, _state); + for(i=0; i<=m-1; i++) + { + r->ptr.p_complex[i] = ae_complex_from_d(0); + } + } + else + { + ae_vector_set_length(r, m+n-1, _state); + for(i=0; i<=m+n-2; i++) + { + r->ptr.p_complex[i] = ae_complex_from_d(0); + } + } + + /* + * pre-calculated FFT(B) + */ + ae_vector_set_length(&bbuf, q+n-1, _state); + ae_v_cmove(&bbuf.ptr.p_complex[0], 1, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + for(j=n; j<=q+n-2; j++) + { + bbuf.ptr.p_complex[j] = ae_complex_from_d(0); + } + fftc1d(&bbuf, q+n-1, _state); + + /* + * prepare FFT plan for chunks of A + */ + ftcomplexfftplan(q+n-1, 1, &plan, _state); + + /* + * main overlap-add cycle + */ + i = 0; + while(i<=m-1) + { + p = ae_minint(q, m-i, _state); + for(j=0; j<=p-1; j++) + { + buf.ptr.p_double[2*j+0] = a->ptr.p_complex[i+j].x; + buf.ptr.p_double[2*j+1] = a->ptr.p_complex[i+j].y; + } + for(j=p; j<=q+n-2; j++) + { + buf.ptr.p_double[2*j+0] = 0; + buf.ptr.p_double[2*j+1] = 0; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + for(j=0; j<=q+n-2; j++) + { + ax = buf.ptr.p_double[2*j+0]; + ay = buf.ptr.p_double[2*j+1]; + bx = bbuf.ptr.p_complex[j].x; + by = bbuf.ptr.p_complex[j].y; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*j+0] = tx; + buf.ptr.p_double[2*j+1] = -ty; + } + ftapplyplan(&plan, &buf, 0, 1, _state); + t = (double)1/(double)(q+n-1); + if( circular ) + { + j1 = ae_minint(i+p+n-2, m-1, _state)-i; + j2 = j1+1; + } + else + { + j1 = p+n-2; + j2 = j1+1; + } + for(j=0; j<=j1; j++) + { + r->ptr.p_complex[i+j].x = r->ptr.p_complex[i+j].x+buf.ptr.p_double[2*j+0]*t; + r->ptr.p_complex[i+j].y = r->ptr.p_complex[i+j].y-buf.ptr.p_double[2*j+1]*t; + } + for(j=j2; j<=p+n-2; j++) + { + r->ptr.p_complex[j-j2].x = r->ptr.p_complex[j-j2].x+buf.ptr.p_double[2*j+0]*t; + r->ptr.p_complex[j-j2].y = r->ptr.p_complex[j-j2].y-buf.ptr.p_double[2*j+1]*t; + } + i = i+p; + } + ae_frame_leave(_state); + return; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional real convolution. + +Extended subroutine which allows to choose convolution algorithm. +Intended for internal use, ALGLIB users should call ConvR1D(). + +INPUT PARAMETERS + A - array[0..M-1] - complex function to be transformed + M - problem size + B - array[0..N-1] - complex function to be transformed + N - problem size, N<=M + Alg - algorithm type: + *-2 auto-select Q for overlap-add + *-1 auto-select algorithm and parameters + * 0 straightforward formula for small N's + * 1 general FFT-based code + * 2 overlap-add with length Q + Q - length for overlap-add + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-1]. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dx(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + ae_bool circular, + ae_int_t alg, + ae_int_t q, + /* Real */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + double v; + ae_int_t i; + ae_int_t j; + ae_int_t p; + ae_int_t ptotal; + ae_int_t i1; + ae_int_t i2; + ae_int_t j1; + ae_int_t j2; + double ax; + double ay; + double bx; + double by; + double tx; + double ty; + double flopcand; + double flopbest; + ae_int_t algbest; + fasttransformplan plan; + ae_vector buf; + ae_vector buf2; + ae_vector buf3; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + _fasttransformplan_init(&plan, _state, ae_true); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf3, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DX: incorrect N or M!", _state); + ae_assert(n<=m, "ConvC1DX: Nptr.p_double[0]; + ae_v_moved(&r->ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1), v); + ae_frame_leave(_state); + return; + } + + /* + * use straightforward formula + */ + if( circular ) + { + + /* + * circular convolution + */ + ae_vector_set_length(r, m, _state); + v = b->ptr.p_double[0]; + ae_v_moved(&r->ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1), v); + for(i=1; i<=n-1; i++) + { + v = b->ptr.p_double[i]; + i1 = 0; + i2 = i-1; + j1 = m-i; + j2 = m-1; + ae_v_addd(&r->ptr.p_double[i1], 1, &a->ptr.p_double[j1], 1, ae_v_len(i1,i2), v); + i1 = i; + i2 = m-1; + j1 = 0; + j2 = m-i-1; + ae_v_addd(&r->ptr.p_double[i1], 1, &a->ptr.p_double[j1], 1, ae_v_len(i1,i2), v); + } + } + else + { + + /* + * non-circular convolution + */ + ae_vector_set_length(r, m+n-1, _state); + for(i=0; i<=m+n-2; i++) + { + r->ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = b->ptr.p_double[i]; + ae_v_addd(&r->ptr.p_double[i], 1, &a->ptr.p_double[0], 1, ae_v_len(i,i+m-1), v); + } + } + ae_frame_leave(_state); + return; + } + + /* + * general FFT-based code for + * circular and non-circular convolutions. + * + * First, if convolution is circular, we test whether M is smooth or not. + * If it is smooth, we just use M-length FFT to calculate convolution. + * If it is not, we calculate non-circular convolution and wrap it arount. + * + * If convolution is non-circular, we use zero-padding + FFT. + * + * We assume that M+N-1>2 - we should call small case code otherwise + */ + if( alg==1 ) + { + ae_assert(m+n-1>2, "ConvR1DX: internal error!", _state); + if( (circular&&ftbaseissmooth(m, _state))&&m%2==0 ) + { + + /* + * special code for circular convolution with smooth even M + */ + ae_vector_set_length(&buf, m, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_vector_set_length(&buf2, m, _state); + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=n; i<=m-1; i++) + { + buf2.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf3, m, _state); + ftcomplexfftplan(m/2, 1, &plan, _state); + fftr1dinternaleven(&buf, m, &buf3, &plan, _state); + fftr1dinternaleven(&buf2, m, &buf3, &plan, _state); + buf.ptr.p_double[0] = buf.ptr.p_double[0]*buf2.ptr.p_double[0]; + buf.ptr.p_double[1] = buf.ptr.p_double[1]*buf2.ptr.p_double[1]; + for(i=1; i<=m/2-1; i++) + { + ax = buf.ptr.p_double[2*i+0]; + ay = buf.ptr.p_double[2*i+1]; + bx = buf2.ptr.p_double[2*i+0]; + by = buf2.ptr.p_double[2*i+1]; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*i+0] = tx; + buf.ptr.p_double[2*i+1] = ty; + } + fftr1dinvinternaleven(&buf, m, &buf3, &plan, _state); + ae_vector_set_length(r, m, _state); + ae_v_move(&r->ptr.p_double[0], 1, &buf.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + else + { + + /* + * M is non-smooth or non-even, general code (circular/non-circular): + * * first part is the same for circular and non-circular + * convolutions. zero padding, FFTs, inverse FFTs + * * second part differs: + * * for non-circular convolution we just copy array + * * for circular convolution we add array tail to its head + */ + p = ftbasefindsmootheven(m+n-1, _state); + ae_vector_set_length(&buf, p, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[0], 1, ae_v_len(0,m-1)); + for(i=m; i<=p-1; i++) + { + buf.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf2, p, _state); + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=n; i<=p-1; i++) + { + buf2.ptr.p_double[i] = 0; + } + ae_vector_set_length(&buf3, p, _state); + ftcomplexfftplan(p/2, 1, &plan, _state); + fftr1dinternaleven(&buf, p, &buf3, &plan, _state); + fftr1dinternaleven(&buf2, p, &buf3, &plan, _state); + buf.ptr.p_double[0] = buf.ptr.p_double[0]*buf2.ptr.p_double[0]; + buf.ptr.p_double[1] = buf.ptr.p_double[1]*buf2.ptr.p_double[1]; + for(i=1; i<=p/2-1; i++) + { + ax = buf.ptr.p_double[2*i+0]; + ay = buf.ptr.p_double[2*i+1]; + bx = buf2.ptr.p_double[2*i+0]; + by = buf2.ptr.p_double[2*i+1]; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*i+0] = tx; + buf.ptr.p_double[2*i+1] = ty; + } + fftr1dinvinternaleven(&buf, p, &buf3, &plan, _state); + if( circular ) + { + + /* + * circular, add tail to head + */ + ae_vector_set_length(r, m, _state); + ae_v_move(&r->ptr.p_double[0], 1, &buf.ptr.p_double[0], 1, ae_v_len(0,m-1)); + if( n>=2 ) + { + ae_v_add(&r->ptr.p_double[0], 1, &buf.ptr.p_double[m], 1, ae_v_len(0,n-2)); + } + } + else + { + + /* + * non-circular, just copy + */ + ae_vector_set_length(r, m+n-1, _state); + ae_v_move(&r->ptr.p_double[0], 1, &buf.ptr.p_double[0], 1, ae_v_len(0,m+n-2)); + } + } + ae_frame_leave(_state); + return; + } + + /* + * overlap-add method + */ + if( alg==2 ) + { + ae_assert((q+n-1)%2==0, "ConvR1DX: internal error!", _state); + ae_vector_set_length(&buf, q+n-1, _state); + ae_vector_set_length(&buf2, q+n-1, _state); + ae_vector_set_length(&buf3, q+n-1, _state); + ftcomplexfftplan((q+n-1)/2, 1, &plan, _state); + + /* + * prepare R + */ + if( circular ) + { + ae_vector_set_length(r, m, _state); + for(i=0; i<=m-1; i++) + { + r->ptr.p_double[i] = 0; + } + } + else + { + ae_vector_set_length(r, m+n-1, _state); + for(i=0; i<=m+n-2; i++) + { + r->ptr.p_double[i] = 0; + } + } + + /* + * pre-calculated FFT(B) + */ + ae_v_move(&buf2.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(j=n; j<=q+n-2; j++) + { + buf2.ptr.p_double[j] = 0; + } + fftr1dinternaleven(&buf2, q+n-1, &buf3, &plan, _state); + + /* + * main overlap-add cycle + */ + i = 0; + while(i<=m-1) + { + p = ae_minint(q, m-i, _state); + ae_v_move(&buf.ptr.p_double[0], 1, &a->ptr.p_double[i], 1, ae_v_len(0,p-1)); + for(j=p; j<=q+n-2; j++) + { + buf.ptr.p_double[j] = 0; + } + fftr1dinternaleven(&buf, q+n-1, &buf3, &plan, _state); + buf.ptr.p_double[0] = buf.ptr.p_double[0]*buf2.ptr.p_double[0]; + buf.ptr.p_double[1] = buf.ptr.p_double[1]*buf2.ptr.p_double[1]; + for(j=1; j<=(q+n-1)/2-1; j++) + { + ax = buf.ptr.p_double[2*j+0]; + ay = buf.ptr.p_double[2*j+1]; + bx = buf2.ptr.p_double[2*j+0]; + by = buf2.ptr.p_double[2*j+1]; + tx = ax*bx-ay*by; + ty = ax*by+ay*bx; + buf.ptr.p_double[2*j+0] = tx; + buf.ptr.p_double[2*j+1] = ty; + } + fftr1dinvinternaleven(&buf, q+n-1, &buf3, &plan, _state); + if( circular ) + { + j1 = ae_minint(i+p+n-2, m-1, _state)-i; + j2 = j1+1; + } + else + { + j1 = p+n-2; + j2 = j1+1; + } + ae_v_add(&r->ptr.p_double[i], 1, &buf.ptr.p_double[0], 1, ae_v_len(i,i+j1)); + if( p+n-2>=j2 ) + { + ae_v_add(&r->ptr.p_double[0], 1, &buf.ptr.p_double[j2], 1, ae_v_len(0,p+n-2-j2)); + } + i = i+p; + } + ae_frame_leave(_state); + return; + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +1-dimensional complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(conj(pattern[j])*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(conj(pattern[j])*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1d(/* Complex */ ae_vector* signal, + ae_int_t n, + /* Complex */ ae_vector* pattern, + ae_int_t m, + /* Complex */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector p; + ae_vector b; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + ae_vector_init(&p, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&b, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0&&m>0, "CorrC1D: incorrect N or M!", _state); + ae_vector_set_length(&p, m, _state); + for(i=0; i<=m-1; i++) + { + p.ptr.p_complex[m-1-i] = ae_c_conj(pattern->ptr.p_complex[i], _state); + } + convc1d(&p, m, signal, n, &b, _state); + ae_vector_set_length(r, m+n-1, _state); + ae_v_cmove(&r->ptr.p_complex[0], 1, &b.ptr.p_complex[m-1], 1, "N", ae_v_len(0,n-1)); + if( m+n-2>=n ) + { + ae_v_cmove(&r->ptr.p_complex[n], 1, &b.ptr.p_complex[0], 1, "N", ae_v_len(n,m+n-2)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional circular complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1dcircular(/* Complex */ ae_vector* signal, + ae_int_t m, + /* Complex */ ae_vector* pattern, + ae_int_t n, + /* Complex */ ae_vector* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector p; + ae_vector b; + ae_int_t i1; + ae_int_t i2; + ae_int_t i; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(c); + ae_vector_init(&p, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&b, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DCircular: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_complex[i1], 1, "N", ae_v_len(0,j2)); + i1 = i1+m; + } + corrc1dcircular(signal, m, &b, m, c, _state); + ae_frame_leave(_state); + return; + } + + /* + * Task is normalized + */ + ae_vector_set_length(&p, n, _state); + for(i=0; i<=n-1; i++) + { + p.ptr.p_complex[n-1-i] = ae_c_conj(pattern->ptr.p_complex[i], _state); + } + convc1dcircular(signal, m, &p, n, &b, _state); + ae_vector_set_length(c, m, _state); + ae_v_cmove(&c->ptr.p_complex[0], 1, &b.ptr.p_complex[n-1], 1, "N", ae_v_len(0,m-n)); + if( m-n+1<=m-1 ) + { + ae_v_cmove(&c->ptr.p_complex[m-n+1], 1, &b.ptr.p_complex[0], 1, "N", ae_v_len(m-n+1,m-1)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(pattern[j]*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(pattern[j]*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1d(/* Real */ ae_vector* signal, + ae_int_t n, + /* Real */ ae_vector* pattern, + ae_int_t m, + /* Real */ ae_vector* r, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector p; + ae_vector b; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + ae_vector_init(&p, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0&&m>0, "CorrR1D: incorrect N or M!", _state); + ae_vector_set_length(&p, m, _state); + for(i=0; i<=m-1; i++) + { + p.ptr.p_double[m-1-i] = pattern->ptr.p_double[i]; + } + convr1d(&p, m, signal, n, &b, _state); + ae_vector_set_length(r, m+n-1, _state); + ae_v_move(&r->ptr.p_double[0], 1, &b.ptr.p_double[m-1], 1, ae_v_len(0,n-1)); + if( m+n-2>=n ) + { + ae_v_move(&r->ptr.p_double[n], 1, &b.ptr.p_double[0], 1, ae_v_len(n,m+n-2)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional circular real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1dcircular(/* Real */ ae_vector* signal, + ae_int_t m, + /* Real */ ae_vector* pattern, + ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector p; + ae_vector b; + ae_int_t i1; + ae_int_t i2; + ae_int_t i; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(c); + ae_vector_init(&p, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0&&m>0, "ConvC1DCircular: incorrect N or M!", _state); + + /* + * normalize task: make M>=N, + * so A will be longer (at least - not shorter) that B. + */ + if( mptr.p_double[i1], 1, ae_v_len(0,j2)); + i1 = i1+m; + } + corrr1dcircular(signal, m, &b, m, c, _state); + ae_frame_leave(_state); + return; + } + + /* + * Task is normalized + */ + ae_vector_set_length(&p, n, _state); + for(i=0; i<=n-1; i++) + { + p.ptr.p_double[n-1-i] = pattern->ptr.p_double[i]; + } + convr1dcircular(signal, m, &p, n, &b, _state); + ae_vector_set_length(c, m, _state); + ae_v_move(&c->ptr.p_double[0], 1, &b.ptr.p_double[n-1], 1, ae_v_len(0,m-n)); + if( m-n+1<=m-1 ) + { + ae_v_move(&c->ptr.p_double[m-n+1], 1, &b.ptr.p_double[0], 1, ae_v_len(m-n+1,m-1)); + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +1-dimensional Fast Hartley Transform. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - FHT of a input array, array[0..N-1], + A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1) + + + -- ALGLIB -- + Copyright 04.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1d(/* Real */ ae_vector* a, ae_int_t n, ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector fa; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&fa, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0, "FHTR1D: incorrect N!", _state); + + /* + * Special case: N=1, FHT is just identity transform. + * After this block we assume that N is strictly greater than 1. + */ + if( n==1 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Reduce FHt to real FFT + */ + fftr1d(a, n, &fa, _state); + for(i=0; i<=n-1; i++) + { + a->ptr.p_double[i] = fa.ptr.p_complex[i].x-fa.ptr.p_complex[i].y; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +1-dimensional inverse FHT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse FHT of a input array, array[0..N-1] + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1dinv(/* Real */ ae_vector* a, ae_int_t n, ae_state *_state) +{ + ae_int_t i; + + + ae_assert(n>0, "FHTR1DInv: incorrect N!", _state); + + /* + * Special case: N=1, iFHT is just identity transform. + * After this block we assume that N is strictly greater than 1. + */ + if( n==1 ) + { + return; + } + + /* + * Inverse FHT can be expressed in terms of the FHT as + * + * invfht(x) = fht(x)/N + */ + fhtr1d(a, n, _state); + for(i=0; i<=n-1; i++) + { + a->ptr.p_double[i] = a->ptr.p_double[i]/n; + } +} + + + +} + diff --git a/src/inc/alglib/fasttransforms.h b/src/inc/alglib/fasttransforms.h new file mode 100644 index 0000000..079da71 --- /dev/null +++ b/src/inc/alglib/fasttransforms.h @@ -0,0 +1,691 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _fasttransforms_pkg_h +#define _fasttransforms_pkg_h +#include "ap.h" +#include "alglibinternal.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +1-dimensional complex FFT. + +Array size N may be arbitrary number (composite or prime). Composite N's +are handled with cache-oblivious variation of a Cooley-Tukey algorithm. +Small prime-factors are transformed using hard coded codelets (similar to +FFTW codelets, but without low-level optimization), large prime-factors +are handled with Bluestein's algorithm. + +Fastests transforms are for smooth N's (prime factors are 2, 3, 5 only), +most fast for powers of 2. When N have prime factors larger than these, +but orders of magnitude smaller than N, computations will be about 4 times +slower than for nearby highly composite N's. When N itself is prime, speed +will be 6 times lower. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1d(complex_1d_array &a, const ae_int_t n); +void fftc1d(complex_1d_array &a); + + +/************************************************************************* +1-dimensional complex inverse FFT. + +Array size N may be arbitrary number (composite or prime). Algorithm has +O(N*logN) complexity for any N (composite or prime). + +See FFTC1D() description for more information about algorithm performance. + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + A_out[j] = SUM(A_in[k]/N*exp(+2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fftc1dinv(complex_1d_array &a, const ae_int_t n); +void fftc1dinv(complex_1d_array &a); + + +/************************************************************************* +1-dimensional real FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + F - DFT of a input array, array[0..N-1] + F[j] = SUM(A[k]*exp(-2*pi*sqrt(-1)*j*k/N), k = 0..N-1) + +NOTE: + F[] satisfies symmetry property F[k] = conj(F[N-k]), so just one half +of array is usually needed. But for convinience subroutine returns full +complex array (with frequencies above N/2), so its result may be used by +other FFT-related subroutines. + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1d(const real_1d_array &a, const ae_int_t n, complex_1d_array &f); +void fftr1d(const real_1d_array &a, complex_1d_array &f); + + +/************************************************************************* +1-dimensional real inverse FFT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + F - array[0..floor(N/2)] - frequencies from forward real FFT + N - problem size + +OUTPUT PARAMETERS + A - inverse DFT of a input array, array[0..N-1] + +NOTE: + F[] should satisfy symmetry property F[k] = conj(F[N-k]), so just one +half of frequencies array is needed - elements from 0 to floor(N/2). F[0] +is ALWAYS real. If N is even F[floor(N/2)] is real too. If N is odd, then +F[floor(N/2)] has no special properties. + +Relying on properties noted above, FFTR1DInv subroutine uses only elements +from 0th to floor(N/2)-th. It ignores imaginary part of F[0], and in case +N is even it ignores imaginary part of F[floor(N/2)] too. + +When you call this function using full arguments list - "FFTR1DInv(F,N,A)" +- you can pass either either frequencies array with N elements or reduced +array with roughly N/2 elements - subroutine will successfully transform +both. + +If you call this function using reduced arguments list - "FFTR1DInv(F,A)" +- you must pass FULL array with N elements (although higher N/2 are still +not used) because array size is used to automatically determine FFT length + + + -- ALGLIB -- + Copyright 01.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fftr1dinv(const complex_1d_array &f, const ae_int_t n, real_1d_array &a); +void fftr1dinv(const complex_1d_array &f, real_1d_array &a); + +/************************************************************************* +1-dimensional complex convolution. + +For given A/B returns conv(A,B) (non-circular). Subroutine can automatically +choose between three implementations: straightforward O(M*N) formula for +very small N (or M), overlap-add algorithm for cases where max(M,N) is +significantly larger than min(M,N), but O(M*N) algorithm is too slow, and +general FFT-based formula for cases where two previois algorithms are too +slow. + +Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N. + +INPUT PARAMETERS + A - array[0..M-1] - complex function to be transformed + M - problem size + B - array[0..N-1] - complex function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1d(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r); + + +/************************************************************************* +1-dimensional complex non-circular deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length, N<=M + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dinv(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r); + + +/************************************************************************* +1-dimensional circular complex convolution. + +For given S/R returns conv(S,R) (circular). Algorithm has linearithmic +complexity for any M/N. + +IMPORTANT: normal convolution is commutative, i.e. it is symmetric - +conv(A,B)=conv(B,A). Cyclic convolution IS NOT. One function - S - is a +signal, periodic function, and another - R - is a response, non-periodic +function with limited length. + +INPUT PARAMETERS + S - array[0..M-1] - complex periodic signal + M - problem size + B - array[0..N-1] - complex non-periodic response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircular(const complex_1d_array &s, const ae_int_t m, const complex_1d_array &r, const ae_int_t n, complex_1d_array &c); + + +/************************************************************************* +1-dimensional circular complex deconvolution (inverse of ConvC1DCircular()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved periodic signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - non-periodic response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-1]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convc1dcircularinv(const complex_1d_array &a, const ae_int_t m, const complex_1d_array &b, const ae_int_t n, complex_1d_array &r); + + +/************************************************************************* +1-dimensional real convolution. + +Analogous to ConvC1D(), see ConvC1D() comments for more details. + +INPUT PARAMETERS + A - array[0..M-1] - real function to be transformed + M - problem size + B - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..N+M-2]. + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1d(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r); + + +/************************************************************************* +1-dimensional real deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length, N<=M + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that A is zero at T<0, B is zero too. If one or both +functions have non-zero values at negative T's, you can still use this +subroutine - just shift its result correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dinv(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r); + + +/************************************************************************* +1-dimensional circular real convolution. + +Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details. + +INPUT PARAMETERS + S - array[0..M-1] - real signal + M - problem size + B - array[0..N-1] - real response + N - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircular(const real_1d_array &s, const ae_int_t m, const real_1d_array &r, const ae_int_t n, real_1d_array &c); + + +/************************************************************************* +1-dimensional complex deconvolution (inverse of ConvC1D()). + +Algorithm has M*log(M)) complexity for any M (composite or prime). + +INPUT PARAMETERS + A - array[0..M-1] - convolved signal, A = conv(R, B) + M - convolved signal length + B - array[0..N-1] - response + N - response length + +OUTPUT PARAMETERS + R - deconvolved signal. array[0..M-N]. + +NOTE: + deconvolution is unstable process and may result in division by zero +(if your response function is degenerate, i.e. has zero Fourier coefficient). + +NOTE: + It is assumed that B is zero at T<0. If it has non-zero values at +negative T's, you can still use this subroutine - just shift its result +correspondingly. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void convr1dcircularinv(const real_1d_array &a, const ae_int_t m, const real_1d_array &b, const ae_int_t n, real_1d_array &r); + +/************************************************************************* +1-dimensional complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(conj(pattern[j])*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(conj(pattern[j])*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1d(const complex_1d_array &signal, const ae_int_t n, const complex_1d_array &pattern, const ae_int_t m, complex_1d_array &r); + + +/************************************************************************* +1-dimensional circular complex cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrC1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - complex function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - complex function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrc1dcircular(const complex_1d_array &signal, const ae_int_t m, const complex_1d_array &pattern, const ae_int_t n, complex_1d_array &c); + + +/************************************************************************* +1-dimensional real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (non-circular). + +Correlation is calculated using reduction to convolution. Algorithm with +max(N,N)*log(max(N,N)) complexity is used (see ConvC1D() for more info +about performance). + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1D(Signal, Pattern) = Pattern x Signal (using traditional + definition of cross-correlation, denoting cross-correlation as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - cross-correlation, array[0..N+M-2]: + * positive lags are stored in R[0..N-1], + R[i] = sum(pattern[j]*signal[i+j] + * negative lags are stored in R[N..N+M-2], + R[N+M-1-i] = sum(pattern[j]*signal[-i+j] + +NOTE: + It is assumed that pattern domain is [0..M-1]. If Pattern is non-zero +on [-K..M-1], you can still use this subroutine, just shift result by K. + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1d(const real_1d_array &signal, const ae_int_t n, const real_1d_array &pattern, const ae_int_t m, real_1d_array &r); + + +/************************************************************************* +1-dimensional circular real cross-correlation. + +For given Pattern/Signal returns corr(Pattern,Signal) (circular). +Algorithm has linearithmic complexity for any M/N. + +IMPORTANT: + for historical reasons subroutine accepts its parameters in reversed + order: CorrR1DCircular(Signal, Pattern) = Pattern x Signal (using + traditional definition of cross-correlation, denoting cross-correlation + as "x"). + +INPUT PARAMETERS + Signal - array[0..N-1] - real function to be transformed, + periodic signal containing pattern + N - problem size + Pattern - array[0..M-1] - real function to be transformed, + non-periodic pattern to search withing signal + M - problem size + +OUTPUT PARAMETERS + R - convolution: A*B. array[0..M-1]. + + + -- ALGLIB -- + Copyright 21.07.2009 by Bochkanov Sergey +*************************************************************************/ +void corrr1dcircular(const real_1d_array &signal, const ae_int_t m, const real_1d_array &pattern, const ae_int_t n, real_1d_array &c); + +/************************************************************************* +1-dimensional Fast Hartley Transform. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - real function to be transformed + N - problem size + +OUTPUT PARAMETERS + A - FHT of a input array, array[0..N-1], + A_out[k] = sum(A_in[j]*(cos(2*pi*j*k/N)+sin(2*pi*j*k/N)), j=0..N-1) + + + -- ALGLIB -- + Copyright 04.06.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1d(real_1d_array &a, const ae_int_t n); + + +/************************************************************************* +1-dimensional inverse FHT. + +Algorithm has O(N*logN) complexity for any N (composite or prime). + +INPUT PARAMETERS + A - array[0..N-1] - complex array to be transformed + N - problem size + +OUTPUT PARAMETERS + A - inverse FHT of a input array, array[0..N-1] + + + -- ALGLIB -- + Copyright 29.05.2009 by Bochkanov Sergey +*************************************************************************/ +void fhtr1dinv(real_1d_array &a, const ae_int_t n); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void fftc1d(/* Complex */ ae_vector* a, ae_int_t n, ae_state *_state); +void fftc1dinv(/* Complex */ ae_vector* a, ae_int_t n, ae_state *_state); +void fftr1d(/* Real */ ae_vector* a, + ae_int_t n, + /* Complex */ ae_vector* f, + ae_state *_state); +void fftr1dinv(/* Complex */ ae_vector* f, + ae_int_t n, + /* Real */ ae_vector* a, + ae_state *_state); +void fftr1dinternaleven(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* buf, + fasttransformplan* plan, + ae_state *_state); +void fftr1dinvinternaleven(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* buf, + fasttransformplan* plan, + ae_state *_state); +void convc1d(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Complex */ ae_vector* r, + ae_state *_state); +void convc1dinv(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Complex */ ae_vector* r, + ae_state *_state); +void convc1dcircular(/* Complex */ ae_vector* s, + ae_int_t m, + /* Complex */ ae_vector* r, + ae_int_t n, + /* Complex */ ae_vector* c, + ae_state *_state); +void convc1dcircularinv(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + /* Complex */ ae_vector* r, + ae_state *_state); +void convr1d(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* r, + ae_state *_state); +void convr1dinv(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* r, + ae_state *_state); +void convr1dcircular(/* Real */ ae_vector* s, + ae_int_t m, + /* Real */ ae_vector* r, + ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +void convr1dcircularinv(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + /* Real */ ae_vector* r, + ae_state *_state); +void convc1dx(/* Complex */ ae_vector* a, + ae_int_t m, + /* Complex */ ae_vector* b, + ae_int_t n, + ae_bool circular, + ae_int_t alg, + ae_int_t q, + /* Complex */ ae_vector* r, + ae_state *_state); +void convr1dx(/* Real */ ae_vector* a, + ae_int_t m, + /* Real */ ae_vector* b, + ae_int_t n, + ae_bool circular, + ae_int_t alg, + ae_int_t q, + /* Real */ ae_vector* r, + ae_state *_state); +void corrc1d(/* Complex */ ae_vector* signal, + ae_int_t n, + /* Complex */ ae_vector* pattern, + ae_int_t m, + /* Complex */ ae_vector* r, + ae_state *_state); +void corrc1dcircular(/* Complex */ ae_vector* signal, + ae_int_t m, + /* Complex */ ae_vector* pattern, + ae_int_t n, + /* Complex */ ae_vector* c, + ae_state *_state); +void corrr1d(/* Real */ ae_vector* signal, + ae_int_t n, + /* Real */ ae_vector* pattern, + ae_int_t m, + /* Real */ ae_vector* r, + ae_state *_state); +void corrr1dcircular(/* Real */ ae_vector* signal, + ae_int_t m, + /* Real */ ae_vector* pattern, + ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +void fhtr1d(/* Real */ ae_vector* a, ae_int_t n, ae_state *_state); +void fhtr1dinv(/* Real */ ae_vector* a, ae_int_t n, ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/integration.cpp b/src/inc/alglib/integration.cpp new file mode 100644 index 0000000..f35b744 --- /dev/null +++ b/src/inc/alglib/integration.cpp @@ -0,0 +1,3961 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "integration.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Computation of nodes and weights for a Gauss quadrature formula + +The algorithm generates the N-point Gauss quadrature formula with weight +function given by coefficients alpha and beta of a recurrence relation +which generates a system of orthogonal polynomials: + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-1], alpha coefficients + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the quadrature formula, N>=1 + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgeneraterec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgeneraterec(const_cast(alpha.c_ptr()), const_cast(beta.c_ptr()), mu0, n, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Computation of nodes and weights for a Gauss-Lobatto quadrature formula + +The algorithm generates the N-point Gauss-Lobatto quadrature formula with +weight function given by coefficients alpha and beta of a recurrence which +generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients + Beta – array[0..N-2], beta coefficients. + Zero-indexed element is not used, may be arbitrary. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + B – right boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=3 + (including the left and right boundary nodes). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslobattorec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const double a, const double b, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategausslobattorec(const_cast(alpha.c_ptr()), const_cast(beta.c_ptr()), mu0, a, b, n, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Computation of nodes and weights for a Gauss-Radau quadrature formula + +The algorithm generates the N-point Gauss-Radau quadrature formula with +weight function given by the coefficients alpha and beta of a recurrence +which generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients. + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=2 + (including the left boundary node). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussradaurec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const double a, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategaussradaurec(const_cast(alpha.c_ptr()), const_cast(beta.c_ptr()), mu0, a, n, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns nodes/weights for Gauss-Legendre quadrature on [-1,1] with N +nodes. + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslegendre(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategausslegendre(n, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns nodes/weights for Gauss-Jacobi quadrature on [-1,1] with weight +function W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha/Beta was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussjacobi(const ae_int_t n, const double alpha, const double beta, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategaussjacobi(n, alpha, beta, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns nodes/weights for Gauss-Laguerre quadrature on [0,+inf) with +weight function W(x)=Power(x,Alpha)*Exp(-x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha is too close to -1 to + obtain weights/nodes with high enough accuracy + or, may be, N is too large. Try to use + multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslaguerre(const ae_int_t n, const double alpha, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategausslaguerre(n, alpha, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns nodes/weights for Gauss-Hermite quadrature on (-inf,+inf) with +weight function W(x)=Exp(-x*x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. May be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausshermite(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gqgenerategausshermite(n, &info, const_cast(x.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Computation of nodes and weights of a Gauss-Kronrod quadrature formula + +The algorithm generates the N-point Gauss-Kronrod quadrature formula with +weight function given by coefficients alpha and beta of a recurrence +relation which generates a system of orthogonal polynomials: + + P-1(x) = 0 + P0(x) = 1 + Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zero moment Mu0 + + Mu0 = integral(W(x)dx,a,b) + + +INPUT PARAMETERS: + Alpha – alpha coefficients, array[0..floor(3*K/2)]. + Beta – beta coefficients, array[0..ceil(3*K/2)]. + Beta[0] is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the Gauss-Kronrod quadrature formula, + N >= 3, + N = 2*K+1. + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 N is too large, task may be ill conditioned - + x[i]=x[i+1] found. + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 08.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgeneraterec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gkqgeneraterec(const_cast(alpha.c_ptr()), const_cast(beta.c_ptr()), mu0, n, &info, const_cast(x.c_ptr()), const_cast(wkronrod.c_ptr()), const_cast(wgauss.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Legendre +quadrature with N points. + +GKQLegendreCalc (calculation) or GKQLegendreTbl (precomputed table) is +used depending on machine precision and number of nodes. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategausslegendre(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gkqgenerategausslegendre(n, &info, const_cast(x.c_ptr()), const_cast(wkronrod.c_ptr()), const_cast(wgauss.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Jacobi +quadrature on [-1,1] with weight function + + W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + * +2 OK, but quadrature rule have exterior nodes, + x[0]<-1 or x[n-1]>+1 + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategaussjacobi(const ae_int_t n, const double alpha, const double beta, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gkqgenerategaussjacobi(n, alpha, beta, &info, const_cast(x.c_ptr()), const_cast(wkronrod.c_ptr()), const_cast(wgauss.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points. + +Reduction to tridiagonal eigenproblem is used. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendrecalc(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gkqlegendrecalc(n, &info, const_cast(x.c_ptr()), const_cast(wkronrod.c_ptr()), const_cast(wgauss.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points using +pre-calculated table. Nodes/weights were computed with accuracy up to +1.0E-32 (if MPFR version of ALGLIB is used). In standard double precision +accuracy reduces to something about 2.0E-16 (depending on your compiler's +handling of long floating point constants). + +INPUT PARAMETERS: + N - number of Kronrod nodes. + N can be 15, 21, 31, 41, 51, 61. + +OUTPUT PARAMETERS: + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendretbl(const ae_int_t n, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss, double &eps) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::gkqlegendretbl(n, const_cast(x.c_ptr()), const_cast(wkronrod.c_ptr()), const_cast(wgauss.c_ptr()), &eps, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Integration report: +* TerminationType = completetion code: + * -5 non-convergence of Gauss-Kronrod nodes + calculation subroutine. + * -1 incorrect parameters were specified + * 1 OK +* Rep.NFEV countains number of function calculations +* Rep.NIntervals contains number of intervals [a,b] + was partitioned into. +*************************************************************************/ +_autogkreport_owner::_autogkreport_owner() +{ + p_struct = (alglib_impl::autogkreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::autogkreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_autogkreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_autogkreport_owner::_autogkreport_owner(const _autogkreport_owner &rhs) +{ + p_struct = (alglib_impl::autogkreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::autogkreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_autogkreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_autogkreport_owner& _autogkreport_owner::operator=(const _autogkreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_autogkreport_clear(p_struct); + if( !alglib_impl::_autogkreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_autogkreport_owner::~_autogkreport_owner() +{ + alglib_impl::_autogkreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::autogkreport* _autogkreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::autogkreport* _autogkreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +autogkreport::autogkreport() : _autogkreport_owner() ,terminationtype(p_struct->terminationtype),nfev(p_struct->nfev),nintervals(p_struct->nintervals) +{ +} + +autogkreport::autogkreport(const autogkreport &rhs):_autogkreport_owner(rhs) ,terminationtype(p_struct->terminationtype),nfev(p_struct->nfev),nintervals(p_struct->nintervals) +{ +} + +autogkreport& autogkreport::operator=(const autogkreport &rhs) +{ + if( this==&rhs ) + return *this; + _autogkreport_owner::operator=(rhs); + return *this; +} + +autogkreport::~autogkreport() +{ +} + + +/************************************************************************* +This structure stores state of the integration algorithm. + +Although this class has public fields, they are not intended for external +use. You should use ALGLIB functions to work with this class: +* autogksmooth()/AutoGKSmoothW()/... to create objects +* autogkintegrate() to begin integration +* autogkresults() to get results +*************************************************************************/ +_autogkstate_owner::_autogkstate_owner() +{ + p_struct = (alglib_impl::autogkstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::autogkstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_autogkstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_autogkstate_owner::_autogkstate_owner(const _autogkstate_owner &rhs) +{ + p_struct = (alglib_impl::autogkstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::autogkstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_autogkstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_autogkstate_owner& _autogkstate_owner::operator=(const _autogkstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_autogkstate_clear(p_struct); + if( !alglib_impl::_autogkstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_autogkstate_owner::~_autogkstate_owner() +{ + alglib_impl::_autogkstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::autogkstate* _autogkstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::autogkstate* _autogkstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +autogkstate::autogkstate() : _autogkstate_owner() ,needf(p_struct->needf),x(p_struct->x),xminusa(p_struct->xminusa),bminusx(p_struct->bminusx),f(p_struct->f) +{ +} + +autogkstate::autogkstate(const autogkstate &rhs):_autogkstate_owner(rhs) ,needf(p_struct->needf),x(p_struct->x),xminusa(p_struct->xminusa),bminusx(p_struct->bminusx),f(p_struct->f) +{ +} + +autogkstate& autogkstate::operator=(const autogkstate &rhs) +{ + if( this==&rhs ) + return *this; + _autogkstate_owner::operator=(rhs); + return *this; +} + +autogkstate::~autogkstate() +{ +} + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +Algorithm works well only with smooth integrands. It may be used with +continuous non-smooth integrands, but with less performance. + +It should never be used with integrands which have integrable singularities +at lower or upper limits - algorithm may crash. Use AutoGKSingular in such +cases. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmooth(const double a, const double b, autogkstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::autogksmooth(a, b, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +This subroutine is same as AutoGKSmooth(), but it guarantees that interval +[a,b] is partitioned into subintervals which have width at most XWidth. + +Subroutine can be used when integrating nearly-constant function with +narrow "bumps" (about XWidth wide). If "bumps" are too narrow, AutoGKSmooth +subroutine can overlook them. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmoothw(const double a, const double b, const double xwidth, autogkstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::autogksmoothw(a, b, xwidth, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Integration on a finite interval [A,B]. +Integrand have integrable singularities at A/B. + +F(X) must diverge as "(x-A)^alpha" at A, as "(B-x)^beta" at B, with known +alpha/beta (alpha>-1, beta>-1). If alpha/beta are not known, estimates +from below can be used (but these estimates should be greater than -1 too). + +One of alpha/beta variables (or even both alpha/beta) may be equal to 0, +which means than function F(x) is non-singular at A/B. Anyway (singular at +bounds or not), function F(x) is supposed to be continuous on (A,B). + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + Alpha - power-law coefficient of the F(x) at A, + Alpha>-1 + Beta - power-law coefficient of the F(x) at B, + Beta>-1 + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSmoothW, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksingular(const double a, const double b, const double alpha, const double beta, autogkstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::autogksingular(a, b, alpha, beta, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool autogkiteration(const autogkstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::autogkiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void autogkintegrate(autogkstate &state, + void (*func)(double x, double xminusa, double bminusx, double &y, void *ptr), + void *ptr){ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'autogkintegrate()' (func is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::autogkiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.xminusa, state.bminusx, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: unexpected error in 'autogkintegrate()'"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +Adaptive integration results + +Called after AutoGKIteration returned False. + +Input parameters: + State - algorithm state (used by AutoGKIteration). + +Output parameters: + V - integral(f(x)dx,a,b) + Rep - optimization report (see AutoGKReport description) + + -- ALGLIB -- + Copyright 14.11.2007 by Bochkanov Sergey +*************************************************************************/ +void autogkresults(const autogkstate &state, double &v, autogkreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::autogkresults(const_cast(state.c_ptr()), &v, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + + + + +static ae_int_t autogk_maxsubintervals = 10000; +static void autogk_autogkinternalprepare(double a, + double b, + double eps, + double xwidth, + autogkinternalstate* state, + ae_state *_state); +static ae_bool autogk_autogkinternaliteration(autogkinternalstate* state, + ae_state *_state); +static void autogk_mheappop(/* Real */ ae_matrix* heap, + ae_int_t heapsize, + ae_int_t heapwidth, + ae_state *_state); +static void autogk_mheappush(/* Real */ ae_matrix* heap, + ae_int_t heapsize, + ae_int_t heapwidth, + ae_state *_state); +static void autogk_mheapresize(/* Real */ ae_matrix* heap, + ae_int_t* heapsize, + ae_int_t newheapsize, + ae_int_t heapwidth, + ae_state *_state); + + + + + +/************************************************************************* +Computation of nodes and weights for a Gauss quadrature formula + +The algorithm generates the N-point Gauss quadrature formula with weight +function given by coefficients alpha and beta of a recurrence relation +which generates a system of orthogonal polynomials: + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-1], alpha coefficients + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the quadrature formula, N>=1 + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgeneraterec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector d; + ae_vector e; + ae_matrix z; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true); + + if( n<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Initialize + */ + ae_vector_set_length(&d, n, _state); + ae_vector_set_length(&e, n, _state); + for(i=1; i<=n-1; i++) + { + d.ptr.p_double[i-1] = alpha->ptr.p_double[i-1]; + if( ae_fp_less_eq(beta->ptr.p_double[i],0) ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + e.ptr.p_double[i-1] = ae_sqrt(beta->ptr.p_double[i], _state); + } + d.ptr.p_double[n-1] = alpha->ptr.p_double[n-1]; + + /* + * EVD + */ + if( !smatrixtdevd(&d, &e, n, 3, &z, _state) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Generate + */ + ae_vector_set_length(x, n, _state); + ae_vector_set_length(w, n, _state); + for(i=1; i<=n; i++) + { + x->ptr.p_double[i-1] = d.ptr.p_double[i-1]; + w->ptr.p_double[i-1] = mu0*ae_sqr(z.ptr.pp_double[0][i-1], _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Computation of nodes and weights for a Gauss-Lobatto quadrature formula + +The algorithm generates the N-point Gauss-Lobatto quadrature formula with +weight function given by coefficients alpha and beta of a recurrence which +generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients + Beta – array[0..N-2], beta coefficients. + Zero-indexed element is not used, may be arbitrary. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + B – right boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=3 + (including the left and right boundary nodes). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslobattorec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + double a, + double b, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _alpha; + ae_vector _beta; + ae_int_t i; + ae_vector d; + ae_vector e; + ae_matrix z; + double pim1a; + double pia; + double pim1b; + double pib; + double t; + double a11; + double a12; + double a21; + double a22; + double b1; + double b2; + double alph; + double bet; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_alpha, alpha, _state, ae_true); + alpha = &_alpha; + ae_vector_init_copy(&_beta, beta, _state, ae_true); + beta = &_beta; + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true); + + if( n<=2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Initialize, D[1:N+1], E[1:N] + */ + n = n-2; + ae_vector_set_length(&d, n+2, _state); + ae_vector_set_length(&e, n+1, _state); + for(i=1; i<=n+1; i++) + { + d.ptr.p_double[i-1] = alpha->ptr.p_double[i-1]; + } + for(i=1; i<=n; i++) + { + if( ae_fp_less_eq(beta->ptr.p_double[i],0) ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + e.ptr.p_double[i-1] = ae_sqrt(beta->ptr.p_double[i], _state); + } + + /* + * Caclulate Pn(a), Pn+1(a), Pn(b), Pn+1(b) + */ + beta->ptr.p_double[0] = 0; + pim1a = 0; + pia = 1; + pim1b = 0; + pib = 1; + for(i=1; i<=n+1; i++) + { + + /* + * Pi(a) + */ + t = (a-alpha->ptr.p_double[i-1])*pia-beta->ptr.p_double[i-1]*pim1a; + pim1a = pia; + pia = t; + + /* + * Pi(b) + */ + t = (b-alpha->ptr.p_double[i-1])*pib-beta->ptr.p_double[i-1]*pim1b; + pim1b = pib; + pib = t; + } + + /* + * Calculate alpha'(n+1), beta'(n+1) + */ + a11 = pia; + a12 = pim1a; + a21 = pib; + a22 = pim1b; + b1 = a*pia; + b2 = b*pib; + if( ae_fp_greater(ae_fabs(a11, _state),ae_fabs(a21, _state)) ) + { + a22 = a22-a12*a21/a11; + b2 = b2-b1*a21/a11; + bet = b2/a22; + alph = (b1-bet*a12)/a11; + } + else + { + a12 = a12-a22*a11/a21; + b1 = b1-b2*a11/a21; + bet = b1/a12; + alph = (b2-bet*a22)/a21; + } + if( ae_fp_less(bet,0) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + d.ptr.p_double[n+1] = alph; + e.ptr.p_double[n] = ae_sqrt(bet, _state); + + /* + * EVD + */ + if( !smatrixtdevd(&d, &e, n+2, 3, &z, _state) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Generate + */ + ae_vector_set_length(x, n+2, _state); + ae_vector_set_length(w, n+2, _state); + for(i=1; i<=n+2; i++) + { + x->ptr.p_double[i-1] = d.ptr.p_double[i-1]; + w->ptr.p_double[i-1] = mu0*ae_sqr(z.ptr.pp_double[0][i-1], _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Computation of nodes and weights for a Gauss-Radau quadrature formula + +The algorithm generates the N-point Gauss-Radau quadrature formula with +weight function given by the coefficients alpha and beta of a recurrence +which generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients. + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=2 + (including the left boundary node). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussradaurec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + double a, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _alpha; + ae_vector _beta; + ae_int_t i; + ae_vector d; + ae_vector e; + ae_matrix z; + double polim1; + double poli; + double t; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_alpha, alpha, _state, ae_true); + alpha = &_alpha; + ae_vector_init_copy(&_beta, beta, _state, ae_true); + beta = &_beta; + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z, 0, 0, DT_REAL, _state, ae_true); + + if( n<2 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * Initialize, D[1:N], E[1:N] + */ + n = n-1; + ae_vector_set_length(&d, n+1, _state); + ae_vector_set_length(&e, n, _state); + for(i=1; i<=n; i++) + { + d.ptr.p_double[i-1] = alpha->ptr.p_double[i-1]; + if( ae_fp_less_eq(beta->ptr.p_double[i],0) ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + e.ptr.p_double[i-1] = ae_sqrt(beta->ptr.p_double[i], _state); + } + + /* + * Caclulate Pn(a), Pn-1(a), and D[N+1] + */ + beta->ptr.p_double[0] = 0; + polim1 = 0; + poli = 1; + for(i=1; i<=n; i++) + { + t = (a-alpha->ptr.p_double[i-1])*poli-beta->ptr.p_double[i-1]*polim1; + polim1 = poli; + poli = t; + } + d.ptr.p_double[n] = a-beta->ptr.p_double[n]*polim1/poli; + + /* + * EVD + */ + if( !smatrixtdevd(&d, &e, n+1, 3, &z, _state) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Generate + */ + ae_vector_set_length(x, n+1, _state); + ae_vector_set_length(w, n+1, _state); + for(i=1; i<=n+1; i++) + { + x->ptr.p_double[i-1] = d.ptr.p_double[i-1]; + w->ptr.p_double[i-1] = mu0*ae_sqr(z.ptr.pp_double[0][i-1], _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns nodes/weights for Gauss-Legendre quadrature on [-1,1] with N +nodes. + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslegendre(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector alpha; + ae_vector beta; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&alpha, 0, DT_REAL, _state, ae_true); + ae_vector_init(&beta, 0, DT_REAL, _state, ae_true); + + if( n<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&alpha, n, _state); + ae_vector_set_length(&beta, n, _state); + for(i=0; i<=n-1; i++) + { + alpha.ptr.p_double[i] = 0; + } + beta.ptr.p_double[0] = 2; + for(i=1; i<=n-1; i++) + { + beta.ptr.p_double[i] = 1/(4-1/ae_sqr(i, _state)); + } + gqgeneraterec(&alpha, &beta, beta.ptr.p_double[0], n, info, x, w, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + if( ae_fp_less(x->ptr.p_double[0],-1)||ae_fp_greater(x->ptr.p_double[n-1],1) ) + { + *info = -4; + } + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns nodes/weights for Gauss-Jacobi quadrature on [-1,1] with weight +function W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha/Beta was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussjacobi(ae_int_t n, + double alpha, + double beta, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector a; + ae_vector b; + double alpha2; + double beta2; + double apb; + double t; + ae_int_t i; + double s; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&a, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + if( (n<1||ae_fp_less_eq(alpha,-1))||ae_fp_less_eq(beta,-1) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&a, n, _state); + ae_vector_set_length(&b, n, _state); + apb = alpha+beta; + a.ptr.p_double[0] = (beta-alpha)/(apb+2); + t = (apb+1)*ae_log(2, _state)+lngamma(alpha+1, &s, _state)+lngamma(beta+1, &s, _state)-lngamma(apb+2, &s, _state); + if( ae_fp_greater(t,ae_log(ae_maxrealnumber, _state)) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + b.ptr.p_double[0] = ae_exp(t, _state); + if( n>1 ) + { + alpha2 = ae_sqr(alpha, _state); + beta2 = ae_sqr(beta, _state); + a.ptr.p_double[1] = (beta2-alpha2)/((apb+2)*(apb+4)); + b.ptr.p_double[1] = 4*(alpha+1)*(beta+1)/((apb+3)*ae_sqr(apb+2, _state)); + for(i=2; i<=n-1; i++) + { + a.ptr.p_double[i] = 0.25*(beta2-alpha2)/(i*i*(1+0.5*apb/i)*(1+0.5*(apb+2)/i)); + b.ptr.p_double[i] = 0.25*(1+alpha/i)*(1+beta/i)*(1+apb/i)/((1+0.5*(apb+1)/i)*(1+0.5*(apb-1)/i)*ae_sqr(1+0.5*apb/i, _state)); + } + } + gqgeneraterec(&a, &b, b.ptr.p_double[0], n, info, x, w, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + if( ae_fp_less(x->ptr.p_double[0],-1)||ae_fp_greater(x->ptr.p_double[n-1],1) ) + { + *info = -4; + } + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns nodes/weights for Gauss-Laguerre quadrature on [0,+inf) with +weight function W(x)=Power(x,Alpha)*Exp(-x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha is too close to -1 to + obtain weights/nodes with high enough accuracy + or, may be, N is too large. Try to use + multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslaguerre(ae_int_t n, + double alpha, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector a; + ae_vector b; + double t; + ae_int_t i; + double s; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&a, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + if( n<1||ae_fp_less_eq(alpha,-1) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&a, n, _state); + ae_vector_set_length(&b, n, _state); + a.ptr.p_double[0] = alpha+1; + t = lngamma(alpha+1, &s, _state); + if( ae_fp_greater_eq(t,ae_log(ae_maxrealnumber, _state)) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + b.ptr.p_double[0] = ae_exp(t, _state); + if( n>1 ) + { + for(i=1; i<=n-1; i++) + { + a.ptr.p_double[i] = 2*i+alpha+1; + b.ptr.p_double[i] = i*(i+alpha); + } + } + gqgeneraterec(&a, &b, b.ptr.p_double[0], n, info, x, w, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + if( ae_fp_less(x->ptr.p_double[0],0) ) + { + *info = -4; + } + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns nodes/weights for Gauss-Hermite quadrature on (-inf,+inf) with +weight function W(x)=Exp(-x*x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. May be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausshermite(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector a; + ae_vector b; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(w); + ae_vector_init(&a, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + if( n<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&a, n, _state); + ae_vector_set_length(&b, n, _state); + for(i=0; i<=n-1; i++) + { + a.ptr.p_double[i] = 0; + } + b.ptr.p_double[0] = ae_sqrt(4*ae_atan(1, _state), _state); + if( n>1 ) + { + for(i=1; i<=n-1; i++) + { + b.ptr.p_double[i] = 0.5*i; + } + } + gqgeneraterec(&a, &b, b.ptr.p_double[0], n, info, x, w, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Computation of nodes and weights of a Gauss-Kronrod quadrature formula + +The algorithm generates the N-point Gauss-Kronrod quadrature formula with +weight function given by coefficients alpha and beta of a recurrence +relation which generates a system of orthogonal polynomials: + + P-1(x) = 0 + P0(x) = 1 + Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zero moment Mu0 + + Mu0 = integral(W(x)dx,a,b) + + +INPUT PARAMETERS: + Alpha – alpha coefficients, array[0..floor(3*K/2)]. + Beta – beta coefficients, array[0..ceil(3*K/2)]. + Beta[0] is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the Gauss-Kronrod quadrature formula, + N >= 3, + N = 2*K+1. + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 N is too large, task may be ill conditioned - + x[i]=x[i+1] found. + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 08.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgeneraterec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _alpha; + ae_vector _beta; + ae_vector ta; + ae_int_t i; + ae_int_t j; + ae_vector t; + ae_vector s; + ae_int_t wlen; + ae_int_t woffs; + double u; + ae_int_t m; + ae_int_t l; + ae_int_t k; + ae_vector xgtmp; + ae_vector wgtmp; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_alpha, alpha, _state, ae_true); + alpha = &_alpha; + ae_vector_init_copy(&_beta, beta, _state, ae_true); + beta = &_beta; + *info = 0; + ae_vector_clear(x); + ae_vector_clear(wkronrod); + ae_vector_clear(wgauss); + ae_vector_init(&ta, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xgtmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wgtmp, 0, DT_REAL, _state, ae_true); + + if( n%2!=1||n<3 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=ae_iceil((double)(3*(n/2))/(double)2, _state); i++) + { + if( ae_fp_less_eq(beta->ptr.p_double[i],0) ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + } + *info = 1; + + /* + * from external conventions about N/Beta/Mu0 to internal + */ + n = n/2; + beta->ptr.p_double[0] = mu0; + + /* + * Calculate Gauss nodes/weights, save them for later processing + */ + gqgeneraterec(alpha, beta, mu0, n, info, &xgtmp, &wgtmp, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Resize: + * * A from 0..floor(3*n/2) to 0..2*n + * * B from 0..ceil(3*n/2) to 0..2*n + */ + ae_vector_set_length(&ta, ae_ifloor((double)(3*n)/(double)2, _state)+1, _state); + ae_v_move(&ta.ptr.p_double[0], 1, &alpha->ptr.p_double[0], 1, ae_v_len(0,ae_ifloor((double)(3*n)/(double)2, _state))); + ae_vector_set_length(alpha, 2*n+1, _state); + ae_v_move(&alpha->ptr.p_double[0], 1, &ta.ptr.p_double[0], 1, ae_v_len(0,ae_ifloor((double)(3*n)/(double)2, _state))); + for(i=ae_ifloor((double)(3*n)/(double)2, _state)+1; i<=2*n; i++) + { + alpha->ptr.p_double[i] = 0; + } + ae_vector_set_length(&ta, ae_iceil((double)(3*n)/(double)2, _state)+1, _state); + ae_v_move(&ta.ptr.p_double[0], 1, &beta->ptr.p_double[0], 1, ae_v_len(0,ae_iceil((double)(3*n)/(double)2, _state))); + ae_vector_set_length(beta, 2*n+1, _state); + ae_v_move(&beta->ptr.p_double[0], 1, &ta.ptr.p_double[0], 1, ae_v_len(0,ae_iceil((double)(3*n)/(double)2, _state))); + for(i=ae_iceil((double)(3*n)/(double)2, _state)+1; i<=2*n; i++) + { + beta->ptr.p_double[i] = 0; + } + + /* + * Initialize T, S + */ + wlen = 2+n/2; + ae_vector_set_length(&t, wlen, _state); + ae_vector_set_length(&s, wlen, _state); + ae_vector_set_length(&ta, wlen, _state); + woffs = 1; + for(i=0; i<=wlen-1; i++) + { + t.ptr.p_double[i] = 0; + s.ptr.p_double[i] = 0; + } + + /* + * Algorithm from Dirk P. Laurie, "Calculation of Gauss-Kronrod quadrature rules", 1997. + */ + t.ptr.p_double[woffs+0] = beta->ptr.p_double[n+1]; + for(m=0; m<=n-2; m++) + { + u = 0; + for(k=(m+1)/2; k>=0; k--) + { + l = m-k; + u = u+(alpha->ptr.p_double[k+n+1]-alpha->ptr.p_double[l])*t.ptr.p_double[woffs+k]+beta->ptr.p_double[k+n+1]*s.ptr.p_double[woffs+k-1]-beta->ptr.p_double[l]*s.ptr.p_double[woffs+k]; + s.ptr.p_double[woffs+k] = u; + } + ae_v_move(&ta.ptr.p_double[0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + ae_v_move(&t.ptr.p_double[0], 1, &s.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + ae_v_move(&s.ptr.p_double[0], 1, &ta.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + } + for(j=n/2; j>=0; j--) + { + s.ptr.p_double[woffs+j] = s.ptr.p_double[woffs+j-1]; + } + for(m=n-1; m<=2*n-3; m++) + { + u = 0; + for(k=m+1-n; k<=(m-1)/2; k++) + { + l = m-k; + j = n-1-l; + u = u-(alpha->ptr.p_double[k+n+1]-alpha->ptr.p_double[l])*t.ptr.p_double[woffs+j]-beta->ptr.p_double[k+n+1]*s.ptr.p_double[woffs+j]+beta->ptr.p_double[l]*s.ptr.p_double[woffs+j+1]; + s.ptr.p_double[woffs+j] = u; + } + if( m%2==0 ) + { + k = m/2; + alpha->ptr.p_double[k+n+1] = alpha->ptr.p_double[k]+(s.ptr.p_double[woffs+j]-beta->ptr.p_double[k+n+1]*s.ptr.p_double[woffs+j+1])/t.ptr.p_double[woffs+j+1]; + } + else + { + k = (m+1)/2; + beta->ptr.p_double[k+n+1] = s.ptr.p_double[woffs+j]/s.ptr.p_double[woffs+j+1]; + } + ae_v_move(&ta.ptr.p_double[0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + ae_v_move(&t.ptr.p_double[0], 1, &s.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + ae_v_move(&s.ptr.p_double[0], 1, &ta.ptr.p_double[0], 1, ae_v_len(0,wlen-1)); + } + alpha->ptr.p_double[2*n] = alpha->ptr.p_double[n-1]-beta->ptr.p_double[2*n]*s.ptr.p_double[woffs+0]/t.ptr.p_double[woffs+0]; + + /* + * calculation of Kronrod nodes and weights, unpacking of Gauss weights + */ + gqgeneraterec(alpha, beta, mu0, 2*n+1, info, x, wkronrod, _state); + if( *info==-2 ) + { + *info = -5; + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + for(i=0; i<=2*n-1; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + ae_vector_set_length(wgauss, 2*n+1, _state); + for(i=0; i<=2*n; i++) + { + wgauss->ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + wgauss->ptr.p_double[2*i+1] = wgtmp.ptr.p_double[i]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Legendre +quadrature with N points. + +GKQLegendreCalc (calculation) or GKQLegendreTbl (precomputed table) is +used depending on machine precision and number of nodes. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategausslegendre(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state) +{ + double eps; + + *info = 0; + ae_vector_clear(x); + ae_vector_clear(wkronrod); + ae_vector_clear(wgauss); + + if( ae_fp_greater(ae_machineepsilon,1.0E-32)&&(((((n==15||n==21)||n==31)||n==41)||n==51)||n==61) ) + { + *info = 1; + gkqlegendretbl(n, x, wkronrod, wgauss, &eps, _state); + } + else + { + gkqlegendrecalc(n, info, x, wkronrod, wgauss, _state); + } +} + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Jacobi +quadrature on [-1,1] with weight function + + W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + * +2 OK, but quadrature rule have exterior nodes, + x[0]<-1 or x[n-1]>+1 + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategaussjacobi(ae_int_t n, + double alpha, + double beta, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t clen; + ae_vector a; + ae_vector b; + double alpha2; + double beta2; + double apb; + double t; + ae_int_t i; + double s; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(wkronrod); + ae_vector_clear(wgauss); + ae_vector_init(&a, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + + if( n%2!=1||n<3 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( ae_fp_less_eq(alpha,-1)||ae_fp_less_eq(beta,-1) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + clen = ae_iceil((double)(3*(n/2))/(double)2, _state)+1; + ae_vector_set_length(&a, clen, _state); + ae_vector_set_length(&b, clen, _state); + for(i=0; i<=clen-1; i++) + { + a.ptr.p_double[i] = 0; + } + apb = alpha+beta; + a.ptr.p_double[0] = (beta-alpha)/(apb+2); + t = (apb+1)*ae_log(2, _state)+lngamma(alpha+1, &s, _state)+lngamma(beta+1, &s, _state)-lngamma(apb+2, &s, _state); + if( ae_fp_greater(t,ae_log(ae_maxrealnumber, _state)) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + b.ptr.p_double[0] = ae_exp(t, _state); + if( clen>1 ) + { + alpha2 = ae_sqr(alpha, _state); + beta2 = ae_sqr(beta, _state); + a.ptr.p_double[1] = (beta2-alpha2)/((apb+2)*(apb+4)); + b.ptr.p_double[1] = 4*(alpha+1)*(beta+1)/((apb+3)*ae_sqr(apb+2, _state)); + for(i=2; i<=clen-1; i++) + { + a.ptr.p_double[i] = 0.25*(beta2-alpha2)/(i*i*(1+0.5*apb/i)*(1+0.5*(apb+2)/i)); + b.ptr.p_double[i] = 0.25*(1+alpha/i)*(1+beta/i)*(1+apb/i)/((1+0.5*(apb+1)/i)*(1+0.5*(apb-1)/i)*ae_sqr(1+0.5*apb/i, _state)); + } + } + gkqgeneraterec(&a, &b, b.ptr.p_double[0], n, info, x, wkronrod, wgauss, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + if( ae_fp_less(x->ptr.p_double[0],-1)||ae_fp_greater(x->ptr.p_double[n-1],1) ) + { + *info = 2; + } + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points. + +Reduction to tridiagonal eigenproblem is used. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendrecalc(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector alpha; + ae_vector beta; + ae_int_t alen; + ae_int_t blen; + double mu0; + ae_int_t k; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(x); + ae_vector_clear(wkronrod); + ae_vector_clear(wgauss); + ae_vector_init(&alpha, 0, DT_REAL, _state, ae_true); + ae_vector_init(&beta, 0, DT_REAL, _state, ae_true); + + if( n%2!=1||n<3 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + mu0 = 2; + alen = ae_ifloor((double)(3*(n/2))/(double)2, _state)+1; + blen = ae_iceil((double)(3*(n/2))/(double)2, _state)+1; + ae_vector_set_length(&alpha, alen, _state); + ae_vector_set_length(&beta, blen, _state); + for(k=0; k<=alen-1; k++) + { + alpha.ptr.p_double[k] = 0; + } + beta.ptr.p_double[0] = 2; + for(k=1; k<=blen-1; k++) + { + beta.ptr.p_double[k] = 1/(4-1/ae_sqr(k, _state)); + } + gkqgeneraterec(&alpha, &beta, mu0, n, info, x, wkronrod, wgauss, _state); + + /* + * test basic properties to detect errors + */ + if( *info>0 ) + { + if( ae_fp_less(x->ptr.p_double[0],-1)||ae_fp_greater(x->ptr.p_double[n-1],1) ) + { + *info = -4; + } + for(i=0; i<=n-2; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],x->ptr.p_double[i+1]) ) + { + *info = -4; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points using +pre-calculated table. Nodes/weights were computed with accuracy up to +1.0E-32 (if MPFR version of ALGLIB is used). In standard double precision +accuracy reduces to something about 2.0E-16 (depending on your compiler's +handling of long floating point constants). + +INPUT PARAMETERS: + N - number of Kronrod nodes. + N can be 15, 21, 31, 41, 51, 61. + +OUTPUT PARAMETERS: + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendretbl(ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + double* eps, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t ng; + ae_vector p1; + ae_vector p2; + double tmp; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(x); + ae_vector_clear(wkronrod); + ae_vector_clear(wgauss); + *eps = 0; + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + ng = 0; + + /* + * Process + */ + ae_assert(((((n==15||n==21)||n==31)||n==41)||n==51)||n==61, "GKQNodesTbl: incorrect N!", _state); + ae_vector_set_length(x, n, _state); + ae_vector_set_length(wkronrod, n, _state); + ae_vector_set_length(wgauss, n, _state); + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = 0; + wkronrod->ptr.p_double[i] = 0; + wgauss->ptr.p_double[i] = 0; + } + *eps = ae_maxreal(ae_machineepsilon, 1.0E-32, _state); + if( n==15 ) + { + ng = 4; + wgauss->ptr.p_double[0] = 0.129484966168869693270611432679082; + wgauss->ptr.p_double[1] = 0.279705391489276667901467771423780; + wgauss->ptr.p_double[2] = 0.381830050505118944950369775488975; + wgauss->ptr.p_double[3] = 0.417959183673469387755102040816327; + x->ptr.p_double[0] = 0.991455371120812639206854697526329; + x->ptr.p_double[1] = 0.949107912342758524526189684047851; + x->ptr.p_double[2] = 0.864864423359769072789712788640926; + x->ptr.p_double[3] = 0.741531185599394439863864773280788; + x->ptr.p_double[4] = 0.586087235467691130294144838258730; + x->ptr.p_double[5] = 0.405845151377397166906606412076961; + x->ptr.p_double[6] = 0.207784955007898467600689403773245; + x->ptr.p_double[7] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.022935322010529224963732008058970; + wkronrod->ptr.p_double[1] = 0.063092092629978553290700663189204; + wkronrod->ptr.p_double[2] = 0.104790010322250183839876322541518; + wkronrod->ptr.p_double[3] = 0.140653259715525918745189590510238; + wkronrod->ptr.p_double[4] = 0.169004726639267902826583426598550; + wkronrod->ptr.p_double[5] = 0.190350578064785409913256402421014; + wkronrod->ptr.p_double[6] = 0.204432940075298892414161999234649; + wkronrod->ptr.p_double[7] = 0.209482141084727828012999174891714; + } + if( n==21 ) + { + ng = 5; + wgauss->ptr.p_double[0] = 0.066671344308688137593568809893332; + wgauss->ptr.p_double[1] = 0.149451349150580593145776339657697; + wgauss->ptr.p_double[2] = 0.219086362515982043995534934228163; + wgauss->ptr.p_double[3] = 0.269266719309996355091226921569469; + wgauss->ptr.p_double[4] = 0.295524224714752870173892994651338; + x->ptr.p_double[0] = 0.995657163025808080735527280689003; + x->ptr.p_double[1] = 0.973906528517171720077964012084452; + x->ptr.p_double[2] = 0.930157491355708226001207180059508; + x->ptr.p_double[3] = 0.865063366688984510732096688423493; + x->ptr.p_double[4] = 0.780817726586416897063717578345042; + x->ptr.p_double[5] = 0.679409568299024406234327365114874; + x->ptr.p_double[6] = 0.562757134668604683339000099272694; + x->ptr.p_double[7] = 0.433395394129247190799265943165784; + x->ptr.p_double[8] = 0.294392862701460198131126603103866; + x->ptr.p_double[9] = 0.148874338981631210884826001129720; + x->ptr.p_double[10] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.011694638867371874278064396062192; + wkronrod->ptr.p_double[1] = 0.032558162307964727478818972459390; + wkronrod->ptr.p_double[2] = 0.054755896574351996031381300244580; + wkronrod->ptr.p_double[3] = 0.075039674810919952767043140916190; + wkronrod->ptr.p_double[4] = 0.093125454583697605535065465083366; + wkronrod->ptr.p_double[5] = 0.109387158802297641899210590325805; + wkronrod->ptr.p_double[6] = 0.123491976262065851077958109831074; + wkronrod->ptr.p_double[7] = 0.134709217311473325928054001771707; + wkronrod->ptr.p_double[8] = 0.142775938577060080797094273138717; + wkronrod->ptr.p_double[9] = 0.147739104901338491374841515972068; + wkronrod->ptr.p_double[10] = 0.149445554002916905664936468389821; + } + if( n==31 ) + { + ng = 8; + wgauss->ptr.p_double[0] = 0.030753241996117268354628393577204; + wgauss->ptr.p_double[1] = 0.070366047488108124709267416450667; + wgauss->ptr.p_double[2] = 0.107159220467171935011869546685869; + wgauss->ptr.p_double[3] = 0.139570677926154314447804794511028; + wgauss->ptr.p_double[4] = 0.166269205816993933553200860481209; + wgauss->ptr.p_double[5] = 0.186161000015562211026800561866423; + wgauss->ptr.p_double[6] = 0.198431485327111576456118326443839; + wgauss->ptr.p_double[7] = 0.202578241925561272880620199967519; + x->ptr.p_double[0] = 0.998002298693397060285172840152271; + x->ptr.p_double[1] = 0.987992518020485428489565718586613; + x->ptr.p_double[2] = 0.967739075679139134257347978784337; + x->ptr.p_double[3] = 0.937273392400705904307758947710209; + x->ptr.p_double[4] = 0.897264532344081900882509656454496; + x->ptr.p_double[5] = 0.848206583410427216200648320774217; + x->ptr.p_double[6] = 0.790418501442465932967649294817947; + x->ptr.p_double[7] = 0.724417731360170047416186054613938; + x->ptr.p_double[8] = 0.650996741297416970533735895313275; + x->ptr.p_double[9] = 0.570972172608538847537226737253911; + x->ptr.p_double[10] = 0.485081863640239680693655740232351; + x->ptr.p_double[11] = 0.394151347077563369897207370981045; + x->ptr.p_double[12] = 0.299180007153168812166780024266389; + x->ptr.p_double[13] = 0.201194093997434522300628303394596; + x->ptr.p_double[14] = 0.101142066918717499027074231447392; + x->ptr.p_double[15] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.005377479872923348987792051430128; + wkronrod->ptr.p_double[1] = 0.015007947329316122538374763075807; + wkronrod->ptr.p_double[2] = 0.025460847326715320186874001019653; + wkronrod->ptr.p_double[3] = 0.035346360791375846222037948478360; + wkronrod->ptr.p_double[4] = 0.044589751324764876608227299373280; + wkronrod->ptr.p_double[5] = 0.053481524690928087265343147239430; + wkronrod->ptr.p_double[6] = 0.062009567800670640285139230960803; + wkronrod->ptr.p_double[7] = 0.069854121318728258709520077099147; + wkronrod->ptr.p_double[8] = 0.076849680757720378894432777482659; + wkronrod->ptr.p_double[9] = 0.083080502823133021038289247286104; + wkronrod->ptr.p_double[10] = 0.088564443056211770647275443693774; + wkronrod->ptr.p_double[11] = 0.093126598170825321225486872747346; + wkronrod->ptr.p_double[12] = 0.096642726983623678505179907627589; + wkronrod->ptr.p_double[13] = 0.099173598721791959332393173484603; + wkronrod->ptr.p_double[14] = 0.100769845523875595044946662617570; + wkronrod->ptr.p_double[15] = 0.101330007014791549017374792767493; + } + if( n==41 ) + { + ng = 10; + wgauss->ptr.p_double[0] = 0.017614007139152118311861962351853; + wgauss->ptr.p_double[1] = 0.040601429800386941331039952274932; + wgauss->ptr.p_double[2] = 0.062672048334109063569506535187042; + wgauss->ptr.p_double[3] = 0.083276741576704748724758143222046; + wgauss->ptr.p_double[4] = 0.101930119817240435036750135480350; + wgauss->ptr.p_double[5] = 0.118194531961518417312377377711382; + wgauss->ptr.p_double[6] = 0.131688638449176626898494499748163; + wgauss->ptr.p_double[7] = 0.142096109318382051329298325067165; + wgauss->ptr.p_double[8] = 0.149172986472603746787828737001969; + wgauss->ptr.p_double[9] = 0.152753387130725850698084331955098; + x->ptr.p_double[0] = 0.998859031588277663838315576545863; + x->ptr.p_double[1] = 0.993128599185094924786122388471320; + x->ptr.p_double[2] = 0.981507877450250259193342994720217; + x->ptr.p_double[3] = 0.963971927277913791267666131197277; + x->ptr.p_double[4] = 0.940822633831754753519982722212443; + x->ptr.p_double[5] = 0.912234428251325905867752441203298; + x->ptr.p_double[6] = 0.878276811252281976077442995113078; + x->ptr.p_double[7] = 0.839116971822218823394529061701521; + x->ptr.p_double[8] = 0.795041428837551198350638833272788; + x->ptr.p_double[9] = 0.746331906460150792614305070355642; + x->ptr.p_double[10] = 0.693237656334751384805490711845932; + x->ptr.p_double[11] = 0.636053680726515025452836696226286; + x->ptr.p_double[12] = 0.575140446819710315342946036586425; + x->ptr.p_double[13] = 0.510867001950827098004364050955251; + x->ptr.p_double[14] = 0.443593175238725103199992213492640; + x->ptr.p_double[15] = 0.373706088715419560672548177024927; + x->ptr.p_double[16] = 0.301627868114913004320555356858592; + x->ptr.p_double[17] = 0.227785851141645078080496195368575; + x->ptr.p_double[18] = 0.152605465240922675505220241022678; + x->ptr.p_double[19] = 0.076526521133497333754640409398838; + x->ptr.p_double[20] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.003073583718520531501218293246031; + wkronrod->ptr.p_double[1] = 0.008600269855642942198661787950102; + wkronrod->ptr.p_double[2] = 0.014626169256971252983787960308868; + wkronrod->ptr.p_double[3] = 0.020388373461266523598010231432755; + wkronrod->ptr.p_double[4] = 0.025882133604951158834505067096153; + wkronrod->ptr.p_double[5] = 0.031287306777032798958543119323801; + wkronrod->ptr.p_double[6] = 0.036600169758200798030557240707211; + wkronrod->ptr.p_double[7] = 0.041668873327973686263788305936895; + wkronrod->ptr.p_double[8] = 0.046434821867497674720231880926108; + wkronrod->ptr.p_double[9] = 0.050944573923728691932707670050345; + wkronrod->ptr.p_double[10] = 0.055195105348285994744832372419777; + wkronrod->ptr.p_double[11] = 0.059111400880639572374967220648594; + wkronrod->ptr.p_double[12] = 0.062653237554781168025870122174255; + wkronrod->ptr.p_double[13] = 0.065834597133618422111563556969398; + wkronrod->ptr.p_double[14] = 0.068648672928521619345623411885368; + wkronrod->ptr.p_double[15] = 0.071054423553444068305790361723210; + wkronrod->ptr.p_double[16] = 0.073030690332786667495189417658913; + wkronrod->ptr.p_double[17] = 0.074582875400499188986581418362488; + wkronrod->ptr.p_double[18] = 0.075704497684556674659542775376617; + wkronrod->ptr.p_double[19] = 0.076377867672080736705502835038061; + wkronrod->ptr.p_double[20] = 0.076600711917999656445049901530102; + } + if( n==51 ) + { + ng = 13; + wgauss->ptr.p_double[0] = 0.011393798501026287947902964113235; + wgauss->ptr.p_double[1] = 0.026354986615032137261901815295299; + wgauss->ptr.p_double[2] = 0.040939156701306312655623487711646; + wgauss->ptr.p_double[3] = 0.054904695975835191925936891540473; + wgauss->ptr.p_double[4] = 0.068038333812356917207187185656708; + wgauss->ptr.p_double[5] = 0.080140700335001018013234959669111; + wgauss->ptr.p_double[6] = 0.091028261982963649811497220702892; + wgauss->ptr.p_double[7] = 0.100535949067050644202206890392686; + wgauss->ptr.p_double[8] = 0.108519624474263653116093957050117; + wgauss->ptr.p_double[9] = 0.114858259145711648339325545869556; + wgauss->ptr.p_double[10] = 0.119455763535784772228178126512901; + wgauss->ptr.p_double[11] = 0.122242442990310041688959518945852; + wgauss->ptr.p_double[12] = 0.123176053726715451203902873079050; + x->ptr.p_double[0] = 0.999262104992609834193457486540341; + x->ptr.p_double[1] = 0.995556969790498097908784946893902; + x->ptr.p_double[2] = 0.988035794534077247637331014577406; + x->ptr.p_double[3] = 0.976663921459517511498315386479594; + x->ptr.p_double[4] = 0.961614986425842512418130033660167; + x->ptr.p_double[5] = 0.942974571228974339414011169658471; + x->ptr.p_double[6] = 0.920747115281701561746346084546331; + x->ptr.p_double[7] = 0.894991997878275368851042006782805; + x->ptr.p_double[8] = 0.865847065293275595448996969588340; + x->ptr.p_double[9] = 0.833442628760834001421021108693570; + x->ptr.p_double[10] = 0.797873797998500059410410904994307; + x->ptr.p_double[11] = 0.759259263037357630577282865204361; + x->ptr.p_double[12] = 0.717766406813084388186654079773298; + x->ptr.p_double[13] = 0.673566368473468364485120633247622; + x->ptr.p_double[14] = 0.626810099010317412788122681624518; + x->ptr.p_double[15] = 0.577662930241222967723689841612654; + x->ptr.p_double[16] = 0.526325284334719182599623778158010; + x->ptr.p_double[17] = 0.473002731445714960522182115009192; + x->ptr.p_double[18] = 0.417885382193037748851814394594572; + x->ptr.p_double[19] = 0.361172305809387837735821730127641; + x->ptr.p_double[20] = 0.303089538931107830167478909980339; + x->ptr.p_double[21] = 0.243866883720988432045190362797452; + x->ptr.p_double[22] = 0.183718939421048892015969888759528; + x->ptr.p_double[23] = 0.122864692610710396387359818808037; + x->ptr.p_double[24] = 0.061544483005685078886546392366797; + x->ptr.p_double[25] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.001987383892330315926507851882843; + wkronrod->ptr.p_double[1] = 0.005561932135356713758040236901066; + wkronrod->ptr.p_double[2] = 0.009473973386174151607207710523655; + wkronrod->ptr.p_double[3] = 0.013236229195571674813656405846976; + wkronrod->ptr.p_double[4] = 0.016847817709128298231516667536336; + wkronrod->ptr.p_double[5] = 0.020435371145882835456568292235939; + wkronrod->ptr.p_double[6] = 0.024009945606953216220092489164881; + wkronrod->ptr.p_double[7] = 0.027475317587851737802948455517811; + wkronrod->ptr.p_double[8] = 0.030792300167387488891109020215229; + wkronrod->ptr.p_double[9] = 0.034002130274329337836748795229551; + wkronrod->ptr.p_double[10] = 0.037116271483415543560330625367620; + wkronrod->ptr.p_double[11] = 0.040083825504032382074839284467076; + wkronrod->ptr.p_double[12] = 0.042872845020170049476895792439495; + wkronrod->ptr.p_double[13] = 0.045502913049921788909870584752660; + wkronrod->ptr.p_double[14] = 0.047982537138836713906392255756915; + wkronrod->ptr.p_double[15] = 0.050277679080715671963325259433440; + wkronrod->ptr.p_double[16] = 0.052362885806407475864366712137873; + wkronrod->ptr.p_double[17] = 0.054251129888545490144543370459876; + wkronrod->ptr.p_double[18] = 0.055950811220412317308240686382747; + wkronrod->ptr.p_double[19] = 0.057437116361567832853582693939506; + wkronrod->ptr.p_double[20] = 0.058689680022394207961974175856788; + wkronrod->ptr.p_double[21] = 0.059720340324174059979099291932562; + wkronrod->ptr.p_double[22] = 0.060539455376045862945360267517565; + wkronrod->ptr.p_double[23] = 0.061128509717053048305859030416293; + wkronrod->ptr.p_double[24] = 0.061471189871425316661544131965264; + wkronrod->ptr.p_double[25] = 0.061580818067832935078759824240055; + } + if( n==61 ) + { + ng = 15; + wgauss->ptr.p_double[0] = 0.007968192496166605615465883474674; + wgauss->ptr.p_double[1] = 0.018466468311090959142302131912047; + wgauss->ptr.p_double[2] = 0.028784707883323369349719179611292; + wgauss->ptr.p_double[3] = 0.038799192569627049596801936446348; + wgauss->ptr.p_double[4] = 0.048402672830594052902938140422808; + wgauss->ptr.p_double[5] = 0.057493156217619066481721689402056; + wgauss->ptr.p_double[6] = 0.065974229882180495128128515115962; + wgauss->ptr.p_double[7] = 0.073755974737705206268243850022191; + wgauss->ptr.p_double[8] = 0.080755895229420215354694938460530; + wgauss->ptr.p_double[9] = 0.086899787201082979802387530715126; + wgauss->ptr.p_double[10] = 0.092122522237786128717632707087619; + wgauss->ptr.p_double[11] = 0.096368737174644259639468626351810; + wgauss->ptr.p_double[12] = 0.099593420586795267062780282103569; + wgauss->ptr.p_double[13] = 0.101762389748405504596428952168554; + wgauss->ptr.p_double[14] = 0.102852652893558840341285636705415; + x->ptr.p_double[0] = 0.999484410050490637571325895705811; + x->ptr.p_double[1] = 0.996893484074649540271630050918695; + x->ptr.p_double[2] = 0.991630996870404594858628366109486; + x->ptr.p_double[3] = 0.983668123279747209970032581605663; + x->ptr.p_double[4] = 0.973116322501126268374693868423707; + x->ptr.p_double[5] = 0.960021864968307512216871025581798; + x->ptr.p_double[6] = 0.944374444748559979415831324037439; + x->ptr.p_double[7] = 0.926200047429274325879324277080474; + x->ptr.p_double[8] = 0.905573307699907798546522558925958; + x->ptr.p_double[9] = 0.882560535792052681543116462530226; + x->ptr.p_double[10] = 0.857205233546061098958658510658944; + x->ptr.p_double[11] = 0.829565762382768397442898119732502; + x->ptr.p_double[12] = 0.799727835821839083013668942322683; + x->ptr.p_double[13] = 0.767777432104826194917977340974503; + x->ptr.p_double[14] = 0.733790062453226804726171131369528; + x->ptr.p_double[15] = 0.697850494793315796932292388026640; + x->ptr.p_double[16] = 0.660061064126626961370053668149271; + x->ptr.p_double[17] = 0.620526182989242861140477556431189; + x->ptr.p_double[18] = 0.579345235826361691756024932172540; + x->ptr.p_double[19] = 0.536624148142019899264169793311073; + x->ptr.p_double[20] = 0.492480467861778574993693061207709; + x->ptr.p_double[21] = 0.447033769538089176780609900322854; + x->ptr.p_double[22] = 0.400401254830394392535476211542661; + x->ptr.p_double[23] = 0.352704725530878113471037207089374; + x->ptr.p_double[24] = 0.304073202273625077372677107199257; + x->ptr.p_double[25] = 0.254636926167889846439805129817805; + x->ptr.p_double[26] = 0.204525116682309891438957671002025; + x->ptr.p_double[27] = 0.153869913608583546963794672743256; + x->ptr.p_double[28] = 0.102806937966737030147096751318001; + x->ptr.p_double[29] = 0.051471842555317695833025213166723; + x->ptr.p_double[30] = 0.000000000000000000000000000000000; + wkronrod->ptr.p_double[0] = 0.001389013698677007624551591226760; + wkronrod->ptr.p_double[1] = 0.003890461127099884051267201844516; + wkronrod->ptr.p_double[2] = 0.006630703915931292173319826369750; + wkronrod->ptr.p_double[3] = 0.009273279659517763428441146892024; + wkronrod->ptr.p_double[4] = 0.011823015253496341742232898853251; + wkronrod->ptr.p_double[5] = 0.014369729507045804812451432443580; + wkronrod->ptr.p_double[6] = 0.016920889189053272627572289420322; + wkronrod->ptr.p_double[7] = 0.019414141193942381173408951050128; + wkronrod->ptr.p_double[8] = 0.021828035821609192297167485738339; + wkronrod->ptr.p_double[9] = 0.024191162078080601365686370725232; + wkronrod->ptr.p_double[10] = 0.026509954882333101610601709335075; + wkronrod->ptr.p_double[11] = 0.028754048765041292843978785354334; + wkronrod->ptr.p_double[12] = 0.030907257562387762472884252943092; + wkronrod->ptr.p_double[13] = 0.032981447057483726031814191016854; + wkronrod->ptr.p_double[14] = 0.034979338028060024137499670731468; + wkronrod->ptr.p_double[15] = 0.036882364651821229223911065617136; + wkronrod->ptr.p_double[16] = 0.038678945624727592950348651532281; + wkronrod->ptr.p_double[17] = 0.040374538951535959111995279752468; + wkronrod->ptr.p_double[18] = 0.041969810215164246147147541285970; + wkronrod->ptr.p_double[19] = 0.043452539701356069316831728117073; + wkronrod->ptr.p_double[20] = 0.044814800133162663192355551616723; + wkronrod->ptr.p_double[21] = 0.046059238271006988116271735559374; + wkronrod->ptr.p_double[22] = 0.047185546569299153945261478181099; + wkronrod->ptr.p_double[23] = 0.048185861757087129140779492298305; + wkronrod->ptr.p_double[24] = 0.049055434555029778887528165367238; + wkronrod->ptr.p_double[25] = 0.049795683427074206357811569379942; + wkronrod->ptr.p_double[26] = 0.050405921402782346840893085653585; + wkronrod->ptr.p_double[27] = 0.050881795898749606492297473049805; + wkronrod->ptr.p_double[28] = 0.051221547849258772170656282604944; + wkronrod->ptr.p_double[29] = 0.051426128537459025933862879215781; + wkronrod->ptr.p_double[30] = 0.051494729429451567558340433647099; + } + + /* + * copy nodes + */ + for(i=n-1; i>=n/2; i--) + { + x->ptr.p_double[i] = -x->ptr.p_double[n-1-i]; + } + + /* + * copy Kronrod weights + */ + for(i=n-1; i>=n/2; i--) + { + wkronrod->ptr.p_double[i] = wkronrod->ptr.p_double[n-1-i]; + } + + /* + * copy Gauss weights + */ + for(i=ng-1; i>=0; i--) + { + wgauss->ptr.p_double[n-2-2*i] = wgauss->ptr.p_double[i]; + wgauss->ptr.p_double[1+2*i] = wgauss->ptr.p_double[i]; + } + for(i=0; i<=n/2; i++) + { + wgauss->ptr.p_double[2*i] = 0; + } + + /* + * reorder + */ + tagsort(x, n, &p1, &p2, _state); + for(i=0; i<=n-1; i++) + { + tmp = wkronrod->ptr.p_double[i]; + wkronrod->ptr.p_double[i] = wkronrod->ptr.p_double[p2.ptr.p_int[i]]; + wkronrod->ptr.p_double[p2.ptr.p_int[i]] = tmp; + tmp = wgauss->ptr.p_double[i]; + wgauss->ptr.p_double[i] = wgauss->ptr.p_double[p2.ptr.p_int[i]]; + wgauss->ptr.p_double[p2.ptr.p_int[i]] = tmp; + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +Algorithm works well only with smooth integrands. It may be used with +continuous non-smooth integrands, but with less performance. + +It should never be used with integrands which have integrable singularities +at lower or upper limits - algorithm may crash. Use AutoGKSingular in such +cases. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmooth(double a, + double b, + autogkstate* state, + ae_state *_state) +{ + + _autogkstate_clear(state); + + ae_assert(ae_isfinite(a, _state), "AutoGKSmooth: A is not finite!", _state); + ae_assert(ae_isfinite(b, _state), "AutoGKSmooth: B is not finite!", _state); + autogksmoothw(a, b, 0.0, state, _state); +} + + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +This subroutine is same as AutoGKSmooth(), but it guarantees that interval +[a,b] is partitioned into subintervals which have width at most XWidth. + +Subroutine can be used when integrating nearly-constant function with +narrow "bumps" (about XWidth wide). If "bumps" are too narrow, AutoGKSmooth +subroutine can overlook them. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmoothw(double a, + double b, + double xwidth, + autogkstate* state, + ae_state *_state) +{ + + _autogkstate_clear(state); + + ae_assert(ae_isfinite(a, _state), "AutoGKSmoothW: A is not finite!", _state); + ae_assert(ae_isfinite(b, _state), "AutoGKSmoothW: B is not finite!", _state); + ae_assert(ae_isfinite(xwidth, _state), "AutoGKSmoothW: XWidth is not finite!", _state); + state->wrappermode = 0; + state->a = a; + state->b = b; + state->xwidth = xwidth; + state->needf = ae_false; + ae_vector_set_length(&state->rstate.ra, 10+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +Integration on a finite interval [A,B]. +Integrand have integrable singularities at A/B. + +F(X) must diverge as "(x-A)^alpha" at A, as "(B-x)^beta" at B, with known +alpha/beta (alpha>-1, beta>-1). If alpha/beta are not known, estimates +from below can be used (but these estimates should be greater than -1 too). + +One of alpha/beta variables (or even both alpha/beta) may be equal to 0, +which means than function F(x) is non-singular at A/B. Anyway (singular at +bounds or not), function F(x) is supposed to be continuous on (A,B). + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + Alpha - power-law coefficient of the F(x) at A, + Alpha>-1 + Beta - power-law coefficient of the F(x) at B, + Beta>-1 + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSmoothW, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksingular(double a, + double b, + double alpha, + double beta, + autogkstate* state, + ae_state *_state) +{ + + _autogkstate_clear(state); + + ae_assert(ae_isfinite(a, _state), "AutoGKSingular: A is not finite!", _state); + ae_assert(ae_isfinite(b, _state), "AutoGKSingular: B is not finite!", _state); + ae_assert(ae_isfinite(alpha, _state), "AutoGKSingular: Alpha is not finite!", _state); + ae_assert(ae_isfinite(beta, _state), "AutoGKSingular: Beta is not finite!", _state); + state->wrappermode = 1; + state->a = a; + state->b = b; + state->alpha = alpha; + state->beta = beta; + state->xwidth = 0.0; + state->needf = ae_false; + ae_vector_set_length(&state->rstate.ra, 10+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 07.05.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool autogkiteration(autogkstate* state, ae_state *_state) +{ + double s; + double tmp; + double eps; + double a; + double b; + double x; + double t; + double alpha; + double beta; + double v1; + double v2; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + s = state->rstate.ra.ptr.p_double[0]; + tmp = state->rstate.ra.ptr.p_double[1]; + eps = state->rstate.ra.ptr.p_double[2]; + a = state->rstate.ra.ptr.p_double[3]; + b = state->rstate.ra.ptr.p_double[4]; + x = state->rstate.ra.ptr.p_double[5]; + t = state->rstate.ra.ptr.p_double[6]; + alpha = state->rstate.ra.ptr.p_double[7]; + beta = state->rstate.ra.ptr.p_double[8]; + v1 = state->rstate.ra.ptr.p_double[9]; + v2 = state->rstate.ra.ptr.p_double[10]; + } + else + { + s = -983; + tmp = -989; + eps = -834; + a = 900; + b = -287; + x = 364; + t = 214; + alpha = -338; + beta = -686; + v1 = 912; + v2 = 585; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + + /* + * Routine body + */ + eps = 0; + a = state->a; + b = state->b; + alpha = state->alpha; + beta = state->beta; + state->terminationtype = -1; + state->nfev = 0; + state->nintervals = 0; + + /* + * smooth function at a finite interval + */ + if( state->wrappermode!=0 ) + { + goto lbl_3; + } + + /* + * special case + */ + if( ae_fp_eq(a,b) ) + { + state->terminationtype = 1; + state->v = 0; + result = ae_false; + return result; + } + + /* + * general case + */ + autogk_autogkinternalprepare(a, b, eps, state->xwidth, &state->internalstate, _state); +lbl_5: + if( !autogk_autogkinternaliteration(&state->internalstate, _state) ) + { + goto lbl_6; + } + x = state->internalstate.x; + state->x = x; + state->xminusa = x-a; + state->bminusx = b-x; + state->needf = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needf = ae_false; + state->nfev = state->nfev+1; + state->internalstate.f = state->f; + goto lbl_5; +lbl_6: + state->v = state->internalstate.r; + state->terminationtype = state->internalstate.info; + state->nintervals = state->internalstate.heapused; + result = ae_false; + return result; +lbl_3: + + /* + * function with power-law singularities at the ends of a finite interval + */ + if( state->wrappermode!=1 ) + { + goto lbl_7; + } + + /* + * test coefficients + */ + if( ae_fp_less_eq(alpha,-1)||ae_fp_less_eq(beta,-1) ) + { + state->terminationtype = -1; + state->v = 0; + result = ae_false; + return result; + } + + /* + * special cases + */ + if( ae_fp_eq(a,b) ) + { + state->terminationtype = 1; + state->v = 0; + result = ae_false; + return result; + } + + /* + * reduction to general form + */ + if( ae_fp_less(a,b) ) + { + s = 1; + } + else + { + s = -1; + tmp = a; + a = b; + b = tmp; + tmp = alpha; + alpha = beta; + beta = tmp; + } + alpha = ae_minreal(alpha, 0, _state); + beta = ae_minreal(beta, 0, _state); + + /* + * first, integrate left half of [a,b]: + * integral(f(x)dx, a, (b+a)/2) = + * = 1/(1+alpha) * integral(t^(-alpha/(1+alpha))*f(a+t^(1/(1+alpha)))dt, 0, (0.5*(b-a))^(1+alpha)) + */ + autogk_autogkinternalprepare(0, ae_pow(0.5*(b-a), 1+alpha, _state), eps, state->xwidth, &state->internalstate, _state); +lbl_9: + if( !autogk_autogkinternaliteration(&state->internalstate, _state) ) + { + goto lbl_10; + } + + /* + * Fill State.X, State.XMinusA, State.BMinusX. + * Latter two are filled correctly even if Binternalstate.x; + t = ae_pow(x, 1/(1+alpha), _state); + state->x = a+t; + if( ae_fp_greater(s,0) ) + { + state->xminusa = t; + state->bminusx = b-(a+t); + } + else + { + state->xminusa = a+t-b; + state->bminusx = -t; + } + state->needf = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->needf = ae_false; + if( ae_fp_neq(alpha,0) ) + { + state->internalstate.f = state->f*ae_pow(x, -alpha/(1+alpha), _state)/(1+alpha); + } + else + { + state->internalstate.f = state->f; + } + state->nfev = state->nfev+1; + goto lbl_9; +lbl_10: + v1 = state->internalstate.r; + state->nintervals = state->nintervals+state->internalstate.heapused; + + /* + * then, integrate right half of [a,b]: + * integral(f(x)dx, (b+a)/2, b) = + * = 1/(1+beta) * integral(t^(-beta/(1+beta))*f(b-t^(1/(1+beta)))dt, 0, (0.5*(b-a))^(1+beta)) + */ + autogk_autogkinternalprepare(0, ae_pow(0.5*(b-a), 1+beta, _state), eps, state->xwidth, &state->internalstate, _state); +lbl_11: + if( !autogk_autogkinternaliteration(&state->internalstate, _state) ) + { + goto lbl_12; + } + + /* + * Fill State.X, State.XMinusA, State.BMinusX. + * Latter two are filled correctly (X-A, B-X) even if Binternalstate.x; + t = ae_pow(x, 1/(1+beta), _state); + state->x = b-t; + if( ae_fp_greater(s,0) ) + { + state->xminusa = b-t-a; + state->bminusx = t; + } + else + { + state->xminusa = -t; + state->bminusx = a-(b-t); + } + state->needf = ae_true; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->needf = ae_false; + if( ae_fp_neq(beta,0) ) + { + state->internalstate.f = state->f*ae_pow(x, -beta/(1+beta), _state)/(1+beta); + } + else + { + state->internalstate.f = state->f; + } + state->nfev = state->nfev+1; + goto lbl_11; +lbl_12: + v2 = state->internalstate.r; + state->nintervals = state->nintervals+state->internalstate.heapused; + + /* + * final result + */ + state->v = s*(v1+v2); + state->terminationtype = 1; + result = ae_false; + return result; +lbl_7: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ra.ptr.p_double[0] = s; + state->rstate.ra.ptr.p_double[1] = tmp; + state->rstate.ra.ptr.p_double[2] = eps; + state->rstate.ra.ptr.p_double[3] = a; + state->rstate.ra.ptr.p_double[4] = b; + state->rstate.ra.ptr.p_double[5] = x; + state->rstate.ra.ptr.p_double[6] = t; + state->rstate.ra.ptr.p_double[7] = alpha; + state->rstate.ra.ptr.p_double[8] = beta; + state->rstate.ra.ptr.p_double[9] = v1; + state->rstate.ra.ptr.p_double[10] = v2; + return result; +} + + +/************************************************************************* +Adaptive integration results + +Called after AutoGKIteration returned False. + +Input parameters: + State - algorithm state (used by AutoGKIteration). + +Output parameters: + V - integral(f(x)dx,a,b) + Rep - optimization report (see AutoGKReport description) + + -- ALGLIB -- + Copyright 14.11.2007 by Bochkanov Sergey +*************************************************************************/ +void autogkresults(autogkstate* state, + double* v, + autogkreport* rep, + ae_state *_state) +{ + + *v = 0; + _autogkreport_clear(rep); + + *v = state->v; + rep->terminationtype = state->terminationtype; + rep->nfev = state->nfev; + rep->nintervals = state->nintervals; +} + + +/************************************************************************* +Internal AutoGK subroutine +eps<0 - error +eps=0 - automatic eps selection + +width<0 - error +width=0 - no width requirements +*************************************************************************/ +static void autogk_autogkinternalprepare(double a, + double b, + double eps, + double xwidth, + autogkinternalstate* state, + ae_state *_state) +{ + + + + /* + * Save settings + */ + state->a = a; + state->b = b; + state->eps = eps; + state->xwidth = xwidth; + + /* + * Prepare RComm structure + */ + ae_vector_set_length(&state->rstate.ia, 3+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +Internal AutoGK subroutine +*************************************************************************/ +static ae_bool autogk_autogkinternaliteration(autogkinternalstate* state, + ae_state *_state) +{ + double c1; + double c2; + ae_int_t i; + ae_int_t j; + double intg; + double intk; + double inta; + double v; + double ta; + double tb; + ae_int_t ns; + double qeps; + ae_int_t info; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + i = state->rstate.ia.ptr.p_int[0]; + j = state->rstate.ia.ptr.p_int[1]; + ns = state->rstate.ia.ptr.p_int[2]; + info = state->rstate.ia.ptr.p_int[3]; + c1 = state->rstate.ra.ptr.p_double[0]; + c2 = state->rstate.ra.ptr.p_double[1]; + intg = state->rstate.ra.ptr.p_double[2]; + intk = state->rstate.ra.ptr.p_double[3]; + inta = state->rstate.ra.ptr.p_double[4]; + v = state->rstate.ra.ptr.p_double[5]; + ta = state->rstate.ra.ptr.p_double[6]; + tb = state->rstate.ra.ptr.p_double[7]; + qeps = state->rstate.ra.ptr.p_double[8]; + } + else + { + i = 497; + j = -271; + ns = -581; + info = 745; + c1 = -533; + c2 = -77; + intg = 678; + intk = -293; + inta = 316; + v = 647; + ta = -756; + tb = 830; + qeps = -871; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + + /* + * Routine body + */ + + /* + * initialize quadratures. + * use 15-point Gauss-Kronrod formula. + */ + state->n = 15; + gkqgenerategausslegendre(state->n, &info, &state->qn, &state->wk, &state->wg, _state); + if( info<0 ) + { + state->info = -5; + state->r = 0; + result = ae_false; + return result; + } + ae_vector_set_length(&state->wr, state->n, _state); + for(i=0; i<=state->n-1; i++) + { + if( i==0 ) + { + state->wr.ptr.p_double[i] = 0.5*ae_fabs(state->qn.ptr.p_double[1]-state->qn.ptr.p_double[0], _state); + continue; + } + if( i==state->n-1 ) + { + state->wr.ptr.p_double[state->n-1] = 0.5*ae_fabs(state->qn.ptr.p_double[state->n-1]-state->qn.ptr.p_double[state->n-2], _state); + continue; + } + state->wr.ptr.p_double[i] = 0.5*ae_fabs(state->qn.ptr.p_double[i-1]-state->qn.ptr.p_double[i+1], _state); + } + + /* + * special case + */ + if( ae_fp_eq(state->a,state->b) ) + { + state->info = 1; + state->r = 0; + result = ae_false; + return result; + } + + /* + * test parameters + */ + if( ae_fp_less(state->eps,0)||ae_fp_less(state->xwidth,0) ) + { + state->info = -1; + state->r = 0; + result = ae_false; + return result; + } + state->info = 1; + if( ae_fp_eq(state->eps,0) ) + { + state->eps = 100000*ae_machineepsilon; + } + + /* + * First, prepare heap + * * column 0 - absolute error + * * column 1 - integral of a F(x) (calculated using Kronrod extension nodes) + * * column 2 - integral of a |F(x)| (calculated using modified rect. method) + * * column 3 - left boundary of a subinterval + * * column 4 - right boundary of a subinterval + */ + if( ae_fp_neq(state->xwidth,0) ) + { + goto lbl_3; + } + + /* + * no maximum width requirements + * start from one big subinterval + */ + state->heapwidth = 5; + state->heapsize = 1; + state->heapused = 1; + ae_matrix_set_length(&state->heap, state->heapsize, state->heapwidth, _state); + c1 = 0.5*(state->b-state->a); + c2 = 0.5*(state->b+state->a); + intg = 0; + intk = 0; + inta = 0; + i = 0; +lbl_5: + if( i>state->n-1 ) + { + goto lbl_7; + } + + /* + * obtain F + */ + state->x = c1*state->qn.ptr.p_double[i]+c2; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + v = state->f; + + /* + * Gauss-Kronrod formula + */ + intk = intk+v*state->wk.ptr.p_double[i]; + if( i%2==1 ) + { + intg = intg+v*state->wg.ptr.p_double[i]; + } + + /* + * Integral |F(x)| + * Use rectangles method + */ + inta = inta+ae_fabs(v, _state)*state->wr.ptr.p_double[i]; + i = i+1; + goto lbl_5; +lbl_7: + intk = intk*(state->b-state->a)*0.5; + intg = intg*(state->b-state->a)*0.5; + inta = inta*(state->b-state->a)*0.5; + state->heap.ptr.pp_double[0][0] = ae_fabs(intg-intk, _state); + state->heap.ptr.pp_double[0][1] = intk; + state->heap.ptr.pp_double[0][2] = inta; + state->heap.ptr.pp_double[0][3] = state->a; + state->heap.ptr.pp_double[0][4] = state->b; + state->sumerr = state->heap.ptr.pp_double[0][0]; + state->sumabs = ae_fabs(inta, _state); + goto lbl_4; +lbl_3: + + /* + * maximum subinterval should be no more than XWidth. + * so we create Ceil((B-A)/XWidth)+1 small subintervals + */ + ns = ae_iceil(ae_fabs(state->b-state->a, _state)/state->xwidth, _state)+1; + state->heapsize = ns; + state->heapused = ns; + state->heapwidth = 5; + ae_matrix_set_length(&state->heap, state->heapsize, state->heapwidth, _state); + state->sumerr = 0; + state->sumabs = 0; + j = 0; +lbl_8: + if( j>ns-1 ) + { + goto lbl_10; + } + ta = state->a+j*(state->b-state->a)/ns; + tb = state->a+(j+1)*(state->b-state->a)/ns; + c1 = 0.5*(tb-ta); + c2 = 0.5*(tb+ta); + intg = 0; + intk = 0; + inta = 0; + i = 0; +lbl_11: + if( i>state->n-1 ) + { + goto lbl_13; + } + + /* + * obtain F + */ + state->x = c1*state->qn.ptr.p_double[i]+c2; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + v = state->f; + + /* + * Gauss-Kronrod formula + */ + intk = intk+v*state->wk.ptr.p_double[i]; + if( i%2==1 ) + { + intg = intg+v*state->wg.ptr.p_double[i]; + } + + /* + * Integral |F(x)| + * Use rectangles method + */ + inta = inta+ae_fabs(v, _state)*state->wr.ptr.p_double[i]; + i = i+1; + goto lbl_11; +lbl_13: + intk = intk*(tb-ta)*0.5; + intg = intg*(tb-ta)*0.5; + inta = inta*(tb-ta)*0.5; + state->heap.ptr.pp_double[j][0] = ae_fabs(intg-intk, _state); + state->heap.ptr.pp_double[j][1] = intk; + state->heap.ptr.pp_double[j][2] = inta; + state->heap.ptr.pp_double[j][3] = ta; + state->heap.ptr.pp_double[j][4] = tb; + state->sumerr = state->sumerr+state->heap.ptr.pp_double[j][0]; + state->sumabs = state->sumabs+ae_fabs(inta, _state); + j = j+1; + goto lbl_8; +lbl_10: +lbl_4: + + /* + * method iterations + */ +lbl_14: + if( ae_false ) + { + goto lbl_15; + } + + /* + * additional memory if needed + */ + if( state->heapused==state->heapsize ) + { + autogk_mheapresize(&state->heap, &state->heapsize, 4*state->heapsize, state->heapwidth, _state); + } + + /* + * TODO: every 20 iterations recalculate errors/sums + */ + if( ae_fp_less_eq(state->sumerr,state->eps*state->sumabs)||state->heapused>=autogk_maxsubintervals ) + { + state->r = 0; + for(j=0; j<=state->heapused-1; j++) + { + state->r = state->r+state->heap.ptr.pp_double[j][1]; + } + result = ae_false; + return result; + } + + /* + * Exclude interval with maximum absolute error + */ + autogk_mheappop(&state->heap, state->heapused, state->heapwidth, _state); + state->sumerr = state->sumerr-state->heap.ptr.pp_double[state->heapused-1][0]; + state->sumabs = state->sumabs-state->heap.ptr.pp_double[state->heapused-1][2]; + + /* + * Divide interval, create subintervals + */ + ta = state->heap.ptr.pp_double[state->heapused-1][3]; + tb = state->heap.ptr.pp_double[state->heapused-1][4]; + state->heap.ptr.pp_double[state->heapused-1][3] = ta; + state->heap.ptr.pp_double[state->heapused-1][4] = 0.5*(ta+tb); + state->heap.ptr.pp_double[state->heapused][3] = 0.5*(ta+tb); + state->heap.ptr.pp_double[state->heapused][4] = tb; + j = state->heapused-1; +lbl_16: + if( j>state->heapused ) + { + goto lbl_18; + } + c1 = 0.5*(state->heap.ptr.pp_double[j][4]-state->heap.ptr.pp_double[j][3]); + c2 = 0.5*(state->heap.ptr.pp_double[j][4]+state->heap.ptr.pp_double[j][3]); + intg = 0; + intk = 0; + inta = 0; + i = 0; +lbl_19: + if( i>state->n-1 ) + { + goto lbl_21; + } + + /* + * F(x) + */ + state->x = c1*state->qn.ptr.p_double[i]+c2; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + v = state->f; + + /* + * Gauss-Kronrod formula + */ + intk = intk+v*state->wk.ptr.p_double[i]; + if( i%2==1 ) + { + intg = intg+v*state->wg.ptr.p_double[i]; + } + + /* + * Integral |F(x)| + * Use rectangles method + */ + inta = inta+ae_fabs(v, _state)*state->wr.ptr.p_double[i]; + i = i+1; + goto lbl_19; +lbl_21: + intk = intk*(state->heap.ptr.pp_double[j][4]-state->heap.ptr.pp_double[j][3])*0.5; + intg = intg*(state->heap.ptr.pp_double[j][4]-state->heap.ptr.pp_double[j][3])*0.5; + inta = inta*(state->heap.ptr.pp_double[j][4]-state->heap.ptr.pp_double[j][3])*0.5; + state->heap.ptr.pp_double[j][0] = ae_fabs(intg-intk, _state); + state->heap.ptr.pp_double[j][1] = intk; + state->heap.ptr.pp_double[j][2] = inta; + state->sumerr = state->sumerr+state->heap.ptr.pp_double[j][0]; + state->sumabs = state->sumabs+state->heap.ptr.pp_double[j][2]; + j = j+1; + goto lbl_16; +lbl_18: + autogk_mheappush(&state->heap, state->heapused-1, state->heapwidth, _state); + autogk_mheappush(&state->heap, state->heapused, state->heapwidth, _state); + state->heapused = state->heapused+1; + goto lbl_14; +lbl_15: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = i; + state->rstate.ia.ptr.p_int[1] = j; + state->rstate.ia.ptr.p_int[2] = ns; + state->rstate.ia.ptr.p_int[3] = info; + state->rstate.ra.ptr.p_double[0] = c1; + state->rstate.ra.ptr.p_double[1] = c2; + state->rstate.ra.ptr.p_double[2] = intg; + state->rstate.ra.ptr.p_double[3] = intk; + state->rstate.ra.ptr.p_double[4] = inta; + state->rstate.ra.ptr.p_double[5] = v; + state->rstate.ra.ptr.p_double[6] = ta; + state->rstate.ra.ptr.p_double[7] = tb; + state->rstate.ra.ptr.p_double[8] = qeps; + return result; +} + + +static void autogk_mheappop(/* Real */ ae_matrix* heap, + ae_int_t heapsize, + ae_int_t heapwidth, + ae_state *_state) +{ + ae_int_t i; + ae_int_t p; + double t; + ae_int_t maxcp; + + + if( heapsize==1 ) + { + return; + } + for(i=0; i<=heapwidth-1; i++) + { + t = heap->ptr.pp_double[heapsize-1][i]; + heap->ptr.pp_double[heapsize-1][i] = heap->ptr.pp_double[0][i]; + heap->ptr.pp_double[0][i] = t; + } + p = 0; + while(2*p+1ptr.pp_double[2*p+2][0],heap->ptr.pp_double[2*p+1][0]) ) + { + maxcp = 2*p+2; + } + } + if( ae_fp_less(heap->ptr.pp_double[p][0],heap->ptr.pp_double[maxcp][0]) ) + { + for(i=0; i<=heapwidth-1; i++) + { + t = heap->ptr.pp_double[p][i]; + heap->ptr.pp_double[p][i] = heap->ptr.pp_double[maxcp][i]; + heap->ptr.pp_double[maxcp][i] = t; + } + p = maxcp; + } + else + { + break; + } + } +} + + +static void autogk_mheappush(/* Real */ ae_matrix* heap, + ae_int_t heapsize, + ae_int_t heapwidth, + ae_state *_state) +{ + ae_int_t i; + ae_int_t p; + double t; + ae_int_t parent; + + + if( heapsize==0 ) + { + return; + } + p = heapsize; + while(p!=0) + { + parent = (p-1)/2; + if( ae_fp_greater(heap->ptr.pp_double[p][0],heap->ptr.pp_double[parent][0]) ) + { + for(i=0; i<=heapwidth-1; i++) + { + t = heap->ptr.pp_double[p][i]; + heap->ptr.pp_double[p][i] = heap->ptr.pp_double[parent][i]; + heap->ptr.pp_double[parent][i] = t; + } + p = parent; + } + else + { + break; + } + } +} + + +static void autogk_mheapresize(/* Real */ ae_matrix* heap, + ae_int_t* heapsize, + ae_int_t newheapsize, + ae_int_t heapwidth, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix tmp; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init(&tmp, 0, 0, DT_REAL, _state, ae_true); + + ae_matrix_set_length(&tmp, *heapsize, heapwidth, _state); + for(i=0; i<=*heapsize-1; i++) + { + ae_v_move(&tmp.ptr.pp_double[i][0], 1, &heap->ptr.pp_double[i][0], 1, ae_v_len(0,heapwidth-1)); + } + ae_matrix_set_length(heap, newheapsize, heapwidth, _state); + for(i=0; i<=*heapsize-1; i++) + { + ae_v_move(&heap->ptr.pp_double[i][0], 1, &tmp.ptr.pp_double[i][0], 1, ae_v_len(0,heapwidth-1)); + } + *heapsize = newheapsize; + ae_frame_leave(_state); +} + + +ae_bool _autogkreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + autogkreport *p = (autogkreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _autogkreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + autogkreport *dst = (autogkreport*)_dst; + autogkreport *src = (autogkreport*)_src; + dst->terminationtype = src->terminationtype; + dst->nfev = src->nfev; + dst->nintervals = src->nintervals; + return ae_true; +} + + +void _autogkreport_clear(void* _p) +{ + autogkreport *p = (autogkreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _autogkreport_destroy(void* _p) +{ + autogkreport *p = (autogkreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _autogkinternalstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + autogkinternalstate *p = (autogkinternalstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->heap, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->qn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wr, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _autogkinternalstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + autogkinternalstate *dst = (autogkinternalstate*)_dst; + autogkinternalstate *src = (autogkinternalstate*)_src; + dst->a = src->a; + dst->b = src->b; + dst->eps = src->eps; + dst->xwidth = src->xwidth; + dst->x = src->x; + dst->f = src->f; + dst->info = src->info; + dst->r = src->r; + if( !ae_matrix_init_copy(&dst->heap, &src->heap, _state, make_automatic) ) + return ae_false; + dst->heapsize = src->heapsize; + dst->heapwidth = src->heapwidth; + dst->heapused = src->heapused; + dst->sumerr = src->sumerr; + dst->sumabs = src->sumabs; + if( !ae_vector_init_copy(&dst->qn, &src->qn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wg, &src->wg, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wk, &src->wk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wr, &src->wr, _state, make_automatic) ) + return ae_false; + dst->n = src->n; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _autogkinternalstate_clear(void* _p) +{ + autogkinternalstate *p = (autogkinternalstate*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->heap); + ae_vector_clear(&p->qn); + ae_vector_clear(&p->wg); + ae_vector_clear(&p->wk); + ae_vector_clear(&p->wr); + _rcommstate_clear(&p->rstate); +} + + +void _autogkinternalstate_destroy(void* _p) +{ + autogkinternalstate *p = (autogkinternalstate*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->heap); + ae_vector_destroy(&p->qn); + ae_vector_destroy(&p->wg); + ae_vector_destroy(&p->wk); + ae_vector_destroy(&p->wr); + _rcommstate_destroy(&p->rstate); +} + + +ae_bool _autogkstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + autogkstate *p = (autogkstate*)_p; + ae_touch_ptr((void*)p); + if( !_autogkinternalstate_init(&p->internalstate, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _autogkstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + autogkstate *dst = (autogkstate*)_dst; + autogkstate *src = (autogkstate*)_src; + dst->a = src->a; + dst->b = src->b; + dst->alpha = src->alpha; + dst->beta = src->beta; + dst->xwidth = src->xwidth; + dst->x = src->x; + dst->xminusa = src->xminusa; + dst->bminusx = src->bminusx; + dst->needf = src->needf; + dst->f = src->f; + dst->wrappermode = src->wrappermode; + if( !_autogkinternalstate_init_copy(&dst->internalstate, &src->internalstate, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->v = src->v; + dst->terminationtype = src->terminationtype; + dst->nfev = src->nfev; + dst->nintervals = src->nintervals; + return ae_true; +} + + +void _autogkstate_clear(void* _p) +{ + autogkstate *p = (autogkstate*)_p; + ae_touch_ptr((void*)p); + _autogkinternalstate_clear(&p->internalstate); + _rcommstate_clear(&p->rstate); +} + + +void _autogkstate_destroy(void* _p) +{ + autogkstate *p = (autogkstate*)_p; + ae_touch_ptr((void*)p); + _autogkinternalstate_destroy(&p->internalstate); + _rcommstate_destroy(&p->rstate); +} + + + +} + diff --git a/src/inc/alglib/integration.h b/src/inc/alglib/integration.h new file mode 100644 index 0000000..b0f25c3 --- /dev/null +++ b/src/inc/alglib/integration.h @@ -0,0 +1,837 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _integration_pkg_h +#define _integration_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "linalg.h" +#include "specialfunctions.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_int_t terminationtype; + ae_int_t nfev; + ae_int_t nintervals; +} autogkreport; +typedef struct +{ + double a; + double b; + double eps; + double xwidth; + double x; + double f; + ae_int_t info; + double r; + ae_matrix heap; + ae_int_t heapsize; + ae_int_t heapwidth; + ae_int_t heapused; + double sumerr; + double sumabs; + ae_vector qn; + ae_vector wg; + ae_vector wk; + ae_vector wr; + ae_int_t n; + rcommstate rstate; +} autogkinternalstate; +typedef struct +{ + double a; + double b; + double alpha; + double beta; + double xwidth; + double x; + double xminusa; + double bminusx; + ae_bool needf; + double f; + ae_int_t wrappermode; + autogkinternalstate internalstate; + rcommstate rstate; + double v; + ae_int_t terminationtype; + ae_int_t nfev; + ae_int_t nintervals; +} autogkstate; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + + + + +/************************************************************************* +Integration report: +* TerminationType = completetion code: + * -5 non-convergence of Gauss-Kronrod nodes + calculation subroutine. + * -1 incorrect parameters were specified + * 1 OK +* Rep.NFEV countains number of function calculations +* Rep.NIntervals contains number of intervals [a,b] + was partitioned into. +*************************************************************************/ +class _autogkreport_owner +{ +public: + _autogkreport_owner(); + _autogkreport_owner(const _autogkreport_owner &rhs); + _autogkreport_owner& operator=(const _autogkreport_owner &rhs); + virtual ~_autogkreport_owner(); + alglib_impl::autogkreport* c_ptr(); + alglib_impl::autogkreport* c_ptr() const; +protected: + alglib_impl::autogkreport *p_struct; +}; +class autogkreport : public _autogkreport_owner +{ +public: + autogkreport(); + autogkreport(const autogkreport &rhs); + autogkreport& operator=(const autogkreport &rhs); + virtual ~autogkreport(); + ae_int_t &terminationtype; + ae_int_t &nfev; + ae_int_t &nintervals; + +}; + + +/************************************************************************* +This structure stores state of the integration algorithm. + +Although this class has public fields, they are not intended for external +use. You should use ALGLIB functions to work with this class: +* autogksmooth()/AutoGKSmoothW()/... to create objects +* autogkintegrate() to begin integration +* autogkresults() to get results +*************************************************************************/ +class _autogkstate_owner +{ +public: + _autogkstate_owner(); + _autogkstate_owner(const _autogkstate_owner &rhs); + _autogkstate_owner& operator=(const _autogkstate_owner &rhs); + virtual ~_autogkstate_owner(); + alglib_impl::autogkstate* c_ptr(); + alglib_impl::autogkstate* c_ptr() const; +protected: + alglib_impl::autogkstate *p_struct; +}; +class autogkstate : public _autogkstate_owner +{ +public: + autogkstate(); + autogkstate(const autogkstate &rhs); + autogkstate& operator=(const autogkstate &rhs); + virtual ~autogkstate(); + ae_bool &needf; + double &x; + double &xminusa; + double &bminusx; + double &f; + +}; + +/************************************************************************* +Computation of nodes and weights for a Gauss quadrature formula + +The algorithm generates the N-point Gauss quadrature formula with weight +function given by coefficients alpha and beta of a recurrence relation +which generates a system of orthogonal polynomials: + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-1], alpha coefficients + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the quadrature formula, N>=1 + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgeneraterec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Computation of nodes and weights for a Gauss-Lobatto quadrature formula + +The algorithm generates the N-point Gauss-Lobatto quadrature formula with +weight function given by coefficients alpha and beta of a recurrence which +generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients + Beta – array[0..N-2], beta coefficients. + Zero-indexed element is not used, may be arbitrary. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + B – right boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=3 + (including the left and right boundary nodes). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslobattorec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const double a, const double b, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Computation of nodes and weights for a Gauss-Radau quadrature formula + +The algorithm generates the N-point Gauss-Radau quadrature formula with +weight function given by the coefficients alpha and beta of a recurrence +which generates a system of orthogonal polynomials. + +P-1(x) = 0 +P0(x) = 1 +Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zeroth moment Mu0 + +Mu0 = integral(W(x)dx,a,b) + +INPUT PARAMETERS: + Alpha – array[0..N-2], alpha coefficients. + Beta – array[0..N-1], beta coefficients + Zero-indexed element is not used. + Beta[I]>0 + Mu0 – zeroth moment of the weighting function. + A – left boundary of the integration interval. + N – number of nodes of the quadrature formula, N>=2 + (including the left boundary node). + +OUTPUT PARAMETERS: + Info - error code: + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * 1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 2005-2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussradaurec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const double a, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Returns nodes/weights for Gauss-Legendre quadrature on [-1,1] with N +nodes. + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslegendre(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Returns nodes/weights for Gauss-Jacobi quadrature on [-1,1] with weight +function W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha/Beta was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategaussjacobi(const ae_int_t n, const double alpha, const double beta, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Returns nodes/weights for Gauss-Laguerre quadrature on [0,+inf) with +weight function W(x)=Power(x,Alpha)*Exp(-x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + Alpha - power-law coefficient, Alpha>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. Alpha is too close to -1 to + obtain weights/nodes with high enough accuracy + or, may be, N is too large. Try to use + multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausslaguerre(const ae_int_t n, const double alpha, ae_int_t &info, real_1d_array &x, real_1d_array &w); + + +/************************************************************************* +Returns nodes/weights for Gauss-Hermite quadrature on (-inf,+inf) with +weight function W(x)=Exp(-x*x) + +INPUT PARAMETERS: + N - number of nodes, >=1 + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. May be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N/Alpha was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + W - array[0..N-1] - array of quadrature weights. + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gqgenerategausshermite(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &w); + +/************************************************************************* +Computation of nodes and weights of a Gauss-Kronrod quadrature formula + +The algorithm generates the N-point Gauss-Kronrod quadrature formula with +weight function given by coefficients alpha and beta of a recurrence +relation which generates a system of orthogonal polynomials: + + P-1(x) = 0 + P0(x) = 1 + Pn+1(x) = (x-alpha(n))*Pn(x) - beta(n)*Pn-1(x) + +and zero moment Mu0 + + Mu0 = integral(W(x)dx,a,b) + + +INPUT PARAMETERS: + Alpha – alpha coefficients, array[0..floor(3*K/2)]. + Beta – beta coefficients, array[0..ceil(3*K/2)]. + Beta[0] is not used and may be arbitrary. + Beta[I]>0. + Mu0 – zeroth moment of the weight function. + N – number of nodes of the Gauss-Kronrod quadrature formula, + N >= 3, + N = 2*K+1. + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 N is too large, task may be ill conditioned - + x[i]=x[i+1] found. + * -3 internal eigenproblem solver hasn't converged + * -2 Beta[i]<=0 + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, + in ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 08.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgeneraterec(const real_1d_array &alpha, const real_1d_array &beta, const double mu0, const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss); + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Legendre +quadrature with N points. + +GKQLegendreCalc (calculation) or GKQLegendreTbl (precomputed table) is +used depending on machine precision and number of nodes. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategausslegendre(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss); + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes/weights for Gauss-Jacobi +quadrature on [-1,1] with weight function + + W(x)=Power(1-x,Alpha)*Power(1+x,Beta). + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + Alpha - power-law coefficient, Alpha>-1 + Beta - power-law coefficient, Beta>-1 + +OUTPUT PARAMETERS: + Info - error code: + * -5 no real and positive Gauss-Kronrod formula can + be created for such a weight function with a + given number of nodes. + * -4 an error was detected when calculating + weights/nodes. Alpha or Beta are too close + to -1 to obtain weights/nodes with high enough + accuracy, or, may be, N is too large. Try to + use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + * +2 OK, but quadrature rule have exterior nodes, + x[0]<-1 or x[n-1]>+1 + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqgenerategaussjacobi(const ae_int_t n, const double alpha, const double beta, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss); + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points. + +Reduction to tridiagonal eigenproblem is used. + +INPUT PARAMETERS: + N - number of Kronrod nodes, must be odd number, >=3. + +OUTPUT PARAMETERS: + Info - error code: + * -4 an error was detected when calculating + weights/nodes. N is too large to obtain + weights/nodes with high enough accuracy. + Try to use multiple precision version. + * -3 internal eigenproblem solver hasn't converged + * -1 incorrect N was passed + * +1 OK + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendrecalc(const ae_int_t n, ae_int_t &info, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss); + + +/************************************************************************* +Returns Gauss and Gauss-Kronrod nodes for quadrature with N points using +pre-calculated table. Nodes/weights were computed with accuracy up to +1.0E-32 (if MPFR version of ALGLIB is used). In standard double precision +accuracy reduces to something about 2.0E-16 (depending on your compiler's +handling of long floating point constants). + +INPUT PARAMETERS: + N - number of Kronrod nodes. + N can be 15, 21, 31, 41, 51, 61. + +OUTPUT PARAMETERS: + X - array[0..N-1] - array of quadrature nodes, ordered in + ascending order. + WKronrod - array[0..N-1] - Kronrod weights + WGauss - array[0..N-1] - Gauss weights (interleaved with zeros + corresponding to extended Kronrod nodes). + + + -- ALGLIB -- + Copyright 12.05.2009 by Bochkanov Sergey +*************************************************************************/ +void gkqlegendretbl(const ae_int_t n, real_1d_array &x, real_1d_array &wkronrod, real_1d_array &wgauss, double &eps); + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +Algorithm works well only with smooth integrands. It may be used with +continuous non-smooth integrands, but with less performance. + +It should never be used with integrands which have integrable singularities +at lower or upper limits - algorithm may crash. Use AutoGKSingular in such +cases. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmoothW, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmooth(const double a, const double b, autogkstate &state); + + +/************************************************************************* +Integration of a smooth function F(x) on a finite interval [a,b]. + +This subroutine is same as AutoGKSmooth(), but it guarantees that interval +[a,b] is partitioned into subintervals which have width at most XWidth. + +Subroutine can be used when integrating nearly-constant function with +narrow "bumps" (about XWidth wide). If "bumps" are too narrow, AutoGKSmooth +subroutine can overlook them. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSingular, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksmoothw(const double a, const double b, const double xwidth, autogkstate &state); + + +/************************************************************************* +Integration on a finite interval [A,B]. +Integrand have integrable singularities at A/B. + +F(X) must diverge as "(x-A)^alpha" at A, as "(B-x)^beta" at B, with known +alpha/beta (alpha>-1, beta>-1). If alpha/beta are not known, estimates +from below can be used (but these estimates should be greater than -1 too). + +One of alpha/beta variables (or even both alpha/beta) may be equal to 0, +which means than function F(x) is non-singular at A/B. Anyway (singular at +bounds or not), function F(x) is supposed to be continuous on (A,B). + +Fast-convergent algorithm based on a Gauss-Kronrod formula is used. Result +is calculated with accuracy close to the machine precision. + +INPUT PARAMETERS: + A, B - interval boundaries (AB) + Alpha - power-law coefficient of the F(x) at A, + Alpha>-1 + Beta - power-law coefficient of the F(x) at B, + Beta>-1 + +OUTPUT PARAMETERS + State - structure which stores algorithm state + +SEE ALSO + AutoGKSmooth, AutoGKSmoothW, AutoGKResults. + + + -- ALGLIB -- + Copyright 06.05.2009 by Bochkanov Sergey +*************************************************************************/ +void autogksingular(const double a, const double b, const double alpha, const double beta, autogkstate &state); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool autogkiteration(const autogkstate &state); + + +/************************************************************************* +This function is used to launcn iterations of the 1-dimensional integrator + +It accepts following parameters: + func - callback which calculates f(x) for given x + ptr - optional pointer which is passed to func; can be NULL + + + -- ALGLIB -- + Copyright 07.05.2009 by Bochkanov Sergey + +*************************************************************************/ +void autogkintegrate(autogkstate &state, + void (*func)(double x, double xminusa, double bminusx, double &y, void *ptr), + void *ptr = NULL); + + +/************************************************************************* +Adaptive integration results + +Called after AutoGKIteration returned False. + +Input parameters: + State - algorithm state (used by AutoGKIteration). + +Output parameters: + V - integral(f(x)dx,a,b) + Rep - optimization report (see AutoGKReport description) + + -- ALGLIB -- + Copyright 14.11.2007 by Bochkanov Sergey +*************************************************************************/ +void autogkresults(const autogkstate &state, double &v, autogkreport &rep); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void gqgeneraterec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategausslobattorec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + double a, + double b, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategaussradaurec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + double a, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategausslegendre(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategaussjacobi(ae_int_t n, + double alpha, + double beta, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategausslaguerre(ae_int_t n, + double alpha, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gqgenerategausshermite(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* w, + ae_state *_state); +void gkqgeneraterec(/* Real */ ae_vector* alpha, + /* Real */ ae_vector* beta, + double mu0, + ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state); +void gkqgenerategausslegendre(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state); +void gkqgenerategaussjacobi(ae_int_t n, + double alpha, + double beta, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state); +void gkqlegendrecalc(ae_int_t n, + ae_int_t* info, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + ae_state *_state); +void gkqlegendretbl(ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* wkronrod, + /* Real */ ae_vector* wgauss, + double* eps, + ae_state *_state); +void autogksmooth(double a, + double b, + autogkstate* state, + ae_state *_state); +void autogksmoothw(double a, + double b, + double xwidth, + autogkstate* state, + ae_state *_state); +void autogksingular(double a, + double b, + double alpha, + double beta, + autogkstate* state, + ae_state *_state); +ae_bool autogkiteration(autogkstate* state, ae_state *_state); +void autogkresults(autogkstate* state, + double* v, + autogkreport* rep, + ae_state *_state); +ae_bool _autogkreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _autogkreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _autogkreport_clear(void* _p); +void _autogkreport_destroy(void* _p); +ae_bool _autogkinternalstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _autogkinternalstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _autogkinternalstate_clear(void* _p); +void _autogkinternalstate_destroy(void* _p); +ae_bool _autogkstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _autogkstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _autogkstate_clear(void* _p); +void _autogkstate_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/interpolation.cpp b/src/inc/alglib/interpolation.cpp new file mode 100644 index 0000000..08ed432 --- /dev/null +++ b/src/inc/alglib/interpolation.cpp @@ -0,0 +1,30715 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "interpolation.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +IDW interpolant. +*************************************************************************/ +_idwinterpolant_owner::_idwinterpolant_owner() +{ + p_struct = (alglib_impl::idwinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::idwinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_idwinterpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_idwinterpolant_owner::_idwinterpolant_owner(const _idwinterpolant_owner &rhs) +{ + p_struct = (alglib_impl::idwinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::idwinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_idwinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_idwinterpolant_owner& _idwinterpolant_owner::operator=(const _idwinterpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_idwinterpolant_clear(p_struct); + if( !alglib_impl::_idwinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_idwinterpolant_owner::~_idwinterpolant_owner() +{ + alglib_impl::_idwinterpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::idwinterpolant* _idwinterpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::idwinterpolant* _idwinterpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +idwinterpolant::idwinterpolant() : _idwinterpolant_owner() +{ +} + +idwinterpolant::idwinterpolant(const idwinterpolant &rhs):_idwinterpolant_owner(rhs) +{ +} + +idwinterpolant& idwinterpolant::operator=(const idwinterpolant &rhs) +{ + if( this==&rhs ) + return *this; + _idwinterpolant_owner::operator=(rhs); + return *this; +} + +idwinterpolant::~idwinterpolant() +{ +} + +/************************************************************************* +IDW interpolation + +INPUT PARAMETERS: + Z - IDW interpolant built with one of model building + subroutines. + X - array[0..NX-1], interpolation point + +Result: + IDW interpolant Z(X) + + -- ALGLIB -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +double idwcalc(const idwinterpolant &z, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::idwcalc(const_cast(z.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +IDW interpolant using modified Shepard method for uniform point +distributions. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function type, either: + * 0 constant model. Just for demonstration only, worst + model ever. + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + * -1 "fast" linear model, use with caution!!! It is + significantly faster than linear/quadratic and better + than constant model. But it is less robust (especially + in the presence of noise). + NQ - number of points used to calculate nodal functions (ignored + for constant models). NQ should be LARGER than: + * max(1.5*(1+NX),2^NX+1) for linear model, + * max(3/4*(NX+2)*(NX+1),2^NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, worst - with constant + models + * when N is large, NQ and NW must be significantly smaller than N both + to obtain optimal performance and to obtain optimal accuracy. In 2 or + 3-dimensional tasks NQ=15 and NW=25 are good values to start with. + * NQ and NW may be greater than N. In such cases they will be + automatically decreased. + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + * this subroutine assumes that point distribution is uniform at the small + scales. If it isn't - for example, points are concentrated along + "lines", but "lines" distribution is uniform at the larger scale - then + you should use IDWBuildModifiedShepardR() + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepard(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t d, const ae_int_t nq, const ae_int_t nw, idwinterpolant &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::idwbuildmodifiedshepard(const_cast(xy.c_ptr()), n, nx, d, nq, nw, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +IDW interpolant using modified Shepard method for non-uniform datasets. + +This type of model uses constant nodal functions and interpolates using +all nodes which are closer than user-specified radius R. It may be used +when points distribution is non-uniform at the small scale, but it is at +the distances as large as R. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + R - radius, R>0 + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: +* if there is less than IDWKMin points within R-ball, algorithm selects + IDWKMin closest ones, so that continuity properties of interpolant are + preserved even far from points. + + -- ALGLIB PROJECT -- + Copyright 11.04.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepardr(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const double r, idwinterpolant &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::idwbuildmodifiedshepardr(const_cast(xy.c_ptr()), n, nx, r, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +IDW model for noisy data. + +This subroutine may be used to handle noisy data, i.e. data with noise in +OUTPUT values. It differs from IDWBuildModifiedShepard() in the following +aspects: +* nodal functions are not constrained to pass through nodes: Qi(xi)<>yi, + i.e. we have fitting instead of interpolation. +* weights which are used during least squares fitting stage are all equal + to 1.0 (independently of distance) +* "fast"-linear or constant nodal functions are not supported (either not + robust enough or too rigid) + +This problem require far more complex tuning than interpolation problems. +Below you can find some recommendations regarding this problem: +* focus on tuning NQ; it controls noise reduction. As for NW, you can just + make it equal to 2*NQ. +* you can use cross-validation to determine optimal NQ. +* optimal NQ is a result of complex tradeoff between noise level (more + noise = larger NQ required) and underlying function complexity (given + fixed N, larger NQ means smoothing of compex features in the data). For + example, NQ=N will reduce noise to the minimum level possible, but you + will end up with just constant/linear/quadratic (depending on D) least + squares model for the whole dataset. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function degree, either: + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models (or for very + noisy problems). + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + NQ - number of points used to calculate nodal functions. NQ should + be significantly larger than 1.5 times the number of + coefficients in a nodal function to overcome effects of noise: + * larger than 1.5*(1+NX) for linear model, + * larger than 3/4*(NX+2)*(NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ or larger + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, linear models are not + recommended to use unless you are pretty sure that it is what you want + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildnoisy(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t d, const ae_int_t nq, const ae_int_t nw, idwinterpolant &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::idwbuildnoisy(const_cast(xy.c_ptr()), n, nx, d, nq, nw, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Barycentric interpolant. +*************************************************************************/ +_barycentricinterpolant_owner::_barycentricinterpolant_owner() +{ + p_struct = (alglib_impl::barycentricinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::barycentricinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_barycentricinterpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_barycentricinterpolant_owner::_barycentricinterpolant_owner(const _barycentricinterpolant_owner &rhs) +{ + p_struct = (alglib_impl::barycentricinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::barycentricinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_barycentricinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_barycentricinterpolant_owner& _barycentricinterpolant_owner::operator=(const _barycentricinterpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_barycentricinterpolant_clear(p_struct); + if( !alglib_impl::_barycentricinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_barycentricinterpolant_owner::~_barycentricinterpolant_owner() +{ + alglib_impl::_barycentricinterpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::barycentricinterpolant* _barycentricinterpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::barycentricinterpolant* _barycentricinterpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +barycentricinterpolant::barycentricinterpolant() : _barycentricinterpolant_owner() +{ +} + +barycentricinterpolant::barycentricinterpolant(const barycentricinterpolant &rhs):_barycentricinterpolant_owner(rhs) +{ +} + +barycentricinterpolant& barycentricinterpolant::operator=(const barycentricinterpolant &rhs) +{ + if( this==&rhs ) + return *this; + _barycentricinterpolant_owner::operator=(rhs); + return *this; +} + +barycentricinterpolant::~barycentricinterpolant() +{ +} + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +double barycentriccalc(const barycentricinterpolant &b, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::barycentriccalc(const_cast(b.c_ptr()), t, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + +NOTE + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff1(const barycentricinterpolant &b, const double t, double &f, double &df) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricdiff1(const_cast(b.c_ptr()), t, &f, &df, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff2(const barycentricinterpolant &b, const double t, double &f, double &df, double &d2f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricdiff2(const_cast(b.c_ptr()), t, &f, &df, &d2f, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransx(const barycentricinterpolant &b, const double ca, const double cb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentriclintransx(const_cast(b.c_ptr()), ca, cb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransy(const barycentricinterpolant &b, const double ca, const double cb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentriclintransy(const_cast(b.c_ptr()), ca, cb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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] + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricunpack(const barycentricinterpolant &b, ae_int_t &n, real_1d_array &x, real_1d_array &y, real_1d_array &w) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricunpack(const_cast(b.c_ptr()), &n, const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildxyw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, barycentricinterpolant &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricbuildxyw(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 17.06.2007 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildfloaterhormann(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t d, barycentricinterpolant &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricbuildfloaterhormann(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, d, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2cheb(const barycentricinterpolant &p, const double a, const double b, real_1d_array &t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbar2cheb(const_cast(p.c_ptr()), a, b, const_cast(t.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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(t.c_ptr()), n, a, b, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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(t.c_ptr()), n, a, b, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2pow(const barycentricinterpolant &p, const double c, const double s, real_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbar2pow(const_cast(p.c_ptr()), c, s, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2pow(const barycentricinterpolant &p, real_1d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + double c; + double s; + + c = 0; + s = 1; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbar2pow(const_cast(p.c_ptr()), c, s, const_cast(a.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialpow2bar(const real_1d_array &a, const ae_int_t n, const double c, const double s, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialpow2bar(const_cast(a.c_ptr()), n, c, s, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialpow2bar(const real_1d_array &a, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + double c; + double s; + + n = a.length(); + c = 0; + s = 1; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialpow2bar(const_cast(a.c_ptr()), n, c, s, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuild(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuild(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuild(const real_1d_array &x, const real_1d_array &y, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'polynomialbuild': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuild(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildeqdist(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildeqdist(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildeqdist(const double a, const double b, const real_1d_array &y, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = y.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildeqdist(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb1(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildcheb1(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb1(const double a, const double b, const real_1d_array &y, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = y.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildcheb1(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb2(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildcheb2(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb2(const double a, const double b, const real_1d_array &y, barycentricinterpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = y.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialbuildcheb2(a, b, const_cast(y.c_ptr()), n, const_cast(p.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalceqdist(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalceqdist(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalceqdist(const double a, const double b, const real_1d_array &f, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = f.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalceqdist(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb1(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalccheb1(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb1(const double a, const double b, const real_1d_array &f, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = f.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalccheb1(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb2(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalccheb2(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb2(const double a, const double b, const real_1d_array &f, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = f.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::polynomialcalccheb2(a, b, const_cast(f.c_ptr()), n, t, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +1-dimensional spline interpolant +*************************************************************************/ +_spline1dinterpolant_owner::_spline1dinterpolant_owner() +{ + p_struct = (alglib_impl::spline1dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline1dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline1dinterpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline1dinterpolant_owner::_spline1dinterpolant_owner(const _spline1dinterpolant_owner &rhs) +{ + p_struct = (alglib_impl::spline1dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline1dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline1dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline1dinterpolant_owner& _spline1dinterpolant_owner::operator=(const _spline1dinterpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_spline1dinterpolant_clear(p_struct); + if( !alglib_impl::_spline1dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_spline1dinterpolant_owner::~_spline1dinterpolant_owner() +{ + alglib_impl::_spline1dinterpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::spline1dinterpolant* _spline1dinterpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::spline1dinterpolant* _spline1dinterpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +spline1dinterpolant::spline1dinterpolant() : _spline1dinterpolant_owner() +{ +} + +spline1dinterpolant::spline1dinterpolant(const spline1dinterpolant &rhs):_spline1dinterpolant_owner(rhs) +{ +} + +spline1dinterpolant& spline1dinterpolant::operator=(const spline1dinterpolant &rhs) +{ + if( this==&rhs ) + return *this; + _spline1dinterpolant_owner::operator=(rhs); + return *this; +} + +spline1dinterpolant::~spline1dinterpolant() +{ +} + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildlinear(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildlinear(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildlinear(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dbuildlinear': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildlinear(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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]. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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]. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildcubic(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dbuildcubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiffcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, real_1d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dgriddiffcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiffcubic(const real_1d_array &x, const real_1d_array &y, real_1d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dgriddiffcubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dgriddiffcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(d.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiff2cubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, real_1d_array &d1, real_1d_array &d2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dgriddiff2cubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(d1.c_ptr()), const_cast(d2.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiff2cubic(const real_1d_array &x, const real_1d_array &y, real_1d_array &d1, real_1d_array &d2) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dgriddiff2cubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dgriddiff2cubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(d1.c_ptr()), const_cast(d2.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvcubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + ae_int_t n2; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dconvcubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + n2 = x2.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiffcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2, real_1d_array &d2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvdiffcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), const_cast(d2.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiffcubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2, real_1d_array &d2) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + ae_int_t n2; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dconvdiffcubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + n2 = x2.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvdiffcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), const_cast(d2.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiff2cubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2, real_1d_array &d2, real_1d_array &dd2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvdiff2cubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), const_cast(d2.c_ptr()), const_cast(dd2.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiff2cubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2, real_1d_array &d2, real_1d_array &dd2) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t boundltype; + double boundl; + ae_int_t boundrtype; + double boundr; + ae_int_t n2; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dconvdiff2cubic': looks like one of arguments has wrong size"); + n = x.length(); + boundltype = 0; + boundl = 0; + boundrtype = 0; + boundr = 0; + n2 = x2.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dconvdiff2cubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, boundltype, boundl, boundrtype, boundr, const_cast(x2.c_ptr()), n2, const_cast(y2.c_ptr()), const_cast(d2.c_ptr()), const_cast(dd2.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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(x.c_ptr()), const_cast(y.c_ptr()), n, boundtype, tension, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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(x.c_ptr()), const_cast(y.c_ptr()), n, boundtype, tension, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildhermite(const real_1d_array &x, const real_1d_array &y, const real_1d_array &d, const ae_int_t n, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildhermite(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(d.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildhermite(const real_1d_array &x, const real_1d_array &y, const real_1d_array &d, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length()) || (x.length()!=d.length())) + throw ap_error("Error while calling 'spline1dbuildhermite': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildhermite(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(d.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds Akima 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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildakima(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildakima(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds Akima 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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildakima(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dbuildakima': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildakima(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine calculates the value of the spline at the given point X. + +INPUT PARAMETERS: + C - spline interpolant + X - point + +Result: + S(x) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dcalc(const spline1dinterpolant &c, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spline1dcalc(const_cast(c.c_ptr()), x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine differentiates the spline. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +Result: + S - S(x) + DS - S'(x) + D2S - S''(x) + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1ddiff(const spline1dinterpolant &c, const double x, double &s, double &ds, double &d2s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1ddiff(const_cast(c.c_ptr()), x, &s, &ds, &d2s, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine unpacks the spline into the coefficients table. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +OUTPUT PARAMETERS: + 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] + +NOTE: + You can rebuild spline with Spline1DBuildHermite() function, which + accepts as inputs function values and derivatives at nodes, which are + easy to calculate when you have coefficients. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dunpack(const spline1dinterpolant &c, ae_int_t &n, real_2d_array &tbl) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dunpack(const_cast(c.c_ptr()), &n, const_cast(tbl.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine performs linear transformation of the spline argument. + +INPUT PARAMETERS: + C - spline interpolant. + A, B- transformation coefficients: x = A*t + B +Result: + C - transformed spline + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransx(const spline1dinterpolant &c, const double a, const double b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dlintransx(const_cast(c.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransy(const spline1dinterpolant &c, const double a, const double b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dlintransy(const_cast(c.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dintegrate(const spline1dinterpolant &c, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spline1dintegrate(const_cast(c.c_ptr()), x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 21.06.2012 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildmonotone(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildmonotone(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + -- ALGLIB PROJECT -- + Copyright 21.06.2012 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildmonotone(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dbuildmonotone': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dbuildmonotone(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 +*************************************************************************/ +_polynomialfitreport_owner::_polynomialfitreport_owner() +{ + p_struct = (alglib_impl::polynomialfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::polynomialfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_polynomialfitreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_polynomialfitreport_owner::_polynomialfitreport_owner(const _polynomialfitreport_owner &rhs) +{ + p_struct = (alglib_impl::polynomialfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::polynomialfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_polynomialfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_polynomialfitreport_owner& _polynomialfitreport_owner::operator=(const _polynomialfitreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_polynomialfitreport_clear(p_struct); + if( !alglib_impl::_polynomialfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_polynomialfitreport_owner::~_polynomialfitreport_owner() +{ + alglib_impl::_polynomialfitreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::polynomialfitreport* _polynomialfitreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::polynomialfitreport* _polynomialfitreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +polynomialfitreport::polynomialfitreport() : _polynomialfitreport_owner() ,taskrcond(p_struct->taskrcond),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +polynomialfitreport::polynomialfitreport(const polynomialfitreport &rhs):_polynomialfitreport_owner(rhs) ,taskrcond(p_struct->taskrcond),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +polynomialfitreport& polynomialfitreport::operator=(const polynomialfitreport &rhs) +{ + if( this==&rhs ) + return *this; + _polynomialfitreport_owner::operator=(rhs); + return *this; +} + +polynomialfitreport::~polynomialfitreport() +{ +} + + +/************************************************************************* +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 +*************************************************************************/ +_barycentricfitreport_owner::_barycentricfitreport_owner() +{ + p_struct = (alglib_impl::barycentricfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::barycentricfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_barycentricfitreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_barycentricfitreport_owner::_barycentricfitreport_owner(const _barycentricfitreport_owner &rhs) +{ + p_struct = (alglib_impl::barycentricfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::barycentricfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_barycentricfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_barycentricfitreport_owner& _barycentricfitreport_owner::operator=(const _barycentricfitreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_barycentricfitreport_clear(p_struct); + if( !alglib_impl::_barycentricfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_barycentricfitreport_owner::~_barycentricfitreport_owner() +{ + alglib_impl::_barycentricfitreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::barycentricfitreport* _barycentricfitreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::barycentricfitreport* _barycentricfitreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +barycentricfitreport::barycentricfitreport() : _barycentricfitreport_owner() ,taskrcond(p_struct->taskrcond),dbest(p_struct->dbest),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +barycentricfitreport::barycentricfitreport(const barycentricfitreport &rhs):_barycentricfitreport_owner(rhs) ,taskrcond(p_struct->taskrcond),dbest(p_struct->dbest),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +barycentricfitreport& barycentricfitreport::operator=(const barycentricfitreport &rhs) +{ + if( this==&rhs ) + return *this; + _barycentricfitreport_owner::operator=(rhs); + return *this; +} + +barycentricfitreport::~barycentricfitreport() +{ +} + + +/************************************************************************* +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 +*************************************************************************/ +_spline1dfitreport_owner::_spline1dfitreport_owner() +{ + p_struct = (alglib_impl::spline1dfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline1dfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline1dfitreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline1dfitreport_owner::_spline1dfitreport_owner(const _spline1dfitreport_owner &rhs) +{ + p_struct = (alglib_impl::spline1dfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline1dfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline1dfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline1dfitreport_owner& _spline1dfitreport_owner::operator=(const _spline1dfitreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_spline1dfitreport_clear(p_struct); + if( !alglib_impl::_spline1dfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_spline1dfitreport_owner::~_spline1dfitreport_owner() +{ + alglib_impl::_spline1dfitreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::spline1dfitreport* _spline1dfitreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::spline1dfitreport* _spline1dfitreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +spline1dfitreport::spline1dfitreport() : _spline1dfitreport_owner() ,taskrcond(p_struct->taskrcond),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +spline1dfitreport::spline1dfitreport(const spline1dfitreport &rhs):_spline1dfitreport_owner(rhs) ,taskrcond(p_struct->taskrcond),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror) +{ +} + +spline1dfitreport& spline1dfitreport::operator=(const spline1dfitreport &rhs) +{ + if( this==&rhs ) + return *this; + _spline1dfitreport_owner::operator=(rhs); + return *this; +} + +spline1dfitreport::~spline1dfitreport() +{ +} + + +/************************************************************************* +Least squares fitting report. This structure contains informational fields +which are set by fitting functions provided by this unit. + +Different functions initialize different sets of fields, so you should +read documentation on specific function you used in order to know which +fields are initialized. + + TaskRCond reciprocal of task's condition number + IterationsCount number of internal iterations + + VarIdx if user-supplied gradient contains errors which were + detected by nonlinear fitter, this field is set to + index of the first component of gradient which is + suspected to be spoiled by bugs. + + RMSError RMS error + AvgError average error + AvgRelError average relative error (for non-zero Y[I]) + MaxError maximum error + + WRMSError weighted RMS error + + CovPar covariance matrix for parameters, filled by some solvers + ErrPar vector of errors in parameters, filled by some solvers + ErrCurve vector of fit errors - variability of the best-fit + curve, filled by some solvers. + Noise vector of per-point noise estimates, filled by + some solvers. + R2 coefficient of determination (non-weighted, non-adjusted), + filled by some solvers. +*************************************************************************/ +_lsfitreport_owner::_lsfitreport_owner() +{ + p_struct = (alglib_impl::lsfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lsfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lsfitreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lsfitreport_owner::_lsfitreport_owner(const _lsfitreport_owner &rhs) +{ + p_struct = (alglib_impl::lsfitreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lsfitreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lsfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lsfitreport_owner& _lsfitreport_owner::operator=(const _lsfitreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_lsfitreport_clear(p_struct); + if( !alglib_impl::_lsfitreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_lsfitreport_owner::~_lsfitreport_owner() +{ + alglib_impl::_lsfitreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::lsfitreport* _lsfitreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::lsfitreport* _lsfitreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +lsfitreport::lsfitreport() : _lsfitreport_owner() ,taskrcond(p_struct->taskrcond),iterationscount(p_struct->iterationscount),varidx(p_struct->varidx),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror),wrmserror(p_struct->wrmserror),covpar(&p_struct->covpar),errpar(&p_struct->errpar),errcurve(&p_struct->errcurve),noise(&p_struct->noise),r2(p_struct->r2) +{ +} + +lsfitreport::lsfitreport(const lsfitreport &rhs):_lsfitreport_owner(rhs) ,taskrcond(p_struct->taskrcond),iterationscount(p_struct->iterationscount),varidx(p_struct->varidx),rmserror(p_struct->rmserror),avgerror(p_struct->avgerror),avgrelerror(p_struct->avgrelerror),maxerror(p_struct->maxerror),wrmserror(p_struct->wrmserror),covpar(&p_struct->covpar),errpar(&p_struct->errpar),errcurve(&p_struct->errcurve),noise(&p_struct->noise),r2(p_struct->r2) +{ +} + +lsfitreport& lsfitreport::operator=(const lsfitreport &rhs) +{ + if( this==&rhs ) + return *this; + _lsfitreport_owner::operator=(rhs); + return *this; +} + +lsfitreport::~lsfitreport() +{ +} + + +/************************************************************************* +Nonlinear fitter. + +You should use ALGLIB functions to work with fitter. +Never try to access its fields directly! +*************************************************************************/ +_lsfitstate_owner::_lsfitstate_owner() +{ + p_struct = (alglib_impl::lsfitstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::lsfitstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lsfitstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lsfitstate_owner::_lsfitstate_owner(const _lsfitstate_owner &rhs) +{ + p_struct = (alglib_impl::lsfitstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::lsfitstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lsfitstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lsfitstate_owner& _lsfitstate_owner::operator=(const _lsfitstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_lsfitstate_clear(p_struct); + if( !alglib_impl::_lsfitstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_lsfitstate_owner::~_lsfitstate_owner() +{ + alglib_impl::_lsfitstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::lsfitstate* _lsfitstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::lsfitstate* _lsfitstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +lsfitstate::lsfitstate() : _lsfitstate_owner() ,needf(p_struct->needf),needfg(p_struct->needfg),needfgh(p_struct->needfgh),xupdated(p_struct->xupdated),c(&p_struct->c),f(p_struct->f),g(&p_struct->g),h(&p_struct->h),x(&p_struct->x) +{ +} + +lsfitstate::lsfitstate(const lsfitstate &rhs):_lsfitstate_owner(rhs) ,needf(p_struct->needf),needfg(p_struct->needfg),needfgh(p_struct->needfgh),xupdated(p_struct->xupdated),c(&p_struct->c),f(p_struct->f),g(&p_struct->g),h(&p_struct->h),x(&p_struct->x) +{ +} + +lsfitstate& lsfitstate::operator=(const lsfitstate &rhs) +{ + if( this==&rhs ) + return *this; + _lsfitstate_owner::operator=(rhs); + return *this; +} + +lsfitstate::~lsfitstate() +{ +} + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfit(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialfit(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(p.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfit(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'polynomialfit': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialfit(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(p.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfitwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialfitwc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(p.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfitwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t k; + if( (x.length()!=y.length()) || (x.length()!=w.length())) + throw ap_error("Error while calling 'polynomialfitwc': looks like one of arguments has wrong size"); + if( (xc.length()!=yc.length()) || (xc.length()!=dc.length())) + throw ap_error("Error while calling 'polynomialfitwc': looks like one of arguments has wrong size"); + n = x.length(); + k = xc.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::polynomialfitwc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(p.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormannwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, barycentricinterpolant &b, barycentricfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricfitfloaterhormannwc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(b.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormann(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, barycentricinterpolant &b, barycentricfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::barycentricfitfloaterhormann(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(b.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalized(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitpenalized(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, rho, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalized(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dfitpenalized': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitpenalized(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, rho, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 xmax(x,xc). It is done +for consistency - we penalize non-linearity at [min(x,xc),max(x,xc)], so +it is natural to force linearity outside of this interval. + +NOTE 2: function automatically sorts points, so caller may pass unsorted +array. + + -- ALGLIB PROJECT -- + Copyright 19.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalizedw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitpenalizedw(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, m, rho, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 xmax(x,xc). It is done +for consistency - we penalize non-linearity at [min(x,xc),max(x,xc)], so +it is natural to force linearity outside of this interval. + +NOTE 2: function automatically sorts points, so caller may pass unsorted +array. + + -- ALGLIB PROJECT -- + Copyright 19.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalizedw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length()) || (x.length()!=w.length())) + throw ap_error("Error while calling 'spline1dfitpenalizedw': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitpenalizedw(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, m, rho, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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. + + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubicwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitcubicwc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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. + + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubicwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t k; + if( (x.length()!=y.length()) || (x.length()!=w.length())) + throw ap_error("Error while calling 'spline1dfitcubicwc': looks like one of arguments has wrong size"); + if( (xc.length()!=yc.length()) || (xc.length()!=dc.length())) + throw ap_error("Error while calling 'spline1dfitcubicwc': looks like one of arguments has wrong size"); + n = x.length(); + k = xc.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitcubicwc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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 + +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 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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermitewc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfithermitewc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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=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 + +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 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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermitewc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t k; + if( (x.length()!=y.length()) || (x.length()!=w.length())) + throw ap_error("Error while calling 'spline1dfithermitewc': looks like one of arguments has wrong size"); + if( (xc.length()!=yc.length()) || (xc.length()!=dc.length())) + throw ap_error("Error while calling 'spline1dfithermitewc': looks like one of arguments has wrong size"); + n = x.length(); + k = xc.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfithermitewc(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), n, const_cast(xc.c_ptr()), const_cast(yc.c_ptr()), const_cast(dc.c_ptr()), k, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dfitcubic': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfitcubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermite(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfithermite(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermite(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spline1dfithermite': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline1dfithermite(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m, &info, const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearw(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const ae_int_t n, const ae_int_t m, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearw(const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(fmatrix.c_ptr()), n, m, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearw(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + if( (y.length()!=w.length()) || (y.length()!=fmatrix.rows())) + throw ap_error("Error while calling 'lsfitlinearw': looks like one of arguments has wrong size"); + n = y.length(); + m = fmatrix.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearw(const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(fmatrix.c_ptr()), n, m, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearwc(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const real_2d_array &cmatrix, const ae_int_t n, const ae_int_t m, const ae_int_t k, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearwc(const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(fmatrix.c_ptr()), const_cast(cmatrix.c_ptr()), n, m, k, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearwc(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const real_2d_array &cmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (y.length()!=w.length()) || (y.length()!=fmatrix.rows())) + throw ap_error("Error while calling 'lsfitlinearwc': looks like one of arguments has wrong size"); + if( (fmatrix.cols()!=cmatrix.cols()-1)) + throw ap_error("Error while calling 'lsfitlinearwc': looks like one of arguments has wrong size"); + n = y.length(); + m = fmatrix.cols(); + k = cmatrix.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearwc(const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(fmatrix.c_ptr()), const_cast(cmatrix.c_ptr()), n, m, k, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinear(const real_1d_array &y, const real_2d_array &fmatrix, const ae_int_t n, const ae_int_t m, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinear(const_cast(y.c_ptr()), const_cast(fmatrix.c_ptr()), n, m, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinear(const real_1d_array &y, const real_2d_array &fmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + if( (y.length()!=fmatrix.rows())) + throw ap_error("Error while calling 'lsfitlinear': looks like one of arguments has wrong size"); + n = y.length(); + m = fmatrix.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinear(const_cast(y.c_ptr()), const_cast(fmatrix.c_ptr()), n, m, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearc(const real_1d_array &y, const real_2d_array &fmatrix, const real_2d_array &cmatrix, const ae_int_t n, const ae_int_t m, const ae_int_t k, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearc(const_cast(y.c_ptr()), const_cast(fmatrix.c_ptr()), const_cast(cmatrix.c_ptr()), n, m, k, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearc(const real_1d_array &y, const real_2d_array &fmatrix, const real_2d_array &cmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (y.length()!=fmatrix.rows())) + throw ap_error("Error while calling 'lsfitlinearc': looks like one of arguments has wrong size"); + if( (fmatrix.cols()!=cmatrix.cols()-1)) + throw ap_error("Error while calling 'lsfitlinearc': looks like one of arguments has wrong size"); + n = y.length(); + m = fmatrix.cols(); + k = cmatrix.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitlinearc(const_cast(y.c_ptr()), const_cast(fmatrix.c_ptr()), const_cast(cmatrix.c_ptr()), n, m, k, &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewf(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const double diffstep, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewf(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewf(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const double diffstep, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length()) || (x.rows()!=w.length())) + throw ap_error("Error while calling 'lsfitcreatewf': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewf(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatef(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const double diffstep, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatef(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatef(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const double diffstep, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length())) + throw ap_error("Error while calling 'lsfitcreatef': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatef(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const bool cheapfg, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewfg(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, cheapfg, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const bool cheapfg, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length()) || (x.rows()!=w.length())) + throw ap_error("Error while calling 'lsfitcreatewfg': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewfg(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, cheapfg, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const bool cheapfg, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatefg(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, cheapfg, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const bool cheapfg, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length())) + throw ap_error("Error while calling 'lsfitcreatefg': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatefg(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, cheapfg, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewfgh(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length()) || (x.rows()!=w.length())) + throw ap_error("Error while calling 'lsfitcreatewfgh': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatewfgh(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(w.c_ptr()), const_cast(c.c_ptr()), n, m, k, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatefgh(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + ae_int_t k; + if( (x.rows()!=y.length())) + throw ap_error("Error while calling 'lsfitcreatefgh': looks like one of arguments has wrong size"); + n = x.rows(); + m = x.cols(); + k = c.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitcreatefgh(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(c.c_ptr()), n, m, k, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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). + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetcond(const lsfitstate &state, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetcond(const_cast(state.c_ptr()), epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetstpmax(const lsfitstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + + -- ALGLIB -- + Copyright 15.08.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetxrep(const lsfitstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets 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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetscale(const lsfitstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets 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 + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetbc(const lsfitstate &state, const real_1d_array &bndl, const real_1d_array &bndu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetbc(const_cast(state.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool lsfititeration(const lsfitstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::lsfititeration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (func is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::lsfititeration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.c, state.x, state.f, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.c, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (func is NULL)"); + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (grad is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::lsfititeration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.c, state.x, state.f, ptr); + continue; + } + if( state.needfg ) + { + grad(state.c, state.x, state.f, state.g, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.c, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*hess)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, real_2d_array &hess, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (func is NULL)"); + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (grad is NULL)"); + if( hess==NULL ) + throw ap_error("ALGLIB: error in 'lsfitfit()' (hess is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::lsfititeration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.c, state.x, state.f, ptr); + continue; + } + if( state.needfg ) + { + grad(state.c, state.x, state.f, state.g, ptr); + continue; + } + if( state.needfgh ) + { + hess(state.c, state.x, state.f, state.g, state.h, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.c, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'lsfitfit' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +Nonlinear least squares fitting results. + +Called after return from LSFitFit(). + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Info - completion code: + * -7 gradient verification failed. + See LSFitSetGradientCheck() for more information. + * 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. On success following fields are set: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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). + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitresults(const lsfitstate &state, ae_int_t &info, real_1d_array &c, lsfitreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitresults(const_cast(state.c_ptr()), &info, const_cast(c.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before fitting begins +* LSFitFit() is called +* prior to actual fitting, for each point in data set X_i and each + component of parameters being fited C_j algorithm performs following + steps: + * two trial steps are made to C_j-TestStep*S[j] and C_j+TestStep*S[j], + where C_j is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on C[] + * F(X_i|C) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N*K (points count * parameters count) gradient + evaluations. It is very costly and you should use it only for low + dimensional problems, when you want to be sure that you've + correctly calculated analytic derivatives. You should not use it + in the production code (unless you want to check derivatives + provided by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with LSFitSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +NOTE 4: this function works only for optimizers created with LSFitCreateWFG() + or LSFitCreateFG() constructors. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetgradientcheck(const lsfitstate &state, const double teststep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lsfitsetgradientcheck(const_cast(state.c_ptr()), teststep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Parametric spline inteprolant: 2-dimensional curve. + +You should not try to access its members directly - use PSpline2XXXXXXXX() +functions instead. +*************************************************************************/ +_pspline2interpolant_owner::_pspline2interpolant_owner() +{ + p_struct = (alglib_impl::pspline2interpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::pspline2interpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_pspline2interpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_pspline2interpolant_owner::_pspline2interpolant_owner(const _pspline2interpolant_owner &rhs) +{ + p_struct = (alglib_impl::pspline2interpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::pspline2interpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_pspline2interpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_pspline2interpolant_owner& _pspline2interpolant_owner::operator=(const _pspline2interpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_pspline2interpolant_clear(p_struct); + if( !alglib_impl::_pspline2interpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_pspline2interpolant_owner::~_pspline2interpolant_owner() +{ + alglib_impl::_pspline2interpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::pspline2interpolant* _pspline2interpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::pspline2interpolant* _pspline2interpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +pspline2interpolant::pspline2interpolant() : _pspline2interpolant_owner() +{ +} + +pspline2interpolant::pspline2interpolant(const pspline2interpolant &rhs):_pspline2interpolant_owner(rhs) +{ +} + +pspline2interpolant& pspline2interpolant::operator=(const pspline2interpolant &rhs) +{ + if( this==&rhs ) + return *this; + _pspline2interpolant_owner::operator=(rhs); + return *this; +} + +pspline2interpolant::~pspline2interpolant() +{ +} + + +/************************************************************************* +Parametric spline inteprolant: 3-dimensional curve. + +You should not try to access its members directly - use PSpline3XXXXXXXX() +functions instead. +*************************************************************************/ +_pspline3interpolant_owner::_pspline3interpolant_owner() +{ + p_struct = (alglib_impl::pspline3interpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::pspline3interpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_pspline3interpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_pspline3interpolant_owner::_pspline3interpolant_owner(const _pspline3interpolant_owner &rhs) +{ + p_struct = (alglib_impl::pspline3interpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::pspline3interpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_pspline3interpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_pspline3interpolant_owner& _pspline3interpolant_owner::operator=(const _pspline3interpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_pspline3interpolant_clear(p_struct); + if( !alglib_impl::_pspline3interpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_pspline3interpolant_owner::~_pspline3interpolant_owner() +{ + alglib_impl::_pspline3interpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::pspline3interpolant* _pspline3interpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::pspline3interpolant* _pspline3interpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +pspline3interpolant::pspline3interpolant() : _pspline3interpolant_owner() +{ +} + +pspline3interpolant::pspline3interpolant(const pspline3interpolant &rhs):_pspline3interpolant_owner(rhs) +{ +} + +pspline3interpolant& pspline3interpolant::operator=(const pspline3interpolant &rhs) +{ + if( this==&rhs ) + return *this; + _pspline3interpolant_owner::operator=(rhs); + return *this; +} + +pspline3interpolant::~pspline3interpolant() +{ +} + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2build(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline2interpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2build(const_cast(xy.c_ptr()), n, st, pt, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3build(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline3interpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3build(const_cast(xy.c_ptr()), n, st, pt, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2buildperiodic(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline2interpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2buildperiodic(const_cast(xy.c_ptr()), n, st, pt, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3buildperiodic(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline3interpolant &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3buildperiodic(const_cast(xy.c_ptr()), n, st, pt, const_cast(p.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns 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](p.c_ptr()), &n, const_cast(t.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns vector of parameter values correspoding to points. + +Same as PSpline2ParameterValues(), but for 3D. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3parametervalues(const pspline3interpolant &p, ae_int_t &n, real_1d_array &t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3parametervalues(const_cast(p.c_ptr()), &n, const_cast(t.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2calc(const pspline2interpolant &p, const double t, double &x, double &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2calc(const_cast(p.c_ptr()), t, &x, &y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3calc(const pspline3interpolant &p, const double t, double &x, double &y, double &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3calc(const_cast(p.c_ptr()), t, &x, &y, &z, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2tangent(const pspline2interpolant &p, const double t, double &x, double &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2tangent(const_cast(p.c_ptr()), t, &x, &y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3tangent(const pspline3interpolant &p, const double t, double &x, double &y, double &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3tangent(const_cast(p.c_ptr()), t, &x, &y, &z, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff(const pspline2interpolant &p, const double t, double &x, double &dx, double &y, double &dy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2diff(const_cast(p.c_ptr()), t, &x, &dx, &y, &dy, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff(const pspline3interpolant &p, const double t, double &x, double &dx, double &y, double &dy, double &z, double &dz) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3diff(const_cast(p.c_ptr()), t, &x, &dx, &y, &dy, &z, &dz, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff2(const pspline2interpolant &p, const double t, double &x, double &dx, double &d2x, double &y, double &dy, double &d2y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline2diff2(const_cast(p.c_ptr()), t, &x, &dx, &d2x, &y, &dy, &d2y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff2(const pspline3interpolant &p, const double t, double &x, double &dx, double &d2x, double &y, double &dy, double &d2y, double &z, double &dz, double &d2z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pspline3diff2(const_cast(p.c_ptr()), t, &x, &dx, &d2x, &y, &dy, &d2y, &z, &dz, &d2z, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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(p.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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(p.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +RBF model. + +Never try to directly work with fields of this object - always use ALGLIB +functions to use this object. +*************************************************************************/ +_rbfmodel_owner::_rbfmodel_owner() +{ + p_struct = (alglib_impl::rbfmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::rbfmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_rbfmodel_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_rbfmodel_owner::_rbfmodel_owner(const _rbfmodel_owner &rhs) +{ + p_struct = (alglib_impl::rbfmodel*)alglib_impl::ae_malloc(sizeof(alglib_impl::rbfmodel), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_rbfmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_rbfmodel_owner& _rbfmodel_owner::operator=(const _rbfmodel_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_rbfmodel_clear(p_struct); + if( !alglib_impl::_rbfmodel_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_rbfmodel_owner::~_rbfmodel_owner() +{ + alglib_impl::_rbfmodel_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::rbfmodel* _rbfmodel_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::rbfmodel* _rbfmodel_owner::c_ptr() const +{ + return const_cast(p_struct); +} +rbfmodel::rbfmodel() : _rbfmodel_owner() +{ +} + +rbfmodel::rbfmodel(const rbfmodel &rhs):_rbfmodel_owner(rhs) +{ +} + +rbfmodel& rbfmodel::operator=(const rbfmodel &rhs) +{ + if( this==&rhs ) + return *this; + _rbfmodel_owner::operator=(rhs); + return *this; +} + +rbfmodel::~rbfmodel() +{ +} + + +/************************************************************************* +RBF solution report: +* TerminationType - termination type, positive values - success, + non-positive - failure. +*************************************************************************/ +_rbfreport_owner::_rbfreport_owner() +{ + p_struct = (alglib_impl::rbfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::rbfreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_rbfreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_rbfreport_owner::_rbfreport_owner(const _rbfreport_owner &rhs) +{ + p_struct = (alglib_impl::rbfreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::rbfreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_rbfreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_rbfreport_owner& _rbfreport_owner::operator=(const _rbfreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_rbfreport_clear(p_struct); + if( !alglib_impl::_rbfreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_rbfreport_owner::~_rbfreport_owner() +{ + alglib_impl::_rbfreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::rbfreport* _rbfreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::rbfreport* _rbfreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +rbfreport::rbfreport() : _rbfreport_owner() ,arows(p_struct->arows),acols(p_struct->acols),annz(p_struct->annz),iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype) +{ +} + +rbfreport::rbfreport(const rbfreport &rhs):_rbfreport_owner(rhs) ,arows(p_struct->arows),acols(p_struct->acols),annz(p_struct->annz),iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype) +{ +} + +rbfreport& rbfreport::operator=(const rbfreport &rhs) +{ + if( this==&rhs ) + return *this; + _rbfreport_owner::operator=(rhs); + return *this; +} + +rbfreport::~rbfreport() +{ +} + + +/************************************************************************* +This function serializes data structure to string. + +Important properties of s_out: +* it contains alphanumeric characters, dots, underscores, minus signs +* these symbols are grouped into words, which are separated by spaces + and Windows-style (CR+LF) newlines +* although serializer uses spaces and CR+LF as separators, you can + replace any separator character by arbitrary combination of spaces, + tabs, Windows or Unix newlines. It allows flexible reformatting of + the string in case you want to include it into text or XML file. + But you should not insert separators into the middle of the "words" + nor you should change case of letters. +* s_out can be freely moved between 32-bit and 64-bit systems, little + and big endian machines, and so on. You can serialize structure on + 32-bit machine and unserialize it on 64-bit one (or vice versa), or + serialize it on SPARC and unserialize on x86. You can also + serialize it in C++ version of ALGLIB and unserialize in C# one, + and vice versa. +*************************************************************************/ +void rbfserialize(rbfmodel &obj, std::string &s_out) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + alglib_impl::ae_int_t ssize; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_alloc_start(&serializer); + alglib_impl::rbfalloc(&serializer, obj.c_ptr(), &state); + ssize = alglib_impl::ae_serializer_get_alloc_size(&serializer); + s_out.clear(); + s_out.reserve((size_t)(ssize+1)); + alglib_impl::ae_serializer_sstart_str(&serializer, &s_out); + alglib_impl::rbfserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + if( s_out.length()>(size_t)ssize ) + throw ap_error("ALGLIB: serialization integrity error"); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} +/************************************************************************* +This function unserializes data structure from string. +*************************************************************************/ +void rbfunserialize(std::string &s_in, rbfmodel &obj) +{ + alglib_impl::ae_state state; + alglib_impl::ae_serializer serializer; + + alglib_impl::ae_state_init(&state); + try + { + alglib_impl::ae_serializer_init(&serializer); + alglib_impl::ae_serializer_ustart_str(&serializer, &s_in); + alglib_impl::rbfunserialize(&serializer, obj.c_ptr(), &state); + alglib_impl::ae_serializer_stop(&serializer); + alglib_impl::ae_serializer_clear(&serializer); + alglib_impl::ae_state_clear(&state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(state.error_msg); + } +} + +/************************************************************************* +This function creates RBF model for a scalar (NY=1) or vector (NY>1) +function in a NX-dimensional space (NX=2 or NX=3). + +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() (points do NOT have to be on a + regular grid) +3. (OPTIONAL) User chooses polynomial term by calling: + * RBFLinTerm() to set linear term + * RBFConstTerm() to set constant term + * RBFZeroTerm() to set zero term + By default, linear term is used. +4. User chooses specific RBF algorithm to use: either QNN (RBFSetAlgoQNN) + or ML (RBFSetAlgoMultiLayer). +5. User calls RBFBuildModel() function which rebuilds model according to + the specification +6. User may call RBFCalc() to calculate model value at the specified point, + RBFGridCalc() to calculate model values at the points of the regular + grid. User may extract model coefficients with RBFUnpack() call. + +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) + +NOTE 1: memory requirements. RBF models require amount of memory which is + proportional to the number of data points. Memory is allocated + during model construction, but most of this memory is freed after + model coefficients are calculated. + + Some approximate estimates for N centers with default settings are + given below: + * about 250*N*(sizeof(double)+2*sizeof(int)) bytes of memory is + needed during model construction stage. + * about 15*N*sizeof(double) bytes is needed after model is built. + For example, for N=100000 we may need 0.6 GB of memory to build + model, but just about 0.012 GB to store it. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcreate(const ae_int_t nx, const ae_int_t ny, rbfmodel &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfcreate(nx, ny, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset. + +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 - 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 + specific, 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: 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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetpoints(const rbfmodel &s, const real_2d_array &xy, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetpoints(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds dataset. + +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 - 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 + specific, 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: 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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetpoints(const rbfmodel &s, const real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = xy.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetpoints(const_cast(s.c_ptr()), const_cast(xy.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-QNN and it is good for point sets with +following properties: +a) all points are distinct +b) all points are well separated. +c) points distribution is approximately uniform. There is no "contour + lines", clusters of points, or other small-scale structures. + +Algorithm description: +1) interpolation centers are allocated to data points +2) interpolation radii are calculated as distances to the nearest centers + times Q coefficient (where Q is a value from [0.75,1.50]). +3) after performing (2) radii are transformed in order to avoid situation + when single outlier has very large radius and influences many points + across all dataset. Transformation has following form: + new_r[i] = min(r[i],Z*median(r[])) + where r[i] is I-th radius, median() is a median radius across entire + dataset, Z is user-specified value which controls amount of deviation + from median radius. + +When (a) is violated, we will be unable to build RBF model. When (b) or +(c) are violated, model will be built, but interpolation quality will be +low. See http://www.alglib.net/interpolation/ for more information on this +subject. + +This algorithm is used by default. + +Additional Q parameter controls smoothness properties of the RBF basis: +* Q<0.75 will give perfectly conditioned basis, but terrible smoothness + properties (RBF interpolant will have sharp peaks around function values) +* Q around 1.0 gives good balance between smoothness and condition number +* Q>1.5 will lead to badly conditioned systems and slow convergence of the + underlying linear solver (although smoothness will be very good) +* Q>2.0 will effectively make optimizer useless because it won't converge + within reasonable amount of iterations. It is possible to set such large + Q, but it is advised not to do so. + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + Q - Q parameter, Q>0, recommended value - 1.0 + Z - Z parameter, Z>0, recommended value - 5.0 + +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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgoqnn(const rbfmodel &s, const double q, const double z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetalgoqnn(const_cast(s.c_ptr()), q, z, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-QNN and it is good for point sets with +following properties: +a) all points are distinct +b) all points are well separated. +c) points distribution is approximately uniform. There is no "contour + lines", clusters of points, or other small-scale structures. + +Algorithm description: +1) interpolation centers are allocated to data points +2) interpolation radii are calculated as distances to the nearest centers + times Q coefficient (where Q is a value from [0.75,1.50]). +3) after performing (2) radii are transformed in order to avoid situation + when single outlier has very large radius and influences many points + across all dataset. Transformation has following form: + new_r[i] = min(r[i],Z*median(r[])) + where r[i] is I-th radius, median() is a median radius across entire + dataset, Z is user-specified value which controls amount of deviation + from median radius. + +When (a) is violated, we will be unable to build RBF model. When (b) or +(c) are violated, model will be built, but interpolation quality will be +low. See http://www.alglib.net/interpolation/ for more information on this +subject. + +This algorithm is used by default. + +Additional Q parameter controls smoothness properties of the RBF basis: +* Q<0.75 will give perfectly conditioned basis, but terrible smoothness + properties (RBF interpolant will have sharp peaks around function values) +* Q around 1.0 gives good balance between smoothness and condition number +* Q>1.5 will lead to badly conditioned systems and slow convergence of the + underlying linear solver (although smoothness will be very good) +* Q>2.0 will effectively make optimizer useless because it won't converge + within reasonable amount of iterations. It is possible to set such large + Q, but it is advised not to do so. + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + Q - Q parameter, Q>0, recommended value - 1.0 + Z - Z parameter, Z>0, recommended value - 5.0 + +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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgoqnn(const rbfmodel &s) +{ + alglib_impl::ae_state _alglib_env_state; + double q; + double z; + + q = 1.0; + z = 5.0; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetalgoqnn(const_cast(s.c_ptr()), q, z, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-ML. It builds multilayer RBF model, i.e. +model with subsequently decreasing radii, which allows us to combine +smoothness (due to large radii of the first layers) with exactness (due +to small radii of the last layers) and fast convergence. + +Internally RBF-ML uses many different means of acceleration, from sparse +matrices to KD-trees, which results in algorithm whose working time is +roughly proportional to N*log(N)*Density*RBase^2*NLayers, where N is a +number of points, Density is an average density if points per unit of the +interpolation space, RBase is an initial radius, NLayers is a number of +layers. + +RBF-ML is good for following kinds of interpolation problems: +1. "exact" problems (perfect fit) with well separated points +2. least squares problems with arbitrary distribution of points (algorithm + gives perfect fit where it is possible, and resorts to least squares + fit in the hard areas). +3. noisy problems where we want to apply some controlled amount of + smoothing. + +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. + LambdaV - regularization value, can be useful when solving problem + in the least squares sense. Optimal lambda is problem- + dependent and require trial and error. In our experience, + good lambda can be as large as 0.1, and you can use 0.001 + as initial guess. + Default value - 0.01, which is used when LambdaV is not + given. You can specify zero value, but it is not + recommended to do so. + +TUNING ALGORITHM + +In order to use this algorithm you have to choose three parameters: +* initial radius RBase +* number of layers in the model NLayers +* regularization coefficient LambdaV + +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. + +Regularization coefficient LambdaV controls smoothness of the individual +models built for each layer. We recommend you to use default value in case +you don't want to tune this parameter, because having non-zero LambdaV +accelerates and stabilizes internal iterative algorithm. In case you want +to suppress noise you can use LambdaV as additional parameter (larger +value = more smoothness) to tune. + +TYPICAL ERRORS + +1. Using initial radius which is too large. Memory requirements of the + RBF-ML are roughly proportional to N*Density*RBase^2 (where Density is + an average density of points per unit of the interpolation space). In + the extreme case of the very large RBase we will need O(N^2) units of + memory - and many layers in order to decrease radius to some reasonably + small value. + +2. 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. + +3. 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. + +4. Using too many layers - you will get too large and too slow model. This + model will perfectly reproduce your function, but maybe you will be + able to achieve similar results with less layers (and less memory). + + -- ALGLIB -- + Copyright 02.03.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgomultilayer(const rbfmodel &s, const double rbase, const ae_int_t nlayers, const double lambdav) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetalgomultilayer(const_cast(s.c_ptr()), rbase, nlayers, lambdav, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-ML. It builds multilayer RBF model, i.e. +model with subsequently decreasing radii, which allows us to combine +smoothness (due to large radii of the first layers) with exactness (due +to small radii of the last layers) and fast convergence. + +Internally RBF-ML uses many different means of acceleration, from sparse +matrices to KD-trees, which results in algorithm whose working time is +roughly proportional to N*log(N)*Density*RBase^2*NLayers, where N is a +number of points, Density is an average density if points per unit of the +interpolation space, RBase is an initial radius, NLayers is a number of +layers. + +RBF-ML is good for following kinds of interpolation problems: +1. "exact" problems (perfect fit) with well separated points +2. least squares problems with arbitrary distribution of points (algorithm + gives perfect fit where it is possible, and resorts to least squares + fit in the hard areas). +3. noisy problems where we want to apply some controlled amount of + smoothing. + +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. + LambdaV - regularization value, can be useful when solving problem + in the least squares sense. Optimal lambda is problem- + dependent and require trial and error. In our experience, + good lambda can be as large as 0.1, and you can use 0.001 + as initial guess. + Default value - 0.01, which is used when LambdaV is not + given. You can specify zero value, but it is not + recommended to do so. + +TUNING ALGORITHM + +In order to use this algorithm you have to choose three parameters: +* initial radius RBase +* number of layers in the model NLayers +* regularization coefficient LambdaV + +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. + +Regularization coefficient LambdaV controls smoothness of the individual +models built for each layer. We recommend you to use default value in case +you don't want to tune this parameter, because having non-zero LambdaV +accelerates and stabilizes internal iterative algorithm. In case you want +to suppress noise you can use LambdaV as additional parameter (larger +value = more smoothness) to tune. + +TYPICAL ERRORS + +1. Using initial radius which is too large. Memory requirements of the + RBF-ML are roughly proportional to N*Density*RBase^2 (where Density is + an average density of points per unit of the interpolation space). In + the extreme case of the very large RBase we will need O(N^2) units of + memory - and many layers in order to decrease radius to some reasonably + small value. + +2. 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. + +3. 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. + +4. Using too many layers - you will get too large and too slow model. This + model will perfectly reproduce your function, but maybe you will be + able to achieve similar results with less layers (and less memory). + + -- ALGLIB -- + Copyright 02.03.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgomultilayer(const rbfmodel &s, const double rbase, const ae_int_t nlayers) +{ + alglib_impl::ae_state _alglib_env_state; + double lambdav; + + lambdav = 0.01; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetalgomultilayer(const_cast(s.c_ptr()), rbase, nlayers, lambdav, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear term (model is a sum of radial basis functions +plus linear polynomial). This function won't have effect until next call +to RBFBuildModel(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetlinterm(const rbfmodel &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetlinterm(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets constant term (model is a sum of radial basis functions +plus constant). This function won't have effect until next call to +RBFBuildModel(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetconstterm(const rbfmodel &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetconstterm(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets zero term (model is a sum of radial basis functions +without polynomial term). This function won't have effect until next call +to RBFBuildModel(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetzeroterm(const rbfmodel &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfsetzeroterm(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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 + * -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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfbuildmodel(const rbfmodel &s, rbfreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfbuildmodel(const_cast(s.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function 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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc2(const rbfmodel &s, const double x0, const double x1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rbfcalc2(const_cast(s.c_ptr()), x0, x1, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc3(const rbfmodel &s, const double x0, const double x1, const double x2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rbfcalc3(const_cast(s.c_ptr()), x0, x1, x2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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(). + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalc(const rbfmodel &s, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfcalc(const_cast(s.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalcbuf(const rbfmodel &s, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfcalcbuf(const_cast(s.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfgridcalc2(const rbfmodel &s, const real_1d_array &x0, const ae_int_t n0, const real_1d_array &x1, const ae_int_t n1, real_2d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfgridcalc2(const_cast(s.c_ptr()), const_cast(x0.c_ptr()), n0, const_cast(x1.c_ptr()), n1, const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfunpack(const rbfmodel &s, ae_int_t &nx, ae_int_t &ny, real_2d_array &xwr, ae_int_t &nc, real_2d_array &v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rbfunpack(const_cast(s.c_ptr()), &nx, &ny, const_cast(xwr.c_ptr()), &nc, const_cast(v.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +2-dimensional spline inteprolant +*************************************************************************/ +_spline2dinterpolant_owner::_spline2dinterpolant_owner() +{ + p_struct = (alglib_impl::spline2dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline2dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline2dinterpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline2dinterpolant_owner::_spline2dinterpolant_owner(const _spline2dinterpolant_owner &rhs) +{ + p_struct = (alglib_impl::spline2dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline2dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline2dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline2dinterpolant_owner& _spline2dinterpolant_owner::operator=(const _spline2dinterpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_spline2dinterpolant_clear(p_struct); + if( !alglib_impl::_spline2dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_spline2dinterpolant_owner::~_spline2dinterpolant_owner() +{ + alglib_impl::_spline2dinterpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::spline2dinterpolant* _spline2dinterpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::spline2dinterpolant* _spline2dinterpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +spline2dinterpolant::spline2dinterpolant() : _spline2dinterpolant_owner() +{ +} + +spline2dinterpolant::spline2dinterpolant(const spline2dinterpolant &rhs):_spline2dinterpolant_owner(rhs) +{ +} + +spline2dinterpolant& spline2dinterpolant::operator=(const spline2dinterpolant &rhs) +{ + if( this==&rhs ) + return *this; + _spline2dinterpolant_owner::operator=(rhs); + return *this; +} + +spline2dinterpolant::~spline2dinterpolant() +{ +} + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +double spline2dcalc(const spline2dinterpolant &c, const double x, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spline2dcalc(const_cast(c.c_ptr()), x, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2ddiff(const spline2dinterpolant &c, const double x, const double y, double &f, double &fx, double &fy, double &fxy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2ddiff(const_cast(c.c_ptr()), x, y, &f, &fx, &fy, &fxy, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransxy(const spline2dinterpolant &c, const double ax, const double bx, const double ay, const double by) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dlintransxy(const_cast(c.c_ptr()), ax, bx, ay, by, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransf(const spline2dinterpolant &c, const double a, const double b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dlintransf(const_cast(c.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine makes the copy of the spline model. + +Input parameters: + C - spline interpolant + +Output parameters: + CC - spline copy + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dcopy(const spline2dinterpolant &c, spline2dinterpolant &cc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dcopy(const_cast(c.c_ptr()), const_cast(cc.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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] + + -- ALGLIB routine -- + 15 May, 2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebicubic(const real_2d_array &a, const ae_int_t oldheight, const ae_int_t oldwidth, real_2d_array &b, const ae_int_t newheight, const ae_int_t newwidth) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dresamplebicubic(const_cast(a.c_ptr()), oldheight, oldwidth, const_cast(b.c_ptr()), newheight, newwidth, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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] + + -- ALGLIB routine -- + 09.07.2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebilinear(const real_2d_array &a, const ae_int_t oldheight, const ae_int_t oldwidth, real_2d_array &b, const ae_int_t newheight, const ae_int_t newwidth) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dresamplebilinear(const_cast(a.c_ptr()), oldheight, oldwidth, const_cast(b.c_ptr()), newheight, newwidth, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinearv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &f, const ae_int_t d, spline2dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dbuildbilinearv(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, const_cast(f.c_ptr()), d, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubicv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &f, const ae_int_t d, spline2dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dbuildbicubicv(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, const_cast(f.c_ptr()), d, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine calculates bilinear or bicubic vector-valued spline at the +given point (X,Y). + +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcvbuf(const spline2dinterpolant &c, const double x, const double y, real_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dcalcvbuf(const_cast(c.c_ptr()), x, y, const_cast(f.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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. + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcv(const spline2dinterpolant &c, const double x, const double y, real_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dcalcv(const_cast(c.c_ptr()), x, y, const_cast(f.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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] + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpackv(const spline2dinterpolant &c, ae_int_t &m, ae_int_t &n, ae_int_t &d, real_2d_array &tbl) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dunpackv(const_cast(c.c_ptr()), &m, &n, &d, const_cast(tbl.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBilinearV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinear(const real_1d_array &x, const real_1d_array &y, const real_2d_array &f, const ae_int_t m, const ae_int_t n, spline2dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dbuildbilinear(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(f.c_ptr()), m, n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBicubicV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubic(const real_1d_array &x, const real_1d_array &y, const real_2d_array &f, const ae_int_t m, const ae_int_t n, spline2dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dbuildbicubic(const_cast(x.c_ptr()), const_cast(y.c_ptr()), const_cast(f.c_ptr()), m, n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DUnpackV(), which is more flexible +and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpack(const spline2dinterpolant &c, ae_int_t &m, ae_int_t &n, real_2d_array &tbl) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline2dunpack(const_cast(c.c_ptr()), &m, &n, const_cast(tbl.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +3-dimensional spline inteprolant +*************************************************************************/ +_spline3dinterpolant_owner::_spline3dinterpolant_owner() +{ + p_struct = (alglib_impl::spline3dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline3dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline3dinterpolant_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline3dinterpolant_owner::_spline3dinterpolant_owner(const _spline3dinterpolant_owner &rhs) +{ + p_struct = (alglib_impl::spline3dinterpolant*)alglib_impl::ae_malloc(sizeof(alglib_impl::spline3dinterpolant), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_spline3dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_spline3dinterpolant_owner& _spline3dinterpolant_owner::operator=(const _spline3dinterpolant_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_spline3dinterpolant_clear(p_struct); + if( !alglib_impl::_spline3dinterpolant_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_spline3dinterpolant_owner::~_spline3dinterpolant_owner() +{ + alglib_impl::_spline3dinterpolant_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::spline3dinterpolant* _spline3dinterpolant_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::spline3dinterpolant* _spline3dinterpolant_owner::c_ptr() const +{ + return const_cast(p_struct); +} +spline3dinterpolant::spline3dinterpolant() : _spline3dinterpolant_owner() +{ +} + +spline3dinterpolant::spline3dinterpolant(const spline3dinterpolant &rhs):_spline3dinterpolant_owner(rhs) +{ +} + +spline3dinterpolant& spline3dinterpolant::operator=(const spline3dinterpolant &rhs) +{ + if( this==&rhs ) + return *this; + _spline3dinterpolant_owner::operator=(rhs); + return *this; +} + +spline3dinterpolant::~spline3dinterpolant() +{ +} + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +double spline3dcalc(const spline3dinterpolant &c, const double x, const double y, const double z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spline3dcalc(const_cast(c.c_ptr()), x, y, z, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransxyz(const spline3dinterpolant &c, const double ax, const double bx, const double ay, const double by, const double az, const double bz) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dlintransxyz(const_cast(c.c_ptr()), ax, bx, ay, by, az, bz, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransf(const spline3dinterpolant &c, const double a, const double b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dlintransf(const_cast(c.c_ptr()), a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +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[..] ... + ... + + -- ALGLIB routine -- + 26.04.2012 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline3dresampletrilinear(const real_1d_array &a, const ae_int_t oldzcount, const ae_int_t oldycount, const ae_int_t oldxcount, const ae_int_t newzcount, const ae_int_t newycount, const ae_int_t newxcount, real_1d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dresampletrilinear(const_cast(a.c_ptr()), oldzcount, oldycount, oldxcount, newzcount, newycount, newxcount, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine builds 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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dbuildtrilinearv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &z, const ae_int_t l, const real_1d_array &f, const ae_int_t d, spline3dinterpolant &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dbuildtrilinearv(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, const_cast(z.c_ptr()), l, const_cast(f.c_ptr()), d, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcvbuf(const spline3dinterpolant &c, const double x, const double y, const double z, real_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dcalcvbuf(const_cast(c.c_ptr()), x, y, z, const_cast(f.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcv(const spline3dinterpolant &c, const double x, const double y, const double z, real_1d_array &f) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dcalcv(const_cast(c.c_ptr()), x, y, z, const_cast(f.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine 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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dunpackv(const spline3dinterpolant &c, ae_int_t &n, ae_int_t &m, ae_int_t &l, ae_int_t &d, ae_int_t &stype, real_2d_array &tbl) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spline3dunpackv(const_cast(c.c_ptr()), &n, &m, &l, &d, &stype, const_cast(tbl.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static double idwint_idwqfactor = 1.5; +static ae_int_t idwint_idwkmin = 5; +static double idwint_idwcalcq(idwinterpolant* z, + /* Real */ ae_vector* x, + ae_int_t k, + ae_state *_state); +static void idwint_idwinit1(ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state); +static void idwint_idwinternalsolver(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_vector* temp, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* x, + double* taskrcond, + ae_state *_state); + + +static void ratint_barycentricnormalize(barycentricinterpolant* b, + ae_state *_state); + + + + +static void spline1d_spline1dgriddiffcubicinternal(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d, + /* Real */ ae_vector* a1, + /* Real */ ae_vector* a2, + /* Real */ ae_vector* a3, + /* Real */ ae_vector* b, + /* Real */ ae_vector* dt, + ae_state *_state); +static void spline1d_heapsortpoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +static void spline1d_heapsortppoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Integer */ ae_vector* p, + ae_int_t n, + ae_state *_state); +static void spline1d_solvetridiagonal(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + ae_int_t n, + /* Real */ ae_vector* x, + ae_state *_state); +static void spline1d_solvecyclictridiagonal(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + ae_int_t n, + /* Real */ ae_vector* x, + ae_state *_state); +static double spline1d_diffthreepoint(double t, + double x0, + double f0, + double x1, + double f1, + double x2, + double f2, + ae_state *_state); +static void spline1d_hermitecalc(double p0, + double m0, + double p1, + double m1, + double t, + double* s, + double* ds, + ae_state *_state); +static double spline1d_rescaleval(double a0, + double b0, + double a1, + double b1, + double t, + ae_state *_state); + + +static void lsfit_spline1dfitinternal(ae_int_t st, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +static void lsfit_lsfitlinearinternal(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +static void lsfit_lsfitclearrequestfields(lsfitstate* state, + ae_state *_state); +static void lsfit_barycentriccalcbasis(barycentricinterpolant* b, + double t, + /* Real */ ae_vector* y, + ae_state *_state); +static void lsfit_internalchebyshevfit(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +static void lsfit_barycentricfitwcfixedd(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t d, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state); +static void lsfit_clearreport(lsfitreport* rep, ae_state *_state); +static void lsfit_estimateerrors(/* Real */ ae_matrix* f1, + /* Real */ ae_vector* f0, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* x, + /* Real */ ae_vector* s, + ae_int_t n, + ae_int_t k, + lsfitreport* rep, + /* Real */ ae_matrix* z, + ae_int_t zkind, + ae_state *_state); + + +static void pspline_pspline2par(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t pt, + /* Real */ ae_vector* p, + ae_state *_state); +static void pspline_pspline3par(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t pt, + /* Real */ ae_vector* p, + ae_state *_state); + + +static double rbf_eps = 1.0E-6; +static ae_int_t rbf_mxnx = 3; +static double rbf_rbffarradius = 6; +static double rbf_rbfnearradius = 2.1; +static double rbf_rbfmlradius = 3; +static ae_int_t rbf_rbffirstversion = 0; +static void rbf_rbfgridpoints(rbfmodel* s, ae_state *_state); +static void rbf_rbfradnn(rbfmodel* s, + double q, + double z, + ae_state *_state); +static ae_bool rbf_buildlinearmodel(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t ny, + ae_int_t modeltype, + /* Real */ ae_matrix* v, + ae_state *_state); +static void rbf_buildrbfmodellsqr(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + /* Real */ ae_matrix* xc, + /* Real */ ae_vector* r, + ae_int_t n, + ae_int_t nc, + ae_int_t ny, + kdtree* pointstree, + kdtree* centerstree, + double epsort, + double epserr, + ae_int_t maxits, + ae_int_t* gnnz, + ae_int_t* snnz, + /* Real */ ae_matrix* w, + ae_int_t* info, + ae_int_t* iterationscount, + ae_int_t* nmv, + ae_state *_state); +static void rbf_buildrbfmlayersmodellsqr(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + /* Real */ ae_matrix* xc, + double rval, + /* Real */ ae_vector* r, + ae_int_t n, + ae_int_t* nc, + ae_int_t ny, + ae_int_t nlayers, + kdtree* centerstree, + double epsort, + double epserr, + ae_int_t maxits, + double lambdav, + ae_int_t* annz, + /* Real */ ae_matrix* w, + ae_int_t* info, + ae_int_t* iterationscount, + ae_int_t* nmv, + ae_state *_state); + + +static void spline2d_bicubiccalcderivatives(/* Real */ ae_matrix* a, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* dx, + /* Real */ ae_matrix* dy, + /* Real */ ae_matrix* dxy, + ae_state *_state); + + +static void spline3d_spline3ddiff(spline3dinterpolant* c, + double x, + double y, + double z, + double* f, + double* fx, + double* fy, + double* fxy, + ae_state *_state); + + + + + +/************************************************************************* +IDW interpolation + +INPUT PARAMETERS: + Z - IDW interpolant built with one of model building + subroutines. + X - array[0..NX-1], interpolation point + +Result: + IDW interpolant Z(X) + + -- ALGLIB -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +double idwcalc(idwinterpolant* z, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + double r; + double s; + double w; + double v1; + double v2; + double d0; + double di; + double result; + + + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + k = 0; + + /* + * Query + */ + if( z->modeltype==0 ) + { + + /* + * NQ/NW-based model + */ + k = kdtreequeryknn(&z->tree, x, z->nw, ae_true, _state); + kdtreequeryresultsdistances(&z->tree, &z->rbuf, _state); + kdtreequeryresultstags(&z->tree, &z->tbuf, _state); + } + if( z->modeltype==1 ) + { + + /* + * R-based model + */ + k = kdtreequeryrnn(&z->tree, x, z->r, ae_true, _state); + kdtreequeryresultsdistances(&z->tree, &z->rbuf, _state); + kdtreequeryresultstags(&z->tree, &z->tbuf, _state); + if( ktree, x, idwint_idwkmin, ae_true, _state); + kdtreequeryresultsdistances(&z->tree, &z->rbuf, _state); + kdtreequeryresultstags(&z->tree, &z->tbuf, _state); + } + } + + /* + * initialize weights for linear/quadratic members calculation. + * + * NOTE 1: weights are calculated using NORMALIZED modified + * Shepard's formula. Original formula gives w(i) = sqr((R-di)/(R*di)), + * where di is i-th distance, R is max(di). Modified formula have + * following form: + * w_mod(i) = 1, if di=d0 + * w_mod(i) = w(i)/w(0), if di<>d0 + * + * NOTE 2: self-match is USED for this query + * + * NOTE 3: last point almost always gain zero weight, but it MUST + * be used for fitting because sometimes it will gain NON-ZERO + * weight - for example, when all distances are equal. + */ + r = z->rbuf.ptr.p_double[k-1]; + d0 = z->rbuf.ptr.p_double[0]; + result = 0; + s = 0; + for(i=0; i<=k-1; i++) + { + di = z->rbuf.ptr.p_double[i]; + if( ae_fp_eq(di,d0) ) + { + + /* + * distance is equal to shortest, set it 1.0 + * without explicitly calculating (which would give + * us same result, but 'll expose us to the risk of + * division by zero). + */ + w = 1; + } + else + { + + /* + * use normalized formula + */ + v1 = (r-di)/(r-d0); + v2 = d0/di; + w = ae_sqr(v1*v2, _state); + } + result = result+w*idwint_idwcalcq(z, x, z->tbuf.ptr.p_int[i], _state); + s = s+w; + } + result = result/s; + return result; +} + + +/************************************************************************* +IDW interpolant using modified Shepard method for uniform point +distributions. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function type, either: + * 0 constant model. Just for demonstration only, worst + model ever. + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + * -1 "fast" linear model, use with caution!!! It is + significantly faster than linear/quadratic and better + than constant model. But it is less robust (especially + in the presence of noise). + NQ - number of points used to calculate nodal functions (ignored + for constant models). NQ should be LARGER than: + * max(1.5*(1+NX),2^NX+1) for linear model, + * max(3/4*(NX+2)*(NX+1),2^NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, worst - with constant + models + * when N is large, NQ and NW must be significantly smaller than N both + to obtain optimal performance and to obtain optimal accuracy. In 2 or + 3-dimensional tasks NQ=15 and NW=25 are good values to start with. + * NQ and NW may be greater than N. In such cases they will be + automatically decreased. + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + * this subroutine assumes that point distribution is uniform at the small + scales. If it isn't - for example, points are concentrated along + "lines", but "lines" distribution is uniform at the larger scale - then + you should use IDWBuildModifiedShepardR() + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepard(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t j2; + ae_int_t j3; + double v; + double r; + double s; + double d0; + double di; + double v1; + double v2; + ae_int_t nc; + ae_int_t offs; + ae_vector x; + ae_vector qrbuf; + ae_matrix qxybuf; + ae_vector y; + ae_matrix fmatrix; + ae_vector w; + ae_vector qsol; + ae_vector temp; + ae_vector tags; + ae_int_t info; + double taskrcond; + + ae_frame_make(_state, &_frame_block); + _idwinterpolant_clear(z); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&qrbuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&qxybuf, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&qsol, 0, DT_REAL, _state, ae_true); + ae_vector_init(&temp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tags, 0, DT_INT, _state, ae_true); + + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + nc = 0; + + /* + * assertions + */ + ae_assert(n>0, "IDWBuildModifiedShepard: N<=0!", _state); + ae_assert(nx>=1, "IDWBuildModifiedShepard: NX<1!", _state); + ae_assert(d>=-1&&d<=2, "IDWBuildModifiedShepard: D<>-1 and D<>0 and D<>1 and D<>2!", _state); + + /* + * Correct parameters if needed + */ + if( d==1 ) + { + nq = ae_maxint(nq, ae_iceil(idwint_idwqfactor*(1+nx), _state)+1, _state); + nq = ae_maxint(nq, ae_round(ae_pow(2, nx, _state), _state)+1, _state); + } + if( d==2 ) + { + nq = ae_maxint(nq, ae_iceil(idwint_idwqfactor*(nx+2)*(nx+1)/2, _state)+1, _state); + nq = ae_maxint(nq, ae_round(ae_pow(2, nx, _state), _state)+1, _state); + } + nw = ae_maxint(nw, ae_round(ae_pow(2, nx, _state), _state)+1, _state); + nq = ae_minint(nq, n, _state); + nw = ae_minint(nw, n, _state); + + /* + * primary initialization of Z + */ + idwint_idwinit1(n, nx, d, nq, nw, z, _state); + z->modeltype = 0; + + /* + * Create KD-tree + */ + ae_vector_set_length(&tags, n, _state); + for(i=0; i<=n-1; i++) + { + tags.ptr.p_int[i] = i; + } + kdtreebuildtagged(xy, &tags, n, nx, 1, 2, &z->tree, _state); + + /* + * build nodal functions + */ + ae_vector_set_length(&temp, nq+1, _state); + ae_vector_set_length(&x, nx, _state); + ae_vector_set_length(&qrbuf, nq, _state); + ae_matrix_set_length(&qxybuf, nq, nx+1, _state); + if( d==-1 ) + { + ae_vector_set_length(&w, nq, _state); + } + if( d==1 ) + { + ae_vector_set_length(&y, nq, _state); + ae_vector_set_length(&w, nq, _state); + ae_vector_set_length(&qsol, nx, _state); + + /* + * NX for linear members, + * 1 for temporary storage + */ + ae_matrix_set_length(&fmatrix, nq, nx+1, _state); + } + if( d==2 ) + { + ae_vector_set_length(&y, nq, _state); + ae_vector_set_length(&w, nq, _state); + ae_vector_set_length(&qsol, nx+ae_round(nx*(nx+1)*0.5, _state), _state); + + /* + * NX for linear members, + * Round(NX*(NX+1)*0.5) for quadratic model, + * 1 for temporary storage + */ + ae_matrix_set_length(&fmatrix, nq, nx+ae_round(nx*(nx+1)*0.5, _state)+1, _state); + } + for(i=0; i<=n-1; i++) + { + + /* + * Initialize center and function value. + * If D=0 it is all what we need + */ + ae_v_move(&z->q.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx)); + if( d==0 ) + { + continue; + } + + /* + * calculate weights for linear/quadratic members calculation. + * + * NOTE 1: weights are calculated using NORMALIZED modified + * Shepard's formula. Original formula is w(i) = sqr((R-di)/(R*di)), + * where di is i-th distance, R is max(di). Modified formula have + * following form: + * w_mod(i) = 1, if di=d0 + * w_mod(i) = w(i)/w(0), if di<>d0 + * + * NOTE 2: self-match is NOT used for this query + * + * NOTE 3: last point almost always gain zero weight, but it MUST + * be used for fitting because sometimes it will gain NON-ZERO + * weight - for example, when all distances are equal. + */ + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx-1)); + k = kdtreequeryknn(&z->tree, &x, nq, ae_false, _state); + kdtreequeryresultsxy(&z->tree, &qxybuf, _state); + kdtreequeryresultsdistances(&z->tree, &qrbuf, _state); + r = qrbuf.ptr.p_double[k-1]; + d0 = qrbuf.ptr.p_double[0]; + for(j=0; j<=k-1; j++) + { + di = qrbuf.ptr.p_double[j]; + if( ae_fp_eq(di,d0) ) + { + + /* + * distance is equal to shortest, set it 1.0 + * without explicitly calculating (which would give + * us same result, but 'll expose us to the risk of + * division by zero). + */ + w.ptr.p_double[j] = 1; + } + else + { + + /* + * use normalized formula + */ + v1 = (r-di)/(r-d0); + v2 = d0/di; + w.ptr.p_double[j] = ae_sqr(v1*v2, _state); + } + } + + /* + * calculate linear/quadratic members + */ + if( d==-1 ) + { + + /* + * "Fast" linear nodal function calculated using + * inverse distance weighting + */ + for(j=0; j<=nx-1; j++) + { + x.ptr.p_double[j] = 0; + } + s = 0; + for(j=0; j<=k-1; j++) + { + + /* + * calculate J-th inverse distance weighted gradient: + * grad_k = (y_j-y_k)*(x_j-x_k)/sqr(norm(x_j-x_k)) + * grad = sum(wk*grad_k)/sum(w_k) + */ + v = 0; + for(j2=0; j2<=nx-1; j2++) + { + v = v+ae_sqr(qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2], _state); + } + + /* + * Although x_j<>x_k, sqr(norm(x_j-x_k)) may be zero due to + * underflow. If it is, we assume than J-th gradient is zero + * (i.e. don't add anything) + */ + if( ae_fp_neq(v,0) ) + { + for(j2=0; j2<=nx-1; j2++) + { + x.ptr.p_double[j2] = x.ptr.p_double[j2]+w.ptr.p_double[j]*(qxybuf.ptr.pp_double[j][nx]-xy->ptr.pp_double[i][nx])*(qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2])/v; + } + } + s = s+w.ptr.p_double[j]; + } + for(j=0; j<=nx-1; j++) + { + z->q.ptr.pp_double[i][nx+1+j] = x.ptr.p_double[j]/s; + } + } + else + { + + /* + * Least squares models: build + */ + if( d==1 ) + { + + /* + * Linear nodal function calculated using + * least squares fitting to its neighbors + */ + for(j=0; j<=k-1; j++) + { + for(j2=0; j2<=nx-1; j2++) + { + fmatrix.ptr.pp_double[j][j2] = qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2]; + } + y.ptr.p_double[j] = qxybuf.ptr.pp_double[j][nx]-xy->ptr.pp_double[i][nx]; + } + nc = nx; + } + if( d==2 ) + { + + /* + * Quadratic nodal function calculated using + * least squares fitting to its neighbors + */ + for(j=0; j<=k-1; j++) + { + offs = 0; + for(j2=0; j2<=nx-1; j2++) + { + fmatrix.ptr.pp_double[j][offs] = qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2]; + offs = offs+1; + } + for(j2=0; j2<=nx-1; j2++) + { + for(j3=j2; j3<=nx-1; j3++) + { + fmatrix.ptr.pp_double[j][offs] = (qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2])*(qxybuf.ptr.pp_double[j][j3]-xy->ptr.pp_double[i][j3]); + offs = offs+1; + } + } + y.ptr.p_double[j] = qxybuf.ptr.pp_double[j][nx]-xy->ptr.pp_double[i][nx]; + } + nc = nx+ae_round(nx*(nx+1)*0.5, _state); + } + idwint_idwinternalsolver(&y, &w, &fmatrix, &temp, k, nc, &info, &qsol, &taskrcond, _state); + + /* + * Least squares models: copy results + */ + if( info>0 ) + { + + /* + * LLS task is solved, copy results + */ + z->debugworstrcond = ae_minreal(z->debugworstrcond, taskrcond, _state); + z->debugbestrcond = ae_maxreal(z->debugbestrcond, taskrcond, _state); + for(j=0; j<=nc-1; j++) + { + z->q.ptr.pp_double[i][nx+1+j] = qsol.ptr.p_double[j]; + } + } + else + { + + /* + * Solver failure, very strange, but we will use + * zero values to handle it. + */ + z->debugsolverfailures = z->debugsolverfailures+1; + for(j=0; j<=nc-1; j++) + { + z->q.ptr.pp_double[i][nx+1+j] = 0; + } + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +IDW interpolant using modified Shepard method for non-uniform datasets. + +This type of model uses constant nodal functions and interpolates using +all nodes which are closer than user-specified radius R. It may be used +when points distribution is non-uniform at the small scale, but it is at +the distances as large as R. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + R - radius, R>0 + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: +* if there is less than IDWKMin points within R-ball, algorithm selects + IDWKMin closest ones, so that continuity properties of interpolant are + preserved even far from points. + + -- ALGLIB PROJECT -- + Copyright 11.04.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepardr(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + double r, + idwinterpolant* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector tags; + + ae_frame_make(_state, &_frame_block); + _idwinterpolant_clear(z); + ae_vector_init(&tags, 0, DT_INT, _state, ae_true); + + + /* + * assertions + */ + ae_assert(n>0, "IDWBuildModifiedShepardR: N<=0!", _state); + ae_assert(nx>=1, "IDWBuildModifiedShepardR: NX<1!", _state); + ae_assert(ae_fp_greater(r,0), "IDWBuildModifiedShepardR: R<=0!", _state); + + /* + * primary initialization of Z + */ + idwint_idwinit1(n, nx, 0, 0, n, z, _state); + z->modeltype = 1; + z->r = r; + + /* + * Create KD-tree + */ + ae_vector_set_length(&tags, n, _state); + for(i=0; i<=n-1; i++) + { + tags.ptr.p_int[i] = i; + } + kdtreebuildtagged(xy, &tags, n, nx, 1, 2, &z->tree, _state); + + /* + * build nodal functions + */ + for(i=0; i<=n-1; i++) + { + ae_v_move(&z->q.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +IDW model for noisy data. + +This subroutine may be used to handle noisy data, i.e. data with noise in +OUTPUT values. It differs from IDWBuildModifiedShepard() in the following +aspects: +* nodal functions are not constrained to pass through nodes: Qi(xi)<>yi, + i.e. we have fitting instead of interpolation. +* weights which are used during least squares fitting stage are all equal + to 1.0 (independently of distance) +* "fast"-linear or constant nodal functions are not supported (either not + robust enough or too rigid) + +This problem require far more complex tuning than interpolation problems. +Below you can find some recommendations regarding this problem: +* focus on tuning NQ; it controls noise reduction. As for NW, you can just + make it equal to 2*NQ. +* you can use cross-validation to determine optimal NQ. +* optimal NQ is a result of complex tradeoff between noise level (more + noise = larger NQ required) and underlying function complexity (given + fixed N, larger NQ means smoothing of compex features in the data). For + example, NQ=N will reduce noise to the minimum level possible, but you + will end up with just constant/linear/quadratic (depending on D) least + squares model for the whole dataset. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function degree, either: + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models (or for very + noisy problems). + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + NQ - number of points used to calculate nodal functions. NQ should + be significantly larger than 1.5 times the number of + coefficients in a nodal function to overcome effects of noise: + * larger than 1.5*(1+NX) for linear model, + * larger than 3/4*(NX+2)*(NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ or larger + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, linear models are not + recommended to use unless you are pretty sure that it is what you want + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildnoisy(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t j2; + ae_int_t j3; + double v; + ae_int_t nc; + ae_int_t offs; + double taskrcond; + ae_vector x; + ae_vector qrbuf; + ae_matrix qxybuf; + ae_vector y; + ae_vector w; + ae_matrix fmatrix; + ae_vector qsol; + ae_vector tags; + ae_vector temp; + ae_int_t info; + + ae_frame_make(_state, &_frame_block); + _idwinterpolant_clear(z); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&qrbuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&qxybuf, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&qsol, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tags, 0, DT_INT, _state, ae_true); + ae_vector_init(&temp, 0, DT_REAL, _state, ae_true); + + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + nc = 0; + + /* + * assertions + */ + ae_assert(n>0, "IDWBuildNoisy: N<=0!", _state); + ae_assert(nx>=1, "IDWBuildNoisy: NX<1!", _state); + ae_assert(d>=1&&d<=2, "IDWBuildNoisy: D<>1 and D<>2!", _state); + + /* + * Correct parameters if needed + */ + if( d==1 ) + { + nq = ae_maxint(nq, ae_iceil(idwint_idwqfactor*(1+nx), _state)+1, _state); + } + if( d==2 ) + { + nq = ae_maxint(nq, ae_iceil(idwint_idwqfactor*(nx+2)*(nx+1)/2, _state)+1, _state); + } + nw = ae_maxint(nw, ae_round(ae_pow(2, nx, _state), _state)+1, _state); + nq = ae_minint(nq, n, _state); + nw = ae_minint(nw, n, _state); + + /* + * primary initialization of Z + */ + idwint_idwinit1(n, nx, d, nq, nw, z, _state); + z->modeltype = 0; + + /* + * Create KD-tree + */ + ae_vector_set_length(&tags, n, _state); + for(i=0; i<=n-1; i++) + { + tags.ptr.p_int[i] = i; + } + kdtreebuildtagged(xy, &tags, n, nx, 1, 2, &z->tree, _state); + + /* + * build nodal functions + * (special algorithm for noisy data is used) + */ + ae_vector_set_length(&temp, nq+1, _state); + ae_vector_set_length(&x, nx, _state); + ae_vector_set_length(&qrbuf, nq, _state); + ae_matrix_set_length(&qxybuf, nq, nx+1, _state); + if( d==1 ) + { + ae_vector_set_length(&y, nq, _state); + ae_vector_set_length(&w, nq, _state); + ae_vector_set_length(&qsol, 1+nx, _state); + + /* + * 1 for constant member, + * NX for linear members, + * 1 for temporary storage + */ + ae_matrix_set_length(&fmatrix, nq, 1+nx+1, _state); + } + if( d==2 ) + { + ae_vector_set_length(&y, nq, _state); + ae_vector_set_length(&w, nq, _state); + ae_vector_set_length(&qsol, 1+nx+ae_round(nx*(nx+1)*0.5, _state), _state); + + /* + * 1 for constant member, + * NX for linear members, + * Round(NX*(NX+1)*0.5) for quadratic model, + * 1 for temporary storage + */ + ae_matrix_set_length(&fmatrix, nq, 1+nx+ae_round(nx*(nx+1)*0.5, _state)+1, _state); + } + for(i=0; i<=n-1; i++) + { + + /* + * Initialize center. + */ + ae_v_move(&z->q.ptr.pp_double[i][0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx-1)); + + /* + * Calculate linear/quadratic members + * using least squares fit + * NOTE 1: all weight are equal to 1.0 + * NOTE 2: self-match is USED for this query + */ + ae_v_move(&x.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nx-1)); + k = kdtreequeryknn(&z->tree, &x, nq, ae_true, _state); + kdtreequeryresultsxy(&z->tree, &qxybuf, _state); + kdtreequeryresultsdistances(&z->tree, &qrbuf, _state); + if( d==1 ) + { + + /* + * Linear nodal function calculated using + * least squares fitting to its neighbors + */ + for(j=0; j<=k-1; j++) + { + fmatrix.ptr.pp_double[j][0] = 1.0; + for(j2=0; j2<=nx-1; j2++) + { + fmatrix.ptr.pp_double[j][1+j2] = qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2]; + } + y.ptr.p_double[j] = qxybuf.ptr.pp_double[j][nx]; + w.ptr.p_double[j] = 1; + } + nc = 1+nx; + } + if( d==2 ) + { + + /* + * Quadratic nodal function calculated using + * least squares fitting to its neighbors + */ + for(j=0; j<=k-1; j++) + { + fmatrix.ptr.pp_double[j][0] = 1; + offs = 1; + for(j2=0; j2<=nx-1; j2++) + { + fmatrix.ptr.pp_double[j][offs] = qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2]; + offs = offs+1; + } + for(j2=0; j2<=nx-1; j2++) + { + for(j3=j2; j3<=nx-1; j3++) + { + fmatrix.ptr.pp_double[j][offs] = (qxybuf.ptr.pp_double[j][j2]-xy->ptr.pp_double[i][j2])*(qxybuf.ptr.pp_double[j][j3]-xy->ptr.pp_double[i][j3]); + offs = offs+1; + } + } + y.ptr.p_double[j] = qxybuf.ptr.pp_double[j][nx]; + w.ptr.p_double[j] = 1; + } + nc = 1+nx+ae_round(nx*(nx+1)*0.5, _state); + } + idwint_idwinternalsolver(&y, &w, &fmatrix, &temp, k, nc, &info, &qsol, &taskrcond, _state); + + /* + * Least squares models: copy results + */ + if( info>0 ) + { + + /* + * LLS task is solved, copy results + */ + z->debugworstrcond = ae_minreal(z->debugworstrcond, taskrcond, _state); + z->debugbestrcond = ae_maxreal(z->debugbestrcond, taskrcond, _state); + for(j=0; j<=nc-1; j++) + { + z->q.ptr.pp_double[i][nx+j] = qsol.ptr.p_double[j]; + } + } + else + { + + /* + * Solver failure, very strange, but we will use + * zero values to handle it. + */ + z->debugsolverfailures = z->debugsolverfailures+1; + v = 0; + for(j=0; j<=k-1; j++) + { + v = v+qxybuf.ptr.pp_double[j][nx]; + } + z->q.ptr.pp_double[i][nx] = v/k; + for(j=0; j<=nc-2; j++) + { + z->q.ptr.pp_double[i][nx+1+j] = 0; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine: K-th nodal function calculation + + -- ALGLIB -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +static double idwint_idwcalcq(idwinterpolant* z, + /* Real */ ae_vector* x, + ae_int_t k, + ae_state *_state) +{ + ae_int_t nx; + ae_int_t i; + ae_int_t j; + ae_int_t offs; + double result; + + + nx = z->nx; + + /* + * constant member + */ + result = z->q.ptr.pp_double[k][nx]; + + /* + * linear members + */ + if( z->d>=1 ) + { + for(i=0; i<=nx-1; i++) + { + result = result+z->q.ptr.pp_double[k][nx+1+i]*(x->ptr.p_double[i]-z->q.ptr.pp_double[k][i]); + } + } + + /* + * quadratic members + */ + if( z->d>=2 ) + { + offs = nx+1+nx; + for(i=0; i<=nx-1; i++) + { + for(j=i; j<=nx-1; j++) + { + result = result+z->q.ptr.pp_double[k][offs]*(x->ptr.p_double[i]-z->q.ptr.pp_double[k][i])*(x->ptr.p_double[j]-z->q.ptr.pp_double[k][j]); + offs = offs+1; + } + } + } + return result; +} + + +/************************************************************************* +Initialization of internal structures. + +It assumes correctness of all parameters. + + -- ALGLIB -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +static void idwint_idwinit1(ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state) +{ + + + z->debugsolverfailures = 0; + z->debugworstrcond = 1.0; + z->debugbestrcond = 0; + z->n = n; + z->nx = nx; + z->d = 0; + if( d==1 ) + { + z->d = 1; + } + if( d==2 ) + { + z->d = 2; + } + if( d==-1 ) + { + z->d = 1; + } + z->nw = nw; + if( d==-1 ) + { + ae_matrix_set_length(&z->q, n, nx+1+nx, _state); + } + if( d==0 ) + { + ae_matrix_set_length(&z->q, n, nx+1, _state); + } + if( d==1 ) + { + ae_matrix_set_length(&z->q, n, nx+1+nx, _state); + } + if( d==2 ) + { + ae_matrix_set_length(&z->q, n, nx+1+nx+ae_round(nx*(nx+1)*0.5, _state), _state); + } + ae_vector_set_length(&z->tbuf, nw, _state); + ae_vector_set_length(&z->rbuf, nw, _state); + ae_matrix_set_length(&z->xybuf, nw, nx+1, _state); + ae_vector_set_length(&z->xbuf, nx, _state); +} + + +/************************************************************************* +Linear least squares solver for small tasks. + +Works faster than standard ALGLIB solver in non-degenerate cases (due to +absense of internal allocations and optimized row/colums). In degenerate +cases it calls standard solver, which results in small performance penalty +associated with preliminary steps. + +INPUT PARAMETERS: + Y array[0..N-1] + W array[0..N-1] + FMatrix array[0..N-1,0..M], have additional column for temporary + values + Temp array[0..N] +*************************************************************************/ +static void idwint_idwinternalsolver(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_vector* temp, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* x, + double* taskrcond, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double tau; + ae_vector b; + densesolverlsreport srep; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + _densesolverlsreport_init(&srep, _state, ae_true); + + + /* + * set up info + */ + *info = 1; + + /* + * prepare matrix + */ + for(i=0; i<=n-1; i++) + { + fmatrix->ptr.pp_double[i][m] = y->ptr.p_double[i]; + v = w->ptr.p_double[i]; + ae_v_muld(&fmatrix->ptr.pp_double[i][0], 1, ae_v_len(0,m), v); + } + + /* + * use either fast algorithm or general algorithm + */ + if( m<=n ) + { + + /* + * QR decomposition + * We assume that M<=N (we would have called LSFit() otherwise) + */ + for(i=0; i<=m-1; i++) + { + if( iptr.p_double[1], 1, &fmatrix->ptr.pp_double[i][i], fmatrix->stride, ae_v_len(1,n-i)); + generatereflection(temp, n-i, &tau, _state); + fmatrix->ptr.pp_double[i][i] = temp->ptr.p_double[1]; + temp->ptr.p_double[1] = 1; + for(j=i+1; j<=m; j++) + { + v = ae_v_dotproduct(&fmatrix->ptr.pp_double[i][j], fmatrix->stride, &temp->ptr.p_double[1], 1, ae_v_len(i,n-1)); + v = tau*v; + ae_v_subd(&fmatrix->ptr.pp_double[i][j], fmatrix->stride, &temp->ptr.p_double[1], 1, ae_v_len(i,n-1), v); + } + } + } + + /* + * Check condition number + */ + *taskrcond = rmatrixtrrcondinf(fmatrix, m, ae_true, ae_false, _state); + + /* + * use either fast algorithm for non-degenerate cases + * or slow algorithm for degenerate cases + */ + if( ae_fp_greater(*taskrcond,10000*n*ae_machineepsilon) ) + { + + /* + * solve triangular system R*x = FMatrix[0:M-1,M] + * using fast algorithm, then exit + */ + x->ptr.p_double[m-1] = fmatrix->ptr.pp_double[m-1][m]/fmatrix->ptr.pp_double[m-1][m-1]; + for(i=m-2; i>=0; i--) + { + v = ae_v_dotproduct(&fmatrix->ptr.pp_double[i][i+1], 1, &x->ptr.p_double[i+1], 1, ae_v_len(i+1,m-1)); + x->ptr.p_double[i] = (fmatrix->ptr.pp_double[i][m]-v)/fmatrix->ptr.pp_double[i][i]; + } + } + else + { + + /* + * use more general algorithm + */ + ae_vector_set_length(&b, m, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=i-1; j++) + { + fmatrix->ptr.pp_double[i][j] = 0.0; + } + b.ptr.p_double[i] = fmatrix->ptr.pp_double[i][m]; + } + rmatrixsolvels(fmatrix, m, m, &b, 10000*ae_machineepsilon, info, &srep, x, _state); + } + } + else + { + + /* + * use more general algorithm + */ + ae_vector_set_length(&b, n, _state); + for(i=0; i<=n-1; i++) + { + b.ptr.p_double[i] = fmatrix->ptr.pp_double[i][m]; + } + rmatrixsolvels(fmatrix, n, m, &b, 10000*ae_machineepsilon, info, &srep, x, _state); + *taskrcond = srep.r2; + } + ae_frame_leave(_state); +} + + +ae_bool _idwinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + idwinterpolant *p = (idwinterpolant*)_p; + ae_touch_ptr((void*)p); + if( !_kdtree_init(&p->tree, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->q, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tbuf, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->xybuf, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _idwinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + idwinterpolant *dst = (idwinterpolant*)_dst; + idwinterpolant *src = (idwinterpolant*)_src; + dst->n = src->n; + dst->nx = src->nx; + dst->d = src->d; + dst->r = src->r; + dst->nw = src->nw; + if( !_kdtree_init_copy(&dst->tree, &src->tree, _state, make_automatic) ) + return ae_false; + dst->modeltype = src->modeltype; + if( !ae_matrix_init_copy(&dst->q, &src->q, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xbuf, &src->xbuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tbuf, &src->tbuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rbuf, &src->rbuf, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->xybuf, &src->xybuf, _state, make_automatic) ) + return ae_false; + dst->debugsolverfailures = src->debugsolverfailures; + dst->debugworstrcond = src->debugworstrcond; + dst->debugbestrcond = src->debugbestrcond; + return ae_true; +} + + +void _idwinterpolant_clear(void* _p) +{ + idwinterpolant *p = (idwinterpolant*)_p; + ae_touch_ptr((void*)p); + _kdtree_clear(&p->tree); + ae_matrix_clear(&p->q); + ae_vector_clear(&p->xbuf); + ae_vector_clear(&p->tbuf); + ae_vector_clear(&p->rbuf); + ae_matrix_clear(&p->xybuf); +} + + +void _idwinterpolant_destroy(void* _p) +{ + idwinterpolant *p = (idwinterpolant*)_p; + ae_touch_ptr((void*)p); + _kdtree_destroy(&p->tree); + ae_matrix_destroy(&p->q); + ae_vector_destroy(&p->xbuf); + ae_vector_destroy(&p->tbuf); + ae_vector_destroy(&p->rbuf); + ae_matrix_destroy(&p->xybuf); +} + + + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +double barycentriccalc(barycentricinterpolant* b, + double t, + ae_state *_state) +{ + double s1; + double s2; + double s; + double v; + ae_int_t i; + double result; + + + ae_assert(!ae_isinf(t, _state), "BarycentricCalc: infinite T!", _state); + + /* + * special case: NaN + */ + if( ae_isnan(t, _state) ) + { + result = _state->v_nan; + return result; + } + + /* + * special case: N=1 + */ + if( b->n==1 ) + { + result = b->sy*b->y.ptr.p_double[0]; + return result; + } + + /* + * Here we assume that task is normalized, i.e.: + * 1. abs(Y[i])<=1 + * 2. abs(W[i])<=1 + * 3. X[] is ordered + */ + s = ae_fabs(t-b->x.ptr.p_double[0], _state); + for(i=0; i<=b->n-1; i++) + { + v = b->x.ptr.p_double[i]; + if( ae_fp_eq(v,t) ) + { + result = b->sy*b->y.ptr.p_double[i]; + return result; + } + v = ae_fabs(t-v, _state); + if( ae_fp_less(v,s) ) + { + s = v; + } + } + s1 = 0; + s2 = 0; + for(i=0; i<=b->n-1; i++) + { + v = s/(t-b->x.ptr.p_double[i]); + v = v*b->w.ptr.p_double[i]; + s1 = s1+v*b->y.ptr.p_double[i]; + s2 = s2+v; + } + result = b->sy*s1/s2; + return result; +} + + +/************************************************************************* +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 + +NOTE + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff1(barycentricinterpolant* b, + double t, + double* f, + double* df, + ae_state *_state) +{ + double v; + double vv; + ae_int_t i; + ae_int_t k; + double n0; + double n1; + double d0; + double d1; + double s0; + double s1; + double xk; + double xi; + double xmin; + double xmax; + double xscale1; + double xoffs1; + double xscale2; + double xoffs2; + double xprev; + + *f = 0; + *df = 0; + + ae_assert(!ae_isinf(t, _state), "BarycentricDiff1: infinite T!", _state); + + /* + * special case: NaN + */ + if( ae_isnan(t, _state) ) + { + *f = _state->v_nan; + *df = _state->v_nan; + return; + } + + /* + * special case: N=1 + */ + if( b->n==1 ) + { + *f = b->sy*b->y.ptr.p_double[0]; + *df = 0; + return; + } + if( ae_fp_eq(b->sy,0) ) + { + *f = 0; + *df = 0; + return; + } + ae_assert(ae_fp_greater(b->sy,0), "BarycentricDiff1: internal error", _state); + + /* + * 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 = ae_fabs(b->x.ptr.p_double[0]-t, _state); + k = 0; + xmin = b->x.ptr.p_double[0]; + xmax = b->x.ptr.p_double[0]; + for(i=1; i<=b->n-1; i++) + { + vv = b->x.ptr.p_double[i]; + if( ae_fp_less(ae_fabs(vv-t, _state),v) ) + { + v = ae_fabs(vv-t, _state); + k = i; + } + xmin = ae_minreal(xmin, vv, _state); + xmax = ae_maxreal(xmax, vv, _state); + } + + /* + * 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->x.ptr.p_double[k]; + xk = xk*xscale1+xoffs1; + xk = xk*xscale2+xoffs2; + v = t-xk; + n0 = 0; + n1 = 0; + d0 = 0; + d1 = 0; + xprev = -2; + for(i=0; i<=b->n-1; i++) + { + xi = b->x.ptr.p_double[i]; + xi = xi*xscale1+xoffs1; + xi = xi*xscale2+xoffs2; + ae_assert(ae_fp_greater(xi,xprev), "BarycentricDiff1: points are too close!", _state); + xprev = xi; + if( i!=k ) + { + vv = ae_sqr(t-xi, _state); + s0 = (t-xk)/(t-xi); + s1 = (xk-xi)/vv; + } + else + { + s0 = 1; + s1 = 0; + } + vv = b->w.ptr.p_double[i]*b->y.ptr.p_double[i]; + n0 = n0+s0*vv; + n1 = n1+s1*vv; + vv = b->w.ptr.p_double[i]; + d0 = d0+s0*vv; + d1 = d1+s1*vv; + } + *f = b->sy*n0/d0; + *df = (n1*d0-n0*d1)/ae_sqr(d0, _state); + if( ae_fp_neq(*df,0) ) + { + *df = ae_sign(*df, _state)*ae_exp(ae_log(ae_fabs(*df, _state), _state)+ae_log(b->sy, _state)+ae_log(xscale1, _state)+ae_log(xscale2, _state), _state); + } +} + + +/************************************************************************* +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. + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff2(barycentricinterpolant* b, + double t, + double* f, + double* df, + double* d2f, + ae_state *_state) +{ + double v; + double vv; + ae_int_t i; + ae_int_t k; + double n0; + double n1; + double n2; + double d0; + double d1; + double d2; + double s0; + double s1; + double s2; + double xk; + double xi; + + *f = 0; + *df = 0; + *d2f = 0; + + ae_assert(!ae_isinf(t, _state), "BarycentricDiff1: infinite T!", _state); + + /* + * special case: NaN + */ + if( ae_isnan(t, _state) ) + { + *f = _state->v_nan; + *df = _state->v_nan; + *d2f = _state->v_nan; + return; + } + + /* + * special case: N=1 + */ + if( b->n==1 ) + { + *f = b->sy*b->y.ptr.p_double[0]; + *df = 0; + *d2f = 0; + return; + } + if( ae_fp_eq(b->sy,0) ) + { + *f = 0; + *df = 0; + *d2f = 0; + 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] + */ + ae_assert(ae_fp_greater(b->sy,0), "BarycentricDiff: internal error", _state); + *f = 0; + *df = 0; + *d2f = 0; + v = ae_fabs(b->x.ptr.p_double[0]-t, _state); + k = 0; + for(i=1; i<=b->n-1; i++) + { + vv = b->x.ptr.p_double[i]; + if( ae_fp_less(ae_fabs(vv-t, _state),v) ) + { + v = ae_fabs(vv-t, _state); + k = i; + } + } + + /* + * pivot point found, calculate dNumerator and dDenominator + */ + xk = b->x.ptr.p_double[k]; + v = t-xk; + n0 = 0; + n1 = 0; + n2 = 0; + d0 = 0; + d1 = 0; + d2 = 0; + for(i=0; i<=b->n-1; i++) + { + if( i!=k ) + { + xi = b->x.ptr.p_double[i]; + vv = ae_sqr(t-xi, _state); + s0 = (t-xk)/(t-xi); + s1 = (xk-xi)/vv; + s2 = -2*(xk-xi)/(vv*(t-xi)); + } + else + { + s0 = 1; + s1 = 0; + s2 = 0; + } + vv = b->w.ptr.p_double[i]*b->y.ptr.p_double[i]; + n0 = n0+s0*vv; + n1 = n1+s1*vv; + n2 = n2+s2*vv; + vv = b->w.ptr.p_double[i]; + d0 = d0+s0*vv; + d1 = d1+s1*vv; + d2 = d2+s2*vv; + } + *f = b->sy*n0/d0; + *df = b->sy*(n1*d0-n0*d1)/ae_sqr(d0, _state); + *d2f = b->sy*((n2*d0-n0*d2)*ae_sqr(d0, _state)-(n1*d0-n0*d1)*2*d0*d1)/ae_sqr(ae_sqr(d0, _state), _state); +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransx(barycentricinterpolant* b, + double ca, + double cb, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + + + + /* + * special case, replace by constant F(CB) + */ + if( ae_fp_eq(ca,0) ) + { + b->sy = barycentriccalc(b, cb, _state); + v = 1; + for(i=0; i<=b->n-1; i++) + { + b->y.ptr.p_double[i] = 1; + b->w.ptr.p_double[i] = v; + v = -v; + } + return; + } + + /* + * general case: CA<>0 + */ + for(i=0; i<=b->n-1; i++) + { + b->x.ptr.p_double[i] = (b->x.ptr.p_double[i]-cb)/ca; + } + if( ae_fp_less(ca,0) ) + { + for(i=0; i<=b->n-1; i++) + { + if( in-1-i ) + { + j = b->n-1-i; + v = b->x.ptr.p_double[i]; + b->x.ptr.p_double[i] = b->x.ptr.p_double[j]; + b->x.ptr.p_double[j] = v; + v = b->y.ptr.p_double[i]; + b->y.ptr.p_double[i] = b->y.ptr.p_double[j]; + b->y.ptr.p_double[j] = v; + v = b->w.ptr.p_double[i]; + b->w.ptr.p_double[i] = b->w.ptr.p_double[j]; + b->w.ptr.p_double[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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransy(barycentricinterpolant* b, + double ca, + double cb, + ae_state *_state) +{ + ae_int_t i; + double v; + + + for(i=0; i<=b->n-1; i++) + { + b->y.ptr.p_double[i] = ca*b->sy*b->y.ptr.p_double[i]+cb; + } + b->sy = 0; + for(i=0; i<=b->n-1; i++) + { + b->sy = ae_maxreal(b->sy, ae_fabs(b->y.ptr.p_double[i], _state), _state); + } + if( ae_fp_greater(b->sy,0) ) + { + v = 1/b->sy; + ae_v_muld(&b->y.ptr.p_double[0], 1, ae_v_len(0,b->n-1), v); + } +} + + +/************************************************************************* +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] + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricunpack(barycentricinterpolant* b, + ae_int_t* n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_state *_state) +{ + double v; + + *n = 0; + ae_vector_clear(x); + ae_vector_clear(y); + ae_vector_clear(w); + + *n = b->n; + ae_vector_set_length(x, *n, _state); + ae_vector_set_length(y, *n, _state); + ae_vector_set_length(w, *n, _state); + v = b->sy; + ae_v_move(&x->ptr.p_double[0], 1, &b->x.ptr.p_double[0], 1, ae_v_len(0,*n-1)); + ae_v_moved(&y->ptr.p_double[0], 1, &b->y.ptr.p_double[0], 1, ae_v_len(0,*n-1), v); + ae_v_move(&w->ptr.p_double[0], 1, &b->w.ptr.p_double[0], 1, ae_v_len(0,*n-1)); +} + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildxyw(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + barycentricinterpolant* b, + ae_state *_state) +{ + + _barycentricinterpolant_clear(b); + + ae_assert(n>0, "BarycentricBuildXYW: incorrect N!", _state); + + /* + * fill X/Y/W + */ + ae_vector_set_length(&b->x, n, _state); + ae_vector_set_length(&b->y, n, _state); + ae_vector_set_length(&b->w, n, _state); + ae_v_move(&b->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&b->y.ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&b->w.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + b->n = n; + + /* + * Normalize + */ + ratint_barycentricnormalize(b, _state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 17.06.2007 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildfloaterhormann(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t d, + barycentricinterpolant* b, + ae_state *_state) +{ + ae_frame _frame_block; + double s0; + double s; + double v; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_vector perm; + ae_vector wtemp; + ae_vector sortrbuf; + ae_vector sortrbuf2; + + ae_frame_make(_state, &_frame_block); + _barycentricinterpolant_clear(b); + ae_vector_init(&perm, 0, DT_INT, _state, ae_true); + ae_vector_init(&wtemp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sortrbuf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sortrbuf2, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "BarycentricFloaterHormann: N<=0!", _state); + ae_assert(d>=0, "BarycentricFloaterHormann: incorrect D!", _state); + + /* + * Prepare + */ + if( d>n-1 ) + { + d = n-1; + } + b->n = n; + + /* + * special case: N=1 + */ + if( n==1 ) + { + ae_vector_set_length(&b->x, n, _state); + ae_vector_set_length(&b->y, n, _state); + ae_vector_set_length(&b->w, n, _state); + b->x.ptr.p_double[0] = x->ptr.p_double[0]; + b->y.ptr.p_double[0] = y->ptr.p_double[0]; + b->w.ptr.p_double[0] = 1; + ratint_barycentricnormalize(b, _state); + ae_frame_leave(_state); + return; + } + + /* + * Fill X/Y + */ + ae_vector_set_length(&b->x, n, _state); + ae_vector_set_length(&b->y, n, _state); + ae_v_move(&b->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&b->y.ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + tagsortfastr(&b->x, &b->y, &sortrbuf, &sortrbuf2, n, _state); + + /* + * Calculate Wk + */ + ae_vector_set_length(&b->w, n, _state); + s0 = 1; + for(k=1; k<=d; k++) + { + s0 = -s0; + } + for(k=0; k<=n-1; k++) + { + + /* + * Wk + */ + s = 0; + for(i=ae_maxint(k-d, 0, _state); i<=ae_minint(k, n-1-d, _state); i++) + { + v = 1; + for(j=i; j<=i+d; j++) + { + if( j!=k ) + { + v = v/ae_fabs(b->x.ptr.p_double[k]-b->x.ptr.p_double[j], _state); + } + } + s = s+v; + } + b->w.ptr.p_double[k] = s0*s; + + /* + * Next S0 + */ + s0 = -s0; + } + + /* + * Normalize + */ + ratint_barycentricnormalize(b, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Copying of the barycentric interpolant (for internal use only) + +INPUT PARAMETERS: + B - barycentric interpolant + +OUTPUT PARAMETERS: + B2 - copy(B1) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriccopy(barycentricinterpolant* b, + barycentricinterpolant* b2, + ae_state *_state) +{ + + _barycentricinterpolant_clear(b2); + + b2->n = b->n; + b2->sy = b->sy; + ae_vector_set_length(&b2->x, b2->n, _state); + ae_vector_set_length(&b2->y, b2->n, _state); + ae_vector_set_length(&b2->w, b2->n, _state); + ae_v_move(&b2->x.ptr.p_double[0], 1, &b->x.ptr.p_double[0], 1, ae_v_len(0,b2->n-1)); + ae_v_move(&b2->y.ptr.p_double[0], 1, &b->y.ptr.p_double[0], 1, ae_v_len(0,b2->n-1)); + ae_v_move(&b2->w.ptr.p_double[0], 1, &b->w.ptr.p_double[0], 1, ae_v_len(0,b2->n-1)); +} + + +/************************************************************************* +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. +*************************************************************************/ +static void ratint_barycentricnormalize(barycentricinterpolant* b, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector p1; + ae_vector p2; + ae_int_t i; + ae_int_t j; + ae_int_t j2; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * Normalize task: |Y|<=1, |W|<=1, sort X[] + */ + b->sy = 0; + for(i=0; i<=b->n-1; i++) + { + b->sy = ae_maxreal(b->sy, ae_fabs(b->y.ptr.p_double[i], _state), _state); + } + if( ae_fp_greater(b->sy,0)&&ae_fp_greater(ae_fabs(b->sy-1, _state),10*ae_machineepsilon) ) + { + v = 1/b->sy; + ae_v_muld(&b->y.ptr.p_double[0], 1, ae_v_len(0,b->n-1), v); + } + v = 0; + for(i=0; i<=b->n-1; i++) + { + v = ae_maxreal(v, ae_fabs(b->w.ptr.p_double[i], _state), _state); + } + if( ae_fp_greater(v,0)&&ae_fp_greater(ae_fabs(v-1, _state),10*ae_machineepsilon) ) + { + v = 1/v; + ae_v_muld(&b->w.ptr.p_double[0], 1, ae_v_len(0,b->n-1), v); + } + for(i=0; i<=b->n-2; i++) + { + if( ae_fp_less(b->x.ptr.p_double[i+1],b->x.ptr.p_double[i]) ) + { + tagsort(&b->x, b->n, &p1, &p2, _state); + for(j=0; j<=b->n-1; j++) + { + j2 = p2.ptr.p_int[j]; + v = b->y.ptr.p_double[j]; + b->y.ptr.p_double[j] = b->y.ptr.p_double[j2]; + b->y.ptr.p_double[j2] = v; + v = b->w.ptr.p_double[j]; + b->w.ptr.p_double[j] = b->w.ptr.p_double[j2]; + b->w.ptr.p_double[j2] = v; + } + break; + } + } + ae_frame_leave(_state); +} + + +ae_bool _barycentricinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + barycentricinterpolant *p = (barycentricinterpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->w, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _barycentricinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + barycentricinterpolant *dst = (barycentricinterpolant*)_dst; + barycentricinterpolant *src = (barycentricinterpolant*)_src; + dst->n = src->n; + dst->sy = src->sy; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->w, &src->w, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _barycentricinterpolant_clear(void* _p) +{ + barycentricinterpolant *p = (barycentricinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->y); + ae_vector_clear(&p->w); +} + + +void _barycentricinterpolant_destroy(void* _p) +{ + barycentricinterpolant *p = (barycentricinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->y); + ae_vector_destroy(&p->w); +} + + + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2cheb(barycentricinterpolant* p, + double a, + double b, + /* Real */ ae_vector* t, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + ae_vector vp; + ae_vector vx; + ae_vector tk; + ae_vector tk1; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(t); + ae_vector_init(&vp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&vx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tk, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tk1, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_isfinite(a, _state), "PolynomialBar2Cheb: A is not finite!", _state); + ae_assert(ae_isfinite(b, _state), "PolynomialBar2Cheb: B is not finite!", _state); + ae_assert(ae_fp_neq(a,b), "PolynomialBar2Cheb: A=B!", _state); + ae_assert(p->n>0, "PolynomialBar2Cheb: P is not correctly initialized barycentric interpolant!", _state); + + /* + * Calculate function values on a Chebyshev grid + */ + ae_vector_set_length(&vp, p->n, _state); + ae_vector_set_length(&vx, p->n, _state); + for(i=0; i<=p->n-1; i++) + { + vx.ptr.p_double[i] = ae_cos(ae_pi*(i+0.5)/p->n, _state); + vp.ptr.p_double[i] = barycentriccalc(p, 0.5*(vx.ptr.p_double[i]+1)*(b-a)+a, _state); + } + + /* + * T[0] + */ + ae_vector_set_length(t, p->n, _state); + v = 0; + for(i=0; i<=p->n-1; i++) + { + v = v+vp.ptr.p_double[i]; + } + t->ptr.p_double[0] = v/p->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->n>1 ) + { + ae_vector_set_length(&tk, p->n, _state); + ae_vector_set_length(&tk1, p->n, _state); + for(i=0; i<=p->n-1; i++) + { + tk.ptr.p_double[i] = vx.ptr.p_double[i]; + tk1.ptr.p_double[i] = 1; + } + for(k=1; k<=p->n-1; k++) + { + + /* + * calculate discrete product of function vector and TK + */ + v = ae_v_dotproduct(&tk.ptr.p_double[0], 1, &vp.ptr.p_double[0], 1, ae_v_len(0,p->n-1)); + t->ptr.p_double[k] = v/(0.5*p->n); + + /* + * Update TK and TK1 + */ + for(i=0; i<=p->n-1; i++) + { + v = 2*vx.ptr.p_double[i]*tk.ptr.p_double[i]-tk1.ptr.p_double[i]; + tk1.ptr.p_double[i] = tk.ptr.p_double[i]; + tk.ptr.p_double[i] = v; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Conversion from Chebyshev basis to barycentric representation. +This function has O(N^2) complexity. + +INPUT PARAMETERS: + T - coefficients of Chebyshev representation; + P(x) = sum { T[i]*Ti(2*(x-A)/(B-A)-1), i=0..N }, + where Ti - I-th Chebyshev polynomial. + N - number of coefficients: + * if given, only leading N elements of T are used + * if not given, automatically determined from size of T + A,B - base interval for Chebyshev polynomials (see above) + A=1, "PolynomialBar2Cheb: N<1", _state); + ae_assert(t->cnt>=n, "PolynomialBar2Cheb: Length(T)ptr.p_double[0]; + tk1 = 1; + tk = vx; + for(k=1; k<=n-1; k++) + { + vy = vy+t->ptr.p_double[k]*tk; + v = 2*vx*tk-tk1; + tk1 = tk; + tk = v; + } + y.ptr.p_double[i] = vy; + } + + /* + * Build barycentric interpolant, map grid from [-1,+1] to [A,B] + */ + polynomialbuildcheb1(a, b, &y, n, p, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2pow(barycentricinterpolant* p, + double c, + double s, + /* Real */ ae_vector* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + double e; + double d; + ae_vector vp; + ae_vector vx; + ae_vector tk; + ae_vector tk1; + ae_vector t; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(a); + ae_vector_init(&vp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&vx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tk, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tk1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_isfinite(c, _state), "PolynomialBar2Pow: C is not finite!", _state); + ae_assert(ae_isfinite(s, _state), "PolynomialBar2Pow: S is not finite!", _state); + ae_assert(ae_fp_neq(s,0), "PolynomialBar2Pow: S=0!", _state); + ae_assert(p->n>0, "PolynomialBar2Pow: P is not correctly initialized barycentric interpolant!", _state); + + /* + * Calculate function values on a Chebyshev grid + */ + ae_vector_set_length(&vp, p->n, _state); + ae_vector_set_length(&vx, p->n, _state); + for(i=0; i<=p->n-1; i++) + { + vx.ptr.p_double[i] = ae_cos(ae_pi*(i+0.5)/p->n, _state); + vp.ptr.p_double[i] = barycentriccalc(p, s*vx.ptr.p_double[i]+c, _state); + } + + /* + * T[0] + */ + ae_vector_set_length(&t, p->n, _state); + v = 0; + for(i=0; i<=p->n-1; i++) + { + v = v+vp.ptr.p_double[i]; + } + t.ptr.p_double[0] = v/p->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->n>1 ) + { + ae_vector_set_length(&tk, p->n, _state); + ae_vector_set_length(&tk1, p->n, _state); + for(i=0; i<=p->n-1; i++) + { + tk.ptr.p_double[i] = vx.ptr.p_double[i]; + tk1.ptr.p_double[i] = 1; + } + for(k=1; k<=p->n-1; k++) + { + + /* + * calculate discrete product of function vector and TK + */ + v = ae_v_dotproduct(&tk.ptr.p_double[0], 1, &vp.ptr.p_double[0], 1, ae_v_len(0,p->n-1)); + t.ptr.p_double[k] = v/(0.5*p->n); + + /* + * Update TK and TK1 + */ + for(i=0; i<=p->n-1; i++) + { + v = 2*vx.ptr.p_double[i]*tk.ptr.p_double[i]-tk1.ptr.p_double[i]; + tk1.ptr.p_double[i] = tk.ptr.p_double[i]; + tk.ptr.p_double[i] = v; + } + } + } + + /* + * Convert from Chebyshev basis to power basis + */ + ae_vector_set_length(a, p->n, _state); + for(i=0; i<=p->n-1; i++) + { + a->ptr.p_double[i] = 0; + } + d = 0; + for(i=0; i<=p->n-1; i++) + { + for(k=i; k<=p->n-1; k++) + { + e = a->ptr.p_double[k]; + a->ptr.p_double[k] = 0; + if( i<=1&&k==i ) + { + a->ptr.p_double[k] = 1; + } + else + { + if( i!=0 ) + { + a->ptr.p_double[k] = 2*d; + } + if( k>i+1 ) + { + a->ptr.p_double[k] = a->ptr.p_double[k]-a->ptr.p_double[k-2]; + } + } + d = e; + } + d = a->ptr.p_double[i]; + e = 0; + k = i; + while(k<=p->n-1) + { + e = e+a->ptr.p_double[k]*t.ptr.p_double[k]; + k = k+2; + } + a->ptr.p_double[i] = e; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialpow2bar(/* Real */ ae_vector* a, + ae_int_t n, + double c, + double s, + barycentricinterpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t k; + ae_vector y; + double vx; + double vy; + double px; + + ae_frame_make(_state, &_frame_block); + _barycentricinterpolant_clear(p); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_isfinite(c, _state), "PolynomialPow2Bar: C is not finite!", _state); + ae_assert(ae_isfinite(s, _state), "PolynomialPow2Bar: S is not finite!", _state); + ae_assert(ae_fp_neq(s,0), "PolynomialPow2Bar: S is zero!", _state); + ae_assert(n>=1, "PolynomialPow2Bar: N<1", _state); + ae_assert(a->cnt>=n, "PolynomialPow2Bar: Length(A)ptr.p_double[0]; + px = vx; + for(k=1; k<=n-1; k++) + { + vy = vy+px*a->ptr.p_double[k]; + px = px*vx; + } + y.ptr.p_double[i] = vy; + } + + /* + * Build barycentric interpolant, map grid from [-1,+1] to [A,B] + */ + polynomialbuildcheb1(c-s, c+s, &y, n, p, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuild(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_int_t j; + ae_int_t k; + ae_vector w; + double b; + double a; + double v; + double mx; + ae_vector sortrbuf; + ae_vector sortrbuf2; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _barycentricinterpolant_clear(p); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sortrbuf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sortrbuf2, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "PolynomialBuild: N<=0!", _state); + ae_assert(x->cnt>=n, "PolynomialBuild: Length(X)cnt>=n, "PolynomialBuild: Length(Y)ptr.p_double[0]; + b = x->ptr.p_double[0]; + for(j=0; j<=n-1; j++) + { + w.ptr.p_double[j] = 1; + a = ae_minreal(a, x->ptr.p_double[j], _state); + b = ae_maxreal(b, x->ptr.p_double[j], _state); + } + for(k=0; k<=n-1; 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 = ae_fabs(w.ptr.p_double[k], _state); + for(j=0; j<=n-1; j++) + { + if( j!=k ) + { + v = (b-a)/(x->ptr.p_double[j]-x->ptr.p_double[k]); + w.ptr.p_double[j] = w.ptr.p_double[j]*v; + mx = ae_maxreal(mx, ae_fabs(w.ptr.p_double[j], _state), _state); + } + } + if( k%5==0 ) + { + + /* + * every 5-th run we renormalize W[] + */ + v = 1/mx; + ae_v_muld(&w.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + } + barycentricbuildxyw(x, y, &w, n, p, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildeqdist(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector w; + ae_vector x; + double v; + + ae_frame_make(_state, &_frame_block); + _barycentricinterpolant_clear(p); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "PolynomialBuildEqDist: N<=0!", _state); + ae_assert(y->cnt>=n, "PolynomialBuildEqDist: Length(Y)=1 + for N=1 a constant model is constructed. + +OUTPUT PARAMETERS + P - barycentric model which represents Lagrange interpolant + (see ratint unit info and BarycentricCalc() description for + more information). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb1(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector w; + ae_vector x; + double v; + double t; + + ae_frame_make(_state, &_frame_block); + _barycentricinterpolant_clear(p); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "PolynomialBuildCheb1: N<=0!", _state); + ae_assert(y->cnt>=n, "PolynomialBuildCheb1: Length(Y)=1 + for N=1 a constant model is constructed. + +OUTPUT PARAMETERS + P - barycentric model which represents Lagrange interpolant + (see ratint unit info and BarycentricCalc() description for + more information). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb2(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector w; + ae_vector x; + double v; + + ae_frame_make(_state, &_frame_block); + _barycentricinterpolant_clear(p); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "PolynomialBuildCheb2: N<=0!", _state); + ae_assert(y->cnt>=n, "PolynomialBuildCheb2: Length(Y)=1 + for N=1 a constant model is constructed. + T - position where P(x) is calculated + +RESULT + value of the Lagrange interpolant at T + +IMPORTANT + this function provides fast interface which is not overflow-safe + nor it is very precise. + the best option is to use PolynomialBuildEqDist()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalceqdist(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state) +{ + double s1; + double s2; + double v; + double threshold; + double s; + double h; + ae_int_t i; + ae_int_t j; + double w; + double x; + double result; + + + ae_assert(n>0, "PolynomialCalcEqDist: N<=0!", _state); + ae_assert(f->cnt>=n, "PolynomialCalcEqDist: Length(F)v_nan; + return result; + } + + /* + * Special case: N=1 + */ + if( n==1 ) + { + result = f->ptr.p_double[0]; + return result; + } + + /* + * First, decide: should we use "safe" formula (guarded + * against overflow) or fast one? + */ + threshold = ae_sqrt(ae_minrealnumber, _state); + j = 0; + s = t-a; + for(i=1; i<=n-1; i++) + { + x = a+(double)i/(double)(n-1)*(b-a); + if( ae_fp_less(ae_fabs(t-x, _state),ae_fabs(s, _state)) ) + { + s = t-x; + j = i; + } + } + if( ae_fp_eq(s,0) ) + { + result = f->ptr.p_double[j]; + return result; + } + if( ae_fp_greater(ae_fabs(s, _state),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); + for(i=0; i<=n-1; i++) + { + if( i!=j ) + { + v = s*w/(t-(a+i*h)); + s1 = s1+v*f->ptr.p_double[i]; + s2 = s2+v; + } + else + { + v = w; + s1 = s1+v*f->ptr.p_double[i]; + s2 = s2+v; + } + w = -w*(n-1-i); + w = w/(i+1); + } + result = s1/s2; + return result; +} + + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb1(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state) +{ + double s1; + double s2; + double v; + double threshold; + double s; + ae_int_t i; + ae_int_t j; + double a0; + double delta; + double alpha; + double beta; + double ca; + double sa; + double tempc; + double temps; + double x; + double w; + double p1; + double result; + + + ae_assert(n>0, "PolynomialCalcCheb1: N<=0!", _state); + ae_assert(f->cnt>=n, "PolynomialCalcCheb1: Length(F)v_nan; + return result; + } + + /* + * Special case: N=1 + */ + if( n==1 ) + { + result = f->ptr.p_double[0]; + return result; + } + + /* + * 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 = ae_sqrt(ae_minrealnumber, _state); + t = (t-0.5*(a+b))/(0.5*(b-a)); + a0 = ae_pi/(2*(n-1)+2); + delta = 2*ae_pi/(2*(n-1)+2); + alpha = 2*ae_sqr(ae_sin(delta/2, _state), _state); + beta = ae_sin(delta, _state); + + /* + * First, decide: should we use "safe" formula (guarded + * against overflow) or fast one? + */ + ca = ae_cos(a0, _state); + sa = ae_sin(a0, _state); + j = 0; + x = ca; + s = t-x; + for(i=1; i<=n-1; 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( ae_fp_less(ae_fabs(t-x, _state),ae_fabs(s, _state)) ) + { + s = t-x; + j = i; + } + } + if( ae_fp_eq(s,0) ) + { + result = f->ptr.p_double[j]; + return result; + } + if( ae_fp_greater(ae_fabs(s, _state),threshold) ) + { + + /* + * use fast formula + */ + j = -1; + s = 1.0; + } + + /* + * Calculate using safe or fast barycentric formula + */ + s1 = 0; + s2 = 0; + ca = ae_cos(a0, _state); + sa = ae_sin(a0, _state); + p1 = 1.0; + for(i=0; i<=n-1; i++) + { + + /* + * Calculate X[i], W[i] + */ + x = ca; + w = p1*sa; + + /* + * Proceed + */ + if( i!=j ) + { + v = s*w/(t-x); + s1 = s1+v*f->ptr.p_double[i]; + s2 = s2+v; + } + else + { + v = w; + s1 = s1+v*f->ptr.p_double[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; + } + result = s1/s2; + return result; +} + + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb2(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state) +{ + double s1; + double s2; + double v; + double threshold; + double s; + ae_int_t i; + ae_int_t j; + double a0; + double delta; + double alpha; + double beta; + double ca; + double sa; + double tempc; + double temps; + double x; + double w; + double p1; + double result; + + + ae_assert(n>0, "PolynomialCalcCheb2: N<=0!", _state); + ae_assert(f->cnt>=n, "PolynomialCalcCheb2: Length(F)v_nan; + return result; + } + + /* + * Special case: N=1 + */ + if( n==1 ) + { + result = f->ptr.p_double[0]; + return result; + } + + /* + * 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 = ae_sqrt(ae_minrealnumber, _state); + t = (t-0.5*(a+b))/(0.5*(b-a)); + a0 = 0.0; + delta = ae_pi/(n-1); + alpha = 2*ae_sqr(ae_sin(delta/2, _state), _state); + beta = ae_sin(delta, _state); + + /* + * First, decide: should we use "safe" formula (guarded + * against overflow) or fast one? + */ + ca = ae_cos(a0, _state); + sa = ae_sin(a0, _state); + j = 0; + x = ca; + s = t-x; + for(i=1; i<=n-1; 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( ae_fp_less(ae_fabs(t-x, _state),ae_fabs(s, _state)) ) + { + s = t-x; + j = i; + } + } + if( ae_fp_eq(s,0) ) + { + result = f->ptr.p_double[j]; + return result; + } + if( ae_fp_greater(ae_fabs(s, _state),threshold) ) + { + + /* + * use fast formula + */ + j = -1; + s = 1.0; + } + + /* + * Calculate using safe or fast barycentric formula + */ + s1 = 0; + s2 = 0; + ca = ae_cos(a0, _state); + sa = ae_sin(a0, _state); + p1 = 1.0; + for(i=0; i<=n-1; i++) + { + + /* + * Calculate X[i], W[i] + */ + x = ca; + 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->ptr.p_double[i]; + s2 = s2+v; + } + else + { + v = w; + s1 = s1+v*f->ptr.p_double[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; + } + result = s1/s2; + return result; +} + + + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildlinear(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _spline1dinterpolant_clear(c); + + ae_assert(n>1, "Spline1DBuildLinear: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DBuildLinear: Length(X)cnt>=n, "Spline1DBuildLinear: Length(Y)periodic = ae_false; + c->n = n; + c->k = 3; + c->continuity = 0; + ae_vector_set_length(&c->x, n, _state); + ae_vector_set_length(&c->c, 4*(n-1)+2, _state); + for(i=0; i<=n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=n-2; i++) + { + c->c.ptr.p_double[4*i+0] = y->ptr.p_double[i]; + c->c.ptr.p_double[4*i+1] = (y->ptr.p_double[i+1]-y->ptr.p_double[i])/(x->ptr.p_double[i+1]-x->ptr.p_double[i]); + c->c.ptr.p_double[4*i+2] = 0; + c->c.ptr.p_double[4*i+3] = 0; + } + c->c.ptr.p_double[4*(n-1)+0] = y->ptr.p_double[n-1]; + c->c.ptr.p_double[4*(n-1)+1] = c->c.ptr.p_double[4*(n-2)+1]; + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + spline1dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector dt; + ae_vector d; + ae_vector p; + ae_int_t ylen; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _spline1dinterpolant_clear(c); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DBuildCubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DBuildCubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DBuildCubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DBuildCubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DBuildCubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DBuildCubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DBuildCubic: Length(X)cnt>=n, "Spline1DBuildCubic: Length(Y)ptr.p_double[n-1] = y->ptr.p_double[0]; + } + spline1d_spline1dgriddiffcubicinternal(x, y, n, boundltype, boundl, boundrtype, boundr, &d, &a1, &a2, &a3, &b, &dt, _state); + spline1dbuildhermite(x, y, &d, n, c, _state); + c->periodic = boundltype==-1||boundrtype==-1; + c->continuity = 2; + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiffcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector dt; + ae_vector p; + ae_int_t i; + ae_int_t ylen; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_clear(d); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DGridDiffCubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DGridDiffCubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DGridDiffCubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DGridDiffCubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DGridDiffCubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DGridDiffCubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DGridDiffCubic: Length(X)cnt>=n, "Spline1DGridDiffCubic: Length(Y)ptr.p_double[i]; + } + ae_v_move(&d->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiff2cubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d1, + /* Real */ ae_vector* d2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector dt; + ae_vector p; + ae_int_t i; + ae_int_t ylen; + double delta; + double delta2; + double delta3; + double s2; + double s3; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_clear(d1); + ae_vector_clear(d2); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DGridDiff2Cubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DGridDiff2Cubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DGridDiff2Cubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DGridDiff2Cubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DGridDiff2Cubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DGridDiff2Cubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DGridDiff2Cubic: Length(X)cnt>=n, "Spline1DGridDiff2Cubic: Length(Y)ptr.p_double[i+1]-x->ptr.p_double[i]; + delta2 = ae_sqr(delta, _state); + delta3 = delta*delta2; + s2 = (3*(y->ptr.p_double[i+1]-y->ptr.p_double[i])-2*d1->ptr.p_double[i]*delta-d1->ptr.p_double[i+1]*delta)/delta2; + s3 = (2*(y->ptr.p_double[i]-y->ptr.p_double[i+1])+d1->ptr.p_double[i]*delta+d1->ptr.p_double[i+1]*delta)/delta3; + d2->ptr.p_double[i] = 2*s2; + } + d2->ptr.p_double[n-1] = 2*s2+6*s3*delta; + + /* + * Remember that HeapSortPPoints() call? + * Now we have to reorder them back. + */ + if( dt.cntptr.p_double[i]; + } + ae_v_move(&d1->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + dt.ptr.p_double[p.ptr.p_int[i]] = d2->ptr.p_double[i]; + } + ae_v_move(&d2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _x2; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector d; + ae_vector dt; + ae_vector d1; + ae_vector d2; + ae_vector p; + ae_vector p2; + ae_int_t i; + ae_int_t ylen; + double t; + double t2; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_x2, x2, _state, ae_true); + x2 = &_x2; + ae_vector_clear(y2); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DConvCubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DConvCubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DConvCubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DConvCubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DConvCubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DConvCubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DConvCubic: Length(X)cnt>=n, "Spline1DConvCubic: Length(Y)=2, "Spline1DConvCubic: N2<2!", _state); + ae_assert(x2->cnt>=n2, "Spline1DConvCubic: Length(X2)ptr.p_double[i]; + apperiodicmap(&t, x->ptr.p_double[0], x->ptr.p_double[n-1], &t2, _state); + x2->ptr.p_double[i] = t; + } + } + spline1d_heapsortppoints(x2, &dt, &p2, n2, _state); + + /* + * 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 + */ + spline1d_spline1dgriddiffcubicinternal(x, y, n, boundltype, boundl, boundrtype, boundr, &d, &a1, &a2, &a3, &b, &dt, _state); + spline1dconvdiffinternal(x, y, &d, n, x2, n2, y2, ae_true, &d1, ae_false, &d2, ae_false, _state); + ae_assert(dt.cnt>=n2, "Spline1DConvCubic: internal error!", _state); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = y2->ptr.p_double[i]; + } + ae_v_move(&y2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiffcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + /* Real */ ae_vector* d2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _x2; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector d; + ae_vector dt; + ae_vector rt1; + ae_vector p; + ae_vector p2; + ae_int_t i; + ae_int_t ylen; + double t; + double t2; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_x2, x2, _state, ae_true); + x2 = &_x2; + ae_vector_clear(y2); + ae_vector_clear(d2); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&rt1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DConvDiffCubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DConvDiffCubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DConvDiffCubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DConvDiffCubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DConvDiffCubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DConvDiffCubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DConvDiffCubic: Length(X)cnt>=n, "Spline1DConvDiffCubic: Length(Y)=2, "Spline1DConvDiffCubic: N2<2!", _state); + ae_assert(x2->cnt>=n2, "Spline1DConvDiffCubic: Length(X2)ptr.p_double[i]; + apperiodicmap(&t, x->ptr.p_double[0], x->ptr.p_double[n-1], &t2, _state); + x2->ptr.p_double[i] = t; + } + } + spline1d_heapsortppoints(x2, &dt, &p2, n2, _state); + + /* + * 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 + */ + spline1d_spline1dgriddiffcubicinternal(x, y, n, boundltype, boundl, boundrtype, boundr, &d, &a1, &a2, &a3, &b, &dt, _state); + spline1dconvdiffinternal(x, y, &d, n, x2, n2, y2, ae_true, d2, ae_true, &rt1, ae_false, _state); + ae_assert(dt.cnt>=n2, "Spline1DConvDiffCubic: internal error!", _state); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = y2->ptr.p_double[i]; + } + ae_v_move(&y2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = d2->ptr.p_double[i]; + } + ae_v_move(&d2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiff2cubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + /* Real */ ae_vector* d2, + /* Real */ ae_vector* dd2, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _x2; + ae_vector a1; + ae_vector a2; + ae_vector a3; + ae_vector b; + ae_vector d; + ae_vector dt; + ae_vector p; + ae_vector p2; + ae_int_t i; + ae_int_t ylen; + double t; + double t2; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_x2, x2, _state, ae_true); + x2 = &_x2; + ae_vector_clear(y2); + ae_vector_clear(d2); + ae_vector_clear(dd2); + ae_vector_init(&a1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&a3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + + /* + * check correctness of boundary conditions + */ + ae_assert(((boundltype==-1||boundltype==0)||boundltype==1)||boundltype==2, "Spline1DConvDiff2Cubic: incorrect BoundLType!", _state); + ae_assert(((boundrtype==-1||boundrtype==0)||boundrtype==1)||boundrtype==2, "Spline1DConvDiff2Cubic: incorrect BoundRType!", _state); + ae_assert((boundrtype==-1&&boundltype==-1)||(boundrtype!=-1&&boundltype!=-1), "Spline1DConvDiff2Cubic: incorrect BoundLType/BoundRType!", _state); + if( boundltype==1||boundltype==2 ) + { + ae_assert(ae_isfinite(boundl, _state), "Spline1DConvDiff2Cubic: BoundL is infinite or NAN!", _state); + } + if( boundrtype==1||boundrtype==2 ) + { + ae_assert(ae_isfinite(boundr, _state), "Spline1DConvDiff2Cubic: BoundR is infinite or NAN!", _state); + } + + /* + * check lengths of arguments + */ + ae_assert(n>=2, "Spline1DConvDiff2Cubic: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DConvDiff2Cubic: Length(X)cnt>=n, "Spline1DConvDiff2Cubic: Length(Y)=2, "Spline1DConvDiff2Cubic: N2<2!", _state); + ae_assert(x2->cnt>=n2, "Spline1DConvDiff2Cubic: Length(X2)ptr.p_double[i]; + apperiodicmap(&t, x->ptr.p_double[0], x->ptr.p_double[n-1], &t2, _state); + x2->ptr.p_double[i] = t; + } + } + spline1d_heapsortppoints(x2, &dt, &p2, n2, _state); + + /* + * 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 + */ + spline1d_spline1dgriddiffcubicinternal(x, y, n, boundltype, boundl, boundrtype, boundr, &d, &a1, &a2, &a3, &b, &dt, _state); + spline1dconvdiffinternal(x, y, &d, n, x2, n2, y2, ae_true, d2, ae_true, dd2, ae_true, _state); + ae_assert(dt.cnt>=n2, "Spline1DConvDiff2Cubic: internal error!", _state); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = y2->ptr.p_double[i]; + } + ae_v_move(&y2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = d2->ptr.p_double[i]; + } + ae_v_move(&d2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + for(i=0; i<=n2-1; i++) + { + dt.ptr.p_double[p2.ptr.p_int[i]] = dd2->ptr.p_double[i]; + } + ae_v_move(&dd2->ptr.p_double[0], 1, &dt.ptr.p_double[0], 1, ae_v_len(0,n2-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine builds Catmull-Rom spline interpolant. + +INPUT PARAMETERS: + X - spline nodes, array[0..N-1]. + Y - function values, array[0..N-1]. + +OPTIONAL PARAMETERS: + N - points count: + * N>=2 + * if given, only first N points are used to build spline + * if not given, automatically detected from X/Y sizes + (len(X) must be equal to len(Y)) + BoundType - boundary condition type: + * -1 for periodic boundary condition + * 0 for parabolically terminated spline (default) + Tension - tension parameter: + * tension=0 corresponds to classic Catmull-Rom spline (default) + * 0=2, "Spline1DBuildCatmullRom: N<2!", _state); + ae_assert(boundtype==-1||boundtype==0, "Spline1DBuildCatmullRom: incorrect BoundType!", _state); + ae_assert(ae_fp_greater_eq(tension,0), "Spline1DBuildCatmullRom: Tension<0!", _state); + ae_assert(ae_fp_less_eq(tension,1), "Spline1DBuildCatmullRom: Tension>1!", _state); + ae_assert(x->cnt>=n, "Spline1DBuildCatmullRom: Length(X)cnt>=n, "Spline1DBuildCatmullRom: Length(Y)ptr.p_double[n-1] = y->ptr.p_double[0]; + ae_vector_set_length(&d, n, _state); + d.ptr.p_double[0] = (y->ptr.p_double[1]-y->ptr.p_double[n-2])/(2*(x->ptr.p_double[1]-x->ptr.p_double[0]+x->ptr.p_double[n-1]-x->ptr.p_double[n-2])); + for(i=1; i<=n-2; i++) + { + d.ptr.p_double[i] = (1-tension)*(y->ptr.p_double[i+1]-y->ptr.p_double[i-1])/(x->ptr.p_double[i+1]-x->ptr.p_double[i-1]); + } + d.ptr.p_double[n-1] = d.ptr.p_double[0]; + + /* + * Now problem is reduced to the cubic Hermite spline + */ + spline1dbuildhermite(x, y, &d, n, c, _state); + c->periodic = ae_true; + } + else + { + + /* + * Non-periodic boundary conditions + */ + ae_vector_set_length(&d, n, _state); + for(i=1; i<=n-2; i++) + { + d.ptr.p_double[i] = (1-tension)*(y->ptr.p_double[i+1]-y->ptr.p_double[i-1])/(x->ptr.p_double[i+1]-x->ptr.p_double[i-1]); + } + d.ptr.p_double[0] = 2*(y->ptr.p_double[1]-y->ptr.p_double[0])/(x->ptr.p_double[1]-x->ptr.p_double[0])-d.ptr.p_double[1]; + d.ptr.p_double[n-1] = 2*(y->ptr.p_double[n-1]-y->ptr.p_double[n-2])/(x->ptr.p_double[n-1]-x->ptr.p_double[n-2])-d.ptr.p_double[n-2]; + + /* + * Now problem is reduced to the cubic Hermite spline + */ + spline1dbuildhermite(x, y, &d, n, c, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildhermite(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* d, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _d; + ae_int_t i; + double delta; + double delta2; + double delta3; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_d, d, _state, ae_true); + d = &_d; + _spline1dinterpolant_clear(c); + + ae_assert(n>=2, "Spline1DBuildHermite: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DBuildHermite: Length(X)cnt>=n, "Spline1DBuildHermite: Length(Y)cnt>=n, "Spline1DBuildHermite: Length(D)x, n, _state); + ae_vector_set_length(&c->c, 4*(n-1)+2, _state); + c->periodic = ae_false; + c->k = 3; + c->n = n; + c->continuity = 1; + for(i=0; i<=n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=n-2; i++) + { + delta = x->ptr.p_double[i+1]-x->ptr.p_double[i]; + delta2 = ae_sqr(delta, _state); + delta3 = delta*delta2; + c->c.ptr.p_double[4*i+0] = y->ptr.p_double[i]; + c->c.ptr.p_double[4*i+1] = d->ptr.p_double[i]; + c->c.ptr.p_double[4*i+2] = (3*(y->ptr.p_double[i+1]-y->ptr.p_double[i])-2*d->ptr.p_double[i]*delta-d->ptr.p_double[i+1]*delta)/delta2; + c->c.ptr.p_double[4*i+3] = (2*(y->ptr.p_double[i]-y->ptr.p_double[i+1])+d->ptr.p_double[i]*delta+d->ptr.p_double[i+1]*delta)/delta3; + } + c->c.ptr.p_double[4*(n-1)+0] = y->ptr.p_double[n-1]; + c->c.ptr.p_double[4*(n-1)+1] = d->ptr.p_double[n-1]; + ae_frame_leave(_state); +} + + +/************************************************************************* +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>=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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildakima(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_int_t i; + ae_vector d; + ae_vector w; + ae_vector diff; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _spline1dinterpolant_clear(c); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&diff, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=2, "Spline1DBuildAkima: N<2!", _state); + ae_assert(x->cnt>=n, "Spline1DBuildAkima: Length(X)cnt>=n, "Spline1DBuildAkima: Length(Y)ptr.p_double[i+1]-y->ptr.p_double[i])/(x->ptr.p_double[i+1]-x->ptr.p_double[i]); + } + for(i=1; i<=n-2; i++) + { + w.ptr.p_double[i] = ae_fabs(diff.ptr.p_double[i]-diff.ptr.p_double[i-1], _state); + } + + /* + * Prepare Hermite interpolation scheme + */ + ae_vector_set_length(&d, n, _state); + for(i=2; i<=n-3; i++) + { + if( ae_fp_neq(ae_fabs(w.ptr.p_double[i-1], _state)+ae_fabs(w.ptr.p_double[i+1], _state),0) ) + { + d.ptr.p_double[i] = (w.ptr.p_double[i+1]*diff.ptr.p_double[i-1]+w.ptr.p_double[i-1]*diff.ptr.p_double[i])/(w.ptr.p_double[i+1]+w.ptr.p_double[i-1]); + } + else + { + d.ptr.p_double[i] = ((x->ptr.p_double[i+1]-x->ptr.p_double[i])*diff.ptr.p_double[i-1]+(x->ptr.p_double[i]-x->ptr.p_double[i-1])*diff.ptr.p_double[i])/(x->ptr.p_double[i+1]-x->ptr.p_double[i-1]); + } + } + d.ptr.p_double[0] = spline1d_diffthreepoint(x->ptr.p_double[0], x->ptr.p_double[0], y->ptr.p_double[0], x->ptr.p_double[1], y->ptr.p_double[1], x->ptr.p_double[2], y->ptr.p_double[2], _state); + d.ptr.p_double[1] = spline1d_diffthreepoint(x->ptr.p_double[1], x->ptr.p_double[0], y->ptr.p_double[0], x->ptr.p_double[1], y->ptr.p_double[1], x->ptr.p_double[2], y->ptr.p_double[2], _state); + d.ptr.p_double[n-2] = spline1d_diffthreepoint(x->ptr.p_double[n-2], x->ptr.p_double[n-3], y->ptr.p_double[n-3], x->ptr.p_double[n-2], y->ptr.p_double[n-2], x->ptr.p_double[n-1], y->ptr.p_double[n-1], _state); + d.ptr.p_double[n-1] = spline1d_diffthreepoint(x->ptr.p_double[n-1], x->ptr.p_double[n-3], y->ptr.p_double[n-3], x->ptr.p_double[n-2], y->ptr.p_double[n-2], x->ptr.p_double[n-1], y->ptr.p_double[n-1], _state); + + /* + * Build Akima spline using Hermite interpolation scheme + */ + spline1dbuildhermite(x, y, &d, n, c, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine calculates the value of the spline at the given point X. + +INPUT PARAMETERS: + C - spline interpolant + X - point + +Result: + S(x) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dcalc(spline1dinterpolant* c, double x, ae_state *_state) +{ + ae_int_t l; + ae_int_t r; + ae_int_t m; + double t; + double result; + + + ae_assert(c->k==3, "Spline1DCalc: internal error", _state); + ae_assert(!ae_isinf(x, _state), "Spline1DCalc: infinite X!", _state); + + /* + * special case: NaN + */ + if( ae_isnan(x, _state) ) + { + result = _state->v_nan; + return result; + } + + /* + * correct if periodic + */ + if( c->periodic ) + { + apperiodicmap(&x, c->x.ptr.p_double[0], c->x.ptr.p_double[c->n-1], &t, _state); + } + + /* + * Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included) + */ + l = 0; + r = c->n-2+1; + while(l!=r-1) + { + m = (l+r)/2; + if( c->x.ptr.p_double[m]>=x ) + { + r = m; + } + else + { + l = m; + } + } + + /* + * Interpolation + */ + x = x-c->x.ptr.p_double[l]; + m = 4*l; + result = c->c.ptr.p_double[m]+x*(c->c.ptr.p_double[m+1]+x*(c->c.ptr.p_double[m+2]+x*c->c.ptr.p_double[m+3])); + return result; +} + + +/************************************************************************* +This subroutine differentiates the spline. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +Result: + S - S(x) + DS - S'(x) + D2S - S''(x) + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1ddiff(spline1dinterpolant* c, + double x, + double* s, + double* ds, + double* d2s, + ae_state *_state) +{ + ae_int_t l; + ae_int_t r; + ae_int_t m; + double t; + + *s = 0; + *ds = 0; + *d2s = 0; + + ae_assert(c->k==3, "Spline1DDiff: internal error", _state); + ae_assert(!ae_isinf(x, _state), "Spline1DDiff: infinite X!", _state); + + /* + * special case: NaN + */ + if( ae_isnan(x, _state) ) + { + *s = _state->v_nan; + *ds = _state->v_nan; + *d2s = _state->v_nan; + return; + } + + /* + * correct if periodic + */ + if( c->periodic ) + { + apperiodicmap(&x, c->x.ptr.p_double[0], c->x.ptr.p_double[c->n-1], &t, _state); + } + + /* + * Binary search + */ + l = 0; + r = c->n-2+1; + while(l!=r-1) + { + m = (l+r)/2; + if( c->x.ptr.p_double[m]>=x ) + { + r = m; + } + else + { + l = m; + } + } + + /* + * Differentiation + */ + x = x-c->x.ptr.p_double[l]; + m = 4*l; + *s = c->c.ptr.p_double[m]+x*(c->c.ptr.p_double[m+1]+x*(c->c.ptr.p_double[m+2]+x*c->c.ptr.p_double[m+3])); + *ds = c->c.ptr.p_double[m+1]+2*x*c->c.ptr.p_double[m+2]+3*ae_sqr(x, _state)*c->c.ptr.p_double[m+3]; + *d2s = 2*c->c.ptr.p_double[m+2]+6*x*c->c.ptr.p_double[m+3]; +} + + +/************************************************************************* +This subroutine makes the copy of the spline. + +INPUT PARAMETERS: + C - spline interpolant. + +Result: + CC - spline copy + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dcopy(spline1dinterpolant* c, + spline1dinterpolant* cc, + ae_state *_state) +{ + ae_int_t s; + + _spline1dinterpolant_clear(cc); + + cc->periodic = c->periodic; + cc->n = c->n; + cc->k = c->k; + cc->continuity = c->continuity; + ae_vector_set_length(&cc->x, cc->n, _state); + ae_v_move(&cc->x.ptr.p_double[0], 1, &c->x.ptr.p_double[0], 1, ae_v_len(0,cc->n-1)); + s = c->c.cnt; + ae_vector_set_length(&cc->c, s, _state); + ae_v_move(&cc->c.ptr.p_double[0], 1, &c->c.ptr.p_double[0], 1, ae_v_len(0,s-1)); +} + + +/************************************************************************* +This subroutine unpacks the spline into the coefficients table. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +OUTPUT PARAMETERS: + 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] + +NOTE: + You can rebuild spline with Spline1DBuildHermite() function, which + accepts as inputs function values and derivatives at nodes, which are + easy to calculate when you have coefficients. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dunpack(spline1dinterpolant* c, + ae_int_t* n, + /* Real */ ae_matrix* tbl, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + *n = 0; + ae_matrix_clear(tbl); + + ae_matrix_set_length(tbl, c->n-2+1, 2+c->k+1, _state); + *n = c->n; + + /* + * Fill + */ + for(i=0; i<=*n-2; i++) + { + tbl->ptr.pp_double[i][0] = c->x.ptr.p_double[i]; + tbl->ptr.pp_double[i][1] = c->x.ptr.p_double[i+1]; + for(j=0; j<=c->k; j++) + { + tbl->ptr.pp_double[i][2+j] = c->c.ptr.p_double[(c->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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransx(spline1dinterpolant* c, + double a, + double b, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t n; + double v; + double dv; + double d2v; + ae_vector x; + ae_vector y; + ae_vector d; + ae_bool isperiodic; + ae_int_t contval; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + + ae_assert(c->k==3, "Spline1DLinTransX: internal error", _state); + n = c->n; + ae_vector_set_length(&x, n, _state); + ae_vector_set_length(&y, n, _state); + ae_vector_set_length(&d, n, _state); + + /* + * Unpack, X, Y, dY/dX. + * Scale and pack with Spline1DBuildHermite again. + */ + if( ae_fp_eq(a,0) ) + { + + /* + * Special case: A=0 + */ + v = spline1dcalc(c, b, _state); + for(i=0; i<=n-1; i++) + { + x.ptr.p_double[i] = c->x.ptr.p_double[i]; + y.ptr.p_double[i] = v; + d.ptr.p_double[i] = 0.0; + } + } + else + { + + /* + * General case, A<>0 + */ + for(i=0; i<=n-1; i++) + { + x.ptr.p_double[i] = c->x.ptr.p_double[i]; + spline1ddiff(c, x.ptr.p_double[i], &v, &dv, &d2v, _state); + x.ptr.p_double[i] = (x.ptr.p_double[i]-b)/a; + y.ptr.p_double[i] = v; + d.ptr.p_double[i] = a*dv; + } + } + isperiodic = c->periodic; + contval = c->continuity; + if( contval>0 ) + { + spline1dbuildhermite(&x, &y, &d, n, c, _state); + } + else + { + spline1dbuildlinear(&x, &y, n, c, _state); + } + c->periodic = isperiodic; + c->continuity = contval; + ae_frame_leave(_state); +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransy(spline1dinterpolant* c, + double a, + double b, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + + + ae_assert(c->k==3, "Spline1DLinTransX: internal error", _state); + n = c->n; + for(i=0; i<=n-2; i++) + { + c->c.ptr.p_double[4*i] = a*c->c.ptr.p_double[4*i]+b; + for(j=1; j<=3; j++) + { + c->c.ptr.p_double[4*i+j] = a*c->c.ptr.p_double[4*i+j]; + } + } + c->c.ptr.p_double[4*(n-1)+0] = a*c->c.ptr.p_double[4*(n-1)+0]+b; + c->c.ptr.p_double[4*(n-1)+1] = a*c->c.ptr.p_double[4*(n-1)+1]; +} + + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dintegrate(spline1dinterpolant* c, + double x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + ae_int_t l; + ae_int_t r; + ae_int_t m; + double w; + double v; + double t; + double intab; + double additionalterm; + double result; + + + n = c->n; + + /* + * 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->periodic&&(ae_fp_less(x,c->x.ptr.p_double[0])||ae_fp_greater(x,c->x.ptr.p_double[c->n-1])) ) + { + + /* + * compute integral(S(x)dx,A,B) + */ + intab = 0; + for(i=0; i<=c->n-2; i++) + { + w = c->x.ptr.p_double[i+1]-c->x.ptr.p_double[i]; + m = (c->k+1)*i; + intab = intab+c->c.ptr.p_double[m]*w; + v = w; + for(j=1; j<=c->k; j++) + { + v = v*w; + intab = intab+c->c.ptr.p_double[m+j]*v/(j+1); + } + } + + /* + * map X into [A,B] + */ + apperiodicmap(&x, c->x.ptr.p_double[0], c->x.ptr.p_double[c->n-1], &t, _state); + 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; + if( ae_fp_greater_eq(c->x.ptr.p_double[m],x) ) + { + r = m; + } + else + { + l = m; + } + } + + /* + * Integration + */ + result = 0; + for(i=0; i<=l-1; i++) + { + w = c->x.ptr.p_double[i+1]-c->x.ptr.p_double[i]; + m = (c->k+1)*i; + result = result+c->c.ptr.p_double[m]*w; + v = w; + for(j=1; j<=c->k; j++) + { + v = v*w; + result = result+c->c.ptr.p_double[m+j]*v/(j+1); + } + } + w = x-c->x.ptr.p_double[l]; + m = (c->k+1)*l; + v = w; + result = result+c->c.ptr.p_double[m]*w; + for(j=1; j<=c->k; j++) + { + v = v*w; + result = result+c->c.ptr.p_double[m+j]*v/(j+1); + } + result = result+additionalterm; + return result; +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiffinternal(/* Real */ ae_vector* xold, + /* Real */ ae_vector* yold, + /* Real */ ae_vector* dold, + ae_int_t n, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y, + ae_bool needy, + /* Real */ ae_vector* d1, + ae_bool needd1, + /* Real */ ae_vector* d2, + ae_bool needd2, + ae_state *_state) +{ + ae_int_t intervalindex; + ae_int_t pointindex; + ae_bool havetoadvance; + double c0; + double c1; + double c2; + double c3; + double a; + double b; + double w; + double w2; + double w3; + double fa; + double fb; + double da; + double db; + double t; + + + + /* + * Prepare space + */ + if( needy&&y->cntcntcnt=n2 ) + { + break; + } + t = x2->ptr.p_double[pointindex]; + + /* + * do we need to advance interval? + */ + havetoadvance = ae_false; + if( intervalindex==-1 ) + { + havetoadvance = ae_true; + } + else + { + if( intervalindexptr.p_double[intervalindex]; + b = xold->ptr.p_double[intervalindex+1]; + w = b-a; + w2 = w*w; + w3 = w*w2; + fa = yold->ptr.p_double[intervalindex]; + fb = yold->ptr.p_double[intervalindex+1]; + da = dold->ptr.p_double[intervalindex]; + db = dold->ptr.p_double[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->ptr.p_double[pointindex] = c0+t*(c1+t*(c2+t*c3)); + } + if( needd1 ) + { + d1->ptr.p_double[pointindex] = c1+2*t*c2+3*t*t*c3; + } + if( needd2 ) + { + d2->ptr.p_double[pointindex] = 2*c2+6*t*c3; + } + pointindex = pointindex+1; + } +} + + +/************************************************************************* +This function finds all roots and extrema of the spline S(x) defined at +[A,B] (interval which contains spline nodes). + +It does not extrapolates function, so roots and extrema located outside +of [A,B] will not be found. It returns all isolated (including multiple) +roots and extrema. + +INPUT PARAMETERS + C - spline interpolant + +OUTPUT PARAMETERS + R - array[NR], contains roots of the spline. + In case there is no roots, this array has zero length. + NR - number of roots, >=0 + DR - is set to True in case there is at least one interval + where spline is just a zero constant. Such degenerate + cases are not reported in the R/NR + E - array[NE], contains extrema (maximums/minimums) of + the spline. In case there is no extrema, this array + has zero length. + ET - array[NE], extrema types: + * ET[i]>0 in case I-th extrema is a minimum + * ET[i]<0 in case I-th extrema is a maximum + NE - number of extrema, >=0 + DE - is set to True in case there is at least one interval + where spline is a constant. Such degenerate cases are + not reported in the E/NE. + +NOTES: + +1. This function does NOT report following kinds of roots: + * intervals where function is constantly zero + * roots which are outside of [A,B] (note: it CAN return A or B) + +2. This function does NOT report following kinds of extrema: + * intervals where function is a constant + * extrema which are outside of (A,B) (note: it WON'T return A or B) + + -- ALGLIB PROJECT -- + Copyright 26.09.2011 by Bochkanov Sergey +*************************************************************************/ +void spline1drootsandextrema(spline1dinterpolant* c, + /* Real */ ae_vector* r, + ae_int_t* nr, + ae_bool* dr, + /* Real */ ae_vector* e, + /* Integer */ ae_vector* et, + ae_int_t* ne, + ae_bool* de, + ae_state *_state) +{ + ae_frame _frame_block; + double pl; + double ml; + double pll; + double pr; + double mr; + ae_vector tr; + ae_vector tmpr; + ae_vector tmpe; + ae_vector tmpet; + ae_vector tmpc; + double x0; + double x1; + double x2; + double ex0; + double ex1; + ae_int_t tne; + ae_int_t tnr; + ae_int_t i; + ae_int_t j; + ae_bool nstep; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(r); + *nr = 0; + *dr = ae_false; + ae_vector_clear(e); + ae_vector_clear(et); + *ne = 0; + *de = ae_false; + ae_vector_init(&tr, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpr, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpe, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpet, 0, DT_INT, _state, ae_true); + ae_vector_init(&tmpc, 0, DT_REAL, _state, ae_true); + + + /* + *exception handling + */ + ae_assert(c->k==3, "Spline1DRootsAndExtrema : incorrect parameter C.K!", _state); + ae_assert(c->continuity>=0, "Spline1DRootsAndExtrema : parameter C.Continuity must not be less than 0!", _state); + + /* + *initialization of variable + */ + *nr = 0; + *ne = 0; + *dr = ae_false; + *de = ae_false; + nstep = ae_true; + + /* + *consider case, when C.Continuty=0 + */ + if( c->continuity==0 ) + { + + /* + *allocation for auxiliary arrays + *'TmpR ' - it stores a time value for roots + *'TmpE ' - it stores a time value for extremums + *'TmpET '- it stores a time value for extremums type + */ + rvectorsetlengthatleast(&tmpr, 3*(c->n-1), _state); + rvectorsetlengthatleast(&tmpe, 2*(c->n-1), _state); + ivectorsetlengthatleast(&tmpet, 2*(c->n-1), _state); + + /* + *start calculating + */ + for(i=0; i<=c->n-2; i++) + { + + /* + *initialization pL, mL, pR, mR + */ + pl = c->c.ptr.p_double[4*i]; + ml = c->c.ptr.p_double[4*i+1]; + pr = c->c.ptr.p_double[4*(i+1)]; + mr = c->c.ptr.p_double[4*i+1]+2*c->c.ptr.p_double[4*i+2]*(c->x.ptr.p_double[i+1]-c->x.ptr.p_double[i])+3*c->c.ptr.p_double[4*i+3]*(c->x.ptr.p_double[i+1]-c->x.ptr.p_double[i])*(c->x.ptr.p_double[i+1]-c->x.ptr.p_double[i]); + + /* + *pre-searching roots and extremums + */ + solvecubicpolinom(pl, ml, pr, mr, c->x.ptr.p_double[i], c->x.ptr.p_double[i+1], &x0, &x1, &x2, &ex0, &ex1, &tnr, &tne, &tr, _state); + *dr = *dr||tnr==-1; + *de = *de||tne==-1; + + /* + *searching of roots + */ + if( tnr==1&&nstep ) + { + + /* + *is there roots? + */ + if( *nr>0 ) + { + + /* + *is a next root equal a previous root? + *if is't, then write new root + */ + if( ae_fp_neq(x0,tmpr.ptr.p_double[*nr-1]) ) + { + tmpr.ptr.p_double[*nr] = x0; + *nr = *nr+1; + } + } + else + { + + /* + *write a first root + */ + tmpr.ptr.p_double[*nr] = x0; + *nr = *nr+1; + } + } + else + { + + /* + *case when function at a segment identically to zero + *then we have to clear a root, if the one located on a + *constant segment + */ + if( tnr==-1 ) + { + + /* + *safe state variable as constant + */ + if( nstep ) + { + nstep = ae_false; + } + + /* + *clear the root, if there is + */ + if( *nr>0 ) + { + if( ae_fp_eq(c->x.ptr.p_double[i],tmpr.ptr.p_double[*nr-1]) ) + { + *nr = *nr-1; + } + } + + /* + *change state for 'DR' + */ + if( !*dr ) + { + *dr = ae_true; + } + } + else + { + nstep = ae_true; + } + } + + /* + *searching of extremums + */ + if( i>0 ) + { + pll = c->c.ptr.p_double[4*(i-1)]; + + /* + *if pL=pLL or pL=pR then + */ + if( tne==-1 ) + { + if( !*de ) + { + *de = ae_true; + } + } + else + { + if( ae_fp_greater(pl,pll)&&ae_fp_greater(pl,pr) ) + { + + /* + *maximum + */ + tmpet.ptr.p_int[*ne] = -1; + tmpe.ptr.p_double[*ne] = c->x.ptr.p_double[i]; + *ne = *ne+1; + } + else + { + if( ae_fp_less(pl,pll)&&ae_fp_less(pl,pr) ) + { + + /* + *minimum + */ + tmpet.ptr.p_int[*ne] = 1; + tmpe.ptr.p_double[*ne] = c->x.ptr.p_double[i]; + *ne = *ne+1; + } + } + } + } + } + + /* + *write final result + */ + rvectorsetlengthatleast(r, *nr, _state); + rvectorsetlengthatleast(e, *ne, _state); + ivectorsetlengthatleast(et, *ne, _state); + + /* + *write roots + */ + for(i=0; i<=*nr-1; i++) + { + r->ptr.p_double[i] = tmpr.ptr.p_double[i]; + } + + /* + *write extremums and their types + */ + for(i=0; i<=*ne-1; i++) + { + e->ptr.p_double[i] = tmpe.ptr.p_double[i]; + et->ptr.p_int[i] = tmpet.ptr.p_int[i]; + } + } + else + { + + /* + *case, when C.Continuity>=1 + *'TmpR ' - it stores a time value for roots + *'TmpC' - it stores a time value for extremums and + *their function value (TmpC={EX0,F(EX0), EX1,F(EX1), ..., EXn,F(EXn)};) + *'TmpE' - it stores a time value for extremums only + *'TmpET'- it stores a time value for extremums type + */ + rvectorsetlengthatleast(&tmpr, 2*c->n-1, _state); + rvectorsetlengthatleast(&tmpc, 4*c->n, _state); + rvectorsetlengthatleast(&tmpe, 2*c->n, _state); + ivectorsetlengthatleast(&tmpet, 2*c->n, _state); + + /* + *start calculating + */ + for(i=0; i<=c->n-2; i++) + { + + /* + *we calculate pL,mL, pR,mR as Fi+1(F'i+1) at left border + */ + pl = c->c.ptr.p_double[4*i]; + ml = c->c.ptr.p_double[4*i+1]; + pr = c->c.ptr.p_double[4*(i+1)]; + mr = c->c.ptr.p_double[4*(i+1)+1]; + + /* + *calculating roots and extremums at [X[i],X[i+1]] + */ + solvecubicpolinom(pl, ml, pr, mr, c->x.ptr.p_double[i], c->x.ptr.p_double[i+1], &x0, &x1, &x2, &ex0, &ex1, &tnr, &tne, &tr, _state); + + /* + *searching roots + */ + if( tnr>0 ) + { + + /* + *re-init tR + */ + if( tnr>=1 ) + { + tr.ptr.p_double[0] = x0; + } + if( tnr>=2 ) + { + tr.ptr.p_double[1] = x1; + } + if( tnr==3 ) + { + tr.ptr.p_double[2] = x2; + } + + /* + *start root selection + */ + if( *nr>0 ) + { + if( ae_fp_neq(tmpr.ptr.p_double[*nr-1],x0) ) + { + + /* + *previous segment was't constant identical zero + */ + if( nstep ) + { + for(j=0; j<=tnr-1; j++) + { + tmpr.ptr.p_double[*nr+j] = tr.ptr.p_double[j]; + } + *nr = *nr+tnr; + } + else + { + + /* + *previous segment was constant identical zero + *and we must ignore [NR+j-1] root + */ + for(j=1; j<=tnr-1; j++) + { + tmpr.ptr.p_double[*nr+j-1] = tr.ptr.p_double[j]; + } + *nr = *nr+tnr-1; + nstep = ae_true; + } + } + else + { + for(j=1; j<=tnr-1; j++) + { + tmpr.ptr.p_double[*nr+j-1] = tr.ptr.p_double[j]; + } + *nr = *nr+tnr-1; + } + } + else + { + + /* + *write first root + */ + for(j=0; j<=tnr-1; j++) + { + tmpr.ptr.p_double[*nr+j] = tr.ptr.p_double[j]; + } + *nr = *nr+tnr; + } + } + else + { + if( tnr==-1 ) + { + + /* + *decrement 'NR' if at previous step was writen a root + *(previous segment identical zero) + */ + if( *nr>0&&nstep ) + { + *nr = *nr-1; + } + + /* + *previous segment is't constant + */ + if( nstep ) + { + nstep = ae_false; + } + + /* + *rewrite 'DR' + */ + if( !*dr ) + { + *dr = ae_true; + } + } + } + + /* + *searching extremums + *write all term like extremums + */ + if( tne==1 ) + { + if( *ne>0 ) + { + + /* + *just ignore identical extremums + *because he must be one + */ + if( ae_fp_neq(tmpc.ptr.p_double[*ne-2],ex0) ) + { + tmpc.ptr.p_double[*ne] = ex0; + tmpc.ptr.p_double[*ne+1] = c->c.ptr.p_double[4*i]+c->c.ptr.p_double[4*i+1]*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+2]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+3]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i]); + *ne = *ne+2; + } + } + else + { + + /* + *write first extremum and it function value + */ + tmpc.ptr.p_double[*ne] = ex0; + tmpc.ptr.p_double[*ne+1] = c->c.ptr.p_double[4*i]+c->c.ptr.p_double[4*i+1]*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+2]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+3]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i]); + *ne = *ne+2; + } + } + else + { + if( tne==2 ) + { + if( *ne>0 ) + { + + /* + *ignore identical extremum + */ + if( ae_fp_neq(tmpc.ptr.p_double[*ne-2],ex0) ) + { + tmpc.ptr.p_double[*ne] = ex0; + tmpc.ptr.p_double[*ne+1] = c->c.ptr.p_double[4*i]+c->c.ptr.p_double[4*i+1]*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+2]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+3]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i]); + *ne = *ne+2; + } + } + else + { + + /* + *write first extremum + */ + tmpc.ptr.p_double[*ne] = ex0; + tmpc.ptr.p_double[*ne+1] = c->c.ptr.p_double[4*i]+c->c.ptr.p_double[4*i+1]*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+2]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+3]*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i])*(ex0-c->x.ptr.p_double[i]); + *ne = *ne+2; + } + + /* + *write second extremum + */ + tmpc.ptr.p_double[*ne] = ex1; + tmpc.ptr.p_double[*ne+1] = c->c.ptr.p_double[4*i]+c->c.ptr.p_double[4*i+1]*(ex1-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+2]*(ex1-c->x.ptr.p_double[i])*(ex1-c->x.ptr.p_double[i])+c->c.ptr.p_double[4*i+3]*(ex1-c->x.ptr.p_double[i])*(ex1-c->x.ptr.p_double[i])*(ex1-c->x.ptr.p_double[i]); + *ne = *ne+2; + } + else + { + if( tne==-1 ) + { + if( !*de ) + { + *de = ae_true; + } + } + } + } + } + + /* + *checking of arrays + *get number of extremums (tNe=NE/2) + *initialize pL as value F0(X[0]) and + *initialize pR as value Fn-1(X[N]) + */ + tne = *ne/2; + *ne = 0; + pl = c->c.ptr.p_double[0]; + pr = c->c.ptr.p_double[4*(c->n-1)]; + for(i=0; i<=tne-1; i++) + { + if( i>0&&ix.ptr.p_double[0]) ) + { + if( ae_fp_greater(tmpc.ptr.p_double[2*i+1],pl)&&ae_fp_greater(tmpc.ptr.p_double[2*i+1],tmpc.ptr.p_double[2*(i+1)+1]) ) + { + + /* + *maximum + */ + tmpe.ptr.p_double[*ne] = tmpc.ptr.p_double[2*i]; + tmpet.ptr.p_int[*ne] = -1; + *ne = *ne+1; + } + else + { + if( ae_fp_less(tmpc.ptr.p_double[2*i+1],pl)&&ae_fp_less(tmpc.ptr.p_double[2*i+1],tmpc.ptr.p_double[2*(i+1)+1]) ) + { + + /* + *minimum + */ + tmpe.ptr.p_double[*ne] = tmpc.ptr.p_double[2*i]; + tmpet.ptr.p_int[*ne] = 1; + *ne = *ne+1; + } + } + } + } + else + { + if( i==tne-1 ) + { + if( ae_fp_neq(tmpc.ptr.p_double[2*i],c->x.ptr.p_double[c->n-1]) ) + { + if( ae_fp_greater(tmpc.ptr.p_double[2*i+1],tmpc.ptr.p_double[2*(i-1)+1])&&ae_fp_greater(tmpc.ptr.p_double[2*i+1],pr) ) + { + + /* + *maximum + */ + tmpe.ptr.p_double[*ne] = tmpc.ptr.p_double[2*i]; + tmpet.ptr.p_int[*ne] = -1; + *ne = *ne+1; + } + else + { + if( ae_fp_less(tmpc.ptr.p_double[2*i+1],tmpc.ptr.p_double[2*(i-1)+1])&&ae_fp_less(tmpc.ptr.p_double[2*i+1],pr) ) + { + + /* + *minimum + */ + tmpe.ptr.p_double[*ne] = tmpc.ptr.p_double[2*i]; + tmpet.ptr.p_int[*ne] = 1; + *ne = *ne+1; + } + } + } + } + } + } + } + + /* + *final results + *allocate R, E, ET + */ + rvectorsetlengthatleast(r, *nr, _state); + rvectorsetlengthatleast(e, *ne, _state); + ivectorsetlengthatleast(et, *ne, _state); + + /* + *write result for extremus and their types + */ + for(i=0; i<=*ne-1; i++) + { + e->ptr.p_double[i] = tmpe.ptr.p_double[i]; + et->ptr.p_int[i] = tmpet.ptr.p_int[i]; + } + + /* + *write result for roots + */ + for(i=0; i<=*nr-1; i++) + { + r->ptr.p_double[i] = tmpr.ptr.p_double[i]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. Heap sort. +*************************************************************************/ +void heapsortdpoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* d, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector rbuf; + ae_vector ibuf; + ae_vector rbuf2; + ae_vector ibuf2; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&rbuf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ibuf, 0, DT_INT, _state, ae_true); + ae_vector_init(&rbuf2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ibuf2, 0, DT_INT, _state, ae_true); + + ae_vector_set_length(&ibuf, n, _state); + ae_vector_set_length(&rbuf, n, _state); + for(i=0; i<=n-1; i++) + { + ibuf.ptr.p_int[i] = i; + } + tagsortfasti(x, &ibuf, &rbuf2, &ibuf2, n, _state); + for(i=0; i<=n-1; i++) + { + rbuf.ptr.p_double[i] = y->ptr.p_double[ibuf.ptr.p_int[i]]; + } + ae_v_move(&y->ptr.p_double[0], 1, &rbuf.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + rbuf.ptr.p_double[i] = d->ptr.p_double[ibuf.ptr.p_int[i]]; + } + ae_v_move(&d->ptr.p_double[0], 1, &rbuf.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +This procedure search roots of an quadratic equation inside [0;1] and it number of roots. + +INPUT PARAMETERS: + P0 - value of a function at 0 + M0 - value of a derivative at 0 + P1 - value of a function at 1 + M1 - value of a derivative at 1 + +OUTPUT PARAMETERS: + X0 - first root of an equation + X1 - second root of an equation + NR - number of roots + +RESTRICTIONS OF PARAMETERS: + +Parameters for this procedure has't to be zero simultaneously. Is expected, +that input polinom is't degenerate or constant identicaly ZERO. + + +REMARK: + +The procedure always fill value for X1 and X2, even if it is't belongs to [0;1]. +But first true root(even if existing one) is in X1. +Number of roots is NR. + + -- ALGLIB PROJECT -- + Copyright 26.09.2011 by Bochkanov Sergey +*************************************************************************/ +void solvepolinom2(double p0, + double m0, + double p1, + double m1, + double* x0, + double* x1, + ae_int_t* nr, + ae_state *_state) +{ + double a; + double b; + double c; + double dd; + double tmp; + double exf; + double extr; + + *x0 = 0; + *x1 = 0; + *nr = 0; + + + /* + *calculate parameters for equation: A, B and C + */ + a = 6*p0+3*m0-6*p1+3*m1; + b = -6*p0-4*m0+6*p1-2*m1; + c = m0; + + /* + *check case, when A=0 + *we are considering the linear equation + */ + if( ae_fp_eq(a,0) ) + { + + /* + *B<>0 and root inside [0;1] + *one root + */ + if( (ae_fp_neq(b,0)&&ae_sign(c, _state)*ae_sign(b, _state)<=0)&&ae_fp_greater_eq(ae_fabs(b, _state),ae_fabs(c, _state)) ) + { + *x0 = -c/b; + *nr = 1; + return; + } + else + { + *nr = 0; + return; + } + } + + /* + *consider case, when extremumu outside (0;1) + *exist one root only + */ + if( ae_fp_less_eq(ae_fabs(2*a, _state),ae_fabs(b, _state))||ae_sign(b, _state)*ae_sign(a, _state)>=0 ) + { + if( ae_sign(m0, _state)*ae_sign(m1, _state)>0 ) + { + *nr = 0; + return; + } + + /* + *consider case, when the one exist + *same sign of derivative + */ + if( ae_sign(m0, _state)*ae_sign(m1, _state)<0 ) + { + *nr = 1; + extr = -b/(2*a); + dd = b*b-4*a*c; + if( ae_fp_less(dd,0) ) + { + return; + } + *x0 = (-b-ae_sqrt(dd, _state))/(2*a); + *x1 = (-b+ae_sqrt(dd, _state))/(2*a); + if( (ae_fp_greater_eq(extr,1)&&ae_fp_less_eq(*x1,extr))||(ae_fp_less_eq(extr,0)&&ae_fp_greater_eq(*x1,extr)) ) + { + *x0 = *x1; + } + return; + } + + /* + *consider case, when the one is 0 + */ + if( ae_fp_eq(m0,0) ) + { + *x0 = 0; + *nr = 1; + return; + } + if( ae_fp_eq(m1,0) ) + { + *x0 = 1; + *nr = 1; + return; + } + } + else + { + + /* + *consider case, when both of derivatives is 0 + */ + if( ae_fp_eq(m0,0)&&ae_fp_eq(m1,0) ) + { + *x0 = 0; + *x1 = 1; + *nr = 2; + return; + } + + /* + *consider case, when derivative at 0 is 0, and derivative at 1 is't 0 + */ + if( ae_fp_eq(m0,0)&&ae_fp_neq(m1,0) ) + { + dd = b*b-4*a*c; + if( ae_fp_less(dd,0) ) + { + *x0 = 0; + *nr = 1; + return; + } + *x0 = (-b-ae_sqrt(dd, _state))/(2*a); + *x1 = (-b+ae_sqrt(dd, _state))/(2*a); + extr = -b/(2*a); + exf = a*extr*extr+b*extr+c; + if( ae_sign(exf, _state)*ae_sign(m1, _state)>0 ) + { + *x0 = 0; + *nr = 1; + return; + } + else + { + if( ae_fp_greater(extr,*x0) ) + { + *x0 = 0; + } + else + { + *x1 = 0; + } + *nr = 2; + + /* + *roots must placed ascending + */ + if( ae_fp_greater(*x0,*x1) ) + { + tmp = *x0; + *x0 = *x1; + *x1 = tmp; + } + return; + } + } + if( ae_fp_eq(m1,0)&&ae_fp_neq(m0,0) ) + { + dd = b*b-4*a*c; + if( ae_fp_less(dd,0) ) + { + *x0 = 1; + *nr = 1; + return; + } + *x0 = (-b-ae_sqrt(dd, _state))/(2*a); + *x1 = (-b+ae_sqrt(dd, _state))/(2*a); + extr = -b/(2*a); + exf = a*extr*extr+b*extr+c; + if( ae_sign(exf, _state)*ae_sign(m0, _state)>0 ) + { + *x0 = 1; + *nr = 1; + return; + } + else + { + if( ae_fp_less(extr,*x0) ) + { + *x0 = 1; + } + else + { + *x1 = 1; + } + *nr = 2; + + /* + *roots must placed ascending + */ + if( ae_fp_greater(*x0,*x1) ) + { + tmp = *x0; + *x0 = *x1; + *x1 = tmp; + } + return; + } + } + else + { + extr = -b/(2*a); + exf = a*extr*extr+b*extr+c; + if( ae_sign(exf, _state)*ae_sign(m0, _state)>0&&ae_sign(exf, _state)*ae_sign(m1, _state)>0 ) + { + *nr = 0; + return; + } + dd = b*b-4*a*c; + if( ae_fp_less(dd,0) ) + { + *nr = 0; + return; + } + *x0 = (-b-ae_sqrt(dd, _state))/(2*a); + *x1 = (-b+ae_sqrt(dd, _state))/(2*a); + + /* + *if EXF and m0, EXF and m1 has different signs, then equation has two roots + */ + if( ae_sign(exf, _state)*ae_sign(m0, _state)<0&&ae_sign(exf, _state)*ae_sign(m1, _state)<0 ) + { + *nr = 2; + + /* + *roots must placed ascending + */ + if( ae_fp_greater(*x0,*x1) ) + { + tmp = *x0; + *x0 = *x1; + *x1 = tmp; + } + return; + } + else + { + *nr = 1; + if( ae_sign(exf, _state)*ae_sign(m0, _state)<0 ) + { + if( ae_fp_less(*x1,extr) ) + { + *x0 = *x1; + } + return; + } + if( ae_sign(exf, _state)*ae_sign(m1, _state)<0 ) + { + if( ae_fp_greater(*x1,extr) ) + { + *x0 = *x1; + } + return; + } + } + } + } +} + + +/************************************************************************* +This procedure search roots of an cubic equation inside [A;B], it number of roots +and number of extremums. + +INPUT PARAMETERS: + pA - value of a function at A + mA - value of a derivative at A + pB - value of a function at B + mB - value of a derivative at B + A0 - left border [A0;B0] + B0 - right border [A0;B0] + +OUTPUT PARAMETERS: + X0 - first root of an equation + X1 - second root of an equation + X2 - third root of an equation + EX0 - first extremum of a function + EX0 - second extremum of a function + NR - number of roots + NR - number of extrmums + +RESTRICTIONS OF PARAMETERS: + +Length of [A;B] must be positive and is't zero, i.e. A<>B and AB + */ + ae_assert(ae_fp_less(a,b), "\nSolveCubicPolinom: incorrect borders for [A;B]!\n", _state); + + /* + *case 1 + *function can be identicaly to ZERO + */ + if( ((ae_fp_eq(ma,0)&&ae_fp_eq(mb,0))&&ae_fp_eq(pa,pb))&&ae_fp_eq(pa,0) ) + { + *nr = -1; + *ne = -1; + return; + } + if( (ae_fp_eq(ma,0)&&ae_fp_eq(mb,0))&&ae_fp_eq(pa,pb) ) + { + *nr = 0; + *ne = -1; + return; + } + tmpma = ma*(b-a); + tmpmb = mb*(b-a); + solvepolinom2(pa, tmpma, pb, tmpmb, ex0, ex1, ne, _state); + *ex0 = spline1d_rescaleval(0, 1, a, b, *ex0, _state); + *ex1 = spline1d_rescaleval(0, 1, a, b, *ex1, _state); + + /* + *case 3.1 + *no extremums at [A;B] + */ + if( *ne==0 ) + { + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, x0, _state); + if( *nr==1 ) + { + *x0 = spline1d_rescaleval(0, 1, a, b, *x0, _state); + } + return; + } + + /* + *case 3.2 + *one extremum + */ + if( *ne==1 ) + { + if( ae_fp_eq(*ex0,a)||ae_fp_eq(*ex0,b) ) + { + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, x0, _state); + if( *nr==1 ) + { + *x0 = spline1d_rescaleval(0, 1, a, b, *x0, _state); + } + return; + } + else + { + *nr = 0; + i = 0; + tex0 = spline1d_rescaleval(a, b, 0, 1, *ex0, _state); + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, x0, _state)+(*nr); + if( *nr>i ) + { + tempdata->ptr.p_double[i] = spline1d_rescaleval(0, tex0, a, *ex0, *x0, _state); + i = i+1; + } + *nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, x0, _state)+(*nr); + if( *nr>i ) + { + *x0 = spline1d_rescaleval(tex0, 1, *ex0, b, *x0, _state); + if( i>0 ) + { + if( ae_fp_neq(*x0,tempdata->ptr.p_double[i-1]) ) + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + else + { + *nr = *nr-1; + } + } + else + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + } + if( *nr>0 ) + { + *x0 = tempdata->ptr.p_double[0]; + if( *nr>1 ) + { + *x1 = tempdata->ptr.p_double[1]; + } + return; + } + } + return; + } + else + { + + /* + *case 3.3 + *two extremums(or more, but it's impossible) + * + * + *case 3.3.0 + *both extremums at the border + */ + if( ae_fp_eq(*ex0,a)&&ae_fp_eq(*ex1,b) ) + { + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, x0, _state); + if( *nr==1 ) + { + *x0 = spline1d_rescaleval(0, 1, a, b, *x0, _state); + } + return; + } + if( ae_fp_eq(*ex0,a)&&ae_fp_neq(*ex1,b) ) + { + *nr = 0; + i = 0; + tex1 = spline1d_rescaleval(a, b, 0, 1, *ex1, _state); + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex1, x0, _state)+(*nr); + if( *nr>i ) + { + tempdata->ptr.p_double[i] = spline1d_rescaleval(0, tex1, a, *ex1, *x0, _state); + i = i+1; + } + *nr = bisectmethod(pa, tmpma, pb, tmpmb, tex1, 1, x0, _state)+(*nr); + if( *nr>i ) + { + *x0 = spline1d_rescaleval(tex1, 1, *ex1, b, *x0, _state); + if( ae_fp_neq(*x0,tempdata->ptr.p_double[i-1]) ) + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + else + { + *nr = *nr-1; + } + } + if( *nr>0 ) + { + *x0 = tempdata->ptr.p_double[0]; + if( *nr>1 ) + { + *x1 = tempdata->ptr.p_double[1]; + } + return; + } + } + if( ae_fp_eq(*ex1,b)&&ae_fp_neq(*ex0,a) ) + { + *nr = 0; + i = 0; + tex0 = spline1d_rescaleval(a, b, 0, 1, *ex0, _state); + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, x0, _state)+(*nr); + if( *nr>i ) + { + tempdata->ptr.p_double[i] = spline1d_rescaleval(0, tex0, a, *ex0, *x0, _state); + i = i+1; + } + *nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, x0, _state)+(*nr); + if( *nr>i ) + { + *x0 = spline1d_rescaleval(tex0, 1, *ex0, b, *x0, _state); + if( i>0 ) + { + if( ae_fp_neq(*x0,tempdata->ptr.p_double[i-1]) ) + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + else + { + *nr = *nr-1; + } + } + else + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + } + if( *nr>0 ) + { + *x0 = tempdata->ptr.p_double[0]; + if( *nr>1 ) + { + *x1 = tempdata->ptr.p_double[1]; + } + return; + } + } + else + { + + /* + *case 3.3.2 + *both extremums inside (0;1) + */ + *nr = 0; + i = 0; + tex0 = spline1d_rescaleval(a, b, 0, 1, *ex0, _state); + tex1 = spline1d_rescaleval(a, b, 0, 1, *ex1, _state); + *nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, x0, _state)+(*nr); + if( *nr>i ) + { + tempdata->ptr.p_double[i] = spline1d_rescaleval(0, tex0, a, *ex0, *x0, _state); + i = i+1; + } + *nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, tex1, x0, _state)+(*nr); + if( *nr>i ) + { + *x0 = spline1d_rescaleval(tex0, tex1, *ex0, *ex1, *x0, _state); + if( i>0 ) + { + if( ae_fp_neq(*x0,tempdata->ptr.p_double[i-1]) ) + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + else + { + *nr = *nr-1; + } + } + else + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + } + *nr = bisectmethod(pa, tmpma, pb, tmpmb, tex1, 1, x0, _state)+(*nr); + if( *nr>i ) + { + *x0 = spline1d_rescaleval(tex1, 1, *ex1, b, *x0, _state); + if( i>0 ) + { + if( ae_fp_neq(*x0,tempdata->ptr.p_double[i-1]) ) + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + else + { + *nr = *nr-1; + } + } + else + { + tempdata->ptr.p_double[i] = *x0; + i = i+1; + } + } + + /* + *write are found roots + */ + if( *nr>0 ) + { + *x0 = tempdata->ptr.p_double[0]; + if( *nr>1 ) + { + *x1 = tempdata->ptr.p_double[1]; + } + if( *nr>2 ) + { + *x2 = tempdata->ptr.p_double[2]; + } + return; + } + } + } +} + + +/************************************************************************* +Function for searching a root at [A;B] by bisection method and return number of roots +(0 or 1) + +INPUT PARAMETERS: + pA - value of a function at A + mA - value of a derivative at A + pB - value of a function at B + mB - value of a derivative at B + A0 - left border [A0;B0] + B0 - right border [A0;B0] + +RESTRICTIONS OF PARAMETERS: + +We assume, that B0>A0. + + +REMARK: + +Assume, that exist one root only at [A;B], else +function may be work incorrectly. +The function dont check value A0,B0! + + -- ALGLIB PROJECT -- + Copyright 26.09.2011 by Bochkanov Sergey +*************************************************************************/ +ae_int_t bisectmethod(double pa, + double ma, + double pb, + double mb, + double a, + double b, + double* x, + ae_state *_state) +{ + double vacuum; + double eps; + double a0; + double b0; + double m; + double lf; + double rf; + double mf; + ae_int_t result; + + *x = 0; + + + /* + *accuracy + */ + eps = 1000*(b-a)*ae_machineepsilon; + + /* + *initialization left and right borders + */ + a0 = a; + b0 = b; + + /* + *initialize function value at 'A' and 'B' + */ + spline1d_hermitecalc(pa, ma, pb, mb, a, &lf, &vacuum, _state); + spline1d_hermitecalc(pa, ma, pb, mb, b, &rf, &vacuum, _state); + + /* + *check, that 'A' and 'B' are't roots, + *and that root exist + */ + if( ae_sign(lf, _state)*ae_sign(rf, _state)>0 ) + { + result = 0; + return result; + } + else + { + if( ae_fp_eq(lf,0) ) + { + *x = a; + result = 1; + return result; + } + else + { + if( ae_fp_eq(rf,0) ) + { + *x = b; + result = 1; + return result; + } + } + } + + /* + *searching a root + */ + do + { + m = (b0+a0)/2; + spline1d_hermitecalc(pa, ma, pb, mb, a0, &lf, &vacuum, _state); + spline1d_hermitecalc(pa, ma, pb, mb, b0, &rf, &vacuum, _state); + spline1d_hermitecalc(pa, ma, pb, mb, m, &mf, &vacuum, _state); + if( ae_sign(mf, _state)*ae_sign(lf, _state)<0 ) + { + b0 = m; + } + else + { + if( ae_sign(mf, _state)*ae_sign(rf, _state)<0 ) + { + a0 = m; + } + else + { + if( ae_fp_eq(lf,0) ) + { + *x = a0; + result = 1; + return result; + } + if( ae_fp_eq(rf,0) ) + { + *x = b0; + result = 1; + return result; + } + if( ae_fp_eq(mf,0) ) + { + *x = m; + result = 1; + return result; + } + } + } + } + while(ae_fp_greater_eq(ae_fabs(b0-a0, _state),eps)); + *x = m; + result = 1; + return result; +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 21.06.2012 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildmonotone(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector d; + ae_vector ex; + ae_vector ey; + ae_vector p; + double delta; + double alpha; + double beta; + ae_int_t tmpn; + ae_int_t sn; + double ca; + double cb; + double epsilon; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _spline1dinterpolant_clear(c); + ae_vector_init(&d, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ex, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ey, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * Check lengths of arguments + */ + ae_assert(n>=2, "Spline1DBuildMonotone: N<2", _state); + ae_assert(x->cnt>=n, "Spline1DBuildMonotone: Length(X)cnt>=n, "Spline1DBuildMonotone: Length(Y)ptr.p_double[0]-ae_fabs(x->ptr.p_double[1]-x->ptr.p_double[0], _state); + ex.ptr.p_double[n-1] = x->ptr.p_double[n-3]+ae_fabs(x->ptr.p_double[n-3]-x->ptr.p_double[n-4], _state); + ey.ptr.p_double[0] = y->ptr.p_double[0]; + ey.ptr.p_double[n-1] = y->ptr.p_double[n-3]; + for(i=1; i<=n-2; i++) + { + ex.ptr.p_double[i] = x->ptr.p_double[i-1]; + ey.ptr.p_double[i] = y->ptr.p_double[i-1]; + } + + /* + * Init sign of the function for first segment + */ + i = 0; + ca = 0; + do + { + ca = ey.ptr.p_double[i+1]-ey.ptr.p_double[i]; + i = i+1; + } + while(!(ae_fp_neq(ca,0)||i>n-2)); + if( ae_fp_neq(ca,0) ) + { + ca = ca/ae_fabs(ca, _state); + } + i = 0; + while(i=2, "Spline1DBuildMonotone: internal error", _state); + + /* + * Calculate derivatives for current segment + */ + d.ptr.p_double[i] = 0; + d.ptr.p_double[sn-1] = 0; + for(j=i+1; j<=sn-2; j++) + { + d.ptr.p_double[j] = ((ey.ptr.p_double[j]-ey.ptr.p_double[j-1])/(ex.ptr.p_double[j]-ex.ptr.p_double[j-1])+(ey.ptr.p_double[j+1]-ey.ptr.p_double[j])/(ex.ptr.p_double[j+1]-ex.ptr.p_double[j]))/2; + } + for(j=i; j<=sn-2; j++) + { + delta = (ey.ptr.p_double[j+1]-ey.ptr.p_double[j])/(ex.ptr.p_double[j+1]-ex.ptr.p_double[j]); + if( ae_fp_less_eq(ae_fabs(delta, _state),epsilon) ) + { + d.ptr.p_double[j] = 0; + d.ptr.p_double[j+1] = 0; + } + else + { + alpha = d.ptr.p_double[j]/delta; + beta = d.ptr.p_double[j+1]/delta; + if( ae_fp_neq(alpha,0) ) + { + cb = alpha*ae_sqrt(1+ae_sqr(beta/alpha, _state), _state); + } + else + { + if( ae_fp_neq(beta,0) ) + { + cb = beta; + } + else + { + continue; + } + } + if( ae_fp_greater(cb,3) ) + { + d.ptr.p_double[j] = 3*alpha*delta/cb; + d.ptr.p_double[j+1] = 3*beta*delta/cb; + } + } + } + + /* + * Transition to next segment + */ + i = sn-1; + } + spline1dbuildhermite(&ex, &ey, &d, n, c, _state); + c->continuity = 2; + ae_frame_leave(_state); +} + + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +static void spline1d_spline1dgriddiffcubicinternal(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d, + /* Real */ ae_vector* a1, + /* Real */ ae_vector* a2, + /* Real */ ae_vector* a3, + /* Real */ ae_vector* b, + /* Real */ ae_vector* dt, + ae_state *_state) +{ + ae_int_t i; + + + + /* + * allocate arrays + */ + if( d->cntcntcntcntcntcntptr.p_double[0] = (y->ptr.p_double[1]-y->ptr.p_double[0])/(x->ptr.p_double[1]-x->ptr.p_double[0]); + d->ptr.p_double[1] = d->ptr.p_double[0]; + return; + } + if( (n==2&&boundltype==-1)&&boundrtype==-1 ) + { + d->ptr.p_double[0] = 0; + d->ptr.p_double[1] = 0; + return; + } + + /* + * Periodic and non-periodic boundary conditions are + * two separate classes + */ + if( boundrtype==-1&&boundltype==-1 ) + { + + /* + * Periodic boundary conditions + */ + y->ptr.p_double[n-1] = y->ptr.p_double[0]; + + /* + * Boundary conditions at N-1 points + * (one point less because last point is the same as first point). + */ + a1->ptr.p_double[0] = x->ptr.p_double[1]-x->ptr.p_double[0]; + a2->ptr.p_double[0] = 2*(x->ptr.p_double[1]-x->ptr.p_double[0]+x->ptr.p_double[n-1]-x->ptr.p_double[n-2]); + a3->ptr.p_double[0] = x->ptr.p_double[n-1]-x->ptr.p_double[n-2]; + b->ptr.p_double[0] = 3*(y->ptr.p_double[n-1]-y->ptr.p_double[n-2])/(x->ptr.p_double[n-1]-x->ptr.p_double[n-2])*(x->ptr.p_double[1]-x->ptr.p_double[0])+3*(y->ptr.p_double[1]-y->ptr.p_double[0])/(x->ptr.p_double[1]-x->ptr.p_double[0])*(x->ptr.p_double[n-1]-x->ptr.p_double[n-2]); + 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->ptr.p_double[i] = x->ptr.p_double[i+1]-x->ptr.p_double[i]; + a2->ptr.p_double[i] = 2*(x->ptr.p_double[i+1]-x->ptr.p_double[i-1]); + a3->ptr.p_double[i] = x->ptr.p_double[i]-x->ptr.p_double[i-1]; + b->ptr.p_double[i] = 3*(y->ptr.p_double[i]-y->ptr.p_double[i-1])/(x->ptr.p_double[i]-x->ptr.p_double[i-1])*(x->ptr.p_double[i+1]-x->ptr.p_double[i])+3*(y->ptr.p_double[i+1]-y->ptr.p_double[i])/(x->ptr.p_double[i+1]-x->ptr.p_double[i])*(x->ptr.p_double[i]-x->ptr.p_double[i-1]); + } + + /* + * Solve, add last point (with index N-1) + */ + spline1d_solvecyclictridiagonal(a1, a2, a3, b, n-1, dt, _state); + ae_v_move(&d->ptr.p_double[0], 1, &dt->ptr.p_double[0], 1, ae_v_len(0,n-2)); + d->ptr.p_double[n-1] = d->ptr.p_double[0]; + } + else + { + + /* + * Non-periodic boundary condition. + * Left boundary conditions. + */ + if( boundltype==0 ) + { + a1->ptr.p_double[0] = 0; + a2->ptr.p_double[0] = 1; + a3->ptr.p_double[0] = 1; + b->ptr.p_double[0] = 2*(y->ptr.p_double[1]-y->ptr.p_double[0])/(x->ptr.p_double[1]-x->ptr.p_double[0]); + } + if( boundltype==1 ) + { + a1->ptr.p_double[0] = 0; + a2->ptr.p_double[0] = 1; + a3->ptr.p_double[0] = 0; + b->ptr.p_double[0] = boundl; + } + if( boundltype==2 ) + { + a1->ptr.p_double[0] = 0; + a2->ptr.p_double[0] = 2; + a3->ptr.p_double[0] = 1; + b->ptr.p_double[0] = 3*(y->ptr.p_double[1]-y->ptr.p_double[0])/(x->ptr.p_double[1]-x->ptr.p_double[0])-0.5*boundl*(x->ptr.p_double[1]-x->ptr.p_double[0]); + } + + /* + * Central conditions + */ + for(i=1; i<=n-2; i++) + { + a1->ptr.p_double[i] = x->ptr.p_double[i+1]-x->ptr.p_double[i]; + a2->ptr.p_double[i] = 2*(x->ptr.p_double[i+1]-x->ptr.p_double[i-1]); + a3->ptr.p_double[i] = x->ptr.p_double[i]-x->ptr.p_double[i-1]; + b->ptr.p_double[i] = 3*(y->ptr.p_double[i]-y->ptr.p_double[i-1])/(x->ptr.p_double[i]-x->ptr.p_double[i-1])*(x->ptr.p_double[i+1]-x->ptr.p_double[i])+3*(y->ptr.p_double[i+1]-y->ptr.p_double[i])/(x->ptr.p_double[i+1]-x->ptr.p_double[i])*(x->ptr.p_double[i]-x->ptr.p_double[i-1]); + } + + /* + * Right boundary conditions + */ + if( boundrtype==0 ) + { + a1->ptr.p_double[n-1] = 1; + a2->ptr.p_double[n-1] = 1; + a3->ptr.p_double[n-1] = 0; + b->ptr.p_double[n-1] = 2*(y->ptr.p_double[n-1]-y->ptr.p_double[n-2])/(x->ptr.p_double[n-1]-x->ptr.p_double[n-2]); + } + if( boundrtype==1 ) + { + a1->ptr.p_double[n-1] = 0; + a2->ptr.p_double[n-1] = 1; + a3->ptr.p_double[n-1] = 0; + b->ptr.p_double[n-1] = boundr; + } + if( boundrtype==2 ) + { + a1->ptr.p_double[n-1] = 1; + a2->ptr.p_double[n-1] = 2; + a3->ptr.p_double[n-1] = 0; + b->ptr.p_double[n-1] = 3*(y->ptr.p_double[n-1]-y->ptr.p_double[n-2])/(x->ptr.p_double[n-1]-x->ptr.p_double[n-2])+0.5*boundr*(x->ptr.p_double[n-1]-x->ptr.p_double[n-2]); + } + + /* + * Solve + */ + spline1d_solvetridiagonal(a1, a2, a3, b, n, d, _state); + } +} + + +/************************************************************************* +Internal subroutine. Heap sort. +*************************************************************************/ +static void spline1d_heapsortpoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector bufx; + ae_vector bufy; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&bufx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bufy, 0, DT_REAL, _state, ae_true); + + tagsortfastr(x, y, &bufx, &bufy, n, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +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]]) +*************************************************************************/ +static void spline1d_heapsortppoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Integer */ ae_vector* p, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector rbuf; + ae_vector ibuf; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&rbuf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ibuf, 0, DT_INT, _state, ae_true); + + if( p->cntptr.p_int[i] = i; + } + tagsortfasti(x, p, &rbuf, &ibuf, n, _state); + for(i=0; i<=n-1; i++) + { + rbuf.ptr.p_double[i] = y->ptr.p_double[p->ptr.p_int[i]]; + } + ae_v_move(&y->ptr.p_double[0], 1, &rbuf.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +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] ) + +*************************************************************************/ +static void spline1d_solvetridiagonal(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + ae_int_t n, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _b; + ae_vector _d; + ae_int_t k; + double t; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_b, b, _state, ae_true); + b = &_b; + ae_vector_init_copy(&_d, d, _state, ae_true); + d = &_d; + + if( x->cntptr.p_double[k]/b->ptr.p_double[k-1]; + b->ptr.p_double[k] = b->ptr.p_double[k]-t*c->ptr.p_double[k-1]; + d->ptr.p_double[k] = d->ptr.p_double[k]-t*d->ptr.p_double[k-1]; + } + x->ptr.p_double[n-1] = d->ptr.p_double[n-1]/b->ptr.p_double[n-1]; + for(k=n-2; k>=0; k--) + { + x->ptr.p_double[k] = (d->ptr.p_double[k]-c->ptr.p_double[k]*x->ptr.p_double[k+1])/b->ptr.p_double[k]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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] ) +*************************************************************************/ +static void spline1d_solvecyclictridiagonal(/* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + ae_int_t n, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _b; + ae_int_t k; + double alpha; + double beta; + double gamma; + ae_vector y; + ae_vector z; + ae_vector u; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_b, b, _state, ae_true); + b = &_b; + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&z, 0, DT_REAL, _state, ae_true); + ae_vector_init(&u, 0, DT_REAL, _state, ae_true); + + if( x->cntptr.p_double[0]; + alpha = c->ptr.p_double[n-1]; + gamma = -b->ptr.p_double[0]; + b->ptr.p_double[0] = 2*b->ptr.p_double[0]; + b->ptr.p_double[n-1] = b->ptr.p_double[n-1]-alpha*beta/gamma; + ae_vector_set_length(&u, n, _state); + for(k=0; k<=n-1; k++) + { + u.ptr.p_double[k] = 0; + } + u.ptr.p_double[0] = gamma; + u.ptr.p_double[n-1] = alpha; + spline1d_solvetridiagonal(a, b, c, d, n, &y, _state); + spline1d_solvetridiagonal(a, b, c, &u, n, &z, _state); + for(k=0; k<=n-1; k++) + { + x->ptr.p_double[k] = y.ptr.p_double[k]-(y.ptr.p_double[0]+beta/gamma*y.ptr.p_double[n-1])/(1+z.ptr.p_double[0]+beta/gamma*z.ptr.p_double[n-1])*z.ptr.p_double[k]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. Three-point differentiation +*************************************************************************/ +static double spline1d_diffthreepoint(double t, + double x0, + double f0, + double x1, + double f1, + double x2, + double f2, + ae_state *_state) +{ + double a; + double b; + double result; + + + t = t-x0; + x1 = x1-x0; + x2 = x2-x0; + a = (f2-f0-x2/x1*(f1-f0))/(ae_sqr(x2, _state)-x1*x2); + b = (f1-f0-a*ae_sqr(x1, _state))/x1; + result = 2*a*t+b; + return result; +} + + +/************************************************************************* +Procedure for calculating value of a function is providet in the form of +Hermite polinom + +INPUT PARAMETERS: + P0 - value of a function at 0 + M0 - value of a derivative at 0 + P1 - value of a function at 1 + M1 - value of a derivative at 1 + T - point inside [0;1] + +OUTPUT PARAMETERS: + S - value of a function at T + B0 - value of a derivative function at T + + -- ALGLIB PROJECT -- + Copyright 26.09.2011 by Bochkanov Sergey +*************************************************************************/ +static void spline1d_hermitecalc(double p0, + double m0, + double p1, + double m1, + double t, + double* s, + double* ds, + ae_state *_state) +{ + + *s = 0; + *ds = 0; + + *s = p0*(1+2*t)*(1-t)*(1-t)+m0*t*(1-t)*(1-t)+p1*(3-2*t)*t*t+m1*t*t*(t-1); + *ds = -p0*6*t*(1-t)+m0*(1-t)*(1-3*t)+p1*6*t*(1-t)+m1*t*(3*t-2); +} + + +/************************************************************************* +Function for mapping from [A0;B0] to [A1;B1] + +INPUT PARAMETERS: + A0 - left border [A0;B0] + B0 - right border [A0;B0] + A1 - left border [A1;B1] + B1 - right border [A1;B1] + T - value inside [A0;B0] + +RESTRICTIONS OF PARAMETERS: + +We assume, that B0>A0 and B1>A1. But we chech, that T is inside [A0;B0], +and if TB0 then T - B1. + +INPUT PARAMETERS: + A0 - left border for segment [A0;B0] from 'T' is converted to [A1;B1] + B0 - right border for segment [A0;B0] from 'T' is converted to [A1;B1] + A1 - left border for segment [A1;B1] to 'T' is converted from [A0;B0] + B1 - right border for segment [A1;B1] to 'T' is converted from [A0;B0] + T - the parameter is mapped from [A0;B0] to [A1;B1] + +Result: + is converted value for 'T' from [A0;B0] to [A1;B1] + +REMARK: + +The function dont check value A0,B0 and A1,B1! + + -- ALGLIB PROJECT -- + Copyright 26.09.2011 by Bochkanov Sergey +*************************************************************************/ +static double spline1d_rescaleval(double a0, + double b0, + double a1, + double b1, + double t, + ae_state *_state) +{ + double result; + + + + /* + *return left border + */ + if( ae_fp_less_eq(t,a0) ) + { + result = a1; + return result; + } + + /* + *return right border + */ + if( ae_fp_greater_eq(t,b0) ) + { + result = b1; + return result; + } + + /* + *return value between left and right borders + */ + result = (b1-a1)*(t-a0)/(b0-a0)+a1; + return result; +} + + +ae_bool _spline1dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + spline1dinterpolant *p = (spline1dinterpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->c, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _spline1dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + spline1dinterpolant *dst = (spline1dinterpolant*)_dst; + spline1dinterpolant *src = (spline1dinterpolant*)_src; + dst->periodic = src->periodic; + dst->n = src->n; + dst->k = src->k; + dst->continuity = src->continuity; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->c, &src->c, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _spline1dinterpolant_clear(void* _p) +{ + spline1dinterpolant *p = (spline1dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->c); +} + + +void _spline1dinterpolant_destroy(void* _p) +{ + spline1dinterpolant *p = (spline1dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->c); +} + + + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfit(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* p, + polynomialfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector w; + ae_vector xc; + ae_vector yc; + ae_vector dc; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _barycentricinterpolant_clear(p); + _polynomialfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dc, 0, DT_INT, _state, ae_true); + + ae_assert(n>0, "PolynomialFit: N<=0!", _state); + ae_assert(m>0, "PolynomialFit: M<=0!", _state); + ae_assert(x->cnt>=n, "PolynomialFit: Length(X)cnt>=n, "PolynomialFit: Length(Y)0. + * if given, only leading N elements of X/Y/W are used + * if not given, automatically determined from sizes of X/Y/W + XC - points where polynomial values/derivatives are constrained, + array[0..K-1]. + YC - values of constraints, array[0..K-1] + DC - array[0..K-1], types of constraints: + * DC[i]=0 means that P(XC[i])=YC[i] + * DC[i]=1 means that P'(XC[i])=YC[i] + SEE BELOW FOR IMPORTANT INFORMATION ON CONSTRAINTS + K - number of constraints, 0<=K=1 + +OUTPUT PARAMETERS: + Info- same format as in LSFitLinearW() subroutine: + * Info>0 task is solved + * Info<=0 an error occured: + -4 means inconvergence of internal SVD + -3 means inconsistent constraints + P - interpolant in barycentric form. + Rep - report, same format as in LSFitLinearW() subroutine. + Following fields are set: + * RMSError rms error on the (X,Y). + * AvgError average error on the (X,Y). + * AvgRelError average relative error on the non-zero Y + * MaxError maximum error + NON-WEIGHTED ERRORS ARE CALCULATED + +IMPORTANT: + this subroitine doesn't calculate task's condition number for K<>0. + +NOTES: + you can convert P from barycentric form to the power or Chebyshev + basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions from + POLINT subpackage. + +SETTING CONSTRAINTS - DANGERS AND OPPORTUNITIES: + +Setting constraints can lead to undesired results, like ill-conditioned +behavior, or inconsistency being detected. From the other side, it allows +us to improve quality of the fit. Here we summarize our experience with +constrained regression splines: +* even simple constraints can be inconsistent, see Wikipedia article on + this subject: http://en.wikipedia.org/wiki/Birkhoff_interpolation +* the greater is M (given fixed constraints), the more chances that + constraints will be consistent +* in the general case, consistency of constraints is NOT GUARANTEED. +* in the one special cases, however, we can guarantee consistency. This + case is: M>1 and constraints on the function values (NOT DERIVATIVES) + +Our final recommendation is to use constraints WHEN AND ONLY when you +can't solve your task without them. Anything beyond special cases given +above is not guaranteed and may result in inconsistency. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfitwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* p, + polynomialfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _w; + ae_vector _xc; + ae_vector _yc; + double xa; + double xb; + double sa; + double sb; + ae_vector xoriginal; + ae_vector yoriginal; + ae_vector y2; + ae_vector w2; + ae_vector tmp; + ae_vector tmp2; + ae_vector bx; + ae_vector by; + ae_vector bw; + ae_int_t i; + ae_int_t j; + double u; + double v; + double s; + ae_int_t relcnt; + lsfitreport lrep; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_w, w, _state, ae_true); + w = &_w; + ae_vector_init_copy(&_xc, xc, _state, ae_true); + xc = &_xc; + ae_vector_init_copy(&_yc, yc, _state, ae_true); + yc = &_yc; + *info = 0; + _barycentricinterpolant_clear(p); + _polynomialfitreport_clear(rep); + ae_vector_init(&xoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&by, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bw, 0, DT_REAL, _state, ae_true); + _lsfitreport_init(&lrep, _state, ae_true); + + ae_assert(n>0, "PolynomialFitWC: N<=0!", _state); + ae_assert(m>0, "PolynomialFitWC: M<=0!", _state); + ae_assert(k>=0, "PolynomialFitWC: K<0!", _state); + ae_assert(k=M!", _state); + ae_assert(x->cnt>=n, "PolynomialFitWC: Length(X)cnt>=n, "PolynomialFitWC: Length(Y)cnt>=n, "PolynomialFitWC: Length(W)cnt>=k, "PolynomialFitWC: Length(XC)cnt>=k, "PolynomialFitWC: Length(YC)cnt>=k, "PolynomialFitWC: Length(DC)ptr.p_int[i]==0||dc->ptr.p_int[i]==1, "PolynomialFitWC: one of DC[] is not 0 or 1!", _state); + } + + /* + * 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, _state); + lsfit_internalchebyshevfit(x, y, w, n, xc, yc, dc, k, m, info, &tmp, &lrep, _state); + if( *info<0 ) + { + ae_frame_leave(_state); + 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. + */ + ae_vector_set_length(&bx, m, _state); + ae_vector_set_length(&by, m, _state); + ae_vector_set_length(&bw, m, _state); + ae_vector_set_length(&tmp2, m, _state); + s = 1; + for(i=0; i<=m-1; i++) + { + if( m!=1 ) + { + u = ae_cos(ae_pi*i/(m-1), _state); + } + else + { + u = 0; + } + v = 0; + for(j=0; j<=m-1; j++) + { + if( j==0 ) + { + tmp2.ptr.p_double[j] = 1; + } + else + { + if( j==1 ) + { + tmp2.ptr.p_double[j] = u; + } + else + { + tmp2.ptr.p_double[j] = 2*u*tmp2.ptr.p_double[j-1]-tmp2.ptr.p_double[j-2]; + } + } + v = v+tmp.ptr.p_double[j]*tmp2.ptr.p_double[j]; + } + bx.ptr.p_double[i] = u; + by.ptr.p_double[i] = v; + bw.ptr.p_double[i] = s; + if( i==0||i==m-1 ) + { + bw.ptr.p_double[i] = 0.5*bw.ptr.p_double[i]; + } + s = -s; + } + barycentricbuildxyw(&bx, &by, &bw, m, p, _state); + barycentriclintransx(p, 2/(xb-xa), -(xa+xb)/(xb-xa), _state); + barycentriclintransy(p, sb-sa, sa, _state); + + /* + * Scale absolute errors obtained from LSFitLinearW. + * Relative error should be calculated separately + * (because of shifting/scaling of the task) + */ + rep->taskrcond = lrep.taskrcond; + rep->rmserror = lrep.rmserror*(sb-sa); + rep->avgerror = lrep.avgerror*(sb-sa); + rep->maxerror = lrep.maxerror*(sb-sa); + rep->avgrelerror = 0; + relcnt = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(yoriginal.ptr.p_double[i],0) ) + { + rep->avgrelerror = rep->avgrelerror+ae_fabs(barycentriccalc(p, xoriginal.ptr.p_double[i], _state)-yoriginal.ptr.p_double[i], _state)/ae_fabs(yoriginal.ptr.p_double[i], _state); + relcnt = relcnt+1; + } + } + if( relcnt!=0 ) + { + rep->avgrelerror = rep->avgrelerror/relcnt; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormannwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t d; + ae_int_t i; + double wrmscur; + double wrmsbest; + barycentricinterpolant locb; + barycentricfitreport locrep; + ae_int_t locinfo; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _barycentricinterpolant_clear(b); + _barycentricfitreport_clear(rep); + _barycentricinterpolant_init(&locb, _state, ae_true); + _barycentricfitreport_init(&locrep, _state, ae_true); + + ae_assert(n>0, "BarycentricFitFloaterHormannWC: N<=0!", _state); + ae_assert(m>0, "BarycentricFitFloaterHormannWC: M<=0!", _state); + ae_assert(k>=0, "BarycentricFitFloaterHormannWC: K<0!", _state); + ae_assert(k=M!", _state); + ae_assert(x->cnt>=n, "BarycentricFitFloaterHormannWC: Length(X)cnt>=n, "BarycentricFitFloaterHormannWC: Length(Y)cnt>=n, "BarycentricFitFloaterHormannWC: Length(W)cnt>=k, "BarycentricFitFloaterHormannWC: Length(XC)cnt>=k, "BarycentricFitFloaterHormannWC: Length(YC)cnt>=k, "BarycentricFitFloaterHormannWC: Length(DC)ptr.p_int[i]==0||dc->ptr.p_int[i]==1, "BarycentricFitFloaterHormannWC: one of DC[] is not 0 or 1!", _state); + } + + /* + * 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 = ae_maxrealnumber; + rep->dbest = -1; + *info = -3; + for(d=0; d<=ae_minint(9, n-1, _state); d++) + { + lsfit_barycentricfitwcfixedd(x, y, w, n, xc, yc, dc, k, m, d, &locinfo, &locb, &locrep, _state); + ae_assert((locinfo==-4||locinfo==-3)||locinfo>0, "BarycentricFitFloaterHormannWC: unexpected result from BarycentricFitWCFixedD!", _state); + if( locinfo>0 ) + { + + /* + * Calculate weghted RMS + */ + wrmscur = 0; + for(i=0; i<=n-1; i++) + { + wrmscur = wrmscur+ae_sqr(w->ptr.p_double[i]*(y->ptr.p_double[i]-barycentriccalc(&locb, x->ptr.p_double[i], _state)), _state); + } + wrmscur = ae_sqrt(wrmscur/n, _state); + if( ae_fp_less(wrmscur,wrmsbest)||rep->dbest<0 ) + { + barycentriccopy(&locb, b, _state); + rep->dbest = d; + *info = 1; + rep->rmserror = locrep.rmserror; + rep->avgerror = locrep.avgerror; + rep->avgrelerror = locrep.avgrelerror; + rep->maxerror = locrep.maxerror; + rep->taskrcond = locrep.taskrcond; + wrmsbest = wrmscur; + } + } + else + { + if( locinfo!=-3&&*info<0 ) + { + *info = locinfo; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormann(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector w; + ae_vector xc; + ae_vector yc; + ae_vector dc; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _barycentricinterpolant_clear(b); + _barycentricfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dc, 0, DT_INT, _state, ae_true); + + ae_assert(n>0, "BarycentricFitFloaterHormann: N<=0!", _state); + ae_assert(m>0, "BarycentricFitFloaterHormann: M<=0!", _state); + ae_assert(x->cnt>=n, "BarycentricFitFloaterHormann: Length(X)cnt>=n, "BarycentricFitFloaterHormann: Length(Y)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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalized(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + double rho, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector w; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "Spline1DFitPenalized: N<1!", _state); + ae_assert(m>=4, "Spline1DFitPenalized: M<4!", _state); + ae_assert(x->cnt>=n, "Spline1DFitPenalized: Length(X)cnt>=n, "Spline1DFitPenalized: Length(Y)0 + * if given, only first N elements of X/Y/W are processed + * if not given, automatically determined from X/Y/W sizes + M - number of basis functions ( = number_of_nodes), M>=4. + Rho - regularization constant passed by user. It penalizes + nonlinearity in the regression spline. It is logarithmically + scaled, i.e. actual value of regularization constant is + calculated as 10^Rho. It is automatically scaled so that: + * Rho=2.0 corresponds to moderate amount of nonlinearity + * generally, it should be somewhere in the [-8.0,+8.0] + If you do not want to penalize nonlineary, + pass small Rho. Values as low as -15 should work. + +OUTPUT PARAMETERS: + Info- same format as in LSFitLinearWC() subroutine. + * Info>0 task is solved + * Info<=0 an error occured: + -4 means inconvergence of internal SVD or + Cholesky decomposition; problem may be + too ill-conditioned (very rare) + S - spline interpolant. + Rep - Following fields are set: + * RMSError rms error on the (X,Y). + * AvgError average error on the (X,Y). + * AvgRelError average relative error on the non-zero Y + * MaxError maximum error + NON-WEIGHTED ERRORS ARE CALCULATED + +IMPORTANT: + this subroitine doesn't calculate task's condition number for K<>0. + +NOTE 1: additional nodes are added to the spline outside of the fitting +interval to force linearity when xmax(x,xc). It is done +for consistency - we penalize non-linearity at [min(x,xc),max(x,xc)], so +it is natural to force linearity outside of this interval. + +NOTE 2: function automatically sorts points, so caller may pass unsorted +array. + + -- ALGLIB PROJECT -- + Copyright 19.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalizedw(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + ae_int_t m, + double rho, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _w; + ae_int_t i; + ae_int_t j; + ae_int_t b; + double v; + double relcnt; + double xa; + double xb; + double sa; + double sb; + ae_vector xoriginal; + ae_vector yoriginal; + double pdecay; + double tdecay; + ae_matrix fmatrix; + ae_vector fcolumn; + ae_vector y2; + ae_vector w2; + ae_vector xc; + ae_vector yc; + ae_vector dc; + double fdmax; + double admax; + ae_matrix amatrix; + ae_matrix d2matrix; + double fa; + double ga; + double fb; + double gb; + double lambdav; + ae_vector bx; + ae_vector by; + ae_vector bd1; + ae_vector bd2; + ae_vector tx; + ae_vector ty; + ae_vector td; + spline1dinterpolant bs; + ae_matrix nmatrix; + ae_vector rightpart; + fblslincgstate cgstate; + ae_vector c; + ae_vector tmp0; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_w, w, _state, ae_true); + w = &_w; + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + ae_vector_init(&xoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yoriginal, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&fcolumn, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dc, 0, DT_INT, _state, ae_true); + ae_matrix_init(&amatrix, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&d2matrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&by, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bd1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bd2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ty, 0, DT_REAL, _state, ae_true); + ae_vector_init(&td, 0, DT_REAL, _state, ae_true); + _spline1dinterpolant_init(&bs, _state, ae_true); + ae_matrix_init(&nmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&rightpart, 0, DT_REAL, _state, ae_true); + _fblslincgstate_init(&cgstate, _state, ae_true); + ae_vector_init(&c, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp0, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "Spline1DFitPenalizedW: N<1!", _state); + ae_assert(m>=4, "Spline1DFitPenalizedW: M<4!", _state); + ae_assert(x->cnt>=n, "Spline1DFitPenalizedW: Length(X)cnt>=n, "Spline1DFitPenalizedW: Length(Y)cnt>=n, "Spline1DFitPenalizedW: Length(W)ptr.p_double[i]*fcolumn.ptr.p_double[i], _state); + } + fdmax = ae_maxreal(fdmax, v, _state); + + /* + * Fill temporary with second derivatives of basis function + */ + ae_v_move(&d2matrix.ptr.pp_double[b][0], 1, &bd2.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + + /* + * * 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++) + { + fa = d2matrix.ptr.pp_double[i][b]; + fb = d2matrix.ptr.pp_double[i][b+1]; + ga = d2matrix.ptr.pp_double[j][b]; + gb = d2matrix.ptr.pp_double[j][b+1]; + v = v+(bx.ptr.p_double[b+1]-bx.ptr.p_double[b])*(fa*ga+(fa*(gb-ga)+ga*(fb-fa))/2+(fb-fa)*(gb-ga)/3); + } + amatrix.ptr.pp_double[i][j] = v; + amatrix.ptr.pp_double[j][i] = v; + } + } + admax = 0; + for(i=0; i<=m-1; i++) + { + admax = ae_maxreal(admax, ae_fabs(amatrix.ptr.pp_double[i][i], _state), _state); + } + pdecay = lambdav*fdmax/admax; + + /* + * Calculate TDecay for Tikhonov regularization + */ + tdecay = fdmax*(1+pdecay)*10*ae_machineepsilon; + + /* + * Prepare system + * + * NOTE: FMatrix is spoiled during this process + */ + for(i=0; i<=n-1; i++) + { + v = w->ptr.p_double[i]; + ae_v_muld(&fmatrix.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + rmatrixgemm(m, m, n, 1.0, &fmatrix, 0, 0, 1, &fmatrix, 0, 0, 0, 0.0, &nmatrix, 0, 0, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=m-1; j++) + { + nmatrix.ptr.pp_double[i][j] = nmatrix.ptr.pp_double[i][j]+pdecay*amatrix.ptr.pp_double[i][j]; + } + } + for(i=0; i<=m-1; i++) + { + nmatrix.ptr.pp_double[i][i] = nmatrix.ptr.pp_double[i][i]+tdecay; + } + for(i=0; i<=m-1; i++) + { + rightpart.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = y->ptr.p_double[i]*w->ptr.p_double[i]; + ae_v_addd(&rightpart.ptr.p_double[0], 1, &fmatrix.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + + /* + * Solve system + */ + if( !spdmatrixcholesky(&nmatrix, m, ae_true, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + fblscholeskysolve(&nmatrix, 1.0, m, ae_true, &rightpart, &tmp0, _state); + ae_v_move(&c.ptr.p_double[0], 1, &rightpart.ptr.p_double[0], 1, ae_v_len(0,m-1)); + + /* + * add nodes to force linearity outside of the fitting interval + */ + spline1dgriddiffcubic(&bx, &c, m, 2, 0.0, 2, 0.0, &bd1, _state); + ae_vector_set_length(&tx, m+2, _state); + ae_vector_set_length(&ty, m+2, _state); + ae_vector_set_length(&td, m+2, _state); + ae_v_move(&tx.ptr.p_double[1], 1, &bx.ptr.p_double[0], 1, ae_v_len(1,m)); + ae_v_move(&ty.ptr.p_double[1], 1, &rightpart.ptr.p_double[0], 1, ae_v_len(1,m)); + ae_v_move(&td.ptr.p_double[1], 1, &bd1.ptr.p_double[0], 1, ae_v_len(1,m)); + tx.ptr.p_double[0] = tx.ptr.p_double[1]-(tx.ptr.p_double[2]-tx.ptr.p_double[1]); + ty.ptr.p_double[0] = ty.ptr.p_double[1]-td.ptr.p_double[1]*(tx.ptr.p_double[2]-tx.ptr.p_double[1]); + td.ptr.p_double[0] = td.ptr.p_double[1]; + tx.ptr.p_double[m+1] = tx.ptr.p_double[m]+(tx.ptr.p_double[m]-tx.ptr.p_double[m-1]); + ty.ptr.p_double[m+1] = ty.ptr.p_double[m]+td.ptr.p_double[m]*(tx.ptr.p_double[m]-tx.ptr.p_double[m-1]); + td.ptr.p_double[m+1] = td.ptr.p_double[m]; + spline1dbuildhermite(&tx, &ty, &td, m+2, s, _state); + spline1dlintransx(s, 2/(xb-xa), -(xa+xb)/(xb-xa), _state); + spline1dlintransy(s, sb-sa, sa, _state); + *info = 1; + + /* + * Fill report + */ + rep->rmserror = 0; + rep->avgerror = 0; + rep->avgrelerror = 0; + rep->maxerror = 0; + relcnt = 0; + spline1dconvcubic(&bx, &rightpart, m, 2, 0.0, 2, 0.0, x, n, &fcolumn, _state); + for(i=0; i<=n-1; i++) + { + v = (sb-sa)*fcolumn.ptr.p_double[i]+sa; + rep->rmserror = rep->rmserror+ae_sqr(v-yoriginal.ptr.p_double[i], _state); + rep->avgerror = rep->avgerror+ae_fabs(v-yoriginal.ptr.p_double[i], _state); + if( ae_fp_neq(yoriginal.ptr.p_double[i],0) ) + { + rep->avgrelerror = rep->avgrelerror+ae_fabs(v-yoriginal.ptr.p_double[i], _state)/ae_fabs(yoriginal.ptr.p_double[i], _state); + relcnt = relcnt+1; + } + rep->maxerror = ae_maxreal(rep->maxerror, ae_fabs(v-yoriginal.ptr.p_double[i], _state), _state); + } + rep->rmserror = ae_sqrt(rep->rmserror/n, _state); + rep->avgerror = rep->avgerror/n; + if( ae_fp_neq(relcnt,0) ) + { + rep->avgrelerror = rep->avgrelerror/relcnt; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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=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. + + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubicwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_int_t i; + + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + + ae_assert(n>=1, "Spline1DFitCubicWC: N<1!", _state); + ae_assert(m>=4, "Spline1DFitCubicWC: M<4!", _state); + ae_assert(k>=0, "Spline1DFitCubicWC: K<0!", _state); + ae_assert(k=M!", _state); + ae_assert(x->cnt>=n, "Spline1DFitCubicWC: Length(X)cnt>=n, "Spline1DFitCubicWC: Length(Y)cnt>=n, "Spline1DFitCubicWC: Length(W)cnt>=k, "Spline1DFitCubicWC: Length(XC)cnt>=k, "Spline1DFitCubicWC: Length(YC)cnt>=k, "Spline1DFitCubicWC: Length(DC)ptr.p_int[i]==0||dc->ptr.p_int[i]==1, "Spline1DFitCubicWC: DC[i] is neither 0 or 1!", _state); + } + lsfit_spline1dfitinternal(0, x, y, w, n, xc, yc, dc, k, m, info, s, rep, _state); +} + + +/************************************************************************* +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=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 + +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 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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermitewc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_int_t i; + + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + + ae_assert(n>=1, "Spline1DFitHermiteWC: N<1!", _state); + ae_assert(m>=4, "Spline1DFitHermiteWC: M<4!", _state); + ae_assert(m%2==0, "Spline1DFitHermiteWC: M is odd!", _state); + ae_assert(k>=0, "Spline1DFitHermiteWC: K<0!", _state); + ae_assert(k=M!", _state); + ae_assert(x->cnt>=n, "Spline1DFitHermiteWC: Length(X)cnt>=n, "Spline1DFitHermiteWC: Length(Y)cnt>=n, "Spline1DFitHermiteWC: Length(W)cnt>=k, "Spline1DFitHermiteWC: Length(XC)cnt>=k, "Spline1DFitHermiteWC: Length(YC)cnt>=k, "Spline1DFitHermiteWC: Length(DC)ptr.p_int[i]==0||dc->ptr.p_int[i]==1, "Spline1DFitHermiteWC: DC[i] is neither 0 or 1!", _state); + } + lsfit_spline1dfitinternal(1, x, y, w, n, xc, yc, dc, k, m, info, s, rep, _state); +} + + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector w; + ae_vector xc; + ae_vector yc; + ae_vector dc; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&dc, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "Spline1DFitCubic: N<1!", _state); + ae_assert(m>=4, "Spline1DFitCubic: M<4!", _state); + ae_assert(x->cnt>=n, "Spline1DFitCubic: Length(X)cnt>=n, "Spline1DFitCubic: Length(Y)=1, "Spline1DFitHermite: N<1!", _state); + ae_assert(m>=4, "Spline1DFitHermite: M<4!", _state); + ae_assert(m%2==0, "Spline1DFitHermite: M is odd!", _state); + ae_assert(x->cnt>=n, "Spline1DFitHermite: Length(X)cnt>=n, "Spline1DFitHermite: Length(Y)=1. + M - number of basis functions, M>=1. + +OUTPUT PARAMETERS: + Info - error code: + * -4 internal SVD decomposition subroutine failed (very + rare and for degenerate systems only) + * -1 incorrect N/M were specified + * 1 task is solved + C - decomposition coefficients, array[0..M-1] + Rep - fitting report. Following fields are set: + * Rep.TaskRCond reciprocal of condition number + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearw(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + + ae_assert(n>=1, "LSFitLinearW: N<1!", _state); + ae_assert(m>=1, "LSFitLinearW: M<1!", _state); + ae_assert(y->cnt>=n, "LSFitLinearW: length(Y)cnt>=n, "LSFitLinearW: length(W)rows>=n, "LSFitLinearW: rows(FMatrix)cols>=m, "LSFitLinearW: cols(FMatrix)=1. + M - number of basis functions, M>=1. + K - number of constraints, 0 <= K < M + K=0 corresponds to absence of constraints. + +OUTPUT PARAMETERS: + Info - error code: + * -4 internal SVD decomposition subroutine failed (very + rare and for degenerate systems only) + * -3 either too many constraints (M or more), + degenerate constraints (some constraints are + repetead twice) or inconsistent constraints were + specified. + * 1 task is solved + C - decomposition coefficients, array[0..M-1] + Rep - fitting report. Following fields are set: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearwc(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_matrix* cmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _y; + ae_matrix _cmatrix; + ae_int_t i; + ae_int_t j; + ae_vector tau; + ae_matrix q; + ae_matrix f2; + ae_vector tmp; + ae_vector c0; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_matrix_init_copy(&_cmatrix, cmatrix, _state, ae_true); + cmatrix = &_cmatrix; + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&q, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&f2, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c0, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "LSFitLinearWC: N<1!", _state); + ae_assert(m>=1, "LSFitLinearWC: M<1!", _state); + ae_assert(k>=0, "LSFitLinearWC: K<0!", _state); + ae_assert(y->cnt>=n, "LSFitLinearWC: length(Y)cnt>=n, "LSFitLinearWC: length(W)rows>=n, "LSFitLinearWC: rows(FMatrix)cols>=m, "LSFitLinearWC: cols(FMatrix)rows>=k, "LSFitLinearWC: rows(CMatrix)cols>=m+1||k==0, "LSFitLinearWC: cols(CMatrix)=m ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Solve + */ + if( k==0 ) + { + + /* + * no constraints + */ + lsfit_lsfitlinearinternal(y, w, fmatrix, n, m, info, c, rep, _state); + } + 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. + */ + rmatrixlq(cmatrix, k, m, &tau, _state); + rmatrixlqunpackq(cmatrix, k, m, &tau, m, &q, _state); + for(i=0; i<=k-1; i++) + { + for(j=i+1; j<=m-1; j++) + { + cmatrix->ptr.pp_double[i][j] = 0.0; + } + } + if( ae_fp_less(rmatrixlurcondinf(cmatrix, k, _state),1000*ae_machineepsilon) ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&tmp, k, _state); + for(i=0; i<=k-1; i++) + { + if( i>0 ) + { + v = ae_v_dotproduct(&cmatrix->ptr.pp_double[i][0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,i-1)); + } + else + { + v = 0; + } + tmp.ptr.p_double[i] = (cmatrix->ptr.pp_double[i][m]-v)/cmatrix->ptr.pp_double[i][i]; + } + ae_vector_set_length(&c0, m, _state); + for(i=0; i<=m-1; i++) + { + c0.ptr.p_double[i] = 0; + } + for(i=0; i<=k-1; i++) + { + v = tmp.ptr.p_double[i]; + ae_v_addd(&c0.ptr.p_double[0], 1, &q.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + + /* + * Second, prepare modified matrix F2 = F*Q2' and solve modified task + */ + ae_vector_set_length(&tmp, ae_maxint(n, m, _state)+1, _state); + ae_matrix_set_length(&f2, n, m-k, _state); + matrixvectormultiply(fmatrix, 0, n-1, 0, m-1, ae_false, &c0, 0, m-1, -1.0, y, 0, n-1, 1.0, _state); + matrixmatrixmultiply(fmatrix, 0, n-1, 0, m-1, ae_false, &q, k, m-1, 0, m-1, ae_true, 1.0, &f2, 0, n-1, 0, m-k-1, 0.0, &tmp, _state); + lsfit_lsfitlinearinternal(y, w, &f2, n, m-k, info, &tmp, rep, _state); + rep->taskrcond = -1; + if( *info<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * then, convert back to original answer: C = C0 + Q2'*Y0 + */ + ae_vector_set_length(c, m, _state); + ae_v_move(&c->ptr.p_double[0], 1, &c0.ptr.p_double[0], 1, ae_v_len(0,m-1)); + matrixvectormultiply(&q, k, m-1, 0, m-1, ae_true, &tmp, 0, m-k-1, 1.0, c, 0, m-1, 1.0, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinear(/* Real */ ae_vector* y, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector w; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "LSFitLinear: N<1!", _state); + ae_assert(m>=1, "LSFitLinear: M<1!", _state); + ae_assert(y->cnt>=n, "LSFitLinear: length(Y)rows>=n, "LSFitLinear: rows(FMatrix)cols>=m, "LSFitLinear: cols(FMatrix)=1. + M - number of basis functions, M>=1. + K - number of constraints, 0 <= K < M + K=0 corresponds to absence of constraints. + +OUTPUT PARAMETERS: + Info - error code: + * -4 internal SVD decomposition subroutine failed (very + rare and for degenerate systems only) + * -3 either too many constraints (M or more), + degenerate constraints (some constraints are + repetead twice) or inconsistent constraints were + specified. + * 1 task is solved + C - decomposition coefficients, array[0..M-1] + Rep - fitting report. Following fields are set: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearc(/* Real */ ae_vector* y, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_matrix* cmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _y; + ae_vector w; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "LSFitLinearC: N<1!", _state); + ae_assert(m>=1, "LSFitLinearC: M<1!", _state); + ae_assert(k>=0, "LSFitLinearC: K<0!", _state); + ae_assert(y->cnt>=n, "LSFitLinearC: length(Y)rows>=n, "LSFitLinearC: rows(FMatrix)cols>=m, "LSFitLinearC: cols(FMatrix)rows>=k, "LSFitLinearC: rows(CMatrix)cols>=m+1||k==0, "LSFitLinearC: cols(CMatrix)1 + M - dimension of space + K - number of parameters being fitted + DiffStep- numerical differentiation step; + should not be very small or large; + large = loss of accuracy + small = growth of round-off errors + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewf(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + double diffstep, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateWF: N<1!", _state); + ae_assert(m>=1, "LSFitCreateWF: M<1!", _state); + ae_assert(k>=1, "LSFitCreateWF: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateWF: length(C)cnt>=n, "LSFitCreateWF: length(Y)cnt>=n, "LSFitCreateWF: length(W)rows>=n, "LSFitCreateWF: rows(X)cols>=m, "LSFitCreateWF: cols(X)teststep = 0; + state->diffstep = diffstep; + state->npoints = n; + state->nweights = n; + state->wkind = 1; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->taskw, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->taskw.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 0; + state->prevnpt = -1; + state->prevalgo = -1; + minlmcreatev(k, n, &state->c, diffstep, &state->optstate, _state); + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatef(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + double diffstep, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateF: N<1!", _state); + ae_assert(m>=1, "LSFitCreateF: M<1!", _state); + ae_assert(k>=1, "LSFitCreateF: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateF: length(C)cnt>=n, "LSFitCreateF: length(Y)rows>=n, "LSFitCreateF: rows(X)cols>=m, "LSFitCreateF: cols(X)rows>=n, "LSFitCreateF: rows(X)cols>=m, "LSFitCreateF: cols(X)teststep = 0; + state->diffstep = diffstep; + state->npoints = n; + state->wkind = 0; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 0; + state->prevnpt = -1; + state->prevalgo = -1; + minlmcreatev(k, n, &state->c, diffstep, &state->optstate, _state); + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfg(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_bool cheapfg, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateWFG: N<1!", _state); + ae_assert(m>=1, "LSFitCreateWFG: M<1!", _state); + ae_assert(k>=1, "LSFitCreateWFG: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateWFG: length(C)cnt>=n, "LSFitCreateWFG: length(Y)cnt>=n, "LSFitCreateWFG: length(W)rows>=n, "LSFitCreateWFG: rows(X)cols>=m, "LSFitCreateWFG: cols(X)teststep = 0; + state->diffstep = 0; + state->npoints = n; + state->nweights = n; + state->wkind = 1; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->taskw, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_vector_set_length(&state->g, k, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->taskw.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 1; + state->prevnpt = -1; + state->prevalgo = -1; + if( cheapfg ) + { + minlmcreatevgj(k, n, &state->c, &state->optstate, _state); + } + else + { + minlmcreatevj(k, n, &state->c, &state->optstate, _state); + } + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefg(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_bool cheapfg, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateFG: N<1!", _state); + ae_assert(m>=1, "LSFitCreateFG: M<1!", _state); + ae_assert(k>=1, "LSFitCreateFG: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateFG: length(C)cnt>=n, "LSFitCreateFG: length(Y)rows>=n, "LSFitCreateFG: rows(X)cols>=m, "LSFitCreateFG: cols(X)rows>=n, "LSFitCreateFG: rows(X)cols>=m, "LSFitCreateFG: cols(X)teststep = 0; + state->diffstep = 0; + state->npoints = n; + state->wkind = 0; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_vector_set_length(&state->g, k, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 1; + state->prevnpt = -1; + state->prevalgo = -1; + if( cheapfg ) + { + minlmcreatevgj(k, n, &state->c, &state->optstate, _state); + } + else + { + minlmcreatevj(k, n, &state->c, &state->optstate, _state); + } + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfgh(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateWFGH: N<1!", _state); + ae_assert(m>=1, "LSFitCreateWFGH: M<1!", _state); + ae_assert(k>=1, "LSFitCreateWFGH: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateWFGH: length(C)cnt>=n, "LSFitCreateWFGH: length(Y)cnt>=n, "LSFitCreateWFGH: length(W)rows>=n, "LSFitCreateWFGH: rows(X)cols>=m, "LSFitCreateWFGH: cols(X)teststep = 0; + state->diffstep = 0; + state->npoints = n; + state->nweights = n; + state->wkind = 1; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->taskw, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_matrix_set_length(&state->h, k, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_vector_set_length(&state->g, k, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->taskw.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 2; + state->prevnpt = -1; + state->prevalgo = -1; + minlmcreatefgh(k, &state->c, &state->optstate, _state); + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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 + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefgh(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + lsfitstate* state, + ae_state *_state) +{ + ae_int_t i; + + _lsfitstate_clear(state); + + ae_assert(n>=1, "LSFitCreateFGH: N<1!", _state); + ae_assert(m>=1, "LSFitCreateFGH: M<1!", _state); + ae_assert(k>=1, "LSFitCreateFGH: K<1!", _state); + ae_assert(c->cnt>=k, "LSFitCreateFGH: length(C)cnt>=n, "LSFitCreateFGH: length(Y)rows>=n, "LSFitCreateFGH: rows(X)cols>=m, "LSFitCreateFGH: cols(X)teststep = 0; + state->diffstep = 0; + state->npoints = n; + state->wkind = 0; + state->m = m; + state->k = k; + lsfitsetcond(state, 0.0, 0.0, 0, _state); + lsfitsetstpmax(state, 0.0, _state); + lsfitsetxrep(state, ae_false, _state); + ae_matrix_set_length(&state->taskx, n, m, _state); + ae_vector_set_length(&state->tasky, n, _state); + ae_vector_set_length(&state->c, k, _state); + ae_matrix_set_length(&state->h, k, k, _state); + ae_vector_set_length(&state->x, m, _state); + ae_vector_set_length(&state->g, k, _state); + ae_v_move(&state->c.ptr.p_double[0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,k-1)); + for(i=0; i<=n-1; i++) + { + ae_v_move(&state->taskx.ptr.pp_double[i][0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->tasky.ptr.p_double[i] = y->ptr.p_double[i]; + } + ae_vector_set_length(&state->s, k, _state); + ae_vector_set_length(&state->bndl, k, _state); + ae_vector_set_length(&state->bndu, k, _state); + for(i=0; i<=k-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + } + state->optalgo = 2; + state->prevnpt = -1; + state->prevalgo = -1; + minlmcreatefgh(k, &state->c, &state->optstate, _state); + lsfit_lsfitclearrequestfields(state, _state); + ae_vector_set_length(&state->rstate.ia, 6+1, _state); + ae_vector_set_length(&state->rstate.ra, 8+1, _state); + state->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). + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetcond(lsfitstate* state, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsf, _state), "LSFitSetCond: EpsF is not finite!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "LSFitSetCond: negative EpsF!", _state); + ae_assert(ae_isfinite(epsx, _state), "LSFitSetCond: EpsX is not finite!", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "LSFitSetCond: negative EpsX!", _state); + ae_assert(maxits>=0, "LSFitSetCond: negative MaxIts!", _state); + state->epsf = epsf; + state->epsx = epsx; + state->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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetstpmax(lsfitstate* state, double stpmax, ae_state *_state) +{ + + + ae_assert(ae_fp_greater_eq(stpmax,0), "LSFitSetStpMax: StpMax<0!", _state); + state->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. + + + -- ALGLIB -- + Copyright 15.08.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetxrep(lsfitstate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetscale(lsfitstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(s->cnt>=state->k, "LSFitSetScale: Length(S)k-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "LSFitSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "LSFitSetScale: S contains infinite or NAN elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetbc(lsfitstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + + k = state->k; + ae_assert(bndl->cnt>=k, "LSFitSetBC: Length(BndL)cnt>=k, "LSFitSetBC: Length(BndU)ptr.p_double[i], _state)||ae_isneginf(bndl->ptr.p_double[i], _state), "LSFitSetBC: BndL contains NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.p_double[i], _state)||ae_isposinf(bndu->ptr.p_double[i], _state), "LSFitSetBC: BndU contains NAN or -INF", _state); + if( ae_isfinite(bndl->ptr.p_double[i], _state)&&ae_isfinite(bndu->ptr.p_double[i], _state) ) + { + ae_assert(ae_fp_less_eq(bndl->ptr.p_double[i],bndu->ptr.p_double[i]), "LSFitSetBC: BndL[i]>BndU[i]", _state); + } + state->bndl.ptr.p_double[i] = bndl->ptr.p_double[i]; + state->bndu.ptr.p_double[i] = bndu->ptr.p_double[i]; + } +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool lsfititeration(lsfitstate* state, ae_state *_state) +{ + double lx; + double lf; + double ld; + double rx; + double rf; + double rd; + ae_int_t n; + ae_int_t m; + ae_int_t k; + double v; + double vv; + double relcnt; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t info; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + k = state->rstate.ia.ptr.p_int[2]; + i = state->rstate.ia.ptr.p_int[3]; + j = state->rstate.ia.ptr.p_int[4]; + j1 = state->rstate.ia.ptr.p_int[5]; + info = state->rstate.ia.ptr.p_int[6]; + lx = state->rstate.ra.ptr.p_double[0]; + lf = state->rstate.ra.ptr.p_double[1]; + ld = state->rstate.ra.ptr.p_double[2]; + rx = state->rstate.ra.ptr.p_double[3]; + rf = state->rstate.ra.ptr.p_double[4]; + rd = state->rstate.ra.ptr.p_double[5]; + v = state->rstate.ra.ptr.p_double[6]; + vv = state->rstate.ra.ptr.p_double[7]; + relcnt = state->rstate.ra.ptr.p_double[8]; + } + else + { + n = -983; + m = -989; + k = -834; + i = 900; + j = -287; + j1 = 364; + info = 214; + lx = -338; + lf = -686; + ld = 912; + rx = 585; + rf = 497; + rd = -271; + v = -581; + vv = 745; + relcnt = -533; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + + /* + * Routine body + */ + + /* + * Init + */ + if( state->wkind==1 ) + { + ae_assert(state->npoints==state->nweights, "LSFitFit: number of points is not equal to the number of weights", _state); + } + state->repvaridx = -1; + n = state->npoints; + m = state->m; + k = state->k; + minlmsetcond(&state->optstate, 0.0, state->epsf, state->epsx, state->maxits, _state); + minlmsetstpmax(&state->optstate, state->stpmax, _state); + minlmsetxrep(&state->optstate, state->xrep, _state); + minlmsetscale(&state->optstate, &state->s, _state); + minlmsetbc(&state->optstate, &state->bndl, &state->bndu, _state); + + /* + * Check that user-supplied gradient is correct + */ + lsfit_lsfitclearrequestfields(state, _state); + if( !(ae_fp_greater(state->teststep,0)&&state->optalgo==1) ) + { + goto lbl_14; + } + for(i=0; i<=k-1; i++) + { + if( ae_isfinite(state->bndl.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_maxreal(state->c.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + if( ae_isfinite(state->bndu.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_minreal(state->c.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + } + state->needfg = ae_true; + i = 0; +lbl_16: + if( i>k-1 ) + { + goto lbl_18; + } + ae_assert(ae_fp_less_eq(state->bndl.ptr.p_double[i],state->c.ptr.p_double[i])&&ae_fp_less_eq(state->c.ptr.p_double[i],state->bndu.ptr.p_double[i]), "LSFitIteration: internal error(State.C is out of bounds)", _state); + v = state->c.ptr.p_double[i]; + j = 0; +lbl_19: + if( j>n-1 ) + { + goto lbl_21; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[j][0], 1, ae_v_len(0,m-1)); + state->c.ptr.p_double[i] = v-state->teststep*state->s.ptr.p_double[i]; + if( ae_isfinite(state->bndl.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_maxreal(state->c.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + lx = state->c.ptr.p_double[i]; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + lf = state->f; + ld = state->g.ptr.p_double[i]; + state->c.ptr.p_double[i] = v+state->teststep*state->s.ptr.p_double[i]; + if( ae_isfinite(state->bndu.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_minreal(state->c.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + rx = state->c.ptr.p_double[i]; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + rf = state->f; + rd = state->g.ptr.p_double[i]; + state->c.ptr.p_double[i] = (lx+rx)/2; + if( ae_isfinite(state->bndl.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_maxreal(state->c.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + if( ae_isfinite(state->bndu.ptr.p_double[i], _state) ) + { + state->c.ptr.p_double[i] = ae_minreal(state->c.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->c.ptr.p_double[i] = v; + if( !derivativecheck(lf, ld, rf, rd, state->f, state->g.ptr.p_double[i], rx-lx, _state) ) + { + state->repvaridx = i; + state->repterminationtype = -7; + result = ae_false; + return result; + } + j = j+1; + goto lbl_19; +lbl_21: + i = i+1; + goto lbl_16; +lbl_18: + state->needfg = ae_false; +lbl_14: + + /* + * Fill WCur by weights: + * * for WKind=0 unit weights are chosen + * * for WKind=1 we use user-supplied weights stored in State.TaskW + */ + rvectorsetlengthatleast(&state->wcur, n, _state); + for(i=0; i<=n-1; i++) + { + state->wcur.ptr.p_double[i] = 1.0; + if( state->wkind==1 ) + { + state->wcur.ptr.p_double[i] = state->taskw.ptr.p_double[i]; + } + } + + /* + * Optimize + */ +lbl_22: + if( !minlmiteration(&state->optstate, _state) ) + { + goto lbl_23; + } + if( !state->optstate.needfi ) + { + goto lbl_24; + } + + /* + * calculate f[] = wi*(f(xi,c)-yi) + */ + i = 0; +lbl_26: + if( i>n-1 ) + { + goto lbl_28; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needf = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needf = ae_false; + vv = state->wcur.ptr.p_double[i]; + state->optstate.fi.ptr.p_double[i] = vv*(state->f-state->tasky.ptr.p_double[i]); + i = i+1; + goto lbl_26; +lbl_28: + goto lbl_22; +lbl_24: + if( !state->optstate.needf ) + { + goto lbl_29; + } + + /* + * calculate F = sum (wi*(f(xi,c)-yi))^2 + */ + state->optstate.f = 0; + i = 0; +lbl_31: + if( i>n-1 ) + { + goto lbl_33; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needf = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->needf = ae_false; + vv = state->wcur.ptr.p_double[i]; + state->optstate.f = state->optstate.f+ae_sqr(vv*(state->f-state->tasky.ptr.p_double[i]), _state); + i = i+1; + goto lbl_31; +lbl_33: + goto lbl_22; +lbl_29: + if( !state->optstate.needfg ) + { + goto lbl_34; + } + + /* + * calculate F/gradF + */ + state->optstate.f = 0; + for(i=0; i<=k-1; i++) + { + state->optstate.g.ptr.p_double[i] = 0; + } + i = 0; +lbl_36: + if( i>n-1 ) + { + goto lbl_38; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->needfg = ae_false; + vv = state->wcur.ptr.p_double[i]; + state->optstate.f = state->optstate.f+ae_sqr(vv*(state->f-state->tasky.ptr.p_double[i]), _state); + v = ae_sqr(vv, _state)*2*(state->f-state->tasky.ptr.p_double[i]); + ae_v_addd(&state->optstate.g.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,k-1), v); + i = i+1; + goto lbl_36; +lbl_38: + goto lbl_22; +lbl_34: + if( !state->optstate.needfij ) + { + goto lbl_39; + } + + /* + * calculate Fi/jac(Fi) + */ + i = 0; +lbl_41: + if( i>n-1 ) + { + goto lbl_43; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->needfg = ae_false; + vv = state->wcur.ptr.p_double[i]; + state->optstate.fi.ptr.p_double[i] = vv*(state->f-state->tasky.ptr.p_double[i]); + ae_v_moved(&state->optstate.j.ptr.pp_double[i][0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,k-1), vv); + i = i+1; + goto lbl_41; +lbl_43: + goto lbl_22; +lbl_39: + if( !state->optstate.needfgh ) + { + goto lbl_44; + } + + /* + * calculate F/grad(F)/hess(F) + */ + state->optstate.f = 0; + for(i=0; i<=k-1; i++) + { + state->optstate.g.ptr.p_double[i] = 0; + } + for(i=0; i<=k-1; i++) + { + for(j=0; j<=k-1; j++) + { + state->optstate.h.ptr.pp_double[i][j] = 0; + } + } + i = 0; +lbl_46: + if( i>n-1 ) + { + goto lbl_48; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needfgh = ae_true; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->needfgh = ae_false; + vv = state->wcur.ptr.p_double[i]; + state->optstate.f = state->optstate.f+ae_sqr(vv*(state->f-state->tasky.ptr.p_double[i]), _state); + v = ae_sqr(vv, _state)*2*(state->f-state->tasky.ptr.p_double[i]); + ae_v_addd(&state->optstate.g.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,k-1), v); + for(j=0; j<=k-1; j++) + { + v = 2*ae_sqr(vv, _state)*state->g.ptr.p_double[j]; + ae_v_addd(&state->optstate.h.ptr.pp_double[j][0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,k-1), v); + v = 2*ae_sqr(vv, _state)*(state->f-state->tasky.ptr.p_double[i]); + ae_v_addd(&state->optstate.h.ptr.pp_double[j][0], 1, &state->h.ptr.pp_double[j][0], 1, ae_v_len(0,k-1), v); + } + i = i+1; + goto lbl_46; +lbl_48: + goto lbl_22; +lbl_44: + if( !state->optstate.xupdated ) + { + goto lbl_49; + } + + /* + * Report new iteration + */ + ae_v_move(&state->c.ptr.p_double[0], 1, &state->optstate.x.ptr.p_double[0], 1, ae_v_len(0,k-1)); + state->f = state->optstate.f; + lsfit_lsfitclearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->xupdated = ae_false; + goto lbl_22; +lbl_49: + goto lbl_22; +lbl_23: + minlmresults(&state->optstate, &state->c, &state->optrep, _state); + state->repterminationtype = state->optrep.terminationtype; + state->repiterationscount = state->optrep.iterationscount; + + /* + * calculate errors + */ + if( state->repterminationtype<=0 ) + { + goto lbl_51; + } + + /* + * Calculate RMS/Avg/Max/... errors + */ + state->reprmserror = 0; + state->repwrmserror = 0; + state->repavgerror = 0; + state->repavgrelerror = 0; + state->repmaxerror = 0; + relcnt = 0; + i = 0; +lbl_53: + if( i>n-1 ) + { + goto lbl_55; + } + ae_v_move(&state->c.ptr.p_double[0], 1, &state->c.ptr.p_double[0], 1, ae_v_len(0,k-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + lsfit_lsfitclearrequestfields(state, _state); + state->needf = ae_true; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->needf = ae_false; + v = state->f; + vv = state->wcur.ptr.p_double[i]; + state->reprmserror = state->reprmserror+ae_sqr(v-state->tasky.ptr.p_double[i], _state); + state->repwrmserror = state->repwrmserror+ae_sqr(vv*(v-state->tasky.ptr.p_double[i]), _state); + state->repavgerror = state->repavgerror+ae_fabs(v-state->tasky.ptr.p_double[i], _state); + if( ae_fp_neq(state->tasky.ptr.p_double[i],0) ) + { + state->repavgrelerror = state->repavgrelerror+ae_fabs(v-state->tasky.ptr.p_double[i], _state)/ae_fabs(state->tasky.ptr.p_double[i], _state); + relcnt = relcnt+1; + } + state->repmaxerror = ae_maxreal(state->repmaxerror, ae_fabs(v-state->tasky.ptr.p_double[i], _state), _state); + i = i+1; + goto lbl_53; +lbl_55: + state->reprmserror = ae_sqrt(state->reprmserror/n, _state); + state->repwrmserror = ae_sqrt(state->repwrmserror/n, _state); + state->repavgerror = state->repavgerror/n; + if( ae_fp_neq(relcnt,0) ) + { + state->repavgrelerror = state->repavgrelerror/relcnt; + } + + /* + * Calculate covariance matrix + */ + rmatrixsetlengthatleast(&state->tmpjac, n, k, _state); + rvectorsetlengthatleast(&state->tmpf, n, _state); + rvectorsetlengthatleast(&state->tmp, k, _state); + if( ae_fp_less_eq(state->diffstep,0) ) + { + goto lbl_56; + } + + /* + * Compute Jacobian by means of numerical differentiation + */ + lsfit_lsfitclearrequestfields(state, _state); + state->needf = ae_true; + i = 0; +lbl_58: + if( i>n-1 ) + { + goto lbl_60; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->tmpf.ptr.p_double[i] = state->f; + j = 0; +lbl_61: + if( j>k-1 ) + { + goto lbl_63; + } + v = state->c.ptr.p_double[j]; + lx = v-state->diffstep*state->s.ptr.p_double[j]; + state->c.ptr.p_double[j] = lx; + if( ae_isfinite(state->bndl.ptr.p_double[j], _state) ) + { + state->c.ptr.p_double[j] = ae_maxreal(state->c.ptr.p_double[j], state->bndl.ptr.p_double[j], _state); + } + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + lf = state->f; + rx = v+state->diffstep*state->s.ptr.p_double[j]; + state->c.ptr.p_double[j] = rx; + if( ae_isfinite(state->bndu.ptr.p_double[j], _state) ) + { + state->c.ptr.p_double[j] = ae_minreal(state->c.ptr.p_double[j], state->bndu.ptr.p_double[j], _state); + } + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + rf = state->f; + state->c.ptr.p_double[j] = v; + if( ae_fp_neq(rx,lx) ) + { + state->tmpjac.ptr.pp_double[i][j] = (rf-lf)/(rx-lx); + } + else + { + state->tmpjac.ptr.pp_double[i][j] = 0; + } + j = j+1; + goto lbl_61; +lbl_63: + i = i+1; + goto lbl_58; +lbl_60: + state->needf = ae_false; + goto lbl_57; +lbl_56: + + /* + * Jacobian is calculated with user-provided analytic gradient + */ + lsfit_lsfitclearrequestfields(state, _state); + state->needfg = ae_true; + i = 0; +lbl_64: + if( i>n-1 ) + { + goto lbl_66; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->taskx.ptr.pp_double[i][0], 1, ae_v_len(0,m-1)); + state->pointindex = i; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->tmpf.ptr.p_double[i] = state->f; + for(j=0; j<=k-1; j++) + { + state->tmpjac.ptr.pp_double[i][j] = state->g.ptr.p_double[j]; + } + i = i+1; + goto lbl_64; +lbl_66: + state->needfg = ae_false; +lbl_57: + for(i=0; i<=k-1; i++) + { + state->tmp.ptr.p_double[i] = 0.0; + } + lsfit_estimateerrors(&state->tmpjac, &state->tmpf, &state->tasky, &state->wcur, &state->tmp, &state->s, n, k, &state->rep, &state->tmpjacw, 0, _state); +lbl_51: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = k; + state->rstate.ia.ptr.p_int[3] = i; + state->rstate.ia.ptr.p_int[4] = j; + state->rstate.ia.ptr.p_int[5] = j1; + state->rstate.ia.ptr.p_int[6] = info; + state->rstate.ra.ptr.p_double[0] = lx; + state->rstate.ra.ptr.p_double[1] = lf; + state->rstate.ra.ptr.p_double[2] = ld; + state->rstate.ra.ptr.p_double[3] = rx; + state->rstate.ra.ptr.p_double[4] = rf; + state->rstate.ra.ptr.p_double[5] = rd; + state->rstate.ra.ptr.p_double[6] = v; + state->rstate.ra.ptr.p_double[7] = vv; + state->rstate.ra.ptr.p_double[8] = relcnt; + return result; +} + + +/************************************************************************* +Nonlinear least squares fitting results. + +Called after return from LSFitFit(). + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Info - completion code: + * -7 gradient verification failed. + See LSFitSetGradientCheck() for more information. + * 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. On success following fields are set: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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). + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitresults(lsfitstate* state, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + + lsfit_clearreport(rep, _state); + *info = state->repterminationtype; + rep->varidx = state->repvaridx; + if( *info>0 ) + { + ae_vector_set_length(c, state->k, _state); + ae_v_move(&c->ptr.p_double[0], 1, &state->c.ptr.p_double[0], 1, ae_v_len(0,state->k-1)); + rep->rmserror = state->reprmserror; + rep->wrmserror = state->repwrmserror; + rep->avgerror = state->repavgerror; + rep->avgrelerror = state->repavgrelerror; + rep->maxerror = state->repmaxerror; + rep->iterationscount = state->repiterationscount; + ae_matrix_set_length(&rep->covpar, state->k, state->k, _state); + ae_vector_set_length(&rep->errpar, state->k, _state); + ae_vector_set_length(&rep->errcurve, state->npoints, _state); + ae_vector_set_length(&rep->noise, state->npoints, _state); + rep->r2 = state->rep.r2; + for(i=0; i<=state->k-1; i++) + { + for(j=0; j<=state->k-1; j++) + { + rep->covpar.ptr.pp_double[i][j] = state->rep.covpar.ptr.pp_double[i][j]; + } + rep->errpar.ptr.p_double[i] = state->rep.errpar.ptr.p_double[i]; + } + for(i=0; i<=state->npoints-1; i++) + { + rep->errcurve.ptr.p_double[i] = state->rep.errcurve.ptr.p_double[i]; + rep->noise.ptr.p_double[i] = state->rep.noise.ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before fitting begins +* LSFitFit() is called +* prior to actual fitting, for each point in data set X_i and each + component of parameters being fited C_j algorithm performs following + steps: + * two trial steps are made to C_j-TestStep*S[j] and C_j+TestStep*S[j], + where C_j is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on C[] + * F(X_i|C) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N*K (points count * parameters count) gradient + evaluations. It is very costly and you should use it only for low + dimensional problems, when you want to be sure that you've + correctly calculated analytic derivatives. You should not use it + in the production code (unless you want to check derivatives + provided by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with LSFitSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +NOTE 4: this function works only for optimizers created with LSFitCreateWFG() + or LSFitCreateFG() constructors. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetgradientcheck(lsfitstate* state, + double teststep, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(teststep, _state), "LSFitSetGradientCheck: TestStep contains NaN or Infinite", _state); + ae_assert(ae_fp_greater_eq(teststep,0), "LSFitSetGradientCheck: invalid argument TestStep(TestStep<0)", _state); + state->teststep = teststep; +} + + +/************************************************************************* +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] + + -- ALGLIB PROJECT -- + Copyright 08.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitscalexy(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + double* xa, + double* xb, + double* sa, + double* sb, + /* Real */ ae_vector* xoriginal, + /* Real */ ae_vector* yoriginal, + ae_state *_state) +{ + double xmin; + double xmax; + ae_int_t i; + double mx; + + *xa = 0; + *xb = 0; + *sa = 0; + *sb = 0; + ae_vector_clear(xoriginal); + ae_vector_clear(yoriginal); + + ae_assert(n>=1, "LSFitScaleXY: incorrect N", _state); + ae_assert(k>=0, "LSFitScaleXY: incorrect K", _state); + + /* + * Calculate xmin/xmax. + * Force xmin<>xmax. + */ + xmin = x->ptr.p_double[0]; + xmax = x->ptr.p_double[0]; + for(i=1; i<=n-1; i++) + { + xmin = ae_minreal(xmin, x->ptr.p_double[i], _state); + xmax = ae_maxreal(xmax, x->ptr.p_double[i], _state); + } + for(i=0; i<=k-1; i++) + { + xmin = ae_minreal(xmin, xc->ptr.p_double[i], _state); + xmax = ae_maxreal(xmax, xc->ptr.p_double[i], _state); + } + if( ae_fp_eq(xmin,xmax) ) + { + if( ae_fp_eq(xmin,0) ) + { + xmin = -1; + xmax = 1; + } + else + { + if( ae_fp_greater(xmin,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). + */ + ae_vector_set_length(xoriginal, n, _state); + ae_v_move(&xoriginal->ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + *xa = xmin; + *xb = xmax; + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = 2*(x->ptr.p_double[i]-0.5*(*xa+(*xb)))/(*xb-(*xa)); + } + for(i=0; i<=k-1; i++) + { + ae_assert(dc->ptr.p_int[i]>=0, "LSFitScaleXY: internal error!", _state); + xc->ptr.p_double[i] = 2*(xc->ptr.p_double[i]-0.5*(*xa+(*xb)))/(*xb-(*xa)); + yc->ptr.p_double[i] = yc->ptr.p_double[i]*ae_pow(0.5*(*xb-(*xa)), dc->ptr.p_int[i], _state); + } + + /* + * 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). + */ + ae_vector_set_length(yoriginal, n, _state); + ae_v_move(&yoriginal->ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + *sa = 0; + for(i=0; i<=n-1; i++) + { + *sa = *sa+y->ptr.p_double[i]; + } + *sa = *sa/n; + *sb = 0; + for(i=0; i<=n-1; i++) + { + *sb = *sb+ae_sqr(y->ptr.p_double[i]-(*sa), _state); + } + *sb = ae_sqrt(*sb/n, _state)+(*sa); + if( ae_fp_eq(*sb,*sa) ) + { + *sb = 2*(*sa); + } + if( ae_fp_eq(*sb,*sa) ) + { + *sb = *sa+1; + } + for(i=0; i<=n-1; i++) + { + y->ptr.p_double[i] = (y->ptr.p_double[i]-(*sa))/(*sb-(*sa)); + } + for(i=0; i<=k-1; i++) + { + if( dc->ptr.p_int[i]==0 ) + { + yc->ptr.p_double[i] = (yc->ptr.p_double[i]-(*sa))/(*sb-(*sa)); + } + else + { + yc->ptr.p_double[i] = yc->ptr.p_double[i]/(*sb-(*sa)); + } + } + + /* + * Scale weights + */ + mx = 0; + for(i=0; i<=n-1; i++) + { + mx = ae_maxreal(mx, ae_fabs(w->ptr.p_double[i], _state), _state); + } + if( ae_fp_neq(mx,0) ) + { + for(i=0; i<=n-1; i++) + { + w->ptr.p_double[i] = w->ptr.p_double[i]/mx; + } + } +} + + +/************************************************************************* +Internal spline fitting subroutine + + -- ALGLIB PROJECT -- + Copyright 08.09.2009 by Bochkanov Sergey +*************************************************************************/ +static void lsfit_spline1dfitinternal(ae_int_t st, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _w; + ae_vector _xc; + ae_vector _yc; + ae_matrix fmatrix; + ae_matrix cmatrix; + ae_vector y2; + ae_vector w2; + ae_vector sx; + ae_vector sy; + ae_vector sd; + ae_vector tmp; + ae_vector xoriginal; + ae_vector yoriginal; + lsfitreport lrep; + double v0; + double v1; + double v2; + double mx; + spline1dinterpolant s2; + ae_int_t i; + ae_int_t j; + ae_int_t relcnt; + double xa; + double xb; + double sa; + double sb; + double bl; + double br; + double decay; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_w, w, _state, ae_true); + w = &_w; + ae_vector_init_copy(&_xc, xc, _state, ae_true); + xc = &_xc; + ae_vector_init_copy(&_yc, yc, _state, ae_true); + yc = &_yc; + *info = 0; + _spline1dinterpolant_clear(s); + _spline1dfitreport_clear(rep); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&cmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sd, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yoriginal, 0, DT_REAL, _state, ae_true); + _lsfitreport_init(&lrep, _state, ae_true); + _spline1dinterpolant_init(&s2, _state, ae_true); + + ae_assert(st==0||st==1, "Spline1DFit: internal error!", _state); + if( st==0&&m<4 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( st==1&&m<4 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( (n<1||k<0)||k>=m ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=k-1; i++) + { + *info = 0; + if( dc->ptr.p_int[i]<0 ) + { + *info = -1; + } + if( dc->ptr.p_int[i]>1 ) + { + *info = -1; + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + } + if( st==1&&m%2!=0 ) + { + + /* + * Hermite fitter must have even number of basis functions + */ + *info = -2; + ae_frame_leave(_state); + return; + } + + /* + * weight decay for correct handling of task which becomes + * degenerate after constraints are applied + */ + decay = 10000*ae_machineepsilon; + + /* + * Scale X, Y, XC, YC + */ + lsfitscalexy(x, y, w, n, xc, yc, dc, k, &xa, &xb, &sa, &sb, &xoriginal, &yoriginal, _state); + + /* + * 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[] + */ + ae_vector_set_length(&y2, n+m, _state); + ae_vector_set_length(&w2, n+m, _state); + ae_matrix_set_length(&fmatrix, n+m, m, _state); + if( k>0 ) + { + ae_matrix_set_length(&cmatrix, k, m+1, _state); + } + if( st==0 ) + { + + /* + * allocate space for cubic spline + */ + ae_vector_set_length(&sx, m-2, _state); + ae_vector_set_length(&sy, m-2, _state); + for(j=0; j<=m-2-1; j++) + { + sx.ptr.p_double[j] = (double)(2*j)/(double)(m-2-1)-1; + } + } + if( st==1 ) + { + + /* + * allocate space for Hermite spline + */ + ae_vector_set_length(&sx, m/2, _state); + ae_vector_set_length(&sy, m/2, _state); + ae_vector_set_length(&sd, m/2, _state); + for(j=0; j<=m/2-1; j++) + { + sx.ptr.p_double[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.ptr.p_double[i] = 0; + } + bl = 0; + br = 0; + if( jptr.p_double[i], _state); + } + for(i=0; i<=k-1; i++) + { + ae_assert(dc->ptr.p_int[i]>=0&&dc->ptr.p_int[i]<=2, "Spline1DFit: internal error!", _state); + spline1ddiff(&s2, xc->ptr.p_double[i], &v0, &v1, &v2, _state); + if( dc->ptr.p_int[i]==0 ) + { + cmatrix.ptr.pp_double[i][j] = v0; + } + if( dc->ptr.p_int[i]==1 ) + { + cmatrix.ptr.pp_double[i][j] = v1; + } + if( dc->ptr.p_int[i]==2 ) + { + cmatrix.ptr.pp_double[i][j] = v2; + } + } + } + for(i=0; i<=k-1; i++) + { + cmatrix.ptr.pp_double[i][m] = yc->ptr.p_double[i]; + } + for(i=0; i<=m-1; i++) + { + for(j=0; j<=m-1; j++) + { + if( i==j ) + { + fmatrix.ptr.pp_double[n+i][j] = decay; + } + else + { + fmatrix.ptr.pp_double[n+i][j] = 0; + } + } + } + ae_vector_set_length(&y2, n+m, _state); + ae_vector_set_length(&w2, n+m, _state); + ae_v_move(&y2.ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&w2.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + mx = 0; + for(i=0; i<=n-1; i++) + { + mx = mx+ae_fabs(w->ptr.p_double[i], _state); + } + mx = mx/n; + for(i=0; i<=m-1; i++) + { + y2.ptr.p_double[n+i] = 0; + w2.ptr.p_double[n+i] = mx; + } + + /* + * Solve constrained task + */ + if( k>0 ) + { + + /* + * solve using regularization + */ + lsfitlinearwc(&y2, &w2, &fmatrix, &cmatrix, n+m, m, k, info, &tmp, &lrep, _state); + } + else + { + + /* + * no constraints, no regularization needed + */ + lsfitlinearwc(y, w, &fmatrix, &cmatrix, n, m, k, info, &tmp, &lrep, _state); + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Generate spline and scale it + */ + if( st==0 ) + { + + /* + * cubic spline basis + */ + ae_v_move(&sy.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,m-2-1)); + spline1dbuildcubic(&sx, &sy, m-2, 1, tmp.ptr.p_double[m-2], 1, tmp.ptr.p_double[m-1], s, _state); + } + if( st==1 ) + { + + /* + * Hermite basis + */ + for(i=0; i<=m/2-1; i++) + { + sy.ptr.p_double[i] = tmp.ptr.p_double[2*i]; + sd.ptr.p_double[i] = tmp.ptr.p_double[2*i+1]; + } + spline1dbuildhermite(&sx, &sy, &sd, m/2, s, _state); + } + spline1dlintransx(s, 2/(xb-xa), -(xa+xb)/(xb-xa), _state); + spline1dlintransy(s, sb-sa, sa, _state); + + /* + * Scale absolute errors obtained from LSFitLinearW. + * Relative error should be calculated separately + * (because of shifting/scaling of the task) + */ + rep->taskrcond = lrep.taskrcond; + rep->rmserror = lrep.rmserror*(sb-sa); + rep->avgerror = lrep.avgerror*(sb-sa); + rep->maxerror = lrep.maxerror*(sb-sa); + rep->avgrelerror = 0; + relcnt = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(yoriginal.ptr.p_double[i],0) ) + { + rep->avgrelerror = rep->avgrelerror+ae_fabs(spline1dcalc(s, xoriginal.ptr.p_double[i], _state)-yoriginal.ptr.p_double[i], _state)/ae_fabs(yoriginal.ptr.p_double[i], _state); + relcnt = relcnt+1; + } + } + if( relcnt!=0 ) + { + rep->avgrelerror = rep->avgrelerror/relcnt; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal fitting subroutine +*************************************************************************/ +static void lsfit_lsfitlinearinternal(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + double threshold; + ae_matrix ft; + ae_matrix q; + ae_matrix l; + ae_matrix r; + ae_vector b; + ae_vector wmod; + ae_vector tau; + ae_vector nzeros; + ae_vector s; + ae_int_t i; + ae_int_t j; + double v; + ae_vector sv; + ae_matrix u; + ae_matrix vt; + ae_vector tmp; + ae_vector utb; + ae_vector sutb; + ae_int_t relcnt; + + ae_frame_make(_state, &_frame_block); + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + ae_matrix_init(&ft, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&q, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&l, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&r, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&b, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wmod, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&nzeros, 0, DT_REAL, _state, ae_true); + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sv, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&utb, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sutb, 0, DT_REAL, _state, ae_true); + + lsfit_clearreport(rep, _state); + if( n<1||m<1 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + *info = 1; + threshold = ae_sqrt(ae_machineepsilon, _state); + + /* + * Degenerate case, needs special handling + */ + if( nptr.p_double[j]; + ae_v_moved(&ft.ptr.pp_double[j][0], 1, &fmatrix->ptr.pp_double[j][0], 1, ae_v_len(0,m-1), v); + b.ptr.p_double[j] = w->ptr.p_double[j]*y->ptr.p_double[j]; + wmod.ptr.p_double[j] = 1; + } + + /* + * LQ decomposition and reduction to M=N + */ + ae_vector_set_length(c, m, _state); + for(i=0; i<=m-1; i++) + { + c->ptr.p_double[i] = 0; + } + rep->taskrcond = 0; + rmatrixlq(&ft, n, m, &tau, _state); + rmatrixlqunpackq(&ft, n, m, &tau, n, &q, _state); + rmatrixlqunpackl(&ft, n, m, &l, _state); + lsfit_lsfitlinearinternal(&b, &wmod, &l, n, n, info, &tmp, rep, _state); + if( *info<=0 ) + { + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + v = tmp.ptr.p_double[i]; + ae_v_addd(&c->ptr.p_double[0], 1, &q.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + ae_frame_leave(_state); + return; + } + + /* + * N>=M. Generate design matrix and reduce to N=M using + * QR decomposition. + */ + ae_matrix_set_length(&ft, n, m, _state); + ae_vector_set_length(&b, n, _state); + for(j=0; j<=n-1; j++) + { + v = w->ptr.p_double[j]; + ae_v_moved(&ft.ptr.pp_double[j][0], 1, &fmatrix->ptr.pp_double[j][0], 1, ae_v_len(0,m-1), v); + b.ptr.p_double[j] = w->ptr.p_double[j]*y->ptr.p_double[j]; + } + rmatrixqr(&ft, n, m, &tau, _state); + rmatrixqrunpackq(&ft, n, m, &tau, m, &q, _state); + rmatrixqrunpackr(&ft, n, m, &r, _state); + ae_vector_set_length(&tmp, m, _state); + for(i=0; i<=m-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = b.ptr.p_double[i]; + ae_v_addd(&tmp.ptr.p_double[0], 1, &q.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + ae_vector_set_length(&b, m, _state); + ae_v_move(&b.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,m-1)); + + /* + * 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->taskrcond = rmatrixlurcondinf(&r, m, _state); + if( ae_fp_greater(rep->taskrcond,threshold) ) + { + + /* + * use QR-based solver + */ + ae_vector_set_length(c, m, _state); + c->ptr.p_double[m-1] = b.ptr.p_double[m-1]/r.ptr.pp_double[m-1][m-1]; + for(i=m-2; i>=0; i--) + { + v = ae_v_dotproduct(&r.ptr.pp_double[i][i+1], 1, &c->ptr.p_double[i+1], 1, ae_v_len(i+1,m-1)); + c->ptr.p_double[i] = (b.ptr.p_double[i]-v)/r.ptr.pp_double[i][i]; + } + } + else + { + + /* + * use SVD-based solver + */ + if( !rmatrixsvd(&r, m, m, 1, 1, 2, &sv, &u, &vt, _state) ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&utb, m, _state); + ae_vector_set_length(&sutb, m, _state); + for(i=0; i<=m-1; i++) + { + utb.ptr.p_double[i] = 0; + } + for(i=0; i<=m-1; i++) + { + v = b.ptr.p_double[i]; + ae_v_addd(&utb.ptr.p_double[0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + if( ae_fp_greater(sv.ptr.p_double[0],0) ) + { + rep->taskrcond = sv.ptr.p_double[m-1]/sv.ptr.p_double[0]; + for(i=0; i<=m-1; i++) + { + if( ae_fp_greater(sv.ptr.p_double[i],threshold*sv.ptr.p_double[0]) ) + { + sutb.ptr.p_double[i] = utb.ptr.p_double[i]/sv.ptr.p_double[i]; + } + else + { + sutb.ptr.p_double[i] = 0; + } + } + } + else + { + rep->taskrcond = 0; + for(i=0; i<=m-1; i++) + { + sutb.ptr.p_double[i] = 0; + } + } + ae_vector_set_length(c, m, _state); + for(i=0; i<=m-1; i++) + { + c->ptr.p_double[i] = 0; + } + for(i=0; i<=m-1; i++) + { + v = sutb.ptr.p_double[i]; + ae_v_addd(&c->ptr.p_double[0], 1, &vt.ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + } + } + + /* + * calculate errors + */ + rep->rmserror = 0; + rep->avgerror = 0; + rep->avgrelerror = 0; + rep->maxerror = 0; + relcnt = 0; + for(i=0; i<=n-1; i++) + { + v = ae_v_dotproduct(&fmatrix->ptr.pp_double[i][0], 1, &c->ptr.p_double[0], 1, ae_v_len(0,m-1)); + rep->rmserror = rep->rmserror+ae_sqr(v-y->ptr.p_double[i], _state); + rep->avgerror = rep->avgerror+ae_fabs(v-y->ptr.p_double[i], _state); + if( ae_fp_neq(y->ptr.p_double[i],0) ) + { + rep->avgrelerror = rep->avgrelerror+ae_fabs(v-y->ptr.p_double[i], _state)/ae_fabs(y->ptr.p_double[i], _state); + relcnt = relcnt+1; + } + rep->maxerror = ae_maxreal(rep->maxerror, ae_fabs(v-y->ptr.p_double[i], _state), _state); + } + rep->rmserror = ae_sqrt(rep->rmserror/n, _state); + rep->avgerror = rep->avgerror/n; + if( relcnt!=0 ) + { + rep->avgrelerror = rep->avgrelerror/relcnt; + } + ae_vector_set_length(&nzeros, n, _state); + ae_vector_set_length(&s, m, _state); + for(i=0; i<=m-1; i++) + { + s.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + s.ptr.p_double[j] = s.ptr.p_double[j]+ae_sqr(fmatrix->ptr.pp_double[i][j], _state); + } + nzeros.ptr.p_double[i] = 0; + } + for(i=0; i<=m-1; i++) + { + if( ae_fp_neq(s.ptr.p_double[i],0) ) + { + s.ptr.p_double[i] = ae_sqrt(1/s.ptr.p_double[i], _state); + } + else + { + s.ptr.p_double[i] = 1; + } + } + lsfit_estimateerrors(fmatrix, &nzeros, y, w, c, &s, n, m, rep, &r, 1, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine +*************************************************************************/ +static void lsfit_lsfitclearrequestfields(lsfitstate* state, + ae_state *_state) +{ + + + state->needf = ae_false; + state->needfg = ae_false; + state->needfgh = ae_false; + state->xupdated = ae_false; +} + + +/************************************************************************* +Internal subroutine, calculates barycentric basis functions. +Used for efficient simultaneous calculation of N basis functions. + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +static void lsfit_barycentriccalcbasis(barycentricinterpolant* b, + double t, + /* Real */ ae_vector* y, + ae_state *_state) +{ + double s2; + double s; + double v; + ae_int_t i; + ae_int_t j; + + + + /* + * special case: N=1 + */ + if( b->n==1 ) + { + y->ptr.p_double[0] = 1; + return; + } + + /* + * Here we assume that task is normalized, i.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 = ae_fabs(t-b->x.ptr.p_double[0], _state); + for(i=0; i<=b->n-1; i++) + { + v = b->x.ptr.p_double[i]; + if( ae_fp_eq(v,t) ) + { + for(j=0; j<=b->n-1; j++) + { + y->ptr.p_double[j] = 0; + } + y->ptr.p_double[i] = 1; + return; + } + v = ae_fabs(t-v, _state); + if( ae_fp_less(v,s) ) + { + s = v; + } + } + s2 = 0; + for(i=0; i<=b->n-1; i++) + { + v = s/(t-b->x.ptr.p_double[i]); + v = v*b->w.ptr.p_double[i]; + y->ptr.p_double[i] = v; + s2 = s2+v; + } + v = 1/s2; + ae_v_muld(&y->ptr.p_double[0], 1, ae_v_len(0,b->n-1), v); +} + + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +static void lsfit_internalchebyshevfit(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _xc; + ae_vector _yc; + ae_vector y2; + ae_vector w2; + ae_vector tmp; + ae_vector tmp2; + ae_vector tmpdiff; + ae_vector bx; + ae_vector by; + ae_vector bw; + ae_matrix fmatrix; + ae_matrix cmatrix; + ae_int_t i; + ae_int_t j; + double mx; + double decay; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_xc, xc, _state, ae_true); + xc = &_xc; + ae_vector_init_copy(&_yc, yc, _state, ae_true); + yc = &_yc; + *info = 0; + ae_vector_clear(c); + _lsfitreport_clear(rep); + ae_vector_init(&y2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpdiff, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&by, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bw, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&cmatrix, 0, 0, DT_REAL, _state, ae_true); + + lsfit_clearreport(rep, _state); + + /* + * weight decay for correct handling of task which becomes + * degenerate after constraints are applied + */ + decay = 10000*ae_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 + */ + ae_vector_set_length(&y2, n+m, _state); + ae_vector_set_length(&w2, n+m, _state); + ae_vector_set_length(&tmp, m, _state); + ae_vector_set_length(&tmpdiff, m, _state); + ae_matrix_set_length(&fmatrix, n+m, m, _state); + if( k>0 ) + { + ae_matrix_set_length(&cmatrix, k, m+1, _state); + } + + /* + * Fill design matrix, Y2, W2: + * * first N rows with basis functions for original points + * * next M rows with decay terms + */ + for(i=0; i<=n-1; i++) + { + + /* + * prepare Ith row + * use Tmp for calculations to avoid multidimensional arrays overhead + */ + for(j=0; j<=m-1; j++) + { + if( j==0 ) + { + tmp.ptr.p_double[j] = 1; + } + else + { + if( j==1 ) + { + tmp.ptr.p_double[j] = x->ptr.p_double[i]; + } + else + { + tmp.ptr.p_double[j] = 2*x->ptr.p_double[i]*tmp.ptr.p_double[j-1]-tmp.ptr.p_double[j-2]; + } + } + } + ae_v_move(&fmatrix.ptr.pp_double[i][0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + for(i=0; i<=m-1; i++) + { + for(j=0; j<=m-1; j++) + { + if( i==j ) + { + fmatrix.ptr.pp_double[n+i][j] = decay; + } + else + { + fmatrix.ptr.pp_double[n+i][j] = 0; + } + } + } + ae_v_move(&y2.ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&w2.ptr.p_double[0], 1, &w->ptr.p_double[0], 1, ae_v_len(0,n-1)); + mx = 0; + for(i=0; i<=n-1; i++) + { + mx = mx+ae_fabs(w->ptr.p_double[i], _state); + } + mx = mx/n; + for(i=0; i<=m-1; i++) + { + y2.ptr.p_double[n+i] = 0; + w2.ptr.p_double[n+i] = mx; + } + + /* + * fill constraints matrix + */ + for(i=0; i<=k-1; i++) + { + + /* + * prepare Ith row + * use Tmp for basis function values, + * TmpDiff for basos function derivatives + */ + for(j=0; j<=m-1; j++) + { + if( j==0 ) + { + tmp.ptr.p_double[j] = 1; + tmpdiff.ptr.p_double[j] = 0; + } + else + { + if( j==1 ) + { + tmp.ptr.p_double[j] = xc->ptr.p_double[i]; + tmpdiff.ptr.p_double[j] = 1; + } + else + { + tmp.ptr.p_double[j] = 2*xc->ptr.p_double[i]*tmp.ptr.p_double[j-1]-tmp.ptr.p_double[j-2]; + tmpdiff.ptr.p_double[j] = 2*(tmp.ptr.p_double[j-1]+xc->ptr.p_double[i]*tmpdiff.ptr.p_double[j-1])-tmpdiff.ptr.p_double[j-2]; + } + } + } + if( dc->ptr.p_int[i]==0 ) + { + ae_v_move(&cmatrix.ptr.pp_double[i][0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + if( dc->ptr.p_int[i]==1 ) + { + ae_v_move(&cmatrix.ptr.pp_double[i][0], 1, &tmpdiff.ptr.p_double[0], 1, ae_v_len(0,m-1)); + } + cmatrix.ptr.pp_double[i][m] = yc->ptr.p_double[i]; + } + + /* + * Solve constrained task + */ + if( k>0 ) + { + + /* + * solve using regularization + */ + lsfitlinearwc(&y2, &w2, &fmatrix, &cmatrix, n+m, m, k, info, c, rep, _state); + } + else + { + + /* + * no constraints, no regularization needed + */ + lsfitlinearwc(y, w, &fmatrix, &cmatrix, n, m, 0, info, c, rep, _state); + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal Floater-Hormann fitting subroutine for fixed D +*************************************************************************/ +static void lsfit_barycentricfitwcfixedd(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t d, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + ae_vector _w; + ae_vector _xc; + ae_vector _yc; + ae_matrix fmatrix; + ae_matrix cmatrix; + ae_vector y2; + ae_vector w2; + ae_vector sx; + ae_vector sy; + ae_vector sbf; + ae_vector xoriginal; + ae_vector yoriginal; + ae_vector tmp; + lsfitreport lrep; + double v0; + double v1; + double mx; + barycentricinterpolant b2; + ae_int_t i; + ae_int_t j; + ae_int_t relcnt; + double xa; + double xb; + double sa; + double sb; + double decay; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_vector_init_copy(&_w, w, _state, ae_true); + w = &_w; + ae_vector_init_copy(&_xc, xc, _state, ae_true); + xc = &_xc; + ae_vector_init_copy(&_yc, yc, _state, ae_true); + yc = &_yc; + *info = 0; + _barycentricinterpolant_clear(b); + _barycentricfitreport_clear(rep); + ae_matrix_init(&fmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&cmatrix, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sbf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&yoriginal, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + _lsfitreport_init(&lrep, _state, ae_true); + _barycentricinterpolant_init(&b2, _state, ae_true); + + if( ((n<1||m<2)||k<0)||k>=m ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=k-1; i++) + { + *info = 0; + if( dc->ptr.p_int[i]<0 ) + { + *info = -1; + } + if( dc->ptr.p_int[i]>1 ) + { + *info = -1; + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + } + + /* + * weight decay for correct handling of task which becomes + * degenerate after constraints are applied + */ + decay = 10000*ae_machineepsilon; + + /* + * Scale X, Y, XC, YC + */ + lsfitscalexy(x, y, w, n, xc, yc, dc, k, &xa, &xb, &sa, &sb, &xoriginal, &yoriginal, _state); + + /* + * allocate space, initialize: + * * FMatrix- values of basis functions at X[] + * * CMatrix- values (derivatives) of basis functions at XC[] + */ + ae_vector_set_length(&y2, n+m, _state); + ae_vector_set_length(&w2, n+m, _state); + ae_matrix_set_length(&fmatrix, n+m, m, _state); + if( k>0 ) + { + ae_matrix_set_length(&cmatrix, k, m+1, _state); + } + ae_vector_set_length(&y2, n+m, _state); + ae_vector_set_length(&w2, n+m, _state); + + /* + * 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 + */ + ae_vector_set_length(&sx, m, _state); + ae_vector_set_length(&sy, m, _state); + ae_vector_set_length(&sbf, m, _state); + for(j=0; j<=m-1; j++) + { + sx.ptr.p_double[j] = (double)(2*j)/(double)(m-1)-1; + } + for(i=0; i<=m-1; i++) + { + sy.ptr.p_double[i] = 1; + } + barycentricbuildfloaterhormann(&sx, &sy, m, d, &b2, _state); + mx = 0; + for(i=0; i<=n-1; i++) + { + lsfit_barycentriccalcbasis(&b2, x->ptr.p_double[i], &sbf, _state); + ae_v_move(&fmatrix.ptr.pp_double[i][0], 1, &sbf.ptr.p_double[0], 1, ae_v_len(0,m-1)); + y2.ptr.p_double[i] = y->ptr.p_double[i]; + w2.ptr.p_double[i] = w->ptr.p_double[i]; + mx = mx+ae_fabs(w->ptr.p_double[i], _state)/n; + } + for(i=0; i<=m-1; i++) + { + for(j=0; j<=m-1; j++) + { + if( i==j ) + { + fmatrix.ptr.pp_double[n+i][j] = decay; + } + else + { + fmatrix.ptr.pp_double[n+i][j] = 0; + } + } + y2.ptr.p_double[n+i] = 0; + w2.ptr.p_double[n+i] = mx; + } + if( k>0 ) + { + for(j=0; j<=m-1; j++) + { + for(i=0; i<=m-1; i++) + { + sy.ptr.p_double[i] = 0; + } + sy.ptr.p_double[j] = 1; + barycentricbuildfloaterhormann(&sx, &sy, m, d, &b2, _state); + for(i=0; i<=k-1; i++) + { + ae_assert(dc->ptr.p_int[i]>=0&&dc->ptr.p_int[i]<=1, "BarycentricFit: internal error!", _state); + barycentricdiff1(&b2, xc->ptr.p_double[i], &v0, &v1, _state); + if( dc->ptr.p_int[i]==0 ) + { + cmatrix.ptr.pp_double[i][j] = v0; + } + if( dc->ptr.p_int[i]==1 ) + { + cmatrix.ptr.pp_double[i][j] = v1; + } + } + } + for(i=0; i<=k-1; i++) + { + cmatrix.ptr.pp_double[i][m] = yc->ptr.p_double[i]; + } + } + + /* + * Solve constrained task + */ + if( k>0 ) + { + + /* + * solve using regularization + */ + lsfitlinearwc(&y2, &w2, &fmatrix, &cmatrix, n+m, m, k, info, &tmp, &lrep, _state); + } + else + { + + /* + * no constraints, no regularization needed + */ + lsfitlinearwc(y, w, &fmatrix, &cmatrix, n, m, k, info, &tmp, &lrep, _state); + } + if( *info<0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Generate interpolant and scale it + */ + ae_v_move(&sy.ptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,m-1)); + barycentricbuildfloaterhormann(&sx, &sy, m, d, b, _state); + barycentriclintransx(b, 2/(xb-xa), -(xa+xb)/(xb-xa), _state); + barycentriclintransy(b, sb-sa, sa, _state); + + /* + * Scale absolute errors obtained from LSFitLinearW. + * Relative error should be calculated separately + * (because of shifting/scaling of the task) + */ + rep->taskrcond = lrep.taskrcond; + rep->rmserror = lrep.rmserror*(sb-sa); + rep->avgerror = lrep.avgerror*(sb-sa); + rep->maxerror = lrep.maxerror*(sb-sa); + rep->avgrelerror = 0; + relcnt = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(yoriginal.ptr.p_double[i],0) ) + { + rep->avgrelerror = rep->avgrelerror+ae_fabs(barycentriccalc(b, xoriginal.ptr.p_double[i], _state)-yoriginal.ptr.p_double[i], _state)/ae_fabs(yoriginal.ptr.p_double[i], _state); + relcnt = relcnt+1; + } + } + if( relcnt!=0 ) + { + rep->avgrelerror = rep->avgrelerror/relcnt; + } + ae_frame_leave(_state); +} + + +static void lsfit_clearreport(lsfitreport* rep, ae_state *_state) +{ + + + rep->taskrcond = 0; + rep->iterationscount = 0; + rep->varidx = -1; + rep->rmserror = 0; + rep->avgerror = 0; + rep->avgrelerror = 0; + rep->maxerror = 0; + rep->wrmserror = 0; + rep->r2 = 0; + ae_matrix_set_length(&rep->covpar, 0, 0, _state); + ae_vector_set_length(&rep->errpar, 0, _state); + ae_vector_set_length(&rep->errcurve, 0, _state); + ae_vector_set_length(&rep->noise, 0, _state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +static void lsfit_estimateerrors(/* Real */ ae_matrix* f1, + /* Real */ ae_vector* f0, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* x, + /* Real */ ae_vector* s, + ae_int_t n, + ae_int_t k, + lsfitreport* rep, + /* Real */ ae_matrix* z, + ae_int_t zkind, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _s; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + double v; + double noisec; + ae_int_t info; + matinvreport invrep; + ae_int_t nzcnt; + double avg; + double rss; + double tss; + double sz; + double ss; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_s, s, _state, ae_true); + s = &_s; + _matinvreport_init(&invrep, _state, ae_true); + + + /* + * Compute NZCnt - count of non-zero weights + */ + nzcnt = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(w->ptr.p_double[i],0) ) + { + nzcnt = nzcnt+1; + } + } + + /* + * Compute R2 + */ + if( nzcnt>0 ) + { + avg = 0.0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(w->ptr.p_double[i],0) ) + { + avg = avg+y->ptr.p_double[i]; + } + } + avg = avg/nzcnt; + rss = 0.0; + tss = 0.0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(w->ptr.p_double[i],0) ) + { + v = ae_v_dotproduct(&f1->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,k-1)); + v = v+f0->ptr.p_double[i]; + rss = rss+ae_sqr(v-y->ptr.p_double[i], _state); + tss = tss+ae_sqr(y->ptr.p_double[i]-avg, _state); + } + } + if( ae_fp_neq(tss,0) ) + { + rep->r2 = ae_maxreal(1.0-rss/tss, 0.0, _state); + } + else + { + rep->r2 = 1.0; + } + } + else + { + rep->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-1; i++) + { + if( ae_fp_neq(w->ptr.p_double[i],0) ) + { + v = ae_v_dotproduct(&f1->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,k-1)); + v = v+f0->ptr.p_double[i]; + noisec = noisec+ae_sqr((v-y->ptr.p_double[i])*w->ptr.p_double[i], _state); + } + } + noisec = ae_sqrt(noisec/(nzcnt-k), _state); + } + else + { + noisec = 0.0; + } + + /* + * Two branches on noise level: + * * NoiseC>0 normal situation + * * NoiseC=0 degenerate case CovPar is filled by zeros + */ + rmatrixsetlengthatleast(&rep->covpar, k, k, _state); + if( ae_fp_greater(noisec,0) ) + { + + /* + * Normal situation: non-zero noise level + */ + ae_assert(zkind==0||zkind==1, "LSFit: internal error in EstimateErrors() function", _state); + 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. + */ + rmatrixsetlengthatleast(z, n, k, _state); + for(i=0; i<=n-1; i++) + { + v = w->ptr.p_double[i]/noisec; + ae_v_moved(&z->ptr.pp_double[i][0], 1, &f1->ptr.pp_double[i][0], 1, ae_v_len(0,k-1), 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-1; i++) + { + for(j=0; j<=k-1; j++) + { + sz = sz+z->ptr.pp_double[i][j]*z->ptr.pp_double[i][j]; + } + } + if( ae_fp_eq(sz,0) ) + { + sz = 1; + } + ss = 0; + for(j=0; j<=k-1; j++) + { + ss = ss+1/ae_sqr(s->ptr.p_double[j], _state); + } + for(j=0; j<=k-1; j++) + { + s->ptr.p_double[j] = sz/ss/ae_sqr(s->ptr.p_double[j], _state); + } + + /* + * Calculate damped inverse inv(Z'*Z+S). + * We increase damping factor V until Z'*Z become well-conditioned. + */ + v = 1.0E3*ae_machineepsilon; + do + { + rmatrixsyrk(k, n, 1.0, z, 0, 0, 2, 0.0, &rep->covpar, 0, 0, ae_true, _state); + for(i=0; i<=k-1; i++) + { + rep->covpar.ptr.pp_double[i][i] = rep->covpar.ptr.pp_double[i][i]+v*s->ptr.p_double[i]; + } + spdmatrixinverse(&rep->covpar, k, ae_true, &info, &invrep, _state); + v = 10*v; + } + while(info<=0); + for(i=0; i<=k-1; i++) + { + for(j=i+1; j<=k-1; j++) + { + rep->covpar.ptr.pp_double[j][i] = rep->covpar.ptr.pp_double[i][j]; + } + } + } + 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 = 0; + for(j=0; j<=k-1; j++) + { + sz = sz+ae_fabs(z->ptr.pp_double[j][j]/noisec, _state); + } + if( ae_fp_eq(sz,0) ) + { + sz = 1; + } + ss = 0; + for(j=0; j<=k-1; j++) + { + ss = ss+1/s->ptr.p_double[j]; + } + for(j=0; j<=k-1; j++) + { + s->ptr.p_double[j] = sz/ss/s->ptr.p_double[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*ae_machineepsilon; + do + { + for(i=0; i<=k-1; i++) + { + for(j=i; j<=k-1; j++) + { + rep->covpar.ptr.pp_double[i][j] = z->ptr.pp_double[i][j]/noisec; + } + rep->covpar.ptr.pp_double[i][i] = rep->covpar.ptr.pp_double[i][i]+v*s->ptr.p_double[i]; + } + spdmatrixcholeskyinverse(&rep->covpar, k, ae_true, &info, &invrep, _state); + v = 10*v; + } + while(info<=0); + for(i=0; i<=k-1; i++) + { + for(j=i+1; j<=k-1; j++) + { + rep->covpar.ptr.pp_double[j][i] = rep->covpar.ptr.pp_double[i][j]; + } + } + } + } + else + { + + /* + * Degenerate situation: zero noise level, covariance matrix is zero. + */ + for(i=0; i<=k-1; i++) + { + for(j=0; j<=k-1; j++) + { + rep->covpar.ptr.pp_double[j][i] = 0; + } + } + } + + /* + * Estimate erorrs in parameters, curve and per-point noise + */ + rvectorsetlengthatleast(&rep->errpar, k, _state); + rvectorsetlengthatleast(&rep->errcurve, n, _state); + rvectorsetlengthatleast(&rep->noise, n, _state); + for(i=0; i<=k-1; i++) + { + rep->errpar.ptr.p_double[i] = ae_sqrt(rep->covpar.ptr.pp_double[i][i], _state); + } + for(i=0; i<=n-1; i++) + { + + /* + * ErrCurve[I] is sqrt(P[i,i]) where P=J*CovPar*J' + */ + v = 0.0; + for(j=0; j<=k-1; j++) + { + for(j1=0; j1<=k-1; j1++) + { + v = v+f1->ptr.pp_double[i][j]*rep->covpar.ptr.pp_double[j][j1]*f1->ptr.pp_double[i][j1]; + } + } + rep->errcurve.ptr.p_double[i] = ae_sqrt(v, _state); + + /* + * Noise[i] is filled using weights and current estimate of noise level + */ + if( ae_fp_neq(w->ptr.p_double[i],0) ) + { + rep->noise.ptr.p_double[i] = noisec/w->ptr.p_double[i]; + } + else + { + rep->noise.ptr.p_double[i] = 0; + } + } + ae_frame_leave(_state); +} + + +ae_bool _polynomialfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + polynomialfitreport *p = (polynomialfitreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _polynomialfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + polynomialfitreport *dst = (polynomialfitreport*)_dst; + polynomialfitreport *src = (polynomialfitreport*)_src; + dst->taskrcond = src->taskrcond; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->maxerror = src->maxerror; + return ae_true; +} + + +void _polynomialfitreport_clear(void* _p) +{ + polynomialfitreport *p = (polynomialfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _polynomialfitreport_destroy(void* _p) +{ + polynomialfitreport *p = (polynomialfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _barycentricfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + barycentricfitreport *p = (barycentricfitreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _barycentricfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + barycentricfitreport *dst = (barycentricfitreport*)_dst; + barycentricfitreport *src = (barycentricfitreport*)_src; + dst->taskrcond = src->taskrcond; + dst->dbest = src->dbest; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->maxerror = src->maxerror; + return ae_true; +} + + +void _barycentricfitreport_clear(void* _p) +{ + barycentricfitreport *p = (barycentricfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _barycentricfitreport_destroy(void* _p) +{ + barycentricfitreport *p = (barycentricfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _spline1dfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + spline1dfitreport *p = (spline1dfitreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _spline1dfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + spline1dfitreport *dst = (spline1dfitreport*)_dst; + spline1dfitreport *src = (spline1dfitreport*)_src; + dst->taskrcond = src->taskrcond; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->maxerror = src->maxerror; + return ae_true; +} + + +void _spline1dfitreport_clear(void* _p) +{ + spline1dfitreport *p = (spline1dfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _spline1dfitreport_destroy(void* _p) +{ + spline1dfitreport *p = (spline1dfitreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _lsfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + lsfitreport *p = (lsfitreport*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->covpar, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->errpar, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->errcurve, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->noise, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _lsfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + lsfitreport *dst = (lsfitreport*)_dst; + lsfitreport *src = (lsfitreport*)_src; + dst->taskrcond = src->taskrcond; + dst->iterationscount = src->iterationscount; + dst->varidx = src->varidx; + dst->rmserror = src->rmserror; + dst->avgerror = src->avgerror; + dst->avgrelerror = src->avgrelerror; + dst->maxerror = src->maxerror; + dst->wrmserror = src->wrmserror; + if( !ae_matrix_init_copy(&dst->covpar, &src->covpar, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->errpar, &src->errpar, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->errcurve, &src->errcurve, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->noise, &src->noise, _state, make_automatic) ) + return ae_false; + dst->r2 = src->r2; + return ae_true; +} + + +void _lsfitreport_clear(void* _p) +{ + lsfitreport *p = (lsfitreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->covpar); + ae_vector_clear(&p->errpar); + ae_vector_clear(&p->errcurve); + ae_vector_clear(&p->noise); +} + + +void _lsfitreport_destroy(void* _p) +{ + lsfitreport *p = (lsfitreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->covpar); + ae_vector_destroy(&p->errpar); + ae_vector_destroy(&p->errcurve); + ae_vector_destroy(&p->noise); +} + + +ae_bool _lsfitstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + lsfitstate *p = (lsfitstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->taskx, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tasky, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->taskw, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->c, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->h, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->wcur, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpjac, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpjacw, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_matinvreport_init(&p->invrep, _state, make_automatic) ) + return ae_false; + if( !_lsfitreport_init(&p->rep, _state, make_automatic) ) + return ae_false; + if( !_minlmstate_init(&p->optstate, _state, make_automatic) ) + return ae_false; + if( !_minlmreport_init(&p->optrep, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _lsfitstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + lsfitstate *dst = (lsfitstate*)_dst; + lsfitstate *src = (lsfitstate*)_src; + dst->optalgo = src->optalgo; + dst->m = src->m; + dst->k = src->k; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->stpmax = src->stpmax; + dst->xrep = src->xrep; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->taskx, &src->taskx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tasky, &src->tasky, _state, make_automatic) ) + return ae_false; + dst->npoints = src->npoints; + if( !ae_vector_init_copy(&dst->taskw, &src->taskw, _state, make_automatic) ) + return ae_false; + dst->nweights = src->nweights; + dst->wkind = src->wkind; + dst->wits = src->wits; + dst->diffstep = src->diffstep; + dst->teststep = src->teststep; + dst->xupdated = src->xupdated; + dst->needf = src->needf; + dst->needfg = src->needfg; + dst->needfgh = src->needfgh; + dst->pointindex = src->pointindex; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->c, &src->c, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->h, &src->h, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->wcur, &src->wcur, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp, &src->tmp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpf, &src->tmpf, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpjac, &src->tmpjac, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpjacw, &src->tmpjacw, _state, make_automatic) ) + return ae_false; + dst->tmpnoise = src->tmpnoise; + if( !_matinvreport_init_copy(&dst->invrep, &src->invrep, _state, make_automatic) ) + return ae_false; + dst->repiterationscount = src->repiterationscount; + dst->repterminationtype = src->repterminationtype; + dst->repvaridx = src->repvaridx; + dst->reprmserror = src->reprmserror; + dst->repavgerror = src->repavgerror; + dst->repavgrelerror = src->repavgrelerror; + dst->repmaxerror = src->repmaxerror; + dst->repwrmserror = src->repwrmserror; + if( !_lsfitreport_init_copy(&dst->rep, &src->rep, _state, make_automatic) ) + return ae_false; + if( !_minlmstate_init_copy(&dst->optstate, &src->optstate, _state, make_automatic) ) + return ae_false; + if( !_minlmreport_init_copy(&dst->optrep, &src->optrep, _state, make_automatic) ) + return ae_false; + dst->prevnpt = src->prevnpt; + dst->prevalgo = src->prevalgo; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _lsfitstate_clear(void* _p) +{ + lsfitstate *p = (lsfitstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->s); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_matrix_clear(&p->taskx); + ae_vector_clear(&p->tasky); + ae_vector_clear(&p->taskw); + ae_vector_clear(&p->x); + ae_vector_clear(&p->c); + ae_vector_clear(&p->g); + ae_matrix_clear(&p->h); + ae_vector_clear(&p->wcur); + ae_vector_clear(&p->tmp); + ae_vector_clear(&p->tmpf); + ae_matrix_clear(&p->tmpjac); + ae_matrix_clear(&p->tmpjacw); + _matinvreport_clear(&p->invrep); + _lsfitreport_clear(&p->rep); + _minlmstate_clear(&p->optstate); + _minlmreport_clear(&p->optrep); + _rcommstate_clear(&p->rstate); +} + + +void _lsfitstate_destroy(void* _p) +{ + lsfitstate *p = (lsfitstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_matrix_destroy(&p->taskx); + ae_vector_destroy(&p->tasky); + ae_vector_destroy(&p->taskw); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->c); + ae_vector_destroy(&p->g); + ae_matrix_destroy(&p->h); + ae_vector_destroy(&p->wcur); + ae_vector_destroy(&p->tmp); + ae_vector_destroy(&p->tmpf); + ae_matrix_destroy(&p->tmpjac); + ae_matrix_destroy(&p->tmpjacw); + _matinvreport_destroy(&p->invrep); + _lsfitreport_destroy(&p->rep); + _minlmstate_destroy(&p->optstate); + _minlmreport_destroy(&p->optrep); + _rcommstate_destroy(&p->rstate); +} + + + + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2build(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline2interpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _xy; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_xy, xy, _state, ae_true); + xy = &_xy; + _pspline2interpolant_clear(p); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + ae_assert(st>=0&&st<=2, "PSpline2Build: incorrect spline type!", _state); + ae_assert(pt>=0&&pt<=2, "PSpline2Build: incorrect parameterization type!", _state); + if( st==0 ) + { + ae_assert(n>=5, "PSpline2Build: N<5 (minimum value for Akima splines)!", _state); + } + else + { + ae_assert(n>=2, "PSpline2Build: N<2!", _state); + } + + /* + * Prepare + */ + p->n = n; + p->periodic = ae_false; + ae_vector_set_length(&tmp, n, _state); + + /* + * Build parameterization, check that all parameters are distinct + */ + pspline_pspline2par(xy, n, pt, &p->p, _state); + ae_assert(aredistinct(&p->p, n, _state), "PSpline2Build: consequent points are too close!", _state); + + /* + * Build splines + */ + if( st==0 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildakima(&p->p, &tmp, n, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildakima(&p->p, &tmp, n, &p->y, _state); + } + if( st==1 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcatmullrom(&p->p, &tmp, n, 0, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcatmullrom(&p->p, &tmp, n, 0, 0.0, &p->y, _state); + } + if( st==2 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcubic(&p->p, &tmp, n, 0, 0.0, 0, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcubic(&p->p, &tmp, n, 0, 0.0, 0, 0.0, &p->y, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3build(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline3interpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _xy; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_xy, xy, _state, ae_true); + xy = &_xy; + _pspline3interpolant_clear(p); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + ae_assert(st>=0&&st<=2, "PSpline3Build: incorrect spline type!", _state); + ae_assert(pt>=0&&pt<=2, "PSpline3Build: incorrect parameterization type!", _state); + if( st==0 ) + { + ae_assert(n>=5, "PSpline3Build: N<5 (minimum value for Akima splines)!", _state); + } + else + { + ae_assert(n>=2, "PSpline3Build: N<2!", _state); + } + + /* + * Prepare + */ + p->n = n; + p->periodic = ae_false; + ae_vector_set_length(&tmp, n, _state); + + /* + * Build parameterization, check that all parameters are distinct + */ + pspline_pspline3par(xy, n, pt, &p->p, _state); + ae_assert(aredistinct(&p->p, n, _state), "PSpline3Build: consequent points are too close!", _state); + + /* + * Build splines + */ + if( st==0 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildakima(&p->p, &tmp, n, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildakima(&p->p, &tmp, n, &p->y, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][2], xy->stride, ae_v_len(0,n-1)); + spline1dbuildakima(&p->p, &tmp, n, &p->z, _state); + } + if( st==1 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcatmullrom(&p->p, &tmp, n, 0, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcatmullrom(&p->p, &tmp, n, 0, 0.0, &p->y, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][2], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcatmullrom(&p->p, &tmp, n, 0, 0.0, &p->z, _state); + } + if( st==2 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcubic(&p->p, &tmp, n, 0, 0.0, 0, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcubic(&p->p, &tmp, n, 0, 0.0, 0, 0.0, &p->y, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xy->ptr.pp_double[0][2], xy->stride, ae_v_len(0,n-1)); + spline1dbuildcubic(&p->p, &tmp, n, 0, 0.0, 0, 0.0, &p->z, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2buildperiodic(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline2interpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _xy; + ae_matrix xyp; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_xy, xy, _state, ae_true); + xy = &_xy; + _pspline2interpolant_clear(p); + ae_matrix_init(&xyp, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + ae_assert(st>=1&&st<=2, "PSpline2BuildPeriodic: incorrect spline type!", _state); + ae_assert(pt>=0&&pt<=2, "PSpline2BuildPeriodic: incorrect parameterization type!", _state); + ae_assert(n>=3, "PSpline2BuildPeriodic: N<3!", _state); + + /* + * Prepare + */ + p->n = n; + p->periodic = ae_true; + ae_vector_set_length(&tmp, n+1, _state); + ae_matrix_set_length(&xyp, n+1, 2, _state); + ae_v_move(&xyp.ptr.pp_double[0][0], xyp.stride, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + ae_v_move(&xyp.ptr.pp_double[0][1], xyp.stride, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + ae_v_move(&xyp.ptr.pp_double[n][0], 1, &xy->ptr.pp_double[0][0], 1, ae_v_len(0,1)); + + /* + * Build parameterization, check that all parameters are distinct + */ + pspline_pspline2par(&xyp, n+1, pt, &p->p, _state); + ae_assert(aredistinct(&p->p, n+1, _state), "PSpline2BuildPeriodic: consequent (or first and last) points are too close!", _state); + + /* + * Build splines + */ + if( st==1 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][0], xyp.stride, ae_v_len(0,n)); + spline1dbuildcatmullrom(&p->p, &tmp, n+1, -1, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][1], xyp.stride, ae_v_len(0,n)); + spline1dbuildcatmullrom(&p->p, &tmp, n+1, -1, 0.0, &p->y, _state); + } + if( st==2 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][0], xyp.stride, ae_v_len(0,n)); + spline1dbuildcubic(&p->p, &tmp, n+1, -1, 0.0, -1, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][1], xyp.stride, ae_v_len(0,n)); + spline1dbuildcubic(&p->p, &tmp, n+1, -1, 0.0, -1, 0.0, &p->y, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3buildperiodic(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline3interpolant* p, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _xy; + ae_matrix xyp; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_xy, xy, _state, ae_true); + xy = &_xy; + _pspline3interpolant_clear(p); + ae_matrix_init(&xyp, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + ae_assert(st>=1&&st<=2, "PSpline3BuildPeriodic: incorrect spline type!", _state); + ae_assert(pt>=0&&pt<=2, "PSpline3BuildPeriodic: incorrect parameterization type!", _state); + ae_assert(n>=3, "PSpline3BuildPeriodic: N<3!", _state); + + /* + * Prepare + */ + p->n = n; + p->periodic = ae_true; + ae_vector_set_length(&tmp, n+1, _state); + ae_matrix_set_length(&xyp, n+1, 3, _state); + ae_v_move(&xyp.ptr.pp_double[0][0], xyp.stride, &xy->ptr.pp_double[0][0], xy->stride, ae_v_len(0,n-1)); + ae_v_move(&xyp.ptr.pp_double[0][1], xyp.stride, &xy->ptr.pp_double[0][1], xy->stride, ae_v_len(0,n-1)); + ae_v_move(&xyp.ptr.pp_double[0][2], xyp.stride, &xy->ptr.pp_double[0][2], xy->stride, ae_v_len(0,n-1)); + ae_v_move(&xyp.ptr.pp_double[n][0], 1, &xy->ptr.pp_double[0][0], 1, ae_v_len(0,2)); + + /* + * Build parameterization, check that all parameters are distinct + */ + pspline_pspline3par(&xyp, n+1, pt, &p->p, _state); + ae_assert(aredistinct(&p->p, n+1, _state), "PSplineBuild2Periodic: consequent (or first and last) points are too close!", _state); + + /* + * Build splines + */ + if( st==1 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][0], xyp.stride, ae_v_len(0,n)); + spline1dbuildcatmullrom(&p->p, &tmp, n+1, -1, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][1], xyp.stride, ae_v_len(0,n)); + spline1dbuildcatmullrom(&p->p, &tmp, n+1, -1, 0.0, &p->y, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][2], xyp.stride, ae_v_len(0,n)); + spline1dbuildcatmullrom(&p->p, &tmp, n+1, -1, 0.0, &p->z, _state); + } + if( st==2 ) + { + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][0], xyp.stride, ae_v_len(0,n)); + spline1dbuildcubic(&p->p, &tmp, n+1, -1, 0.0, -1, 0.0, &p->x, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][1], xyp.stride, ae_v_len(0,n)); + spline1dbuildcubic(&p->p, &tmp, n+1, -1, 0.0, -1, 0.0, &p->y, _state); + ae_v_move(&tmp.ptr.p_double[0], 1, &xyp.ptr.pp_double[0][2], xyp.stride, ae_v_len(0,n)); + spline1dbuildcubic(&p->p, &tmp, n+1, -1, 0.0, -1, 0.0, &p->z, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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]n>=2, "PSpline2ParameterValues: internal error!", _state); + *n = p->n; + ae_vector_set_length(t, *n, _state); + ae_v_move(&t->ptr.p_double[0], 1, &p->p.ptr.p_double[0], 1, ae_v_len(0,*n-1)); + t->ptr.p_double[0] = 0; + if( !p->periodic ) + { + t->ptr.p_double[*n-1] = 1; + } +} + + +/************************************************************************* +This function returns vector of parameter values correspoding to points. + +Same as PSpline2ParameterValues(), but for 3D. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3parametervalues(pspline3interpolant* p, + ae_int_t* n, + /* Real */ ae_vector* t, + ae_state *_state) +{ + + *n = 0; + ae_vector_clear(t); + + ae_assert(p->n>=2, "PSpline3ParameterValues: internal error!", _state); + *n = p->n; + ae_vector_set_length(t, *n, _state); + ae_v_move(&t->ptr.p_double[0], 1, &p->p.ptr.p_double[0], 1, ae_v_len(0,*n-1)); + t->ptr.p_double[0] = 0; + if( !p->periodic ) + { + t->ptr.p_double[*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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2calc(pspline2interpolant* p, + double t, + double* x, + double* y, + ae_state *_state) +{ + + *x = 0; + *y = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + *x = spline1dcalc(&p->x, t, _state); + *y = spline1dcalc(&p->y, t, _state); +} + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3calc(pspline3interpolant* p, + double t, + double* x, + double* y, + double* z, + ae_state *_state) +{ + + *x = 0; + *y = 0; + *z = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + *x = spline1dcalc(&p->x, t, _state); + *y = spline1dcalc(&p->y, t, _state); + *z = spline1dcalc(&p->z, t, _state); +} + + +/************************************************************************* +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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2tangent(pspline2interpolant* p, + double t, + double* x, + double* y, + ae_state *_state) +{ + double v; + double v0; + double v1; + + *x = 0; + *y = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + pspline2diff(p, t, &v0, x, &v1, y, _state); + if( ae_fp_neq(*x,0)||ae_fp_neq(*y,0) ) + { + + /* + * this code is a bit more complex than X^2+Y^2 to avoid + * overflow for large values of X and Y. + */ + v = safepythag2(*x, *y, _state); + *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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3tangent(pspline3interpolant* p, + double t, + double* x, + double* y, + double* z, + ae_state *_state) +{ + double v; + double v0; + double v1; + double v2; + + *x = 0; + *y = 0; + *z = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + pspline3diff(p, t, &v0, x, &v1, y, &v2, z, _state); + if( (ae_fp_neq(*x,0)||ae_fp_neq(*y,0))||ae_fp_neq(*z,0) ) + { + v = safepythag3(*x, *y, *z, _state); + *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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff(pspline2interpolant* p, + double t, + double* x, + double* dx, + double* y, + double* dy, + ae_state *_state) +{ + double d2s; + + *x = 0; + *dx = 0; + *y = 0; + *dy = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + spline1ddiff(&p->x, t, x, dx, &d2s, _state); + spline1ddiff(&p->y, t, y, dy, &d2s, _state); +} + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff(pspline3interpolant* p, + double t, + double* x, + double* dx, + double* y, + double* dy, + double* z, + double* dz, + ae_state *_state) +{ + double d2s; + + *x = 0; + *dx = 0; + *y = 0; + *dy = 0; + *z = 0; + *dz = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + spline1ddiff(&p->x, t, x, dx, &d2s, _state); + spline1ddiff(&p->y, t, y, dy, &d2s, _state); + spline1ddiff(&p->z, t, z, dz, &d2s, _state); +} + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff2(pspline2interpolant* p, + double t, + double* x, + double* dx, + double* d2x, + double* y, + double* dy, + double* d2y, + ae_state *_state) +{ + + *x = 0; + *dx = 0; + *d2x = 0; + *y = 0; + *dy = 0; + *d2y = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + spline1ddiff(&p->x, t, x, dx, d2x, _state); + spline1ddiff(&p->y, t, y, dy, d2y, _state); +} + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff2(pspline3interpolant* p, + double t, + double* x, + double* dx, + double* d2x, + double* y, + double* dy, + double* d2y, + double* z, + double* dz, + double* d2z, + ae_state *_state) +{ + + *x = 0; + *dx = 0; + *d2x = 0; + *y = 0; + *dy = 0; + *d2y = 0; + *z = 0; + *dz = 0; + *d2z = 0; + + if( p->periodic ) + { + t = t-ae_ifloor(t, _state); + } + spline1ddiff(&p->x, t, x, dx, d2x, _state); + spline1ddiff(&p->y, t, y, dy, d2y, _state); + spline1ddiff(&p->z, t, z, dz, d2z, _state); +} + + +/************************************************************************* +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 + * Bx, state.x, &sx, &dsx, &d2sx, _state); + spline1ddiff(&p->y, state.x, &sy, &dsy, &d2sy, _state); + state.f = safepythag2(dsx, dsy, _state); + } + autogkresults(&state, &result, &rep, _state); + ae_assert(rep.terminationtype>0, "PSpline2ArcLength: internal error!", _state); + ae_frame_leave(_state); + 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 + * Bx, state.x, &sx, &dsx, &d2sx, _state); + spline1ddiff(&p->y, state.x, &sy, &dsy, &d2sy, _state); + spline1ddiff(&p->z, state.x, &sz, &dsz, &d2sz, _state); + state.f = safepythag3(dsx, dsy, dsz, _state); + } + autogkresults(&state, &result, &rep, _state); + ae_assert(rep.terminationtype>0, "PSpline3ArcLength: internal error!", _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Builds non-periodic parameterization for 2-dimensional spline +*************************************************************************/ +static void pspline_pspline2par(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t pt, + /* Real */ ae_vector* p, + ae_state *_state) +{ + double v; + ae_int_t i; + + ae_vector_clear(p); + + ae_assert(pt>=0&&pt<=2, "PSpline2Par: internal error!", _state); + + /* + * Build parameterization: + * * fill by non-normalized values + * * normalize them so we have P[0]=0, P[N-1]=1. + */ + ae_vector_set_length(p, n, _state); + if( pt==0 ) + { + for(i=0; i<=n-1; i++) + { + p->ptr.p_double[i] = i; + } + } + if( pt==1 ) + { + p->ptr.p_double[0] = 0; + for(i=1; i<=n-1; i++) + { + p->ptr.p_double[i] = p->ptr.p_double[i-1]+safepythag2(xy->ptr.pp_double[i][0]-xy->ptr.pp_double[i-1][0], xy->ptr.pp_double[i][1]-xy->ptr.pp_double[i-1][1], _state); + } + } + if( pt==2 ) + { + p->ptr.p_double[0] = 0; + for(i=1; i<=n-1; i++) + { + p->ptr.p_double[i] = p->ptr.p_double[i-1]+ae_sqrt(safepythag2(xy->ptr.pp_double[i][0]-xy->ptr.pp_double[i-1][0], xy->ptr.pp_double[i][1]-xy->ptr.pp_double[i-1][1], _state), _state); + } + } + v = 1/p->ptr.p_double[n-1]; + ae_v_muld(&p->ptr.p_double[0], 1, ae_v_len(0,n-1), v); +} + + +/************************************************************************* +Builds non-periodic parameterization for 3-dimensional spline +*************************************************************************/ +static void pspline_pspline3par(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t pt, + /* Real */ ae_vector* p, + ae_state *_state) +{ + double v; + ae_int_t i; + + ae_vector_clear(p); + + ae_assert(pt>=0&&pt<=2, "PSpline3Par: internal error!", _state); + + /* + * Build parameterization: + * * fill by non-normalized values + * * normalize them so we have P[0]=0, P[N-1]=1. + */ + ae_vector_set_length(p, n, _state); + if( pt==0 ) + { + for(i=0; i<=n-1; i++) + { + p->ptr.p_double[i] = i; + } + } + if( pt==1 ) + { + p->ptr.p_double[0] = 0; + for(i=1; i<=n-1; i++) + { + p->ptr.p_double[i] = p->ptr.p_double[i-1]+safepythag3(xy->ptr.pp_double[i][0]-xy->ptr.pp_double[i-1][0], xy->ptr.pp_double[i][1]-xy->ptr.pp_double[i-1][1], xy->ptr.pp_double[i][2]-xy->ptr.pp_double[i-1][2], _state); + } + } + if( pt==2 ) + { + p->ptr.p_double[0] = 0; + for(i=1; i<=n-1; i++) + { + p->ptr.p_double[i] = p->ptr.p_double[i-1]+ae_sqrt(safepythag3(xy->ptr.pp_double[i][0]-xy->ptr.pp_double[i-1][0], xy->ptr.pp_double[i][1]-xy->ptr.pp_double[i-1][1], xy->ptr.pp_double[i][2]-xy->ptr.pp_double[i-1][2], _state), _state); + } + } + v = 1/p->ptr.p_double[n-1]; + ae_v_muld(&p->ptr.p_double[0], 1, ae_v_len(0,n-1), v); +} + + +ae_bool _pspline2interpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + pspline2interpolant *p = (pspline2interpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->p, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init(&p->x, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init(&p->y, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _pspline2interpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + pspline2interpolant *dst = (pspline2interpolant*)_dst; + pspline2interpolant *src = (pspline2interpolant*)_src; + dst->n = src->n; + dst->periodic = src->periodic; + if( !ae_vector_init_copy(&dst->p, &src->p, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _pspline2interpolant_clear(void* _p) +{ + pspline2interpolant *p = (pspline2interpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->p); + _spline1dinterpolant_clear(&p->x); + _spline1dinterpolant_clear(&p->y); +} + + +void _pspline2interpolant_destroy(void* _p) +{ + pspline2interpolant *p = (pspline2interpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->p); + _spline1dinterpolant_destroy(&p->x); + _spline1dinterpolant_destroy(&p->y); +} + + +ae_bool _pspline3interpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + pspline3interpolant *p = (pspline3interpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->p, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init(&p->x, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init(&p->y, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init(&p->z, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _pspline3interpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + pspline3interpolant *dst = (pspline3interpolant*)_dst; + pspline3interpolant *src = (pspline3interpolant*)_src; + dst->n = src->n; + dst->periodic = src->periodic; + if( !ae_vector_init_copy(&dst->p, &src->p, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !_spline1dinterpolant_init_copy(&dst->z, &src->z, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _pspline3interpolant_clear(void* _p) +{ + pspline3interpolant *p = (pspline3interpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->p); + _spline1dinterpolant_clear(&p->x); + _spline1dinterpolant_clear(&p->y); + _spline1dinterpolant_clear(&p->z); +} + + +void _pspline3interpolant_destroy(void* _p) +{ + pspline3interpolant *p = (pspline3interpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->p); + _spline1dinterpolant_destroy(&p->x); + _spline1dinterpolant_destroy(&p->y); + _spline1dinterpolant_destroy(&p->z); +} + + + + +/************************************************************************* +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). + +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() (points do NOT have to be on a + regular grid) +3. (OPTIONAL) User chooses polynomial term by calling: + * RBFLinTerm() to set linear term + * RBFConstTerm() to set constant term + * RBFZeroTerm() to set zero term + By default, linear term is used. +4. User chooses specific RBF algorithm to use: either QNN (RBFSetAlgoQNN) + or ML (RBFSetAlgoMultiLayer). +5. User calls RBFBuildModel() function which rebuilds model according to + the specification +6. User may call RBFCalc() to calculate model value at the specified point, + RBFGridCalc() to calculate model values at the points of the regular + grid. User may extract model coefficients with RBFUnpack() call. + +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) + +NOTE 1: memory requirements. RBF models require amount of memory which is + proportional to the number of data points. Memory is allocated + during model construction, but most of this memory is freed after + model coefficients are calculated. + + Some approximate estimates for N centers with default settings are + given below: + * about 250*N*(sizeof(double)+2*sizeof(int)) bytes of memory is + needed during model construction stage. + * about 15*N*sizeof(double) bytes is needed after model is built. + For example, for N=100000 we may need 0.6 GB of memory to build + model, but just about 0.012 GB to store it. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcreate(ae_int_t nx, ae_int_t ny, rbfmodel* s, ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + _rbfmodel_clear(s); + + ae_assert(nx==2||nx==3, "RBFCreate: NX<>2 and NX<>3", _state); + ae_assert(ny>=1, "RBFCreate: NY<1", _state); + s->nx = nx; + s->ny = ny; + s->nl = 0; + s->nc = 0; + ae_matrix_set_length(&s->v, ny, rbf_mxnx+1, _state); + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + s->v.ptr.pp_double[i][j] = 0; + } + } + s->n = 0; + s->rmax = 0; + s->gridtype = 2; + s->fixrad = ae_false; + s->radvalue = 1; + s->radzvalue = 5; + s->aterm = 1; + s->algorithmtype = 1; + + /* + * stopping criteria + */ + s->epsort = rbf_eps; + s->epserr = rbf_eps; + s->maxits = 0; +} + + +/************************************************************************* +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. + +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 + specific, 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: 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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetpoints(rbfmodel* s, + /* Real */ ae_matrix* xy, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + ae_assert(n>0, "RBFSetPoints: N<0", _state); + ae_assert(xy->rows>=n, "RBFSetPoints: Rows(XY)cols>=s->nx+s->ny, "RBFSetPoints: Cols(XY)n = n; + ae_matrix_set_length(&s->x, s->n, rbf_mxnx, _state); + ae_matrix_set_length(&s->y, s->n, s->ny, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + s->x.ptr.pp_double[i][j] = 0; + } + for(j=0; j<=s->nx-1; j++) + { + s->x.ptr.pp_double[i][j] = xy->ptr.pp_double[i][j]; + } + for(j=0; j<=s->ny-1; j++) + { + s->y.ptr.pp_double[i][j] = xy->ptr.pp_double[i][j+s->nx]; + } + } +} + + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-QNN and it is good for point sets with +following properties: +a) all points are distinct +b) all points are well separated. +c) points distribution is approximately uniform. There is no "contour + lines", clusters of points, or other small-scale structures. + +Algorithm description: +1) interpolation centers are allocated to data points +2) interpolation radii are calculated as distances to the nearest centers + times Q coefficient (where Q is a value from [0.75,1.50]). +3) after performing (2) radii are transformed in order to avoid situation + when single outlier has very large radius and influences many points + across all dataset. Transformation has following form: + new_r[i] = min(r[i],Z*median(r[])) + where r[i] is I-th radius, median() is a median radius across entire + dataset, Z is user-specified value which controls amount of deviation + from median radius. + +When (a) is violated, we will be unable to build RBF model. When (b) or +(c) are violated, model will be built, but interpolation quality will be +low. See http://www.alglib.net/interpolation/ for more information on this +subject. + +This algorithm is used by default. + +Additional Q parameter controls smoothness properties of the RBF basis: +* Q<0.75 will give perfectly conditioned basis, but terrible smoothness + properties (RBF interpolant will have sharp peaks around function values) +* Q around 1.0 gives good balance between smoothness and condition number +* Q>1.5 will lead to badly conditioned systems and slow convergence of the + underlying linear solver (although smoothness will be very good) +* Q>2.0 will effectively make optimizer useless because it won't converge + within reasonable amount of iterations. It is possible to set such large + Q, but it is advised not to do so. + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + Q - Q parameter, Q>0, recommended value - 1.0 + Z - Z parameter, Z>0, recommended value - 5.0 + +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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgoqnn(rbfmodel* s, double q, double z, ae_state *_state) +{ + + + ae_assert(ae_isfinite(q, _state), "RBFSetAlgoQNN: Q is infinite or NAN", _state); + ae_assert(ae_fp_greater(q,0), "RBFSetAlgoQNN: Q<=0", _state); + rbf_rbfgridpoints(s, _state); + rbf_rbfradnn(s, q, z, _state); + s->algorithmtype = 1; +} + + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-ML. It builds multilayer RBF model, i.e. +model with subsequently decreasing radii, which allows us to combine +smoothness (due to large radii of the first layers) with exactness (due +to small radii of the last layers) and fast convergence. + +Internally RBF-ML uses many different means of acceleration, from sparse +matrices to KD-trees, which results in algorithm whose working time is +roughly proportional to N*log(N)*Density*RBase^2*NLayers, where N is a +number of points, Density is an average density if points per unit of the +interpolation space, RBase is an initial radius, NLayers is a number of +layers. + +RBF-ML is good for following kinds of interpolation problems: +1. "exact" problems (perfect fit) with well separated points +2. least squares problems with arbitrary distribution of points (algorithm + gives perfect fit where it is possible, and resorts to least squares + fit in the hard areas). +3. noisy problems where we want to apply some controlled amount of + smoothing. + +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. + LambdaV - regularization value, can be useful when solving problem + in the least squares sense. Optimal lambda is problem- + dependent and require trial and error. In our experience, + good lambda can be as large as 0.1, and you can use 0.001 + as initial guess. + Default value - 0.01, which is used when LambdaV is not + given. You can specify zero value, but it is not + recommended to do so. + +TUNING ALGORITHM + +In order to use this algorithm you have to choose three parameters: +* initial radius RBase +* number of layers in the model NLayers +* regularization coefficient LambdaV + +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. + +Regularization coefficient LambdaV controls smoothness of the individual +models built for each layer. We recommend you to use default value in case +you don't want to tune this parameter, because having non-zero LambdaV +accelerates and stabilizes internal iterative algorithm. In case you want +to suppress noise you can use LambdaV as additional parameter (larger +value = more smoothness) to tune. + +TYPICAL ERRORS + +1. Using initial radius which is too large. Memory requirements of the + RBF-ML are roughly proportional to N*Density*RBase^2 (where Density is + an average density of points per unit of the interpolation space). In + the extreme case of the very large RBase we will need O(N^2) units of + memory - and many layers in order to decrease radius to some reasonably + small value. + +2. 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. + +3. 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. + +4. Using too many layers - you will get too large and too slow model. This + model will perfectly reproduce your function, but maybe you will be + able to achieve similar results with less layers (and less memory). + + -- ALGLIB -- + Copyright 02.03.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgomultilayer(rbfmodel* s, + double rbase, + ae_int_t nlayers, + double lambdav, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(rbase, _state), "RBFSetAlgoMultiLayer: RBase is infinite or NaN", _state); + ae_assert(ae_fp_greater(rbase,0), "RBFSetAlgoMultiLayer: RBase<=0", _state); + ae_assert(nlayers>=0, "RBFSetAlgoMultiLayer: NLayers<0", _state); + ae_assert(ae_isfinite(lambdav, _state), "RBFSetAlgoMultiLayer: LambdaV is infinite or NAN", _state); + ae_assert(ae_fp_greater_eq(lambdav,0), "RBFSetAlgoMultiLayer: LambdaV<0", _state); + s->radvalue = rbase; + s->nlayers = nlayers; + s->algorithmtype = 2; + s->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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetlinterm(rbfmodel* s, ae_state *_state) +{ + + + s->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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetconstterm(rbfmodel* s, ae_state *_state) +{ + + + s->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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetzeroterm(rbfmodel* s, ae_state *_state) +{ + + + s->aterm = 3; +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetcond(rbfmodel* s, + double epsort, + double epserr, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsort, _state)&&ae_fp_greater_eq(epsort,0), "RBFSetCond: EpsOrt is negative, INF or NAN", _state); + ae_assert(ae_isfinite(epserr, _state)&&ae_fp_greater_eq(epserr,0), "RBFSetCond: EpsB is negative, INF or NAN", _state); + ae_assert(maxits>=0, "RBFSetCond: MaxIts is negative", _state); + if( (ae_fp_eq(epsort,0)&&ae_fp_eq(epserr,0))&&maxits==0 ) + { + s->epsort = rbf_eps; + s->epserr = rbf_eps; + s->maxits = 0; + } + else + { + s->epsort = epsort; + s->epserr = epserr; + s->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 + * -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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfbuildmodel(rbfmodel* s, rbfreport* rep, ae_state *_state) +{ + ae_frame _frame_block; + kdtree tree; + kdtree ctree; + ae_vector dist; + ae_vector xcx; + ae_matrix a; + ae_matrix v; + ae_matrix omega; + ae_vector y; + ae_matrix residualy; + ae_vector radius; + ae_matrix xc; + ae_vector mnx; + ae_vector mxx; + ae_vector edge; + ae_vector mxsteps; + ae_int_t nc; + double rmax; + ae_vector tags; + ae_vector ctags; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t k2; + ae_int_t snnz; + ae_vector tmp0; + ae_vector tmp1; + ae_int_t layerscnt; + + ae_frame_make(_state, &_frame_block); + _rbfreport_clear(rep); + _kdtree_init(&tree, _state, ae_true); + _kdtree_init(&ctree, _state, ae_true); + ae_vector_init(&dist, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xcx, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&v, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&omega, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&residualy, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&radius, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xc, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&mnx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&mxx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&edge, 0, DT_REAL, _state, ae_true); + ae_vector_init(&mxsteps, 0, DT_INT, _state, ae_true); + ae_vector_init(&tags, 0, DT_INT, _state, ae_true); + ae_vector_init(&ctags, 0, DT_INT, _state, ae_true); + ae_vector_init(&tmp0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp1, 0, DT_REAL, _state, ae_true); + + ae_assert(s->nx==2||s->nx==3, "RBFBuildModel: S.NX<>2 or S.NX<>3!", _state); + + /* + * Quick exit when we have no points + */ + if( s->n==0 ) + { + rep->terminationtype = 1; + rep->iterationscount = 0; + rep->nmv = 0; + rep->arows = 0; + rep->acols = 0; + kdtreebuildtagged(&s->xc, &tags, 0, rbf_mxnx, 0, 2, &s->tree, _state); + ae_matrix_set_length(&s->xc, 0, 0, _state); + ae_matrix_set_length(&s->wr, 0, 0, _state); + s->nc = 0; + s->rmax = 0; + ae_matrix_set_length(&s->v, s->ny, rbf_mxnx+1, _state); + for(i=0; i<=s->ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + s->v.ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * General case, N>0 + */ + rep->annz = 0; + rep->iterationscount = 0; + rep->nmv = 0; + ae_vector_set_length(&xcx, rbf_mxnx, _state); + + /* + * First model in a sequence - linear model. + * Residuals from linear regression are stored in the ResidualY variable + * (used later to build RBF models). + */ + ae_matrix_set_length(&residualy, s->n, s->ny, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=s->ny-1; j++) + { + residualy.ptr.pp_double[i][j] = s->y.ptr.pp_double[i][j]; + } + } + if( !rbf_buildlinearmodel(&s->x, &residualy, s->n, s->ny, s->aterm, &v, _state) ) + { + rep->terminationtype = -5; + ae_frame_leave(_state); + return; + } + + /* + * Handle special case: multilayer model with NLayers=0. + * Quick exit. + */ + if( s->algorithmtype==2&&s->nlayers==0 ) + { + rep->terminationtype = 1; + rep->iterationscount = 0; + rep->nmv = 0; + rep->arows = 0; + rep->acols = 0; + kdtreebuildtagged(&s->xc, &tags, 0, rbf_mxnx, 0, 2, &s->tree, _state); + ae_matrix_set_length(&s->xc, 0, 0, _state); + ae_matrix_set_length(&s->wr, 0, 0, _state); + s->nc = 0; + s->rmax = 0; + ae_matrix_set_length(&s->v, s->ny, rbf_mxnx+1, _state); + for(i=0; i<=s->ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + s->v.ptr.pp_double[i][j] = v.ptr.pp_double[i][j]; + } + } + ae_frame_leave(_state); + 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; + if( s->algorithmtype==1 ) + { + + /* + * Add RBF model. + * This model uses local KD-trees to speed-up nearest neighbor searches. + */ + if( s->gridtype==1 ) + { + ae_vector_set_length(&mxx, s->nx, _state); + ae_vector_set_length(&mnx, s->nx, _state); + ae_vector_set_length(&mxsteps, s->nx, _state); + ae_vector_set_length(&edge, s->nx, _state); + for(i=0; i<=s->nx-1; i++) + { + mxx.ptr.p_double[i] = s->x.ptr.pp_double[0][i]; + mnx.ptr.p_double[i] = s->x.ptr.pp_double[0][i]; + } + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=s->nx-1; j++) + { + if( ae_fp_less(mxx.ptr.p_double[j],s->x.ptr.pp_double[i][j]) ) + { + mxx.ptr.p_double[j] = s->x.ptr.pp_double[i][j]; + } + if( ae_fp_greater(mnx.ptr.p_double[j],s->x.ptr.pp_double[i][j]) ) + { + mnx.ptr.p_double[j] = s->x.ptr.pp_double[i][j]; + } + } + } + for(i=0; i<=s->nx-1; i++) + { + mxsteps.ptr.p_int[i] = ae_trunc((mxx.ptr.p_double[i]-mnx.ptr.p_double[i])/(2*s->h), _state)+1; + edge.ptr.p_double[i] = (mxx.ptr.p_double[i]+mnx.ptr.p_double[i])/2-s->h*mxsteps.ptr.p_int[i]; + } + nc = 1; + for(i=0; i<=s->nx-1; i++) + { + mxsteps.ptr.p_int[i] = 2*mxsteps.ptr.p_int[i]+1; + nc = nc*mxsteps.ptr.p_int[i]; + } + ae_matrix_set_length(&xc, nc, rbf_mxnx, _state); + if( s->nx==2 ) + { + for(i=0; i<=mxsteps.ptr.p_int[0]-1; i++) + { + for(j=0; j<=mxsteps.ptr.p_int[1]-1; j++) + { + for(k2=0; k2<=rbf_mxnx-1; k2++) + { + xc.ptr.pp_double[i*mxsteps.ptr.p_int[1]+j][k2] = 0; + } + xc.ptr.pp_double[i*mxsteps.ptr.p_int[1]+j][0] = edge.ptr.p_double[0]+s->h*i; + xc.ptr.pp_double[i*mxsteps.ptr.p_int[1]+j][1] = edge.ptr.p_double[1]+s->h*j; + } + } + } + if( s->nx==3 ) + { + for(i=0; i<=mxsteps.ptr.p_int[0]-1; i++) + { + for(j=0; j<=mxsteps.ptr.p_int[1]-1; j++) + { + for(k=0; k<=mxsteps.ptr.p_int[2]-1; k++) + { + for(k2=0; k2<=rbf_mxnx-1; k2++) + { + xc.ptr.pp_double[i*mxsteps.ptr.p_int[1]+j][k2] = 0; + } + xc.ptr.pp_double[(i*mxsteps.ptr.p_int[1]+j)*mxsteps.ptr.p_int[2]+k][0] = edge.ptr.p_double[0]+s->h*i; + xc.ptr.pp_double[(i*mxsteps.ptr.p_int[1]+j)*mxsteps.ptr.p_int[2]+k][1] = edge.ptr.p_double[1]+s->h*j; + xc.ptr.pp_double[(i*mxsteps.ptr.p_int[1]+j)*mxsteps.ptr.p_int[2]+k][2] = edge.ptr.p_double[2]+s->h*k; + } + } + } + } + } + else + { + if( s->gridtype==2 ) + { + nc = s->n; + ae_matrix_set_length(&xc, nc, rbf_mxnx, _state); + for(i=0; i<=nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xc.ptr.pp_double[i][j] = s->x.ptr.pp_double[i][j]; + } + } + } + else + { + if( s->gridtype==3 ) + { + nc = s->nc; + ae_matrix_set_length(&xc, nc, rbf_mxnx, _state); + for(i=0; i<=nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xc.ptr.pp_double[i][j] = s->xc.ptr.pp_double[i][j]; + } + } + } + else + { + ae_assert(ae_false, "RBFBuildModel: either S.GridType<1 or S.GridType>3!", _state); + } + } + } + rmax = 0; + ae_vector_set_length(&radius, nc, _state); + ae_vector_set_length(&ctags, nc, _state); + for(i=0; i<=nc-1; i++) + { + ctags.ptr.p_int[i] = i; + } + kdtreebuildtagged(&xc, &ctags, nc, rbf_mxnx, 0, 2, &ctree, _state); + if( s->fixrad ) + { + + /* + * Fixed radius + */ + for(i=0; i<=nc-1; i++) + { + radius.ptr.p_double[i] = s->radvalue; + } + rmax = radius.ptr.p_double[0]; + } + else + { + + /* + * Dynamic radius + */ + if( nc==0 ) + { + rmax = 1; + } + else + { + if( nc==1 ) + { + radius.ptr.p_double[0] = s->radvalue; + rmax = radius.ptr.p_double[0]; + } + else + { + + /* + * NC>1, calculate radii using distances to nearest neigbors + */ + for(i=0; i<=nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xcx.ptr.p_double[j] = xc.ptr.pp_double[i][j]; + } + if( kdtreequeryknn(&ctree, &xcx, 1, ae_false, _state)>0 ) + { + kdtreequeryresultsdistances(&ctree, &dist, _state); + radius.ptr.p_double[i] = s->radvalue*dist.ptr.p_double[0]; + } + else + { + + /* + * No neighbors found (it will happen when we have only one center). + * Initialize radius with default value. + */ + radius.ptr.p_double[i] = 1.0; + } + } + + /* + * Apply filtering + */ + rvectorsetlengthatleast(&tmp0, nc, _state); + for(i=0; i<=nc-1; i++) + { + tmp0.ptr.p_double[i] = radius.ptr.p_double[i]; + } + tagsortfast(&tmp0, &tmp1, nc, _state); + for(i=0; i<=nc-1; i++) + { + radius.ptr.p_double[i] = ae_minreal(radius.ptr.p_double[i], s->radzvalue*tmp0.ptr.p_double[nc/2], _state); + } + + /* + * Calculate RMax, check that all radii are non-zero + */ + for(i=0; i<=nc-1; i++) + { + rmax = ae_maxreal(rmax, radius.ptr.p_double[i], _state); + } + for(i=0; i<=nc-1; i++) + { + if( ae_fp_eq(radius.ptr.p_double[i],0) ) + { + rep->terminationtype = -5; + ae_frame_leave(_state); + return; + } + } + } + } + } + ivectorsetlengthatleast(&tags, s->n, _state); + for(i=0; i<=s->n-1; i++) + { + tags.ptr.p_int[i] = i; + } + kdtreebuildtagged(&s->x, &tags, s->n, rbf_mxnx, 0, 2, &tree, _state); + rbf_buildrbfmodellsqr(&s->x, &residualy, &xc, &radius, s->n, nc, s->ny, &tree, &ctree, s->epsort, s->epserr, s->maxits, &rep->annz, &snnz, &omega, &rep->terminationtype, &rep->iterationscount, &rep->nmv, _state); + layerscnt = 1; + } + else + { + if( s->algorithmtype==2 ) + { + rmax = s->radvalue; + rbf_buildrbfmlayersmodellsqr(&s->x, &residualy, &xc, s->radvalue, &radius, s->n, &nc, s->ny, s->nlayers, &ctree, 1.0E-6, 1.0E-6, 50, s->lambdav, &rep->annz, &omega, &rep->terminationtype, &rep->iterationscount, &rep->nmv, _state); + layerscnt = s->nlayers; + } + else + { + ae_assert(ae_false, "RBFBuildModel: internal error(AlgorithmType neither 1 nor 2)", _state); + } + } + if( rep->terminationtype<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Model is built + */ + s->nc = nc/layerscnt; + s->rmax = rmax; + s->nl = layerscnt; + ae_matrix_set_length(&s->xc, s->nc, rbf_mxnx, _state); + ae_matrix_set_length(&s->wr, s->nc, 1+s->nl*s->ny, _state); + ae_matrix_set_length(&s->v, s->ny, rbf_mxnx+1, _state); + for(i=0; i<=s->nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + s->xc.ptr.pp_double[i][j] = xc.ptr.pp_double[i][j]; + } + } + ivectorsetlengthatleast(&tags, s->nc, _state); + for(i=0; i<=s->nc-1; i++) + { + tags.ptr.p_int[i] = i; + } + kdtreebuildtagged(&s->xc, &tags, s->nc, rbf_mxnx, 0, 2, &s->tree, _state); + for(i=0; i<=s->nc-1; i++) + { + s->wr.ptr.pp_double[i][0] = radius.ptr.p_double[i]; + for(k=0; k<=layerscnt-1; k++) + { + for(j=0; j<=s->ny-1; j++) + { + s->wr.ptr.pp_double[i][1+k*s->ny+j] = omega.ptr.pp_double[k*s->nc+i][j]; + } + } + } + for(i=0; i<=s->ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + s->v.ptr.pp_double[i][j] = v.ptr.pp_double[i][j]; + } + } + rep->terminationtype = 1; + rep->arows = s->n; + rep->acols = s->nc; + ae_frame_leave(_state); +} + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc2(rbfmodel* s, double x0, double x1, ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t lx; + ae_int_t tg; + double d2; + double t; + double bfcur; + double rcur; + double result; + + + ae_assert(ae_isfinite(x0, _state), "RBFCalc2: invalid value for X0 (X0 is Inf)!", _state); + ae_assert(ae_isfinite(x1, _state), "RBFCalc2: invalid value for X1 (X1 is Inf)!", _state); + if( s->ny!=1||s->nx!=2 ) + { + result = 0; + return result; + } + result = s->v.ptr.pp_double[0][0]*x0+s->v.ptr.pp_double[0][1]*x1+s->v.ptr.pp_double[0][rbf_mxnx]; + if( s->nc==0 ) + { + return result; + } + rvectorsetlengthatleast(&s->calcbufxcx, rbf_mxnx, _state); + for(i=0; i<=rbf_mxnx-1; i++) + { + s->calcbufxcx.ptr.p_double[i] = 0.0; + } + s->calcbufxcx.ptr.p_double[0] = x0; + s->calcbufxcx.ptr.p_double[1] = x1; + lx = kdtreequeryrnn(&s->tree, &s->calcbufxcx, s->rmax*rbf_rbffarradius, ae_true, _state); + kdtreequeryresultsx(&s->tree, &s->calcbufx, _state); + kdtreequeryresultstags(&s->tree, &s->calcbuftags, _state); + for(i=0; i<=lx-1; i++) + { + tg = s->calcbuftags.ptr.p_int[i]; + d2 = ae_sqr(x0-s->calcbufx.ptr.pp_double[i][0], _state)+ae_sqr(x1-s->calcbufx.ptr.pp_double[i][1], _state); + rcur = s->wr.ptr.pp_double[tg][0]; + bfcur = ae_exp(-d2/(rcur*rcur), _state); + for(j=0; j<=s->nl-1; j++) + { + result = result+bfcur*s->wr.ptr.pp_double[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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc3(rbfmodel* s, + double x0, + double x1, + double x2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t lx; + ae_int_t tg; + double t; + double rcur; + double bf; + double result; + + + ae_assert(ae_isfinite(x0, _state), "RBFCalc3: invalid value for X0 (X0 is Inf or NaN)!", _state); + ae_assert(ae_isfinite(x1, _state), "RBFCalc3: invalid value for X1 (X1 is Inf or NaN)!", _state); + ae_assert(ae_isfinite(x2, _state), "RBFCalc3: invalid value for X2 (X2 is Inf or NaN)!", _state); + if( s->ny!=1||s->nx!=3 ) + { + result = 0; + return result; + } + result = s->v.ptr.pp_double[0][0]*x0+s->v.ptr.pp_double[0][1]*x1+s->v.ptr.pp_double[0][2]*x2+s->v.ptr.pp_double[0][rbf_mxnx]; + if( s->nc==0 ) + { + return result; + } + + /* + * calculating value for F(X) + */ + rvectorsetlengthatleast(&s->calcbufxcx, rbf_mxnx, _state); + for(i=0; i<=rbf_mxnx-1; i++) + { + s->calcbufxcx.ptr.p_double[i] = 0.0; + } + s->calcbufxcx.ptr.p_double[0] = x0; + s->calcbufxcx.ptr.p_double[1] = x1; + s->calcbufxcx.ptr.p_double[2] = x2; + lx = kdtreequeryrnn(&s->tree, &s->calcbufxcx, s->rmax*rbf_rbffarradius, ae_true, _state); + kdtreequeryresultsx(&s->tree, &s->calcbufx, _state); + kdtreequeryresultstags(&s->tree, &s->calcbuftags, _state); + for(i=0; i<=lx-1; i++) + { + tg = s->calcbuftags.ptr.p_int[i]; + rcur = s->wr.ptr.pp_double[tg][0]; + bf = ae_exp(-(ae_sqr(x0-s->calcbufx.ptr.pp_double[i][0], _state)+ae_sqr(x1-s->calcbufx.ptr.pp_double[i][1], _state)+ae_sqr(x2-s->calcbufx.ptr.pp_double[i][2], _state))/ae_sqr(rcur, _state), _state); + for(j=0; j<=s->nl-1; j++) + { + result = result+bf*s->wr.ptr.pp_double[tg][1+j]; + t = bf*bf; + bf = t*t; + } + } + return result; +} + + +/************************************************************************* +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(). + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalc(rbfmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + + ae_vector_clear(y); + + ae_assert(x->cnt>=s->nx, "RBFCalc: Length(X)nx, _state), "RBFCalc: X contains infinite or NaN values", _state); + rbfcalcbuf(s, x, y, _state); +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalcbuf(rbfmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t lx; + ae_int_t tg; + double t; + double rcur; + double bf; + + + ae_assert(x->cnt>=s->nx, "RBFCalcBuf: Length(X)nx, _state), "RBFCalcBuf: X contains infinite or NaN values", _state); + if( y->cntny ) + { + ae_vector_set_length(y, s->ny, _state); + } + for(i=0; i<=s->ny-1; i++) + { + y->ptr.p_double[i] = s->v.ptr.pp_double[i][rbf_mxnx]; + for(j=0; j<=s->nx-1; j++) + { + y->ptr.p_double[i] = y->ptr.p_double[i]+s->v.ptr.pp_double[i][j]*x->ptr.p_double[j]; + } + } + if( s->nc==0 ) + { + return; + } + rvectorsetlengthatleast(&s->calcbufxcx, rbf_mxnx, _state); + for(i=0; i<=rbf_mxnx-1; i++) + { + s->calcbufxcx.ptr.p_double[i] = 0.0; + } + for(i=0; i<=s->nx-1; i++) + { + s->calcbufxcx.ptr.p_double[i] = x->ptr.p_double[i]; + } + lx = kdtreequeryrnn(&s->tree, &s->calcbufxcx, s->rmax*rbf_rbffarradius, ae_true, _state); + kdtreequeryresultsx(&s->tree, &s->calcbufx, _state); + kdtreequeryresultstags(&s->tree, &s->calcbuftags, _state); + for(i=0; i<=s->ny-1; i++) + { + for(j=0; j<=lx-1; j++) + { + tg = s->calcbuftags.ptr.p_int[j]; + rcur = s->wr.ptr.pp_double[tg][0]; + bf = ae_exp(-(ae_sqr(s->calcbufxcx.ptr.p_double[0]-s->calcbufx.ptr.pp_double[j][0], _state)+ae_sqr(s->calcbufxcx.ptr.p_double[1]-s->calcbufx.ptr.pp_double[j][1], _state)+ae_sqr(s->calcbufxcx.ptr.p_double[2]-s->calcbufx.ptr.pp_double[j][2], _state))/ae_sqr(rcur, _state), _state); + for(k=0; k<=s->nl-1; k++) + { + y->ptr.p_double[i] = y->ptr.p_double[i]+bf*s->wr.ptr.pp_double[tg][1+k*s->ny+i]; + t = bf*bf; + bf = t*t; + } + } + } +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfgridcalc2(rbfmodel* s, + /* Real */ ae_vector* x0, + ae_int_t n0, + /* Real */ ae_vector* x1, + ae_int_t n1, + /* Real */ ae_matrix* y, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector cpx0; + ae_vector cpx1; + ae_vector p01; + ae_vector p11; + ae_vector p2; + double rlimit; + double xcnorm2; + ae_int_t hp01; + double hcpx0; + double xc0; + double xc1; + double omega; + double radius; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t d; + ae_int_t i00; + ae_int_t i01; + ae_int_t i10; + ae_int_t i11; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(y); + ae_vector_init(&cpx0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&cpx1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p01, 0, DT_INT, _state, ae_true); + ae_vector_init(&p11, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + + ae_assert(n0>0, "RBFGridCalc2: invalid value for N0 (N0<=0)!", _state); + ae_assert(n1>0, "RBFGridCalc2: invalid value for N1 (N1<=0)!", _state); + ae_assert(x0->cnt>=n0, "RBFGridCalc2: Length(X0)cnt>=n1, "RBFGridCalc2: Length(X1)ptr.pp_double[i][j] = 0; + } + } + if( (s->ny!=1||s->nx!=2)||s->nc==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + *create and sort arrays + */ + ae_vector_set_length(&cpx0, n0, _state); + for(i=0; i<=n0-1; i++) + { + cpx0.ptr.p_double[i] = x0->ptr.p_double[i]; + } + tagsort(&cpx0, n0, &p01, &p2, _state); + ae_vector_set_length(&cpx1, n1, _state); + for(i=0; i<=n1-1; i++) + { + cpx1.ptr.p_double[i] = x1->ptr.p_double[i]; + } + tagsort(&cpx1, n1, &p11, &p2, _state); + + /* + *calculate function's value + */ + for(i=0; i<=s->nc-1; i++) + { + radius = s->wr.ptr.pp_double[i][0]; + for(d=0; d<=s->nl-1; d++) + { + omega = s->wr.ptr.pp_double[i][1+d]; + rlimit = radius*rbf_rbffarradius; + + /* + *search lower and upper indexes + */ + i00 = lowerbound(&cpx0, n0, s->xc.ptr.pp_double[i][0]-rlimit, _state); + i01 = upperbound(&cpx0, n0, s->xc.ptr.pp_double[i][0]+rlimit, _state); + i10 = lowerbound(&cpx1, n1, s->xc.ptr.pp_double[i][1]-rlimit, _state); + i11 = upperbound(&cpx1, n1, s->xc.ptr.pp_double[i][1]+rlimit, _state); + xc0 = s->xc.ptr.pp_double[i][0]; + xc1 = s->xc.ptr.pp_double[i][1]; + for(j=i00; j<=i01-1; j++) + { + hcpx0 = cpx0.ptr.p_double[j]; + hp01 = p01.ptr.p_int[j]; + for(k=i10; k<=i11-1; k++) + { + xcnorm2 = ae_sqr(hcpx0-xc0, _state)+ae_sqr(cpx1.ptr.p_double[k]-xc1, _state); + if( ae_fp_less_eq(xcnorm2,rlimit*rlimit) ) + { + y->ptr.pp_double[hp01][p11.ptr.p_int[k]] = y->ptr.pp_double[hp01][p11.ptr.p_int[k]]+ae_exp(-xcnorm2/ae_sqr(radius, _state), _state)*omega; + } + } + } + radius = 0.5*radius; + } + } + + /* + *add linear term + */ + for(i=0; i<=n0-1; i++) + { + for(j=0; j<=n1-1; j++) + { + y->ptr.pp_double[i][j] = y->ptr.pp_double[i][j]+s->v.ptr.pp_double[0][0]*x0->ptr.p_double[i]+s->v.ptr.pp_double[0][1]*x1->ptr.p_double[j]+s->v.ptr.pp_double[0][rbf_mxnx]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfunpack(rbfmodel* s, + ae_int_t* nx, + ae_int_t* ny, + /* Real */ ae_matrix* xwr, + ae_int_t* nc, + /* Real */ ae_matrix* v, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double rcur; + + *nx = 0; + *ny = 0; + ae_matrix_clear(xwr); + *nc = 0; + ae_matrix_clear(v); + + *nx = s->nx; + *ny = s->ny; + *nc = s->nc; + + /* + * Fill V + */ + ae_matrix_set_length(v, s->ny, s->nx+1, _state); + for(i=0; i<=s->ny-1; i++) + { + ae_v_move(&v->ptr.pp_double[i][0], 1, &s->v.ptr.pp_double[i][0], 1, ae_v_len(0,s->nx-1)); + v->ptr.pp_double[i][s->nx] = s->v.ptr.pp_double[i][rbf_mxnx]; + } + + /* + * Fill XWR and V + */ + if( *nc*s->nl>0 ) + { + ae_matrix_set_length(xwr, s->nc*s->nl, s->nx+s->ny+1, _state); + for(i=0; i<=s->nc-1; i++) + { + rcur = s->wr.ptr.pp_double[i][0]; + for(j=0; j<=s->nl-1; j++) + { + ae_v_move(&xwr->ptr.pp_double[i*s->nl+j][0], 1, &s->xc.ptr.pp_double[i][0], 1, ae_v_len(0,s->nx-1)); + ae_v_move(&xwr->ptr.pp_double[i*s->nl+j][s->nx], 1, &s->wr.ptr.pp_double[i][1+j*s->ny], 1, ae_v_len(s->nx,s->nx+s->ny-1)); + xwr->ptr.pp_double[i*s->nl+j][s->nx+s->ny] = rcur; + rcur = 0.5*rcur; + } + } + } +} + + +/************************************************************************* +Serializer: allocation + + -- ALGLIB -- + Copyright 02.02.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfalloc(ae_serializer* s, rbfmodel* model, ae_state *_state) +{ + + + + /* + * Header + */ + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + + /* + * Data + */ + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + ae_serializer_alloc_entry(s); + kdtreealloc(s, &model->tree, _state); + allocrealmatrix(s, &model->xc, -1, -1, _state); + allocrealmatrix(s, &model->wr, -1, -1, _state); + ae_serializer_alloc_entry(s); + allocrealmatrix(s, &model->v, -1, -1, _state); +} + + +/************************************************************************* +Serializer: serialization + + -- ALGLIB -- + Copyright 02.02.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfserialize(ae_serializer* s, rbfmodel* model, ae_state *_state) +{ + + + + /* + * Header + */ + ae_serializer_serialize_int(s, getrbfserializationcode(_state), _state); + ae_serializer_serialize_int(s, rbf_rbffirstversion, _state); + + /* + * Data + */ + ae_serializer_serialize_int(s, model->nx, _state); + ae_serializer_serialize_int(s, model->ny, _state); + ae_serializer_serialize_int(s, model->nc, _state); + ae_serializer_serialize_int(s, model->nl, _state); + kdtreeserialize(s, &model->tree, _state); + serializerealmatrix(s, &model->xc, -1, -1, _state); + serializerealmatrix(s, &model->wr, -1, -1, _state); + ae_serializer_serialize_double(s, model->rmax, _state); + serializerealmatrix(s, &model->v, -1, -1, _state); +} + + +/************************************************************************* +Serializer: unserialization + + -- ALGLIB -- + Copyright 02.02.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfunserialize(ae_serializer* s, rbfmodel* model, ae_state *_state) +{ + ae_int_t i0; + ae_int_t i1; + ae_int_t nx; + ae_int_t ny; + + _rbfmodel_clear(model); + + + /* + * Header + */ + ae_serializer_unserialize_int(s, &i0, _state); + ae_assert(i0==getrbfserializationcode(_state), "RBFUnserialize: stream header corrupted", _state); + ae_serializer_unserialize_int(s, &i1, _state); + ae_assert(i1==rbf_rbffirstversion, "RBFUnserialize: stream header corrupted", _state); + + /* + * Unserialize primary model parameters, initialize model. + * + * It is necessary to call RBFCreate() because some internal fields + * which are NOT unserialized will need initialization. + */ + ae_serializer_unserialize_int(s, &nx, _state); + ae_serializer_unserialize_int(s, &ny, _state); + rbfcreate(nx, ny, model, _state); + ae_serializer_unserialize_int(s, &model->nc, _state); + ae_serializer_unserialize_int(s, &model->nl, _state); + kdtreeunserialize(s, &model->tree, _state); + unserializerealmatrix(s, &model->xc, _state); + unserializerealmatrix(s, &model->wr, _state); + ae_serializer_unserialize_double(s, &model->rmax, _state); + unserializerealmatrix(s, &model->v, _state); +} + + +/************************************************************************* +This function changes centers allocation algorithm to one which allocates +centers exactly at the dataset points (one input point = one center). This +function won't have effect until next call to RBFBuildModel(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +static void rbf_rbfgridpoints(rbfmodel* s, ae_state *_state) +{ + + + s->gridtype = 2; +} + + +/************************************************************************* +This function changes radii calculation algorithm to one which makes +radius for I-th node equal to R[i]=DistNN[i]*Q, where: +* R[i] is a radius calculated by the algorithm +* DistNN[i] is distance from I-th center to its nearest neighbor center +* Q is a scale parameter, which should be within [0.75,1.50], with + recommended value equal to 1.0 +* after performing radii calculation, radii are transformed in order to + avoid situation when single outlier has very large radius and influences + many points across entire dataset. Transformation has following form: + new_r[i] = min(r[i],Z*median(r[])) + where r[i] is I-th radius, median() is a median radius across entire + dataset, Z is user-specified value which controls amount of deviation + from median radius. + +This function won't have effect until next call to RBFBuildModel(). + +The idea behind this algorithm is to choose radii corresponding to basis +functions is such way that I-th radius is approximately equal to distance +from I-th center to its nearest neighbor. In this case interactions with +distant points will be insignificant, and we will get well conditioned +basis. + +Properties of this basis depend on the value of Q: +* Q<0.75 will give perfectly conditioned basis, but terrible smoothness + properties (RBF interpolant will have sharp peaks around function values) +* Q>1.5 will lead to badly conditioned systems and slow convergence of the + underlying linear solver (although smoothness will be very good) +* Q around 1.0 gives good balance between smoothness and condition number + + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + Q - radius coefficient, Q>0 + Z - z-parameter, Z>0 + +Default value of Q is equal to 1.0 +Default value of Z is equal to 5.0 + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +static void rbf_rbfradnn(rbfmodel* s, + double q, + double z, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(q, _state)&&ae_fp_greater(q,0), "RBFRadNN: Q<=0, infinite or NAN", _state); + ae_assert(ae_isfinite(z, _state)&&ae_fp_greater(z,0), "RBFRadNN: Z<=0, infinite or NAN", _state); + s->fixrad = ae_false; + s->radvalue = q; + s->radzvalue = z; +} + + +static ae_bool rbf_buildlinearmodel(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t ny, + ae_int_t modeltype, + /* Real */ ae_matrix* v, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmpy; + ae_matrix a; + double scaling; + ae_vector shifting; + double mn; + double mx; + ae_vector c; + lsfitreport rep; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t info; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(v); + ae_vector_init(&tmpy, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&shifting, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_REAL, _state, ae_true); + _lsfitreport_init(&rep, _state, ae_true); + + ae_assert(n>=0, "BuildLinearModel: N<0", _state); + ae_assert(ny>0, "BuildLinearModel: NY<=0", _state); + + /* + * Handle degenerate case (N=0) + */ + result = ae_true; + ae_matrix_set_length(v, ny, rbf_mxnx+1, _state); + if( n==0 ) + { + for(j=0; j<=rbf_mxnx; j++) + { + for(i=0; i<=ny-1; i++) + { + v->ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return result; + } + + /* + * Allocate temporaries + */ + ae_vector_set_length(&tmpy, n, _state); + + /* + * General linear model. + */ + if( modeltype==1 ) + { + + /* + * Calculate scaling/shifting, transform variables, prepare LLS problem + */ + ae_matrix_set_length(&a, n, rbf_mxnx+1, _state); + ae_vector_set_length(&shifting, rbf_mxnx, _state); + scaling = 0; + for(i=0; i<=rbf_mxnx-1; i++) + { + mn = x->ptr.pp_double[0][i]; + mx = mn; + for(j=1; j<=n-1; j++) + { + if( ae_fp_greater(mn,x->ptr.pp_double[j][i]) ) + { + mn = x->ptr.pp_double[j][i]; + } + if( ae_fp_less(mx,x->ptr.pp_double[j][i]) ) + { + mx = x->ptr.pp_double[j][i]; + } + } + scaling = ae_maxreal(scaling, mx-mn, _state); + shifting.ptr.p_double[i] = 0.5*(mx+mn); + } + if( ae_fp_eq(scaling,0) ) + { + scaling = 1; + } + else + { + scaling = 0.5*scaling; + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + a.ptr.pp_double[i][j] = (x->ptr.pp_double[i][j]-shifting.ptr.p_double[j])/scaling; + } + } + for(i=0; i<=n-1; i++) + { + a.ptr.pp_double[i][rbf_mxnx] = 1; + } + + /* + * Solve linear system in transformed variables, make backward + */ + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=n-1; j++) + { + tmpy.ptr.p_double[j] = y->ptr.pp_double[j][i]; + } + lsfitlinear(&tmpy, &a, n, rbf_mxnx+1, &info, &c, &rep, _state); + if( info<=0 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + for(j=0; j<=rbf_mxnx-1; j++) + { + v->ptr.pp_double[i][j] = c.ptr.p_double[j]/scaling; + } + v->ptr.pp_double[i][rbf_mxnx] = c.ptr.p_double[rbf_mxnx]; + for(j=0; j<=rbf_mxnx-1; j++) + { + v->ptr.pp_double[i][rbf_mxnx] = v->ptr.pp_double[i][rbf_mxnx]-shifting.ptr.p_double[j]*v->ptr.pp_double[i][j]; + } + for(j=0; j<=n-1; j++) + { + for(k=0; k<=rbf_mxnx-1; k++) + { + y->ptr.pp_double[j][i] = y->ptr.pp_double[j][i]-x->ptr.pp_double[j][k]*v->ptr.pp_double[i][k]; + } + y->ptr.pp_double[j][i] = y->ptr.pp_double[j][i]-v->ptr.pp_double[i][rbf_mxnx]; + } + } + ae_frame_leave(_state); + return result; + } + + /* + * Constant model, very simple + */ + if( modeltype==2 ) + { + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + v->ptr.pp_double[i][j] = 0; + } + for(j=0; j<=n-1; j++) + { + v->ptr.pp_double[i][rbf_mxnx] = v->ptr.pp_double[i][rbf_mxnx]+y->ptr.pp_double[j][i]; + } + if( n>0 ) + { + v->ptr.pp_double[i][rbf_mxnx] = v->ptr.pp_double[i][rbf_mxnx]/n; + } + for(j=0; j<=n-1; j++) + { + y->ptr.pp_double[j][i] = y->ptr.pp_double[j][i]-v->ptr.pp_double[i][rbf_mxnx]; + } + } + ae_frame_leave(_state); + return result; + } + + /* + * Zero model + */ + ae_assert(modeltype==3, "BuildLinearModel: unknown model type", _state); + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=rbf_mxnx; j++) + { + v->ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return result; +} + + +static void rbf_buildrbfmodellsqr(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + /* Real */ ae_matrix* xc, + /* Real */ ae_vector* r, + ae_int_t n, + ae_int_t nc, + ae_int_t ny, + kdtree* pointstree, + kdtree* centerstree, + double epsort, + double epserr, + ae_int_t maxits, + ae_int_t* gnnz, + ae_int_t* snnz, + /* Real */ ae_matrix* w, + ae_int_t* info, + ae_int_t* iterationscount, + ae_int_t* nmv, + ae_state *_state) +{ + ae_frame _frame_block; + linlsqrstate state; + linlsqrreport lsqrrep; + sparsematrix spg; + sparsematrix sps; + ae_vector nearcenterscnt; + ae_vector nearpointscnt; + ae_vector skipnearpointscnt; + ae_vector farpointscnt; + ae_int_t maxnearcenterscnt; + ae_int_t maxnearpointscnt; + ae_int_t maxfarpointscnt; + ae_int_t sumnearcenterscnt; + ae_int_t sumnearpointscnt; + ae_int_t sumfarpointscnt; + double maxrad; + ae_vector pointstags; + ae_vector centerstags; + ae_matrix nearpoints; + ae_matrix nearcenters; + ae_matrix farpoints; + ae_int_t tmpi; + ae_int_t pointscnt; + ae_int_t centerscnt; + ae_vector xcx; + ae_vector tmpy; + ae_vector tc; + ae_vector g; + ae_vector c; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t sind; + ae_matrix a; + double vv; + double vx; + double vy; + double vz; + double vr; + double gnorm2; + ae_vector tmp0; + ae_vector tmp1; + ae_vector tmp2; + double fx; + ae_matrix xx; + ae_matrix cx; + double mrad; + + ae_frame_make(_state, &_frame_block); + *gnnz = 0; + *snnz = 0; + ae_matrix_clear(w); + *info = 0; + *iterationscount = 0; + *nmv = 0; + _linlsqrstate_init(&state, _state, ae_true); + _linlsqrreport_init(&lsqrrep, _state, ae_true); + _sparsematrix_init(&spg, _state, ae_true); + _sparsematrix_init(&sps, _state, ae_true); + ae_vector_init(&nearcenterscnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&nearpointscnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&skipnearpointscnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&farpointscnt, 0, DT_INT, _state, ae_true); + ae_vector_init(&pointstags, 0, DT_INT, _state, ae_true); + ae_vector_init(¢erstags, 0, DT_INT, _state, ae_true); + ae_matrix_init(&nearpoints, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&nearcenters, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&farpoints, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xcx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&g, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp2, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xx, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&cx, 0, 0, DT_REAL, _state, ae_true); + + + /* + * Handle special cases: NC=0 + */ + if( nc==0 ) + { + *info = 1; + *iterationscount = 0; + *nmv = 0; + ae_frame_leave(_state); + return; + } + + /* + * Prepare for general case, NC>0 + */ + ae_vector_set_length(&xcx, rbf_mxnx, _state); + ae_vector_set_length(&pointstags, n, _state); + ae_vector_set_length(¢erstags, nc, _state); + *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) + */ + ae_vector_set_length(&nearcenterscnt, nc, _state); + ae_vector_set_length(&nearpointscnt, nc, _state); + ae_vector_set_length(&skipnearpointscnt, nc, _state); + ae_vector_set_length(&farpointscnt, nc, _state); + maxnearcenterscnt = 0; + maxnearpointscnt = 0; + maxfarpointscnt = 0; + sumnearcenterscnt = 0; + sumnearpointscnt = 0; + sumfarpointscnt = 0; + for(i=0; i<=nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xcx.ptr.p_double[j] = xc->ptr.pp_double[i][j]; + } + + /* + * Determine number of near centers and maximum radius of near centers + */ + nearcenterscnt.ptr.p_int[i] = kdtreequeryrnn(centerstree, &xcx, r->ptr.p_double[i]*rbf_rbfnearradius, ae_true, _state); + kdtreequeryresultstags(centerstree, ¢erstags, _state); + maxrad = 0; + for(j=0; j<=nearcenterscnt.ptr.p_int[i]-1; j++) + { + maxrad = ae_maxreal(maxrad, ae_fabs(r->ptr.p_double[centerstags.ptr.p_int[j]], _state), _state); + } + + /* + * 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.ptr.p_int[i] = kdtreequeryrnn(pointstree, &xcx, 0.1*r->ptr.p_double[i], ae_true, _state); + nearpointscnt.ptr.p_int[i] = kdtreequeryrnn(pointstree, &xcx, (r->ptr.p_double[i]+maxrad)*rbf_rbfnearradius, ae_true, _state)-skipnearpointscnt.ptr.p_int[i]; + ae_assert(nearpointscnt.ptr.p_int[i]>=0, "BuildRBFModelLSQR: internal error", _state); + + /* + * Determine number of far points + */ + farpointscnt.ptr.p_int[i] = kdtreequeryrnn(pointstree, &xcx, ae_maxreal(r->ptr.p_double[i]*rbf_rbfnearradius+maxrad*rbf_rbffarradius, r->ptr.p_double[i]*rbf_rbffarradius, _state), ae_true, _state); + + /* + * calculate sum and max, make some basic checks + */ + ae_assert(nearcenterscnt.ptr.p_int[i]>0, "BuildRBFModelLSQR: internal error", _state); + maxnearcenterscnt = ae_maxint(maxnearcenterscnt, nearcenterscnt.ptr.p_int[i], _state); + maxnearpointscnt = ae_maxint(maxnearpointscnt, nearpointscnt.ptr.p_int[i], _state); + maxfarpointscnt = ae_maxint(maxfarpointscnt, farpointscnt.ptr.p_int[i], _state); + sumnearcenterscnt = sumnearcenterscnt+nearcenterscnt.ptr.p_int[i]; + sumnearpointscnt = sumnearpointscnt+nearpointscnt.ptr.p_int[i]; + sumfarpointscnt = sumfarpointscnt+farpointscnt.ptr.p_int[i]; + } + *snnz = sumnearcenterscnt; + *gnnz = sumfarpointscnt; + ae_assert(maxnearcenterscnt>0, "BuildRBFModelLSQR: internal error", _state); + + /* + * 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. + */ + ae_matrix_set_length(&a, maxnearpointscnt+maxnearcenterscnt, maxnearcenterscnt, _state); + ae_vector_set_length(&tmpy, maxnearpointscnt+maxnearcenterscnt, _state); + ae_vector_set_length(&g, maxnearcenterscnt, _state); + ae_vector_set_length(&c, maxnearcenterscnt, _state); + ae_matrix_set_length(&nearcenters, maxnearcenterscnt, rbf_mxnx, _state); + ae_matrix_set_length(&nearpoints, ae_maxint(maxnearpointscnt, 1, _state), rbf_mxnx, _state); + ae_matrix_set_length(&farpoints, ae_maxint(maxfarpointscnt, 1, _state), rbf_mxnx, _state); + + /* + * fill matrix SpG + */ + sparsecreate(n, nc, *gnnz, &spg, _state); + sparsecreate(nc, nc, *snnz, &sps, _state); + for(i=0; i<=nc-1; i++) + { + centerscnt = nearcenterscnt.ptr.p_int[i]; + + /* + * main center + */ + for(j=0; j<=rbf_mxnx-1; j++) + { + xcx.ptr.p_double[j] = xc->ptr.pp_double[i][j]; + } + + /* + * center's tree + */ + tmpi = kdtreequeryknn(centerstree, &xcx, centerscnt, ae_true, _state); + ae_assert(tmpi==centerscnt, "BuildRBFModelLSQR: internal error", _state); + kdtreequeryresultsx(centerstree, &cx, _state); + kdtreequeryresultstags(centerstree, ¢erstags, _state); + + /* + * point's tree + */ + mrad = 0; + for(j=0; j<=centerscnt-1; j++) + { + mrad = ae_maxreal(mrad, r->ptr.p_double[centerstags.ptr.p_int[j]], _state); + } + + /* + * we need to be sure that 'CTree' contains + * at least one side center + */ + sparseset(&sps, i, i, 1, _state); + c.ptr.p_double[0] = 1.0; + for(j=1; j<=centerscnt-1; j++) + { + c.ptr.p_double[j] = 0.0; + } + if( centerscnt>1&&nearpointscnt.ptr.p_int[i]>0 ) + { + + /* + * first KDTree request for points + */ + pointscnt = nearpointscnt.ptr.p_int[i]; + tmpi = kdtreequeryknn(pointstree, &xcx, skipnearpointscnt.ptr.p_int[i]+nearpointscnt.ptr.p_int[i], ae_true, _state); + ae_assert(tmpi==skipnearpointscnt.ptr.p_int[i]+nearpointscnt.ptr.p_int[i], "BuildRBFModelLSQR: internal error", _state); + kdtreequeryresultsx(pointstree, &xx, _state); + sind = skipnearpointscnt.ptr.p_int[i]; + for(j=0; j<=pointscnt-1; j++) + { + vx = xx.ptr.pp_double[sind+j][0]; + vy = xx.ptr.pp_double[sind+j][1]; + vz = xx.ptr.pp_double[sind+j][2]; + for(k=0; k<=centerscnt-1; k++) + { + vr = 0.0; + vv = vx-cx.ptr.pp_double[k][0]; + vr = vr+vv*vv; + vv = vy-cx.ptr.pp_double[k][1]; + vr = vr+vv*vv; + vv = vz-cx.ptr.pp_double[k][2]; + vr = vr+vv*vv; + vv = r->ptr.p_double[centerstags.ptr.p_int[k]]; + a.ptr.pp_double[j][k] = ae_exp(-vr/(vv*vv), _state); + } + } + for(j=0; j<=centerscnt-1; j++) + { + g.ptr.p_double[j] = ae_exp(-(ae_sqr(xcx.ptr.p_double[0]-cx.ptr.pp_double[j][0], _state)+ae_sqr(xcx.ptr.p_double[1]-cx.ptr.pp_double[j][1], _state)+ae_sqr(xcx.ptr.p_double[2]-cx.ptr.pp_double[j][2], _state))/ae_sqr(r->ptr.p_double[centerstags.ptr.p_int[j]], _state), _state); + } + + /* + * calculate the problem + */ + gnorm2 = ae_v_dotproduct(&g.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1)); + for(j=0; j<=pointscnt-1; j++) + { + vv = ae_v_dotproduct(&a.ptr.pp_double[j][0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1)); + vv = vv/gnorm2; + tmpy.ptr.p_double[j] = -vv; + ae_v_subd(&a.ptr.pp_double[j][0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1), vv); + } + for(j=pointscnt; j<=pointscnt+centerscnt-1; j++) + { + for(k=0; k<=centerscnt-1; k++) + { + a.ptr.pp_double[j][k] = 0.0; + } + a.ptr.pp_double[j][j-pointscnt] = 1.0E-6; + tmpy.ptr.p_double[j] = 0.0; + } + fblssolvels(&a, &tmpy, pointscnt+centerscnt, centerscnt, &tmp0, &tmp1, &tmp2, _state); + ae_v_move(&c.ptr.p_double[0], 1, &tmpy.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1)); + vv = ae_v_dotproduct(&g.ptr.p_double[0], 1, &c.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1)); + vv = vv/gnorm2; + ae_v_subd(&c.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1), vv); + vv = 1/gnorm2; + ae_v_addd(&c.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,centerscnt-1), vv); + for(j=0; j<=centerscnt-1; j++) + { + sparseset(&sps, i, centerstags.ptr.p_int[j], c.ptr.p_double[j], _state); + } + } + + /* + * second KDTree request for points + */ + pointscnt = farpointscnt.ptr.p_int[i]; + tmpi = kdtreequeryknn(pointstree, &xcx, pointscnt, ae_true, _state); + ae_assert(tmpi==pointscnt, "BuildRBFModelLSQR: internal error", _state); + kdtreequeryresultsx(pointstree, &xx, _state); + kdtreequeryresultstags(pointstree, &pointstags, _state); + + /* + *fill SpG matrix + */ + for(j=0; j<=pointscnt-1; j++) + { + fx = 0; + vx = xx.ptr.pp_double[j][0]; + vy = xx.ptr.pp_double[j][1]; + vz = xx.ptr.pp_double[j][2]; + for(k=0; k<=centerscnt-1; k++) + { + vr = 0.0; + vv = vx-cx.ptr.pp_double[k][0]; + vr = vr+vv*vv; + vv = vy-cx.ptr.pp_double[k][1]; + vr = vr+vv*vv; + vv = vz-cx.ptr.pp_double[k][2]; + vr = vr+vv*vv; + vv = r->ptr.p_double[centerstags.ptr.p_int[k]]; + vv = vv*vv; + fx = fx+c.ptr.p_double[k]*ae_exp(-vr/vv, _state); + } + sparseset(&spg, pointstags.ptr.p_int[j], i, fx, _state); + } + } + sparseconverttocrs(&spg, _state); + sparseconverttocrs(&sps, _state); + + /* + * solve by LSQR method + */ + ae_vector_set_length(&tmpy, n, _state); + ae_vector_set_length(&tc, nc, _state); + ae_matrix_set_length(w, nc, ny, _state); + linlsqrcreate(n, nc, &state, _state); + linlsqrsetcond(&state, epsort, epserr, maxits, _state); + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=n-1; j++) + { + tmpy.ptr.p_double[j] = y->ptr.pp_double[j][i]; + } + linlsqrsolvesparse(&state, &spg, &tmpy, _state); + linlsqrresults(&state, &c, &lsqrrep, _state); + if( lsqrrep.terminationtype<=0 ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + sparsemtv(&sps, &c, &tc, _state); + for(j=0; j<=nc-1; j++) + { + w->ptr.pp_double[j][i] = tc.ptr.p_double[j]; + } + *iterationscount = *iterationscount+lsqrrep.iterationscount; + *nmv = *nmv+lsqrrep.nmv; + } + *info = 1; + ae_frame_leave(_state); +} + + +static void rbf_buildrbfmlayersmodellsqr(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + /* Real */ ae_matrix* xc, + double rval, + /* Real */ ae_vector* r, + ae_int_t n, + ae_int_t* nc, + ae_int_t ny, + ae_int_t nlayers, + kdtree* centerstree, + double epsort, + double epserr, + ae_int_t maxits, + double lambdav, + ae_int_t* annz, + /* Real */ ae_matrix* w, + ae_int_t* info, + ae_int_t* iterationscount, + ae_int_t* nmv, + ae_state *_state) +{ + ae_frame _frame_block; + linlsqrstate state; + linlsqrreport lsqrrep; + sparsematrix spa; + double anorm; + ae_vector omega; + ae_vector xx; + ae_vector tmpy; + ae_matrix cx; + double yval; + ae_int_t nec; + ae_vector centerstags; + ae_int_t layer; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + double rmaxbefore; + double rmaxafter; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(xc); + ae_vector_clear(r); + *nc = 0; + *annz = 0; + ae_matrix_clear(w); + *info = 0; + *iterationscount = 0; + *nmv = 0; + _linlsqrstate_init(&state, _state, ae_true); + _linlsqrreport_init(&lsqrrep, _state, ae_true); + _sparsematrix_init(&spa, _state, ae_true); + ae_vector_init(&omega, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpy, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&cx, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(¢erstags, 0, DT_INT, _state, ae_true); + + ae_assert(nlayers>=0, "BuildRBFMLayersModelLSQR: invalid argument(NLayers<0)", _state); + ae_assert(n>=0, "BuildRBFMLayersModelLSQR: invalid argument(N<0)", _state); + ae_assert(rbf_mxnx>0&&rbf_mxnx<=3, "BuildRBFMLayersModelLSQR: internal error(invalid global const MxNX: either MxNX<=0 or MxNX>3)", _state); + *annz = 0; + if( n==0||nlayers==0 ) + { + *info = 1; + *iterationscount = 0; + *nmv = 0; + ae_frame_leave(_state); + return; + } + *nc = n*nlayers; + ae_vector_set_length(&xx, rbf_mxnx, _state); + ae_vector_set_length(¢erstags, n, _state); + ae_matrix_set_length(xc, *nc, rbf_mxnx, _state); + ae_vector_set_length(r, *nc, _state); + for(i=0; i<=*nc-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xc->ptr.pp_double[i][j] = x->ptr.pp_double[i%n][j]; + } + } + for(i=0; i<=*nc-1; i++) + { + r->ptr.p_double[i] = rval/ae_pow(2, i/n, _state); + } + for(i=0; i<=n-1; i++) + { + centerstags.ptr.p_int[i] = i; + } + kdtreebuildtagged(xc, ¢erstags, n, rbf_mxnx, 0, 2, centerstree, _state); + ae_vector_set_length(&omega, n, _state); + ae_vector_set_length(&tmpy, n, _state); + ae_matrix_set_length(w, *nc, ny, _state); + *info = -1; + *iterationscount = 0; + *nmv = 0; + linlsqrcreate(n, n, &state, _state); + linlsqrsetcond(&state, epsort, epserr, maxits, _state); + linlsqrsetlambdai(&state, 1.0E-6, _state); + + /* + * calculate number of non-zero elements for sparse matrix + */ + for(i=0; i<=n-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xx.ptr.p_double[j] = x->ptr.pp_double[i][j]; + } + *annz = *annz+kdtreequeryrnn(centerstree, &xx, r->ptr.p_double[0]*rbf_rbfmlradius, ae_true, _state); + } + for(layer=0; layer<=nlayers-1; layer++) + { + + /* + * Fill sparse matrix, calculate norm(A) + */ + anorm = 0.0; + sparsecreate(n, n, *annz, &spa, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=rbf_mxnx-1; j++) + { + xx.ptr.p_double[j] = x->ptr.pp_double[i][j]; + } + nec = kdtreequeryrnn(centerstree, &xx, r->ptr.p_double[layer*n]*rbf_rbfmlradius, ae_true, _state); + kdtreequeryresultsx(centerstree, &cx, _state); + kdtreequeryresultstags(centerstree, ¢erstags, _state); + for(j=0; j<=nec-1; j++) + { + v = ae_exp(-(ae_sqr(xx.ptr.p_double[0]-cx.ptr.pp_double[j][0], _state)+ae_sqr(xx.ptr.p_double[1]-cx.ptr.pp_double[j][1], _state)+ae_sqr(xx.ptr.p_double[2]-cx.ptr.pp_double[j][2], _state))/ae_sqr(r->ptr.p_double[layer*n+centerstags.ptr.p_int[j]], _state), _state); + sparseset(&spa, i, centerstags.ptr.p_int[j], v, _state); + anorm = anorm+ae_sqr(v, _state); + } + } + anorm = ae_sqrt(anorm, _state); + sparseconverttocrs(&spa, _state); + + /* + * 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-1; j++) + { + for(i=0; i<=ny-1; i++) + { + rmaxbefore = ae_maxreal(rmaxbefore, ae_fabs(y->ptr.pp_double[j][i], _state), _state); + } + } + + /* + * Process NY dimensions of the target function + */ + for(i=0; i<=ny-1; i++) + { + for(j=0; j<=n-1; j++) + { + tmpy.ptr.p_double[j] = y->ptr.pp_double[j][i]; + } + + /* + * calculate Omega for current layer + */ + linlsqrsetlambdai(&state, lambdav*anorm/n, _state); + linlsqrsolvesparse(&state, &spa, &tmpy, _state); + linlsqrresults(&state, &omega, &lsqrrep, _state); + if( lsqrrep.terminationtype<=0 ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + + /* + * calculate error for current layer + */ + for(j=0; j<=n-1; j++) + { + yval = 0; + for(k=0; k<=rbf_mxnx-1; k++) + { + xx.ptr.p_double[k] = x->ptr.pp_double[j][k]; + } + nec = kdtreequeryrnn(centerstree, &xx, r->ptr.p_double[layer*n]*rbf_rbffarradius, ae_true, _state); + kdtreequeryresultsx(centerstree, &cx, _state); + kdtreequeryresultstags(centerstree, ¢erstags, _state); + for(k=0; k<=nec-1; k++) + { + yval = yval+omega.ptr.p_double[centerstags.ptr.p_int[k]]*ae_exp(-(ae_sqr(xx.ptr.p_double[0]-cx.ptr.pp_double[k][0], _state)+ae_sqr(xx.ptr.p_double[1]-cx.ptr.pp_double[k][1], _state)+ae_sqr(xx.ptr.p_double[2]-cx.ptr.pp_double[k][2], _state))/ae_sqr(r->ptr.p_double[layer*n+centerstags.ptr.p_int[k]], _state), _state); + } + y->ptr.pp_double[j][i] = y->ptr.pp_double[j][i]-yval; + } + + /* + * write Omega in out parameter W + */ + for(j=0; j<=n-1; j++) + { + w->ptr.pp_double[layer*n+j][i] = omega.ptr.p_double[j]; + } + *iterationscount = *iterationscount+lsqrrep.iterationscount; + *nmv = *nmv+lsqrrep.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-1; j++) + { + for(i=0; i<=ny-1; i++) + { + rmaxafter = ae_maxreal(rmaxafter, ae_fabs(y->ptr.pp_double[j][i], _state), _state); + } + } + } + *info = 1; + ae_frame_leave(_state); +} + + +ae_bool _rbfmodel_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + rbfmodel *p = (rbfmodel*)_p; + ae_touch_ptr((void*)p); + if( !_kdtree_init(&p->tree, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->xc, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->wr, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->v, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->x, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->y, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->calcbufxcx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->calcbufx, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->calcbuftags, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _rbfmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + rbfmodel *dst = (rbfmodel*)_dst; + rbfmodel *src = (rbfmodel*)_src; + dst->ny = src->ny; + dst->nx = src->nx; + dst->nc = src->nc; + dst->nl = src->nl; + if( !_kdtree_init_copy(&dst->tree, &src->tree, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->xc, &src->xc, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->wr, &src->wr, _state, make_automatic) ) + return ae_false; + dst->rmax = src->rmax; + if( !ae_matrix_init_copy(&dst->v, &src->v, _state, make_automatic) ) + return ae_false; + dst->gridtype = src->gridtype; + dst->fixrad = src->fixrad; + dst->lambdav = src->lambdav; + dst->radvalue = src->radvalue; + dst->radzvalue = src->radzvalue; + dst->nlayers = src->nlayers; + dst->aterm = src->aterm; + dst->algorithmtype = src->algorithmtype; + dst->epsort = src->epsort; + dst->epserr = src->epserr; + dst->maxits = src->maxits; + dst->h = src->h; + dst->n = src->n; + if( !ae_matrix_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->calcbufxcx, &src->calcbufxcx, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->calcbufx, &src->calcbufx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->calcbuftags, &src->calcbuftags, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _rbfmodel_clear(void* _p) +{ + rbfmodel *p = (rbfmodel*)_p; + ae_touch_ptr((void*)p); + _kdtree_clear(&p->tree); + ae_matrix_clear(&p->xc); + ae_matrix_clear(&p->wr); + ae_matrix_clear(&p->v); + ae_matrix_clear(&p->x); + ae_matrix_clear(&p->y); + ae_vector_clear(&p->calcbufxcx); + ae_matrix_clear(&p->calcbufx); + ae_vector_clear(&p->calcbuftags); +} + + +void _rbfmodel_destroy(void* _p) +{ + rbfmodel *p = (rbfmodel*)_p; + ae_touch_ptr((void*)p); + _kdtree_destroy(&p->tree); + ae_matrix_destroy(&p->xc); + ae_matrix_destroy(&p->wr); + ae_matrix_destroy(&p->v); + ae_matrix_destroy(&p->x); + ae_matrix_destroy(&p->y); + ae_vector_destroy(&p->calcbufxcx); + ae_matrix_destroy(&p->calcbufx); + ae_vector_destroy(&p->calcbuftags); +} + + +ae_bool _rbfreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + rbfreport *p = (rbfreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _rbfreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + rbfreport *dst = (rbfreport*)_dst; + rbfreport *src = (rbfreport*)_src; + dst->arows = src->arows; + dst->acols = src->acols; + dst->annz = src->annz; + dst->iterationscount = src->iterationscount; + dst->nmv = src->nmv; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _rbfreport_clear(void* _p) +{ + rbfreport *p = (rbfreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _rbfreport_destroy(void* _p) +{ + rbfreport *p = (rbfreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +double spline2dcalc(spline2dinterpolant* c, + double x, + double y, + ae_state *_state) +{ + double v; + double vx; + double vy; + double vxy; + double result; + + + ae_assert(c->stype==-1||c->stype==-3, "Spline2DCalc: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(x, _state)&&ae_isfinite(y, _state), "Spline2DCalc: X or Y contains NaN or Infinite value", _state); + if( c->d!=1 ) + { + result = 0; + return result; + } + spline2ddiff(c, x, y, &v, &vx, &vy, &vxy, _state); + result = v; + 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 + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2ddiff(spline2dinterpolant* c, + double x, + double y, + double* f, + double* fx, + double* fy, + double* fxy, + ae_state *_state) +{ + double t; + double dt; + double u; + double du; + ae_int_t ix; + ae_int_t iy; + ae_int_t l; + ae_int_t r; + ae_int_t h; + ae_int_t s1; + ae_int_t s2; + ae_int_t s3; + ae_int_t s4; + ae_int_t sfx; + ae_int_t sfy; + ae_int_t sfxy; + double y1; + double y2; + double y3; + double y4; + double v; + double t0; + double t1; + double t2; + double t3; + double u0; + double u1; + double u2; + double u3; + + *f = 0; + *fx = 0; + *fy = 0; + *fxy = 0; + + ae_assert(c->stype==-1||c->stype==-3, "Spline2DDiff: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(x, _state)&&ae_isfinite(y, _state), "Spline2DDiff: X or Y contains NaN or Infinite value", _state); + + /* + * Prepare F, dF/dX, dF/dY, d2F/dXdY + */ + *f = 0; + *fx = 0; + *fy = 0; + *fxy = 0; + if( c->d!=1 ) + { + return; + } + + /* + * Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included) + */ + l = 0; + r = c->n-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->x.ptr.p_double[h],x) ) + { + r = h; + } + else + { + l = h; + } + } + t = (x-c->x.ptr.p_double[l])/(c->x.ptr.p_double[l+1]-c->x.ptr.p_double[l]); + dt = 1.0/(c->x.ptr.p_double[l+1]-c->x.ptr.p_double[l]); + ix = l; + + /* + * Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included) + */ + l = 0; + r = c->m-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->y.ptr.p_double[h],y) ) + { + r = h; + } + else + { + l = h; + } + } + u = (y-c->y.ptr.p_double[l])/(c->y.ptr.p_double[l+1]-c->y.ptr.p_double[l]); + du = 1.0/(c->y.ptr.p_double[l+1]-c->y.ptr.p_double[l]); + iy = l; + + /* + * Bilinear interpolation + */ + if( c->stype==-1 ) + { + y1 = c->f.ptr.p_double[c->n*iy+ix]; + y2 = c->f.ptr.p_double[c->n*iy+(ix+1)]; + y3 = c->f.ptr.p_double[c->n*(iy+1)+(ix+1)]; + y4 = c->f.ptr.p_double[c->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; + return; + } + + /* + * Bicubic interpolation + */ + if( c->stype==-3 ) + { + + /* + * Prepare info + */ + t0 = 1; + t1 = t; + t2 = ae_sqr(t, _state); + t3 = t*t2; + u0 = 1; + u1 = u; + u2 = ae_sqr(u, _state); + u3 = u*u2; + sfx = c->n*c->m; + sfy = 2*c->n*c->m; + sfxy = 3*c->n*c->m; + s1 = c->n*iy+ix; + s2 = c->n*iy+(ix+1); + s3 = c->n*(iy+1)+(ix+1); + s4 = c->n*(iy+1)+ix; + + /* + * Calculate + */ + v = c->f.ptr.p_double[s1]; + *f = *f+v*t0*u0; + v = c->f.ptr.p_double[sfy+s1]/du; + *f = *f+v*t0*u1; + *fy = *fy+v*t0*u0*du; + v = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s4]-2*c->f.ptr.p_double[sfy+s1]/du-c->f.ptr.p_double[sfy+s4]/du; + *f = *f+v*t0*u2; + *fy = *fy+2*v*t0*u1*du; + v = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s4]+c->f.ptr.p_double[sfy+s1]/du+c->f.ptr.p_double[sfy+s4]/du; + *f = *f+v*t0*u3; + *fy = *fy+3*v*t0*u2*du; + v = c->f.ptr.p_double[sfx+s1]/dt; + *f = *f+v*t1*u0; + *fx = *fx+v*t0*u0*dt; + v = c->f.ptr.p_double[sfxy+s1]/(dt*du); + *f = *f+v*t1*u1; + *fx = *fx+v*t0*u1*dt; + *fy = *fy+v*t1*u0*du; + *fxy = *fxy+v*t0*u0*dt*du; + v = -3*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t1*u2; + *fx = *fx+v*t0*u2*dt; + *fy = *fy+2*v*t1*u1*du; + *fxy = *fxy+2*v*t0*u1*dt*du; + v = 2*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t1*u3; + *fx = *fx+v*t0*u3*dt; + *fy = *fy+3*v*t1*u2*du; + *fxy = *fxy+3*v*t0*u2*dt*du; + v = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s2]-2*c->f.ptr.p_double[sfx+s1]/dt-c->f.ptr.p_double[sfx+s2]/dt; + *f = *f+v*t2*u0; + *fx = *fx+2*v*t1*u0*dt; + v = -3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du); + *f = *f+v*t2*u1; + *fx = *fx+2*v*t1*u1*dt; + *fy = *fy+v*t2*u0*du; + *fxy = *fxy+2*v*t1*u0*dt*du; + v = 9*c->f.ptr.p_double[s1]-9*c->f.ptr.p_double[s2]+9*c->f.ptr.p_double[s3]-9*c->f.ptr.p_double[s4]+6*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s2]/dt-3*c->f.ptr.p_double[sfx+s3]/dt-6*c->f.ptr.p_double[sfx+s4]/dt+6*c->f.ptr.p_double[sfy+s1]/du-6*c->f.ptr.p_double[sfy+s2]/du-3*c->f.ptr.p_double[sfy+s3]/du+3*c->f.ptr.p_double[sfy+s4]/du+4*c->f.ptr.p_double[sfxy+s1]/(dt*du)+2*c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t2*u2; + *fx = *fx+2*v*t1*u2*dt; + *fy = *fy+2*v*t2*u1*du; + *fxy = *fxy+4*v*t1*u1*dt*du; + v = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-4*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s2]/dt+2*c->f.ptr.p_double[sfx+s3]/dt+4*c->f.ptr.p_double[sfx+s4]/dt-3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du+3*c->f.ptr.p_double[sfy+s3]/du-3*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t2*u3; + *fx = *fx+2*v*t1*u3*dt; + *fy = *fy+3*v*t2*u2*du; + *fxy = *fxy+6*v*t1*u2*dt*du; + v = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s2]+c->f.ptr.p_double[sfx+s1]/dt+c->f.ptr.p_double[sfx+s2]/dt; + *f = *f+v*t3*u0; + *fx = *fx+3*v*t2*u0*dt; + v = 2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du); + *f = *f+v*t3*u1; + *fx = *fx+3*v*t2*u1*dt; + *fy = *fy+v*t3*u0*du; + *fxy = *fxy+3*v*t2*u0*dt*du; + v = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-3*c->f.ptr.p_double[sfx+s1]/dt-3*c->f.ptr.p_double[sfx+s2]/dt+3*c->f.ptr.p_double[sfx+s3]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-4*c->f.ptr.p_double[sfy+s1]/du+4*c->f.ptr.p_double[sfy+s2]/du+2*c->f.ptr.p_double[sfy+s3]/du-2*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-2*c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t3*u2; + *fx = *fx+3*v*t2*u2*dt; + *fy = *fy+2*v*t3*u1*du; + *fxy = *fxy+6*v*t2*u1*dt*du; + v = 4*c->f.ptr.p_double[s1]-4*c->f.ptr.p_double[s2]+4*c->f.ptr.p_double[s3]-4*c->f.ptr.p_double[s4]+2*c->f.ptr.p_double[sfx+s1]/dt+2*c->f.ptr.p_double[sfx+s2]/dt-2*c->f.ptr.p_double[sfx+s3]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfy+s3]/du+2*c->f.ptr.p_double[sfy+s4]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + *f = *f+v*t3*u3; + *fx = *fx+3*v*t2*u3*dt; + *fy = *fy+3*v*t3*u2*du; + *fxy = *fxy+9*v*t2*u2*dt*du; + return; + } +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransxy(spline2dinterpolant* c, + double ax, + double bx, + double ay, + double by, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_vector f; + ae_vector v; + ae_int_t i; + ae_int_t j; + ae_int_t k; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&f, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + + ae_assert(c->stype==-3||c->stype==-1, "Spline2DLinTransXY: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(ax, _state), "Spline2DLinTransXY: AX is infinite or NaN", _state); + ae_assert(ae_isfinite(bx, _state), "Spline2DLinTransXY: BX is infinite or NaN", _state); + ae_assert(ae_isfinite(ay, _state), "Spline2DLinTransXY: AY is infinite or NaN", _state); + ae_assert(ae_isfinite(by, _state), "Spline2DLinTransXY: BY is infinite or NaN", _state); + ae_vector_set_length(&x, c->n, _state); + ae_vector_set_length(&y, c->m, _state); + ae_vector_set_length(&f, c->m*c->n*c->d, _state); + for(j=0; j<=c->n-1; j++) + { + x.ptr.p_double[j] = c->x.ptr.p_double[j]; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = c->y.ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + for(k=0; k<=c->d-1; k++) + { + f.ptr.p_double[c->d*(i*c->n+j)+k] = c->f.ptr.p_double[c->d*(i*c->n+j)+k]; + } + } + } + + /* + * Handle different combinations of AX/AY + */ + if( ae_fp_eq(ax,0)&&ae_fp_neq(ay,0) ) + { + for(i=0; i<=c->m-1; i++) + { + spline2dcalcvbuf(c, bx, y.ptr.p_double[i], &v, _state); + y.ptr.p_double[i] = (y.ptr.p_double[i]-by)/ay; + for(j=0; j<=c->n-1; j++) + { + for(k=0; k<=c->d-1; k++) + { + f.ptr.p_double[c->d*(i*c->n+j)+k] = v.ptr.p_double[k]; + } + } + } + } + if( ae_fp_neq(ax,0)&&ae_fp_eq(ay,0) ) + { + for(j=0; j<=c->n-1; j++) + { + spline2dcalcvbuf(c, x.ptr.p_double[j], by, &v, _state); + x.ptr.p_double[j] = (x.ptr.p_double[j]-bx)/ax; + for(i=0; i<=c->m-1; i++) + { + for(k=0; k<=c->d-1; k++) + { + f.ptr.p_double[c->d*(i*c->n+j)+k] = v.ptr.p_double[k]; + } + } + } + } + if( ae_fp_neq(ax,0)&&ae_fp_neq(ay,0) ) + { + for(j=0; j<=c->n-1; j++) + { + x.ptr.p_double[j] = (x.ptr.p_double[j]-bx)/ax; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = (y.ptr.p_double[i]-by)/ay; + } + } + if( ae_fp_eq(ax,0)&&ae_fp_eq(ay,0) ) + { + spline2dcalcvbuf(c, bx, by, &v, _state); + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + for(k=0; k<=c->d-1; k++) + { + f.ptr.p_double[c->d*(i*c->n+j)+k] = v.ptr.p_double[k]; + } + } + } + } + + /* + * Rebuild spline + */ + if( c->stype==-3 ) + { + spline2dbuildbicubicv(&x, c->n, &y, c->m, &f, c->d, c, _state); + } + if( c->stype==-1 ) + { + spline2dbuildbilinearv(&x, c->n, &y, c->m, &f, c->d, c, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransf(spline2dinterpolant* c, + double a, + double b, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_vector f; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&f, 0, DT_REAL, _state, ae_true); + + ae_assert(c->stype==-3||c->stype==-1, "Spline2DLinTransF: incorrect C (incorrect parameter C.SType)", _state); + ae_vector_set_length(&x, c->n, _state); + ae_vector_set_length(&y, c->m, _state); + ae_vector_set_length(&f, c->m*c->n*c->d, _state); + for(j=0; j<=c->n-1; j++) + { + x.ptr.p_double[j] = c->x.ptr.p_double[j]; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = c->y.ptr.p_double[i]; + } + for(i=0; i<=c->m*c->n*c->d-1; i++) + { + f.ptr.p_double[i] = a*c->f.ptr.p_double[i]+b; + } + if( c->stype==-3 ) + { + spline2dbuildbicubicv(&x, c->n, &y, c->m, &f, c->d, c, _state); + } + if( c->stype==-1 ) + { + spline2dbuildbilinearv(&x, c->n, &y, c->m, &f, c->d, c, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine makes the copy of the spline model. + +Input parameters: + C - spline interpolant + +Output parameters: + CC - spline copy + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dcopy(spline2dinterpolant* c, + spline2dinterpolant* cc, + ae_state *_state) +{ + ae_int_t tblsize; + + _spline2dinterpolant_clear(cc); + + ae_assert(c->k==1||c->k==3, "Spline2DCopy: incorrect C (incorrect parameter C.K)", _state); + cc->k = c->k; + cc->n = c->n; + cc->m = c->m; + cc->d = c->d; + cc->stype = c->stype; + tblsize = -1; + if( c->stype==-3 ) + { + tblsize = 4*c->n*c->m*c->d; + } + if( c->stype==-1 ) + { + tblsize = c->n*c->m*c->d; + } + ae_assert(tblsize>0, "Spline2DCopy: internal error", _state); + ae_vector_set_length(&cc->x, cc->n, _state); + ae_vector_set_length(&cc->y, cc->m, _state); + ae_vector_set_length(&cc->f, tblsize, _state); + ae_v_move(&cc->x.ptr.p_double[0], 1, &c->x.ptr.p_double[0], 1, ae_v_len(0,cc->n-1)); + ae_v_move(&cc->y.ptr.p_double[0], 1, &c->y.ptr.p_double[0], 1, ae_v_len(0,cc->m-1)); + ae_v_move(&cc->f.ptr.p_double[0], 1, &c->f.ptr.p_double[0], 1, ae_v_len(0,tblsize-1)); +} + + +/************************************************************************* +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] + + -- ALGLIB routine -- + 15 May, 2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebicubic(/* Real */ ae_matrix* a, + ae_int_t oldheight, + ae_int_t oldwidth, + /* Real */ ae_matrix* b, + ae_int_t newheight, + ae_int_t newwidth, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix buf; + ae_vector x; + ae_vector y; + spline1dinterpolant c; + ae_int_t mw; + ae_int_t mh; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(b); + ae_matrix_init(&buf, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + _spline1dinterpolant_init(&c, _state, ae_true); + + ae_assert(oldwidth>1&&oldheight>1, "Spline2DResampleBicubic: width/height less than 1", _state); + ae_assert(newwidth>1&&newheight>1, "Spline2DResampleBicubic: width/height less than 1", _state); + + /* + * Prepare + */ + mw = ae_maxint(oldwidth, newwidth, _state); + mh = ae_maxint(oldheight, newheight, _state); + ae_matrix_set_length(b, newheight, newwidth, _state); + ae_matrix_set_length(&buf, oldheight, newwidth, _state); + ae_vector_set_length(&x, ae_maxint(mw, mh, _state), _state); + ae_vector_set_length(&y, ae_maxint(mw, mh, _state), _state); + + /* + * Horizontal interpolation + */ + for(i=0; i<=oldheight-1; i++) + { + + /* + * Fill X, Y + */ + for(j=0; j<=oldwidth-1; j++) + { + x.ptr.p_double[j] = (double)j/(double)(oldwidth-1); + y.ptr.p_double[j] = a->ptr.pp_double[i][j]; + } + + /* + * Interpolate and place result into temporary matrix + */ + spline1dbuildcubic(&x, &y, oldwidth, 0, 0.0, 0, 0.0, &c, _state); + for(j=0; j<=newwidth-1; j++) + { + buf.ptr.pp_double[i][j] = spline1dcalc(&c, (double)j/(double)(newwidth-1), _state); + } + } + + /* + * Vertical interpolation + */ + for(j=0; j<=newwidth-1; j++) + { + + /* + * Fill X, Y + */ + for(i=0; i<=oldheight-1; i++) + { + x.ptr.p_double[i] = (double)i/(double)(oldheight-1); + y.ptr.p_double[i] = buf.ptr.pp_double[i][j]; + } + + /* + * Interpolate and place result into B + */ + spline1dbuildcubic(&x, &y, oldheight, 0, 0.0, 0, 0.0, &c, _state); + for(i=0; i<=newheight-1; i++) + { + b->ptr.pp_double[i][j] = spline1dcalc(&c, (double)i/(double)(newheight-1), _state); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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] + + -- ALGLIB routine -- + 09.07.2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebilinear(/* Real */ ae_matrix* a, + ae_int_t oldheight, + ae_int_t oldwidth, + /* Real */ ae_matrix* b, + ae_int_t newheight, + ae_int_t newwidth, + ae_state *_state) +{ + ae_int_t l; + ae_int_t c; + double t; + double u; + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(b); + + ae_assert(oldwidth>1&&oldheight>1, "Spline2DResampleBilinear: width/height less than 1", _state); + ae_assert(newwidth>1&&newheight>1, "Spline2DResampleBilinear: width/height less than 1", _state); + ae_matrix_set_length(b, newheight, newwidth, _state); + for(i=0; i<=newheight-1; i++) + { + for(j=0; j<=newwidth-1; j++) + { + l = i*(oldheight-1)/(newheight-1); + if( l==oldheight-1 ) + { + l = oldheight-2; + } + u = (double)i/(double)(newheight-1)*(oldheight-1)-l; + c = j*(oldwidth-1)/(newwidth-1); + if( c==oldwidth-1 ) + { + c = oldwidth-2; + } + t = (double)(j*(oldwidth-1))/(double)(newwidth-1)-c; + b->ptr.pp_double[i][j] = (1-t)*(1-u)*a->ptr.pp_double[l][c]+t*(1-u)*a->ptr.pp_double[l][c+1]+t*u*a->ptr.pp_double[l+1][c+1]+(1-t)*u*a->ptr.pp_double[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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinearv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* f, + ae_int_t d, + spline2dinterpolant* c, + ae_state *_state) +{ + double t; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t i0; + + _spline2dinterpolant_clear(c); + + ae_assert(n>=2, "Spline2DBuildBilinearV: N is less then 2", _state); + ae_assert(m>=2, "Spline2DBuildBilinearV: M is less then 2", _state); + ae_assert(d>=1, "Spline2DBuildBilinearV: invalid argument D (D<1)", _state); + ae_assert(x->cnt>=n&&y->cnt>=m, "Spline2DBuildBilinearV: length of X or Y is too short (Length(X/Y)cnt>=k, "Spline2DBuildBilinearV: length of F is too short (Length(F)k = 1; + c->n = n; + c->m = m; + c->d = d; + c->stype = -1; + ae_vector_set_length(&c->x, c->n, _state); + ae_vector_set_length(&c->y, c->m, _state); + ae_vector_set_length(&c->f, k, _state); + for(i=0; i<=c->n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + c->y.ptr.p_double[i] = y->ptr.p_double[i]; + } + for(i=0; i<=k-1; i++) + { + c->f.ptr.p_double[i] = f->ptr.p_double[i]; + } + + /* + * Sort points + */ + for(j=0; j<=c->n-1; j++) + { + k = j; + for(i=j+1; i<=c->n-1; i++) + { + if( ae_fp_less(c->x.ptr.p_double[i],c->x.ptr.p_double[k]) ) + { + k = i; + } + } + if( k!=j ) + { + for(i=0; i<=c->m-1; i++) + { + for(i0=0; i0<=c->d-1; i0++) + { + t = c->f.ptr.p_double[c->d*(i*c->n+j)+i0]; + c->f.ptr.p_double[c->d*(i*c->n+j)+i0] = c->f.ptr.p_double[c->d*(i*c->n+k)+i0]; + c->f.ptr.p_double[c->d*(i*c->n+k)+i0] = t; + } + } + t = c->x.ptr.p_double[j]; + c->x.ptr.p_double[j] = c->x.ptr.p_double[k]; + c->x.ptr.p_double[k] = t; + } + } + for(i=0; i<=c->m-1; i++) + { + k = i; + for(j=i+1; j<=c->m-1; j++) + { + if( ae_fp_less(c->y.ptr.p_double[j],c->y.ptr.p_double[k]) ) + { + k = j; + } + } + if( k!=i ) + { + for(j=0; j<=c->n-1; j++) + { + for(i0=0; i0<=c->d-1; i0++) + { + t = c->f.ptr.p_double[c->d*(i*c->n+j)+i0]; + c->f.ptr.p_double[c->d*(i*c->n+j)+i0] = c->f.ptr.p_double[c->d*(k*c->n+j)+i0]; + c->f.ptr.p_double[c->d*(k*c->n+j)+i0] = t; + } + } + t = c->y.ptr.p_double[i]; + c->y.ptr.p_double[i] = c->y.ptr.p_double[k]; + c->y.ptr.p_double[k] = t; + } + } +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubicv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* f, + ae_int_t d, + spline2dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _f; + ae_matrix tf; + ae_matrix dx; + ae_matrix dy; + ae_matrix dxy; + double t; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t di; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_f, f, _state, ae_true); + f = &_f; + _spline2dinterpolant_clear(c); + ae_matrix_init(&tf, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&dx, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&dy, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&dxy, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=2, "Spline2DBuildBicubicV: N is less than 2", _state); + ae_assert(m>=2, "Spline2DBuildBicubicV: M is less than 2", _state); + ae_assert(d>=1, "Spline2DBuildBicubicV: invalid argument D (D<1)", _state); + ae_assert(x->cnt>=n&&y->cnt>=m, "Spline2DBuildBicubicV: length of X or Y is too short (Length(X/Y)cnt>=k, "Spline2DBuildBicubicV: length of F is too short (Length(F)k = 3; + c->d = d; + c->n = n; + c->m = m; + c->stype = -3; + k = 4*k; + ae_vector_set_length(&c->x, c->n, _state); + ae_vector_set_length(&c->y, c->m, _state); + ae_vector_set_length(&c->f, k, _state); + ae_matrix_set_length(&tf, c->m, c->n, _state); + for(i=0; i<=c->n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + c->y.ptr.p_double[i] = y->ptr.p_double[i]; + } + + /* + * Sort points + */ + for(j=0; j<=c->n-1; j++) + { + k = j; + for(i=j+1; i<=c->n-1; i++) + { + if( ae_fp_less(c->x.ptr.p_double[i],c->x.ptr.p_double[k]) ) + { + k = i; + } + } + if( k!=j ) + { + for(i=0; i<=c->m-1; i++) + { + for(di=0; di<=c->d-1; di++) + { + t = f->ptr.p_double[c->d*(i*c->n+j)+di]; + f->ptr.p_double[c->d*(i*c->n+j)+di] = f->ptr.p_double[c->d*(i*c->n+k)+di]; + f->ptr.p_double[c->d*(i*c->n+k)+di] = t; + } + } + t = c->x.ptr.p_double[j]; + c->x.ptr.p_double[j] = c->x.ptr.p_double[k]; + c->x.ptr.p_double[k] = t; + } + } + for(i=0; i<=c->m-1; i++) + { + k = i; + for(j=i+1; j<=c->m-1; j++) + { + if( ae_fp_less(c->y.ptr.p_double[j],c->y.ptr.p_double[k]) ) + { + k = j; + } + } + if( k!=i ) + { + for(j=0; j<=c->n-1; j++) + { + for(di=0; di<=c->d-1; di++) + { + t = f->ptr.p_double[c->d*(i*c->n+j)+di]; + f->ptr.p_double[c->d*(i*c->n+j)+di] = f->ptr.p_double[c->d*(k*c->n+j)+di]; + f->ptr.p_double[c->d*(k*c->n+j)+di] = t; + } + } + t = c->y.ptr.p_double[i]; + c->y.ptr.p_double[i] = c->y.ptr.p_double[k]; + c->y.ptr.p_double[k] = t; + } + } + for(di=0; di<=c->d-1; di++) + { + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + tf.ptr.pp_double[i][j] = f->ptr.p_double[c->d*(i*c->n+j)+di]; + } + } + spline2d_bicubiccalcderivatives(&tf, &c->x, &c->y, c->m, c->n, &dx, &dy, &dxy, _state); + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + k = c->d*(i*c->n+j)+di; + c->f.ptr.p_double[k] = tf.ptr.pp_double[i][j]; + c->f.ptr.p_double[c->n*c->m*c->d+k] = dx.ptr.pp_double[i][j]; + c->f.ptr.p_double[2*c->n*c->m*c->d+k] = dy.ptr.pp_double[i][j]; + c->f.ptr.p_double[3*c->n*c->m*c->d+k] = dxy.ptr.pp_double[i][j]; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine calculates bilinear or bicubic vector-valued spline at the +given point (X,Y). + +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcvbuf(spline2dinterpolant* c, + double x, + double y, + /* Real */ ae_vector* f, + ae_state *_state) +{ + double t; + double dt; + double u; + double du; + ae_int_t ix; + ae_int_t iy; + ae_int_t l; + ae_int_t r; + ae_int_t h; + ae_int_t s1; + ae_int_t s2; + ae_int_t s3; + ae_int_t s4; + ae_int_t sfx; + ae_int_t sfy; + ae_int_t sfxy; + double y1; + double y2; + double y3; + double y4; + double v; + double t0; + double t1; + double t2; + double t3; + double u0; + double u1; + double u2; + double u3; + ae_int_t i; + + + ae_assert(c->stype==-1||c->stype==-3, "Spline2DCalcVBuf: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(x, _state)&&ae_isfinite(y, _state), "Spline2DCalcVBuf: either X=NaN/Infinite or Y=NaN/Infinite", _state); + rvectorsetlengthatleast(f, c->d, _state); + + /* + * Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included) + */ + l = 0; + r = c->n-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->x.ptr.p_double[h],x) ) + { + r = h; + } + else + { + l = h; + } + } + t = (x-c->x.ptr.p_double[l])/(c->x.ptr.p_double[l+1]-c->x.ptr.p_double[l]); + dt = 1.0/(c->x.ptr.p_double[l+1]-c->x.ptr.p_double[l]); + ix = l; + + /* + * Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included) + */ + l = 0; + r = c->m-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->y.ptr.p_double[h],y) ) + { + r = h; + } + else + { + l = h; + } + } + u = (y-c->y.ptr.p_double[l])/(c->y.ptr.p_double[l+1]-c->y.ptr.p_double[l]); + du = 1.0/(c->y.ptr.p_double[l+1]-c->y.ptr.p_double[l]); + iy = l; + + /* + * Bilinear interpolation + */ + if( c->stype==-1 ) + { + for(i=0; i<=c->d-1; i++) + { + y1 = c->f.ptr.p_double[c->d*(c->n*iy+ix)+i]; + y2 = c->f.ptr.p_double[c->d*(c->n*iy+(ix+1))+i]; + y3 = c->f.ptr.p_double[c->d*(c->n*(iy+1)+(ix+1))+i]; + y4 = c->f.ptr.p_double[c->d*(c->n*(iy+1)+ix)+i]; + f->ptr.p_double[i] = (1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4; + } + return; + } + + /* + * Bicubic interpolation + */ + if( c->stype==-3 ) + { + + /* + * Prepare info + */ + t0 = 1; + t1 = t; + t2 = ae_sqr(t, _state); + t3 = t*t2; + u0 = 1; + u1 = u; + u2 = ae_sqr(u, _state); + u3 = u*u2; + sfx = c->n*c->m*c->d; + sfy = 2*c->n*c->m*c->d; + sfxy = 3*c->n*c->m*c->d; + for(i=0; i<=c->d-1; i++) + { + + /* + * Prepare F, dF/dX, dF/dY, d2F/dXdY + */ + f->ptr.p_double[i] = 0; + s1 = c->d*(c->n*iy+ix)+i; + s2 = c->d*(c->n*iy+(ix+1))+i; + s3 = c->d*(c->n*(iy+1)+(ix+1))+i; + s4 = c->d*(c->n*(iy+1)+ix)+i; + + /* + * Calculate + */ + v = c->f.ptr.p_double[s1]; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t0*u0; + v = c->f.ptr.p_double[sfy+s1]/du; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t0*u1; + v = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s4]-2*c->f.ptr.p_double[sfy+s1]/du-c->f.ptr.p_double[sfy+s4]/du; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t0*u2; + v = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s4]+c->f.ptr.p_double[sfy+s1]/du+c->f.ptr.p_double[sfy+s4]/du; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t0*u3; + v = c->f.ptr.p_double[sfx+s1]/dt; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t1*u0; + v = c->f.ptr.p_double[sfxy+s1]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t1*u1; + v = -3*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t1*u2; + v = 2*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t1*u3; + v = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s2]-2*c->f.ptr.p_double[sfx+s1]/dt-c->f.ptr.p_double[sfx+s2]/dt; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t2*u0; + v = -3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t2*u1; + v = 9*c->f.ptr.p_double[s1]-9*c->f.ptr.p_double[s2]+9*c->f.ptr.p_double[s3]-9*c->f.ptr.p_double[s4]+6*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s2]/dt-3*c->f.ptr.p_double[sfx+s3]/dt-6*c->f.ptr.p_double[sfx+s4]/dt+6*c->f.ptr.p_double[sfy+s1]/du-6*c->f.ptr.p_double[sfy+s2]/du-3*c->f.ptr.p_double[sfy+s3]/du+3*c->f.ptr.p_double[sfy+s4]/du+4*c->f.ptr.p_double[sfxy+s1]/(dt*du)+2*c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t2*u2; + v = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-4*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s2]/dt+2*c->f.ptr.p_double[sfx+s3]/dt+4*c->f.ptr.p_double[sfx+s4]/dt-3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du+3*c->f.ptr.p_double[sfy+s3]/du-3*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t2*u3; + v = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s2]+c->f.ptr.p_double[sfx+s1]/dt+c->f.ptr.p_double[sfx+s2]/dt; + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t3*u0; + v = 2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t3*u1; + v = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-3*c->f.ptr.p_double[sfx+s1]/dt-3*c->f.ptr.p_double[sfx+s2]/dt+3*c->f.ptr.p_double[sfx+s3]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-4*c->f.ptr.p_double[sfy+s1]/du+4*c->f.ptr.p_double[sfy+s2]/du+2*c->f.ptr.p_double[sfy+s3]/du-2*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-2*c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t3*u2; + v = 4*c->f.ptr.p_double[s1]-4*c->f.ptr.p_double[s2]+4*c->f.ptr.p_double[s3]-4*c->f.ptr.p_double[s4]+2*c->f.ptr.p_double[sfx+s1]/dt+2*c->f.ptr.p_double[sfx+s2]/dt-2*c->f.ptr.p_double[sfx+s3]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfy+s3]/du+2*c->f.ptr.p_double[sfy+s4]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + f->ptr.p_double[i] = f->ptr.p_double[i]+v*t3*u3; + } + return; + } +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcv(spline2dinterpolant* c, + double x, + double y, + /* Real */ ae_vector* f, + ae_state *_state) +{ + + ae_vector_clear(f); + + ae_assert(c->stype==-1||c->stype==-3, "Spline2DCalcV: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(x, _state)&&ae_isfinite(y, _state), "Spline2DCalcV: either X=NaN/Infinite or Y=NaN/Infinite", _state); + ae_vector_set_length(f, c->d, _state); + spline2dcalcvbuf(c, x, y, f, _state); +} + + +/************************************************************************* +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] + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpackv(spline2dinterpolant* c, + ae_int_t* m, + ae_int_t* n, + ae_int_t* d, + /* Real */ ae_matrix* tbl, + ae_state *_state) +{ + ae_int_t k; + ae_int_t p; + ae_int_t ci; + ae_int_t cj; + ae_int_t s1; + ae_int_t s2; + ae_int_t s3; + ae_int_t s4; + ae_int_t sfx; + ae_int_t sfy; + ae_int_t sfxy; + double y1; + double y2; + double y3; + double y4; + double dt; + double du; + ae_int_t i; + ae_int_t j; + ae_int_t k0; + + *m = 0; + *n = 0; + *d = 0; + ae_matrix_clear(tbl); + + ae_assert(c->stype==-3||c->stype==-1, "Spline2DUnpackV: incorrect C (incorrect parameter C.SType)", _state); + *n = c->n; + *m = c->m; + *d = c->d; + ae_matrix_set_length(tbl, (*n-1)*(*m-1)*(*d), 20, _state); + sfx = *n*(*m)*(*d); + sfy = 2*(*n)*(*m)*(*d); + sfxy = 3*(*n)*(*m)*(*d); + for(i=0; i<=*m-2; i++) + { + for(j=0; j<=*n-2; j++) + { + for(k=0; k<=*d-1; k++) + { + p = *d*(i*(*n-1)+j)+k; + tbl->ptr.pp_double[p][0] = c->x.ptr.p_double[j]; + tbl->ptr.pp_double[p][1] = c->x.ptr.p_double[j+1]; + tbl->ptr.pp_double[p][2] = c->y.ptr.p_double[i]; + tbl->ptr.pp_double[p][3] = c->y.ptr.p_double[i+1]; + dt = 1/(tbl->ptr.pp_double[p][1]-tbl->ptr.pp_double[p][0]); + du = 1/(tbl->ptr.pp_double[p][3]-tbl->ptr.pp_double[p][2]); + + /* + * Bilinear interpolation + */ + if( c->stype==-1 ) + { + for(k0=4; k0<=19; k0++) + { + tbl->ptr.pp_double[p][k0] = 0; + } + y1 = c->f.ptr.p_double[*d*(*n*i+j)+k]; + y2 = c->f.ptr.p_double[*d*(*n*i+(j+1))+k]; + y3 = c->f.ptr.p_double[*d*(*n*(i+1)+(j+1))+k]; + y4 = c->f.ptr.p_double[*d*(*n*(i+1)+j)+k]; + tbl->ptr.pp_double[p][4] = y1; + tbl->ptr.pp_double[p][4+1*4+0] = y2-y1; + tbl->ptr.pp_double[p][4+0*4+1] = y4-y1; + tbl->ptr.pp_double[p][4+1*4+1] = y3-y2-y4+y1; + } + + /* + * Bicubic interpolation + */ + if( c->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->ptr.pp_double[p][4+0*4+0] = c->f.ptr.p_double[s1]; + tbl->ptr.pp_double[p][4+0*4+1] = c->f.ptr.p_double[sfy+s1]/du; + tbl->ptr.pp_double[p][4+0*4+2] = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s4]-2*c->f.ptr.p_double[sfy+s1]/du-c->f.ptr.p_double[sfy+s4]/du; + tbl->ptr.pp_double[p][4+0*4+3] = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s4]+c->f.ptr.p_double[sfy+s1]/du+c->f.ptr.p_double[sfy+s4]/du; + tbl->ptr.pp_double[p][4+1*4+0] = c->f.ptr.p_double[sfx+s1]/dt; + tbl->ptr.pp_double[p][4+1*4+1] = c->f.ptr.p_double[sfxy+s1]/(dt*du); + tbl->ptr.pp_double[p][4+1*4+2] = -3*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+1*4+3] = 2*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+0] = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s2]-2*c->f.ptr.p_double[sfx+s1]/dt-c->f.ptr.p_double[sfx+s2]/dt; + tbl->ptr.pp_double[p][4+2*4+1] = -3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+2] = 9*c->f.ptr.p_double[s1]-9*c->f.ptr.p_double[s2]+9*c->f.ptr.p_double[s3]-9*c->f.ptr.p_double[s4]+6*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s2]/dt-3*c->f.ptr.p_double[sfx+s3]/dt-6*c->f.ptr.p_double[sfx+s4]/dt+6*c->f.ptr.p_double[sfy+s1]/du-6*c->f.ptr.p_double[sfy+s2]/du-3*c->f.ptr.p_double[sfy+s3]/du+3*c->f.ptr.p_double[sfy+s4]/du+4*c->f.ptr.p_double[sfxy+s1]/(dt*du)+2*c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+3] = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-4*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s2]/dt+2*c->f.ptr.p_double[sfx+s3]/dt+4*c->f.ptr.p_double[sfx+s4]/dt-3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du+3*c->f.ptr.p_double[sfy+s3]/du-3*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+0] = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s2]+c->f.ptr.p_double[sfx+s1]/dt+c->f.ptr.p_double[sfx+s2]/dt; + tbl->ptr.pp_double[p][4+3*4+1] = 2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+2] = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-3*c->f.ptr.p_double[sfx+s1]/dt-3*c->f.ptr.p_double[sfx+s2]/dt+3*c->f.ptr.p_double[sfx+s3]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-4*c->f.ptr.p_double[sfy+s1]/du+4*c->f.ptr.p_double[sfy+s2]/du+2*c->f.ptr.p_double[sfy+s3]/du-2*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-2*c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+3] = 4*c->f.ptr.p_double[s1]-4*c->f.ptr.p_double[s2]+4*c->f.ptr.p_double[s3]-4*c->f.ptr.p_double[s4]+2*c->f.ptr.p_double[sfx+s1]/dt+2*c->f.ptr.p_double[sfx+s2]/dt-2*c->f.ptr.p_double[sfx+s3]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfy+s3]/du+2*c->f.ptr.p_double[sfy+s4]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + } + + /* + * Rescale Cij + */ + for(ci=0; ci<=3; ci++) + { + for(cj=0; cj<=3; cj++) + { + tbl->ptr.pp_double[p][4+ci*4+cj] = tbl->ptr.pp_double[p][4+ci*4+cj]*ae_pow(dt, ci, _state)*ae_pow(du, cj, _state); + } + } + } + } + } +} + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBilinearV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinear(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_matrix* f, + ae_int_t m, + ae_int_t n, + spline2dinterpolant* c, + ae_state *_state) +{ + double t; + ae_int_t i; + ae_int_t j; + ae_int_t k; + + _spline2dinterpolant_clear(c); + + ae_assert(n>=2, "Spline2DBuildBilinear: N<2", _state); + ae_assert(m>=2, "Spline2DBuildBilinear: M<2", _state); + ae_assert(x->cnt>=n&&y->cnt>=m, "Spline2DBuildBilinear: length of X or Y is too short (Length(X/Y)rows>=m&&f->cols>=n, "Spline2DBuildBilinear: size of F is too small (rows(F)k = 1; + c->n = n; + c->m = m; + c->d = 1; + c->stype = -1; + ae_vector_set_length(&c->x, c->n, _state); + ae_vector_set_length(&c->y, c->m, _state); + ae_vector_set_length(&c->f, c->n*c->m, _state); + for(i=0; i<=c->n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + c->y.ptr.p_double[i] = y->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + c->f.ptr.p_double[i*c->n+j] = f->ptr.pp_double[i][j]; + } + } + + /* + * Sort points + */ + for(j=0; j<=c->n-1; j++) + { + k = j; + for(i=j+1; i<=c->n-1; i++) + { + if( ae_fp_less(c->x.ptr.p_double[i],c->x.ptr.p_double[k]) ) + { + k = i; + } + } + if( k!=j ) + { + for(i=0; i<=c->m-1; i++) + { + t = c->f.ptr.p_double[i*c->n+j]; + c->f.ptr.p_double[i*c->n+j] = c->f.ptr.p_double[i*c->n+k]; + c->f.ptr.p_double[i*c->n+k] = t; + } + t = c->x.ptr.p_double[j]; + c->x.ptr.p_double[j] = c->x.ptr.p_double[k]; + c->x.ptr.p_double[k] = t; + } + } + for(i=0; i<=c->m-1; i++) + { + k = i; + for(j=i+1; j<=c->m-1; j++) + { + if( ae_fp_less(c->y.ptr.p_double[j],c->y.ptr.p_double[k]) ) + { + k = j; + } + } + if( k!=i ) + { + for(j=0; j<=c->n-1; j++) + { + t = c->f.ptr.p_double[i*c->n+j]; + c->f.ptr.p_double[i*c->n+j] = c->f.ptr.p_double[k*c->n+j]; + c->f.ptr.p_double[k*c->n+j] = t; + } + t = c->y.ptr.p_double[i]; + c->y.ptr.p_double[i] = c->y.ptr.p_double[k]; + c->y.ptr.p_double[k] = t; + } + } +} + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBicubicV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_matrix* f, + ae_int_t m, + ae_int_t n, + spline2dinterpolant* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _f; + ae_int_t sfx; + ae_int_t sfy; + ae_int_t sfxy; + ae_matrix dx; + ae_matrix dy; + ae_matrix dxy; + double t; + ae_int_t i; + ae_int_t j; + ae_int_t k; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_f, f, _state, ae_true); + f = &_f; + _spline2dinterpolant_clear(c); + ae_matrix_init(&dx, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&dy, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&dxy, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=2, "Spline2DBuildBicubicSpline: N<2", _state); + ae_assert(m>=2, "Spline2DBuildBicubicSpline: M<2", _state); + ae_assert(x->cnt>=n&&y->cnt>=m, "Spline2DBuildBicubic: length of X or Y is too short (Length(X/Y)rows>=m&&f->cols>=n, "Spline2DBuildBicubic: size of F is too small (rows(F)k = 3; + c->d = 1; + c->n = n; + c->m = m; + c->stype = -3; + sfx = c->n*c->m; + sfy = 2*c->n*c->m; + sfxy = 3*c->n*c->m; + ae_vector_set_length(&c->x, c->n, _state); + ae_vector_set_length(&c->y, c->m, _state); + ae_vector_set_length(&c->f, 4*c->n*c->m, _state); + for(i=0; i<=c->n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + c->y.ptr.p_double[i] = y->ptr.p_double[i]; + } + + /* + * Sort points + */ + for(j=0; j<=c->n-1; j++) + { + k = j; + for(i=j+1; i<=c->n-1; i++) + { + if( ae_fp_less(c->x.ptr.p_double[i],c->x.ptr.p_double[k]) ) + { + k = i; + } + } + if( k!=j ) + { + for(i=0; i<=c->m-1; i++) + { + t = f->ptr.pp_double[i][j]; + f->ptr.pp_double[i][j] = f->ptr.pp_double[i][k]; + f->ptr.pp_double[i][k] = t; + } + t = c->x.ptr.p_double[j]; + c->x.ptr.p_double[j] = c->x.ptr.p_double[k]; + c->x.ptr.p_double[k] = t; + } + } + for(i=0; i<=c->m-1; i++) + { + k = i; + for(j=i+1; j<=c->m-1; j++) + { + if( ae_fp_less(c->y.ptr.p_double[j],c->y.ptr.p_double[k]) ) + { + k = j; + } + } + if( k!=i ) + { + for(j=0; j<=c->n-1; j++) + { + t = f->ptr.pp_double[i][j]; + f->ptr.pp_double[i][j] = f->ptr.pp_double[k][j]; + f->ptr.pp_double[k][j] = t; + } + t = c->y.ptr.p_double[i]; + c->y.ptr.p_double[i] = c->y.ptr.p_double[k]; + c->y.ptr.p_double[k] = t; + } + } + spline2d_bicubiccalcderivatives(f, &c->x, &c->y, c->m, c->n, &dx, &dy, &dxy, _state); + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->n-1; j++) + { + k = i*c->n+j; + c->f.ptr.p_double[k] = f->ptr.pp_double[i][j]; + c->f.ptr.p_double[sfx+k] = dx.ptr.pp_double[i][j]; + c->f.ptr.p_double[sfy+k] = dy.ptr.pp_double[i][j]; + c->f.ptr.p_double[sfxy+k] = dxy.ptr.pp_double[i][j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DUnpackV(), which is more flexible +and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpack(spline2dinterpolant* c, + ae_int_t* m, + ae_int_t* n, + /* Real */ ae_matrix* tbl, + ae_state *_state) +{ + ae_int_t k; + ae_int_t p; + ae_int_t ci; + ae_int_t cj; + ae_int_t s1; + ae_int_t s2; + ae_int_t s3; + ae_int_t s4; + ae_int_t sfx; + ae_int_t sfy; + ae_int_t sfxy; + double y1; + double y2; + double y3; + double y4; + double dt; + double du; + ae_int_t i; + ae_int_t j; + + *m = 0; + *n = 0; + ae_matrix_clear(tbl); + + ae_assert(c->stype==-3||c->stype==-1, "Spline2DUnpack: incorrect C (incorrect parameter C.SType)", _state); + if( c->d!=1 ) + { + *n = 0; + *m = 0; + return; + } + *n = c->n; + *m = c->m; + ae_matrix_set_length(tbl, (*n-1)*(*m-1), 20, _state); + sfx = *n*(*m); + sfy = 2*(*n)*(*m); + sfxy = 3*(*n)*(*m); + + /* + * Fill + */ + for(i=0; i<=*m-2; i++) + { + for(j=0; j<=*n-2; j++) + { + p = i*(*n-1)+j; + tbl->ptr.pp_double[p][0] = c->x.ptr.p_double[j]; + tbl->ptr.pp_double[p][1] = c->x.ptr.p_double[j+1]; + tbl->ptr.pp_double[p][2] = c->y.ptr.p_double[i]; + tbl->ptr.pp_double[p][3] = c->y.ptr.p_double[i+1]; + dt = 1/(tbl->ptr.pp_double[p][1]-tbl->ptr.pp_double[p][0]); + du = 1/(tbl->ptr.pp_double[p][3]-tbl->ptr.pp_double[p][2]); + + /* + * Bilinear interpolation + */ + if( c->stype==-1 ) + { + for(k=4; k<=19; k++) + { + tbl->ptr.pp_double[p][k] = 0; + } + y1 = c->f.ptr.p_double[*n*i+j]; + y2 = c->f.ptr.p_double[*n*i+(j+1)]; + y3 = c->f.ptr.p_double[*n*(i+1)+(j+1)]; + y4 = c->f.ptr.p_double[*n*(i+1)+j]; + tbl->ptr.pp_double[p][4] = y1; + tbl->ptr.pp_double[p][4+1*4+0] = y2-y1; + tbl->ptr.pp_double[p][4+0*4+1] = y4-y1; + tbl->ptr.pp_double[p][4+1*4+1] = y3-y2-y4+y1; + } + + /* + * Bicubic interpolation + */ + if( c->stype==-3 ) + { + s1 = *n*i+j; + s2 = *n*i+(j+1); + s3 = *n*(i+1)+(j+1); + s4 = *n*(i+1)+j; + tbl->ptr.pp_double[p][4+0*4+0] = c->f.ptr.p_double[s1]; + tbl->ptr.pp_double[p][4+0*4+1] = c->f.ptr.p_double[sfy+s1]/du; + tbl->ptr.pp_double[p][4+0*4+2] = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s4]-2*c->f.ptr.p_double[sfy+s1]/du-c->f.ptr.p_double[sfy+s4]/du; + tbl->ptr.pp_double[p][4+0*4+3] = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s4]+c->f.ptr.p_double[sfy+s1]/du+c->f.ptr.p_double[sfy+s4]/du; + tbl->ptr.pp_double[p][4+1*4+0] = c->f.ptr.p_double[sfx+s1]/dt; + tbl->ptr.pp_double[p][4+1*4+1] = c->f.ptr.p_double[sfxy+s1]/(dt*du); + tbl->ptr.pp_double[p][4+1*4+2] = -3*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+1*4+3] = 2*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+0] = -3*c->f.ptr.p_double[s1]+3*c->f.ptr.p_double[s2]-2*c->f.ptr.p_double[sfx+s1]/dt-c->f.ptr.p_double[sfx+s2]/dt; + tbl->ptr.pp_double[p][4+2*4+1] = -3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+2] = 9*c->f.ptr.p_double[s1]-9*c->f.ptr.p_double[s2]+9*c->f.ptr.p_double[s3]-9*c->f.ptr.p_double[s4]+6*c->f.ptr.p_double[sfx+s1]/dt+3*c->f.ptr.p_double[sfx+s2]/dt-3*c->f.ptr.p_double[sfx+s3]/dt-6*c->f.ptr.p_double[sfx+s4]/dt+6*c->f.ptr.p_double[sfy+s1]/du-6*c->f.ptr.p_double[sfy+s2]/du-3*c->f.ptr.p_double[sfy+s3]/du+3*c->f.ptr.p_double[sfy+s4]/du+4*c->f.ptr.p_double[sfxy+s1]/(dt*du)+2*c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+2*4+3] = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-4*c->f.ptr.p_double[sfx+s1]/dt-2*c->f.ptr.p_double[sfx+s2]/dt+2*c->f.ptr.p_double[sfx+s3]/dt+4*c->f.ptr.p_double[sfx+s4]/dt-3*c->f.ptr.p_double[sfy+s1]/du+3*c->f.ptr.p_double[sfy+s2]/du+3*c->f.ptr.p_double[sfy+s3]/du-3*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-2*c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+0] = 2*c->f.ptr.p_double[s1]-2*c->f.ptr.p_double[s2]+c->f.ptr.p_double[sfx+s1]/dt+c->f.ptr.p_double[sfx+s2]/dt; + tbl->ptr.pp_double[p][4+3*4+1] = 2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+2] = -6*c->f.ptr.p_double[s1]+6*c->f.ptr.p_double[s2]-6*c->f.ptr.p_double[s3]+6*c->f.ptr.p_double[s4]-3*c->f.ptr.p_double[sfx+s1]/dt-3*c->f.ptr.p_double[sfx+s2]/dt+3*c->f.ptr.p_double[sfx+s3]/dt+3*c->f.ptr.p_double[sfx+s4]/dt-4*c->f.ptr.p_double[sfy+s1]/du+4*c->f.ptr.p_double[sfy+s2]/du+2*c->f.ptr.p_double[sfy+s3]/du-2*c->f.ptr.p_double[sfy+s4]/du-2*c->f.ptr.p_double[sfxy+s1]/(dt*du)-2*c->f.ptr.p_double[sfxy+s2]/(dt*du)-c->f.ptr.p_double[sfxy+s3]/(dt*du)-c->f.ptr.p_double[sfxy+s4]/(dt*du); + tbl->ptr.pp_double[p][4+3*4+3] = 4*c->f.ptr.p_double[s1]-4*c->f.ptr.p_double[s2]+4*c->f.ptr.p_double[s3]-4*c->f.ptr.p_double[s4]+2*c->f.ptr.p_double[sfx+s1]/dt+2*c->f.ptr.p_double[sfx+s2]/dt-2*c->f.ptr.p_double[sfx+s3]/dt-2*c->f.ptr.p_double[sfx+s4]/dt+2*c->f.ptr.p_double[sfy+s1]/du-2*c->f.ptr.p_double[sfy+s2]/du-2*c->f.ptr.p_double[sfy+s3]/du+2*c->f.ptr.p_double[sfy+s4]/du+c->f.ptr.p_double[sfxy+s1]/(dt*du)+c->f.ptr.p_double[sfxy+s2]/(dt*du)+c->f.ptr.p_double[sfxy+s3]/(dt*du)+c->f.ptr.p_double[sfxy+s4]/(dt*du); + } + + /* + * Rescale Cij + */ + for(ci=0; ci<=3; ci++) + { + for(cj=0; cj<=3; cj++) + { + tbl->ptr.pp_double[p][4+ci*4+cj] = tbl->ptr.pp_double[p][4+ci*4+cj]*ae_pow(dt, ci, _state)*ae_pow(du, cj, _state); + } + } + } + } +} + + +/************************************************************************* +Internal subroutine. +Calculation of the first derivatives and the cross-derivative. +*************************************************************************/ +static void spline2d_bicubiccalcderivatives(/* Real */ ae_matrix* a, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* dx, + /* Real */ ae_matrix* dy, + /* Real */ ae_matrix* dxy, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector xt; + ae_vector ft; + double s; + double ds; + double d2s; + spline1dinterpolant c; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(dx); + ae_matrix_clear(dy); + ae_matrix_clear(dxy); + ae_vector_init(&xt, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ft, 0, DT_REAL, _state, ae_true); + _spline1dinterpolant_init(&c, _state, ae_true); + + ae_matrix_set_length(dx, m, n, _state); + ae_matrix_set_length(dy, m, n, _state); + ae_matrix_set_length(dxy, m, n, _state); + + /* + * dF/dX + */ + ae_vector_set_length(&xt, n, _state); + ae_vector_set_length(&ft, n, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + xt.ptr.p_double[j] = x->ptr.p_double[j]; + ft.ptr.p_double[j] = a->ptr.pp_double[i][j]; + } + spline1dbuildcubic(&xt, &ft, n, 0, 0.0, 0, 0.0, &c, _state); + for(j=0; j<=n-1; j++) + { + spline1ddiff(&c, x->ptr.p_double[j], &s, &ds, &d2s, _state); + dx->ptr.pp_double[i][j] = ds; + } + } + + /* + * dF/dY + */ + ae_vector_set_length(&xt, m, _state); + ae_vector_set_length(&ft, m, _state); + for(j=0; j<=n-1; j++) + { + for(i=0; i<=m-1; i++) + { + xt.ptr.p_double[i] = y->ptr.p_double[i]; + ft.ptr.p_double[i] = a->ptr.pp_double[i][j]; + } + spline1dbuildcubic(&xt, &ft, m, 0, 0.0, 0, 0.0, &c, _state); + for(i=0; i<=m-1; i++) + { + spline1ddiff(&c, y->ptr.p_double[i], &s, &ds, &d2s, _state); + dy->ptr.pp_double[i][j] = ds; + } + } + + /* + * d2F/dXdY + */ + ae_vector_set_length(&xt, n, _state); + ae_vector_set_length(&ft, n, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + xt.ptr.p_double[j] = x->ptr.p_double[j]; + ft.ptr.p_double[j] = dy->ptr.pp_double[i][j]; + } + spline1dbuildcubic(&xt, &ft, n, 0, 0.0, 0, 0.0, &c, _state); + for(j=0; j<=n-1; j++) + { + spline1ddiff(&c, x->ptr.p_double[j], &s, &ds, &d2s, _state); + dxy->ptr.pp_double[i][j] = ds; + } + } + ae_frame_leave(_state); +} + + +ae_bool _spline2dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + spline2dinterpolant *p = (spline2dinterpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->f, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _spline2dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + spline2dinterpolant *dst = (spline2dinterpolant*)_dst; + spline2dinterpolant *src = (spline2dinterpolant*)_src; + dst->k = src->k; + dst->stype = src->stype; + dst->n = src->n; + dst->m = src->m; + dst->d = src->d; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->f, &src->f, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _spline2dinterpolant_clear(void* _p) +{ + spline2dinterpolant *p = (spline2dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->y); + ae_vector_clear(&p->f); +} + + +void _spline2dinterpolant_destroy(void* _p) +{ + spline2dinterpolant *p = (spline2dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->y); + ae_vector_destroy(&p->f); +} + + + + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +double spline3dcalc(spline3dinterpolant* c, + double x, + double y, + double z, + ae_state *_state) +{ + double v; + double vx; + double vy; + double vxy; + double result; + + + ae_assert(c->stype==-1||c->stype==-3, "Spline3DCalc: incorrect C (incorrect parameter C.SType)", _state); + ae_assert((ae_isfinite(x, _state)&&ae_isfinite(y, _state))&&ae_isfinite(z, _state), "Spline3DCalc: X=NaN/Infinite, Y=NaN/Infinite or Z=NaN/Infinite", _state); + if( c->d!=1 ) + { + result = 0; + return result; + } + spline3d_spline3ddiff(c, x, y, z, &v, &vx, &vy, &vxy, _state); + result = v; + 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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransxyz(spline3dinterpolant* c, + double ax, + double bx, + double ay, + double by, + double az, + double bz, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_vector z; + ae_vector f; + ae_vector v; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t di; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&z, 0, DT_REAL, _state, ae_true); + ae_vector_init(&f, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + + ae_assert(c->stype==-3||c->stype==-1, "Spline3DLinTransXYZ: incorrect C (incorrect parameter C.SType)", _state); + ae_vector_set_length(&x, c->n, _state); + ae_vector_set_length(&y, c->m, _state); + ae_vector_set_length(&z, c->l, _state); + ae_vector_set_length(&f, c->m*c->n*c->l*c->d, _state); + for(j=0; j<=c->n-1; j++) + { + x.ptr.p_double[j] = c->x.ptr.p_double[j]; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = c->y.ptr.p_double[i]; + } + for(i=0; i<=c->l-1; i++) + { + z.ptr.p_double[i] = c->z.ptr.p_double[i]; + } + + /* + * Handle different combinations of zero/nonzero AX/AY/AZ + */ + if( (ae_fp_neq(ax,0)&&ae_fp_neq(ay,0))&&ae_fp_neq(az,0) ) + { + ae_v_move(&f.ptr.p_double[0], 1, &c->f.ptr.p_double[0], 1, ae_v_len(0,c->m*c->n*c->l*c->d-1)); + } + if( (ae_fp_eq(ax,0)&&ae_fp_neq(ay,0))&&ae_fp_neq(az,0) ) + { + for(i=0; i<=c->m-1; i++) + { + for(j=0; j<=c->l-1; j++) + { + spline3dcalcv(c, bx, y.ptr.p_double[i], z.ptr.p_double[j], &v, _state); + for(k=0; k<=c->n-1; k++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*j+i)+k)+di] = v.ptr.p_double[di]; + } + } + } + } + ax = 1; + bx = 0; + } + if( (ae_fp_neq(ax,0)&&ae_fp_eq(ay,0))&&ae_fp_neq(az,0) ) + { + for(i=0; i<=c->n-1; i++) + { + for(j=0; j<=c->l-1; j++) + { + spline3dcalcv(c, x.ptr.p_double[i], by, z.ptr.p_double[j], &v, _state); + for(k=0; k<=c->m-1; k++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*j+k)+i)+di] = v.ptr.p_double[di]; + } + } + } + } + ay = 1; + by = 0; + } + if( (ae_fp_neq(ax,0)&&ae_fp_neq(ay,0))&&ae_fp_eq(az,0) ) + { + for(i=0; i<=c->n-1; i++) + { + for(j=0; j<=c->m-1; j++) + { + spline3dcalcv(c, x.ptr.p_double[i], y.ptr.p_double[j], bz, &v, _state); + for(k=0; k<=c->l-1; k++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*k+j)+i)+di] = v.ptr.p_double[di]; + } + } + } + } + az = 1; + bz = 0; + } + if( (ae_fp_eq(ax,0)&&ae_fp_eq(ay,0))&&ae_fp_neq(az,0) ) + { + for(i=0; i<=c->l-1; i++) + { + spline3dcalcv(c, bx, by, z.ptr.p_double[i], &v, _state); + for(k=0; k<=c->m-1; k++) + { + for(j=0; j<=c->n-1; j++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*i+k)+j)+di] = v.ptr.p_double[di]; + } + } + } + } + ax = 1; + bx = 0; + ay = 1; + by = 0; + } + if( (ae_fp_eq(ax,0)&&ae_fp_neq(ay,0))&&ae_fp_eq(az,0) ) + { + for(i=0; i<=c->m-1; i++) + { + spline3dcalcv(c, bx, y.ptr.p_double[i], bz, &v, _state); + for(k=0; k<=c->l-1; k++) + { + for(j=0; j<=c->n-1; j++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*k+i)+j)+di] = v.ptr.p_double[di]; + } + } + } + } + ax = 1; + bx = 0; + az = 1; + bz = 0; + } + if( (ae_fp_neq(ax,0)&&ae_fp_eq(ay,0))&&ae_fp_eq(az,0) ) + { + for(i=0; i<=c->n-1; i++) + { + spline3dcalcv(c, x.ptr.p_double[i], by, bz, &v, _state); + for(k=0; k<=c->l-1; k++) + { + for(j=0; j<=c->m-1; j++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*k+j)+i)+di] = v.ptr.p_double[di]; + } + } + } + } + ay = 1; + by = 0; + az = 1; + bz = 0; + } + if( (ae_fp_eq(ax,0)&&ae_fp_eq(ay,0))&&ae_fp_eq(az,0) ) + { + spline3dcalcv(c, bx, by, bz, &v, _state); + for(k=0; k<=c->l-1; k++) + { + for(j=0; j<=c->m-1; j++) + { + for(i=0; i<=c->n-1; i++) + { + for(di=0; di<=c->d-1; di++) + { + f.ptr.p_double[c->d*(c->n*(c->m*k+j)+i)+di] = v.ptr.p_double[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. + */ + for(i=0; i<=c->n-1; i++) + { + x.ptr.p_double[i] = (x.ptr.p_double[i]-bx)/ax; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = (y.ptr.p_double[i]-by)/ay; + } + for(i=0; i<=c->l-1; i++) + { + z.ptr.p_double[i] = (z.ptr.p_double[i]-bz)/az; + } + if( c->stype==-1 ) + { + spline3dbuildtrilinearv(&x, c->n, &y, c->m, &z, c->l, &f, c->d, c, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransf(spline3dinterpolant* c, + double a, + double b, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector x; + ae_vector y; + ae_vector z; + ae_vector f; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&x, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&z, 0, DT_REAL, _state, ae_true); + ae_vector_init(&f, 0, DT_REAL, _state, ae_true); + + ae_assert(c->stype==-3||c->stype==-1, "Spline3DLinTransF: incorrect C (incorrect parameter C.SType)", _state); + ae_vector_set_length(&x, c->n, _state); + ae_vector_set_length(&y, c->m, _state); + ae_vector_set_length(&z, c->l, _state); + ae_vector_set_length(&f, c->m*c->n*c->l*c->d, _state); + for(j=0; j<=c->n-1; j++) + { + x.ptr.p_double[j] = c->x.ptr.p_double[j]; + } + for(i=0; i<=c->m-1; i++) + { + y.ptr.p_double[i] = c->y.ptr.p_double[i]; + } + for(i=0; i<=c->l-1; i++) + { + z.ptr.p_double[i] = c->z.ptr.p_double[i]; + } + for(i=0; i<=c->m*c->n*c->l*c->d-1; i++) + { + f.ptr.p_double[i] = a*c->f.ptr.p_double[i]+b; + } + if( c->stype==-1 ) + { + spline3dbuildtrilinearv(&x, c->n, &y, c->m, &z, c->l, &f, c->d, c, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine makes the copy of the spline model. + +INPUT PARAMETERS: + C - spline interpolant + +OUTPUT PARAMETERS: + CC - spline copy + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcopy(spline3dinterpolant* c, + spline3dinterpolant* cc, + ae_state *_state) +{ + ae_int_t tblsize; + + _spline3dinterpolant_clear(cc); + + ae_assert(c->k==1||c->k==3, "Spline3DCopy: incorrect C (incorrect parameter C.K)", _state); + cc->k = c->k; + cc->n = c->n; + cc->m = c->m; + cc->l = c->l; + cc->d = c->d; + tblsize = c->n*c->m*c->l*c->d; + cc->stype = c->stype; + ae_vector_set_length(&cc->x, cc->n, _state); + ae_vector_set_length(&cc->y, cc->m, _state); + ae_vector_set_length(&cc->z, cc->l, _state); + ae_vector_set_length(&cc->f, tblsize, _state); + ae_v_move(&cc->x.ptr.p_double[0], 1, &c->x.ptr.p_double[0], 1, ae_v_len(0,cc->n-1)); + ae_v_move(&cc->y.ptr.p_double[0], 1, &c->y.ptr.p_double[0], 1, ae_v_len(0,cc->m-1)); + ae_v_move(&cc->z.ptr.p_double[0], 1, &c->z.ptr.p_double[0], 1, ae_v_len(0,cc->l-1)); + ae_v_move(&cc->f.ptr.p_double[0], 1, &c->f.ptr.p_double[0], 1, ae_v_len(0,tblsize-1)); +} + + +/************************************************************************* +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[..] ... + ... + + -- ALGLIB routine -- + 26.04.2012 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline3dresampletrilinear(/* Real */ ae_vector* a, + ae_int_t oldzcount, + ae_int_t oldycount, + ae_int_t oldxcount, + ae_int_t newzcount, + ae_int_t newycount, + ae_int_t newxcount, + /* Real */ ae_vector* b, + ae_state *_state) +{ + double xd; + double yd; + double zd; + double c0; + double c1; + double c2; + double c3; + ae_int_t ix; + ae_int_t iy; + ae_int_t iz; + ae_int_t i; + ae_int_t j; + ae_int_t k; + + ae_vector_clear(b); + + ae_assert((oldycount>1&&oldzcount>1)&&oldxcount>1, "Spline3DResampleTrilinear: length/width/height less than 1", _state); + ae_assert((newycount>1&&newzcount>1)&&newxcount>1, "Spline3DResampleTrilinear: length/width/height less than 1", _state); + ae_assert(a->cnt>=oldycount*oldzcount*oldxcount, "Spline3DResampleTrilinear: length/width/height less than 1", _state); + ae_vector_set_length(b, newxcount*newycount*newzcount, _state); + for(i=0; i<=newxcount-1; i++) + { + for(j=0; j<=newycount-1; j++) + { + for(k=0; k<=newzcount-1; 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->ptr.p_double[oldxcount*(oldycount*iz+iy)+ix]*(1-xd)+a->ptr.p_double[oldxcount*(oldycount*iz+iy)+(ix+1)]*xd; + c1 = a->ptr.p_double[oldxcount*(oldycount*iz+(iy+1))+ix]*(1-xd)+a->ptr.p_double[oldxcount*(oldycount*iz+(iy+1))+(ix+1)]*xd; + c2 = a->ptr.p_double[oldxcount*(oldycount*(iz+1)+iy)+ix]*(1-xd)+a->ptr.p_double[oldxcount*(oldycount*(iz+1)+iy)+(ix+1)]*xd; + c3 = a->ptr.p_double[oldxcount*(oldycount*(iz+1)+(iy+1))+ix]*(1-xd)+a->ptr.p_double[oldxcount*(oldycount*(iz+1)+(iy+1))+(ix+1)]*xd; + c0 = c0*(1-yd)+c1*yd; + c1 = c2*(1-yd)+c3*yd; + b->ptr.p_double[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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dbuildtrilinearv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* z, + ae_int_t l, + /* Real */ ae_vector* f, + ae_int_t d, + spline3dinterpolant* c, + ae_state *_state) +{ + double t; + ae_int_t tblsize; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t i0; + ae_int_t j0; + + _spline3dinterpolant_clear(c); + + ae_assert(m>=2, "Spline3DBuildTrilinearV: M<2", _state); + ae_assert(n>=2, "Spline3DBuildTrilinearV: N<2", _state); + ae_assert(l>=2, "Spline3DBuildTrilinearV: L<2", _state); + ae_assert(d>=1, "Spline3DBuildTrilinearV: D<1", _state); + ae_assert((x->cnt>=n&&y->cnt>=m)&&z->cnt>=l, "Spline3DBuildTrilinearV: length of X, Y or Z is too short (Length(X/Y/Z)cnt>=tblsize, "Spline3DBuildTrilinearV: length of F is too short (Length(F)k = 1; + c->n = n; + c->m = m; + c->l = l; + c->d = d; + c->stype = -1; + ae_vector_set_length(&c->x, c->n, _state); + ae_vector_set_length(&c->y, c->m, _state); + ae_vector_set_length(&c->z, c->l, _state); + ae_vector_set_length(&c->f, tblsize, _state); + for(i=0; i<=c->n-1; i++) + { + c->x.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=c->m-1; i++) + { + c->y.ptr.p_double[i] = y->ptr.p_double[i]; + } + for(i=0; i<=c->l-1; i++) + { + c->z.ptr.p_double[i] = z->ptr.p_double[i]; + } + for(i=0; i<=tblsize-1; i++) + { + c->f.ptr.p_double[i] = f->ptr.p_double[i]; + } + + /* + * Sort points: + * * sort x; + * * sort y; + * * sort z. + */ + for(j=0; j<=c->n-1; j++) + { + k = j; + for(i=j+1; i<=c->n-1; i++) + { + if( ae_fp_less(c->x.ptr.p_double[i],c->x.ptr.p_double[k]) ) + { + k = i; + } + } + if( k!=j ) + { + for(i=0; i<=c->m-1; i++) + { + for(j0=0; j0<=c->l-1; j0++) + { + for(i0=0; i0<=c->d-1; i0++) + { + t = c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+j)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+j)+i0] = c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+k)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+k)+i0] = t; + } + } + } + t = c->x.ptr.p_double[j]; + c->x.ptr.p_double[j] = c->x.ptr.p_double[k]; + c->x.ptr.p_double[k] = t; + } + } + for(i=0; i<=c->m-1; i++) + { + k = i; + for(j=i+1; j<=c->m-1; j++) + { + if( ae_fp_less(c->y.ptr.p_double[j],c->y.ptr.p_double[k]) ) + { + k = j; + } + } + if( k!=i ) + { + for(j=0; j<=c->n-1; j++) + { + for(j0=0; j0<=c->l-1; j0++) + { + for(i0=0; i0<=c->d-1; i0++) + { + t = c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+j)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*j0+i)+j)+i0] = c->f.ptr.p_double[c->d*(c->n*(c->m*j0+k)+j)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*j0+k)+j)+i0] = t; + } + } + } + t = c->y.ptr.p_double[i]; + c->y.ptr.p_double[i] = c->y.ptr.p_double[k]; + c->y.ptr.p_double[k] = t; + } + } + for(k=0; k<=c->l-1; k++) + { + i = k; + for(j=i+1; j<=c->l-1; j++) + { + if( ae_fp_less(c->z.ptr.p_double[j],c->z.ptr.p_double[i]) ) + { + i = j; + } + } + if( i!=k ) + { + for(j=0; j<=c->m-1; j++) + { + for(j0=0; j0<=c->n-1; j0++) + { + for(i0=0; i0<=c->d-1; i0++) + { + t = c->f.ptr.p_double[c->d*(c->n*(c->m*k+j)+j0)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*k+j)+j0)+i0] = c->f.ptr.p_double[c->d*(c->n*(c->m*i+j)+j0)+i0]; + c->f.ptr.p_double[c->d*(c->n*(c->m*i+j)+j0)+i0] = t; + } + } + } + t = c->z.ptr.p_double[k]; + c->z.ptr.p_double[k] = c->z.ptr.p_double[i]; + c->z.ptr.p_double[i] = t; + } + } +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcvbuf(spline3dinterpolant* c, + double x, + double y, + double z, + /* Real */ ae_vector* f, + ae_state *_state) +{ + double xd; + double yd; + double zd; + double c0; + double c1; + double c2; + double c3; + ae_int_t ix; + ae_int_t iy; + ae_int_t iz; + ae_int_t l; + ae_int_t r; + ae_int_t h; + ae_int_t i; + + + ae_assert(c->stype==-1||c->stype==-3, "Spline3DCalcVBuf: incorrect C (incorrect parameter C.SType)", _state); + ae_assert((ae_isfinite(x, _state)&&ae_isfinite(y, _state))&&ae_isfinite(z, _state), "Spline3DCalcVBuf: X, Y or Z contains NaN/Infinite", _state); + rvectorsetlengthatleast(f, c->d, _state); + + /* + * Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included) + */ + l = 0; + r = c->n-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->x.ptr.p_double[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-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->y.ptr.p_double[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->l-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->z.ptr.p_double[h],z) ) + { + r = h; + } + else + { + l = h; + } + } + iz = l; + xd = (x-c->x.ptr.p_double[ix])/(c->x.ptr.p_double[ix+1]-c->x.ptr.p_double[ix]); + yd = (y-c->y.ptr.p_double[iy])/(c->y.ptr.p_double[iy+1]-c->y.ptr.p_double[iy]); + zd = (z-c->z.ptr.p_double[iz])/(c->z.ptr.p_double[iz+1]-c->z.ptr.p_double[iz]); + for(i=0; i<=c->d-1; i++) + { + + /* + * Trilinear interpolation + */ + if( c->stype==-1 ) + { + c0 = c->f.ptr.p_double[c->d*(c->n*(c->m*iz+iy)+ix)+i]*(1-xd)+c->f.ptr.p_double[c->d*(c->n*(c->m*iz+iy)+(ix+1))+i]*xd; + c1 = c->f.ptr.p_double[c->d*(c->n*(c->m*iz+(iy+1))+ix)+i]*(1-xd)+c->f.ptr.p_double[c->d*(c->n*(c->m*iz+(iy+1))+(ix+1))+i]*xd; + c2 = c->f.ptr.p_double[c->d*(c->n*(c->m*(iz+1)+iy)+ix)+i]*(1-xd)+c->f.ptr.p_double[c->d*(c->n*(c->m*(iz+1)+iy)+(ix+1))+i]*xd; + c3 = c->f.ptr.p_double[c->d*(c->n*(c->m*(iz+1)+(iy+1))+ix)+i]*(1-xd)+c->f.ptr.p_double[c->d*(c->n*(c->m*(iz+1)+(iy+1))+(ix+1))+i]*xd; + c0 = c0*(1-yd)+c1*yd; + c1 = c2*(1-yd)+c3*yd; + f->ptr.p_double[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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcv(spline3dinterpolant* c, + double x, + double y, + double z, + /* Real */ ae_vector* f, + ae_state *_state) +{ + + ae_vector_clear(f); + + ae_assert(c->stype==-1||c->stype==-3, "Spline3DCalcV: incorrect C (incorrect parameter C.SType)", _state); + ae_assert((ae_isfinite(x, _state)&&ae_isfinite(y, _state))&&ae_isfinite(z, _state), "Spline3DCalcV: X=NaN/Infinite, Y=NaN/Infinite or Z=NaN/Infinite", _state); + ae_vector_set_length(f, c->d, _state); + spline3dcalcvbuf(c, x, y, z, f, _state); +} + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dunpackv(spline3dinterpolant* c, + ae_int_t* n, + ae_int_t* m, + ae_int_t* l, + ae_int_t* d, + ae_int_t* stype, + /* Real */ ae_matrix* tbl, + ae_state *_state) +{ + ae_int_t p; + ae_int_t ci; + ae_int_t cj; + ae_int_t ck; + double du; + double dv; + double dw; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t di; + ae_int_t i0; + + *n = 0; + *m = 0; + *l = 0; + *d = 0; + *stype = 0; + ae_matrix_clear(tbl); + + ae_assert(c->stype==-1, "Spline3DUnpackV: incorrect C (incorrect parameter C.SType)", _state); + *n = c->n; + *m = c->m; + *l = c->l; + *d = c->d; + *stype = ae_iabs(c->stype, _state); + ae_matrix_set_length(tbl, (*n-1)*(*m-1)*(*l-1)*(*d), 14, _state); + + /* + * Fill + */ + for(i=0; i<=*n-2; i++) + { + for(j=0; j<=*m-2; j++) + { + for(k=0; k<=*l-2; k++) + { + for(di=0; di<=*d-1; di++) + { + p = *d*((*n-1)*((*m-1)*k+j)+i)+di; + tbl->ptr.pp_double[p][0] = c->x.ptr.p_double[i]; + tbl->ptr.pp_double[p][1] = c->x.ptr.p_double[i+1]; + tbl->ptr.pp_double[p][2] = c->y.ptr.p_double[j]; + tbl->ptr.pp_double[p][3] = c->y.ptr.p_double[j+1]; + tbl->ptr.pp_double[p][4] = c->z.ptr.p_double[k]; + tbl->ptr.pp_double[p][5] = c->z.ptr.p_double[k+1]; + du = 1/(tbl->ptr.pp_double[p][1]-tbl->ptr.pp_double[p][0]); + dv = 1/(tbl->ptr.pp_double[p][3]-tbl->ptr.pp_double[p][2]); + dw = 1/(tbl->ptr.pp_double[p][5]-tbl->ptr.pp_double[p][4]); + + /* + * Trilinear interpolation + */ + if( c->stype==-1 ) + { + for(i0=6; i0<=13; i0++) + { + tbl->ptr.pp_double[p][i0] = 0; + } + tbl->ptr.pp_double[p][6+2*(2*0+0)+0] = c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*0+0)+1] = c->f.ptr.p_double[*d*(*n*(*m*k+j)+(i+1))+di]-c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*0+1)+0] = c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*0+1)+1] = c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+(i+1))+di]-c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+j)+(i+1))+di]+c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*1+0)+0] = c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*1+0)+1] = c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+(i+1))+di]-c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+j)+(i+1))+di]+c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*1+1)+0] = c->f.ptr.p_double[*d*(*n*(*m*(k+1)+(j+1))+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+i)+di]+c->f.ptr.p_double[*d*(*n*(*m*k+j)+i)+di]; + tbl->ptr.pp_double[p][6+2*(2*1+1)+1] = c->f.ptr.p_double[*d*(*n*(*m*(k+1)+(j+1))+(i+1))+di]-c->f.ptr.p_double[*d*(*n*(*m*(k+1)+(j+1))+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+(i+1))+di]+c->f.ptr.p_double[*d*(*n*(*m*(k+1)+j)+i)+di]-c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+(i+1))+di]+c->f.ptr.p_double[*d*(*n*(*m*k+(j+1))+i)+di]+c->f.ptr.p_double[*d*(*n*(*m*k+j)+(i+1))+di]-c->f.ptr.p_double[*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->ptr.pp_double[p][6+2*(2*ck+cj)+ci] = tbl->ptr.pp_double[p][6+2*(2*ck+cj)+ci]*ae_pow(du, ci, _state)*ae_pow(dv, cj, _state)*ae_pow(dw, ck, _state); + } + } + } + } + } + } + } +} + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +static void spline3d_spline3ddiff(spline3dinterpolant* c, + double x, + double y, + double z, + double* f, + double* fx, + double* fy, + double* fxy, + ae_state *_state) +{ + double xd; + double yd; + double zd; + double c0; + double c1; + double c2; + double c3; + ae_int_t ix; + ae_int_t iy; + ae_int_t iz; + ae_int_t l; + ae_int_t r; + ae_int_t h; + + *f = 0; + *fx = 0; + *fy = 0; + *fxy = 0; + + ae_assert(c->stype==-1||c->stype==-3, "Spline3DDiff: incorrect C (incorrect parameter C.SType)", _state); + ae_assert(ae_isfinite(x, _state)&&ae_isfinite(y, _state), "Spline3DDiff: X or Y contains NaN or Infinite value", _state); + + /* + * Prepare F, dF/dX, dF/dY, d2F/dXdY + */ + *f = 0; + *fx = 0; + *fy = 0; + *fxy = 0; + if( c->d!=1 ) + { + return; + } + + /* + * Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included) + */ + l = 0; + r = c->n-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->x.ptr.p_double[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-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->y.ptr.p_double[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->l-1; + while(l!=r-1) + { + h = (l+r)/2; + if( ae_fp_greater_eq(c->z.ptr.p_double[h],z) ) + { + r = h; + } + else + { + l = h; + } + } + iz = l; + xd = (x-c->x.ptr.p_double[ix])/(c->x.ptr.p_double[ix+1]-c->x.ptr.p_double[ix]); + yd = (y-c->y.ptr.p_double[iy])/(c->y.ptr.p_double[iy+1]-c->y.ptr.p_double[iy]); + zd = (z-c->z.ptr.p_double[iz])/(c->z.ptr.p_double[iz+1]-c->z.ptr.p_double[iz]); + + /* + * Trilinear interpolation + */ + if( c->stype==-1 ) + { + c0 = c->f.ptr.p_double[c->n*(c->m*iz+iy)+ix]*(1-xd)+c->f.ptr.p_double[c->n*(c->m*iz+iy)+(ix+1)]*xd; + c1 = c->f.ptr.p_double[c->n*(c->m*iz+(iy+1))+ix]*(1-xd)+c->f.ptr.p_double[c->n*(c->m*iz+(iy+1))+(ix+1)]*xd; + c2 = c->f.ptr.p_double[c->n*(c->m*(iz+1)+iy)+ix]*(1-xd)+c->f.ptr.p_double[c->n*(c->m*(iz+1)+iy)+(ix+1)]*xd; + c3 = c->f.ptr.p_double[c->n*(c->m*(iz+1)+(iy+1))+ix]*(1-xd)+c->f.ptr.p_double[c->n*(c->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; + } +} + + +ae_bool _spline3dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + spline3dinterpolant *p = (spline3dinterpolant*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->y, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->z, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->f, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _spline3dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + spline3dinterpolant *dst = (spline3dinterpolant*)_dst; + spline3dinterpolant *src = (spline3dinterpolant*)_src; + dst->k = src->k; + dst->stype = src->stype; + dst->n = src->n; + dst->m = src->m; + dst->l = src->l; + dst->d = src->d; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->y, &src->y, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->z, &src->z, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->f, &src->f, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _spline3dinterpolant_clear(void* _p) +{ + spline3dinterpolant *p = (spline3dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->y); + ae_vector_clear(&p->z); + ae_vector_clear(&p->f); +} + + +void _spline3dinterpolant_destroy(void* _p) +{ + spline3dinterpolant *p = (spline3dinterpolant*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->y); + ae_vector_destroy(&p->z); + ae_vector_destroy(&p->f); +} + + + +} + diff --git a/src/inc/alglib/interpolation.h b/src/inc/alglib/interpolation.h new file mode 100644 index 0000000..f2c9d34 --- /dev/null +++ b/src/inc/alglib/interpolation.h @@ -0,0 +1,5906 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _interpolation_pkg_h +#define _interpolation_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "alglibmisc.h" +#include "linalg.h" +#include "solvers.h" +#include "optimization.h" +#include "specialfunctions.h" +#include "integration.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_int_t n; + ae_int_t nx; + ae_int_t d; + double r; + ae_int_t nw; + kdtree tree; + ae_int_t modeltype; + ae_matrix q; + ae_vector xbuf; + ae_vector tbuf; + ae_vector rbuf; + ae_matrix xybuf; + ae_int_t debugsolverfailures; + double debugworstrcond; + double debugbestrcond; +} idwinterpolant; +typedef struct +{ + ae_int_t n; + double sy; + ae_vector x; + ae_vector y; + ae_vector w; +} barycentricinterpolant; +typedef struct +{ + ae_bool periodic; + ae_int_t n; + ae_int_t k; + ae_int_t continuity; + ae_vector x; + ae_vector c; +} spline1dinterpolant; +typedef struct +{ + double taskrcond; + double rmserror; + double avgerror; + double avgrelerror; + double maxerror; +} polynomialfitreport; +typedef struct +{ + double taskrcond; + ae_int_t dbest; + double rmserror; + double avgerror; + double avgrelerror; + double maxerror; +} barycentricfitreport; +typedef struct +{ + double taskrcond; + double rmserror; + double avgerror; + double avgrelerror; + double maxerror; +} spline1dfitreport; +typedef struct +{ + double taskrcond; + ae_int_t iterationscount; + ae_int_t varidx; + double rmserror; + double avgerror; + double avgrelerror; + double maxerror; + double wrmserror; + ae_matrix covpar; + ae_vector errpar; + ae_vector errcurve; + ae_vector noise; + double r2; +} lsfitreport; +typedef struct +{ + ae_int_t optalgo; + ae_int_t m; + ae_int_t k; + double epsf; + double epsx; + ae_int_t maxits; + double stpmax; + ae_bool xrep; + ae_vector s; + ae_vector bndl; + ae_vector bndu; + ae_matrix taskx; + ae_vector tasky; + ae_int_t npoints; + ae_vector taskw; + ae_int_t nweights; + ae_int_t wkind; + ae_int_t wits; + double diffstep; + double teststep; + ae_bool xupdated; + ae_bool needf; + ae_bool needfg; + ae_bool needfgh; + ae_int_t pointindex; + ae_vector x; + ae_vector c; + double f; + ae_vector g; + ae_matrix h; + ae_vector wcur; + ae_vector tmp; + ae_vector tmpf; + ae_matrix tmpjac; + ae_matrix tmpjacw; + double tmpnoise; + matinvreport invrep; + ae_int_t repiterationscount; + ae_int_t repterminationtype; + ae_int_t repvaridx; + double reprmserror; + double repavgerror; + double repavgrelerror; + double repmaxerror; + double repwrmserror; + lsfitreport rep; + minlmstate optstate; + minlmreport optrep; + ae_int_t prevnpt; + ae_int_t prevalgo; + rcommstate rstate; +} lsfitstate; +typedef struct +{ + ae_int_t n; + ae_bool periodic; + ae_vector p; + spline1dinterpolant x; + spline1dinterpolant y; +} pspline2interpolant; +typedef struct +{ + ae_int_t n; + ae_bool periodic; + ae_vector p; + spline1dinterpolant x; + spline1dinterpolant y; + spline1dinterpolant z; +} pspline3interpolant; +typedef struct +{ + ae_int_t ny; + ae_int_t nx; + ae_int_t nc; + ae_int_t nl; + kdtree tree; + ae_matrix xc; + ae_matrix wr; + double rmax; + ae_matrix v; + ae_int_t gridtype; + ae_bool fixrad; + double lambdav; + double radvalue; + double radzvalue; + ae_int_t nlayers; + ae_int_t aterm; + ae_int_t algorithmtype; + double epsort; + double epserr; + ae_int_t maxits; + double h; + ae_int_t n; + ae_matrix x; + ae_matrix y; + ae_vector calcbufxcx; + ae_matrix calcbufx; + ae_vector calcbuftags; +} rbfmodel; +typedef struct +{ + ae_int_t arows; + ae_int_t acols; + ae_int_t annz; + ae_int_t iterationscount; + ae_int_t nmv; + ae_int_t terminationtype; +} rbfreport; +typedef struct +{ + ae_int_t k; + ae_int_t stype; + ae_int_t n; + ae_int_t m; + ae_int_t d; + ae_vector x; + ae_vector y; + ae_vector f; +} spline2dinterpolant; +typedef struct +{ + ae_int_t k; + ae_int_t stype; + ae_int_t n; + ae_int_t m; + ae_int_t l; + ae_int_t d; + ae_vector x; + ae_vector y; + ae_vector z; + ae_vector f; +} spline3dinterpolant; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + +/************************************************************************* +IDW interpolant. +*************************************************************************/ +class _idwinterpolant_owner +{ +public: + _idwinterpolant_owner(); + _idwinterpolant_owner(const _idwinterpolant_owner &rhs); + _idwinterpolant_owner& operator=(const _idwinterpolant_owner &rhs); + virtual ~_idwinterpolant_owner(); + alglib_impl::idwinterpolant* c_ptr(); + alglib_impl::idwinterpolant* c_ptr() const; +protected: + alglib_impl::idwinterpolant *p_struct; +}; +class idwinterpolant : public _idwinterpolant_owner +{ +public: + idwinterpolant(); + idwinterpolant(const idwinterpolant &rhs); + idwinterpolant& operator=(const idwinterpolant &rhs); + virtual ~idwinterpolant(); + +}; + +/************************************************************************* +Barycentric interpolant. +*************************************************************************/ +class _barycentricinterpolant_owner +{ +public: + _barycentricinterpolant_owner(); + _barycentricinterpolant_owner(const _barycentricinterpolant_owner &rhs); + _barycentricinterpolant_owner& operator=(const _barycentricinterpolant_owner &rhs); + virtual ~_barycentricinterpolant_owner(); + alglib_impl::barycentricinterpolant* c_ptr(); + alglib_impl::barycentricinterpolant* c_ptr() const; +protected: + alglib_impl::barycentricinterpolant *p_struct; +}; +class barycentricinterpolant : public _barycentricinterpolant_owner +{ +public: + barycentricinterpolant(); + barycentricinterpolant(const barycentricinterpolant &rhs); + barycentricinterpolant& operator=(const barycentricinterpolant &rhs); + virtual ~barycentricinterpolant(); + +}; + + + +/************************************************************************* +1-dimensional spline interpolant +*************************************************************************/ +class _spline1dinterpolant_owner +{ +public: + _spline1dinterpolant_owner(); + _spline1dinterpolant_owner(const _spline1dinterpolant_owner &rhs); + _spline1dinterpolant_owner& operator=(const _spline1dinterpolant_owner &rhs); + virtual ~_spline1dinterpolant_owner(); + alglib_impl::spline1dinterpolant* c_ptr(); + alglib_impl::spline1dinterpolant* c_ptr() const; +protected: + alglib_impl::spline1dinterpolant *p_struct; +}; +class spline1dinterpolant : public _spline1dinterpolant_owner +{ +public: + spline1dinterpolant(); + spline1dinterpolant(const spline1dinterpolant &rhs); + spline1dinterpolant& operator=(const spline1dinterpolant &rhs); + virtual ~spline1dinterpolant(); + +}; + +/************************************************************************* +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 _polynomialfitreport_owner +{ +public: + _polynomialfitreport_owner(); + _polynomialfitreport_owner(const _polynomialfitreport_owner &rhs); + _polynomialfitreport_owner& operator=(const _polynomialfitreport_owner &rhs); + virtual ~_polynomialfitreport_owner(); + alglib_impl::polynomialfitreport* c_ptr(); + alglib_impl::polynomialfitreport* c_ptr() const; +protected: + alglib_impl::polynomialfitreport *p_struct; +}; +class polynomialfitreport : public _polynomialfitreport_owner +{ +public: + polynomialfitreport(); + polynomialfitreport(const polynomialfitreport &rhs); + polynomialfitreport& operator=(const polynomialfitreport &rhs); + virtual ~polynomialfitreport(); + double &taskrcond; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &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 _barycentricfitreport_owner +{ +public: + _barycentricfitreport_owner(); + _barycentricfitreport_owner(const _barycentricfitreport_owner &rhs); + _barycentricfitreport_owner& operator=(const _barycentricfitreport_owner &rhs); + virtual ~_barycentricfitreport_owner(); + alglib_impl::barycentricfitreport* c_ptr(); + alglib_impl::barycentricfitreport* c_ptr() const; +protected: + alglib_impl::barycentricfitreport *p_struct; +}; +class barycentricfitreport : public _barycentricfitreport_owner +{ +public: + barycentricfitreport(); + barycentricfitreport(const barycentricfitreport &rhs); + barycentricfitreport& operator=(const barycentricfitreport &rhs); + virtual ~barycentricfitreport(); + double &taskrcond; + ae_int_t &dbest; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &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 _spline1dfitreport_owner +{ +public: + _spline1dfitreport_owner(); + _spline1dfitreport_owner(const _spline1dfitreport_owner &rhs); + _spline1dfitreport_owner& operator=(const _spline1dfitreport_owner &rhs); + virtual ~_spline1dfitreport_owner(); + alglib_impl::spline1dfitreport* c_ptr(); + alglib_impl::spline1dfitreport* c_ptr() const; +protected: + alglib_impl::spline1dfitreport *p_struct; +}; +class spline1dfitreport : public _spline1dfitreport_owner +{ +public: + spline1dfitreport(); + spline1dfitreport(const spline1dfitreport &rhs); + spline1dfitreport& operator=(const spline1dfitreport &rhs); + virtual ~spline1dfitreport(); + double &taskrcond; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &maxerror; + +}; + + +/************************************************************************* +Least squares fitting report. This structure contains informational fields +which are set by fitting functions provided by this unit. + +Different functions initialize different sets of fields, so you should +read documentation on specific function you used in order to know which +fields are initialized. + + TaskRCond reciprocal of task's condition number + IterationsCount number of internal iterations + + VarIdx if user-supplied gradient contains errors which were + detected by nonlinear fitter, this field is set to + index of the first component of gradient which is + suspected to be spoiled by bugs. + + RMSError RMS error + AvgError average error + AvgRelError average relative error (for non-zero Y[I]) + MaxError maximum error + + WRMSError weighted RMS error + + CovPar covariance matrix for parameters, filled by some solvers + ErrPar vector of errors in parameters, filled by some solvers + ErrCurve vector of fit errors - variability of the best-fit + curve, filled by some solvers. + Noise vector of per-point noise estimates, filled by + some solvers. + R2 coefficient of determination (non-weighted, non-adjusted), + filled by some solvers. +*************************************************************************/ +class _lsfitreport_owner +{ +public: + _lsfitreport_owner(); + _lsfitreport_owner(const _lsfitreport_owner &rhs); + _lsfitreport_owner& operator=(const _lsfitreport_owner &rhs); + virtual ~_lsfitreport_owner(); + alglib_impl::lsfitreport* c_ptr(); + alglib_impl::lsfitreport* c_ptr() const; +protected: + alglib_impl::lsfitreport *p_struct; +}; +class lsfitreport : public _lsfitreport_owner +{ +public: + lsfitreport(); + lsfitreport(const lsfitreport &rhs); + lsfitreport& operator=(const lsfitreport &rhs); + virtual ~lsfitreport(); + double &taskrcond; + ae_int_t &iterationscount; + ae_int_t &varidx; + double &rmserror; + double &avgerror; + double &avgrelerror; + double &maxerror; + double &wrmserror; + real_2d_array covpar; + real_1d_array errpar; + real_1d_array errcurve; + real_1d_array noise; + double &r2; + +}; + + +/************************************************************************* +Nonlinear fitter. + +You should use ALGLIB functions to work with fitter. +Never try to access its fields directly! +*************************************************************************/ +class _lsfitstate_owner +{ +public: + _lsfitstate_owner(); + _lsfitstate_owner(const _lsfitstate_owner &rhs); + _lsfitstate_owner& operator=(const _lsfitstate_owner &rhs); + virtual ~_lsfitstate_owner(); + alglib_impl::lsfitstate* c_ptr(); + alglib_impl::lsfitstate* c_ptr() const; +protected: + alglib_impl::lsfitstate *p_struct; +}; +class lsfitstate : public _lsfitstate_owner +{ +public: + lsfitstate(); + lsfitstate(const lsfitstate &rhs); + lsfitstate& operator=(const lsfitstate &rhs); + virtual ~lsfitstate(); + ae_bool &needf; + ae_bool &needfg; + ae_bool &needfgh; + ae_bool &xupdated; + real_1d_array c; + double &f; + real_1d_array g; + real_2d_array h; + real_1d_array x; + +}; + +/************************************************************************* +Parametric spline inteprolant: 2-dimensional curve. + +You should not try to access its members directly - use PSpline2XXXXXXXX() +functions instead. +*************************************************************************/ +class _pspline2interpolant_owner +{ +public: + _pspline2interpolant_owner(); + _pspline2interpolant_owner(const _pspline2interpolant_owner &rhs); + _pspline2interpolant_owner& operator=(const _pspline2interpolant_owner &rhs); + virtual ~_pspline2interpolant_owner(); + alglib_impl::pspline2interpolant* c_ptr(); + alglib_impl::pspline2interpolant* c_ptr() const; +protected: + alglib_impl::pspline2interpolant *p_struct; +}; +class pspline2interpolant : public _pspline2interpolant_owner +{ +public: + pspline2interpolant(); + pspline2interpolant(const pspline2interpolant &rhs); + pspline2interpolant& operator=(const pspline2interpolant &rhs); + virtual ~pspline2interpolant(); + +}; + + +/************************************************************************* +Parametric spline inteprolant: 3-dimensional curve. + +You should not try to access its members directly - use PSpline3XXXXXXXX() +functions instead. +*************************************************************************/ +class _pspline3interpolant_owner +{ +public: + _pspline3interpolant_owner(); + _pspline3interpolant_owner(const _pspline3interpolant_owner &rhs); + _pspline3interpolant_owner& operator=(const _pspline3interpolant_owner &rhs); + virtual ~_pspline3interpolant_owner(); + alglib_impl::pspline3interpolant* c_ptr(); + alglib_impl::pspline3interpolant* c_ptr() const; +protected: + alglib_impl::pspline3interpolant *p_struct; +}; +class pspline3interpolant : public _pspline3interpolant_owner +{ +public: + pspline3interpolant(); + pspline3interpolant(const pspline3interpolant &rhs); + pspline3interpolant& operator=(const pspline3interpolant &rhs); + virtual ~pspline3interpolant(); + +}; + +/************************************************************************* +RBF model. + +Never try to directly work with fields of this object - always use ALGLIB +functions to use this object. +*************************************************************************/ +class _rbfmodel_owner +{ +public: + _rbfmodel_owner(); + _rbfmodel_owner(const _rbfmodel_owner &rhs); + _rbfmodel_owner& operator=(const _rbfmodel_owner &rhs); + virtual ~_rbfmodel_owner(); + alglib_impl::rbfmodel* c_ptr(); + alglib_impl::rbfmodel* c_ptr() const; +protected: + alglib_impl::rbfmodel *p_struct; +}; +class rbfmodel : public _rbfmodel_owner +{ +public: + rbfmodel(); + rbfmodel(const rbfmodel &rhs); + rbfmodel& operator=(const rbfmodel &rhs); + virtual ~rbfmodel(); + +}; + + +/************************************************************************* +RBF solution report: +* TerminationType - termination type, positive values - success, + non-positive - failure. +*************************************************************************/ +class _rbfreport_owner +{ +public: + _rbfreport_owner(); + _rbfreport_owner(const _rbfreport_owner &rhs); + _rbfreport_owner& operator=(const _rbfreport_owner &rhs); + virtual ~_rbfreport_owner(); + alglib_impl::rbfreport* c_ptr(); + alglib_impl::rbfreport* c_ptr() const; +protected: + alglib_impl::rbfreport *p_struct; +}; +class rbfreport : public _rbfreport_owner +{ +public: + rbfreport(); + rbfreport(const rbfreport &rhs); + rbfreport& operator=(const rbfreport &rhs); + virtual ~rbfreport(); + ae_int_t &arows; + ae_int_t &acols; + ae_int_t &annz; + ae_int_t &iterationscount; + ae_int_t &nmv; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +2-dimensional spline inteprolant +*************************************************************************/ +class _spline2dinterpolant_owner +{ +public: + _spline2dinterpolant_owner(); + _spline2dinterpolant_owner(const _spline2dinterpolant_owner &rhs); + _spline2dinterpolant_owner& operator=(const _spline2dinterpolant_owner &rhs); + virtual ~_spline2dinterpolant_owner(); + alglib_impl::spline2dinterpolant* c_ptr(); + alglib_impl::spline2dinterpolant* c_ptr() const; +protected: + alglib_impl::spline2dinterpolant *p_struct; +}; +class spline2dinterpolant : public _spline2dinterpolant_owner +{ +public: + spline2dinterpolant(); + spline2dinterpolant(const spline2dinterpolant &rhs); + spline2dinterpolant& operator=(const spline2dinterpolant &rhs); + virtual ~spline2dinterpolant(); + +}; + +/************************************************************************* +3-dimensional spline inteprolant +*************************************************************************/ +class _spline3dinterpolant_owner +{ +public: + _spline3dinterpolant_owner(); + _spline3dinterpolant_owner(const _spline3dinterpolant_owner &rhs); + _spline3dinterpolant_owner& operator=(const _spline3dinterpolant_owner &rhs); + virtual ~_spline3dinterpolant_owner(); + alglib_impl::spline3dinterpolant* c_ptr(); + alglib_impl::spline3dinterpolant* c_ptr() const; +protected: + alglib_impl::spline3dinterpolant *p_struct; +}; +class spline3dinterpolant : public _spline3dinterpolant_owner +{ +public: + spline3dinterpolant(); + spline3dinterpolant(const spline3dinterpolant &rhs); + spline3dinterpolant& operator=(const spline3dinterpolant &rhs); + virtual ~spline3dinterpolant(); + +}; + +/************************************************************************* +IDW interpolation + +INPUT PARAMETERS: + Z - IDW interpolant built with one of model building + subroutines. + X - array[0..NX-1], interpolation point + +Result: + IDW interpolant Z(X) + + -- ALGLIB -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +double idwcalc(const idwinterpolant &z, const real_1d_array &x); + + +/************************************************************************* +IDW interpolant using modified Shepard method for uniform point +distributions. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function type, either: + * 0 constant model. Just for demonstration only, worst + model ever. + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + * -1 "fast" linear model, use with caution!!! It is + significantly faster than linear/quadratic and better + than constant model. But it is less robust (especially + in the presence of noise). + NQ - number of points used to calculate nodal functions (ignored + for constant models). NQ should be LARGER than: + * max(1.5*(1+NX),2^NX+1) for linear model, + * max(3/4*(NX+2)*(NX+1),2^NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, worst - with constant + models + * when N is large, NQ and NW must be significantly smaller than N both + to obtain optimal performance and to obtain optimal accuracy. In 2 or + 3-dimensional tasks NQ=15 and NW=25 are good values to start with. + * NQ and NW may be greater than N. In such cases they will be + automatically decreased. + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + * this subroutine assumes that point distribution is uniform at the small + scales. If it isn't - for example, points are concentrated along + "lines", but "lines" distribution is uniform at the larger scale - then + you should use IDWBuildModifiedShepardR() + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepard(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t d, const ae_int_t nq, const ae_int_t nw, idwinterpolant &z); + + +/************************************************************************* +IDW interpolant using modified Shepard method for non-uniform datasets. + +This type of model uses constant nodal functions and interpolates using +all nodes which are closer than user-specified radius R. It may be used +when points distribution is non-uniform at the small scale, but it is at +the distances as large as R. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + R - radius, R>0 + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: +* if there is less than IDWKMin points within R-ball, algorithm selects + IDWKMin closest ones, so that continuity properties of interpolant are + preserved even far from points. + + -- ALGLIB PROJECT -- + Copyright 11.04.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildmodifiedshepardr(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const double r, idwinterpolant &z); + + +/************************************************************************* +IDW model for noisy data. + +This subroutine may be used to handle noisy data, i.e. data with noise in +OUTPUT values. It differs from IDWBuildModifiedShepard() in the following +aspects: +* nodal functions are not constrained to pass through nodes: Qi(xi)<>yi, + i.e. we have fitting instead of interpolation. +* weights which are used during least squares fitting stage are all equal + to 1.0 (independently of distance) +* "fast"-linear or constant nodal functions are not supported (either not + robust enough or too rigid) + +This problem require far more complex tuning than interpolation problems. +Below you can find some recommendations regarding this problem: +* focus on tuning NQ; it controls noise reduction. As for NW, you can just + make it equal to 2*NQ. +* you can use cross-validation to determine optimal NQ. +* optimal NQ is a result of complex tradeoff between noise level (more + noise = larger NQ required) and underlying function complexity (given + fixed N, larger NQ means smoothing of compex features in the data). For + example, NQ=N will reduce noise to the minimum level possible, but you + will end up with just constant/linear/quadratic (depending on D) least + squares model for the whole dataset. + +INPUT PARAMETERS: + XY - X and Y values, array[0..N-1,0..NX]. + First NX columns contain X-values, last column contain + Y-values. + N - number of nodes, N>0. + NX - space dimension, NX>=1. + D - nodal function degree, either: + * 1 linear model, least squares fitting. Simpe model for + datasets too small for quadratic models (or for very + noisy problems). + * 2 quadratic model, least squares fitting. Best model + available (if your dataset is large enough). + NQ - number of points used to calculate nodal functions. NQ should + be significantly larger than 1.5 times the number of + coefficients in a nodal function to overcome effects of noise: + * larger than 1.5*(1+NX) for linear model, + * larger than 3/4*(NX+2)*(NX+1) for quadratic model. + Values less than this threshold will be silently increased. + NW - number of points used to calculate weights and to interpolate. + Required: >=2^NX+1, values less than this threshold will be + silently increased. + Recommended value: about 2*NQ or larger + +OUTPUT PARAMETERS: + Z - IDW interpolant. + +NOTES: + * best results are obtained with quadratic models, linear models are not + recommended to use unless you are pretty sure that it is what you want + * this subroutine is always succeeds (as long as correct parameters are + passed). + * see 'Multivariate Interpolation of Large Sets of Scattered Data' by + Robert J. Renka for more information on this algorithm. + + + -- ALGLIB PROJECT -- + Copyright 02.03.2010 by Bochkanov Sergey +*************************************************************************/ +void idwbuildnoisy(const real_2d_array &xy, const ae_int_t n, const ae_int_t nx, const ae_int_t d, const ae_int_t nq, const ae_int_t nw, idwinterpolant &z); + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +double barycentriccalc(const barycentricinterpolant &b, const double t); + + +/************************************************************************* +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 + +NOTE + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff1(const barycentricinterpolant &b, const double t, double &f, double &df); + + +/************************************************************************* +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. + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricdiff2(const barycentricinterpolant &b, const double t, double &f, double &df, double &d2f); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransx(const barycentricinterpolant &b, const double ca, const double cb); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 19.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentriclintransy(const barycentricinterpolant &b, const double ca, const double cb); + + +/************************************************************************* +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] + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricunpack(const barycentricinterpolant &b, ae_int_t &n, real_1d_array &x, real_1d_array &y, real_1d_array &w); + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildxyw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, barycentricinterpolant &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. + + -- ALGLIB PROJECT -- + Copyright 17.06.2007 by Bochkanov Sergey +*************************************************************************/ +void barycentricbuildfloaterhormann(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t d, barycentricinterpolant &b); + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2cheb(const barycentricinterpolant &p, const double a, const double b, real_1d_array &t); + + +/************************************************************************* +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) + A0. + +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialbar2pow(const barycentricinterpolant &p, const double c, const double s, real_1d_array &a); +void polynomialbar2pow(const barycentricinterpolant &p, real_1d_array &a); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 30.09.2010 by Bochkanov Sergey +*************************************************************************/ +void polynomialpow2bar(const real_1d_array &a, const ae_int_t n, const double c, const double s, barycentricinterpolant &p); +void polynomialpow2bar(const real_1d_array &a, barycentricinterpolant &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). + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuild(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p); +void polynomialbuild(const real_1d_array &x, const real_1d_array &y, barycentricinterpolant &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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildeqdist(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p); +void polynomialbuildeqdist(const double a, const double b, const real_1d_array &y, barycentricinterpolant &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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb1(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p); +void polynomialbuildcheb1(const double a, const double b, const real_1d_array &y, barycentricinterpolant &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). + + -- ALGLIB -- + Copyright 03.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialbuildcheb2(const double a, const double b, const real_1d_array &y, const ae_int_t n, barycentricinterpolant &p); +void polynomialbuildcheb2(const double a, const double b, const real_1d_array &y, barycentricinterpolant &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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalceqdist(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t); +double polynomialcalceqdist(const double a, const double b, const real_1d_array &f, const double t); + + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb1(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t); +double polynomialcalccheb1(const double a, const double b, const real_1d_array &f, const double t); + + +/************************************************************************* +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()/BarycentricCalc() + subroutines unless you are pretty sure that your data will not result + in overflow. + + -- ALGLIB -- + Copyright 02.12.2009 by Bochkanov Sergey +*************************************************************************/ +double polynomialcalccheb2(const double a, const double b, const real_1d_array &f, const ae_int_t n, const double t); +double polynomialcalccheb2(const double a, const double b, const real_1d_array &f, const double t); + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildlinear(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c); +void spline1dbuildlinear(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, spline1dinterpolant &c); +void spline1dbuildcubic(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiffcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, real_1d_array &d); +void spline1dgriddiffcubic(const real_1d_array &x, const real_1d_array &y, real_1d_array &d); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dgriddiff2cubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, real_1d_array &d1, real_1d_array &d2); +void spline1dgriddiff2cubic(const real_1d_array &x, const real_1d_array &y, real_1d_array &d1, real_1d_array &d2); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2); +void spline1dconvcubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiffcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2, real_1d_array &d2); +void spline1dconvdiffcubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2, real_1d_array &d2); + + +/************************************************************************* +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]. + + -- ALGLIB PROJECT -- + Copyright 03.09.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dconvdiff2cubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t boundltype, const double boundl, const ae_int_t boundrtype, const double boundr, const real_1d_array &x2, const ae_int_t n2, real_1d_array &y2, real_1d_array &d2, real_1d_array &dd2); +void spline1dconvdiff2cubic(const real_1d_array &x, const real_1d_array &y, const real_1d_array &x2, real_1d_array &y2, real_1d_array &d2, real_1d_array &dd2); + + +/************************************************************************* +This subroutine builds Catmull-Rom spline interpolant. + +INPUT PARAMETERS: + X - spline nodes, array[0..N-1]. + Y - function values, array[0..N-1]. + +OPTIONAL PARAMETERS: + N - points count: + * N>=2 + * if given, only first N points are used to build spline + * if not given, automatically detected from X/Y sizes + (len(X) must be equal to len(Y)) + BoundType - boundary condition type: + * -1 for periodic boundary condition + * 0 for parabolically terminated spline (default) + Tension - tension parameter: + * tension=0 corresponds to classic Catmull-Rom spline (default) + * 0=2 + * 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. + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildhermite(const real_1d_array &x, const real_1d_array &y, const real_1d_array &d, const ae_int_t n, spline1dinterpolant &c); +void spline1dbuildhermite(const real_1d_array &x, const real_1d_array &y, const real_1d_array &d, spline1dinterpolant &c); + + +/************************************************************************* +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>=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. + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildakima(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c); +void spline1dbuildakima(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c); + + +/************************************************************************* +This subroutine calculates the value of the spline at the given point X. + +INPUT PARAMETERS: + C - spline interpolant + X - point + +Result: + S(x) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dcalc(const spline1dinterpolant &c, const double x); + + +/************************************************************************* +This subroutine differentiates the spline. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +Result: + S - S(x) + DS - S'(x) + D2S - S''(x) + + -- ALGLIB PROJECT -- + Copyright 24.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1ddiff(const spline1dinterpolant &c, const double x, double &s, double &ds, double &d2s); + + +/************************************************************************* +This subroutine unpacks the spline into the coefficients table. + +INPUT PARAMETERS: + C - spline interpolant. + X - point + +OUTPUT PARAMETERS: + 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] + +NOTE: + You can rebuild spline with Spline1DBuildHermite() function, which + accepts as inputs function values and derivatives at nodes, which are + easy to calculate when you have coefficients. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dunpack(const spline1dinterpolant &c, ae_int_t &n, real_2d_array &tbl); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransx(const spline1dinterpolant &c, const double a, const double b); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline1dlintransy(const spline1dinterpolant &c, const double a, const double b); + + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 23.06.2007 by Bochkanov Sergey +*************************************************************************/ +double spline1dintegrate(const spline1dinterpolant &c, const double x); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 21.06.2012 by Bochkanov Sergey +*************************************************************************/ +void spline1dbuildmonotone(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, spline1dinterpolant &c); +void spline1dbuildmonotone(const real_1d_array &x, const real_1d_array &y, spline1dinterpolant &c); + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfit(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep); +void polynomialfit(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &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=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. + + -- ALGLIB PROJECT -- + Copyright 10.12.2009 by Bochkanov Sergey +*************************************************************************/ +void polynomialfitwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep); +void polynomialfitwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, barycentricinterpolant &p, polynomialfitreport &rep); + + +/************************************************************************* +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=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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormannwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, barycentricinterpolant &b, barycentricfitreport &rep); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void barycentricfitfloaterhormann(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, barycentricinterpolant &b, barycentricfitreport &rep); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalized(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfitpenalized(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); + + +/************************************************************************* +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 xmax(x,xc). It is done +for consistency - we penalize non-linearity at [min(x,xc),max(x,xc)], so +it is natural to force linearity outside of this interval. + +NOTE 2: function automatically sorts points, so caller may pass unsorted +array. + + -- ALGLIB PROJECT -- + Copyright 19.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitpenalizedw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfitpenalizedw(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t m, const double rho, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &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=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. + + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubicwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfitcubicwc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &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=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 + +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 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. + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermitewc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const ae_int_t n, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t k, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfithermitewc(const real_1d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &xc, const real_1d_array &yc, const integer_1d_array &dc, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &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) + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfitcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfitcubic(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &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). + + -- ALGLIB PROJECT -- + Copyright 18.08.2009 by Bochkanov Sergey +*************************************************************************/ +void spline1dfithermite(const real_1d_array &x, const real_1d_array &y, const ae_int_t n, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &rep); +void spline1dfithermite(const real_1d_array &x, const real_1d_array &y, const ae_int_t m, ae_int_t &info, spline1dinterpolant &s, spline1dfitreport &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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearw(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const ae_int_t n, const ae_int_t m, ae_int_t &info, real_1d_array &c, lsfitreport &rep); +void lsfitlinearw(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearwc(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const real_2d_array &cmatrix, const ae_int_t n, const ae_int_t m, const ae_int_t k, ae_int_t &info, real_1d_array &c, lsfitreport &rep); +void lsfitlinearwc(const real_1d_array &y, const real_1d_array &w, const real_2d_array &fmatrix, const real_2d_array &cmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep); + + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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 + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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 + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinear(const real_1d_array &y, const real_2d_array &fmatrix, const ae_int_t n, const ae_int_t m, ae_int_t &info, real_1d_array &c, lsfitreport &rep); +void lsfitlinear(const real_1d_array &y, const real_2d_array &fmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &rep); + + +/************************************************************************* +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. + +IMPORTANT: if you want to perform polynomial fitting, it may be more + convenient to use PolynomialFit() function. This function gives + best results on polynomial problems and solves numerical + stability issues which arise when you fit high-degree + polynomials to your data. + +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: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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. + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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(F*CovPar*F')), + where F is functions matrix. +* Rep.Noise vector of per-point estimates of noise, array[N] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 07.09.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitlinearc(const real_1d_array &y, const real_2d_array &fmatrix, const real_2d_array &cmatrix, const ae_int_t n, const ae_int_t m, const ae_int_t k, ae_int_t &info, real_1d_array &c, lsfitreport &rep); +void lsfitlinearc(const real_1d_array &y, const real_2d_array &fmatrix, const real_2d_array &cmatrix, ae_int_t &info, real_1d_array &c, lsfitreport &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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewf(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const double diffstep, lsfitstate &state); +void lsfitcreatewf(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const double diffstep, lsfitstate &state); + + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 18.10.2008 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatef(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const double diffstep, lsfitstate &state); +void lsfitcreatef(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const double diffstep, lsfitstate &state); + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const bool cheapfg, lsfitstate &state); +void lsfitcreatewfg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const bool cheapfg, lsfitstate &state); + + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, const bool cheapfg, lsfitstate &state); +void lsfitcreatefg(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const bool cheapfg, lsfitstate &state); + + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatewfgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, lsfitstate &state); +void lsfitcreatewfgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &w, const real_1d_array &c, lsfitstate &state); + + +/************************************************************************* +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 + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitcreatefgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, const ae_int_t n, const ae_int_t m, const ae_int_t k, lsfitstate &state); +void lsfitcreatefgh(const real_2d_array &x, const real_1d_array &y, const real_1d_array &c, lsfitstate &state); + + +/************************************************************************* +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). + + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetcond(const lsfitstate &state, const double epsf, const double epsx, const ae_int_t 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetstpmax(const lsfitstate &state, const double 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. + + + -- ALGLIB -- + Copyright 15.08.2010 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetxrep(const lsfitstate &state, const bool 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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetscale(const lsfitstate &state, const real_1d_array &s); + + +/************************************************************************* +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 + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetbc(const lsfitstate &state, const real_1d_array &bndl, const real_1d_array &bndu); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool lsfititeration(const lsfitstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear fitter + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + hess - callback which calculates function (or merit function) + value func, gradient grad and Hessian hess at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + +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. + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey + +*************************************************************************/ +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr) = NULL, + void *ptr = NULL); +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr) = NULL, + void *ptr = NULL); +void lsfitfit(lsfitstate &state, + void (*func)(const real_1d_array &c, const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*hess)(const real_1d_array &c, const real_1d_array &x, double &func, real_1d_array &grad, real_2d_array &hess, void *ptr), + void (*rep)(const real_1d_array &c, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +Nonlinear least squares fitting results. + +Called after return from LSFitFit(). + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Info - completion code: + * -7 gradient verification failed. + See LSFitSetGradientCheck() for more information. + * 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. On success following fields are set: + * R2 non-adjusted coefficient of determination + (non-weighted) + * 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). + +ERRORS IN PARAMETERS + +This solver also calculates different kinds of errors in parameters and +fills corresponding fields of report: +* 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] + +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. + +NOTE: covariance matrix is estimated using correction for degrees + of freedom (covariances are divided by N-M instead of dividing + by N). + + -- ALGLIB -- + Copyright 17.08.2009 by Bochkanov Sergey +*************************************************************************/ +void lsfitresults(const lsfitstate &state, ae_int_t &info, real_1d_array &c, lsfitreport &rep); + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before fitting begins +* LSFitFit() is called +* prior to actual fitting, for each point in data set X_i and each + component of parameters being fited C_j algorithm performs following + steps: + * two trial steps are made to C_j-TestStep*S[j] and C_j+TestStep*S[j], + where C_j is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on C[] + * F(X_i|C) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N*K (points count * parameters count) gradient + evaluations. It is very costly and you should use it only for low + dimensional problems, when you want to be sure that you've + correctly calculated analytic derivatives. You should not use it + in the production code (unless you want to check derivatives + provided by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with LSFitSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +NOTE 4: this function works only for optimizers created with LSFitCreateWFG() + or LSFitCreateFG() constructors. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void lsfitsetgradientcheck(const lsfitstate &state, const double teststep); + +/************************************************************************* +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). + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2build(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline2interpolant &p); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3build(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline3interpolant &p); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2buildperiodic(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline2interpolant &p); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3buildperiodic(const real_2d_array &xy, const ae_int_t n, const ae_int_t st, const ae_int_t pt, pspline3interpolant &p); + + +/************************************************************************* +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]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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2calc(const pspline2interpolant &p, const double t, double &x, double &y); + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3calc(const pspline3interpolant &p, const double t, double &x, double &y, double &z); + + +/************************************************************************* +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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2tangent(const pspline2interpolant &p, const double t, double &x, double &y); + + +/************************************************************************* +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. + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3tangent(const pspline3interpolant &p, const double t, double &x, double &y, double &z); + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff(const pspline2interpolant &p, const double t, double &x, double &dx, double &y, double &dy); + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff(const pspline3interpolant &p, const double t, double &x, double &dx, double &y, double &dy, double &z, double &dz); + + +/************************************************************************* +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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline2diff2(const pspline2interpolant &p, const double t, double &x, double &dx, double &d2x, double &y, double &dy, double &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 + + + -- ALGLIB PROJECT -- + Copyright 28.05.2010 by Bochkanov Sergey +*************************************************************************/ +void pspline3diff2(const pspline3interpolant &p, const double t, double &x, double &dx, double &d2x, double &y, double &dy, double &d2y, double &z, double &dz, double &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 + * BA will result in positive length returned + * B1) +function in a NX-dimensional space (NX=2 or NX=3). + +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() (points do NOT have to be on a + regular grid) +3. (OPTIONAL) User chooses polynomial term by calling: + * RBFLinTerm() to set linear term + * RBFConstTerm() to set constant term + * RBFZeroTerm() to set zero term + By default, linear term is used. +4. User chooses specific RBF algorithm to use: either QNN (RBFSetAlgoQNN) + or ML (RBFSetAlgoMultiLayer). +5. User calls RBFBuildModel() function which rebuilds model according to + the specification +6. User may call RBFCalc() to calculate model value at the specified point, + RBFGridCalc() to calculate model values at the points of the regular + grid. User may extract model coefficients with RBFUnpack() call. + +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) + +NOTE 1: memory requirements. RBF models require amount of memory which is + proportional to the number of data points. Memory is allocated + during model construction, but most of this memory is freed after + model coefficients are calculated. + + Some approximate estimates for N centers with default settings are + given below: + * about 250*N*(sizeof(double)+2*sizeof(int)) bytes of memory is + needed during model construction stage. + * about 15*N*sizeof(double) bytes is needed after model is built. + For example, for N=100000 we may need 0.6 GB of memory to build + model, but just about 0.012 GB to store it. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcreate(const ae_int_t nx, const ae_int_t ny, rbfmodel &s); + + +/************************************************************************* +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. + +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 + specific, 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: 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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetpoints(const rbfmodel &s, const real_2d_array &xy, const ae_int_t n); +void rbfsetpoints(const rbfmodel &s, const real_2d_array &xy); + + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-QNN and it is good for point sets with +following properties: +a) all points are distinct +b) all points are well separated. +c) points distribution is approximately uniform. There is no "contour + lines", clusters of points, or other small-scale structures. + +Algorithm description: +1) interpolation centers are allocated to data points +2) interpolation radii are calculated as distances to the nearest centers + times Q coefficient (where Q is a value from [0.75,1.50]). +3) after performing (2) radii are transformed in order to avoid situation + when single outlier has very large radius and influences many points + across all dataset. Transformation has following form: + new_r[i] = min(r[i],Z*median(r[])) + where r[i] is I-th radius, median() is a median radius across entire + dataset, Z is user-specified value which controls amount of deviation + from median radius. + +When (a) is violated, we will be unable to build RBF model. When (b) or +(c) are violated, model will be built, but interpolation quality will be +low. See http://www.alglib.net/interpolation/ for more information on this +subject. + +This algorithm is used by default. + +Additional Q parameter controls smoothness properties of the RBF basis: +* Q<0.75 will give perfectly conditioned basis, but terrible smoothness + properties (RBF interpolant will have sharp peaks around function values) +* Q around 1.0 gives good balance between smoothness and condition number +* Q>1.5 will lead to badly conditioned systems and slow convergence of the + underlying linear solver (although smoothness will be very good) +* Q>2.0 will effectively make optimizer useless because it won't converge + within reasonable amount of iterations. It is possible to set such large + Q, but it is advised not to do so. + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + Q - Q parameter, Q>0, recommended value - 1.0 + Z - Z parameter, Z>0, recommended value - 5.0 + +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. + + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgoqnn(const rbfmodel &s, const double q, const double z); +void rbfsetalgoqnn(const rbfmodel &s); + + +/************************************************************************* +This function sets RBF interpolation algorithm. ALGLIB supports several +RBF algorithms with different properties. + +This algorithm is called RBF-ML. It builds multilayer RBF model, i.e. +model with subsequently decreasing radii, which allows us to combine +smoothness (due to large radii of the first layers) with exactness (due +to small radii of the last layers) and fast convergence. + +Internally RBF-ML uses many different means of acceleration, from sparse +matrices to KD-trees, which results in algorithm whose working time is +roughly proportional to N*log(N)*Density*RBase^2*NLayers, where N is a +number of points, Density is an average density if points per unit of the +interpolation space, RBase is an initial radius, NLayers is a number of +layers. + +RBF-ML is good for following kinds of interpolation problems: +1. "exact" problems (perfect fit) with well separated points +2. least squares problems with arbitrary distribution of points (algorithm + gives perfect fit where it is possible, and resorts to least squares + fit in the hard areas). +3. noisy problems where we want to apply some controlled amount of + smoothing. + +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. + LambdaV - regularization value, can be useful when solving problem + in the least squares sense. Optimal lambda is problem- + dependent and require trial and error. In our experience, + good lambda can be as large as 0.1, and you can use 0.001 + as initial guess. + Default value - 0.01, which is used when LambdaV is not + given. You can specify zero value, but it is not + recommended to do so. + +TUNING ALGORITHM + +In order to use this algorithm you have to choose three parameters: +* initial radius RBase +* number of layers in the model NLayers +* regularization coefficient LambdaV + +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. + +Regularization coefficient LambdaV controls smoothness of the individual +models built for each layer. We recommend you to use default value in case +you don't want to tune this parameter, because having non-zero LambdaV +accelerates and stabilizes internal iterative algorithm. In case you want +to suppress noise you can use LambdaV as additional parameter (larger +value = more smoothness) to tune. + +TYPICAL ERRORS + +1. Using initial radius which is too large. Memory requirements of the + RBF-ML are roughly proportional to N*Density*RBase^2 (where Density is + an average density of points per unit of the interpolation space). In + the extreme case of the very large RBase we will need O(N^2) units of + memory - and many layers in order to decrease radius to some reasonably + small value. + +2. 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. + +3. 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. + +4. Using too many layers - you will get too large and too slow model. This + model will perfectly reproduce your function, but maybe you will be + able to achieve similar results with less layers (and less memory). + + -- ALGLIB -- + Copyright 02.03.2012 by Bochkanov Sergey +*************************************************************************/ +void rbfsetalgomultilayer(const rbfmodel &s, const double rbase, const ae_int_t nlayers, const double lambdav); +void rbfsetalgomultilayer(const rbfmodel &s, const double rbase, const ae_int_t nlayers); + + +/************************************************************************* +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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetlinterm(const rbfmodel &s); + + +/************************************************************************* +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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetconstterm(const rbfmodel &s); + + +/************************************************************************* +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(). + +INPUT PARAMETERS: + S - RBF model, initialized by RBFCreate() call + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfsetzeroterm(const rbfmodel &s); + + +/************************************************************************* +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 + * -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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfbuildmodel(const rbfmodel &s, rbfreport &rep); + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc2(const rbfmodel &s, const double x0, const double x1); + + +/************************************************************************* +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) + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +double rbfcalc3(const rbfmodel &s, const double x0, const double x1, const double x2); + + +/************************************************************************* +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(). + +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalc(const rbfmodel &s, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfcalcbuf(const rbfmodel &s, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfgridcalc2(const rbfmodel &s, const real_1d_array &x0, const ae_int_t n0, const real_1d_array &x1, const ae_int_t n1, real_2d_array &y); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 13.12.2011 by Bochkanov Sergey +*************************************************************************/ +void rbfunpack(const rbfmodel &s, ae_int_t &nx, ae_int_t &ny, real_2d_array &xwr, ae_int_t &nc, real_2d_array &v); + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +double spline2dcalc(const spline2dinterpolant &c, const double x, const double y); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2ddiff(const spline2dinterpolant &c, const double x, const double y, double &f, double &fx, double &fy, double &fxy); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransxy(const spline2dinterpolant &c, const double ax, const double bx, const double ay, const double by); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 30.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dlintransf(const spline2dinterpolant &c, const double a, const double b); + + +/************************************************************************* +This subroutine makes the copy of the spline model. + +Input parameters: + C - spline interpolant + +Output parameters: + CC - spline copy + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dcopy(const spline2dinterpolant &c, spline2dinterpolant &cc); + + +/************************************************************************* +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] + + -- ALGLIB routine -- + 15 May, 2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebicubic(const real_2d_array &a, const ae_int_t oldheight, const ae_int_t oldwidth, real_2d_array &b, const ae_int_t newheight, const ae_int_t newwidth); + + +/************************************************************************* +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] + + -- ALGLIB routine -- + 09.07.2007 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline2dresamplebilinear(const real_2d_array &a, const ae_int_t oldheight, const ae_int_t oldwidth, real_2d_array &b, const ae_int_t newheight, const ae_int_t newwidth); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinearv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &f, const ae_int_t d, spline2dinterpolant &c); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubicv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &f, const ae_int_t d, spline2dinterpolant &c); + + +/************************************************************************* +This subroutine calculates bilinear or bicubic vector-valued spline at the +given point (X,Y). + +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 + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcvbuf(const spline2dinterpolant &c, const double x, const double y, real_1d_array &f); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dcalcv(const spline2dinterpolant &c, const double x, const double y, real_1d_array &f); + + +/************************************************************************* +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] + + -- ALGLIB PROJECT -- + Copyright 16.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpackv(const spline2dinterpolant &c, ae_int_t &m, ae_int_t &n, ae_int_t &d, real_2d_array &tbl); + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBilinearV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbilinear(const real_1d_array &x, const real_1d_array &y, const real_2d_array &f, const ae_int_t m, const ae_int_t n, spline2dinterpolant &c); + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DBuildBicubicV(), which is more +flexible and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 05.07.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dbuildbicubic(const real_1d_array &x, const real_1d_array &y, const real_2d_array &f, const ae_int_t m, const ae_int_t n, spline2dinterpolant &c); + + +/************************************************************************* +This subroutine was deprecated in ALGLIB 3.6.0 + +We recommend you to switch to Spline2DUnpackV(), which is more flexible +and accepts its arguments in more convenient order. + + -- ALGLIB PROJECT -- + Copyright 29.06.2007 by Bochkanov Sergey +*************************************************************************/ +void spline2dunpack(const spline2dinterpolant &c, ae_int_t &m, ae_int_t &n, real_2d_array &tbl); + +/************************************************************************* +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) + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +double spline3dcalc(const spline3dinterpolant &c, const double x, const double y, const double z); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransxyz(const spline3dinterpolant &c, const double ax, const double bx, const double ay, const double by, const double az, const double bz); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dlintransf(const spline3dinterpolant &c, const double a, const double b); + + +/************************************************************************* +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[..] ... + ... + + -- ALGLIB routine -- + 26.04.2012 + Copyright by Bochkanov Sergey +*************************************************************************/ +void spline3dresampletrilinear(const real_1d_array &a, const ae_int_t oldzcount, const ae_int_t oldycount, const ae_int_t oldxcount, const ae_int_t newzcount, const ae_int_t newycount, const ae_int_t newxcount, real_1d_array &b); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dbuildtrilinearv(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, const real_1d_array &z, const ae_int_t l, const real_1d_array &f, const ae_int_t d, spline3dinterpolant &c); + + +/************************************************************************* +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 + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcvbuf(const spline3dinterpolant &c, const double x, const double y, const double z, real_1d_array &f); + + +/************************************************************************* +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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dcalcv(const spline3dinterpolant &c, const double x, const double y, const double z, real_1d_array &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. + + -- ALGLIB PROJECT -- + Copyright 26.04.2012 by Bochkanov Sergey +*************************************************************************/ +void spline3dunpackv(const spline3dinterpolant &c, ae_int_t &n, ae_int_t &m, ae_int_t &l, ae_int_t &d, ae_int_t &stype, real_2d_array &tbl); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +double idwcalc(idwinterpolant* z, + /* Real */ ae_vector* x, + ae_state *_state); +void idwbuildmodifiedshepard(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state); +void idwbuildmodifiedshepardr(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + double r, + idwinterpolant* z, + ae_state *_state); +void idwbuildnoisy(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t nx, + ae_int_t d, + ae_int_t nq, + ae_int_t nw, + idwinterpolant* z, + ae_state *_state); +ae_bool _idwinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _idwinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _idwinterpolant_clear(void* _p); +void _idwinterpolant_destroy(void* _p); +double barycentriccalc(barycentricinterpolant* b, + double t, + ae_state *_state); +void barycentricdiff1(barycentricinterpolant* b, + double t, + double* f, + double* df, + ae_state *_state); +void barycentricdiff2(barycentricinterpolant* b, + double t, + double* f, + double* df, + double* d2f, + ae_state *_state); +void barycentriclintransx(barycentricinterpolant* b, + double ca, + double cb, + ae_state *_state); +void barycentriclintransy(barycentricinterpolant* b, + double ca, + double cb, + ae_state *_state); +void barycentricunpack(barycentricinterpolant* b, + ae_int_t* n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_state *_state); +void barycentricbuildxyw(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + barycentricinterpolant* b, + ae_state *_state); +void barycentricbuildfloaterhormann(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t d, + barycentricinterpolant* b, + ae_state *_state); +void barycentriccopy(barycentricinterpolant* b, + barycentricinterpolant* b2, + ae_state *_state); +ae_bool _barycentricinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _barycentricinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _barycentricinterpolant_clear(void* _p); +void _barycentricinterpolant_destroy(void* _p); +void polynomialbar2cheb(barycentricinterpolant* p, + double a, + double b, + /* Real */ ae_vector* t, + ae_state *_state); +void polynomialcheb2bar(/* Real */ ae_vector* t, + ae_int_t n, + double a, + double b, + barycentricinterpolant* p, + ae_state *_state); +void polynomialbar2pow(barycentricinterpolant* p, + double c, + double s, + /* Real */ ae_vector* a, + ae_state *_state); +void polynomialpow2bar(/* Real */ ae_vector* a, + ae_int_t n, + double c, + double s, + barycentricinterpolant* p, + ae_state *_state); +void polynomialbuild(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state); +void polynomialbuildeqdist(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state); +void polynomialbuildcheb1(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state); +void polynomialbuildcheb2(double a, + double b, + /* Real */ ae_vector* y, + ae_int_t n, + barycentricinterpolant* p, + ae_state *_state); +double polynomialcalceqdist(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state); +double polynomialcalccheb1(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state); +double polynomialcalccheb2(double a, + double b, + /* Real */ ae_vector* f, + ae_int_t n, + double t, + ae_state *_state); +void spline1dbuildlinear(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state); +void spline1dbuildcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + spline1dinterpolant* c, + ae_state *_state); +void spline1dgriddiffcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d, + ae_state *_state); +void spline1dgriddiff2cubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* d1, + /* Real */ ae_vector* d2, + ae_state *_state); +void spline1dconvcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + ae_state *_state); +void spline1dconvdiffcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + /* Real */ ae_vector* d2, + ae_state *_state); +void spline1dconvdiff2cubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundltype, + double boundl, + ae_int_t boundrtype, + double boundr, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y2, + /* Real */ ae_vector* d2, + /* Real */ ae_vector* dd2, + ae_state *_state); +void spline1dbuildcatmullrom(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t boundtype, + double tension, + spline1dinterpolant* c, + ae_state *_state); +void spline1dbuildhermite(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* d, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state); +void spline1dbuildakima(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state); +double spline1dcalc(spline1dinterpolant* c, double x, ae_state *_state); +void spline1ddiff(spline1dinterpolant* c, + double x, + double* s, + double* ds, + double* d2s, + ae_state *_state); +void spline1dcopy(spline1dinterpolant* c, + spline1dinterpolant* cc, + ae_state *_state); +void spline1dunpack(spline1dinterpolant* c, + ae_int_t* n, + /* Real */ ae_matrix* tbl, + ae_state *_state); +void spline1dlintransx(spline1dinterpolant* c, + double a, + double b, + ae_state *_state); +void spline1dlintransy(spline1dinterpolant* c, + double a, + double b, + ae_state *_state); +double spline1dintegrate(spline1dinterpolant* c, + double x, + ae_state *_state); +void spline1dconvdiffinternal(/* Real */ ae_vector* xold, + /* Real */ ae_vector* yold, + /* Real */ ae_vector* dold, + ae_int_t n, + /* Real */ ae_vector* x2, + ae_int_t n2, + /* Real */ ae_vector* y, + ae_bool needy, + /* Real */ ae_vector* d1, + ae_bool needd1, + /* Real */ ae_vector* d2, + ae_bool needd2, + ae_state *_state); +void spline1drootsandextrema(spline1dinterpolant* c, + /* Real */ ae_vector* r, + ae_int_t* nr, + ae_bool* dr, + /* Real */ ae_vector* e, + /* Integer */ ae_vector* et, + ae_int_t* ne, + ae_bool* de, + ae_state *_state); +void heapsortdpoints(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* d, + ae_int_t n, + ae_state *_state); +void solvepolinom2(double p0, + double m0, + double p1, + double m1, + double* x0, + double* x1, + ae_int_t* nr, + ae_state *_state); +void solvecubicpolinom(double pa, + double ma, + double pb, + double mb, + double a, + double b, + double* x0, + double* x1, + double* x2, + double* ex0, + double* ex1, + ae_int_t* nr, + ae_int_t* ne, + /* Real */ ae_vector* tempdata, + ae_state *_state); +ae_int_t bisectmethod(double pa, + double ma, + double pb, + double mb, + double a, + double b, + double* x, + ae_state *_state); +void spline1dbuildmonotone(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + spline1dinterpolant* c, + ae_state *_state); +ae_bool _spline1dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _spline1dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _spline1dinterpolant_clear(void* _p); +void _spline1dinterpolant_destroy(void* _p); +void polynomialfit(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* p, + polynomialfitreport* rep, + ae_state *_state); +void polynomialfitwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* p, + polynomialfitreport* rep, + ae_state *_state); +void barycentricfitfloaterhormannwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state); +void barycentricfitfloaterhormann(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + barycentricinterpolant* b, + barycentricfitreport* rep, + ae_state *_state); +void spline1dfitpenalized(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + double rho, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void spline1dfitpenalizedw(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + ae_int_t m, + double rho, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void spline1dfitcubicwc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void spline1dfithermitewc(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void spline1dfitcubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void spline1dfithermite(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + spline1dinterpolant* s, + spline1dfitreport* rep, + ae_state *_state); +void lsfitlinearw(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +void lsfitlinearwc(/* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_matrix* cmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +void lsfitlinear(/* Real */ ae_vector* y, + /* Real */ ae_matrix* fmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +void lsfitlinearc(/* Real */ ae_vector* y, + /* Real */ ae_matrix* fmatrix, + /* Real */ ae_matrix* cmatrix, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +void lsfitcreatewf(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + double diffstep, + lsfitstate* state, + ae_state *_state); +void lsfitcreatef(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + double diffstep, + lsfitstate* state, + ae_state *_state); +void lsfitcreatewfg(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_bool cheapfg, + lsfitstate* state, + ae_state *_state); +void lsfitcreatefg(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + ae_bool cheapfg, + lsfitstate* state, + ae_state *_state); +void lsfitcreatewfgh(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + lsfitstate* state, + ae_state *_state); +void lsfitcreatefgh(/* Real */ ae_matrix* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* c, + ae_int_t n, + ae_int_t m, + ae_int_t k, + lsfitstate* state, + ae_state *_state); +void lsfitsetcond(lsfitstate* state, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void lsfitsetstpmax(lsfitstate* state, double stpmax, ae_state *_state); +void lsfitsetxrep(lsfitstate* state, ae_bool needxrep, ae_state *_state); +void lsfitsetscale(lsfitstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void lsfitsetbc(lsfitstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +ae_bool lsfititeration(lsfitstate* state, ae_state *_state); +void lsfitresults(lsfitstate* state, + ae_int_t* info, + /* Real */ ae_vector* c, + lsfitreport* rep, + ae_state *_state); +void lsfitsetgradientcheck(lsfitstate* state, + double teststep, + ae_state *_state); +void lsfitscalexy(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* w, + ae_int_t n, + /* Real */ ae_vector* xc, + /* Real */ ae_vector* yc, + /* Integer */ ae_vector* dc, + ae_int_t k, + double* xa, + double* xb, + double* sa, + double* sb, + /* Real */ ae_vector* xoriginal, + /* Real */ ae_vector* yoriginal, + ae_state *_state); +ae_bool _polynomialfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _polynomialfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _polynomialfitreport_clear(void* _p); +void _polynomialfitreport_destroy(void* _p); +ae_bool _barycentricfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _barycentricfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _barycentricfitreport_clear(void* _p); +void _barycentricfitreport_destroy(void* _p); +ae_bool _spline1dfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _spline1dfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _spline1dfitreport_clear(void* _p); +void _spline1dfitreport_destroy(void* _p); +ae_bool _lsfitreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _lsfitreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _lsfitreport_clear(void* _p); +void _lsfitreport_destroy(void* _p); +ae_bool _lsfitstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _lsfitstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _lsfitstate_clear(void* _p); +void _lsfitstate_destroy(void* _p); +void pspline2build(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline2interpolant* p, + ae_state *_state); +void pspline3build(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline3interpolant* p, + ae_state *_state); +void pspline2buildperiodic(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline2interpolant* p, + ae_state *_state); +void pspline3buildperiodic(/* Real */ ae_matrix* xy, + ae_int_t n, + ae_int_t st, + ae_int_t pt, + pspline3interpolant* p, + ae_state *_state); +void pspline2parametervalues(pspline2interpolant* p, + ae_int_t* n, + /* Real */ ae_vector* t, + ae_state *_state); +void pspline3parametervalues(pspline3interpolant* p, + ae_int_t* n, + /* Real */ ae_vector* t, + ae_state *_state); +void pspline2calc(pspline2interpolant* p, + double t, + double* x, + double* y, + ae_state *_state); +void pspline3calc(pspline3interpolant* p, + double t, + double* x, + double* y, + double* z, + ae_state *_state); +void pspline2tangent(pspline2interpolant* p, + double t, + double* x, + double* y, + ae_state *_state); +void pspline3tangent(pspline3interpolant* p, + double t, + double* x, + double* y, + double* z, + ae_state *_state); +void pspline2diff(pspline2interpolant* p, + double t, + double* x, + double* dx, + double* y, + double* dy, + ae_state *_state); +void pspline3diff(pspline3interpolant* p, + double t, + double* x, + double* dx, + double* y, + double* dy, + double* z, + double* dz, + ae_state *_state); +void pspline2diff2(pspline2interpolant* p, + double t, + double* x, + double* dx, + double* d2x, + double* y, + double* dy, + double* d2y, + ae_state *_state); +void pspline3diff2(pspline3interpolant* p, + double t, + double* x, + double* dx, + double* d2x, + double* y, + double* dy, + double* d2y, + double* z, + double* dz, + double* d2z, + ae_state *_state); +double pspline2arclength(pspline2interpolant* p, + double a, + double b, + ae_state *_state); +double pspline3arclength(pspline3interpolant* p, + double a, + double b, + ae_state *_state); +ae_bool _pspline2interpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _pspline2interpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _pspline2interpolant_clear(void* _p); +void _pspline2interpolant_destroy(void* _p); +ae_bool _pspline3interpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _pspline3interpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _pspline3interpolant_clear(void* _p); +void _pspline3interpolant_destroy(void* _p); +void rbfcreate(ae_int_t nx, ae_int_t ny, rbfmodel* s, ae_state *_state); +void rbfsetpoints(rbfmodel* s, + /* Real */ ae_matrix* xy, + ae_int_t n, + ae_state *_state); +void rbfsetalgoqnn(rbfmodel* s, double q, double z, ae_state *_state); +void rbfsetalgomultilayer(rbfmodel* s, + double rbase, + ae_int_t nlayers, + double lambdav, + ae_state *_state); +void rbfsetlinterm(rbfmodel* s, ae_state *_state); +void rbfsetconstterm(rbfmodel* s, ae_state *_state); +void rbfsetzeroterm(rbfmodel* s, ae_state *_state); +void rbfsetcond(rbfmodel* s, + double epsort, + double epserr, + ae_int_t maxits, + ae_state *_state); +void rbfbuildmodel(rbfmodel* s, rbfreport* rep, ae_state *_state); +double rbfcalc2(rbfmodel* s, double x0, double x1, ae_state *_state); +double rbfcalc3(rbfmodel* s, + double x0, + double x1, + double x2, + ae_state *_state); +void rbfcalc(rbfmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void rbfcalcbuf(rbfmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void rbfgridcalc2(rbfmodel* s, + /* Real */ ae_vector* x0, + ae_int_t n0, + /* Real */ ae_vector* x1, + ae_int_t n1, + /* Real */ ae_matrix* y, + ae_state *_state); +void rbfunpack(rbfmodel* s, + ae_int_t* nx, + ae_int_t* ny, + /* Real */ ae_matrix* xwr, + ae_int_t* nc, + /* Real */ ae_matrix* v, + ae_state *_state); +void rbfalloc(ae_serializer* s, rbfmodel* model, ae_state *_state); +void rbfserialize(ae_serializer* s, rbfmodel* model, ae_state *_state); +void rbfunserialize(ae_serializer* s, rbfmodel* model, ae_state *_state); +ae_bool _rbfmodel_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _rbfmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _rbfmodel_clear(void* _p); +void _rbfmodel_destroy(void* _p); +ae_bool _rbfreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _rbfreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _rbfreport_clear(void* _p); +void _rbfreport_destroy(void* _p); +double spline2dcalc(spline2dinterpolant* c, + double x, + double y, + ae_state *_state); +void spline2ddiff(spline2dinterpolant* c, + double x, + double y, + double* f, + double* fx, + double* fy, + double* fxy, + ae_state *_state); +void spline2dlintransxy(spline2dinterpolant* c, + double ax, + double bx, + double ay, + double by, + ae_state *_state); +void spline2dlintransf(spline2dinterpolant* c, + double a, + double b, + ae_state *_state); +void spline2dcopy(spline2dinterpolant* c, + spline2dinterpolant* cc, + ae_state *_state); +void spline2dresamplebicubic(/* Real */ ae_matrix* a, + ae_int_t oldheight, + ae_int_t oldwidth, + /* Real */ ae_matrix* b, + ae_int_t newheight, + ae_int_t newwidth, + ae_state *_state); +void spline2dresamplebilinear(/* Real */ ae_matrix* a, + ae_int_t oldheight, + ae_int_t oldwidth, + /* Real */ ae_matrix* b, + ae_int_t newheight, + ae_int_t newwidth, + ae_state *_state); +void spline2dbuildbilinearv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* f, + ae_int_t d, + spline2dinterpolant* c, + ae_state *_state); +void spline2dbuildbicubicv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* f, + ae_int_t d, + spline2dinterpolant* c, + ae_state *_state); +void spline2dcalcvbuf(spline2dinterpolant* c, + double x, + double y, + /* Real */ ae_vector* f, + ae_state *_state); +void spline2dcalcv(spline2dinterpolant* c, + double x, + double y, + /* Real */ ae_vector* f, + ae_state *_state); +void spline2dunpackv(spline2dinterpolant* c, + ae_int_t* m, + ae_int_t* n, + ae_int_t* d, + /* Real */ ae_matrix* tbl, + ae_state *_state); +void spline2dbuildbilinear(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_matrix* f, + ae_int_t m, + ae_int_t n, + spline2dinterpolant* c, + ae_state *_state); +void spline2dbuildbicubic(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_matrix* f, + ae_int_t m, + ae_int_t n, + spline2dinterpolant* c, + ae_state *_state); +void spline2dunpack(spline2dinterpolant* c, + ae_int_t* m, + ae_int_t* n, + /* Real */ ae_matrix* tbl, + ae_state *_state); +ae_bool _spline2dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _spline2dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _spline2dinterpolant_clear(void* _p); +void _spline2dinterpolant_destroy(void* _p); +double spline3dcalc(spline3dinterpolant* c, + double x, + double y, + double z, + ae_state *_state); +void spline3dlintransxyz(spline3dinterpolant* c, + double ax, + double bx, + double ay, + double by, + double az, + double bz, + ae_state *_state); +void spline3dlintransf(spline3dinterpolant* c, + double a, + double b, + ae_state *_state); +void spline3dcopy(spline3dinterpolant* c, + spline3dinterpolant* cc, + ae_state *_state); +void spline3dresampletrilinear(/* Real */ ae_vector* a, + ae_int_t oldzcount, + ae_int_t oldycount, + ae_int_t oldxcount, + ae_int_t newzcount, + ae_int_t newycount, + ae_int_t newxcount, + /* Real */ ae_vector* b, + ae_state *_state); +void spline3dbuildtrilinearv(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + /* Real */ ae_vector* z, + ae_int_t l, + /* Real */ ae_vector* f, + ae_int_t d, + spline3dinterpolant* c, + ae_state *_state); +void spline3dcalcvbuf(spline3dinterpolant* c, + double x, + double y, + double z, + /* Real */ ae_vector* f, + ae_state *_state); +void spline3dcalcv(spline3dinterpolant* c, + double x, + double y, + double z, + /* Real */ ae_vector* f, + ae_state *_state); +void spline3dunpackv(spline3dinterpolant* c, + ae_int_t* n, + ae_int_t* m, + ae_int_t* l, + ae_int_t* d, + ae_int_t* stype, + /* Real */ ae_matrix* tbl, + ae_state *_state); +ae_bool _spline3dinterpolant_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _spline3dinterpolant_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _spline3dinterpolant_clear(void* _p); +void _spline3dinterpolant_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/linalg.cpp b/src/inc/alglib/linalg.cpp new file mode 100644 index 0000000..1ef178c --- /dev/null +++ b/src/inc/alglib/linalg.cpp @@ -0,0 +1,33805 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "linalg.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Cache-oblivous complex "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixtranspose(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_2d_array &b, const ae_int_t ib, const ae_int_t jb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixtranspose(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(b.c_ptr()), ib, jb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cache-oblivous real "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixtranspose(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_2d_array &b, const ae_int_t ib, const ae_int_t jb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixtranspose(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(b.c_ptr()), ib, jb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This code enforces symmetricy of the matrix by copying Upper part to lower +one (or vice versa). + +INPUT PARAMETERS: + A - matrix + N - number of rows/columns + IsUpper - whether we want to copy upper triangle to lower one (True) + or vice versa (False). +*************************************************************************/ +void rmatrixenforcesymmetricity(const real_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixenforcesymmetricity(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixcopy(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_2d_array &b, const ae_int_t ib, const ae_int_t jb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixcopy(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(b.c_ptr()), ib, jb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixcopy(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_2d_array &b, const ae_int_t ib, const ae_int_t jb) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixcopy(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(b.c_ptr()), ib, jb, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void cmatrixrank1(const ae_int_t m, const ae_int_t n, complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_1d_array &u, const ae_int_t iu, complex_1d_array &v, const ae_int_t iv) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrank1(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(u.c_ptr()), iu, const_cast(v.c_ptr()), iv, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void rmatrixrank1(const ae_int_t m, const ae_int_t n, real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_1d_array &u, const ae_int_t iu, real_1d_array &v, const ae_int_t iv) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrank1(m, n, const_cast(a.c_ptr()), ia, ja, const_cast(u.c_ptr()), iu, const_cast(v.c_ptr()), iv, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + M>=0 + N - number of columns of op(A) + N>=0 + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + * OpA=2 => op(A) = A^H + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixmv(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t opa, const complex_1d_array &x, const ae_int_t ix, complex_1d_array &y, const ae_int_t iy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixmv(m, n, const_cast(a.c_ptr()), ia, ja, opa, const_cast(x.c_ptr()), ix, const_cast(y.c_ptr()), iy, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + N - number of columns of op(A) + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixmv(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t opa, const real_1d_array &x, const ae_int_t ix, real_1d_array &y, const ae_int_t iy) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixmv(m, n, const_cast(a.c_ptr()), ia, ja, opa, const_cast(x.c_ptr()), ix, const_cast(y.c_ptr()), iy, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void cmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrighttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_cmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_cmatrixrighttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void cmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlefttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_cmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_cmatrixlefttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrighttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rmatrixrighttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlefttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rmatrixlefttrsm(m, n, const_cast(a.c_ptr()), i1, j1, isupper, isunit, optype, const_cast(x.c_ptr()), i2, j2, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void cmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixsyrk(n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, beta, const_cast(c.c_ptr()), ic, jc, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_cmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_cmatrixsyrk(n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, beta, const_cast(c.c_ptr()), ic, jc, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixsyrk(n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, beta, const_cast(c.c_ptr()), ic, jc, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rmatrixsyrk(n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, beta, const_cast(c.c_ptr()), ic, jc, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void cmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const alglib::complex alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const complex_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const alglib::complex beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixgemm(m, n, k, *alpha.c_ptr(), const_cast(a.c_ptr()), ia, ja, optypea, const_cast(b.c_ptr()), ib, jb, optypeb, *beta.c_ptr(), const_cast(c.c_ptr()), ic, jc, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_cmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const alglib::complex alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const complex_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const alglib::complex beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_cmatrixgemm(m, n, k, *alpha.c_ptr(), const_cast(a.c_ptr()), ia, ja, optypea, const_cast(b.c_ptr()), ib, jb, optypeb, *beta.c_ptr(), const_cast(c.c_ptr()), ic, jc, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const real_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixgemm(m, n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, const_cast(b.c_ptr()), ib, jb, optypeb, beta, const_cast(c.c_ptr()), ic, jc, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const real_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rmatrixgemm(m, n, k, alpha, const_cast(a.c_ptr()), ia, ja, optypea, const_cast(b.c_ptr()), ib, jb, optypeb, beta, const_cast(c.c_ptr()), ic, jc, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +QR decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form (see below). + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0.. Min(M-1,N-1)]. + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size M x N. + +The elements of matrix R are located on and above the main diagonal of +matrix A. The elements which are located in Tau array and below the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(k-1), + +where k = min(m,n), and each H(i) is in the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, +so that v(0:i-1) = 0, v(i) = 1, v(i+1:m-1) stored in A(i+1:m-1,i). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqr(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tau) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixqr(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +LQ decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices L and Q in compact form (see below) + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0..Min(M,N)-1]. + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size M x N. + +The elements of matrix L are located on and below the main diagonal of +matrix A. The elements which are located in Tau array and above the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(k-1)*H(k-2)*...*H(1)*H(0), + +where k = min(m,n), and each H(i) is of the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, so that v(0:i-1)=0, +v(i) = 1, v(i+1:n-1) stored in A(i,i+1:n-1). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlq(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tau) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +QR decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixqr(complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_1d_array &tau) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixqr(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +LQ decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and L in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixlq(complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_1d_array &tau) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Partial unpacking of matrix Q from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixQR subroutine. + QColumns - required number of columns of matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose indexes range within [0..M-1, 0..QColumns-1]. + If QColumns=0, the array remains unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackq(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const real_1d_array &tau, const ae_int_t qcolumns, real_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixqrunpackq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), qcolumns, const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackr(const real_2d_array &a, const ae_int_t m, const ae_int_t n, real_2d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixqrunpackr(const_cast(a.c_ptr()), m, n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Partial unpacking of matrix Q from the LQ decomposition of a matrix A + +Input parameters: + A - matrices L and Q in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixLQ subroutine. + QRows - required number of rows in matrix Q. N>=QRows>=0. + +Output parameters: + Q - first QRows rows of matrix Q. Array whose indexes range + within [0..QRows-1, 0..N-1]. If QRows=0, the array remains + unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackq(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const real_1d_array &tau, const ae_int_t qrows, real_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlqunpackq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), qrows, const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackl(const real_2d_array &a, const ae_int_t m, const ae_int_t n, real_2d_array &l) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlqunpackl(const_cast(a.c_ptr()), m, n, const_cast(l.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Partial unpacking of matrix Q from QR decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixQR subroutine . + QColumns - required number of columns in matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose index ranges within [0..M-1, 0..QColumns-1]. + If QColumns=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackq(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, const complex_1d_array &tau, const ae_int_t qcolumns, complex_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixqrunpackq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), qcolumns, const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackr(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_2d_array &r) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixqrunpackr(const_cast(a.c_ptr()), m, n, const_cast(r.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Partial unpacking of matrix Q from LQ decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixLQ subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixLQ subroutine . + QRows - required number of rows in matrix Q. N>=QColumns>=0. + +Output parameters: + Q - first QRows rows of matrix Q. + Array whose index ranges within [0..QRows-1, 0..N-1]. + If QRows=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackq(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, const complex_1d_array &tau, const ae_int_t qrows, complex_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlqunpackq(const_cast(a.c_ptr()), m, n, const_cast(tau.c_ptr()), qrows, const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of CMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackl(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_2d_array &l) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlqunpackl(const_cast(a.c_ptr()), m, n, const_cast(l.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Reduction of a rectangular matrix to bidiagonal form + +The algorithm reduces the rectangular matrix A to bidiagonal form by +orthogonal transformations P and Q: A = Q*B*P. + +Input parameters: + A - source matrix. array[0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q, B, P in compact form (see below). + TauQ - scalar factors which are used to form matrix Q. + TauP - scalar factors which are used to form matrix P. + +The main diagonal and one of the secondary diagonals of matrix A are +replaced with bidiagonal matrix B. Other elements contain elementary +reflections which form MxM matrix Q and NxN matrix P, respectively. + +If M>=N, B is the upper bidiagonal MxN matrix and is stored in the +corresponding elements of matrix A. Matrix Q is represented as a +product of elementary reflections Q = H(0)*H(1)*...*H(n-1), where +H(i) = 1-tau*v*v'. Here tau is a scalar which is stored in TauQ[i], and +vector v has the following structure: v(0:i-1)=0, v(i)=1, v(i+1:m-1) is +stored in elements A(i+1:m-1,i). Matrix P is as follows: P = +G(0)*G(1)*...*G(n-2), where G(i) = 1 - tau*u*u'. Tau is stored in TauP[i], +u(0:i)=0, u(i+1)=1, u(i+2:n-1) is stored in elements A(i,i+2:n-1). + +If M n): m=5, n=6 (m < n): + +( d e u1 u1 u1 ) ( d u1 u1 u1 u1 u1 ) +( v1 d e u2 u2 ) ( e d u2 u2 u2 u2 ) +( v1 v2 d e u3 ) ( v1 e d u3 u3 u3 ) +( v1 v2 v3 d e ) ( v1 v2 e d u4 u4 ) +( v1 v2 v3 v4 d ) ( v1 v2 v3 e d u5 ) +( v1 v2 v3 v4 v5 ) + +Here vi and ui are vectors which form H(i) and G(i), and d and e - +are the diagonal and off-diagonal elements of matrix B. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +void rmatrixbd(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tauq, real_1d_array &taup) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbd(const_cast(a.c_ptr()), m, n, const_cast(tauq.c_ptr()), const_cast(taup.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix Q which reduces a matrix to bidiagonal form. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + QColumns - required number of columns in matrix Q. + M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array[0..M-1, 0..QColumns-1] + If QColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackq(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &tauq, const ae_int_t qcolumns, real_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbdunpackq(const_cast(qp.c_ptr()), m, n, const_cast(tauq.c_ptr()), qcolumns, const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication by matrix Q which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by Q or Q'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + Z - multiplied matrix. + array[0..ZRows-1,0..ZColumns-1] + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=M, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=M, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by Q or Q'. + +Output parameters: + Z - product of Z and Q. + Array[0..ZRows-1,0..ZColumns-1] + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyq(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &tauq, real_2d_array &z, const ae_int_t zrows, const ae_int_t zcolumns, const bool fromtheright, const bool dotranspose) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbdmultiplybyq(const_cast(qp.c_ptr()), m, n, const_cast(tauq.c_ptr()), const_cast(z.c_ptr()), zrows, zcolumns, fromtheright, dotranspose, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix P which reduces matrix A to bidiagonal form. +The subroutine returns transposed matrix P. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of ToBidiagonal subroutine. + PTRows - required number of rows of matrix P^T. N >= PTRows >= 0. + +Output parameters: + PT - first PTRows columns of matrix P^T + Array[0..PTRows-1, 0..N-1] + If PTRows=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackpt(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &taup, const ae_int_t ptrows, real_2d_array &pt) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbdunpackpt(const_cast(qp.c_ptr()), m, n, const_cast(taup.c_ptr()), ptrows, const_cast(pt.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication by matrix P which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by P or P'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of RMatrixBD subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of RMatrixBD subroutine. + Z - multiplied matrix. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=N, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=N, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by P or P'. + +Output parameters: + Z - product of Z and P. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyp(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &taup, real_2d_array &z, const ae_int_t zrows, const ae_int_t zcolumns, const bool fromtheright, const bool dotranspose) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbdmultiplybyp(const_cast(qp.c_ptr()), m, n, const_cast(taup.c_ptr()), const_cast(z.c_ptr()), zrows, zcolumns, fromtheright, dotranspose, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking of the main and secondary diagonals of bidiagonal decomposition +of matrix A. + +Input parameters: + B - output of RMatrixBD subroutine. + M - number of rows in matrix B. + N - number of columns in matrix B. + +Output parameters: + IsUpper - True, if the matrix is upper bidiagonal. + otherwise IsUpper is False. + D - the main diagonal. + Array whose index ranges within [0..Min(M,N)-1]. + E - the secondary diagonal (upper or lower, depending on + the value of IsUpper). + Array index ranges within [0..Min(M,N)-1], the last + element is not used. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackdiagonals(const real_2d_array &b, const ae_int_t m, const ae_int_t n, bool &isupper, real_1d_array &d, real_1d_array &e) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixbdunpackdiagonals(const_cast(b.c_ptr()), m, n, &isupper, const_cast(d.c_ptr()), const_cast(e.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Reduction of a square matrix to upper Hessenberg form: Q'*A*Q = H, +where Q is an orthogonal matrix, H - Hessenberg matrix. + +Input parameters: + A - matrix A with elements [0..N-1, 0..N-1] + N - size of matrix A. + +Output parameters: + A - matrices Q and P in compact form (see below). + Tau - array of scalar factors which are used to form matrix Q. + Array whose index ranges within [0..N-2] + +Matrix H is located on the main diagonal, on the lower secondary diagonal +and above the main diagonal of matrix A. The elements which are used to +form matrix Q are situated in array Tau and below the lower secondary +diagonal of matrix A as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(n-2), + +where each H(i) is given by + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - is a real vector, +so that v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) stored in A(i+2:n-1,i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void rmatrixhessenberg(real_2d_array &a, const ae_int_t n, real_1d_array &tau) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixhessenberg(const_cast(a.c_ptr()), n, const_cast(tau.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix Q which reduces matrix A to upper Hessenberg form + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + Tau - scalar factors which are used to form Q. + Output of RMatrixHessenberg subroutine. + +Output parameters: + Q - matrix Q. + Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackq(const real_2d_array &a, const ae_int_t n, const real_1d_array &tau, real_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixhessenbergunpackq(const_cast(a.c_ptr()), n, const_cast(tau.c_ptr()), const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix H (the result of matrix A reduction to upper Hessenberg form) + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + +Output parameters: + H - matrix H. Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackh(const real_2d_array &a, const ae_int_t n, real_2d_array &h) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixhessenbergunpackh(const_cast(a.c_ptr()), n, const_cast(h.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Reduction of a symmetric matrix which is given by its higher or lower +triangular part to a tridiagonal matrix using orthogonal similarity +transformation: Q'*A*Q=T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + + where d and e denote diagonal and off-diagonal elements of T, and vi + denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void smatrixtd(real_2d_array &a, const ae_int_t n, const bool isupper, real_1d_array &tau, real_1d_array &d, real_1d_array &e) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::smatrixtd(const_cast(a.c_ptr()), n, isupper, const_cast(tau.c_ptr()), const_cast(d.c_ptr()), const_cast(e.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix Q which reduces symmetric matrix to a tridiagonal +form. + +Input parameters: + A - the result of a SMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of SMatrixTD subroutine) + Tau - the result of a SMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void smatrixtdunpackq(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_1d_array &tau, real_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::smatrixtdunpackq(const_cast(a.c_ptr()), n, isupper, const_cast(tau.c_ptr()), const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Reduction of a Hermitian matrix which is given by its higher or lower +triangular part to a real tridiagonal matrix using unitary similarity +transformation: Q'*A*Q = T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of real symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of real symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + +where d and e denote diagonal and off-diagonal elements of T, and vi +denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void hmatrixtd(complex_2d_array &a, const ae_int_t n, const bool isupper, complex_1d_array &tau, real_1d_array &d, real_1d_array &e) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hmatrixtd(const_cast(a.c_ptr()), n, isupper, const_cast(tau.c_ptr()), const_cast(d.c_ptr()), const_cast(e.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Unpacking matrix Q which reduces a Hermitian matrix to a real tridiagonal +form. + +Input parameters: + A - the result of a HMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of HMatrixTD subroutine) + Tau - the result of a HMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void hmatrixtdunpackq(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_1d_array &tau, complex_2d_array &q) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hmatrixtdunpackq(const_cast(a.c_ptr()), n, isupper, const_cast(tau.c_ptr()), const_cast(q.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Singular value decomposition of a bidiagonal matrix (extended algorithm) + +The algorithm performs the singular value decomposition of a bidiagonal +matrix B (upper or lower) representing it as B = Q*S*P^T, where Q and P - +orthogonal matrices, S - diagonal matrix with non-negative elements on the +main diagonal, in descending order. + +The algorithm finds singular values. In addition, the algorithm can +calculate matrices Q and P (more precisely, not the matrices, but their +product with given matrices U and VT - U*Q and (P^T)*VT)). Of course, +matrices U and VT can be of any type, including identity. Furthermore, the +algorithm can calculate Q'*C (this product is calculated more effectively +than U*Q, because this calculation operates with rows instead of matrix +columns). + +The feature of the algorithm is its ability to find all singular values +including those which are arbitrarily close to 0 with relative accuracy +close to machine precision. If the parameter IsFractionalAccuracyRequired +is set to True, all singular values will have high relative accuracy close +to machine precision. If the parameter is set to False, only the biggest +singular value will have relative accuracy close to machine precision. +The absolute error of other singular values is equal to the absolute error +of the biggest singular value. + +Input parameters: + D - main diagonal of matrix B. + Array whose index ranges within [0..N-1]. + E - superdiagonal (or subdiagonal) of matrix B. + Array whose index ranges within [0..N-2]. + N - size of matrix B. + IsUpper - True, if the matrix is upper bidiagonal. + IsFractionalAccuracyRequired - + THIS PARAMETER IS IGNORED SINCE ALGLIB 3.5.0 + SINGULAR VALUES ARE ALWAYS SEARCHED WITH HIGH ACCURACY. + U - matrix to be multiplied by Q. + Array whose indexes range within [0..NRU-1, 0..N-1]. + The matrix can be bigger, in that case only the submatrix + [0..NRU-1, 0..N-1] will be multiplied by Q. + NRU - number of rows in matrix U. + C - matrix to be multiplied by Q'. + Array whose indexes range within [0..N-1, 0..NCC-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCC-1] will be multiplied by Q'. + NCC - number of columns in matrix C. + VT - matrix to be multiplied by P^T. + Array whose indexes range within [0..N-1, 0..NCVT-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCVT-1] will be multiplied by P^T. + NCVT - number of columns in matrix VT. + +Output parameters: + D - singular values of matrix B in descending order. + U - if NRU>0, contains matrix U*Q. + VT - if NCVT>0, contains matrix (P^T)*VT. + C - if NCC>0, contains matrix Q'*C. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Additional information: + The type of convergence is controlled by the internal parameter TOL. + If the parameter is greater than 0, the singular values will have + relative accuracy TOL. If TOL<0, the singular values will have + absolute accuracy ABS(TOL)*norm(B). + By default, |TOL| falls within the range of 10*Epsilon and 100*Epsilon, + where Epsilon is the machine precision. It is not recommended to use + TOL less than 10*Epsilon since this will considerably slow down the + algorithm and may not lead to error decreasing. +History: + * 31 March, 2007. + changed MAXITR from 6 to 12. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1999. +*************************************************************************/ +bool rmatrixbdsvd(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const bool isupper, const bool isfractionalaccuracyrequired, real_2d_array &u, const ae_int_t nru, real_2d_array &c, const ae_int_t ncc, real_2d_array &vt, const ae_int_t ncvt) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::rmatrixbdsvd(const_cast(d.c_ptr()), const_cast(e.c_ptr()), n, isupper, isfractionalaccuracyrequired, const_cast(u.c_ptr()), nru, const_cast(c.c_ptr()), ncc, const_cast(vt.c_ptr()), ncvt, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Singular value decomposition of a rectangular matrix. + +The algorithm calculates the singular value decomposition of a matrix of +size MxN: A = U * S * V^T + +The algorithm finds the singular values and, optionally, matrices U and V^T. +The algorithm can find both first min(M,N) columns of matrix U and rows of +matrix V^T (singular vectors), and matrices U and V^T wholly (of sizes MxM +and NxN respectively). + +Take into account that the subroutine does not return matrix V but V^T. + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + UNeeded - 0, 1 or 2. See the description of the parameter U. + VTNeeded - 0, 1 or 2. See the description of the parameter VT. + AdditionalMemory - + If the parameter: + * equals 0, the algorithm doesn’t use additional + memory (lower requirements, lower performance). + * equals 1, the algorithm uses additional + memory of size min(M,N)*min(M,N) of real numbers. + It often speeds up the algorithm. + * equals 2, the algorithm uses additional + memory of size M*min(M,N) of real numbers. + It allows to get a maximum performance. + The recommended value of the parameter is 2. + +Output parameters: + W - contains singular values in descending order. + U - if UNeeded=0, U isn't changed, the left singular vectors + are not calculated. + if Uneeded=1, U contains left singular vectors (first + min(M,N) columns of matrix U). Array whose indexes range + within [0..M-1, 0..Min(M,N)-1]. + if UNeeded=2, U contains matrix U wholly. Array whose + indexes range within [0..M-1, 0..M-1]. + VT - if VTNeeded=0, VT isn’t changed, the right singular vectors + are not calculated. + if VTNeeded=1, VT contains right singular vectors (first + min(M,N) rows of matrix V^T). Array whose indexes range + within [0..min(M,N)-1, 0..N-1]. + if VTNeeded=2, VT contains matrix V^T wholly. Array whose + indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +bool rmatrixsvd(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const ae_int_t uneeded, const ae_int_t vtneeded, const ae_int_t additionalmemory, real_1d_array &w, real_2d_array &u, real_2d_array &vt) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::rmatrixsvd(const_cast(a.c_ptr()), m, n, uneeded, vtneeded, additionalmemory, const_cast(w.c_ptr()), const_cast(u.c_ptr()), const_cast(vt.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a symmetric matrix + +The algorithm finds eigen pairs of a symmetric matrix by reducing it to +tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpper - storage format. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevd(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, real_1d_array &d, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixevd(const_cast(a.c_ptr()), n, zneeded, isupper, const_cast(d.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a symmetric +matrix in a given half open interval (A, B] by using a bisection and +inverse iteration + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half open interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval (M>=0). + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, + M is equal to 0. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevdr(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const double b1, const double b2, ae_int_t &m, real_1d_array &w, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixevdr(const_cast(a.c_ptr()), n, zneeded, isupper, b1, b2, &m, const_cast(w.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a symmetric +matrix with given indexes by using bisection and inverse iteration methods. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevdi(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const ae_int_t i1, const ae_int_t i2, real_1d_array &w, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixevdi(const_cast(a.c_ptr()), n, zneeded, isupper, i1, i2, const_cast(w.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a Hermitian matrix + +The algorithm finds eigen pairs of a Hermitian matrix by reducing it to +real tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Note: + eigenvectors of Hermitian matrix are defined up to multiplication by + a complex number L, such that |L|=1. + + -- ALGLIB -- + Copyright 2005, 23 March 2007 by Bochkanov Sergey +*************************************************************************/ +bool hmatrixevd(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, real_1d_array &d, complex_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::hmatrixevd(const_cast(a.c_ptr()), n, zneeded, isupper, const_cast(d.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a Hermitian +matrix in a given half-interval (A, B] by using a bisection and inverse +iteration + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. Array whose indexes range within + [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half-interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval, M>=0 + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, M is + equal to 0. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +bool hmatrixevdr(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const double b1, const double b2, ae_int_t &m, real_1d_array &w, complex_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::hmatrixevdr(const_cast(a.c_ptr()), n, zneeded, isupper, b1, b2, &m, const_cast(w.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a Hermitian +matrix with given indexes by using bisection and inverse iteration methods + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix + columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +bool hmatrixevdi(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const ae_int_t i1, const ae_int_t i2, real_1d_array &w, complex_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::hmatrixevdi(const_cast(a.c_ptr()), n, zneeded, isupper, i1, i2, const_cast(w.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a tridiagonal symmetric matrix + +The algorithm finds the eigen pairs of a tridiagonal symmetric matrix by +using an QL/QR algorithm with implicit shifts. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix + are multiplied by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity + transformation of a symmetric matrix; + * 2, the eigenvectors of a tridiagonal matrix replace the + square matrix Z; + * 3, matrix Z contains the first row of the eigenvectors + matrix. + Z - if ZNeeded=1, Z contains the square matrix by which the + eigenvectors are multiplied. + Array whose indexes range within [0..N-1, 0..N-1]. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the product of a given matrix (from the left) + and the eigenvectors matrix (from the right); + * 2, Z contains the eigenvectors. + * 3, Z contains the first row of the eigenvectors matrix. + If ZNeeded<3, Z is the array whose indexes range within [0..N-1, 0..N-1]. + In that case, the eigenvectors are stored in the matrix columns. + If ZNeeded=3, Z is the array whose indexes range within [0..0, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +bool smatrixtdevd(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixtdevd(const_cast(d.c_ptr()), const_cast(e.c_ptr()), n, zneeded, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding the tridiagonal matrix eigenvalues/vectors in a +given half-interval (A, B] by using bisection and inverse iteration. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix, N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the tridiagonal + matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace matrix Z. + A, B - half-interval (A, B] to search eigenvalues in. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range + within [0..N-1, 0..N-1]) which reduces the given symmetric + matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + M - number of eigenvalues found in the given half-interval (M>=0). + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the + left) and NxM matrix of the eigenvectors found (from the + right). Array whose indexes range within [0..N-1, 0..M-1]. + * 2, contains the matrix of the eigenvectors found. + Array whose indexes range within [0..N-1, 0..M-1]. + +Result: + + True, if successful. In that case, M contains the number of eigenvalues + in the given half-interval (could be equal to 0), D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. In that case, + the eigenvalues and eigenvectors are not returned, M is equal to 0. + + -- ALGLIB -- + Copyright 31.03.2008 by Bochkanov Sergey +*************************************************************************/ +bool smatrixtdevdr(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, const double a, const double b, ae_int_t &m, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixtdevdr(const_cast(d.c_ptr()), const_cast(e.c_ptr()), n, zneeded, a, b, &m, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine for finding tridiagonal matrix eigenvalues/vectors with given +indexes (in ascending order) by using the bisection and inverse iteraion. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix. N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace + matrix Z. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range within [0..N-1, 0..N-1]) + which reduces the given symmetric matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the left) and + Nx(I2-I1) matrix of the eigenvectors found (from the right). + Array whose indexes range within [0..N-1, 0..I2-I1]. + * 2, contains the matrix of the eigenvalues found. + Array whose indexes range within [0..N-1, 0..I2-I1]. + + +Result: + + True, if successful. In that case, D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the eigenvalues + in the given interval or if the inverse iteration subroutine wasn't able + to find all the corresponding eigenvectors. In that case, the eigenvalues + and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 25.12.2005 by Bochkanov Sergey +*************************************************************************/ +bool smatrixtdevdi(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, const ae_int_t i1, const ae_int_t i2, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixtdevdi(const_cast(d.c_ptr()), const_cast(e.c_ptr()), n, zneeded, i1, i2, const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Finding eigenvalues and eigenvectors of a general matrix + +The algorithm finds eigenvalues and eigenvectors of a general matrix by +using the QR algorithm with multiple shifts. The algorithm can find +eigenvalues and both left and right eigenvectors. + +The right eigenvector is a vector x such that A*x = w*x, and the left +eigenvector is a vector y such that y'*A = w*y' (here y' implies a complex +conjugate transposition of vector y). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + VNeeded - flag controlling whether eigenvectors are needed or not. + If VNeeded is equal to: + * 0, eigenvectors are not returned; + * 1, right eigenvectors are returned; + * 2, left eigenvectors are returned; + * 3, both left and right eigenvectors are returned. + +Output parameters: + WR - real parts of eigenvalues. + Array whose index ranges within [0..N-1]. + WR - imaginary parts of eigenvalues. + Array whose index ranges within [0..N-1]. + VL, VR - arrays of left and right eigenvectors (if they are needed). + If WI[i]=0, the respective eigenvalue is a real number, + and it corresponds to the column number I of matrices VL/VR. + If WI[i]>0, we have a pair of complex conjugate numbers with + positive and negative imaginary parts: + the first eigenvalue WR[i] + sqrt(-1)*WI[i]; + the second eigenvalue WR[i+1] + sqrt(-1)*WI[i+1]; + WI[i]>0 + WI[i+1] = -WI[i] < 0 + In that case, the eigenvector corresponding to the first + eigenvalue is located in i and i+1 columns of matrices + VL/VR (the column number i contains the real part, and the + column number i+1 contains the imaginary part), and the vector + corresponding to the second eigenvalue is a complex conjugate to + the first vector. + Arrays whose indexes range within [0..N-1, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm has not converged. + +Note 1: + Some users may ask the following question: what if WI[N-1]>0? + WI[N] must contain an eigenvalue which is complex conjugate to the + N-th eigenvalue, but the array has only size N? + The answer is as follows: such a situation cannot occur because the + algorithm finds a pairs of eigenvalues, therefore, if WI[i]>0, I is + strictly less than N-1. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms of linear algebra). If you require maximum performance + on your machine, it is recommended to adjust this parameter manually. + + +See also the InternalTREVC subroutine. + +The algorithm is based on the LAPACK 3.0 library. +*************************************************************************/ +bool rmatrixevd(const real_2d_array &a, const ae_int_t n, const ae_int_t vneeded, real_1d_array &wr, real_1d_array &wi, real_2d_array &vl, real_2d_array &vr) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::rmatrixevd(const_cast(a.c_ptr()), n, vneeded, const_cast(wr.c_ptr()), const_cast(wi.c_ptr()), const_cast(vl.c_ptr()), const_cast(vr.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of a random uniformly distributed (Haar) orthogonal matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonal(const ae_int_t n, real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrndorthogonal(n, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN matrix with given condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndcond(const ae_int_t n, const double c, real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of a random Haar distributed orthogonal complex matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonal(const ae_int_t n, complex_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrndorthogonal(n, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN complex matrix with given condition number C and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN symmetric matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndcond(const ae_int_t n, const double c, real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::smatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN symmetric positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random SPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixrndcond(const ae_int_t n, const double c, real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN Hermitian matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hmatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Generation of random NxN Hermitian positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random HPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixrndcond(n, c, const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication of MxN matrix by NxN random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheright(real_2d_array &a, const ae_int_t m, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrndorthogonalfromtheright(const_cast(a.c_ptr()), m, n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication of MxN matrix by MxM random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheleft(real_2d_array &a, const ae_int_t m, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixrndorthogonalfromtheleft(const_cast(a.c_ptr()), m, n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication of MxN complex matrix by NxN random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheright(complex_2d_array &a, const ae_int_t m, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrndorthogonalfromtheright(const_cast(a.c_ptr()), m, n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Multiplication of MxN complex matrix by MxM random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheleft(complex_2d_array &a, const ae_int_t m, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixrndorthogonalfromtheleft(const_cast(a.c_ptr()), m, n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Symmetric multiplication of NxN matrix by random Haar distributed +orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q'*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndmultiply(real_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::smatrixrndmultiply(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Hermitian multiplication of NxN matrix by random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q^H*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndmultiply(complex_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hmatrixrndmultiply(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +LU decomposition of a general real matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. +It is optimized for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlu(real_2d_array &a, const ae_int_t m, const ae_int_t n, integer_1d_array &pivots) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlu(const_cast(a.c_ptr()), m, n, const_cast(pivots.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +LU decomposition of a general complex matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. It is optimized +for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlu(complex_2d_array &a, const ae_int_t m, const ae_int_t n, integer_1d_array &pivots) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlu(const_cast(a.c_ptr()), m, n, const_cast(pivots.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a Hermitian positive- +definite matrix. The result of an algorithm is a representation of A as +A=U'*U or A=L*L' (here X' detones conj(X^T)). + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U'*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +bool hpdmatrixcholesky(complex_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::hpdmatrixcholesky(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a symmetric positive- +definite matrix. The result of an algorithm is a representation of A as +A=U^T*U or A=L*L^T + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U^T*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +bool spdmatrixcholesky(real_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::spdmatrixcholesky(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcond1(const real_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixrcond1(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcondinf(const real_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixrcondinf(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - symmetric positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixrcond(const real_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixrcond(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcond1(const real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixtrrcond1(const_cast(a.c_ptr()), n, isupper, isunit, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcondinf(const real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixtrrcondinf(const_cast(a.c_ptr()), n, isupper, isunit, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - Hermitian positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixrcond(const complex_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hpdmatrixrcond(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcond1(const complex_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixrcond1(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcondinf(const complex_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixrcondinf(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcond1(const real_2d_array &lua, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixlurcond1(const_cast(lua.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcondinf(const real_2d_array &lua, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixlurcondinf(const_cast(lua.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixcholeskyrcond(const real_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixcholeskyrcond(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixcholeskyrcond(const complex_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hpdmatrixcholeskyrcond(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcond1(const complex_2d_array &lua, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixlurcond1(const_cast(lua.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcondinf(const complex_2d_array &lua, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixlurcondinf(const_cast(lua.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcond1(const complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixtrrcond1(const_cast(a.c_ptr()), n, isupper, isunit, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcondinf(const complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cmatrixtrrcondinf(const_cast(a.c_ptr()), n, isupper, isunit, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Matrix inverse report: +* R1 reciprocal of condition number in 1-norm +* RInf reciprocal of condition number in inf-norm +*************************************************************************/ +_matinvreport_owner::_matinvreport_owner() +{ + p_struct = (alglib_impl::matinvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::matinvreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_matinvreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_matinvreport_owner::_matinvreport_owner(const _matinvreport_owner &rhs) +{ + p_struct = (alglib_impl::matinvreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::matinvreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_matinvreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_matinvreport_owner& _matinvreport_owner::operator=(const _matinvreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_matinvreport_clear(p_struct); + if( !alglib_impl::_matinvreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_matinvreport_owner::~_matinvreport_owner() +{ + alglib_impl::_matinvreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::matinvreport* _matinvreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::matinvreport* _matinvreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +matinvreport::matinvreport() : _matinvreport_owner() ,r1(p_struct->r1),rinf(p_struct->rinf) +{ +} + +matinvreport::matinvreport(const matinvreport &rhs):_matinvreport_owner(rhs) ,r1(p_struct->r1),rinf(p_struct->rinf) +{ +} + +matinvreport& matinvreport::operator=(const matinvreport &rhs) +{ + if( this==&rhs ) + return *this; + _matinvreport_owner::operator=(rhs); + return *this; +} + +matinvreport::~matinvreport() +{ +} + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of RMatrixLU subroutine). + Pivots - table of permutations + (the output of RMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code: + * -3 A is singular, or VERY close to singular. + it is filled by zeros in such cases. + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + A - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixluinverse(real_2d_array &a, const integer_1d_array &pivots, const ae_int_t n, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixluinverse(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of RMatrixLU subroutine). + Pivots - table of permutations + (the output of RMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code: + * -3 A is singular, or VERY close to singular. + it is filled by zeros in such cases. + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + A - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixluinverse(real_2d_array &a, const integer_1d_array &pivots, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.cols()!=a.rows()) || (a.cols()!=pivots.length())) + throw ap_error("Error while calling 'rmatrixluinverse': looks like one of arguments has wrong size"); + n = a.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixluinverse(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + +Result: + True, if the matrix is not singular. + False, if the matrix is singular. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinverse(real_2d_array &a, const ae_int_t n, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinverse(const_cast(a.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + +Result: + True, if the matrix is not singular. + False, if the matrix is singular. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'rmatrixinverse': looks like one of arguments has wrong size"); + n = a.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinverse(const_cast(a.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of CMatrixLU subroutine). + Pivots - table of permutations + (the output of CMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixluinverse(complex_2d_array &a, const integer_1d_array &pivots, const ae_int_t n, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixluinverse(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of CMatrixLU subroutine). + Pivots - table of permutations + (the output of CMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixluinverse(complex_2d_array &a, const integer_1d_array &pivots, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.cols()!=a.rows()) || (a.cols()!=pivots.length())) + throw ap_error("Error while calling 'cmatrixluinverse': looks like one of arguments has wrong size"); + n = a.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixluinverse(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void cmatrixinverse(complex_2d_array &a, const ae_int_t n, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixinverse(const_cast(a.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void cmatrixinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'cmatrixinverse': looks like one of arguments has wrong size"); + n = a.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixinverse(const_cast(a.c_ptr()), n, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a symmetric positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of SPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskyinverse(real_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixcholeskyinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a symmetric positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of SPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskyinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isupper; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'spdmatrixcholeskyinverse': looks like one of arguments has wrong size"); + n = a.cols(); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixcholeskyinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a symmetric positive definite matrix. + +Given an upper or lower triangle of a symmetric positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixinverse(real_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a symmetric positive definite matrix. + +Given an upper or lower triangle of a symmetric positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isupper; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'spdmatrixinverse': looks like one of arguments has wrong size"); + if( !alglib_impl::ae_is_symmetric(const_cast(a.c_ptr())) ) + throw ap_error("'a' parameter is not symmetric matrix"); + n = a.cols(); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + if( !alglib_impl::ae_force_symmetric(const_cast(a.c_ptr())) ) + throw ap_error("Internal error while forcing symmetricity of 'a' parameter"); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a Hermitian positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of HPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskyinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixcholeskyinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a Hermitian positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of HPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskyinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isupper; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'hpdmatrixcholeskyinverse': looks like one of arguments has wrong size"); + n = a.cols(); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixcholeskyinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a Hermitian positive definite matrix. + +Given an upper or lower triangle of a Hermitian positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inversion of a Hermitian positive definite matrix. + +Given an upper or lower triangle of a Hermitian positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isupper; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'hpdmatrixinverse': looks like one of arguments has wrong size"); + if( !alglib_impl::ae_is_hermitian(const_cast(a.c_ptr())) ) + throw ap_error("'a' parameter is not Hermitian matrix"); + n = a.cols(); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixinverse(const_cast(a.c_ptr()), n, isupper, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + if( !alglib_impl::ae_force_hermitian(const_cast(a.c_ptr())) ) + throw ap_error("Internal error while forcing Hermitian properties of 'a' parameter"); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix inverse (real) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixtrinverse(real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixtrinverse(const_cast(a.c_ptr()), n, isupper, isunit, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix inverse (real) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixtrinverse(real_2d_array &a, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isunit; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'rmatrixtrinverse': looks like one of arguments has wrong size"); + n = a.cols(); + isunit = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixtrinverse(const_cast(a.c_ptr()), n, isupper, isunit, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix inverse (complex) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixtrinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixtrinverse(const_cast(a.c_ptr()), n, isupper, isunit, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Triangular matrix inverse (complex) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixtrinverse(complex_2d_array &a, const bool isupper, ae_int_t &info, matinvreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isunit; + if( (a.cols()!=a.rows())) + throw ap_error("Error while calling 'cmatrixtrinverse': looks like one of arguments has wrong size"); + n = a.cols(); + isunit = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixtrinverse(const_cast(a.c_ptr()), n, isupper, isunit, &info, const_cast(rep.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Sparse matrix + +You should use ALGLIB functions to work with sparse matrix. +Never try to access its fields directly! +*************************************************************************/ +_sparsematrix_owner::_sparsematrix_owner() +{ + p_struct = (alglib_impl::sparsematrix*)alglib_impl::ae_malloc(sizeof(alglib_impl::sparsematrix), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_sparsematrix_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_sparsematrix_owner::_sparsematrix_owner(const _sparsematrix_owner &rhs) +{ + p_struct = (alglib_impl::sparsematrix*)alglib_impl::ae_malloc(sizeof(alglib_impl::sparsematrix), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_sparsematrix_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_sparsematrix_owner& _sparsematrix_owner::operator=(const _sparsematrix_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_sparsematrix_clear(p_struct); + if( !alglib_impl::_sparsematrix_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_sparsematrix_owner::~_sparsematrix_owner() +{ + alglib_impl::_sparsematrix_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::sparsematrix* _sparsematrix_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::sparsematrix* _sparsematrix_owner::c_ptr() const +{ + return const_cast(p_struct); +} +sparsematrix::sparsematrix() : _sparsematrix_owner() +{ +} + +sparsematrix::sparsematrix(const sparsematrix &rhs):_sparsematrix_owner(rhs) +{ +} + +sparsematrix& sparsematrix::operator=(const sparsematrix &rhs) +{ + if( this==&rhs ) + return *this; + _sparsematrix_owner::operator=(rhs); + return *this; +} + +sparsematrix::~sparsematrix() +{ +} + +/************************************************************************* +This function creates sparse matrix in a Hash-Table format. + +This function creates Hast-Table matrix, which can be converted to CRS +format after its initialization is over. Typical usage scenario for a +sparse matrix is: +1. creation in a Hash-Table format +2. insertion of the matrix elements +3. conversion to the CRS representation +4. matrix is passed to some linear algebra algorithm + +Some information about different matrix formats can be found below, in +the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + K - K>=0, expected number of non-zero elements in a matrix. + K can be inexact approximation, can be less than actual + number of elements (table will grow when needed) or + even zero). + It is important to understand that although hash-table + may grow automatically, it is better to provide good + estimate of data size. + +OUTPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + All elements of the matrix are zero. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreate(const ae_int_t m, const ae_int_t n, const ae_int_t k, sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecreate(m, n, k, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function creates sparse matrix in a Hash-Table format. + +This function creates Hast-Table matrix, which can be converted to CRS +format after its initialization is over. Typical usage scenario for a +sparse matrix is: +1. creation in a Hash-Table format +2. insertion of the matrix elements +3. conversion to the CRS representation +4. matrix is passed to some linear algebra algorithm + +Some information about different matrix formats can be found below, in +the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + K - K>=0, expected number of non-zero elements in a matrix. + K can be inexact approximation, can be less than actual + number of elements (table will grow when needed) or + even zero). + It is important to understand that although hash-table + may grow automatically, it is better to provide good + estimate of data size. + +OUTPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + All elements of the matrix are zero. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreate(const ae_int_t m, const ae_int_t n, sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t k; + + k = 0; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecreate(m, n, k, const_cast(s.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function creates sparse matrix in a CRS format (expert function for +situations when you are running out of memory). + +This function creates CRS matrix. Typical usage scenario for a CRS matrix +is: +1. creation (you have to tell number of non-zero elements at each row at + this moment) +2. insertion of the matrix elements (row by row, from left to right) +3. matrix is passed to some linear algebra algorithm + +This function is a memory-efficient alternative to SparseCreate(), but it +is more complex because it requires you to know in advance how large your +matrix is. Some information about different matrix formats can be found +below, in the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + NER - number of elements at each row, array[M], NER[I]>=0 + +OUTPUT PARAMETERS + S - sparse M*N matrix in CRS representation. + You have to fill ALL non-zero elements by calling + SparseSet() BEFORE you try to use this matrix. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreatecrs(const ae_int_t m, const ae_int_t n, const integer_1d_array &ner, sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecreatecrs(m, n, const_cast(ner.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function copies S0 to S1. + +NOTE: this function does not verify its arguments, it just copies all +fields of the structure. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecopy(const sparsematrix &s0, sparsematrix &s1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecopy(const_cast(s0.c_ptr()), const_cast(s1.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function adds value to S[i,j] - element of the sparse matrix. Matrix +must be in a Hash-Table mode. + +In case S[i,j] already exists in the table, V i added to its value. In +case S[i,j] is non-existent, it is inserted in the table. Table +automatically grows when necessary. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - row index of the element to modify, 0<=I(s.c_ptr()), i, j, v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function modifies S[i,j] - element of the sparse matrix. + +For Hash-based storage format: +* new value can be zero or non-zero. In case new value of S[i,j] is zero, + this element is deleted from the table. +* this function has no effect when called with zero V for non-existent + element. + +For CRS-bases storage format: +* new value MUST be non-zero. Exception will be thrown for zero V. +* elements must be initialized in correct order - from top row to bottom, + within row - from left to right. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + I - row index of the element to modify, 0<=I(s.c_ptr()), i, j, v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns S[i,j] - element of the sparse matrix. Matrix can +be in any mode (Hash-Table or CRS), but this function is less efficient +for CRS matrices. Hash-Table matrices can find element in O(1) time, +while CRS matrices need O(log(RS)) time, where RS is an number of non- +zero elements in a row. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - row index of the element to modify, 0<=I(s.c_ptr()), i, j, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns I-th diagonal element of the sparse matrix. + +Matrix can be in any mode (Hash-Table or CRS storage), but this function +is most efficient for CRS matrices - it requires less than 50 CPU cycles +to extract diagonal element. For Hash-Table matrices we still have O(1) +query time, but function is many times slower. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - index of the element to modify, 0<=I(s.c_ptr()), i, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function converts matrix to CRS format. + +Some algorithms (linear algebra ones, for example) require matrices in +CRS format. + +INPUT PARAMETERS + S - sparse M*N matrix in any format + +OUTPUT PARAMETERS + S - matrix in CRS format + +NOTE: this function has no effect when called with matrix which is +already in CRS mode. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparseconverttocrs(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparseconverttocrs(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-vector product S*x. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[M], S*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemv(const sparsematrix &s, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemv(const_cast(s.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-vector product S^T*x. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[M], input vector. For performance reasons we + make only quick checks - we check that array size is + at least M, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[N], S^T*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemtv(const sparsematrix &s, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemtv(const_cast(s.c_ptr()), const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function simultaneously calculates two matrix-vector products: + S*x and S^T*x. +S must be square (non-rectangular) matrix stored in CRS format (exception +will be thrown otherwise). + +INPUT PARAMETERS + S - sparse N*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y0 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + Y1 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y0 - array[N], S*x + Y1 - array[N], S^T*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. It also throws exception when S is non-square. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemv2(const sparsematrix &s, const real_1d_array &x, real_1d_array &y0, real_1d_array &y1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemv2(const_cast(s.c_ptr()), const_cast(x.c_ptr()), const_cast(y0.c_ptr()), const_cast(y1.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-vector product S*x, when S is symmetric +matrix. Matrix S must be stored in CRS format (exception will be +thrown otherwise). + +INPUT PARAMETERS + S - sparse M*M matrix in CRS format (you MUST convert it + to CRS before calling this function). + IsUpper - whether upper or lower triangle of S is given: + * if upper triangle is given, only S[i,j] for j>=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[M], S*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmv(const sparsematrix &s, const bool isupper, const real_1d_array &x, real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsesmv(const_cast(s.c_ptr()), isupper, const_cast(x.c_ptr()), const_cast(y.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-matrix product S*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size + is at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemm(const_cast(s.c_ptr()), const_cast(a.c_ptr()), k, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-matrix product S^T*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[M][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least M, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemtm(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemtm(const_cast(s.c_ptr()), const_cast(a.c_ptr()), k, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function simultaneously calculates two matrix-matrix products: + S*A and S^T*A. +S must be square (non-rectangular) matrix stored in CRS format (exception +will be thrown otherwise). + +INPUT PARAMETERS + S - sparse N*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B0 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + B1 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B0 - array[N][K], S*A + B1 - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. It also throws exception when S is non-square. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm2(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b0, real_2d_array &b1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsemm2(const_cast(s.c_ptr()), const_cast(a.c_ptr()), k, const_cast(b0.c_ptr()), const_cast(b1.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function calculates matrix-matrix product S*A, when S is symmetric +matrix. Matrix S must be stored in CRS format (exception will be +thrown otherwise). + +INPUT PARAMETERS + S - sparse M*M matrix in CRS format (you MUST convert it + to CRS before calling this function). + IsUpper - whether upper or lower triangle of S is given: + * if upper triangle is given, only S[i,j] for j>=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmm(const sparsematrix &s, const bool isupper, const real_2d_array &a, const ae_int_t k, real_2d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsesmm(const_cast(s.c_ptr()), isupper, const_cast(a.c_ptr()), k, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This procedure resizes Hash-Table matrix. It can be called when you have +deleted too many elements from the matrix, and you want to free unneeded +memory. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparseresizematrix(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparseresizematrix(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to enumerate all elements of the sparse matrix. +Before first call user initializes T0 and T1 counters by zero. These +counters are used to remember current position in a matrix; after each +call they are updated by the function. + +Subsequent calls to this function return non-zero elements of the sparse +matrix, one by one. If you enumerate CRS matrix, matrix is traversed from +left to right, from top to bottom. In case you enumerate matrix stored as +Hash table, elements are returned in random order. + +EXAMPLE + > T0=0 + > T1=0 + > while SparseEnumerate(S,T0,T1,I,J,V) do + > ....do something with I,J,V + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + T0 - internal counter + T1 - internal counter + +OUTPUT PARAMETERS + T0 - new value of the internal counter + T1 - new value of the internal counter + I - row index of non-zero element, 0<=I(s.c_ptr()), &t0, &t1, &i, &j, &v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function rewrites existing (non-zero) element. It returns True if +element exists or False, when it is called for non-existing (zero) +element. + +The purpose of this function is to provide convenient thread-safe way to +modify sparse matrix. Such modification (already existing element is +rewritten) is guaranteed to be thread-safe without any synchronization, as +long as different threads modify different elements. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + I - row index of non-zero element to modify, 0<=I(s.c_ptr()), i, j, v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns I-th row of the sparse matrix stored in CRS format. + +NOTE: when incorrect I (outside of [0,M-1]) or matrix (non-CRS) are + passed, this function throws exception. + +INPUT PARAMETERS: + S - sparse M*N matrix in CRS format + I - row index, 0<=I(s.c_ptr()), i, const_cast(irow.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function performs in-place conversion from CRS format to Hash table +storage. + +INPUT PARAMETERS + S - sparse matrix in CRS format. + +OUTPUT PARAMETERS + S - sparse matrix in Hash table format. + +NOTE: this function has no effect when called with matrix which is +already in Hash table mode. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparseconverttohash(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparseconverttohash(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function performs out-of-place conversion to Hash table storage +format. S0 is copied to S1 and converted on-the-fly. + +INPUT PARAMETERS + S0 - sparse matrix in any format. + +OUTPUT PARAMETERS + S1 - sparse matrix in Hash table format. + +NOTE: if S0 is stored as Hash-table, it is just copied without conversion. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsecopytohash(const sparsematrix &s0, sparsematrix &s1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecopytohash(const_cast(s0.c_ptr()), const_cast(s1.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function performs out-of-place conversion to CRS format. S0 is +copied to S1 and converted on-the-fly. + +INPUT PARAMETERS + S0 - sparse matrix in any format. + +OUTPUT PARAMETERS + S1 - sparse matrix in CRS format. + +NOTE: if S0 is stored as CRS, it is just copied without conversion. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsecopytocrs(const sparsematrix &s0, sparsematrix &s1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsecopytocrs(const_cast(s0.c_ptr()), const_cast(s1.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function returns type of the matrix storage format. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + sparse storage format used by matrix: + 0 - Hash-table + 1 - CRS-format + +NOTE: future versions of ALGLIB may include additional sparse storage + formats. + + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetmatrixtype(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::sparsegetmatrixtype(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function checks matrix storage format and returns True when matrix is +stored using Hash table representation. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + True if matrix type is Hash table + False if matrix type is not Hash table + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +bool sparseishash(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::sparseishash(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function checks matrix storage format and returns True when matrix is +stored using CRS representation. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + True if matrix type is CRS + False if matrix type is not CRS + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +bool sparseiscrs(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::sparseiscrs(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The function frees all memory occupied by sparse matrix. Sparse matrix +structure becomes unusable after this call. + +OUTPUT PARAMETERS + S - sparse matrix to delete + + -- ALGLIB PROJECT -- + Copyright 24.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsefree(sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sparsefree(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The function returns number of rows of a sparse matrix. + +RESULT: number of rows of a sparse matrix. + + -- ALGLIB PROJECT -- + Copyright 23.08.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetnrows(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::sparsegetnrows(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The function returns number of columns of a sparse matrix. + +RESULT: number of columns of a sparse matrix. + + -- ALGLIB PROJECT -- + Copyright 23.08.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetncols(const sparsematrix &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_int_t result = alglib_impl::sparsegetncols(const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +This object stores state of the iterative norm estimation algorithm. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +_normestimatorstate_owner::_normestimatorstate_owner() +{ + p_struct = (alglib_impl::normestimatorstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::normestimatorstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_normestimatorstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_normestimatorstate_owner::_normestimatorstate_owner(const _normestimatorstate_owner &rhs) +{ + p_struct = (alglib_impl::normestimatorstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::normestimatorstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_normestimatorstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_normestimatorstate_owner& _normestimatorstate_owner::operator=(const _normestimatorstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_normestimatorstate_clear(p_struct); + if( !alglib_impl::_normestimatorstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_normestimatorstate_owner::~_normestimatorstate_owner() +{ + alglib_impl::_normestimatorstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::normestimatorstate* _normestimatorstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::normestimatorstate* _normestimatorstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +normestimatorstate::normestimatorstate() : _normestimatorstate_owner() +{ +} + +normestimatorstate::normestimatorstate(const normestimatorstate &rhs):_normestimatorstate_owner(rhs) +{ +} + +normestimatorstate& normestimatorstate::operator=(const normestimatorstate &rhs) +{ + if( this==&rhs ) + return *this; + _normestimatorstate_owner::operator=(rhs); + return *this; +} + +normestimatorstate::~normestimatorstate() +{ +} + +/************************************************************************* +This procedure initializes matrix norm estimator. + +USAGE: +1. User initializes algorithm state with NormEstimatorCreate() call +2. User calls NormEstimatorEstimateSparse() (or NormEstimatorIteration()) +3. User calls NormEstimatorResults() to get solution. + +INPUT PARAMETERS: + M - number of rows in the matrix being estimated, M>0 + N - number of columns in the matrix being estimated, N>0 + NStart - number of random starting vectors + recommended value - at least 5. + NIts - number of iterations to do with best starting vector + recommended value - at least 5. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTE: this algorithm is effectively deterministic, i.e. it always returns +same result when repeatedly called for the same matrix. In fact, algorithm +uses randomized starting vectors, but internal random numbers generator +always generates same sequence of the random values (it is a feature, not +bug). + +Algorithm can be made non-deterministic with NormEstimatorSetSeed(0) call. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorcreate(const ae_int_t m, const ae_int_t n, const ae_int_t nstart, const ae_int_t nits, normestimatorstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::normestimatorcreate(m, n, nstart, nits, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function changes seed value used by algorithm. In some cases we need +deterministic processing, i.e. subsequent calls must return equal results, +in other cases we need non-deterministic algorithm which returns different +results for the same matrix on every pass. + +Setting zero seed will lead to non-deterministic algorithm, while non-zero +value will make our algorithm deterministic. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + SeedVal - seed value, >=0. Zero value = non-deterministic algo. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorsetseed(const normestimatorstate &state, const ae_int_t seedval) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::normestimatorsetseed(const_cast(state.c_ptr()), seedval, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function estimates norm of the sparse M*N matrix A. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + A - sparse M*N matrix, must be converted to CRS format + prior to calling this function. + +After this function is over you can call NormEstimatorResults() to get +estimate of the norm(A). + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorestimatesparse(const normestimatorstate &state, const sparsematrix &a) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::normestimatorestimatesparse(const_cast(state.c_ptr()), const_cast(a.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Matrix norm estimation results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Nrm - estimate of the matrix norm, Nrm>=0 + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorresults(const normestimatorstate &state, double &nrm) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::normestimatorresults(const_cast(state.c_ptr()), &nrm, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixludet(const real_2d_array &a, const integer_1d_array &pivots, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixludet(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixludet(const real_2d_array &a, const integer_1d_array &pivots) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.rows()!=a.cols()) || (a.rows()!=pivots.length())) + throw ap_error("Error while calling 'rmatrixludet': looks like one of arguments has wrong size"); + n = a.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixludet(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixdet(const real_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixdet(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixdet(const real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.rows()!=a.cols())) + throw ap_error("Error while calling 'rmatrixdet': looks like one of arguments has wrong size"); + n = a.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::rmatrixdet(const_cast(a.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixludet(const complex_2d_array &a, const integer_1d_array &pivots, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_complex result = alglib_impl::cmatrixludet(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixludet(const complex_2d_array &a, const integer_1d_array &pivots) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.rows()!=a.cols()) || (a.rows()!=pivots.length())) + throw ap_error("Error while calling 'cmatrixludet': looks like one of arguments has wrong size"); + n = a.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_complex result = alglib_impl::cmatrixludet(const_cast(a.c_ptr()), const_cast(pivots.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixdet(const complex_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_complex result = alglib_impl::cmatrixdet(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixdet(const complex_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.rows()!=a.cols())) + throw ap_error("Error while calling 'cmatrixdet': looks like one of arguments has wrong size"); + n = a.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ae_complex result = alglib_impl::cmatrixdet(const_cast(a.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by the Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition, + output of SMatrixCholesky subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +As the determinant is equal to the product of squares of diagonal elements, +it’s not necessary to specify which triangle - lower or upper - the matrix +is stored in. + +Result: + matrix determinant. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixcholeskydet(const real_2d_array &a, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixcholeskydet(const_cast(a.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the matrix given by the Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition, + output of SMatrixCholesky subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +As the determinant is equal to the product of squares of diagonal elements, +it’s not necessary to specify which triangle - lower or upper - the matrix +is stored in. + +Result: + matrix determinant. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixcholeskydet(const real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (a.rows()!=a.cols())) + throw ap_error("Error while calling 'spdmatrixcholeskydet': looks like one of arguments has wrong size"); + n = a.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixcholeskydet(const_cast(a.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the symmetric positive definite matrix. + +Input parameters: + A - matrix. Array with elements [0..N-1, 0..N-1]. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Result: + determinant of matrix A. + If matrix A is not positive definite, exception is thrown. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixdet(const real_2d_array &a, const ae_int_t n, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixdet(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Determinant calculation of the symmetric positive definite matrix. + +Input parameters: + A - matrix. Array with elements [0..N-1, 0..N-1]. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Result: + determinant of matrix A. + If matrix A is not positive definite, exception is thrown. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixdet(const real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + bool isupper; + if( (a.rows()!=a.cols())) + throw ap_error("Error while calling 'spdmatrixdet': looks like one of arguments has wrong size"); + if( !alglib_impl::ae_is_symmetric(const_cast(a.c_ptr())) ) + throw ap_error("'a' parameter is not symmetric matrix"); + n = a.rows(); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spdmatrixdet(const_cast(a.c_ptr()), n, isupper, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Algorithm for solving the following generalized symmetric positive-definite +eigenproblem: + A*x = lambda*B*x (1) or + A*B*x = lambda*x (2) or + B*A*x = lambda*x (3). +where A is a symmetric matrix, B - symmetric positive-definite matrix. +The problem is solved by reducing it to an ordinary symmetric eigenvalue +problem. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrices A and B. + IsUpperA - storage format of matrix A. + B - symmetric positive-definite matrix which is given by + its upper or lower triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperB - storage format of matrix B. + ZNeeded - if ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + ProblemType - if ProblemType is equal to: + * 1, the following problem is solved: A*x = lambda*B*x; + * 2, the following problem is solved: A*B*x = lambda*x; + * 3, the following problem is solved: B*A*x = lambda*x. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in matrix columns. It should + be noted that the eigenvectors in such problems do not + form an orthogonal system. + +Result: + True, if the problem was solved successfully. + False, if the error occurred during the Cholesky decomposition of matrix + B (the matrix isn’t positive-definite) or during the work of the iterative + algorithm for solving the symmetric eigenproblem. + +See also the GeneralizedSymmetricDefiniteEVDReduce subroutine. + + -- ALGLIB -- + Copyright 1.28.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixgevd(const real_2d_array &a, const ae_int_t n, const bool isuppera, const real_2d_array &b, const bool isupperb, const ae_int_t zneeded, const ae_int_t problemtype, real_1d_array &d, real_2d_array &z) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixgevd(const_cast(a.c_ptr()), n, isuppera, const_cast(b.c_ptr()), isupperb, zneeded, problemtype, const_cast(d.c_ptr()), const_cast(z.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Algorithm for reduction of the following generalized symmetric positive- +definite eigenvalue problem: + A*x = lambda*B*x (1) or + A*B*x = lambda*x (2) or + B*A*x = lambda*x (3) +to the symmetric eigenvalues problem C*y = lambda*y (eigenvalues of this and +the given problems are the same, and the eigenvectors of the given problem +could be obtained by multiplying the obtained eigenvectors by the +transformation matrix x = R*y). + +Here A is a symmetric matrix, B - symmetric positive-definite matrix. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrices A and B. + IsUpperA - storage format of matrix A. + B - symmetric positive-definite matrix which is given by + its upper or lower triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperB - storage format of matrix B. + ProblemType - if ProblemType is equal to: + * 1, the following problem is solved: A*x = lambda*B*x; + * 2, the following problem is solved: A*B*x = lambda*x; + * 3, the following problem is solved: B*A*x = lambda*x. + +Output parameters: + A - symmetric matrix which is given by its upper or lower + triangle depending on IsUpperA. Contains matrix C. + Array whose indexes range within [0..N-1, 0..N-1]. + R - upper triangular or low triangular transformation matrix + which is used to obtain the eigenvectors of a given problem + as the product of eigenvectors of C (from the right) and + matrix R (from the left). If the matrix is upper + triangular, the elements below the main diagonal + are equal to 0 (and vice versa). Thus, we can perform + the multiplication without taking into account the + internal structure (which is an easier though less + effective way). + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperR - type of matrix R (upper or lower triangular). + +Result: + True, if the problem was reduced successfully. + False, if the error occurred during the Cholesky decomposition of + matrix B (the matrix is not positive-definite). + + -- ALGLIB -- + Copyright 1.28.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixgevdreduce(real_2d_array &a, const ae_int_t n, const bool isuppera, const real_2d_array &b, const bool isupperb, const ae_int_t problemtype, real_2d_array &r, bool &isupperr) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::smatrixgevdreduce(const_cast(a.c_ptr()), n, isuppera, const_cast(b.c_ptr()), isupperb, problemtype, const_cast(r.c_ptr()), &isupperr, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a number to an element +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - row where the element to be updated is stored. + UpdColumn - column where the element to be updated is stored. + UpdVal - a number to be added to the element. + + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatesimple(real_2d_array &inva, const ae_int_t n, const ae_int_t updrow, const ae_int_t updcolumn, const double updval) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinvupdatesimple(const_cast(inva.c_ptr()), n, updrow, updcolumn, updval, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a row +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - the row of A whose vector V was added. + 0 <= Row <= N-1 + V - the vector to be added to a row. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdaterow(real_2d_array &inva, const ae_int_t n, const ae_int_t updrow, const real_1d_array &v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinvupdaterow(const_cast(inva.c_ptr()), n, updrow, const_cast(v.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a column +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdColumn - the column of A whose vector U was added. + 0 <= UpdColumn <= N-1 + U - the vector to be added to a column. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatecolumn(real_2d_array &inva, const ae_int_t n, const ae_int_t updcolumn, const real_1d_array &u) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinvupdatecolumn(const_cast(inva.c_ptr()), n, updcolumn, const_cast(u.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm computes the inverse of matrix A+u*v’ by using the given matrix +A^-1 and the vectors u and v. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + U - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + V - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of matrix A + u*v'. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdateuv(real_2d_array &inva, const ae_int_t n, const real_1d_array &u, const real_1d_array &v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixinvupdateuv(const_cast(inva.c_ptr()), n, const_cast(u.c_ptr()), const_cast(v.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Subroutine performing the Schur decomposition of a general matrix by using +the QR algorithm with multiple shifts. + +The source matrix A is represented as S'*A*S = T, where S is an orthogonal +matrix (Schur vectors), T - upper quasi-triangular matrix (with blocks of +sizes 1x1 and 2x2 on the main diagonal). + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of A, N>=0. + + +Output parameters: + A - contains matrix T. + Array whose indexes range within [0..N-1, 0..N-1]. + S - contains Schur vectors. + Array whose indexes range within [0..N-1, 0..N-1]. + +Note 1: + The block structure of matrix T can be easily recognized: since all + the elements below the blocks are zeros, the elements a[i+1,i] which + are equal to 0 show the block border. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms in linear algebra). If you require maximum performance on + your machine, it is recommended to adjust this parameter manually. + +Result: + True, + if the algorithm has converged and parameters A and S contain the result. + False, + if the algorithm has not converged. + +Algorithm implemented on the basis of the DHSEQR subroutine (LAPACK 3.0 library). +*************************************************************************/ +bool rmatrixschur(real_2d_array &a, const ae_int_t n, real_2d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::rmatrixschur(const_cast(a.c_ptr()), n, const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static ae_int_t ablas_rgemmparallelsize = 64; +static ae_int_t ablas_cgemmparallelsize = 64; +static void ablas_ablasinternalsplitlength(ae_int_t n, + ae_int_t nb, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state); +static void ablas_cmatrixrighttrsm2(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +static void ablas_cmatrixlefttrsm2(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +static void ablas_rmatrixrighttrsm2(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +static void ablas_rmatrixlefttrsm2(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +static void ablas_cmatrixsyrk2(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +static void ablas_rmatrixsyrk2(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); + + +static void ortfac_cmatrixqrbasecase(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* work, + /* Complex */ ae_vector* t, + /* Complex */ ae_vector* tau, + ae_state *_state); +static void ortfac_cmatrixlqbasecase(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* work, + /* Complex */ ae_vector* t, + /* Complex */ ae_vector* tau, + ae_state *_state); +static void ortfac_rmatrixblockreflector(/* Real */ ae_matrix* a, + /* Real */ ae_vector* tau, + ae_bool columnwisea, + ae_int_t lengtha, + ae_int_t blocksize, + /* Real */ ae_matrix* t, + /* Real */ ae_vector* work, + ae_state *_state); +static void ortfac_cmatrixblockreflector(/* Complex */ ae_matrix* a, + /* Complex */ ae_vector* tau, + ae_bool columnwisea, + ae_int_t lengtha, + ae_int_t blocksize, + /* Complex */ ae_matrix* t, + /* Complex */ ae_vector* work, + ae_state *_state); + + +static ae_bool bdsvd_bidiagonalsvddecompositioninternal(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t ustart, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t cstart, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t vstart, + ae_int_t ncvt, + ae_state *_state); +static double bdsvd_extsignbdsqr(double a, double b, ae_state *_state); +static void bdsvd_svd2x2(double f, + double g, + double h, + double* ssmin, + double* ssmax, + ae_state *_state); +static void bdsvd_svdv2x2(double f, + double g, + double h, + double* ssmin, + double* ssmax, + double* snr, + double* csr, + double* snl, + double* csl, + ae_state *_state); + + + + +static ae_bool evd_tridiagonalevd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + /* Real */ ae_matrix* z, + ae_state *_state); +static void evd_tdevde2(double a, + double b, + double c, + double* rt1, + double* rt2, + ae_state *_state); +static void evd_tdevdev2(double a, + double b, + double c, + double* rt1, + double* rt2, + double* cs1, + double* sn1, + ae_state *_state); +static double evd_tdevdpythag(double a, double b, ae_state *_state); +static double evd_tdevdextsign(double a, double b, ae_state *_state); +static ae_bool evd_internalbisectioneigenvalues(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t irange, + ae_int_t iorder, + double vl, + double vu, + ae_int_t il, + ae_int_t iu, + double abstol, + /* Real */ ae_vector* w, + ae_int_t* m, + ae_int_t* nsplit, + /* Integer */ ae_vector* iblock, + /* Integer */ ae_vector* isplit, + ae_int_t* errorcode, + ae_state *_state); +static void evd_internaldstein(ae_int_t n, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t m, + /* Real */ ae_vector* w, + /* Integer */ ae_vector* iblock, + /* Integer */ ae_vector* isplit, + /* Real */ ae_matrix* z, + /* Integer */ ae_vector* ifail, + ae_int_t* info, + ae_state *_state); +static void evd_tdininternaldlagtf(ae_int_t n, + /* Real */ ae_vector* a, + double lambdav, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + double tol, + /* Real */ ae_vector* d, + /* Integer */ ae_vector* iin, + ae_int_t* info, + ae_state *_state); +static void evd_tdininternaldlagts(ae_int_t n, + /* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + /* Integer */ ae_vector* iin, + /* Real */ ae_vector* y, + double* tol, + ae_int_t* info, + ae_state *_state); +static void evd_internaldlaebz(ae_int_t ijob, + ae_int_t nitmax, + ae_int_t n, + ae_int_t mmax, + ae_int_t minp, + double abstol, + double reltol, + double pivmin, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + /* Real */ ae_vector* e2, + /* Integer */ ae_vector* nval, + /* Real */ ae_matrix* ab, + /* Real */ ae_vector* c, + ae_int_t* mout, + /* Integer */ ae_matrix* nab, + /* Real */ ae_vector* work, + /* Integer */ ae_vector* iwork, + ae_int_t* info, + ae_state *_state); +static void evd_internaltrevc(/* Real */ ae_matrix* t, + ae_int_t n, + ae_int_t side, + ae_int_t howmny, + /* Boolean */ ae_vector* vselect, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_int_t* m, + ae_int_t* info, + ae_state *_state); +static void evd_internalhsevdlaln2(ae_bool ltrans, + ae_int_t na, + ae_int_t nw, + double smin, + double ca, + /* Real */ ae_matrix* a, + double d1, + double d2, + /* Real */ ae_matrix* b, + double wr, + double wi, + /* Boolean */ ae_vector* rswap4, + /* Boolean */ ae_vector* zswap4, + /* Integer */ ae_matrix* ipivot44, + /* Real */ ae_vector* civ4, + /* Real */ ae_vector* crv4, + /* Real */ ae_matrix* x, + double* scl, + double* xnorm, + ae_int_t* info, + ae_state *_state); +static void evd_internalhsevdladiv(double a, + double b, + double c, + double d, + double* p, + double* q, + ae_state *_state); +static ae_bool evd_nonsymmetricevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t vneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_state *_state); +static void evd_toupperhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state); +static void evd_unpackqfromupperhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state); + + + + +static void trfac_cmatrixluprec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static void trfac_rmatrixluprec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void trfac_cmatrixplurec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static void trfac_rmatrixplurec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void trfac_cmatrixlup2(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static void trfac_rmatrixlup2(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void trfac_cmatrixplu2(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static void trfac_rmatrixplu2(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state); +static ae_bool trfac_hpdmatrixcholeskyrec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static ae_bool trfac_hpdmatrixcholesky2(/* Complex */ ae_matrix* aaa, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static ae_bool trfac_spdmatrixcholesky2(/* Real */ ae_matrix* aaa, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state); + + +static void rcond_rmatrixrcondtrinternal(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_bool onenorm, + double anorm, + double* rc, + ae_state *_state); +static void rcond_cmatrixrcondtrinternal(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_bool onenorm, + double anorm, + double* rc, + ae_state *_state); +static void rcond_spdmatrixrcondcholeskyinternal(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + ae_bool isnormprovided, + double anorm, + double* rc, + ae_state *_state); +static void rcond_hpdmatrixrcondcholeskyinternal(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + ae_bool isnormprovided, + double anorm, + double* rc, + ae_state *_state); +static void rcond_rmatrixrcondluinternal(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_bool onenorm, + ae_bool isanormprovided, + double anorm, + double* rc, + ae_state *_state); +static void rcond_cmatrixrcondluinternal(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_bool onenorm, + ae_bool isanormprovided, + double anorm, + double* rc, + ae_state *_state); +static void rcond_rmatrixestimatenorm(ae_int_t n, + /* Real */ ae_vector* v, + /* Real */ ae_vector* x, + /* Integer */ ae_vector* isgn, + double* est, + ae_int_t* kase, + ae_state *_state); +static void rcond_cmatrixestimatenorm(ae_int_t n, + /* Complex */ ae_vector* v, + /* Complex */ ae_vector* x, + double* est, + ae_int_t* kase, + /* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_state *_state); +static double rcond_internalcomplexrcondscsum1(/* Complex */ ae_vector* x, + ae_int_t n, + ae_state *_state); +static ae_int_t rcond_internalcomplexrcondicmax1(/* Complex */ ae_vector* x, + ae_int_t n, + ae_state *_state); +static void rcond_internalcomplexrcondsaveall(/* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_int_t* i, + ae_int_t* iter, + ae_int_t* j, + ae_int_t* jlast, + ae_int_t* jump, + double* absxi, + double* altsgn, + double* estold, + double* temp, + ae_state *_state); +static void rcond_internalcomplexrcondloadall(/* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_int_t* i, + ae_int_t* iter, + ae_int_t* j, + ae_int_t* jlast, + ae_int_t* jump, + double* absxi, + double* altsgn, + double* estold, + double* temp, + ae_state *_state); + + +static void matinv_rmatrixtrinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + /* Real */ ae_vector* tmp, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +static void matinv_cmatrixtrinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + /* Complex */ ae_vector* tmp, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +static void matinv_rmatrixluinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + /* Real */ ae_vector* work, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +static void matinv_cmatrixluinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + /* Complex */ ae_vector* work, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +static void matinv_spdmatrixcholeskyinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void matinv_hpdmatrixcholeskyinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state); + + +static double sparse_desiredloadfactor = 0.66; +static double sparse_maxloadfactor = 0.75; +static double sparse_growfactor = 2.00; +static ae_int_t sparse_additional = 10; +static ae_int_t sparse_linalgswitch = 16; +static void sparse_sparseinitduidx(sparsematrix* s, ae_state *_state); +static ae_int_t sparse_hash(ae_int_t i, + ae_int_t j, + ae_int_t tabsize, + ae_state *_state); + + + + + + + + + + + + + + + + + +/************************************************************************* +Splits matrix length in two parts, left part should match ABLAS block size + +INPUT PARAMETERS + A - real matrix, is passed to ensure that we didn't split + complex matrix using real splitting subroutine. + matrix itself is not changed. + N - length, N>0 + +OUTPUT PARAMETERS + N1 - length + N2 - length + +N1+N2=N, N1>=N2, N2 may be zero + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +void ablassplitlength(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state) +{ + + *n1 = 0; + *n2 = 0; + + if( n>ablasblocksize(a, _state) ) + { + ablas_ablasinternalsplitlength(n, ablasblocksize(a, _state), n1, n2, _state); + } + else + { + ablas_ablasinternalsplitlength(n, ablasmicroblocksize(_state), n1, n2, _state); + } +} + + +/************************************************************************* +Complex ABLASSplitLength + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +void ablascomplexsplitlength(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state) +{ + + *n1 = 0; + *n2 = 0; + + if( n>ablascomplexblocksize(a, _state) ) + { + ablas_ablasinternalsplitlength(n, ablascomplexblocksize(a, _state), n1, n2, _state); + } + else + { + ablas_ablasinternalsplitlength(n, ablasmicroblocksize(_state), n1, n2, _state); + } +} + + +/************************************************************************* +Returns block size - subdivision size where cache-oblivious soubroutines +switch to the optimized kernel. + +INPUT PARAMETERS + A - real matrix, is passed to ensure that we didn't split + complex matrix using real splitting subroutine. + matrix itself is not changed. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_int_t ablasblocksize(/* Real */ ae_matrix* a, ae_state *_state) +{ + ae_int_t result; + + + result = 32; + return result; +} + + +/************************************************************************* +Block size for complex subroutines. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_int_t ablascomplexblocksize(/* Complex */ ae_matrix* a, + ae_state *_state) +{ + ae_int_t result; + + + result = 24; + return result; +} + + +/************************************************************************* +Microblock size + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_int_t ablasmicroblocksize(ae_state *_state) +{ + ae_int_t result; + + + result = 8; + return result; +} + + +/************************************************************************* +Cache-oblivous complex "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixtranspose(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state) +{ + ae_int_t i; + ae_int_t s1; + ae_int_t s2; + + + if( m<=2*ablascomplexblocksize(a, _state)&&n<=2*ablascomplexblocksize(a, _state) ) + { + + /* + * base case + */ + for(i=0; i<=m-1; i++) + { + ae_v_cmove(&b->ptr.pp_complex[ib][jb+i], b->stride, &a->ptr.pp_complex[ia+i][ja], 1, "N", ae_v_len(ib,ib+n-1)); + } + } + else + { + + /* + * Cache-oblivious recursion + */ + if( m>n ) + { + ablascomplexsplitlength(a, m, &s1, &s2, _state); + cmatrixtranspose(s1, n, a, ia, ja, b, ib, jb, _state); + cmatrixtranspose(s2, n, a, ia+s1, ja, b, ib, jb+s1, _state); + } + else + { + ablascomplexsplitlength(a, n, &s1, &s2, _state); + cmatrixtranspose(m, s1, a, ia, ja, b, ib, jb, _state); + cmatrixtranspose(m, s2, a, ia, ja+s1, b, ib+s1, jb, _state); + } + } +} + + +/************************************************************************* +Cache-oblivous real "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixtranspose(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state) +{ + ae_int_t i; + ae_int_t s1; + ae_int_t s2; + + + if( m<=2*ablasblocksize(a, _state)&&n<=2*ablasblocksize(a, _state) ) + { + + /* + * base case + */ + for(i=0; i<=m-1; i++) + { + ae_v_move(&b->ptr.pp_double[ib][jb+i], b->stride, &a->ptr.pp_double[ia+i][ja], 1, ae_v_len(ib,ib+n-1)); + } + } + else + { + + /* + * Cache-oblivious recursion + */ + if( m>n ) + { + ablassplitlength(a, m, &s1, &s2, _state); + rmatrixtranspose(s1, n, a, ia, ja, b, ib, jb, _state); + rmatrixtranspose(s2, n, a, ia+s1, ja, b, ib, jb+s1, _state); + } + else + { + ablassplitlength(a, n, &s1, &s2, _state); + rmatrixtranspose(m, s1, a, ia, ja, b, ib, jb, _state); + rmatrixtranspose(m, s2, a, ia, ja+s1, b, ib+s1, jb, _state); + } + } +} + + +/************************************************************************* +This code enforces symmetricy of the matrix by copying Upper part to lower +one (or vice versa). + +INPUT PARAMETERS: + A - matrix + N - number of rows/columns + IsUpper - whether we want to copy upper triangle to lower one (True) + or vice versa (False). +*************************************************************************/ +void rmatrixenforcesymmetricity(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + if( isupper ) + { + for(i=0; i<=n-1; i++) + { + for(j=i+1; j<=n-1; j++) + { + a->ptr.pp_double[j][i] = a->ptr.pp_double[i][j]; + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + for(j=i+1; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = a->ptr.pp_double[j][i]; + } + } + } +} + + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixcopy(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state) +{ + ae_int_t i; + + + if( m==0||n==0 ) + { + return; + } + for(i=0; i<=m-1; i++) + { + ae_v_cmove(&b->ptr.pp_complex[ib+i][jb], 1, &a->ptr.pp_complex[ia+i][ja], 1, "N", ae_v_len(jb,jb+n-1)); + } +} + + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixcopy(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state) +{ + ae_int_t i; + + + if( m==0||n==0 ) + { + return; + } + for(i=0; i<=m-1; i++) + { + ae_v_move(&b->ptr.pp_double[ib+i][jb], 1, &a->ptr.pp_double[ia+i][ja], 1, ae_v_len(jb,jb+n-1)); + } +} + + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void cmatrixrank1(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_vector* u, + ae_int_t iu, + /* Complex */ ae_vector* v, + ae_int_t iv, + ae_state *_state) +{ + ae_int_t i; + ae_complex s; + + + if( m==0||n==0 ) + { + return; + } + if( cmatrixrank1f(m, n, a, ia, ja, u, iu, v, iv, _state) ) + { + return; + } + for(i=0; i<=m-1; i++) + { + s = u->ptr.p_complex[iu+i]; + ae_v_caddc(&a->ptr.pp_complex[ia+i][ja], 1, &v->ptr.p_complex[iv], 1, "N", ae_v_len(ja,ja+n-1), s); + } +} + + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void rmatrixrank1(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_vector* u, + ae_int_t iu, + /* Real */ ae_vector* v, + ae_int_t iv, + ae_state *_state) +{ + ae_int_t i; + double s; + + + if( m==0||n==0 ) + { + return; + } + if( rmatrixrank1f(m, n, a, ia, ja, u, iu, v, iv, _state) ) + { + return; + } + for(i=0; i<=m-1; i++) + { + s = u->ptr.p_double[iu+i]; + ae_v_addd(&a->ptr.pp_double[ia+i][ja], 1, &v->ptr.p_double[iv], 1, ae_v_len(ja,ja+n-1), s); + } +} + + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + M>=0 + N - number of columns of op(A) + N>=0 + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + * OpA=2 => op(A) = A^H + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixmv(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Complex */ ae_vector* x, + ae_int_t ix, + /* Complex */ ae_vector* y, + ae_int_t iy, + ae_state *_state) +{ + ae_int_t i; + ae_complex v; + + + if( m==0 ) + { + return; + } + if( n==0 ) + { + for(i=0; i<=m-1; i++) + { + y->ptr.p_complex[iy+i] = ae_complex_from_d(0); + } + return; + } + if( cmatrixmvf(m, n, a, ia, ja, opa, x, ix, y, iy, _state) ) + { + return; + } + if( opa==0 ) + { + + /* + * y = A*x + */ + for(i=0; i<=m-1; i++) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia+i][ja], 1, "N", &x->ptr.p_complex[ix], 1, "N", ae_v_len(ja,ja+n-1)); + y->ptr.p_complex[iy+i] = v; + } + return; + } + if( opa==1 ) + { + + /* + * y = A^T*x + */ + for(i=0; i<=m-1; i++) + { + y->ptr.p_complex[iy+i] = ae_complex_from_d(0); + } + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_complex[ix+i]; + ae_v_caddc(&y->ptr.p_complex[iy], 1, &a->ptr.pp_complex[ia+i][ja], 1, "N", ae_v_len(iy,iy+m-1), v); + } + return; + } + if( opa==2 ) + { + + /* + * y = A^H*x + */ + for(i=0; i<=m-1; i++) + { + y->ptr.p_complex[iy+i] = ae_complex_from_d(0); + } + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_complex[ix+i]; + ae_v_caddc(&y->ptr.p_complex[iy], 1, &a->ptr.pp_complex[ia+i][ja], 1, "Conj", ae_v_len(iy,iy+m-1), v); + } + return; + } +} + + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + N - number of columns of op(A) + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixmv(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Real */ ae_vector* x, + ae_int_t ix, + /* Real */ ae_vector* y, + ae_int_t iy, + ae_state *_state) +{ + ae_int_t i; + double v; + + + if( m==0 ) + { + return; + } + if( n==0 ) + { + for(i=0; i<=m-1; i++) + { + y->ptr.p_double[iy+i] = 0; + } + return; + } + if( rmatrixmvf(m, n, a, ia, ja, opa, x, ix, y, iy, _state) ) + { + return; + } + if( opa==0 ) + { + + /* + * y = A*x + */ + for(i=0; i<=m-1; i++) + { + v = ae_v_dotproduct(&a->ptr.pp_double[ia+i][ja], 1, &x->ptr.p_double[ix], 1, ae_v_len(ja,ja+n-1)); + y->ptr.p_double[iy+i] = v; + } + return; + } + if( opa==1 ) + { + + /* + * y = A^T*x + */ + for(i=0; i<=m-1; i++) + { + y->ptr.p_double[iy+i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_double[ix+i]; + ae_v_addd(&y->ptr.p_double[iy], 1, &a->ptr.pp_double[ia+i][ja], 1, ae_v_len(iy,iy+m-1), v); + } + return; + } +} + + +void cmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablascomplexblocksize(a, _state); + if( m<=bs&&n<=bs ) + { + ablas_cmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( m>=n ) + { + + /* + * Split X: X*A = (X1 X2)^T*A + */ + ablascomplexsplitlength(a, m, &s1, &s2, _state); + cmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + else + { + + /* + * Split A: + * (A1 A12) + * X*op(A) = X*op( ) + * ( A2) + * + * Different variants depending on + * IsUpper/OpType combinations + */ + ablascomplexsplitlength(a, n, &s1, &s2, _state); + if( isupper&&optype==0 ) + { + + /* + * (A1 A12)-1 + * X*A^-1 = (X1 X2)*( ) + * ( A2) + */ + cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixgemm(m, s2, s1, ae_complex_from_d(-1.0), x, i2, j2, 0, a, i1, j1+s1, 0, ae_complex_from_d(1.0), x, i2, j2+s1, _state); + cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + return; + } + if( isupper&&optype!=0 ) + { + + /* + * (A1' )-1 + * X*A^-1 = (X1 X2)*( ) + * (A12' A2') + */ + cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + cmatrixgemm(m, s1, s2, ae_complex_from_d(-1.0), x, i2, j2+s1, 0, a, i1, j1+s1, optype, ae_complex_from_d(1.0), x, i2, j2, _state); + cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( !isupper&&optype==0 ) + { + + /* + * (A1 )-1 + * X*A^-1 = (X1 X2)*( ) + * (A21 A2) + */ + cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + cmatrixgemm(m, s1, s2, ae_complex_from_d(-1.0), x, i2, j2+s1, 0, a, i1+s1, j1, 0, ae_complex_from_d(1.0), x, i2, j2, _state); + cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( !isupper&&optype!=0 ) + { + + /* + * (A1' A21')-1 + * X*A^-1 = (X1 X2)*( ) + * ( A2') + */ + cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixgemm(m, s2, s1, ae_complex_from_d(-1.0), x, i2, j2, 0, a, i1+s1, j1, optype, ae_complex_from_d(1.0), x, i2, j2+s1, _state); + cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_cmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state) +{ + cmatrixrighttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _state); +} + + +void cmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablascomplexblocksize(a, _state); + if( m<=bs&&n<=bs ) + { + ablas_cmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( n>=m ) + { + + /* + * Split X: op(A)^-1*X = op(A)^-1*(X1 X2) + */ + ablascomplexsplitlength(x, n, &s1, &s2, _state); + cmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _state); + return; + } + else + { + + /* + * Split A + */ + ablascomplexsplitlength(a, m, &s1, &s2, _state); + if( isupper&&optype==0 ) + { + + /* + * (A1 A12)-1 ( X1 ) + * A^-1*X* = ( ) *( ) + * ( A2) ( X2 ) + */ + cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + cmatrixgemm(s1, n, s2, ae_complex_from_d(-1.0), a, i1, j1+s1, 0, x, i2+s1, j2, 0, ae_complex_from_d(1.0), x, i2, j2, _state); + cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( isupper&&optype!=0 ) + { + + /* + * (A1' )-1 ( X1 ) + * A^-1*X = ( ) *( ) + * (A12' A2') ( X2 ) + */ + cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixgemm(s2, n, s1, ae_complex_from_d(-1.0), a, i1, j1+s1, optype, x, i2, j2, 0, ae_complex_from_d(1.0), x, i2+s1, j2, _state); + cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + if( !isupper&&optype==0 ) + { + + /* + * (A1 )-1 ( X1 ) + * A^-1*X = ( ) *( ) + * (A21 A2) ( X2 ) + */ + cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + cmatrixgemm(s2, n, s1, ae_complex_from_d(-1.0), a, i1+s1, j1, 0, x, i2, j2, 0, ae_complex_from_d(1.0), x, i2+s1, j2, _state); + cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + if( !isupper&&optype!=0 ) + { + + /* + * (A1' A21')-1 ( X1 ) + * A^-1*X = ( ) *( ) + * ( A2') ( X2 ) + */ + cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + cmatrixgemm(s1, n, s2, ae_complex_from_d(-1.0), a, i1+s1, j1, optype, x, i2+s1, j2, 0, ae_complex_from_d(1.0), x, i2, j2, _state); + cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_cmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state) +{ + cmatrixlefttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _state); +} + + +void rmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablasblocksize(a, _state); + if( m<=bs&&n<=bs ) + { + ablas_rmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( m>=n ) + { + + /* + * Split X: X*A = (X1 X2)^T*A + */ + ablassplitlength(a, m, &s1, &s2, _state); + rmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + else + { + + /* + * Split A: + * (A1 A12) + * X*op(A) = X*op( ) + * ( A2) + * + * Different variants depending on + * IsUpper/OpType combinations + */ + ablassplitlength(a, n, &s1, &s2, _state); + if( isupper&&optype==0 ) + { + + /* + * (A1 A12)-1 + * X*A^-1 = (X1 X2)*( ) + * ( A2) + */ + rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1, j1+s1, 0, 1.0, x, i2, j2+s1, _state); + rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + return; + } + if( isupper&&optype!=0 ) + { + + /* + * (A1' )-1 + * X*A^-1 = (X1 X2)*( ) + * (A12' A2') + */ + rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1, j1+s1, optype, 1.0, x, i2, j2, _state); + rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( !isupper&&optype==0 ) + { + + /* + * (A1 )-1 + * X*A^-1 = (X1 X2)*( ) + * (A21 A2) + */ + rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1+s1, j1, 0, 1.0, x, i2, j2, _state); + rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( !isupper&&optype!=0 ) + { + + /* + * (A1' A21')-1 + * X*A^-1 = (X1 X2)*( ) + * ( A2') + */ + rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1+s1, j1, optype, 1.0, x, i2, j2+s1, _state); + rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_rmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state) +{ + rmatrixrighttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _state); +} + + +void rmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablasblocksize(a, _state); + if( m<=bs&&n<=bs ) + { + ablas_rmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( n>=m ) + { + + /* + * Split X: op(A)^-1*X = op(A)^-1*(X1 X2) + */ + ablassplitlength(x, n, &s1, &s2, _state); + rmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _state); + } + else + { + + /* + * Split A + */ + ablassplitlength(a, m, &s1, &s2, _state); + if( isupper&&optype==0 ) + { + + /* + * (A1 A12)-1 ( X1 ) + * A^-1*X* = ( ) *( ) + * ( A2) ( X2 ) + */ + rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + rmatrixgemm(s1, n, s2, -1.0, a, i1, j1+s1, 0, x, i2+s1, j2, 0, 1.0, x, i2, j2, _state); + rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + if( isupper&&optype!=0 ) + { + + /* + * (A1' )-1 ( X1 ) + * A^-1*X = ( ) *( ) + * (A12' A2') ( X2 ) + */ + rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixgemm(s2, n, s1, -1.0, a, i1, j1+s1, optype, x, i2, j2, 0, 1.0, x, i2+s1, j2, _state); + rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + if( !isupper&&optype==0 ) + { + + /* + * (A1 )-1 ( X1 ) + * A^-1*X = ( ) *( ) + * (A21 A2) ( X2 ) + */ + rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + rmatrixgemm(s2, n, s1, -1.0, a, i1+s1, j1, 0, x, i2, j2, 0, 1.0, x, i2+s1, j2, _state); + rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + return; + } + if( !isupper&&optype!=0 ) + { + + /* + * (A1' A21')-1 ( X1 ) + * A^-1*X = ( ) *( ) + * ( A2') ( X2 ) + */ + rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _state); + rmatrixgemm(s1, n, s2, -1.0, a, i1+s1, j1, optype, x, i2+s1, j2, 0, 1.0, x, i2, j2, _state); + rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_rmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state) +{ + rmatrixlefttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _state); +} + + +void cmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablascomplexblocksize(a, _state); + if( n<=bs&&k<=bs ) + { + ablas_cmatrixsyrk2(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + return; + } + if( k>=n ) + { + + /* + * Split K + */ + ablascomplexsplitlength(a, k, &s1, &s2, _state); + if( optypea==0 ) + { + cmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixsyrk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, c, ic, jc, isupper, _state); + } + else + { + cmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixsyrk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, c, ic, jc, isupper, _state); + } + } + else + { + + /* + * Split N + */ + ablascomplexsplitlength(a, n, &s1, &s2, _state); + if( optypea==0&&isupper ) + { + cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixgemm(s1, s2, k, ae_complex_from_d(alpha), a, ia, ja, 0, a, ia+s1, ja, 2, ae_complex_from_d(beta), c, ic, jc+s1, _state); + cmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea==0&&!isupper ) + { + cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixgemm(s2, s1, k, ae_complex_from_d(alpha), a, ia+s1, ja, 0, a, ia, ja, 2, ae_complex_from_d(beta), c, ic+s1, jc, _state); + cmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea!=0&&isupper ) + { + cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixgemm(s1, s2, k, ae_complex_from_d(alpha), a, ia, ja, 2, a, ia, ja+s1, 0, ae_complex_from_d(beta), c, ic, jc+s1, _state); + cmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea!=0&&!isupper ) + { + cmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + cmatrixgemm(s2, s1, k, ae_complex_from_d(alpha), a, ia, ja+s1, 2, a, ia, ja, 0, ae_complex_from_d(beta), c, ic+s1, jc, _state); + cmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_cmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, ae_state *_state) +{ + cmatrixsyrk(n,k,alpha,a,ia,ja,optypea,beta,c,ic,jc,isupper, _state); +} + + +void rmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablasblocksize(a, _state); + + /* + * Use MKL or generic basecase code + */ + if( rmatrixsyrkmkl(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state) ) + { + return; + } + if( n<=bs&&k<=bs ) + { + ablas_rmatrixsyrk2(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + return; + } + + /* + * Recursive subdivision of the problem + */ + if( k>=n ) + { + + /* + * Split K + */ + ablassplitlength(a, k, &s1, &s2, _state); + if( optypea==0 ) + { + rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixsyrk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, c, ic, jc, isupper, _state); + } + else + { + rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixsyrk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, c, ic, jc, isupper, _state); + } + } + else + { + + /* + * Split N + */ + ablassplitlength(a, n, &s1, &s2, _state); + if( optypea==0&&isupper ) + { + rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 0, a, ia+s1, ja, 1, beta, c, ic, jc+s1, _state); + rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea==0&&!isupper ) + { + rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixgemm(s2, s1, k, alpha, a, ia+s1, ja, 0, a, ia, ja, 1, beta, c, ic+s1, jc, _state); + rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea!=0&&isupper ) + { + rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 1, a, ia, ja+s1, 0, beta, c, ic, jc+s1, _state); + rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + if( optypea!=0&&!isupper ) + { + rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state); + rmatrixgemm(s2, s1, k, alpha, a, ia, ja+s1, 1, a, ia, ja, 0, beta, c, ic+s1, jc, _state); + rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _state); + return; + } + } +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_rmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, ae_state *_state) +{ + rmatrixsyrk(n,k,alpha,a,ia,ja,optypea,beta,c,ic,jc,isupper, _state); +} + + +void cmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablascomplexblocksize(a, _state); + if( (m<=bs&&n<=bs)&&k<=bs ) + { + cmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + return; + } + + /* + * SMP support is turned on when M or N are larger than some boundary value. + * Magnitude of K is not taken into account because splitting on K does not + * allow us to spawn child tasks. + */ + + /* + * Recursive algorithm: parallel splitting on M/N + */ + if( m>=n&&m>=k ) + { + + /* + * A*B = (A1 A2)^T*B + */ + ablascomplexsplitlength(a, m, &s1, &s2, _state); + cmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + if( optypea==0 ) + { + cmatrixgemm(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _state); + } + else + { + cmatrixgemm(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _state); + } + return; + } + if( n>=m&&n>=k ) + { + + /* + * A*B = A*(B1 B2) + */ + ablascomplexsplitlength(a, n, &s1, &s2, _state); + if( optypeb==0 ) + { + cmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, c, ic, jc+s1, _state); + } + else + { + cmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, c, ic, jc+s1, _state); + } + return; + } + + /* + * Recursive algorithm: serial splitting on K + */ + + /* + * A*B = (A1 A2)*(B1 B2)^T + */ + ablascomplexsplitlength(a, k, &s1, &s2, _state); + if( optypea==0&&optypeb==0 ) + { + cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, ae_complex_from_d(1.0), c, ic, jc, _state); + } + if( optypea==0&&optypeb!=0 ) + { + cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, ae_complex_from_d(1.0), c, ic, jc, _state); + } + if( optypea!=0&&optypeb==0 ) + { + cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, ae_complex_from_d(1.0), c, ic, jc, _state); + } + if( optypea!=0&&optypeb!=0 ) + { + cmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + cmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, ae_complex_from_d(1.0), c, ic, jc, _state); + } + return; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_cmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, ae_state *_state) +{ + cmatrixgemm(m,n,k,alpha,a,ia,ja,optypea,b,ib,jb,optypeb,beta,c,ic,jc, _state); +} + + +void rmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state) +{ + ae_int_t s1; + ae_int_t s2; + ae_int_t bs; + + + bs = ablasblocksize(a, _state); + + /* + * Check input sizes for correctness + */ + ae_assert(optypea==0||optypea==1, "RMatrixGEMM: incorrect OpTypeA (must be 0 or 1)", _state); + ae_assert(optypeb==0||optypeb==1, "RMatrixGEMM: incorrect OpTypeB (must be 0 or 1)", _state); + ae_assert(ic+m<=c->rows, "RMatrixGEMM: incorect size of output matrix C", _state); + ae_assert(jc+n<=c->cols, "RMatrixGEMM: incorect size of output matrix C", _state); + + /* + * Use MKL or ALGLIB basecase code + */ + if( rmatrixgemmmkl(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state) ) + { + return; + } + if( (m<=bs&&n<=bs)&&k<=bs ) + { + rmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + return; + } + + /* + * SMP support is turned on when M or N are larger than some boundary value. + * Magnitude of K is not taken into account because splitting on K does not + * allow us to spawn child tasks. + */ + + /* + * Recursive algorithm: split on M or N + */ + if( m>=n&&m>=k ) + { + + /* + * A*B = (A1 A2)^T*B + */ + ablassplitlength(a, m, &s1, &s2, _state); + if( optypea==0 ) + { + rmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _state); + } + else + { + rmatrixgemm(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _state); + } + return; + } + if( n>=m&&n>=k ) + { + + /* + * A*B = A*(B1 B2) + */ + ablassplitlength(a, n, &s1, &s2, _state); + if( optypeb==0 ) + { + rmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, c, ic, jc+s1, _state); + } + else + { + rmatrixgemm(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, c, ic, jc+s1, _state); + } + return; + } + + /* + * Recursive algorithm: split on K + */ + + /* + * A*B = (A1 A2)*(B1 B2)^T + */ + ablassplitlength(a, k, &s1, &s2, _state); + if( optypea==0&&optypeb==0 ) + { + rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _state); + } + if( optypea==0&&optypeb!=0 ) + { + rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _state); + } + if( optypea!=0&&optypeb==0 ) + { + rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _state); + } + if( optypea!=0&&optypeb!=0 ) + { + rmatrixgemm(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _state); + rmatrixgemm(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _state); + } + return; +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_rmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, ae_state *_state) +{ + rmatrixgemm(m,n,k,alpha,a,ia,ja,optypea,b,ib,jb,optypeb,beta,c,ic,jc, _state); +} + + +/************************************************************************* +Complex ABLASSplitLength + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +static void ablas_ablasinternalsplitlength(ae_int_t n, + ae_int_t nb, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state) +{ + ae_int_t r; + + *n1 = 0; + *n2 = 0; + + if( n<=nb ) + { + + /* + * Block size, no further splitting + */ + *n1 = n; + *n2 = 0; + } + else + { + + /* + * Greater than block size + */ + if( n%nb!=0 ) + { + + /* + * Split remainder + */ + *n2 = n%nb; + *n1 = n-(*n2); + } + else + { + + /* + * Split on block boundaries + */ + *n2 = n/2; + *n1 = n-(*n2); + if( *n1%nb==0 ) + { + return; + } + r = nb-*n1%nb; + *n1 = *n1+r; + *n2 = *n2-r; + } + } +} + + +/************************************************************************* +Level 2 variant of CMatrixRightTRSM +*************************************************************************/ +static void ablas_cmatrixrighttrsm2(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_complex vc; + ae_complex vd; + + + + /* + * Special case + */ + if( n*m==0 ) + { + return; + } + + /* + * Try to call fast TRSM + */ + if( cmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state) ) + { + return; + } + + /* + * General case + */ + if( isupper ) + { + + /* + * Upper triangular matrix + */ + if( optype==0 ) + { + + /* + * X*A^(-1) + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = a->ptr.pp_complex[i1+j][j1+j]; + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(x->ptr.pp_complex[i2+i][j2+j],vd); + if( jptr.pp_complex[i2+i][j2+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+i][j2+j+1], 1, &a->ptr.pp_complex[i1+j][j1+j+1], 1, "N", ae_v_len(j2+j+1,j2+n-1), vc); + } + } + } + return; + } + if( optype==1 ) + { + + /* + * X*A^(-T) + */ + for(i=0; i<=m-1; i++) + { + for(j=n-1; j>=0; j--) + { + vc = ae_complex_from_d(0); + vd = ae_complex_from_d(1); + if( jptr.pp_complex[i2+i][j2+j+1], 1, "N", &a->ptr.pp_complex[i1+j][j1+j+1], 1, "N", ae_v_len(j2+j+1,j2+n-1)); + } + if( !isunit ) + { + vd = a->ptr.pp_complex[i1+j][j1+j]; + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(ae_c_sub(x->ptr.pp_complex[i2+i][j2+j],vc),vd); + } + } + return; + } + if( optype==2 ) + { + + /* + * X*A^(-H) + */ + for(i=0; i<=m-1; i++) + { + for(j=n-1; j>=0; j--) + { + vc = ae_complex_from_d(0); + vd = ae_complex_from_d(1); + if( jptr.pp_complex[i2+i][j2+j+1], 1, "N", &a->ptr.pp_complex[i1+j][j1+j+1], 1, "Conj", ae_v_len(j2+j+1,j2+n-1)); + } + if( !isunit ) + { + vd = ae_c_conj(a->ptr.pp_complex[i1+j][j1+j], _state); + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(ae_c_sub(x->ptr.pp_complex[i2+i][j2+j],vc),vd); + } + } + return; + } + } + else + { + + /* + * Lower triangular matrix + */ + if( optype==0 ) + { + + /* + * X*A^(-1) + */ + for(i=0; i<=m-1; i++) + { + for(j=n-1; j>=0; j--) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = a->ptr.pp_complex[i1+j][j1+j]; + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(x->ptr.pp_complex[i2+i][j2+j],vd); + if( j>0 ) + { + vc = x->ptr.pp_complex[i2+i][j2+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+i][j2], 1, &a->ptr.pp_complex[i1+j][j1], 1, "N", ae_v_len(j2,j2+j-1), vc); + } + } + } + return; + } + if( optype==1 ) + { + + /* + * X*A^(-T) + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + vc = ae_complex_from_d(0); + vd = ae_complex_from_d(1); + if( j>0 ) + { + vc = ae_v_cdotproduct(&x->ptr.pp_complex[i2+i][j2], 1, "N", &a->ptr.pp_complex[i1+j][j1], 1, "N", ae_v_len(j2,j2+j-1)); + } + if( !isunit ) + { + vd = a->ptr.pp_complex[i1+j][j1+j]; + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(ae_c_sub(x->ptr.pp_complex[i2+i][j2+j],vc),vd); + } + } + return; + } + if( optype==2 ) + { + + /* + * X*A^(-H) + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + vc = ae_complex_from_d(0); + vd = ae_complex_from_d(1); + if( j>0 ) + { + vc = ae_v_cdotproduct(&x->ptr.pp_complex[i2+i][j2], 1, "N", &a->ptr.pp_complex[i1+j][j1], 1, "Conj", ae_v_len(j2,j2+j-1)); + } + if( !isunit ) + { + vd = ae_c_conj(a->ptr.pp_complex[i1+j][j1+j], _state); + } + x->ptr.pp_complex[i2+i][j2+j] = ae_c_div(ae_c_sub(x->ptr.pp_complex[i2+i][j2+j],vc),vd); + } + } + return; + } + } +} + + +/************************************************************************* +Level-2 subroutine +*************************************************************************/ +static void ablas_cmatrixlefttrsm2(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_complex vc; + ae_complex vd; + + + + /* + * Special case + */ + if( n*m==0 ) + { + return; + } + + /* + * Try to call fast TRSM + */ + if( cmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state) ) + { + return; + } + + /* + * General case + */ + if( isupper ) + { + + /* + * Upper triangular matrix + */ + if( optype==0 ) + { + + /* + * A^(-1)*X + */ + for(i=m-1; i>=0; i--) + { + for(j=i+1; j<=m-1; j++) + { + vc = a->ptr.pp_complex[i1+i][j1+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+i][j2], 1, &x->ptr.pp_complex[i2+j][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + if( !isunit ) + { + vd = ae_c_d_div(1,a->ptr.pp_complex[i1+i][j1+i]); + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + } + } + return; + } + if( optype==1 ) + { + + /* + * A^(-T)*X + */ + for(i=0; i<=m-1; i++) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = ae_c_d_div(1,a->ptr.pp_complex[i1+i][j1+i]); + } + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i+1; j<=m-1; j++) + { + vc = a->ptr.pp_complex[i1+i][j1+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+j][j2], 1, &x->ptr.pp_complex[i2+i][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + } + return; + } + if( optype==2 ) + { + + /* + * A^(-H)*X + */ + for(i=0; i<=m-1; i++) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = ae_c_d_div(1,ae_c_conj(a->ptr.pp_complex[i1+i][j1+i], _state)); + } + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i+1; j<=m-1; j++) + { + vc = ae_c_conj(a->ptr.pp_complex[i1+i][j1+j], _state); + ae_v_csubc(&x->ptr.pp_complex[i2+j][j2], 1, &x->ptr.pp_complex[i2+i][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + } + return; + } + } + else + { + + /* + * Lower triangular matrix + */ + if( optype==0 ) + { + + /* + * A^(-1)*X + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=i-1; j++) + { + vc = a->ptr.pp_complex[i1+i][j1+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+i][j2], 1, &x->ptr.pp_complex[i2+j][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = ae_c_d_div(1,a->ptr.pp_complex[i1+j][j1+j]); + } + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + } + return; + } + if( optype==1 ) + { + + /* + * A^(-T)*X + */ + for(i=m-1; i>=0; i--) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = ae_c_d_div(1,a->ptr.pp_complex[i1+i][j1+i]); + } + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i-1; j>=0; j--) + { + vc = a->ptr.pp_complex[i1+i][j1+j]; + ae_v_csubc(&x->ptr.pp_complex[i2+j][j2], 1, &x->ptr.pp_complex[i2+i][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + } + return; + } + if( optype==2 ) + { + + /* + * A^(-H)*X + */ + for(i=m-1; i>=0; i--) + { + if( isunit ) + { + vd = ae_complex_from_d(1); + } + else + { + vd = ae_c_d_div(1,ae_c_conj(a->ptr.pp_complex[i1+i][j1+i], _state)); + } + ae_v_cmulc(&x->ptr.pp_complex[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i-1; j>=0; j--) + { + vc = ae_c_conj(a->ptr.pp_complex[i1+i][j1+j], _state); + ae_v_csubc(&x->ptr.pp_complex[i2+j][j2], 1, &x->ptr.pp_complex[i2+i][j2], 1, "N", ae_v_len(j2,j2+n-1), vc); + } + } + return; + } + } +} + + +/************************************************************************* +Level 2 subroutine + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +static void ablas_rmatrixrighttrsm2(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double vr; + double vd; + + + + /* + * Special case + */ + if( n*m==0 ) + { + return; + } + + /* + * Try to use "fast" code + */ + if( rmatrixrighttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state) ) + { + return; + } + + /* + * General case + */ + if( isupper ) + { + + /* + * Upper triangular matrix + */ + if( optype==0 ) + { + + /* + * X*A^(-1) + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( isunit ) + { + vd = 1; + } + else + { + vd = a->ptr.pp_double[i1+j][j1+j]; + } + x->ptr.pp_double[i2+i][j2+j] = x->ptr.pp_double[i2+i][j2+j]/vd; + if( jptr.pp_double[i2+i][j2+j]; + ae_v_subd(&x->ptr.pp_double[i2+i][j2+j+1], 1, &a->ptr.pp_double[i1+j][j1+j+1], 1, ae_v_len(j2+j+1,j2+n-1), vr); + } + } + } + return; + } + if( optype==1 ) + { + + /* + * X*A^(-T) + */ + for(i=0; i<=m-1; i++) + { + for(j=n-1; j>=0; j--) + { + vr = 0; + vd = 1; + if( jptr.pp_double[i2+i][j2+j+1], 1, &a->ptr.pp_double[i1+j][j1+j+1], 1, ae_v_len(j2+j+1,j2+n-1)); + } + if( !isunit ) + { + vd = a->ptr.pp_double[i1+j][j1+j]; + } + x->ptr.pp_double[i2+i][j2+j] = (x->ptr.pp_double[i2+i][j2+j]-vr)/vd; + } + } + return; + } + } + else + { + + /* + * Lower triangular matrix + */ + if( optype==0 ) + { + + /* + * X*A^(-1) + */ + for(i=0; i<=m-1; i++) + { + for(j=n-1; j>=0; j--) + { + if( isunit ) + { + vd = 1; + } + else + { + vd = a->ptr.pp_double[i1+j][j1+j]; + } + x->ptr.pp_double[i2+i][j2+j] = x->ptr.pp_double[i2+i][j2+j]/vd; + if( j>0 ) + { + vr = x->ptr.pp_double[i2+i][j2+j]; + ae_v_subd(&x->ptr.pp_double[i2+i][j2], 1, &a->ptr.pp_double[i1+j][j1], 1, ae_v_len(j2,j2+j-1), vr); + } + } + } + return; + } + if( optype==1 ) + { + + /* + * X*A^(-T) + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + vr = 0; + vd = 1; + if( j>0 ) + { + vr = ae_v_dotproduct(&x->ptr.pp_double[i2+i][j2], 1, &a->ptr.pp_double[i1+j][j1], 1, ae_v_len(j2,j2+j-1)); + } + if( !isunit ) + { + vd = a->ptr.pp_double[i1+j][j1+j]; + } + x->ptr.pp_double[i2+i][j2+j] = (x->ptr.pp_double[i2+i][j2+j]-vr)/vd; + } + } + return; + } + } +} + + +/************************************************************************* +Level 2 subroutine +*************************************************************************/ +static void ablas_rmatrixlefttrsm2(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double vr; + double vd; + + + + /* + * Special case + */ + if( n==0||m==0 ) + { + return; + } + + /* + * Try fast code + */ + if( rmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _state) ) + { + return; + } + + /* + * General case + */ + if( isupper ) + { + + /* + * Upper triangular matrix + */ + if( optype==0 ) + { + + /* + * A^(-1)*X + */ + for(i=m-1; i>=0; i--) + { + for(j=i+1; j<=m-1; j++) + { + vr = a->ptr.pp_double[i1+i][j1+j]; + ae_v_subd(&x->ptr.pp_double[i2+i][j2], 1, &x->ptr.pp_double[i2+j][j2], 1, ae_v_len(j2,j2+n-1), vr); + } + if( !isunit ) + { + vd = 1/a->ptr.pp_double[i1+i][j1+i]; + ae_v_muld(&x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + } + } + return; + } + if( optype==1 ) + { + + /* + * A^(-T)*X + */ + for(i=0; i<=m-1; i++) + { + if( isunit ) + { + vd = 1; + } + else + { + vd = 1/a->ptr.pp_double[i1+i][j1+i]; + } + ae_v_muld(&x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i+1; j<=m-1; j++) + { + vr = a->ptr.pp_double[i1+i][j1+j]; + ae_v_subd(&x->ptr.pp_double[i2+j][j2], 1, &x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vr); + } + } + return; + } + } + else + { + + /* + * Lower triangular matrix + */ + if( optype==0 ) + { + + /* + * A^(-1)*X + */ + for(i=0; i<=m-1; i++) + { + for(j=0; j<=i-1; j++) + { + vr = a->ptr.pp_double[i1+i][j1+j]; + ae_v_subd(&x->ptr.pp_double[i2+i][j2], 1, &x->ptr.pp_double[i2+j][j2], 1, ae_v_len(j2,j2+n-1), vr); + } + if( isunit ) + { + vd = 1; + } + else + { + vd = 1/a->ptr.pp_double[i1+j][j1+j]; + } + ae_v_muld(&x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + } + return; + } + if( optype==1 ) + { + + /* + * A^(-T)*X + */ + for(i=m-1; i>=0; i--) + { + if( isunit ) + { + vd = 1; + } + else + { + vd = 1/a->ptr.pp_double[i1+i][j1+i]; + } + ae_v_muld(&x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vd); + for(j=i-1; j>=0; j--) + { + vr = a->ptr.pp_double[i1+i][j1+j]; + ae_v_subd(&x->ptr.pp_double[i2+j][j2], 1, &x->ptr.pp_double[i2+i][j2], 1, ae_v_len(j2,j2+n-1), vr); + } + } + return; + } + } +} + + +/************************************************************************* +Level 2 subroutine +*************************************************************************/ +static void ablas_cmatrixsyrk2(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + ae_complex v; + + + + /* + * Fast exit (nothing to be done) + */ + if( (ae_fp_eq(alpha,0)||k==0)&&ae_fp_eq(beta,1) ) + { + return; + } + + /* + * Try to call fast SYRK + */ + if( cmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state) ) + { + return; + } + + /* + * SYRK + */ + if( optypea==0 ) + { + + /* + * C=alpha*A*A^H+beta*C + */ + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + if( ae_fp_neq(alpha,0)&&k>0 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[ia+i][ja], 1, "N", &a->ptr.pp_complex[ia+j][ja], 1, "Conj", ae_v_len(ja,ja+k-1)); + } + else + { + v = ae_complex_from_d(0); + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_complex[ic+i][jc+j] = ae_c_mul_d(v,alpha); + } + else + { + c->ptr.pp_complex[ic+i][jc+j] = ae_c_add(ae_c_mul_d(c->ptr.pp_complex[ic+i][jc+j],beta),ae_c_mul_d(v,alpha)); + } + } + } + return; + } + else + { + + /* + * C=alpha*A^H*A+beta*C + */ + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + if( ae_fp_eq(beta,0) ) + { + for(j=j1; j<=j2; j++) + { + c->ptr.pp_complex[ic+i][jc+j] = ae_complex_from_d(0); + } + } + else + { + ae_v_cmuld(&c->ptr.pp_complex[ic+i][jc+j1], 1, ae_v_len(jc+j1,jc+j2), beta); + } + } + for(i=0; i<=k-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( isupper ) + { + j1 = j; + j2 = n-1; + } + else + { + j1 = 0; + j2 = j; + } + v = ae_c_mul_d(ae_c_conj(a->ptr.pp_complex[ia+i][ja+j], _state),alpha); + ae_v_caddc(&c->ptr.pp_complex[ic+j][jc+j1], 1, &a->ptr.pp_complex[ia+i][ja+j1], 1, "N", ae_v_len(jc+j1,jc+j2), v); + } + } + return; + } +} + + +/************************************************************************* +Level 2 subrotuine +*************************************************************************/ +static void ablas_rmatrixsyrk2(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + double v; + + + + /* + * Fast exit (nothing to be done) + */ + if( (ae_fp_eq(alpha,0)||k==0)&&ae_fp_eq(beta,1) ) + { + return; + } + + /* + * Try to call fast SYRK + */ + if( rmatrixsyrkf(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _state) ) + { + return; + } + + /* + * SYRK + */ + if( optypea==0 ) + { + + /* + * C=alpha*A*A^H+beta*C + */ + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + if( ae_fp_neq(alpha,0)&&k>0 ) + { + v = ae_v_dotproduct(&a->ptr.pp_double[ia+i][ja], 1, &a->ptr.pp_double[ia+j][ja], 1, ae_v_len(ja,ja+k-1)); + } + else + { + v = 0; + } + if( ae_fp_eq(beta,0) ) + { + c->ptr.pp_double[ic+i][jc+j] = alpha*v; + } + else + { + c->ptr.pp_double[ic+i][jc+j] = beta*c->ptr.pp_double[ic+i][jc+j]+alpha*v; + } + } + } + return; + } + else + { + + /* + * C=alpha*A^H*A+beta*C + */ + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + if( ae_fp_eq(beta,0) ) + { + for(j=j1; j<=j2; j++) + { + c->ptr.pp_double[ic+i][jc+j] = 0; + } + } + else + { + ae_v_muld(&c->ptr.pp_double[ic+i][jc+j1], 1, ae_v_len(jc+j1,jc+j2), beta); + } + } + for(i=0; i<=k-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( isupper ) + { + j1 = j; + j2 = n-1; + } + else + { + j1 = 0; + j2 = j; + } + v = alpha*a->ptr.pp_double[ia+i][ja+j]; + ae_v_addd(&c->ptr.pp_double[ic+j][jc+j1], 1, &a->ptr.pp_double[ia+i][ja+j1], 1, ae_v_len(jc+j1,jc+j2), v); + } + } + return; + } +} + + + + +/************************************************************************* +QR decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form (see below). + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0.. Min(M-1,N-1)]. + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size M x N. + +The elements of matrix R are located on and above the main diagonal of +matrix A. The elements which are located in Tau array and below the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(k-1), + +where k = min(m,n), and each H(i) is in the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, +so that v(0:i-1) = 0, v(i) = 1, v(i+1:m-1) stored in A(i+1:m-1,i). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqr(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t rowscount; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_REAL, _state, ae_true); + + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + minmn = ae_minint(m, n, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(tau, minmn, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, m, ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpt, ablasblocksize(a, _state), 2*ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, 2*ablasblocksize(a, _state), n, _state); + + /* + * Blocked code + */ + blockstart = 0; + while(blockstart!=minmn) + { + + /* + * Determine block size + */ + blocksize = minmn-blockstart; + if( blocksize>ablasblocksize(a, _state) ) + { + blocksize = ablasblocksize(a, _state); + } + rowscount = m-blockstart; + + /* + * QR decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + rmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, &tmpa, 0, 0, _state); + rmatrixqrbasecase(&tmpa, rowscount, blocksize, &work, &t, &taubuf, _state); + rmatrixcopy(rowscount, blocksize, &tmpa, 0, 0, a, blockstart, blockstart, _state); + ae_v_move(&tau->ptr.p_double[blockstart], 1, &taubuf.ptr.p_double[0], 1, ae_v_len(blockstart,blockstart+blocksize-1)); + + /* + * Update the rest, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( blockstart+blocksize<=n-1 ) + { + if( n-blockstart-blocksize>=2*ablasblocksize(a, _state)||rowscount>=4*ablasblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_rmatrixblockreflector(&tmpa, &taubuf, ae_true, rowscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q'. + * + * Q = E + Y*T*Y' = E + TmpA*TmpT*TmpA' + * Q' = E + Y*T'*Y' = E + TmpA*TmpT'*TmpA' + */ + rmatrixgemm(blocksize, n-blockstart-blocksize, rowscount, 1.0, &tmpa, 0, 0, 1, a, blockstart, blockstart+blocksize, 0, 0.0, &tmpr, 0, 0, _state); + rmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, 1.0, &tmpt, 0, 0, 1, &tmpr, 0, 0, 0, 0.0, &tmpr, blocksize, 0, _state); + rmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, 1.0, &tmpa, 0, 0, 0, &tmpr, blocksize, 0, 0, 1.0, a, blockstart, blockstart+blocksize, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=0; i<=blocksize-1; i++) + { + ae_v_move(&t.ptr.p_double[1], 1, &tmpa.ptr.pp_double[i][i], tmpa.stride, ae_v_len(1,rowscount-i)); + t.ptr.p_double[1] = 1; + applyreflectionfromtheleft(a, taubuf.ptr.p_double[i], &t, blockstart+i, m-1, blockstart+blocksize, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart+blocksize; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +LQ decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices L and Q in compact form (see below) + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0..Min(M,N)-1]. + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size M x N. + +The elements of matrix L are located on and below the main diagonal of +matrix A. The elements which are located in Tau array and above the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(k-1)*H(k-2)*...*H(1)*H(0), + +where k = min(m,n), and each H(i) is of the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, so that v(0:i-1)=0, +v(i) = 1, v(i+1:n-1) stored in A(i,i+1:n-1). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t columnscount; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_REAL, _state, ae_true); + + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + minmn = ae_minint(m, n, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(tau, minmn, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, ablasblocksize(a, _state), n, _state); + ae_matrix_set_length(&tmpt, ablasblocksize(a, _state), 2*ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, m, 2*ablasblocksize(a, _state), _state); + + /* + * Blocked code + */ + blockstart = 0; + while(blockstart!=minmn) + { + + /* + * Determine block size + */ + blocksize = minmn-blockstart; + if( blocksize>ablasblocksize(a, _state) ) + { + blocksize = ablasblocksize(a, _state); + } + columnscount = n-blockstart; + + /* + * LQ decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + rmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, &tmpa, 0, 0, _state); + rmatrixlqbasecase(&tmpa, blocksize, columnscount, &work, &t, &taubuf, _state); + rmatrixcopy(blocksize, columnscount, &tmpa, 0, 0, a, blockstart, blockstart, _state); + ae_v_move(&tau->ptr.p_double[blockstart], 1, &taubuf.ptr.p_double[0], 1, ae_v_len(blockstart,blockstart+blocksize-1)); + + /* + * Update the rest, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( blockstart+blocksize<=m-1 ) + { + if( m-blockstart-blocksize>=2*ablasblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_rmatrixblockreflector(&tmpa, &taubuf, ae_false, columnscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q. + * + * Q = E + Y*T*Y' = E + TmpA'*TmpT*TmpA + */ + rmatrixgemm(m-blockstart-blocksize, blocksize, columnscount, 1.0, a, blockstart+blocksize, blockstart, 0, &tmpa, 0, 0, 1, 0.0, &tmpr, 0, 0, _state); + rmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, 1.0, &tmpr, 0, 0, 0, &tmpt, 0, 0, 0, 0.0, &tmpr, 0, blocksize, _state); + rmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, 1.0, &tmpr, 0, blocksize, 0, &tmpa, 0, 0, 0, 1.0, a, blockstart+blocksize, blockstart, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=0; i<=blocksize-1; i++) + { + ae_v_move(&t.ptr.p_double[1], 1, &tmpa.ptr.pp_double[i][i], 1, ae_v_len(1,columnscount-i)); + t.ptr.p_double[1] = 1; + applyreflectionfromtheright(a, taubuf.ptr.p_double[i], &t, blockstart+blocksize, m-1, blockstart+i, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart+blocksize; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +QR decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixqr(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t rowscount; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&work, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_COMPLEX, _state, ae_true); + + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + minmn = ae_minint(m, n, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(tau, minmn, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, m, ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpt, ablascomplexblocksize(a, _state), ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, 2*ablascomplexblocksize(a, _state), n, _state); + + /* + * Blocked code + */ + blockstart = 0; + while(blockstart!=minmn) + { + + /* + * Determine block size + */ + blocksize = minmn-blockstart; + if( blocksize>ablascomplexblocksize(a, _state) ) + { + blocksize = ablascomplexblocksize(a, _state); + } + rowscount = m-blockstart; + + /* + * QR decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + cmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ortfac_cmatrixqrbasecase(&tmpa, rowscount, blocksize, &work, &t, &taubuf, _state); + cmatrixcopy(rowscount, blocksize, &tmpa, 0, 0, a, blockstart, blockstart, _state); + ae_v_cmove(&tau->ptr.p_complex[blockstart], 1, &taubuf.ptr.p_complex[0], 1, "N", ae_v_len(blockstart,blockstart+blocksize-1)); + + /* + * Update the rest, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( blockstart+blocksize<=n-1 ) + { + if( n-blockstart-blocksize>=2*ablascomplexblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_cmatrixblockreflector(&tmpa, &taubuf, ae_true, rowscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q'. + * + * Q = E + Y*T*Y' = E + TmpA*TmpT*TmpA' + * Q' = E + Y*T'*Y' = E + TmpA*TmpT'*TmpA' + */ + cmatrixgemm(blocksize, n-blockstart-blocksize, rowscount, ae_complex_from_d(1.0), &tmpa, 0, 0, 2, a, blockstart, blockstart+blocksize, 0, ae_complex_from_d(0.0), &tmpr, 0, 0, _state); + cmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, ae_complex_from_d(1.0), &tmpt, 0, 0, 2, &tmpr, 0, 0, 0, ae_complex_from_d(0.0), &tmpr, blocksize, 0, _state); + cmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, ae_complex_from_d(1.0), &tmpa, 0, 0, 0, &tmpr, blocksize, 0, 0, ae_complex_from_d(1.0), a, blockstart, blockstart+blocksize, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=0; i<=blocksize-1; i++) + { + ae_v_cmove(&t.ptr.p_complex[1], 1, &tmpa.ptr.pp_complex[i][i], tmpa.stride, "N", ae_v_len(1,rowscount-i)); + t.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheleft(a, ae_c_conj(taubuf.ptr.p_complex[i], _state), &t, blockstart+i, m-1, blockstart+blocksize, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart+blocksize; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +LQ decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and L in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixlq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t columnscount; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&work, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_COMPLEX, _state, ae_true); + + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + minmn = ae_minint(m, n, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(tau, minmn, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, ablascomplexblocksize(a, _state), n, _state); + ae_matrix_set_length(&tmpt, ablascomplexblocksize(a, _state), ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, m, 2*ablascomplexblocksize(a, _state), _state); + + /* + * Blocked code + */ + blockstart = 0; + while(blockstart!=minmn) + { + + /* + * Determine block size + */ + blocksize = minmn-blockstart; + if( blocksize>ablascomplexblocksize(a, _state) ) + { + blocksize = ablascomplexblocksize(a, _state); + } + columnscount = n-blockstart; + + /* + * LQ decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + cmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ortfac_cmatrixlqbasecase(&tmpa, blocksize, columnscount, &work, &t, &taubuf, _state); + cmatrixcopy(blocksize, columnscount, &tmpa, 0, 0, a, blockstart, blockstart, _state); + ae_v_cmove(&tau->ptr.p_complex[blockstart], 1, &taubuf.ptr.p_complex[0], 1, "N", ae_v_len(blockstart,blockstart+blocksize-1)); + + /* + * Update the rest, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( blockstart+blocksize<=m-1 ) + { + if( m-blockstart-blocksize>=2*ablascomplexblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_cmatrixblockreflector(&tmpa, &taubuf, ae_false, columnscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q. + * + * Q = E + Y*T*Y' = E + TmpA'*TmpT*TmpA + */ + cmatrixgemm(m-blockstart-blocksize, blocksize, columnscount, ae_complex_from_d(1.0), a, blockstart+blocksize, blockstart, 0, &tmpa, 0, 0, 2, ae_complex_from_d(0.0), &tmpr, 0, 0, _state); + cmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, ae_complex_from_d(1.0), &tmpr, 0, 0, 0, &tmpt, 0, 0, 0, ae_complex_from_d(0.0), &tmpr, 0, blocksize, _state); + cmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, ae_complex_from_d(1.0), &tmpr, 0, blocksize, 0, &tmpa, 0, 0, 0, ae_complex_from_d(1.0), a, blockstart+blocksize, blockstart, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=0; i<=blocksize-1; i++) + { + ae_v_cmove(&t.ptr.p_complex[1], 1, &tmpa.ptr.pp_complex[i][i], 1, "Conj", ae_v_len(1,columnscount-i)); + t.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheright(a, taubuf.ptr.p_complex[i], &t, blockstart+blocksize, m-1, blockstart+i, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart+blocksize; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Partial unpacking of matrix Q from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixQR subroutine. + QColumns - required number of columns of matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose indexes range within [0..M-1, 0..QColumns-1]. + If QColumns=0, the array remains unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_int_t qcolumns, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_int_t refcnt; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t rowscount; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(qcolumns<=m, "UnpackQFromQR: QColumns>M!", _state); + if( (m<=0||n<=0)||qcolumns<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + minmn = ae_minint(m, n, _state); + refcnt = ae_minint(minmn, qcolumns, _state); + ae_matrix_set_length(q, m, qcolumns, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=qcolumns-1; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + ae_vector_set_length(&work, ae_maxint(m, qcolumns, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, qcolumns, _state)+1, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, m, ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpt, ablasblocksize(a, _state), 2*ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, 2*ablasblocksize(a, _state), qcolumns, _state); + + /* + * Blocked code + */ + blockstart = ablasblocksize(a, _state)*(refcnt/ablasblocksize(a, _state)); + blocksize = refcnt-blockstart; + while(blockstart>=0) + { + rowscount = m-blockstart; + if( blocksize>0 ) + { + + /* + * Copy current block + */ + rmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ae_v_move(&taubuf.ptr.p_double[0], 1, &tau->ptr.p_double[blockstart], 1, ae_v_len(0,blocksize-1)); + + /* + * Update, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( qcolumns>=2*ablasblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_rmatrixblockreflector(&tmpa, &taubuf, ae_true, rowscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply matrix by Q. + * + * Q = E + Y*T*Y' = E + TmpA*TmpT*TmpA' + */ + rmatrixgemm(blocksize, qcolumns, rowscount, 1.0, &tmpa, 0, 0, 1, q, blockstart, 0, 0, 0.0, &tmpr, 0, 0, _state); + rmatrixgemm(blocksize, qcolumns, blocksize, 1.0, &tmpt, 0, 0, 0, &tmpr, 0, 0, 0, 0.0, &tmpr, blocksize, 0, _state); + rmatrixgemm(rowscount, qcolumns, blocksize, 1.0, &tmpa, 0, 0, 0, &tmpr, blocksize, 0, 0, 1.0, q, blockstart, 0, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=blocksize-1; i>=0; i--) + { + ae_v_move(&t.ptr.p_double[1], 1, &tmpa.ptr.pp_double[i][i], tmpa.stride, ae_v_len(1,rowscount-i)); + t.ptr.p_double[1] = 1; + applyreflectionfromtheleft(q, taubuf.ptr.p_double[i], &t, blockstart+i, m-1, 0, qcolumns-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart-ablasblocksize(a, _state); + blocksize = ablasblocksize(a, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackr(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* r, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + ae_matrix_clear(r); + + if( m<=0||n<=0 ) + { + return; + } + k = ae_minint(m, n, _state); + ae_matrix_set_length(r, m, n, _state); + for(i=0; i<=n-1; i++) + { + r->ptr.pp_double[0][i] = 0; + } + for(i=1; i<=m-1; i++) + { + ae_v_move(&r->ptr.pp_double[i][0], 1, &r->ptr.pp_double[0][0], 1, ae_v_len(0,n-1)); + } + for(i=0; i<=k-1; i++) + { + ae_v_move(&r->ptr.pp_double[i][i], 1, &a->ptr.pp_double[i][i], 1, ae_v_len(i,n-1)); + } +} + + +/************************************************************************* +Partial unpacking of matrix Q from the LQ decomposition of a matrix A + +Input parameters: + A - matrices L and Q in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixLQ subroutine. + QRows - required number of rows in matrix Q. N>=QRows>=0. + +Output parameters: + Q - first QRows rows of matrix Q. Array whose indexes range + within [0..QRows-1, 0..N-1]. If QRows=0, the array remains + unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_int_t qrows, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_int_t refcnt; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t columnscount; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(qrows<=n, "RMatrixLQUnpackQ: QRows>N!", _state); + if( (m<=0||n<=0)||qrows<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + minmn = ae_minint(m, n, _state); + refcnt = ae_minint(minmn, qrows, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, ablasblocksize(a, _state), n, _state); + ae_matrix_set_length(&tmpt, ablasblocksize(a, _state), 2*ablasblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, qrows, 2*ablasblocksize(a, _state), _state); + ae_matrix_set_length(q, qrows, n, _state); + for(i=0; i<=qrows-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * Blocked code + */ + blockstart = ablasblocksize(a, _state)*(refcnt/ablasblocksize(a, _state)); + blocksize = refcnt-blockstart; + while(blockstart>=0) + { + columnscount = n-blockstart; + if( blocksize>0 ) + { + + /* + * Copy submatrix + */ + rmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ae_v_move(&taubuf.ptr.p_double[0], 1, &tau->ptr.p_double[blockstart], 1, ae_v_len(0,blocksize-1)); + + /* + * Update matrix, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( qrows>=2*ablasblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_rmatrixblockreflector(&tmpa, &taubuf, ae_false, columnscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q'. + * + * Q' = E + Y*T'*Y' = E + TmpA'*TmpT'*TmpA + */ + rmatrixgemm(qrows, blocksize, columnscount, 1.0, q, 0, blockstart, 0, &tmpa, 0, 0, 1, 0.0, &tmpr, 0, 0, _state); + rmatrixgemm(qrows, blocksize, blocksize, 1.0, &tmpr, 0, 0, 0, &tmpt, 0, 0, 1, 0.0, &tmpr, 0, blocksize, _state); + rmatrixgemm(qrows, columnscount, blocksize, 1.0, &tmpr, 0, blocksize, 0, &tmpa, 0, 0, 0, 1.0, q, 0, blockstart, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=blocksize-1; i>=0; i--) + { + ae_v_move(&t.ptr.p_double[1], 1, &tmpa.ptr.pp_double[i][i], 1, ae_v_len(1,columnscount-i)); + t.ptr.p_double[1] = 1; + applyreflectionfromtheright(q, taubuf.ptr.p_double[i], &t, 0, qrows-1, blockstart+i, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart-ablasblocksize(a, _state); + blocksize = ablasblocksize(a, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackl(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* l, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + ae_matrix_clear(l); + + if( m<=0||n<=0 ) + { + return; + } + ae_matrix_set_length(l, m, n, _state); + for(i=0; i<=n-1; i++) + { + l->ptr.pp_double[0][i] = 0; + } + for(i=1; i<=m-1; i++) + { + ae_v_move(&l->ptr.pp_double[i][0], 1, &l->ptr.pp_double[0][0], 1, ae_v_len(0,n-1)); + } + for(i=0; i<=m-1; i++) + { + k = ae_minint(i, n-1, _state); + ae_v_move(&l->ptr.pp_double[i][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,k)); + } +} + + +/************************************************************************* +Partial unpacking of matrix Q from QR decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixQR subroutine . + QColumns - required number of columns in matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose index ranges within [0..M-1, 0..QColumns-1]. + If QColumns=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_int_t qcolumns, + /* Complex */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_int_t refcnt; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t rowscount; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&work, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(qcolumns<=m, "UnpackQFromQR: QColumns>M!", _state); + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + minmn = ae_minint(m, n, _state); + refcnt = ae_minint(minmn, qcolumns, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, m, ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpt, ablascomplexblocksize(a, _state), ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, 2*ablascomplexblocksize(a, _state), qcolumns, _state); + ae_matrix_set_length(q, m, qcolumns, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=qcolumns-1; j++) + { + if( i==j ) + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(1); + } + else + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + + /* + * Blocked code + */ + blockstart = ablascomplexblocksize(a, _state)*(refcnt/ablascomplexblocksize(a, _state)); + blocksize = refcnt-blockstart; + while(blockstart>=0) + { + rowscount = m-blockstart; + if( blocksize>0 ) + { + + /* + * QR decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + cmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ae_v_cmove(&taubuf.ptr.p_complex[0], 1, &tau->ptr.p_complex[blockstart], 1, "N", ae_v_len(0,blocksize-1)); + + /* + * Update matrix, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( qcolumns>=2*ablascomplexblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_cmatrixblockreflector(&tmpa, &taubuf, ae_true, rowscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q. + * + * Q = E + Y*T*Y' = E + TmpA*TmpT*TmpA' + */ + cmatrixgemm(blocksize, qcolumns, rowscount, ae_complex_from_d(1.0), &tmpa, 0, 0, 2, q, blockstart, 0, 0, ae_complex_from_d(0.0), &tmpr, 0, 0, _state); + cmatrixgemm(blocksize, qcolumns, blocksize, ae_complex_from_d(1.0), &tmpt, 0, 0, 0, &tmpr, 0, 0, 0, ae_complex_from_d(0.0), &tmpr, blocksize, 0, _state); + cmatrixgemm(rowscount, qcolumns, blocksize, ae_complex_from_d(1.0), &tmpa, 0, 0, 0, &tmpr, blocksize, 0, 0, ae_complex_from_d(1.0), q, blockstart, 0, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=blocksize-1; i>=0; i--) + { + ae_v_cmove(&t.ptr.p_complex[1], 1, &tmpa.ptr.pp_complex[i][i], tmpa.stride, "N", ae_v_len(1,rowscount-i)); + t.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheleft(q, taubuf.ptr.p_complex[i], &t, blockstart+i, m-1, 0, qcolumns-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart-ablascomplexblocksize(a, _state); + blocksize = ablascomplexblocksize(a, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackr(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* r, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + ae_matrix_clear(r); + + if( m<=0||n<=0 ) + { + return; + } + k = ae_minint(m, n, _state); + ae_matrix_set_length(r, m, n, _state); + for(i=0; i<=n-1; i++) + { + r->ptr.pp_complex[0][i] = ae_complex_from_d(0); + } + for(i=1; i<=m-1; i++) + { + ae_v_cmove(&r->ptr.pp_complex[i][0], 1, &r->ptr.pp_complex[0][0], 1, "N", ae_v_len(0,n-1)); + } + for(i=0; i<=k-1; i++) + { + ae_v_cmove(&r->ptr.pp_complex[i][i], 1, &a->ptr.pp_complex[i][i], 1, "N", ae_v_len(i,n-1)); + } +} + + +/************************************************************************* +Partial unpacking of matrix Q from LQ decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixLQ subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixLQ subroutine . + QRows - required number of rows in matrix Q. N>=QColumns>=0. + +Output parameters: + Q - first QRows rows of matrix Q. + Array whose index ranges within [0..QRows-1, 0..N-1]. + If QRows=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_int_t qrows, + /* Complex */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_vector taubuf; + ae_int_t minmn; + ae_int_t refcnt; + ae_matrix tmpa; + ae_matrix tmpt; + ae_matrix tmpr; + ae_int_t blockstart; + ae_int_t blocksize; + ae_int_t columnscount; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&work, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&taubuf, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpa, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpt, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&tmpr, 0, 0, DT_COMPLEX, _state, ae_true); + + if( m<=0||n<=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Init + */ + minmn = ae_minint(m, n, _state); + refcnt = ae_minint(minmn, qrows, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&t, ae_maxint(m, n, _state)+1, _state); + ae_vector_set_length(&taubuf, minmn, _state); + ae_matrix_set_length(&tmpa, ablascomplexblocksize(a, _state), n, _state); + ae_matrix_set_length(&tmpt, ablascomplexblocksize(a, _state), ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(&tmpr, qrows, 2*ablascomplexblocksize(a, _state), _state); + ae_matrix_set_length(q, qrows, n, _state); + for(i=0; i<=qrows-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(1); + } + else + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + + /* + * Blocked code + */ + blockstart = ablascomplexblocksize(a, _state)*(refcnt/ablascomplexblocksize(a, _state)); + blocksize = refcnt-blockstart; + while(blockstart>=0) + { + columnscount = n-blockstart; + if( blocksize>0 ) + { + + /* + * LQ decomposition of submatrix. + * Matrix is copied to temporary storage to solve + * some TLB issues arising from non-contiguous memory + * access pattern. + */ + cmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, &tmpa, 0, 0, _state); + ae_v_cmove(&taubuf.ptr.p_complex[0], 1, &tau->ptr.p_complex[blockstart], 1, "N", ae_v_len(0,blocksize-1)); + + /* + * Update matrix, choose between: + * a) Level 2 algorithm (when the rest of the matrix is small enough) + * b) blocked algorithm, see algorithm 5 from 'A storage efficient WY + * representation for products of Householder transformations', + * by R. Schreiber and C. Van Loan. + */ + if( qrows>=2*ablascomplexblocksize(a, _state) ) + { + + /* + * Prepare block reflector + */ + ortfac_cmatrixblockreflector(&tmpa, &taubuf, ae_false, columnscount, blocksize, &tmpt, &work, _state); + + /* + * Multiply the rest of A by Q'. + * + * Q' = E + Y*T'*Y' = E + TmpA'*TmpT'*TmpA + */ + cmatrixgemm(qrows, blocksize, columnscount, ae_complex_from_d(1.0), q, 0, blockstart, 0, &tmpa, 0, 0, 2, ae_complex_from_d(0.0), &tmpr, 0, 0, _state); + cmatrixgemm(qrows, blocksize, blocksize, ae_complex_from_d(1.0), &tmpr, 0, 0, 0, &tmpt, 0, 0, 2, ae_complex_from_d(0.0), &tmpr, 0, blocksize, _state); + cmatrixgemm(qrows, columnscount, blocksize, ae_complex_from_d(1.0), &tmpr, 0, blocksize, 0, &tmpa, 0, 0, 0, ae_complex_from_d(1.0), q, 0, blockstart, _state); + } + else + { + + /* + * Level 2 algorithm + */ + for(i=blocksize-1; i>=0; i--) + { + ae_v_cmove(&t.ptr.p_complex[1], 1, &tmpa.ptr.pp_complex[i][i], 1, "Conj", ae_v_len(1,columnscount-i)); + t.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheright(q, ae_c_conj(taubuf.ptr.p_complex[i], _state), &t, 0, qrows-1, blockstart+i, n-1, &work, _state); + } + } + } + + /* + * Advance + */ + blockstart = blockstart-ablascomplexblocksize(a, _state); + blocksize = ablascomplexblocksize(a, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of CMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackl(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* l, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + + ae_matrix_clear(l); + + if( m<=0||n<=0 ) + { + return; + } + ae_matrix_set_length(l, m, n, _state); + for(i=0; i<=n-1; i++) + { + l->ptr.pp_complex[0][i] = ae_complex_from_d(0); + } + for(i=1; i<=m-1; i++) + { + ae_v_cmove(&l->ptr.pp_complex[i][0], 1, &l->ptr.pp_complex[0][0], 1, "N", ae_v_len(0,n-1)); + } + for(i=0; i<=m-1; i++) + { + k = ae_minint(i, n-1, _state); + ae_v_cmove(&l->ptr.pp_complex[i][0], 1, &a->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,k)); + } +} + + +/************************************************************************* +Base case for real QR + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +void rmatrixqrbasecase(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* work, + /* Real */ ae_vector* t, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t minmn; + double tmp; + + + minmn = ae_minint(m, n, _state); + + /* + * Test the input arguments + */ + k = minmn; + for(i=0; i<=k-1; i++) + { + + /* + * Generate elementary reflector H(i) to annihilate A(i+1:m,i) + */ + ae_v_move(&t->ptr.p_double[1], 1, &a->ptr.pp_double[i][i], a->stride, ae_v_len(1,m-i)); + generatereflection(t, m-i, &tmp, _state); + tau->ptr.p_double[i] = tmp; + ae_v_move(&a->ptr.pp_double[i][i], a->stride, &t->ptr.p_double[1], 1, ae_v_len(i,m-1)); + t->ptr.p_double[1] = 1; + if( iptr.p_double[i], t, i, m-1, i+1, n-1, work, _state); + } + } +} + + +/************************************************************************* +Base case for real LQ + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +void rmatrixlqbasecase(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* work, + /* Real */ ae_vector* t, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + double tmp; + + + k = ae_minint(m, n, _state); + for(i=0; i<=k-1; i++) + { + + /* + * Generate elementary reflector H(i) to annihilate A(i,i+1:n-1) + */ + ae_v_move(&t->ptr.p_double[1], 1, &a->ptr.pp_double[i][i], 1, ae_v_len(1,n-i)); + generatereflection(t, n-i, &tmp, _state); + tau->ptr.p_double[i] = tmp; + ae_v_move(&a->ptr.pp_double[i][i], 1, &t->ptr.p_double[1], 1, ae_v_len(i,n-1)); + t->ptr.p_double[1] = 1; + if( iptr.p_double[i], t, i+1, m-1, i, n-1, work, _state); + } + } +} + + +/************************************************************************* +Reduction of a rectangular matrix to bidiagonal form + +The algorithm reduces the rectangular matrix A to bidiagonal form by +orthogonal transformations P and Q: A = Q*B*P. + +Input parameters: + A - source matrix. array[0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q, B, P in compact form (see below). + TauQ - scalar factors which are used to form matrix Q. + TauP - scalar factors which are used to form matrix P. + +The main diagonal and one of the secondary diagonals of matrix A are +replaced with bidiagonal matrix B. Other elements contain elementary +reflections which form MxM matrix Q and NxN matrix P, respectively. + +If M>=N, B is the upper bidiagonal MxN matrix and is stored in the +corresponding elements of matrix A. Matrix Q is represented as a +product of elementary reflections Q = H(0)*H(1)*...*H(n-1), where +H(i) = 1-tau*v*v'. Here tau is a scalar which is stored in TauQ[i], and +vector v has the following structure: v(0:i-1)=0, v(i)=1, v(i+1:m-1) is +stored in elements A(i+1:m-1,i). Matrix P is as follows: P = +G(0)*G(1)*...*G(n-2), where G(i) = 1 - tau*u*u'. Tau is stored in TauP[i], +u(0:i)=0, u(i+1)=1, u(i+2:n-1) is stored in elements A(i,i+2:n-1). + +If M n): m=5, n=6 (m < n): + +( d e u1 u1 u1 ) ( d u1 u1 u1 u1 u1 ) +( v1 d e u2 u2 ) ( e d u2 u2 u2 u2 ) +( v1 v2 d e u3 ) ( v1 e d u3 u3 u3 ) +( v1 v2 v3 d e ) ( v1 v2 e d u4 u4 ) +( v1 v2 v3 v4 d ) ( v1 v2 v3 e d u5 ) +( v1 v2 v3 v4 v5 ) + +Here vi and ui are vectors which form H(i) and G(i), and d and e - +are the diagonal and off-diagonal elements of matrix B. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +void rmatrixbd(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + /* Real */ ae_vector* taup, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_vector t; + ae_int_t maxmn; + ae_int_t i; + double ltau; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tauq); + ae_vector_clear(taup); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + + /* + * Prepare + */ + if( n<=0||m<=0 ) + { + ae_frame_leave(_state); + return; + } + maxmn = ae_maxint(m, n, _state); + ae_vector_set_length(&work, maxmn+1, _state); + ae_vector_set_length(&t, maxmn+1, _state); + if( m>=n ) + { + ae_vector_set_length(tauq, n, _state); + ae_vector_set_length(taup, n, _state); + } + else + { + ae_vector_set_length(tauq, m, _state); + ae_vector_set_length(taup, m, _state); + } + if( m>=n ) + { + + /* + * Reduce to upper bidiagonal form + */ + for(i=0; i<=n-1; i++) + { + + /* + * Generate elementary reflector H(i) to annihilate A(i+1:m-1,i) + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i][i], a->stride, ae_v_len(1,m-i)); + generatereflection(&t, m-i, <au, _state); + tauq->ptr.p_double[i] = ltau; + ae_v_move(&a->ptr.pp_double[i][i], a->stride, &t.ptr.p_double[1], 1, ae_v_len(i,m-1)); + t.ptr.p_double[1] = 1; + + /* + * Apply H(i) to A(i:m-1,i+1:n-1) from the left + */ + applyreflectionfromtheleft(a, ltau, &t, i, m-1, i+1, n-1, &work, _state); + if( iptr.pp_double[i][i+1], 1, ae_v_len(1,n-i-1)); + generatereflection(&t, n-1-i, <au, _state); + taup->ptr.p_double[i] = ltau; + ae_v_move(&a->ptr.pp_double[i][i+1], 1, &t.ptr.p_double[1], 1, ae_v_len(i+1,n-1)); + t.ptr.p_double[1] = 1; + + /* + * Apply G(i) to A(i+1:m-1,i+1:n-1) from the right + */ + applyreflectionfromtheright(a, ltau, &t, i+1, m-1, i+1, n-1, &work, _state); + } + else + { + taup->ptr.p_double[i] = 0; + } + } + } + else + { + + /* + * Reduce to lower bidiagonal form + */ + for(i=0; i<=m-1; i++) + { + + /* + * Generate elementary reflector G(i) to annihilate A(i,i+1:n-1) + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i][i], 1, ae_v_len(1,n-i)); + generatereflection(&t, n-i, <au, _state); + taup->ptr.p_double[i] = ltau; + ae_v_move(&a->ptr.pp_double[i][i], 1, &t.ptr.p_double[1], 1, ae_v_len(i,n-1)); + t.ptr.p_double[1] = 1; + + /* + * Apply G(i) to A(i+1:m-1,i:n-1) from the right + */ + applyreflectionfromtheright(a, ltau, &t, i+1, m-1, i, n-1, &work, _state); + if( iptr.pp_double[i+1][i], a->stride, ae_v_len(1,m-1-i)); + generatereflection(&t, m-1-i, <au, _state); + tauq->ptr.p_double[i] = ltau; + ae_v_move(&a->ptr.pp_double[i+1][i], a->stride, &t.ptr.p_double[1], 1, ae_v_len(i+1,m-1)); + t.ptr.p_double[1] = 1; + + /* + * Apply H(i) to A(i+1:m-1,i+1:n-1) from the left + */ + applyreflectionfromtheleft(a, ltau, &t, i+1, m-1, i+1, n-1, &work, _state); + } + else + { + tauq->ptr.p_double[i] = 0; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix Q which reduces a matrix to bidiagonal form. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + QColumns - required number of columns in matrix Q. + M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array[0..M-1, 0..QColumns-1] + If QColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackq(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + ae_int_t qcolumns, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(q); + + ae_assert(qcolumns<=m, "RMatrixBDUnpackQ: QColumns>M!", _state); + ae_assert(qcolumns>=0, "RMatrixBDUnpackQ: QColumns<0!", _state); + if( (m==0||n==0)||qcolumns==0 ) + { + return; + } + + /* + * prepare Q + */ + ae_matrix_set_length(q, m, qcolumns, _state); + for(i=0; i<=m-1; i++) + { + for(j=0; j<=qcolumns-1; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * Calculate + */ + rmatrixbdmultiplybyq(qp, m, n, tauq, q, m, qcolumns, ae_false, ae_false, _state); +} + + +/************************************************************************* +Multiplication by matrix Q which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by Q or Q'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + Z - multiplied matrix. + array[0..ZRows-1,0..ZColumns-1] + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=M, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=M, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by Q or Q'. + +Output parameters: + Z - product of Z and Q. + Array[0..ZRows-1,0..ZColumns-1] + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyq(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + /* Real */ ae_matrix* z, + ae_int_t zrows, + ae_int_t zcolumns, + ae_bool fromtheright, + ae_bool dotranspose, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t i1; + ae_int_t i2; + ae_int_t istep; + ae_vector v; + ae_vector work; + ae_int_t mx; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( ((m<=0||n<=0)||zrows<=0)||zcolumns<=0 ) + { + ae_frame_leave(_state); + return; + } + ae_assert((fromtheright&&zcolumns==m)||(!fromtheright&&zrows==m), "RMatrixBDMultiplyByQ: incorrect Z size!", _state); + + /* + * init + */ + mx = ae_maxint(m, n, _state); + mx = ae_maxint(mx, zrows, _state); + mx = ae_maxint(mx, zcolumns, _state); + ae_vector_set_length(&v, mx+1, _state); + ae_vector_set_length(&work, mx+1, _state); + if( m>=n ) + { + + /* + * setup + */ + if( fromtheright ) + { + i1 = 0; + i2 = n-1; + istep = 1; + } + else + { + i1 = n-1; + i2 = 0; + istep = -1; + } + if( dotranspose ) + { + i = i1; + i1 = i2; + i2 = i; + istep = -istep; + } + + /* + * Process + */ + i = i1; + do + { + ae_v_move(&v.ptr.p_double[1], 1, &qp->ptr.pp_double[i][i], qp->stride, ae_v_len(1,m-i)); + v.ptr.p_double[1] = 1; + if( fromtheright ) + { + applyreflectionfromtheright(z, tauq->ptr.p_double[i], &v, 0, zrows-1, i, m-1, &work, _state); + } + else + { + applyreflectionfromtheleft(z, tauq->ptr.p_double[i], &v, i, m-1, 0, zcolumns-1, &work, _state); + } + i = i+istep; + } + while(i!=i2+istep); + } + else + { + + /* + * setup + */ + if( fromtheright ) + { + i1 = 0; + i2 = m-2; + istep = 1; + } + else + { + i1 = m-2; + i2 = 0; + istep = -1; + } + if( dotranspose ) + { + i = i1; + i1 = i2; + i2 = i; + istep = -istep; + } + + /* + * Process + */ + if( m-1>0 ) + { + i = i1; + do + { + ae_v_move(&v.ptr.p_double[1], 1, &qp->ptr.pp_double[i+1][i], qp->stride, ae_v_len(1,m-i-1)); + v.ptr.p_double[1] = 1; + if( fromtheright ) + { + applyreflectionfromtheright(z, tauq->ptr.p_double[i], &v, 0, zrows-1, i+1, m-1, &work, _state); + } + else + { + applyreflectionfromtheleft(z, tauq->ptr.p_double[i], &v, i+1, m-1, 0, zcolumns-1, &work, _state); + } + i = i+istep; + } + while(i!=i2+istep); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix P which reduces matrix A to bidiagonal form. +The subroutine returns transposed matrix P. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of ToBidiagonal subroutine. + PTRows - required number of rows of matrix P^T. N >= PTRows >= 0. + +Output parameters: + PT - first PTRows columns of matrix P^T + Array[0..PTRows-1, 0..N-1] + If PTRows=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackpt(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* taup, + ae_int_t ptrows, + /* Real */ ae_matrix* pt, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(pt); + + ae_assert(ptrows<=n, "RMatrixBDUnpackPT: PTRows>N!", _state); + ae_assert(ptrows>=0, "RMatrixBDUnpackPT: PTRows<0!", _state); + if( (m==0||n==0)||ptrows==0 ) + { + return; + } + + /* + * prepare PT + */ + ae_matrix_set_length(pt, ptrows, n, _state); + for(i=0; i<=ptrows-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + pt->ptr.pp_double[i][j] = 1; + } + else + { + pt->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * Calculate + */ + rmatrixbdmultiplybyp(qp, m, n, taup, pt, ptrows, n, ae_true, ae_true, _state); +} + + +/************************************************************************* +Multiplication by matrix P which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by P or P'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of RMatrixBD subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of RMatrixBD subroutine. + Z - multiplied matrix. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=N, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=N, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by P or P'. + +Output parameters: + Z - product of Z and P. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyp(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* taup, + /* Real */ ae_matrix* z, + ae_int_t zrows, + ae_int_t zcolumns, + ae_bool fromtheright, + ae_bool dotranspose, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector v; + ae_vector work; + ae_int_t mx; + ae_int_t i1; + ae_int_t i2; + ae_int_t istep; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( ((m<=0||n<=0)||zrows<=0)||zcolumns<=0 ) + { + ae_frame_leave(_state); + return; + } + ae_assert((fromtheright&&zcolumns==n)||(!fromtheright&&zrows==n), "RMatrixBDMultiplyByP: incorrect Z size!", _state); + + /* + * init + */ + mx = ae_maxint(m, n, _state); + mx = ae_maxint(mx, zrows, _state); + mx = ae_maxint(mx, zcolumns, _state); + ae_vector_set_length(&v, mx+1, _state); + ae_vector_set_length(&work, mx+1, _state); + if( m>=n ) + { + + /* + * setup + */ + if( fromtheright ) + { + i1 = n-2; + i2 = 0; + istep = -1; + } + else + { + i1 = 0; + i2 = n-2; + istep = 1; + } + if( !dotranspose ) + { + i = i1; + i1 = i2; + i2 = i; + istep = -istep; + } + + /* + * Process + */ + if( n-1>0 ) + { + i = i1; + do + { + ae_v_move(&v.ptr.p_double[1], 1, &qp->ptr.pp_double[i][i+1], 1, ae_v_len(1,n-1-i)); + v.ptr.p_double[1] = 1; + if( fromtheright ) + { + applyreflectionfromtheright(z, taup->ptr.p_double[i], &v, 0, zrows-1, i+1, n-1, &work, _state); + } + else + { + applyreflectionfromtheleft(z, taup->ptr.p_double[i], &v, i+1, n-1, 0, zcolumns-1, &work, _state); + } + i = i+istep; + } + while(i!=i2+istep); + } + } + else + { + + /* + * setup + */ + if( fromtheright ) + { + i1 = m-1; + i2 = 0; + istep = -1; + } + else + { + i1 = 0; + i2 = m-1; + istep = 1; + } + if( !dotranspose ) + { + i = i1; + i1 = i2; + i2 = i; + istep = -istep; + } + + /* + * Process + */ + i = i1; + do + { + ae_v_move(&v.ptr.p_double[1], 1, &qp->ptr.pp_double[i][i], 1, ae_v_len(1,n-i)); + v.ptr.p_double[1] = 1; + if( fromtheright ) + { + applyreflectionfromtheright(z, taup->ptr.p_double[i], &v, 0, zrows-1, i, n-1, &work, _state); + } + else + { + applyreflectionfromtheleft(z, taup->ptr.p_double[i], &v, i, n-1, 0, zcolumns-1, &work, _state); + } + i = i+istep; + } + while(i!=i2+istep); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking of the main and secondary diagonals of bidiagonal decomposition +of matrix A. + +Input parameters: + B - output of RMatrixBD subroutine. + M - number of rows in matrix B. + N - number of columns in matrix B. + +Output parameters: + IsUpper - True, if the matrix is upper bidiagonal. + otherwise IsUpper is False. + D - the main diagonal. + Array whose index ranges within [0..Min(M,N)-1]. + E - the secondary diagonal (upper or lower, depending on + the value of IsUpper). + Array index ranges within [0..Min(M,N)-1], the last + element is not used. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackdiagonals(/* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t n, + ae_bool* isupper, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state) +{ + ae_int_t i; + + *isupper = ae_false; + ae_vector_clear(d); + ae_vector_clear(e); + + *isupper = m>=n; + if( m<=0||n<=0 ) + { + return; + } + if( *isupper ) + { + ae_vector_set_length(d, n, _state); + ae_vector_set_length(e, n, _state); + for(i=0; i<=n-2; i++) + { + d->ptr.p_double[i] = b->ptr.pp_double[i][i]; + e->ptr.p_double[i] = b->ptr.pp_double[i][i+1]; + } + d->ptr.p_double[n-1] = b->ptr.pp_double[n-1][n-1]; + } + else + { + ae_vector_set_length(d, m, _state); + ae_vector_set_length(e, m, _state); + for(i=0; i<=m-2; i++) + { + d->ptr.p_double[i] = b->ptr.pp_double[i][i]; + e->ptr.p_double[i] = b->ptr.pp_double[i+1][i]; + } + d->ptr.p_double[m-1] = b->ptr.pp_double[m-1][m-1]; + } +} + + +/************************************************************************* +Reduction of a square matrix to upper Hessenberg form: Q'*A*Q = H, +where Q is an orthogonal matrix, H - Hessenberg matrix. + +Input parameters: + A - matrix A with elements [0..N-1, 0..N-1] + N - size of matrix A. + +Output parameters: + A - matrices Q and P in compact form (see below). + Tau - array of scalar factors which are used to form matrix Q. + Array whose index ranges within [0..N-2] + +Matrix H is located on the main diagonal, on the lower secondary diagonal +and above the main diagonal of matrix A. The elements which are used to +form matrix Q are situated in array Tau and below the lower secondary +diagonal of matrix A as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(n-2), + +where each H(i) is given by + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - is a real vector, +so that v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) stored in A(i+2:n-1,i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void rmatrixhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + double v; + ae_vector t; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "RMatrixHessenberg: incorrect N!", _state); + + /* + * Quick return if possible + */ + if( n<=1 ) + { + ae_frame_leave(_state); + return; + } + ae_vector_set_length(tau, n-2+1, _state); + ae_vector_set_length(&t, n+1, _state); + ae_vector_set_length(&work, n-1+1, _state); + for(i=0; i<=n-2; i++) + { + + /* + * Compute elementary reflector H(i) to annihilate A(i+2:ihi,i) + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + generatereflection(&t, n-i-1, &v, _state); + ae_v_move(&a->ptr.pp_double[i+1][i], a->stride, &t.ptr.p_double[1], 1, ae_v_len(i+1,n-1)); + tau->ptr.p_double[i] = v; + t.ptr.p_double[1] = 1; + + /* + * Apply H(i) to A(1:ihi,i+1:ihi) from the right + */ + applyreflectionfromtheright(a, v, &t, 0, n-1, i+1, n-1, &work, _state); + + /* + * Apply H(i) to A(i+1:ihi,i+1:n) from the left + */ + applyreflectionfromtheleft(a, v, &t, i+1, n-1, i+1, n-1, &work, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix Q which reduces matrix A to upper Hessenberg form + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + Tau - scalar factors which are used to form Q. + Output of RMatrixHessenberg subroutine. + +Output parameters: + Q - matrix Q. + Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackq(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector v; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + ae_matrix_set_length(q, n-1+1, n-1+1, _state); + ae_vector_set_length(&v, n-1+1, _state); + ae_vector_set_length(&work, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * unpack Q + */ + for(i=0; i<=n-2; i++) + { + + /* + * Apply H(i) + */ + ae_v_move(&v.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + v.ptr.p_double[1] = 1; + applyreflectionfromtheright(q, tau->ptr.p_double[i], &v, 0, n-1, i+1, n-1, &work, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix H (the result of matrix A reduction to upper Hessenberg form) + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + +Output parameters: + H - matrix H. Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackh(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* h, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector v; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(h); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(h, n-1+1, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-2; j++) + { + h->ptr.pp_double[i][j] = 0; + } + j = ae_maxint(0, i-1, _state); + ae_v_move(&h->ptr.pp_double[i][j], 1, &a->ptr.pp_double[i][j], 1, ae_v_len(j,n-1)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Reduction of a symmetric matrix which is given by its higher or lower +triangular part to a tridiagonal matrix using orthogonal similarity +transformation: Q'*A*Q=T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + + where d and e denote diagonal and off-diagonal elements of T, and vi + denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void smatrixtd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tau, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + double alpha; + double taui; + double v; + ae_vector t; + ae_vector t2; + ae_vector t3; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_clear(d); + ae_vector_clear(e); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t3, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&t, n+1, _state); + ae_vector_set_length(&t2, n+1, _state); + ae_vector_set_length(&t3, n+1, _state); + if( n>1 ) + { + ae_vector_set_length(tau, n-2+1, _state); + } + ae_vector_set_length(d, n-1+1, _state); + if( n>1 ) + { + ae_vector_set_length(e, n-2+1, _state); + } + if( isupper ) + { + + /* + * Reduce the upper triangle of A + */ + for(i=n-2; i>=0; i--) + { + + /* + * Generate elementary reflector H() = E - tau * v * v' + */ + if( i>=1 ) + { + ae_v_move(&t.ptr.p_double[2], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(2,i+1)); + } + t.ptr.p_double[1] = a->ptr.pp_double[i][i+1]; + generatereflection(&t, i+1, &taui, _state); + if( i>=1 ) + { + ae_v_move(&a->ptr.pp_double[0][i+1], a->stride, &t.ptr.p_double[2], 1, ae_v_len(0,i-1)); + } + a->ptr.pp_double[i][i+1] = t.ptr.p_double[1]; + e->ptr.p_double[i] = a->ptr.pp_double[i][i+1]; + if( ae_fp_neq(taui,0) ) + { + + /* + * Apply H from both sides to A + */ + a->ptr.pp_double[i][i+1] = 1; + + /* + * Compute x := tau * A * v storing x in TAU + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(1,i+1)); + symmetricmatrixvectormultiply(a, isupper, 0, i, &t, taui, &t3, _state); + ae_v_move(&tau->ptr.p_double[0], 1, &t3.ptr.p_double[1], 1, ae_v_len(0,i)); + + /* + * Compute w := x - 1/2 * tau * (x'*v) * v + */ + v = ae_v_dotproduct(&tau->ptr.p_double[0], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(0,i)); + alpha = -0.5*taui*v; + ae_v_addd(&tau->ptr.p_double[0], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(0,i), alpha); + + /* + * Apply the transformation as a rank-2 update: + * A := A - v * w' - w * v' + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(1,i+1)); + ae_v_move(&t3.ptr.p_double[1], 1, &tau->ptr.p_double[0], 1, ae_v_len(1,i+1)); + symmetricrank2update(a, isupper, 0, i, &t, &t3, &t2, -1, _state); + a->ptr.pp_double[i][i+1] = e->ptr.p_double[i]; + } + d->ptr.p_double[i+1] = a->ptr.pp_double[i+1][i+1]; + tau->ptr.p_double[i] = taui; + } + d->ptr.p_double[0] = a->ptr.pp_double[0][0]; + } + else + { + + /* + * Reduce the lower triangle of A + */ + for(i=0; i<=n-2; i++) + { + + /* + * Generate elementary reflector H = E - tau * v * v' + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + generatereflection(&t, n-i-1, &taui, _state); + ae_v_move(&a->ptr.pp_double[i+1][i], a->stride, &t.ptr.p_double[1], 1, ae_v_len(i+1,n-1)); + e->ptr.p_double[i] = a->ptr.pp_double[i+1][i]; + if( ae_fp_neq(taui,0) ) + { + + /* + * Apply H from both sides to A + */ + a->ptr.pp_double[i+1][i] = 1; + + /* + * Compute x := tau * A * v storing y in TAU + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + symmetricmatrixvectormultiply(a, isupper, i+1, n-1, &t, taui, &t2, _state); + ae_v_move(&tau->ptr.p_double[i], 1, &t2.ptr.p_double[1], 1, ae_v_len(i,n-2)); + + /* + * Compute w := x - 1/2 * tau * (x'*v) * v + */ + v = ae_v_dotproduct(&tau->ptr.p_double[i], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(i,n-2)); + alpha = -0.5*taui*v; + ae_v_addd(&tau->ptr.p_double[i], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(i,n-2), alpha); + + /* + * Apply the transformation as a rank-2 update: + * A := A - v * w' - w * v' + * + */ + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + ae_v_move(&t2.ptr.p_double[1], 1, &tau->ptr.p_double[i], 1, ae_v_len(1,n-i-1)); + symmetricrank2update(a, isupper, i+1, n-1, &t, &t2, &t3, -1, _state); + a->ptr.pp_double[i+1][i] = e->ptr.p_double[i]; + } + d->ptr.p_double[i] = a->ptr.pp_double[i][i]; + tau->ptr.p_double[i] = taui; + } + d->ptr.p_double[n-1] = a->ptr.pp_double[n-1][n-1]; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix Q which reduces symmetric matrix to a tridiagonal +form. + +Input parameters: + A - the result of a SMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of SMatrixTD subroutine) + Tau - the result of a SMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void smatrixtdunpackq(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector v; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + ae_matrix_set_length(q, n-1+1, n-1+1, _state); + ae_vector_set_length(&v, n+1, _state); + ae_vector_set_length(&work, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * unpack Q + */ + if( isupper ) + { + for(i=0; i<=n-2; i++) + { + + /* + * Apply H(i) + */ + ae_v_move(&v.ptr.p_double[1], 1, &a->ptr.pp_double[0][i+1], a->stride, ae_v_len(1,i+1)); + v.ptr.p_double[i+1] = 1; + applyreflectionfromtheleft(q, tau->ptr.p_double[i], &v, 0, i, 0, n-1, &work, _state); + } + } + else + { + for(i=n-2; i>=0; i--) + { + + /* + * Apply H(i) + */ + ae_v_move(&v.ptr.p_double[1], 1, &a->ptr.pp_double[i+1][i], a->stride, ae_v_len(1,n-i-1)); + v.ptr.p_double[1] = 1; + applyreflectionfromtheleft(q, tau->ptr.p_double[i], &v, i+1, n-1, 0, n-1, &work, _state); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Reduction of a Hermitian matrix which is given by its higher or lower +triangular part to a real tridiagonal matrix using unitary similarity +transformation: Q'*A*Q = T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of real symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of real symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + +where d and e denote diagonal and off-diagonal elements of T, and vi +denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void hmatrixtd(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tau, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_complex alpha; + ae_complex taui; + ae_complex v; + ae_vector t; + ae_vector t2; + ae_vector t3; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_clear(d); + ae_vector_clear(e); + ae_vector_init(&t, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t2, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&t3, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + ae_assert(ae_fp_eq(a->ptr.pp_complex[i][i].y,0), "Assertion failed", _state); + } + if( n>1 ) + { + ae_vector_set_length(tau, n-2+1, _state); + ae_vector_set_length(e, n-2+1, _state); + } + ae_vector_set_length(d, n-1+1, _state); + ae_vector_set_length(&t, n-1+1, _state); + ae_vector_set_length(&t2, n-1+1, _state); + ae_vector_set_length(&t3, n-1+1, _state); + if( isupper ) + { + + /* + * Reduce the upper triangle of A + */ + a->ptr.pp_complex[n-1][n-1] = ae_complex_from_d(a->ptr.pp_complex[n-1][n-1].x); + for(i=n-2; i>=0; i--) + { + + /* + * Generate elementary reflector H = I+1 - tau * v * v' + */ + alpha = a->ptr.pp_complex[i][i+1]; + t.ptr.p_complex[1] = alpha; + if( i>=1 ) + { + ae_v_cmove(&t.ptr.p_complex[2], 1, &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(2,i+1)); + } + complexgeneratereflection(&t, i+1, &taui, _state); + if( i>=1 ) + { + ae_v_cmove(&a->ptr.pp_complex[0][i+1], a->stride, &t.ptr.p_complex[2], 1, "N", ae_v_len(0,i-1)); + } + alpha = t.ptr.p_complex[1]; + e->ptr.p_double[i] = alpha.x; + if( ae_c_neq_d(taui,0) ) + { + + /* + * Apply H(I+1) from both sides to A + */ + a->ptr.pp_complex[i][i+1] = ae_complex_from_d(1); + + /* + * Compute x := tau * A * v storing x in TAU + */ + ae_v_cmove(&t.ptr.p_complex[1], 1, &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(1,i+1)); + hermitianmatrixvectormultiply(a, isupper, 0, i, &t, taui, &t2, _state); + ae_v_cmove(&tau->ptr.p_complex[0], 1, &t2.ptr.p_complex[1], 1, "N", ae_v_len(0,i)); + + /* + * Compute w := x - 1/2 * tau * (x'*v) * v + */ + v = ae_v_cdotproduct(&tau->ptr.p_complex[0], 1, "Conj", &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(0,i)); + alpha = ae_c_neg(ae_c_mul(ae_c_mul_d(taui,0.5),v)); + ae_v_caddc(&tau->ptr.p_complex[0], 1, &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(0,i), alpha); + + /* + * Apply the transformation as a rank-2 update: + * A := A - v * w' - w * v' + */ + ae_v_cmove(&t.ptr.p_complex[1], 1, &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(1,i+1)); + ae_v_cmove(&t3.ptr.p_complex[1], 1, &tau->ptr.p_complex[0], 1, "N", ae_v_len(1,i+1)); + hermitianrank2update(a, isupper, 0, i, &t, &t3, &t2, ae_complex_from_d(-1), _state); + } + else + { + a->ptr.pp_complex[i][i] = ae_complex_from_d(a->ptr.pp_complex[i][i].x); + } + a->ptr.pp_complex[i][i+1] = ae_complex_from_d(e->ptr.p_double[i]); + d->ptr.p_double[i+1] = a->ptr.pp_complex[i+1][i+1].x; + tau->ptr.p_complex[i] = taui; + } + d->ptr.p_double[0] = a->ptr.pp_complex[0][0].x; + } + else + { + + /* + * Reduce the lower triangle of A + */ + a->ptr.pp_complex[0][0] = ae_complex_from_d(a->ptr.pp_complex[0][0].x); + for(i=0; i<=n-2; i++) + { + + /* + * Generate elementary reflector H = I - tau * v * v' + */ + ae_v_cmove(&t.ptr.p_complex[1], 1, &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(1,n-i-1)); + complexgeneratereflection(&t, n-i-1, &taui, _state); + ae_v_cmove(&a->ptr.pp_complex[i+1][i], a->stride, &t.ptr.p_complex[1], 1, "N", ae_v_len(i+1,n-1)); + e->ptr.p_double[i] = a->ptr.pp_complex[i+1][i].x; + if( ae_c_neq_d(taui,0) ) + { + + /* + * Apply H(i) from both sides to A(i+1:n,i+1:n) + */ + a->ptr.pp_complex[i+1][i] = ae_complex_from_d(1); + + /* + * Compute x := tau * A * v storing y in TAU + */ + ae_v_cmove(&t.ptr.p_complex[1], 1, &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(1,n-i-1)); + hermitianmatrixvectormultiply(a, isupper, i+1, n-1, &t, taui, &t2, _state); + ae_v_cmove(&tau->ptr.p_complex[i], 1, &t2.ptr.p_complex[1], 1, "N", ae_v_len(i,n-2)); + + /* + * Compute w := x - 1/2 * tau * (x'*v) * v + */ + v = ae_v_cdotproduct(&tau->ptr.p_complex[i], 1, "Conj", &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(i,n-2)); + alpha = ae_c_neg(ae_c_mul(ae_c_mul_d(taui,0.5),v)); + ae_v_caddc(&tau->ptr.p_complex[i], 1, &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(i,n-2), alpha); + + /* + * Apply the transformation as a rank-2 update: + * A := A - v * w' - w * v' + */ + ae_v_cmove(&t.ptr.p_complex[1], 1, &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(1,n-i-1)); + ae_v_cmove(&t2.ptr.p_complex[1], 1, &tau->ptr.p_complex[i], 1, "N", ae_v_len(1,n-i-1)); + hermitianrank2update(a, isupper, i+1, n-1, &t, &t2, &t3, ae_complex_from_d(-1), _state); + } + else + { + a->ptr.pp_complex[i+1][i+1] = ae_complex_from_d(a->ptr.pp_complex[i+1][i+1].x); + } + a->ptr.pp_complex[i+1][i] = ae_complex_from_d(e->ptr.p_double[i]); + d->ptr.p_double[i] = a->ptr.pp_complex[i][i].x; + tau->ptr.p_complex[i] = taui; + } + d->ptr.p_double[n-1] = a->ptr.pp_complex[n-1][n-1].x; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Unpacking matrix Q which reduces a Hermitian matrix to a real tridiagonal +form. + +Input parameters: + A - the result of a HMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of HMatrixTD subroutine) + Tau - the result of a HMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void hmatrixtdunpackq(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tau, + /* Complex */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector v; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&v, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&work, 0, DT_COMPLEX, _state, ae_true); + + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + ae_matrix_set_length(q, n-1+1, n-1+1, _state); + ae_vector_set_length(&v, n+1, _state); + ae_vector_set_length(&work, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(1); + } + else + { + q->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + + /* + * unpack Q + */ + if( isupper ) + { + for(i=0; i<=n-2; i++) + { + + /* + * Apply H(i) + */ + ae_v_cmove(&v.ptr.p_complex[1], 1, &a->ptr.pp_complex[0][i+1], a->stride, "N", ae_v_len(1,i+1)); + v.ptr.p_complex[i+1] = ae_complex_from_d(1); + complexapplyreflectionfromtheleft(q, tau->ptr.p_complex[i], &v, 0, i, 0, n-1, &work, _state); + } + } + else + { + for(i=n-2; i>=0; i--) + { + + /* + * Apply H(i) + */ + ae_v_cmove(&v.ptr.p_complex[1], 1, &a->ptr.pp_complex[i+1][i], a->stride, "N", ae_v_len(1,n-i-1)); + v.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheleft(q, tau->ptr.p_complex[i], &v, i+1, n-1, 0, n-1, &work, _state); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Base case for complex QR + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +static void ortfac_cmatrixqrbasecase(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* work, + /* Complex */ ae_vector* t, + /* Complex */ ae_vector* tau, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_int_t mmi; + ae_int_t minmn; + ae_complex tmp; + + + minmn = ae_minint(m, n, _state); + if( minmn<=0 ) + { + return; + } + + /* + * Test the input arguments + */ + k = ae_minint(m, n, _state); + for(i=0; i<=k-1; i++) + { + + /* + * Generate elementary reflector H(i) to annihilate A(i+1:m,i) + */ + mmi = m-i; + ae_v_cmove(&t->ptr.p_complex[1], 1, &a->ptr.pp_complex[i][i], a->stride, "N", ae_v_len(1,mmi)); + complexgeneratereflection(t, mmi, &tmp, _state); + tau->ptr.p_complex[i] = tmp; + ae_v_cmove(&a->ptr.pp_complex[i][i], a->stride, &t->ptr.p_complex[1], 1, "N", ae_v_len(i,m-1)); + t->ptr.p_complex[1] = ae_complex_from_d(1); + if( iptr.p_complex[i], _state), t, i, m-1, i+1, n-1, work, _state); + } + } +} + + +/************************************************************************* +Base case for complex LQ + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +static void ortfac_cmatrixlqbasecase(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* work, + /* Complex */ ae_vector* t, + /* Complex */ ae_vector* tau, + ae_state *_state) +{ + ae_int_t i; + ae_int_t minmn; + ae_complex tmp; + + + minmn = ae_minint(m, n, _state); + if( minmn<=0 ) + { + return; + } + + /* + * Test the input arguments + */ + for(i=0; i<=minmn-1; i++) + { + + /* + * Generate elementary reflector H(i) + * + * NOTE: ComplexGenerateReflection() generates left reflector, + * i.e. H which reduces x by applyiong from the left, but we + * need RIGHT reflector. So we replace H=E-tau*v*v' by H^H, + * which changes v to conj(v). + */ + ae_v_cmove(&t->ptr.p_complex[1], 1, &a->ptr.pp_complex[i][i], 1, "Conj", ae_v_len(1,n-i)); + complexgeneratereflection(t, n-i, &tmp, _state); + tau->ptr.p_complex[i] = tmp; + ae_v_cmove(&a->ptr.pp_complex[i][i], 1, &t->ptr.p_complex[1], 1, "Conj", ae_v_len(i,n-1)); + t->ptr.p_complex[1] = ae_complex_from_d(1); + if( iptr.p_complex[i], t, i+1, m-1, i, n-1, work, _state); + } + } +} + + +/************************************************************************* +Generate block reflector: +* fill unused parts of reflectors matrix by zeros +* fill diagonal of reflectors matrix by ones +* generate triangular factor T + +PARAMETERS: + A - either LengthA*BlockSize (if ColumnwiseA) or + BlockSize*LengthA (if not ColumnwiseA) matrix of + elementary reflectors. + Modified on exit. + Tau - scalar factors + ColumnwiseA - reflectors are stored in rows or in columns + LengthA - length of largest reflector + BlockSize - number of reflectors + T - array[BlockSize,2*BlockSize]. Left BlockSize*BlockSize + submatrix stores triangular factor on exit. + WORK - array[BlockSize] + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +static void ortfac_rmatrixblockreflector(/* Real */ ae_matrix* a, + /* Real */ ae_vector* tau, + ae_bool columnwisea, + ae_int_t lengtha, + ae_int_t blocksize, + /* Real */ ae_matrix* t, + /* Real */ ae_vector* work, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + + + + /* + * fill beginning of new column with zeros, + * load 1.0 in the first non-zero element + */ + for(k=0; k<=blocksize-1; k++) + { + if( columnwisea ) + { + for(i=0; i<=k-1; i++) + { + a->ptr.pp_double[i][k] = 0; + } + } + else + { + for(i=0; i<=k-1; i++) + { + a->ptr.pp_double[k][i] = 0; + } + } + a->ptr.pp_double[k][k] = 1; + } + + /* + * Calculate Gram matrix of A + */ + for(i=0; i<=blocksize-1; i++) + { + for(j=0; j<=blocksize-1; j++) + { + t->ptr.pp_double[i][blocksize+j] = 0; + } + } + for(k=0; k<=lengtha-1; k++) + { + for(j=1; j<=blocksize-1; j++) + { + if( columnwisea ) + { + v = a->ptr.pp_double[k][j]; + if( ae_fp_neq(v,0) ) + { + ae_v_addd(&t->ptr.pp_double[j][blocksize], 1, &a->ptr.pp_double[k][0], 1, ae_v_len(blocksize,blocksize+j-1), v); + } + } + else + { + v = a->ptr.pp_double[j][k]; + if( ae_fp_neq(v,0) ) + { + ae_v_addd(&t->ptr.pp_double[j][blocksize], 1, &a->ptr.pp_double[0][k], a->stride, ae_v_len(blocksize,blocksize+j-1), v); + } + } + } + } + + /* + * Prepare Y (stored in TmpA) and T (stored in TmpT) + */ + for(k=0; k<=blocksize-1; k++) + { + + /* + * fill non-zero part of T, use pre-calculated Gram matrix + */ + ae_v_move(&work->ptr.p_double[0], 1, &t->ptr.pp_double[k][blocksize], 1, ae_v_len(0,k-1)); + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&t->ptr.pp_double[i][i], 1, &work->ptr.p_double[i], 1, ae_v_len(i,k-1)); + t->ptr.pp_double[i][k] = -tau->ptr.p_double[k]*v; + } + t->ptr.pp_double[k][k] = -tau->ptr.p_double[k]; + + /* + * Rest of T is filled by zeros + */ + for(i=k+1; i<=blocksize-1; i++) + { + t->ptr.pp_double[i][k] = 0; + } + } +} + + +/************************************************************************* +Generate block reflector (complex): +* fill unused parts of reflectors matrix by zeros +* fill diagonal of reflectors matrix by ones +* generate triangular factor T + + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +static void ortfac_cmatrixblockreflector(/* Complex */ ae_matrix* a, + /* Complex */ ae_vector* tau, + ae_bool columnwisea, + ae_int_t lengtha, + ae_int_t blocksize, + /* Complex */ ae_matrix* t, + /* Complex */ ae_vector* work, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + ae_complex v; + + + + /* + * Prepare Y (stored in TmpA) and T (stored in TmpT) + */ + for(k=0; k<=blocksize-1; k++) + { + + /* + * fill beginning of new column with zeros, + * load 1.0 in the first non-zero element + */ + if( columnwisea ) + { + for(i=0; i<=k-1; i++) + { + a->ptr.pp_complex[i][k] = ae_complex_from_d(0); + } + } + else + { + for(i=0; i<=k-1; i++) + { + a->ptr.pp_complex[k][i] = ae_complex_from_d(0); + } + } + a->ptr.pp_complex[k][k] = ae_complex_from_d(1); + + /* + * fill non-zero part of T, + */ + for(i=0; i<=k-1; i++) + { + if( columnwisea ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[k][i], a->stride, "Conj", &a->ptr.pp_complex[k][k], a->stride, "N", ae_v_len(k,lengtha-1)); + } + else + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[i][k], 1, "N", &a->ptr.pp_complex[k][k], 1, "Conj", ae_v_len(k,lengtha-1)); + } + work->ptr.p_complex[i] = v; + } + for(i=0; i<=k-1; i++) + { + v = ae_v_cdotproduct(&t->ptr.pp_complex[i][i], 1, "N", &work->ptr.p_complex[i], 1, "N", ae_v_len(i,k-1)); + t->ptr.pp_complex[i][k] = ae_c_neg(ae_c_mul(tau->ptr.p_complex[k],v)); + } + t->ptr.pp_complex[k][k] = ae_c_neg(tau->ptr.p_complex[k]); + + /* + * Rest of T is filled by zeros + */ + for(i=k+1; i<=blocksize-1; i++) + { + t->ptr.pp_complex[i][k] = ae_complex_from_d(0); + } + } +} + + + + +/************************************************************************* +Singular value decomposition of a bidiagonal matrix (extended algorithm) + +The algorithm performs the singular value decomposition of a bidiagonal +matrix B (upper or lower) representing it as B = Q*S*P^T, where Q and P - +orthogonal matrices, S - diagonal matrix with non-negative elements on the +main diagonal, in descending order. + +The algorithm finds singular values. In addition, the algorithm can +calculate matrices Q and P (more precisely, not the matrices, but their +product with given matrices U and VT - U*Q and (P^T)*VT)). Of course, +matrices U and VT can be of any type, including identity. Furthermore, the +algorithm can calculate Q'*C (this product is calculated more effectively +than U*Q, because this calculation operates with rows instead of matrix +columns). + +The feature of the algorithm is its ability to find all singular values +including those which are arbitrarily close to 0 with relative accuracy +close to machine precision. If the parameter IsFractionalAccuracyRequired +is set to True, all singular values will have high relative accuracy close +to machine precision. If the parameter is set to False, only the biggest +singular value will have relative accuracy close to machine precision. +The absolute error of other singular values is equal to the absolute error +of the biggest singular value. + +Input parameters: + D - main diagonal of matrix B. + Array whose index ranges within [0..N-1]. + E - superdiagonal (or subdiagonal) of matrix B. + Array whose index ranges within [0..N-2]. + N - size of matrix B. + IsUpper - True, if the matrix is upper bidiagonal. + IsFractionalAccuracyRequired - + THIS PARAMETER IS IGNORED SINCE ALGLIB 3.5.0 + SINGULAR VALUES ARE ALWAYS SEARCHED WITH HIGH ACCURACY. + U - matrix to be multiplied by Q. + Array whose indexes range within [0..NRU-1, 0..N-1]. + The matrix can be bigger, in that case only the submatrix + [0..NRU-1, 0..N-1] will be multiplied by Q. + NRU - number of rows in matrix U. + C - matrix to be multiplied by Q'. + Array whose indexes range within [0..N-1, 0..NCC-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCC-1] will be multiplied by Q'. + NCC - number of columns in matrix C. + VT - matrix to be multiplied by P^T. + Array whose indexes range within [0..N-1, 0..NCVT-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCVT-1] will be multiplied by P^T. + NCVT - number of columns in matrix VT. + +Output parameters: + D - singular values of matrix B in descending order. + U - if NRU>0, contains matrix U*Q. + VT - if NCVT>0, contains matrix (P^T)*VT. + C - if NCC>0, contains matrix Q'*C. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Additional information: + The type of convergence is controlled by the internal parameter TOL. + If the parameter is greater than 0, the singular values will have + relative accuracy TOL. If TOL<0, the singular values will have + absolute accuracy ABS(TOL)*norm(B). + By default, |TOL| falls within the range of 10*Epsilon and 100*Epsilon, + where Epsilon is the machine precision. It is not recommended to use + TOL less than 10*Epsilon since this will considerably slow down the + algorithm and may not lead to error decreasing. +History: + * 31 March, 2007. + changed MAXITR from 6 to 12. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1999. +*************************************************************************/ +ae_bool rmatrixbdsvd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t ncvt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_vector d1; + ae_vector e1; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_init(&d1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e1, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&d1, n+1, _state); + ae_v_move(&d1.ptr.p_double[1], 1, &d->ptr.p_double[0], 1, ae_v_len(1,n)); + if( n>1 ) + { + ae_vector_set_length(&e1, n-1+1, _state); + ae_v_move(&e1.ptr.p_double[1], 1, &e->ptr.p_double[0], 1, ae_v_len(1,n-1)); + } + result = bdsvd_bidiagonalsvddecompositioninternal(&d1, &e1, n, isupper, isfractionalaccuracyrequired, u, 0, nru, c, 0, ncc, vt, 0, ncvt, _state); + ae_v_move(&d->ptr.p_double[0], 1, &d1.ptr.p_double[1], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); + return result; +} + + +ae_bool bidiagonalsvddecomposition(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t ncvt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + + result = bdsvd_bidiagonalsvddecompositioninternal(d, e, n, isupper, isfractionalaccuracyrequired, u, 1, nru, c, 1, ncc, vt, 1, ncvt, _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Internal working subroutine for bidiagonal decomposition +*************************************************************************/ +static ae_bool bdsvd_bidiagonalsvddecompositioninternal(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t ustart, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t cstart, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t vstart, + ae_int_t ncvt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_int_t i; + ae_int_t idir; + ae_int_t isub; + ae_int_t iter; + ae_int_t j; + ae_int_t ll; + ae_int_t lll; + ae_int_t m; + ae_int_t maxit; + ae_int_t oldll; + ae_int_t oldm; + double abse; + double abss; + double cosl; + double cosr; + double cs; + double eps; + double f; + double g; + double h; + double mu; + double oldcs; + double oldsn; + double r; + double shift; + double sigmn; + double sigmx; + double sinl; + double sinr; + double sll; + double smax; + double smin; + double sminl; + double sminoa; + double sn; + double thresh; + double tol; + double tolmul; + double unfl; + ae_vector work0; + ae_vector work1; + ae_vector work2; + ae_vector work3; + ae_int_t maxitr; + ae_bool matrixsplitflag; + ae_bool iterflag; + ae_vector utemp; + ae_vector vttemp; + ae_vector ctemp; + ae_vector etemp; + ae_bool fwddir; + double tmp; + ae_int_t mm1; + ae_int_t mm0; + ae_bool bchangedir; + ae_int_t uend; + ae_int_t cend; + ae_int_t vend; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_init(&work0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&utemp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&vttemp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ctemp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&etemp, 0, DT_REAL, _state, ae_true); + + result = ae_true; + if( n==0 ) + { + ae_frame_leave(_state); + return result; + } + if( n==1 ) + { + if( ae_fp_less(d->ptr.p_double[1],0) ) + { + d->ptr.p_double[1] = -d->ptr.p_double[1]; + if( ncvt>0 ) + { + ae_v_muld(&vt->ptr.pp_double[vstart][vstart], 1, ae_v_len(vstart,vstart+ncvt-1), -1); + } + } + ae_frame_leave(_state); + return result; + } + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + ll = 0; + oldsn = 0; + + /* + * init + */ + ae_vector_set_length(&work0, n-1+1, _state); + ae_vector_set_length(&work1, n-1+1, _state); + ae_vector_set_length(&work2, n-1+1, _state); + ae_vector_set_length(&work3, n-1+1, _state); + uend = ustart+ae_maxint(nru-1, 0, _state); + vend = vstart+ae_maxint(ncvt-1, 0, _state); + cend = cstart+ae_maxint(ncc-1, 0, _state); + ae_vector_set_length(&utemp, uend+1, _state); + ae_vector_set_length(&vttemp, vend+1, _state); + ae_vector_set_length(&ctemp, cend+1, _state); + maxitr = 12; + fwddir = ae_true; + + /* + * resize E from N-1 to N + */ + ae_vector_set_length(&etemp, n+1, _state); + for(i=1; i<=n-1; i++) + { + etemp.ptr.p_double[i] = e->ptr.p_double[i]; + } + ae_vector_set_length(e, n+1, _state); + for(i=1; i<=n-1; i++) + { + e->ptr.p_double[i] = etemp.ptr.p_double[i]; + } + e->ptr.p_double[n] = 0; + idir = 0; + + /* + * Get machine constants + */ + eps = ae_machineepsilon; + unfl = ae_minrealnumber; + + /* + * If matrix lower bidiagonal, rotate to be upper bidiagonal + * by applying Givens rotations on the left + */ + if( !isupper ) + { + for(i=1; i<=n-1; i++) + { + generaterotation(d->ptr.p_double[i], e->ptr.p_double[i], &cs, &sn, &r, _state); + d->ptr.p_double[i] = r; + e->ptr.p_double[i] = sn*d->ptr.p_double[i+1]; + d->ptr.p_double[i+1] = cs*d->ptr.p_double[i+1]; + work0.ptr.p_double[i] = cs; + work1.ptr.p_double[i] = sn; + } + + /* + * Update singular vectors if desired + */ + if( nru>0 ) + { + applyrotationsfromtheright(fwddir, ustart, uend, 1+ustart-1, n+ustart-1, &work0, &work1, u, &utemp, _state); + } + if( ncc>0 ) + { + applyrotationsfromtheleft(fwddir, 1+cstart-1, n+cstart-1, cstart, cend, &work0, &work1, c, &ctemp, _state); + } + } + + /* + * Compute singular values to relative accuracy TOL + * (By setting TOL to be negative, algorithm will compute + * singular values to absolute accuracy ABS(TOL)*norm(input matrix)) + */ + tolmul = ae_maxreal(10, ae_minreal(100, ae_pow(eps, -0.125, _state), _state), _state); + tol = tolmul*eps; + + /* + * Compute approximate maximum, minimum singular values + */ + smax = 0; + for(i=1; i<=n; i++) + { + smax = ae_maxreal(smax, ae_fabs(d->ptr.p_double[i], _state), _state); + } + for(i=1; i<=n-1; i++) + { + smax = ae_maxreal(smax, ae_fabs(e->ptr.p_double[i], _state), _state); + } + sminl = 0; + if( ae_fp_greater_eq(tol,0) ) + { + + /* + * Relative accuracy desired + */ + sminoa = ae_fabs(d->ptr.p_double[1], _state); + if( ae_fp_neq(sminoa,0) ) + { + mu = sminoa; + for(i=2; i<=n; i++) + { + mu = ae_fabs(d->ptr.p_double[i], _state)*(mu/(mu+ae_fabs(e->ptr.p_double[i-1], _state))); + sminoa = ae_minreal(sminoa, mu, _state); + if( ae_fp_eq(sminoa,0) ) + { + break; + } + } + } + sminoa = sminoa/ae_sqrt(n, _state); + thresh = ae_maxreal(tol*sminoa, maxitr*n*n*unfl, _state); + } + else + { + + /* + * Absolute accuracy desired + */ + thresh = ae_maxreal(ae_fabs(tol, _state)*smax, maxitr*n*n*unfl, _state); + } + + /* + * Prepare for main iteration loop for the singular values + * (MAXIT is the maximum number of passes through the inner + * loop permitted before nonconvergence signalled.) + */ + maxit = maxitr*n*n; + iter = 0; + oldll = -1; + oldm = -1; + + /* + * M points to last element of unconverged part of matrix + */ + m = n; + + /* + * Begin main iteration loop + */ + for(;;) + { + + /* + * Check for convergence or exceeding iteration count + */ + if( m<=1 ) + { + break; + } + if( iter>maxit ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Find diagonal block of matrix to work on + */ + if( ae_fp_less(tol,0)&&ae_fp_less_eq(ae_fabs(d->ptr.p_double[m], _state),thresh) ) + { + d->ptr.p_double[m] = 0; + } + smax = ae_fabs(d->ptr.p_double[m], _state); + smin = smax; + matrixsplitflag = ae_false; + for(lll=1; lll<=m-1; lll++) + { + ll = m-lll; + abss = ae_fabs(d->ptr.p_double[ll], _state); + abse = ae_fabs(e->ptr.p_double[ll], _state); + if( ae_fp_less(tol,0)&&ae_fp_less_eq(abss,thresh) ) + { + d->ptr.p_double[ll] = 0; + } + if( ae_fp_less_eq(abse,thresh) ) + { + matrixsplitflag = ae_true; + break; + } + smin = ae_minreal(smin, abss, _state); + smax = ae_maxreal(smax, ae_maxreal(abss, abse, _state), _state); + } + if( !matrixsplitflag ) + { + ll = 0; + } + else + { + + /* + * Matrix splits since E(LL) = 0 + */ + e->ptr.p_double[ll] = 0; + if( ll==m-1 ) + { + + /* + * Convergence of bottom singular value, return to top of loop + */ + m = m-1; + continue; + } + } + ll = ll+1; + + /* + * E(LL) through E(M-1) are nonzero, E(LL-1) is zero + */ + if( ll==m-1 ) + { + + /* + * 2 by 2 block, handle separately + */ + bdsvd_svdv2x2(d->ptr.p_double[m-1], e->ptr.p_double[m-1], d->ptr.p_double[m], &sigmn, &sigmx, &sinr, &cosr, &sinl, &cosl, _state); + d->ptr.p_double[m-1] = sigmx; + e->ptr.p_double[m-1] = 0; + d->ptr.p_double[m] = sigmn; + + /* + * Compute singular vectors, if desired + */ + if( ncvt>0 ) + { + mm0 = m+(vstart-1); + mm1 = m-1+(vstart-1); + ae_v_moved(&vttemp.ptr.p_double[vstart], 1, &vt->ptr.pp_double[mm1][vstart], 1, ae_v_len(vstart,vend), cosr); + ae_v_addd(&vttemp.ptr.p_double[vstart], 1, &vt->ptr.pp_double[mm0][vstart], 1, ae_v_len(vstart,vend), sinr); + ae_v_muld(&vt->ptr.pp_double[mm0][vstart], 1, ae_v_len(vstart,vend), cosr); + ae_v_subd(&vt->ptr.pp_double[mm0][vstart], 1, &vt->ptr.pp_double[mm1][vstart], 1, ae_v_len(vstart,vend), sinr); + ae_v_move(&vt->ptr.pp_double[mm1][vstart], 1, &vttemp.ptr.p_double[vstart], 1, ae_v_len(vstart,vend)); + } + if( nru>0 ) + { + mm0 = m+ustart-1; + mm1 = m-1+ustart-1; + ae_v_moved(&utemp.ptr.p_double[ustart], 1, &u->ptr.pp_double[ustart][mm1], u->stride, ae_v_len(ustart,uend), cosl); + ae_v_addd(&utemp.ptr.p_double[ustart], 1, &u->ptr.pp_double[ustart][mm0], u->stride, ae_v_len(ustart,uend), sinl); + ae_v_muld(&u->ptr.pp_double[ustart][mm0], u->stride, ae_v_len(ustart,uend), cosl); + ae_v_subd(&u->ptr.pp_double[ustart][mm0], u->stride, &u->ptr.pp_double[ustart][mm1], u->stride, ae_v_len(ustart,uend), sinl); + ae_v_move(&u->ptr.pp_double[ustart][mm1], u->stride, &utemp.ptr.p_double[ustart], 1, ae_v_len(ustart,uend)); + } + if( ncc>0 ) + { + mm0 = m+cstart-1; + mm1 = m-1+cstart-1; + ae_v_moved(&ctemp.ptr.p_double[cstart], 1, &c->ptr.pp_double[mm1][cstart], 1, ae_v_len(cstart,cend), cosl); + ae_v_addd(&ctemp.ptr.p_double[cstart], 1, &c->ptr.pp_double[mm0][cstart], 1, ae_v_len(cstart,cend), sinl); + ae_v_muld(&c->ptr.pp_double[mm0][cstart], 1, ae_v_len(cstart,cend), cosl); + ae_v_subd(&c->ptr.pp_double[mm0][cstart], 1, &c->ptr.pp_double[mm1][cstart], 1, ae_v_len(cstart,cend), sinl); + ae_v_move(&c->ptr.pp_double[mm1][cstart], 1, &ctemp.ptr.p_double[cstart], 1, ae_v_len(cstart,cend)); + } + m = m-2; + continue; + } + + /* + * If working on new submatrix, choose shift direction + * (from larger end diagonal element towards smaller) + * + * Previously was + * "if (LL>OLDM) or (M + * Very strange that LAPACK still contains it. + */ + bchangedir = ae_false; + if( idir==1&&ae_fp_less(ae_fabs(d->ptr.p_double[ll], _state),1.0E-3*ae_fabs(d->ptr.p_double[m], _state)) ) + { + bchangedir = ae_true; + } + if( idir==2&&ae_fp_less(ae_fabs(d->ptr.p_double[m], _state),1.0E-3*ae_fabs(d->ptr.p_double[ll], _state)) ) + { + bchangedir = ae_true; + } + if( (ll!=oldll||m!=oldm)||bchangedir ) + { + if( ae_fp_greater_eq(ae_fabs(d->ptr.p_double[ll], _state),ae_fabs(d->ptr.p_double[m], _state)) ) + { + + /* + * Chase bulge from top (big end) to bottom (small end) + */ + idir = 1; + } + else + { + + /* + * Chase bulge from bottom (big end) to top (small end) + */ + idir = 2; + } + } + + /* + * Apply convergence tests + */ + if( idir==1 ) + { + + /* + * Run convergence test in forward direction + * First apply standard test to bottom of matrix + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[m-1], _state),ae_fabs(tol, _state)*ae_fabs(d->ptr.p_double[m], _state))||(ae_fp_less(tol,0)&&ae_fp_less_eq(ae_fabs(e->ptr.p_double[m-1], _state),thresh)) ) + { + e->ptr.p_double[m-1] = 0; + continue; + } + if( ae_fp_greater_eq(tol,0) ) + { + + /* + * If relative accuracy desired, + * apply convergence criterion forward + */ + mu = ae_fabs(d->ptr.p_double[ll], _state); + sminl = mu; + iterflag = ae_false; + for(lll=ll; lll<=m-1; lll++) + { + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[lll], _state),tol*mu) ) + { + e->ptr.p_double[lll] = 0; + iterflag = ae_true; + break; + } + mu = ae_fabs(d->ptr.p_double[lll+1], _state)*(mu/(mu+ae_fabs(e->ptr.p_double[lll], _state))); + sminl = ae_minreal(sminl, mu, _state); + } + if( iterflag ) + { + continue; + } + } + } + else + { + + /* + * Run convergence test in backward direction + * First apply standard test to top of matrix + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[ll], _state),ae_fabs(tol, _state)*ae_fabs(d->ptr.p_double[ll], _state))||(ae_fp_less(tol,0)&&ae_fp_less_eq(ae_fabs(e->ptr.p_double[ll], _state),thresh)) ) + { + e->ptr.p_double[ll] = 0; + continue; + } + if( ae_fp_greater_eq(tol,0) ) + { + + /* + * If relative accuracy desired, + * apply convergence criterion backward + */ + mu = ae_fabs(d->ptr.p_double[m], _state); + sminl = mu; + iterflag = ae_false; + for(lll=m-1; lll>=ll; lll--) + { + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[lll], _state),tol*mu) ) + { + e->ptr.p_double[lll] = 0; + iterflag = ae_true; + break; + } + mu = ae_fabs(d->ptr.p_double[lll], _state)*(mu/(mu+ae_fabs(e->ptr.p_double[lll], _state))); + sminl = ae_minreal(sminl, mu, _state); + } + if( iterflag ) + { + continue; + } + } + } + oldll = ll; + oldm = m; + + /* + * Compute shift. First, test if shifting would ruin relative + * accuracy, and if so set the shift to zero. + */ + if( ae_fp_greater_eq(tol,0)&&ae_fp_less_eq(n*tol*(sminl/smax),ae_maxreal(eps, 0.01*tol, _state)) ) + { + + /* + * Use a zero shift to avoid loss of relative accuracy + */ + shift = 0; + } + else + { + + /* + * Compute the shift from 2-by-2 block at end of matrix + */ + if( idir==1 ) + { + sll = ae_fabs(d->ptr.p_double[ll], _state); + bdsvd_svd2x2(d->ptr.p_double[m-1], e->ptr.p_double[m-1], d->ptr.p_double[m], &shift, &r, _state); + } + else + { + sll = ae_fabs(d->ptr.p_double[m], _state); + bdsvd_svd2x2(d->ptr.p_double[ll], e->ptr.p_double[ll], d->ptr.p_double[ll+1], &shift, &r, _state); + } + + /* + * Test if shift negligible, and if so set to zero + */ + if( ae_fp_greater(sll,0) ) + { + if( ae_fp_less(ae_sqr(shift/sll, _state),eps) ) + { + shift = 0; + } + } + } + + /* + * Increment iteration count + */ + iter = iter+m-ll; + + /* + * If SHIFT = 0, do simplified QR iteration + */ + if( ae_fp_eq(shift,0) ) + { + if( idir==1 ) + { + + /* + * Chase bulge from top to bottom + * Save cosines and sines for later singular vector updates + */ + cs = 1; + oldcs = 1; + for(i=ll; i<=m-1; i++) + { + generaterotation(d->ptr.p_double[i]*cs, e->ptr.p_double[i], &cs, &sn, &r, _state); + if( i>ll ) + { + e->ptr.p_double[i-1] = oldsn*r; + } + generaterotation(oldcs*r, d->ptr.p_double[i+1]*sn, &oldcs, &oldsn, &tmp, _state); + d->ptr.p_double[i] = tmp; + work0.ptr.p_double[i-ll+1] = cs; + work1.ptr.p_double[i-ll+1] = sn; + work2.ptr.p_double[i-ll+1] = oldcs; + work3.ptr.p_double[i-ll+1] = oldsn; + } + h = d->ptr.p_double[m]*cs; + d->ptr.p_double[m] = h*oldcs; + e->ptr.p_double[m-1] = h*oldsn; + + /* + * Update singular vectors + */ + if( ncvt>0 ) + { + applyrotationsfromtheleft(fwddir, ll+vstart-1, m+vstart-1, vstart, vend, &work0, &work1, vt, &vttemp, _state); + } + if( nru>0 ) + { + applyrotationsfromtheright(fwddir, ustart, uend, ll+ustart-1, m+ustart-1, &work2, &work3, u, &utemp, _state); + } + if( ncc>0 ) + { + applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, &work2, &work3, c, &ctemp, _state); + } + + /* + * Test convergence + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[m-1], _state),thresh) ) + { + e->ptr.p_double[m-1] = 0; + } + } + else + { + + /* + * Chase bulge from bottom to top + * Save cosines and sines for later singular vector updates + */ + cs = 1; + oldcs = 1; + for(i=m; i>=ll+1; i--) + { + generaterotation(d->ptr.p_double[i]*cs, e->ptr.p_double[i-1], &cs, &sn, &r, _state); + if( iptr.p_double[i] = oldsn*r; + } + generaterotation(oldcs*r, d->ptr.p_double[i-1]*sn, &oldcs, &oldsn, &tmp, _state); + d->ptr.p_double[i] = tmp; + work0.ptr.p_double[i-ll] = cs; + work1.ptr.p_double[i-ll] = -sn; + work2.ptr.p_double[i-ll] = oldcs; + work3.ptr.p_double[i-ll] = -oldsn; + } + h = d->ptr.p_double[ll]*cs; + d->ptr.p_double[ll] = h*oldcs; + e->ptr.p_double[ll] = h*oldsn; + + /* + * Update singular vectors + */ + if( ncvt>0 ) + { + applyrotationsfromtheleft(!fwddir, ll+vstart-1, m+vstart-1, vstart, vend, &work2, &work3, vt, &vttemp, _state); + } + if( nru>0 ) + { + applyrotationsfromtheright(!fwddir, ustart, uend, ll+ustart-1, m+ustart-1, &work0, &work1, u, &utemp, _state); + } + if( ncc>0 ) + { + applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, &work0, &work1, c, &ctemp, _state); + } + + /* + * Test convergence + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[ll], _state),thresh) ) + { + e->ptr.p_double[ll] = 0; + } + } + } + else + { + + /* + * Use nonzero shift + */ + if( idir==1 ) + { + + /* + * Chase bulge from top to bottom + * Save cosines and sines for later singular vector updates + */ + f = (ae_fabs(d->ptr.p_double[ll], _state)-shift)*(bdsvd_extsignbdsqr(1, d->ptr.p_double[ll], _state)+shift/d->ptr.p_double[ll]); + g = e->ptr.p_double[ll]; + for(i=ll; i<=m-1; i++) + { + generaterotation(f, g, &cosr, &sinr, &r, _state); + if( i>ll ) + { + e->ptr.p_double[i-1] = r; + } + f = cosr*d->ptr.p_double[i]+sinr*e->ptr.p_double[i]; + e->ptr.p_double[i] = cosr*e->ptr.p_double[i]-sinr*d->ptr.p_double[i]; + g = sinr*d->ptr.p_double[i+1]; + d->ptr.p_double[i+1] = cosr*d->ptr.p_double[i+1]; + generaterotation(f, g, &cosl, &sinl, &r, _state); + d->ptr.p_double[i] = r; + f = cosl*e->ptr.p_double[i]+sinl*d->ptr.p_double[i+1]; + d->ptr.p_double[i+1] = cosl*d->ptr.p_double[i+1]-sinl*e->ptr.p_double[i]; + if( iptr.p_double[i+1]; + e->ptr.p_double[i+1] = cosl*e->ptr.p_double[i+1]; + } + work0.ptr.p_double[i-ll+1] = cosr; + work1.ptr.p_double[i-ll+1] = sinr; + work2.ptr.p_double[i-ll+1] = cosl; + work3.ptr.p_double[i-ll+1] = sinl; + } + e->ptr.p_double[m-1] = f; + + /* + * Update singular vectors + */ + if( ncvt>0 ) + { + applyrotationsfromtheleft(fwddir, ll+vstart-1, m+vstart-1, vstart, vend, &work0, &work1, vt, &vttemp, _state); + } + if( nru>0 ) + { + applyrotationsfromtheright(fwddir, ustart, uend, ll+ustart-1, m+ustart-1, &work2, &work3, u, &utemp, _state); + } + if( ncc>0 ) + { + applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, &work2, &work3, c, &ctemp, _state); + } + + /* + * Test convergence + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[m-1], _state),thresh) ) + { + e->ptr.p_double[m-1] = 0; + } + } + else + { + + /* + * Chase bulge from bottom to top + * Save cosines and sines for later singular vector updates + */ + f = (ae_fabs(d->ptr.p_double[m], _state)-shift)*(bdsvd_extsignbdsqr(1, d->ptr.p_double[m], _state)+shift/d->ptr.p_double[m]); + g = e->ptr.p_double[m-1]; + for(i=m; i>=ll+1; i--) + { + generaterotation(f, g, &cosr, &sinr, &r, _state); + if( iptr.p_double[i] = r; + } + f = cosr*d->ptr.p_double[i]+sinr*e->ptr.p_double[i-1]; + e->ptr.p_double[i-1] = cosr*e->ptr.p_double[i-1]-sinr*d->ptr.p_double[i]; + g = sinr*d->ptr.p_double[i-1]; + d->ptr.p_double[i-1] = cosr*d->ptr.p_double[i-1]; + generaterotation(f, g, &cosl, &sinl, &r, _state); + d->ptr.p_double[i] = r; + f = cosl*e->ptr.p_double[i-1]+sinl*d->ptr.p_double[i-1]; + d->ptr.p_double[i-1] = cosl*d->ptr.p_double[i-1]-sinl*e->ptr.p_double[i-1]; + if( i>ll+1 ) + { + g = sinl*e->ptr.p_double[i-2]; + e->ptr.p_double[i-2] = cosl*e->ptr.p_double[i-2]; + } + work0.ptr.p_double[i-ll] = cosr; + work1.ptr.p_double[i-ll] = -sinr; + work2.ptr.p_double[i-ll] = cosl; + work3.ptr.p_double[i-ll] = -sinl; + } + e->ptr.p_double[ll] = f; + + /* + * Test convergence + */ + if( ae_fp_less_eq(ae_fabs(e->ptr.p_double[ll], _state),thresh) ) + { + e->ptr.p_double[ll] = 0; + } + + /* + * Update singular vectors if desired + */ + if( ncvt>0 ) + { + applyrotationsfromtheleft(!fwddir, ll+vstart-1, m+vstart-1, vstart, vend, &work2, &work3, vt, &vttemp, _state); + } + if( nru>0 ) + { + applyrotationsfromtheright(!fwddir, ustart, uend, ll+ustart-1, m+ustart-1, &work0, &work1, u, &utemp, _state); + } + if( ncc>0 ) + { + applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, &work0, &work1, c, &ctemp, _state); + } + } + } + + /* + * QR iteration finished, go back and check convergence + */ + continue; + } + + /* + * All singular values converged, so make them positive + */ + for(i=1; i<=n; i++) + { + if( ae_fp_less(d->ptr.p_double[i],0) ) + { + d->ptr.p_double[i] = -d->ptr.p_double[i]; + + /* + * Change sign of singular vectors, if desired + */ + if( ncvt>0 ) + { + ae_v_muld(&vt->ptr.pp_double[i+vstart-1][vstart], 1, ae_v_len(vstart,vend), -1); + } + } + } + + /* + * Sort the singular values into decreasing order (insertion sort on + * singular values, but only one transposition per singular vector) + */ + for(i=1; i<=n-1; i++) + { + + /* + * Scan for smallest D(I) + */ + isub = 1; + smin = d->ptr.p_double[1]; + for(j=2; j<=n+1-i; j++) + { + if( ae_fp_less_eq(d->ptr.p_double[j],smin) ) + { + isub = j; + smin = d->ptr.p_double[j]; + } + } + if( isub!=n+1-i ) + { + + /* + * Swap singular values and vectors + */ + d->ptr.p_double[isub] = d->ptr.p_double[n+1-i]; + d->ptr.p_double[n+1-i] = smin; + if( ncvt>0 ) + { + j = n+1-i; + ae_v_move(&vttemp.ptr.p_double[vstart], 1, &vt->ptr.pp_double[isub+vstart-1][vstart], 1, ae_v_len(vstart,vend)); + ae_v_move(&vt->ptr.pp_double[isub+vstart-1][vstart], 1, &vt->ptr.pp_double[j+vstart-1][vstart], 1, ae_v_len(vstart,vend)); + ae_v_move(&vt->ptr.pp_double[j+vstart-1][vstart], 1, &vttemp.ptr.p_double[vstart], 1, ae_v_len(vstart,vend)); + } + if( nru>0 ) + { + j = n+1-i; + ae_v_move(&utemp.ptr.p_double[ustart], 1, &u->ptr.pp_double[ustart][isub+ustart-1], u->stride, ae_v_len(ustart,uend)); + ae_v_move(&u->ptr.pp_double[ustart][isub+ustart-1], u->stride, &u->ptr.pp_double[ustart][j+ustart-1], u->stride, ae_v_len(ustart,uend)); + ae_v_move(&u->ptr.pp_double[ustart][j+ustart-1], u->stride, &utemp.ptr.p_double[ustart], 1, ae_v_len(ustart,uend)); + } + if( ncc>0 ) + { + j = n+1-i; + ae_v_move(&ctemp.ptr.p_double[cstart], 1, &c->ptr.pp_double[isub+cstart-1][cstart], 1, ae_v_len(cstart,cend)); + ae_v_move(&c->ptr.pp_double[isub+cstart-1][cstart], 1, &c->ptr.pp_double[j+cstart-1][cstart], 1, ae_v_len(cstart,cend)); + ae_v_move(&c->ptr.pp_double[j+cstart-1][cstart], 1, &ctemp.ptr.p_double[cstart], 1, ae_v_len(cstart,cend)); + } + } + } + ae_frame_leave(_state); + return result; +} + + +static double bdsvd_extsignbdsqr(double a, double b, ae_state *_state) +{ + double result; + + + if( ae_fp_greater_eq(b,0) ) + { + result = ae_fabs(a, _state); + } + else + { + result = -ae_fabs(a, _state); + } + return result; +} + + +static void bdsvd_svd2x2(double f, + double g, + double h, + double* ssmin, + double* ssmax, + ae_state *_state) +{ + double aas; + double at; + double au; + double c; + double fa; + double fhmn; + double fhmx; + double ga; + double ha; + + *ssmin = 0; + *ssmax = 0; + + fa = ae_fabs(f, _state); + ga = ae_fabs(g, _state); + ha = ae_fabs(h, _state); + fhmn = ae_minreal(fa, ha, _state); + fhmx = ae_maxreal(fa, ha, _state); + if( ae_fp_eq(fhmn,0) ) + { + *ssmin = 0; + if( ae_fp_eq(fhmx,0) ) + { + *ssmax = ga; + } + else + { + *ssmax = ae_maxreal(fhmx, ga, _state)*ae_sqrt(1+ae_sqr(ae_minreal(fhmx, ga, _state)/ae_maxreal(fhmx, ga, _state), _state), _state); + } + } + else + { + if( ae_fp_less(ga,fhmx) ) + { + aas = 1+fhmn/fhmx; + at = (fhmx-fhmn)/fhmx; + au = ae_sqr(ga/fhmx, _state); + c = 2/(ae_sqrt(aas*aas+au, _state)+ae_sqrt(at*at+au, _state)); + *ssmin = fhmn*c; + *ssmax = fhmx/c; + } + else + { + au = fhmx/ga; + if( ae_fp_eq(au,0) ) + { + + /* + * Avoid possible harmful underflow if exponent range + * asymmetric (true SSMIN may not underflow even if + * AU underflows) + */ + *ssmin = fhmn*fhmx/ga; + *ssmax = ga; + } + else + { + aas = 1+fhmn/fhmx; + at = (fhmx-fhmn)/fhmx; + c = 1/(ae_sqrt(1+ae_sqr(aas*au, _state), _state)+ae_sqrt(1+ae_sqr(at*au, _state), _state)); + *ssmin = fhmn*c*au; + *ssmin = *ssmin+(*ssmin); + *ssmax = ga/(c+c); + } + } + } +} + + +static void bdsvd_svdv2x2(double f, + double g, + double h, + double* ssmin, + double* ssmax, + double* snr, + double* csr, + double* snl, + double* csl, + ae_state *_state) +{ + ae_bool gasmal; + ae_bool swp; + ae_int_t pmax; + double a; + double clt; + double crt; + double d; + double fa; + double ft; + double ga; + double gt; + double ha; + double ht; + double l; + double m; + double mm; + double r; + double s; + double slt; + double srt; + double t; + double temp; + double tsign; + double tt; + double v; + + *ssmin = 0; + *ssmax = 0; + *snr = 0; + *csr = 0; + *snl = 0; + *csl = 0; + + ft = f; + fa = ae_fabs(ft, _state); + ht = h; + ha = ae_fabs(h, _state); + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + clt = 0; + crt = 0; + slt = 0; + srt = 0; + tsign = 0; + + /* + * PMAX points to the maximum absolute element of matrix + * PMAX = 1 if F largest in absolute values + * PMAX = 2 if G largest in absolute values + * PMAX = 3 if H largest in absolute values + */ + pmax = 1; + swp = ae_fp_greater(ha,fa); + if( swp ) + { + + /* + * Now FA .ge. HA + */ + pmax = 3; + temp = ft; + ft = ht; + ht = temp; + temp = fa; + fa = ha; + ha = temp; + } + gt = g; + ga = ae_fabs(gt, _state); + if( ae_fp_eq(ga,0) ) + { + + /* + * Diagonal matrix + */ + *ssmin = ha; + *ssmax = fa; + clt = 1; + crt = 1; + slt = 0; + srt = 0; + } + else + { + gasmal = ae_true; + if( ae_fp_greater(ga,fa) ) + { + pmax = 2; + if( ae_fp_less(fa/ga,ae_machineepsilon) ) + { + + /* + * Case of very large GA + */ + gasmal = ae_false; + *ssmax = ga; + if( ae_fp_greater(ha,1) ) + { + v = ga/ha; + *ssmin = fa/v; + } + else + { + v = fa/ga; + *ssmin = v*ha; + } + clt = 1; + slt = ht/gt; + srt = 1; + crt = ft/gt; + } + } + if( gasmal ) + { + + /* + * Normal case + */ + d = fa-ha; + if( ae_fp_eq(d,fa) ) + { + l = 1; + } + else + { + l = d/fa; + } + m = gt/ft; + t = 2-l; + mm = m*m; + tt = t*t; + s = ae_sqrt(tt+mm, _state); + if( ae_fp_eq(l,0) ) + { + r = ae_fabs(m, _state); + } + else + { + r = ae_sqrt(l*l+mm, _state); + } + a = 0.5*(s+r); + *ssmin = ha/a; + *ssmax = fa*a; + if( ae_fp_eq(mm,0) ) + { + + /* + * Note that M is very tiny + */ + if( ae_fp_eq(l,0) ) + { + t = bdsvd_extsignbdsqr(2, ft, _state)*bdsvd_extsignbdsqr(1, gt, _state); + } + else + { + t = gt/bdsvd_extsignbdsqr(d, ft, _state)+m/t; + } + } + else + { + t = (m/(s+t)+m/(r+l))*(1+a); + } + l = ae_sqrt(t*t+4, _state); + crt = 2/l; + srt = t/l; + clt = (crt+srt*m)/a; + v = ht/ft; + slt = v*srt/a; + } + } + if( swp ) + { + *csl = srt; + *snl = crt; + *csr = slt; + *snr = clt; + } + else + { + *csl = clt; + *snl = slt; + *csr = crt; + *snr = srt; + } + + /* + * Correct signs of SSMAX and SSMIN + */ + if( pmax==1 ) + { + tsign = bdsvd_extsignbdsqr(1, *csr, _state)*bdsvd_extsignbdsqr(1, *csl, _state)*bdsvd_extsignbdsqr(1, f, _state); + } + if( pmax==2 ) + { + tsign = bdsvd_extsignbdsqr(1, *snr, _state)*bdsvd_extsignbdsqr(1, *csl, _state)*bdsvd_extsignbdsqr(1, g, _state); + } + if( pmax==3 ) + { + tsign = bdsvd_extsignbdsqr(1, *snr, _state)*bdsvd_extsignbdsqr(1, *snl, _state)*bdsvd_extsignbdsqr(1, h, _state); + } + *ssmax = bdsvd_extsignbdsqr(*ssmax, tsign, _state); + *ssmin = bdsvd_extsignbdsqr(*ssmin, tsign*bdsvd_extsignbdsqr(1, f, _state)*bdsvd_extsignbdsqr(1, h, _state), _state); +} + + + + +/************************************************************************* +Singular value decomposition of a rectangular matrix. + +The algorithm calculates the singular value decomposition of a matrix of +size MxN: A = U * S * V^T + +The algorithm finds the singular values and, optionally, matrices U and V^T. +The algorithm can find both first min(M,N) columns of matrix U and rows of +matrix V^T (singular vectors), and matrices U and V^T wholly (of sizes MxM +and NxN respectively). + +Take into account that the subroutine does not return matrix V but V^T. + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + UNeeded - 0, 1 or 2. See the description of the parameter U. + VTNeeded - 0, 1 or 2. See the description of the parameter VT. + AdditionalMemory - + If the parameter: + * equals 0, the algorithm doesn’t use additional + memory (lower requirements, lower performance). + * equals 1, the algorithm uses additional + memory of size min(M,N)*min(M,N) of real numbers. + It often speeds up the algorithm. + * equals 2, the algorithm uses additional + memory of size M*min(M,N) of real numbers. + It allows to get a maximum performance. + The recommended value of the parameter is 2. + +Output parameters: + W - contains singular values in descending order. + U - if UNeeded=0, U isn't changed, the left singular vectors + are not calculated. + if Uneeded=1, U contains left singular vectors (first + min(M,N) columns of matrix U). Array whose indexes range + within [0..M-1, 0..Min(M,N)-1]. + if UNeeded=2, U contains matrix U wholly. Array whose + indexes range within [0..M-1, 0..M-1]. + VT - if VTNeeded=0, VT isn’t changed, the right singular vectors + are not calculated. + if VTNeeded=1, VT contains right singular vectors (first + min(M,N) rows of matrix V^T). Array whose indexes range + within [0..min(M,N)-1, 0..N-1]. + if VTNeeded=2, VT contains matrix V^T wholly. Array whose + indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +ae_bool rmatrixsvd(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_int_t uneeded, + ae_int_t vtneeded, + ae_int_t additionalmemory, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* u, + /* Real */ ae_matrix* vt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector tauq; + ae_vector taup; + ae_vector tau; + ae_vector e; + ae_vector work; + ae_matrix t2; + ae_bool isupper; + ae_int_t minmn; + ae_int_t ncu; + ae_int_t nrvt; + ae_int_t nru; + ae_int_t ncvt; + ae_int_t i; + ae_int_t j; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(w); + ae_matrix_clear(u); + ae_matrix_clear(vt); + ae_vector_init(&tauq, 0, DT_REAL, _state, ae_true); + ae_vector_init(&taup, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&t2, 0, 0, DT_REAL, _state, ae_true); + + result = ae_true; + if( m==0||n==0 ) + { + ae_frame_leave(_state); + return result; + } + ae_assert(uneeded>=0&&uneeded<=2, "SVDDecomposition: wrong parameters!", _state); + ae_assert(vtneeded>=0&&vtneeded<=2, "SVDDecomposition: wrong parameters!", _state); + ae_assert(additionalmemory>=0&&additionalmemory<=2, "SVDDecomposition: wrong parameters!", _state); + + /* + * initialize + */ + minmn = ae_minint(m, n, _state); + ae_vector_set_length(w, minmn+1, _state); + ncu = 0; + nru = 0; + if( uneeded==1 ) + { + nru = m; + ncu = minmn; + ae_matrix_set_length(u, nru-1+1, ncu-1+1, _state); + } + if( uneeded==2 ) + { + nru = m; + ncu = m; + ae_matrix_set_length(u, nru-1+1, ncu-1+1, _state); + } + nrvt = 0; + ncvt = 0; + if( vtneeded==1 ) + { + nrvt = minmn; + ncvt = n; + ae_matrix_set_length(vt, nrvt-1+1, ncvt-1+1, _state); + } + if( vtneeded==2 ) + { + nrvt = n; + ncvt = n; + ae_matrix_set_length(vt, nrvt-1+1, ncvt-1+1, _state); + } + + /* + * M much larger than N + * Use bidiagonal reduction with QR-decomposition + */ + if( ae_fp_greater(m,1.6*n) ) + { + if( uneeded==0 ) + { + + /* + * No left singular vectors to be computed + */ + rmatrixqr(a, m, n, &tau, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rmatrixbd(a, n, n, &tauq, &taup, _state); + rmatrixbdunpackpt(a, n, n, &taup, nrvt, vt, _state); + rmatrixbdunpackdiagonals(a, n, n, &isupper, w, &e, _state); + result = rmatrixbdsvd(w, &e, n, isupper, ae_false, u, 0, a, 0, vt, ncvt, _state); + ae_frame_leave(_state); + return result; + } + else + { + + /* + * Left singular vectors (may be full matrix U) to be computed + */ + rmatrixqr(a, m, n, &tau, _state); + rmatrixqrunpackq(a, m, n, &tau, ncu, u, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rmatrixbd(a, n, n, &tauq, &taup, _state); + rmatrixbdunpackpt(a, n, n, &taup, nrvt, vt, _state); + rmatrixbdunpackdiagonals(a, n, n, &isupper, w, &e, _state); + if( additionalmemory<1 ) + { + + /* + * No additional memory can be used + */ + rmatrixbdmultiplybyq(a, n, n, &tauq, u, m, n, ae_true, ae_false, _state); + result = rmatrixbdsvd(w, &e, n, isupper, ae_false, u, m, a, 0, vt, ncvt, _state); + } + else + { + + /* + * Large U. Transforming intermediate matrix T2 + */ + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + rmatrixbdunpackq(a, n, n, &tauq, n, &t2, _state); + copymatrix(u, 0, m-1, 0, n-1, a, 0, m-1, 0, n-1, _state); + inplacetranspose(&t2, 0, n-1, 0, n-1, &work, _state); + result = rmatrixbdsvd(w, &e, n, isupper, ae_false, u, 0, &t2, n, vt, ncvt, _state); + matrixmatrixmultiply(a, 0, m-1, 0, n-1, ae_false, &t2, 0, n-1, 0, n-1, ae_true, 1.0, u, 0, m-1, 0, n-1, 0.0, &work, _state); + } + ae_frame_leave(_state); + return result; + } + } + + /* + * N much larger than M + * Use bidiagonal reduction with LQ-decomposition + */ + if( ae_fp_greater(n,1.6*m) ) + { + if( vtneeded==0 ) + { + + /* + * No right singular vectors to be computed + */ + rmatrixlq(a, m, n, &tau, _state); + for(i=0; i<=m-1; i++) + { + for(j=i+1; j<=m-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rmatrixbd(a, m, m, &tauq, &taup, _state); + rmatrixbdunpackq(a, m, m, &tauq, ncu, u, _state); + rmatrixbdunpackdiagonals(a, m, m, &isupper, w, &e, _state); + ae_vector_set_length(&work, m+1, _state); + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + result = rmatrixbdsvd(w, &e, m, isupper, ae_false, a, 0, u, nru, vt, 0, _state); + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + ae_frame_leave(_state); + return result; + } + else + { + + /* + * Right singular vectors (may be full matrix VT) to be computed + */ + rmatrixlq(a, m, n, &tau, _state); + rmatrixlqunpackq(a, m, n, &tau, nrvt, vt, _state); + for(i=0; i<=m-1; i++) + { + for(j=i+1; j<=m-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rmatrixbd(a, m, m, &tauq, &taup, _state); + rmatrixbdunpackq(a, m, m, &tauq, ncu, u, _state); + rmatrixbdunpackdiagonals(a, m, m, &isupper, w, &e, _state); + ae_vector_set_length(&work, ae_maxint(m, n, _state)+1, _state); + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + if( additionalmemory<1 ) + { + + /* + * No additional memory available + */ + rmatrixbdmultiplybyp(a, m, m, &taup, vt, m, n, ae_false, ae_true, _state); + result = rmatrixbdsvd(w, &e, m, isupper, ae_false, a, 0, u, nru, vt, n, _state); + } + else + { + + /* + * Large VT. Transforming intermediate matrix T2 + */ + rmatrixbdunpackpt(a, m, m, &taup, m, &t2, _state); + result = rmatrixbdsvd(w, &e, m, isupper, ae_false, a, 0, u, nru, &t2, m, _state); + copymatrix(vt, 0, m-1, 0, n-1, a, 0, m-1, 0, n-1, _state); + matrixmatrixmultiply(&t2, 0, m-1, 0, m-1, ae_false, a, 0, m-1, 0, n-1, ae_false, 1.0, vt, 0, m-1, 0, n-1, 0.0, &work, _state); + } + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + ae_frame_leave(_state); + return result; + } + } + + /* + * M<=N + * We can use inplace transposition of U to get rid of columnwise operations + */ + if( m<=n ) + { + rmatrixbd(a, m, n, &tauq, &taup, _state); + rmatrixbdunpackq(a, m, n, &tauq, ncu, u, _state); + rmatrixbdunpackpt(a, m, n, &taup, nrvt, vt, _state); + rmatrixbdunpackdiagonals(a, m, n, &isupper, w, &e, _state); + ae_vector_set_length(&work, m+1, _state); + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + result = rmatrixbdsvd(w, &e, minmn, isupper, ae_false, a, 0, u, nru, vt, ncvt, _state); + inplacetranspose(u, 0, nru-1, 0, ncu-1, &work, _state); + ae_frame_leave(_state); + return result; + } + + /* + * Simple bidiagonal reduction + */ + rmatrixbd(a, m, n, &tauq, &taup, _state); + rmatrixbdunpackq(a, m, n, &tauq, ncu, u, _state); + rmatrixbdunpackpt(a, m, n, &taup, nrvt, vt, _state); + rmatrixbdunpackdiagonals(a, m, n, &isupper, w, &e, _state); + if( additionalmemory<2||uneeded==0 ) + { + + /* + * We cant use additional memory or there is no need in such operations + */ + result = rmatrixbdsvd(w, &e, minmn, isupper, ae_false, u, nru, a, 0, vt, ncvt, _state); + } + else + { + + /* + * We can use additional memory + */ + ae_matrix_set_length(&t2, minmn-1+1, m-1+1, _state); + copyandtranspose(u, 0, m-1, 0, minmn-1, &t2, 0, minmn-1, 0, m-1, _state); + result = rmatrixbdsvd(w, &e, minmn, isupper, ae_false, u, 0, &t2, m, vt, ncvt, _state); + copyandtranspose(&t2, 0, minmn-1, 0, m-1, u, 0, m-1, 0, minmn-1, _state); + } + ae_frame_leave(_state); + return result; +} + + + + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a symmetric matrix + +The algorithm finds eigen pairs of a symmetric matrix by reducing it to +tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpper - storage format. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + /* Real */ ae_vector* d, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector tau; + ae_vector e; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(d); + ae_matrix_clear(z); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "SMatrixEVD: incorrect ZNeeded", _state); + smatrixtd(a, n, isupper, &tau, d, &e, _state); + if( zneeded==1 ) + { + smatrixtdunpackq(a, n, isupper, &tau, z, _state); + } + result = smatrixtdevd(d, &e, n, zneeded, z, _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a symmetric +matrix in a given half open interval (A, B] by using a bisection and +inverse iteration + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half open interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval (M>=0). + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, + M is equal to 0. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixevdr(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + double b1, + double b2, + ae_int_t* m, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector tau; + ae_vector e; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + *m = 0; + ae_vector_clear(w); + ae_matrix_clear(z); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "SMatrixTDEVDR: incorrect ZNeeded", _state); + smatrixtd(a, n, isupper, &tau, w, &e, _state); + if( zneeded==1 ) + { + smatrixtdunpackq(a, n, isupper, &tau, z, _state); + } + result = smatrixtdevdr(w, &e, n, zneeded, b1, b2, m, z, _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a symmetric +matrix with given indexes by using bisection and inverse iteration methods. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixevdi(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector tau; + ae_vector e; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(w); + ae_matrix_clear(z); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "SMatrixEVDI: incorrect ZNeeded", _state); + smatrixtd(a, n, isupper, &tau, w, &e, _state); + if( zneeded==1 ) + { + smatrixtdunpackq(a, n, isupper, &tau, z, _state); + } + result = smatrixtdevdi(w, &e, n, zneeded, i1, i2, z, _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a Hermitian matrix + +The algorithm finds eigen pairs of a Hermitian matrix by reducing it to +real tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Note: + eigenvectors of Hermitian matrix are defined up to multiplication by + a complex number L, such that |L|=1. + + -- ALGLIB -- + Copyright 2005, 23 March 2007 by Bochkanov Sergey +*************************************************************************/ +ae_bool hmatrixevd(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + /* Real */ ae_vector* d, + /* Complex */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector tau; + ae_vector e; + ae_vector work; + ae_matrix t; + ae_matrix q; + ae_int_t i; + ae_int_t k; + double v; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(d); + ae_matrix_clear(z); + ae_vector_init(&tau, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&t, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&q, 0, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "HermitianEVD: incorrect ZNeeded", _state); + + /* + * Reduce to tridiagonal form + */ + hmatrixtd(a, n, isupper, &tau, d, &e, _state); + if( zneeded==1 ) + { + hmatrixtdunpackq(a, n, isupper, &tau, &q, _state); + zneeded = 2; + } + + /* + * TDEVD + */ + result = smatrixtdevd(d, &e, n, zneeded, &t, _state); + + /* + * Eigenvectors are needed + * Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T + */ + if( result&&zneeded!=0 ) + { + ae_vector_set_length(&work, n-1+1, _state); + ae_matrix_set_length(z, n-1+1, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + + /* + * Calculate real part + */ + for(k=0; k<=n-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].x; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,n-1), v); + } + for(k=0; k<=n-1; k++) + { + z->ptr.pp_complex[i][k].x = work.ptr.p_double[k]; + } + + /* + * Calculate imaginary part + */ + for(k=0; k<=n-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].y; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,n-1), v); + } + for(k=0; k<=n-1; k++) + { + z->ptr.pp_complex[i][k].y = work.ptr.p_double[k]; + } + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a Hermitian +matrix in a given half-interval (A, B] by using a bisection and inverse +iteration + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. Array whose indexes range within + [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half-interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval, M>=0 + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, M is + equal to 0. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +ae_bool hmatrixevdr(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + double b1, + double b2, + ae_int_t* m, + /* Real */ ae_vector* w, + /* Complex */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_matrix q; + ae_matrix t; + ae_vector tau; + ae_vector e; + ae_vector work; + ae_int_t i; + ae_int_t k; + double v; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + *m = 0; + ae_vector_clear(w); + ae_matrix_clear(z); + ae_matrix_init(&q, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&t, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tau, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "HermitianEigenValuesAndVectorsInInterval: incorrect ZNeeded", _state); + + /* + * Reduce to tridiagonal form + */ + hmatrixtd(a, n, isupper, &tau, w, &e, _state); + if( zneeded==1 ) + { + hmatrixtdunpackq(a, n, isupper, &tau, &q, _state); + zneeded = 2; + } + + /* + * Bisection and inverse iteration + */ + result = smatrixtdevdr(w, &e, n, zneeded, b1, b2, m, &t, _state); + + /* + * Eigenvectors are needed + * Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T + */ + if( (result&&zneeded!=0)&&*m!=0 ) + { + ae_vector_set_length(&work, *m-1+1, _state); + ae_matrix_set_length(z, n-1+1, *m-1+1, _state); + for(i=0; i<=n-1; i++) + { + + /* + * Calculate real part + */ + for(k=0; k<=*m-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].x; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,*m-1), v); + } + for(k=0; k<=*m-1; k++) + { + z->ptr.pp_complex[i][k].x = work.ptr.p_double[k]; + } + + /* + * Calculate imaginary part + */ + for(k=0; k<=*m-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].y; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,*m-1), v); + } + for(k=0; k<=*m-1; k++) + { + z->ptr.pp_complex[i][k].y = work.ptr.p_double[k]; + } + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a Hermitian +matrix with given indexes by using bisection and inverse iteration methods + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix + columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +ae_bool hmatrixevdi(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* w, + /* Complex */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_matrix q; + ae_matrix t; + ae_vector tau; + ae_vector e; + ae_vector work; + ae_int_t i; + ae_int_t k; + double v; + ae_int_t m; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(w); + ae_matrix_clear(z); + ae_matrix_init(&q, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&t, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tau, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&e, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded==0||zneeded==1, "HermitianEigenValuesAndVectorsByIndexes: incorrect ZNeeded", _state); + + /* + * Reduce to tridiagonal form + */ + hmatrixtd(a, n, isupper, &tau, w, &e, _state); + if( zneeded==1 ) + { + hmatrixtdunpackq(a, n, isupper, &tau, &q, _state); + zneeded = 2; + } + + /* + * Bisection and inverse iteration + */ + result = smatrixtdevdi(w, &e, n, zneeded, i1, i2, &t, _state); + + /* + * Eigenvectors are needed + * Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T + */ + m = i2-i1+1; + if( result&&zneeded!=0 ) + { + ae_vector_set_length(&work, m-1+1, _state); + ae_matrix_set_length(z, n-1+1, m-1+1, _state); + for(i=0; i<=n-1; i++) + { + + /* + * Calculate real part + */ + for(k=0; k<=m-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].x; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,m-1), v); + } + for(k=0; k<=m-1; k++) + { + z->ptr.pp_complex[i][k].x = work.ptr.p_double[k]; + } + + /* + * Calculate imaginary part + */ + for(k=0; k<=m-1; k++) + { + work.ptr.p_double[k] = 0; + } + for(k=0; k<=n-1; k++) + { + v = q.ptr.pp_complex[i][k].y; + ae_v_addd(&work.ptr.p_double[0], 1, &t.ptr.pp_double[k][0], 1, ae_v_len(0,m-1), v); + } + for(k=0; k<=m-1; k++) + { + z->ptr.pp_complex[i][k].y = work.ptr.p_double[k]; + } + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a tridiagonal symmetric matrix + +The algorithm finds the eigen pairs of a tridiagonal symmetric matrix by +using an QL/QR algorithm with implicit shifts. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix + are multiplied by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity + transformation of a symmetric matrix; + * 2, the eigenvectors of a tridiagonal matrix replace the + square matrix Z; + * 3, matrix Z contains the first row of the eigenvectors + matrix. + Z - if ZNeeded=1, Z contains the square matrix by which the + eigenvectors are multiplied. + Array whose indexes range within [0..N-1, 0..N-1]. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the product of a given matrix (from the left) + and the eigenvectors matrix (from the right); + * 2, Z contains the eigenvectors. + * 3, Z contains the first row of the eigenvectors matrix. + If ZNeeded<3, Z is the array whose indexes range within [0..N-1, 0..N-1]. + In that case, the eigenvectors are stored in the matrix columns. + If ZNeeded=3, Z is the array whose indexes range within [0..0, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +ae_bool smatrixtdevd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_vector d1; + ae_vector e1; + ae_matrix z1; + ae_int_t i; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_init(&d1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e1, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z1, 0, 0, DT_REAL, _state, ae_true); + + + /* + * Prepare 1-based task + */ + ae_vector_set_length(&d1, n+1, _state); + ae_vector_set_length(&e1, n+1, _state); + ae_v_move(&d1.ptr.p_double[1], 1, &d->ptr.p_double[0], 1, ae_v_len(1,n)); + if( n>1 ) + { + ae_v_move(&e1.ptr.p_double[1], 1, &e->ptr.p_double[0], 1, ae_v_len(1,n-1)); + } + if( zneeded==1 ) + { + ae_matrix_set_length(&z1, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&z1.ptr.pp_double[i][1], 1, &z->ptr.pp_double[i-1][0], 1, ae_v_len(1,n)); + } + } + + /* + * Solve 1-based task + */ + result = evd_tridiagonalevd(&d1, &e1, n, zneeded, &z1, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + + /* + * Convert back to 0-based result + */ + ae_v_move(&d->ptr.p_double[0], 1, &d1.ptr.p_double[1], 1, ae_v_len(0,n-1)); + if( zneeded!=0 ) + { + if( zneeded==1 ) + { + for(i=1; i<=n; i++) + { + ae_v_move(&z->ptr.pp_double[i-1][0], 1, &z1.ptr.pp_double[i][1], 1, ae_v_len(0,n-1)); + } + ae_frame_leave(_state); + return result; + } + if( zneeded==2 ) + { + ae_matrix_set_length(z, n-1+1, n-1+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&z->ptr.pp_double[i-1][0], 1, &z1.ptr.pp_double[i][1], 1, ae_v_len(0,n-1)); + } + ae_frame_leave(_state); + return result; + } + if( zneeded==3 ) + { + ae_matrix_set_length(z, 0+1, n-1+1, _state); + ae_v_move(&z->ptr.pp_double[0][0], 1, &z1.ptr.pp_double[1][1], 1, ae_v_len(0,n-1)); + ae_frame_leave(_state); + return result; + } + ae_assert(ae_false, "SMatrixTDEVD: Incorrect ZNeeded!", _state); + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding the tridiagonal matrix eigenvalues/vectors in a +given half-interval (A, B] by using bisection and inverse iteration. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix, N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the tridiagonal + matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace matrix Z. + A, B - half-interval (A, B] to search eigenvalues in. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range + within [0..N-1, 0..N-1]) which reduces the given symmetric + matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + M - number of eigenvalues found in the given half-interval (M>=0). + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the + left) and NxM matrix of the eigenvectors found (from the + right). Array whose indexes range within [0..N-1, 0..M-1]. + * 2, contains the matrix of the eigenvectors found. + Array whose indexes range within [0..N-1, 0..M-1]. + +Result: + + True, if successful. In that case, M contains the number of eigenvalues + in the given half-interval (could be equal to 0), D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. In that case, + the eigenvalues and eigenvectors are not returned, M is equal to 0. + + -- ALGLIB -- + Copyright 31.03.2008 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixtdevdr(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + double a, + double b, + ae_int_t* m, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t errorcode; + ae_int_t nsplit; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t cr; + ae_vector iblock; + ae_vector isplit; + ae_vector ifail; + ae_vector d1; + ae_vector e1; + ae_vector w; + ae_matrix z2; + ae_matrix z3; + double v; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + *m = 0; + ae_vector_init(&iblock, 0, DT_INT, _state, ae_true); + ae_vector_init(&isplit, 0, DT_INT, _state, ae_true); + ae_vector_init(&ifail, 0, DT_INT, _state, ae_true); + ae_vector_init(&d1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z2, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z3, 0, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded>=0&&zneeded<=2, "SMatrixTDEVDR: incorrect ZNeeded!", _state); + + /* + * Special cases + */ + if( ae_fp_less_eq(b,a) ) + { + *m = 0; + result = ae_true; + ae_frame_leave(_state); + return result; + } + if( n<=0 ) + { + *m = 0; + result = ae_true; + ae_frame_leave(_state); + return result; + } + + /* + * Copy D,E to D1, E1 + */ + ae_vector_set_length(&d1, n+1, _state); + ae_v_move(&d1.ptr.p_double[1], 1, &d->ptr.p_double[0], 1, ae_v_len(1,n)); + if( n>1 ) + { + ae_vector_set_length(&e1, n-1+1, _state); + ae_v_move(&e1.ptr.p_double[1], 1, &e->ptr.p_double[0], 1, ae_v_len(1,n-1)); + } + + /* + * No eigen vectors + */ + if( zneeded==0 ) + { + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 2, 1, a, b, 0, 0, -1, &w, m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result||*m==0 ) + { + *m = 0; + ae_frame_leave(_state); + return result; + } + ae_vector_set_length(d, *m-1+1, _state); + ae_v_move(&d->ptr.p_double[0], 1, &w.ptr.p_double[1], 1, ae_v_len(0,*m-1)); + ae_frame_leave(_state); + return result; + } + + /* + * Eigen vectors are multiplied by Z + */ + if( zneeded==1 ) + { + + /* + * Find eigen pairs + */ + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 2, 2, a, b, 0, 0, -1, &w, m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result||*m==0 ) + { + *m = 0; + ae_frame_leave(_state); + return result; + } + evd_internaldstein(n, &d1, &e1, *m, &w, &iblock, &isplit, &z2, &ifail, &cr, _state); + if( cr!=0 ) + { + *m = 0; + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Sort eigen values and vectors + */ + for(i=1; i<=*m; i++) + { + k = i; + for(j=i; j<=*m; j++) + { + if( ae_fp_less(w.ptr.p_double[j],w.ptr.p_double[k]) ) + { + k = j; + } + } + v = w.ptr.p_double[i]; + w.ptr.p_double[i] = w.ptr.p_double[k]; + w.ptr.p_double[k] = v; + for(j=1; j<=n; j++) + { + v = z2.ptr.pp_double[j][i]; + z2.ptr.pp_double[j][i] = z2.ptr.pp_double[j][k]; + z2.ptr.pp_double[j][k] = v; + } + } + + /* + * Transform Z2 and overwrite Z + */ + ae_matrix_set_length(&z3, *m+1, n+1, _state); + for(i=1; i<=*m; i++) + { + ae_v_move(&z3.ptr.pp_double[i][1], 1, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(1,n)); + } + for(i=1; i<=n; i++) + { + for(j=1; j<=*m; j++) + { + v = ae_v_dotproduct(&z->ptr.pp_double[i-1][0], 1, &z3.ptr.pp_double[j][1], 1, ae_v_len(0,n-1)); + z2.ptr.pp_double[i][j] = v; + } + } + ae_matrix_set_length(z, n-1+1, *m-1+1, _state); + for(i=1; i<=*m; i++) + { + ae_v_move(&z->ptr.pp_double[0][i-1], z->stride, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(0,n-1)); + } + + /* + * Store W + */ + ae_vector_set_length(d, *m-1+1, _state); + for(i=1; i<=*m; i++) + { + d->ptr.p_double[i-1] = w.ptr.p_double[i]; + } + ae_frame_leave(_state); + return result; + } + + /* + * Eigen vectors are stored in Z + */ + if( zneeded==2 ) + { + + /* + * Find eigen pairs + */ + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 2, 2, a, b, 0, 0, -1, &w, m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result||*m==0 ) + { + *m = 0; + ae_frame_leave(_state); + return result; + } + evd_internaldstein(n, &d1, &e1, *m, &w, &iblock, &isplit, &z2, &ifail, &cr, _state); + if( cr!=0 ) + { + *m = 0; + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Sort eigen values and vectors + */ + for(i=1; i<=*m; i++) + { + k = i; + for(j=i; j<=*m; j++) + { + if( ae_fp_less(w.ptr.p_double[j],w.ptr.p_double[k]) ) + { + k = j; + } + } + v = w.ptr.p_double[i]; + w.ptr.p_double[i] = w.ptr.p_double[k]; + w.ptr.p_double[k] = v; + for(j=1; j<=n; j++) + { + v = z2.ptr.pp_double[j][i]; + z2.ptr.pp_double[j][i] = z2.ptr.pp_double[j][k]; + z2.ptr.pp_double[j][k] = v; + } + } + + /* + * Store W + */ + ae_vector_set_length(d, *m-1+1, _state); + for(i=1; i<=*m; i++) + { + d->ptr.p_double[i-1] = w.ptr.p_double[i]; + } + ae_matrix_set_length(z, n-1+1, *m-1+1, _state); + for(i=1; i<=*m; i++) + { + ae_v_move(&z->ptr.pp_double[0][i-1], z->stride, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(0,n-1)); + } + ae_frame_leave(_state); + return result; + } + result = ae_false; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Subroutine for finding tridiagonal matrix eigenvalues/vectors with given +indexes (in ascending order) by using the bisection and inverse iteraion. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix. N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace + matrix Z. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range within [0..N-1, 0..N-1]) + which reduces the given symmetric matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the left) and + Nx(I2-I1) matrix of the eigenvectors found (from the right). + Array whose indexes range within [0..N-1, 0..I2-I1]. + * 2, contains the matrix of the eigenvalues found. + Array whose indexes range within [0..N-1, 0..I2-I1]. + + +Result: + + True, if successful. In that case, D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the eigenvalues + in the given interval or if the inverse iteration subroutine wasn't able + to find all the corresponding eigenvectors. In that case, the eigenvalues + and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 25.12.2005 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixtdevdi(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t errorcode; + ae_int_t nsplit; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t m; + ae_int_t cr; + ae_vector iblock; + ae_vector isplit; + ae_vector ifail; + ae_vector w; + ae_vector d1; + ae_vector e1; + ae_matrix z2; + ae_matrix z3; + double v; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&iblock, 0, DT_INT, _state, ae_true); + ae_vector_init(&isplit, 0, DT_INT, _state, ae_true); + ae_vector_init(&ifail, 0, DT_INT, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&d1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&e1, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z2, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&z3, 0, 0, DT_REAL, _state, ae_true); + + ae_assert((0<=i1&&i1<=i2)&&i2ptr.p_double[0], 1, ae_v_len(1,n)); + if( n>1 ) + { + ae_vector_set_length(&e1, n-1+1, _state); + ae_v_move(&e1.ptr.p_double[1], 1, &e->ptr.p_double[0], 1, ae_v_len(1,n-1)); + } + + /* + * No eigen vectors + */ + if( zneeded==0 ) + { + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 3, 1, 0, 0, i1+1, i2+1, -1, &w, &m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + if( m!=i2-i1+1 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + ae_vector_set_length(d, m-1+1, _state); + for(i=1; i<=m; i++) + { + d->ptr.p_double[i-1] = w.ptr.p_double[i]; + } + ae_frame_leave(_state); + return result; + } + + /* + * Eigen vectors are multiplied by Z + */ + if( zneeded==1 ) + { + + /* + * Find eigen pairs + */ + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 3, 2, 0, 0, i1+1, i2+1, -1, &w, &m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + if( m!=i2-i1+1 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + evd_internaldstein(n, &d1, &e1, m, &w, &iblock, &isplit, &z2, &ifail, &cr, _state); + if( cr!=0 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Sort eigen values and vectors + */ + for(i=1; i<=m; i++) + { + k = i; + for(j=i; j<=m; j++) + { + if( ae_fp_less(w.ptr.p_double[j],w.ptr.p_double[k]) ) + { + k = j; + } + } + v = w.ptr.p_double[i]; + w.ptr.p_double[i] = w.ptr.p_double[k]; + w.ptr.p_double[k] = v; + for(j=1; j<=n; j++) + { + v = z2.ptr.pp_double[j][i]; + z2.ptr.pp_double[j][i] = z2.ptr.pp_double[j][k]; + z2.ptr.pp_double[j][k] = v; + } + } + + /* + * Transform Z2 and overwrite Z + */ + ae_matrix_set_length(&z3, m+1, n+1, _state); + for(i=1; i<=m; i++) + { + ae_v_move(&z3.ptr.pp_double[i][1], 1, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(1,n)); + } + for(i=1; i<=n; i++) + { + for(j=1; j<=m; j++) + { + v = ae_v_dotproduct(&z->ptr.pp_double[i-1][0], 1, &z3.ptr.pp_double[j][1], 1, ae_v_len(0,n-1)); + z2.ptr.pp_double[i][j] = v; + } + } + ae_matrix_set_length(z, n-1+1, m-1+1, _state); + for(i=1; i<=m; i++) + { + ae_v_move(&z->ptr.pp_double[0][i-1], z->stride, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(0,n-1)); + } + + /* + * Store W + */ + ae_vector_set_length(d, m-1+1, _state); + for(i=1; i<=m; i++) + { + d->ptr.p_double[i-1] = w.ptr.p_double[i]; + } + ae_frame_leave(_state); + return result; + } + + /* + * Eigen vectors are stored in Z + */ + if( zneeded==2 ) + { + + /* + * Find eigen pairs + */ + result = evd_internalbisectioneigenvalues(&d1, &e1, n, 3, 2, 0, 0, i1+1, i2+1, -1, &w, &m, &nsplit, &iblock, &isplit, &errorcode, _state); + if( !result ) + { + ae_frame_leave(_state); + return result; + } + if( m!=i2-i1+1 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + evd_internaldstein(n, &d1, &e1, m, &w, &iblock, &isplit, &z2, &ifail, &cr, _state); + if( cr!=0 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Sort eigen values and vectors + */ + for(i=1; i<=m; i++) + { + k = i; + for(j=i; j<=m; j++) + { + if( ae_fp_less(w.ptr.p_double[j],w.ptr.p_double[k]) ) + { + k = j; + } + } + v = w.ptr.p_double[i]; + w.ptr.p_double[i] = w.ptr.p_double[k]; + w.ptr.p_double[k] = v; + for(j=1; j<=n; j++) + { + v = z2.ptr.pp_double[j][i]; + z2.ptr.pp_double[j][i] = z2.ptr.pp_double[j][k]; + z2.ptr.pp_double[j][k] = v; + } + } + + /* + * Store Z + */ + ae_matrix_set_length(z, n-1+1, m-1+1, _state); + for(i=1; i<=m; i++) + { + ae_v_move(&z->ptr.pp_double[0][i-1], z->stride, &z2.ptr.pp_double[1][i], z2.stride, ae_v_len(0,n-1)); + } + + /* + * Store W + */ + ae_vector_set_length(d, m-1+1, _state); + for(i=1; i<=m; i++) + { + d->ptr.p_double[i-1] = w.ptr.p_double[i]; + } + ae_frame_leave(_state); + return result; + } + result = ae_false; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Finding eigenvalues and eigenvectors of a general matrix + +The algorithm finds eigenvalues and eigenvectors of a general matrix by +using the QR algorithm with multiple shifts. The algorithm can find +eigenvalues and both left and right eigenvectors. + +The right eigenvector is a vector x such that A*x = w*x, and the left +eigenvector is a vector y such that y'*A = w*y' (here y' implies a complex +conjugate transposition of vector y). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + VNeeded - flag controlling whether eigenvectors are needed or not. + If VNeeded is equal to: + * 0, eigenvectors are not returned; + * 1, right eigenvectors are returned; + * 2, left eigenvectors are returned; + * 3, both left and right eigenvectors are returned. + +Output parameters: + WR - real parts of eigenvalues. + Array whose index ranges within [0..N-1]. + WR - imaginary parts of eigenvalues. + Array whose index ranges within [0..N-1]. + VL, VR - arrays of left and right eigenvectors (if they are needed). + If WI[i]=0, the respective eigenvalue is a real number, + and it corresponds to the column number I of matrices VL/VR. + If WI[i]>0, we have a pair of complex conjugate numbers with + positive and negative imaginary parts: + the first eigenvalue WR[i] + sqrt(-1)*WI[i]; + the second eigenvalue WR[i+1] + sqrt(-1)*WI[i+1]; + WI[i]>0 + WI[i+1] = -WI[i] < 0 + In that case, the eigenvector corresponding to the first + eigenvalue is located in i and i+1 columns of matrices + VL/VR (the column number i contains the real part, and the + column number i+1 contains the imaginary part), and the vector + corresponding to the second eigenvalue is a complex conjugate to + the first vector. + Arrays whose indexes range within [0..N-1, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm has not converged. + +Note 1: + Some users may ask the following question: what if WI[N-1]>0? + WI[N] must contain an eigenvalue which is complex conjugate to the + N-th eigenvalue, but the array has only size N? + The answer is as follows: such a situation cannot occur because the + algorithm finds a pairs of eigenvalues, therefore, if WI[i]>0, I is + strictly less than N-1. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms of linear algebra). If you require maximum performance + on your machine, it is recommended to adjust this parameter manually. + + +See also the InternalTREVC subroutine. + +The algorithm is based on the LAPACK 3.0 library. +*************************************************************************/ +ae_bool rmatrixevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t vneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_matrix a1; + ae_matrix vl1; + ae_matrix vr1; + ae_vector wr1; + ae_vector wi1; + ae_int_t i; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(wr); + ae_vector_clear(wi); + ae_matrix_clear(vl); + ae_matrix_clear(vr); + ae_matrix_init(&a1, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vl1, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vr1, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wr1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wi1, 0, DT_REAL, _state, ae_true); + + ae_assert(vneeded>=0&&vneeded<=3, "RMatrixEVD: incorrect VNeeded!", _state); + ae_matrix_set_length(&a1, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&a1.ptr.pp_double[i][1], 1, &a->ptr.pp_double[i-1][0], 1, ae_v_len(1,n)); + } + result = evd_nonsymmetricevd(&a1, n, vneeded, &wr1, &wi1, &vl1, &vr1, _state); + if( result ) + { + ae_vector_set_length(wr, n-1+1, _state); + ae_vector_set_length(wi, n-1+1, _state); + ae_v_move(&wr->ptr.p_double[0], 1, &wr1.ptr.p_double[1], 1, ae_v_len(0,n-1)); + ae_v_move(&wi->ptr.p_double[0], 1, &wi1.ptr.p_double[1], 1, ae_v_len(0,n-1)); + if( vneeded==2||vneeded==3 ) + { + ae_matrix_set_length(vl, n-1+1, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + ae_v_move(&vl->ptr.pp_double[i][0], 1, &vl1.ptr.pp_double[i+1][1], 1, ae_v_len(0,n-1)); + } + } + if( vneeded==1||vneeded==3 ) + { + ae_matrix_set_length(vr, n-1+1, n-1+1, _state); + for(i=0; i<=n-1; i++) + { + ae_v_move(&vr->ptr.pp_double[i][0], 1, &vr1.ptr.pp_double[i+1][1], 1, ae_v_len(0,n-1)); + } + } + } + ae_frame_leave(_state); + return result; +} + + +static ae_bool evd_tridiagonalevd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + /* Real */ ae_matrix* z, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_int_t maxit; + ae_int_t i; + ae_int_t ii; + ae_int_t iscale; + ae_int_t j; + ae_int_t jtot; + ae_int_t k; + ae_int_t t; + ae_int_t l; + ae_int_t l1; + ae_int_t lend; + ae_int_t lendm1; + ae_int_t lendp1; + ae_int_t lendsv; + ae_int_t lm1; + ae_int_t lsv; + ae_int_t m; + ae_int_t mm1; + ae_int_t nm1; + ae_int_t nmaxit; + ae_int_t tmpint; + double anorm; + double b; + double c; + double eps; + double eps2; + double f; + double g; + double p; + double r; + double rt1; + double rt2; + double s; + double safmax; + double safmin; + double ssfmax; + double ssfmin; + double tst; + double tmp; + ae_vector work1; + ae_vector work2; + ae_vector workc; + ae_vector works; + ae_vector wtemp; + ae_bool gotoflag; + ae_int_t zrows; + ae_bool wastranspose; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_init(&work1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&workc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&works, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wtemp, 0, DT_REAL, _state, ae_true); + + ae_assert(zneeded>=0&&zneeded<=3, "TridiagonalEVD: Incorrent ZNeeded", _state); + + /* + * Quick return if possible + */ + if( zneeded<0||zneeded>3 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + result = ae_true; + if( n==0 ) + { + ae_frame_leave(_state); + return result; + } + if( n==1 ) + { + if( zneeded==2||zneeded==3 ) + { + ae_matrix_set_length(z, 1+1, 1+1, _state); + z->ptr.pp_double[1][1] = 1; + } + ae_frame_leave(_state); + return result; + } + maxit = 30; + + /* + * Initialize arrays + */ + ae_vector_set_length(&wtemp, n+1, _state); + ae_vector_set_length(&work1, n-1+1, _state); + ae_vector_set_length(&work2, n-1+1, _state); + ae_vector_set_length(&workc, n+1, _state); + ae_vector_set_length(&works, n+1, _state); + + /* + * Determine the unit roundoff and over/underflow thresholds. + */ + eps = ae_machineepsilon; + eps2 = ae_sqr(eps, _state); + safmin = ae_minrealnumber; + safmax = ae_maxrealnumber; + ssfmax = ae_sqrt(safmax, _state)/3; + ssfmin = ae_sqrt(safmin, _state)/eps2; + + /* + * Prepare Z + * + * Here we are using transposition to get rid of column operations + * + */ + wastranspose = ae_false; + zrows = 0; + if( zneeded==1 ) + { + zrows = n; + } + if( zneeded==2 ) + { + zrows = n; + } + if( zneeded==3 ) + { + zrows = 1; + } + if( zneeded==1 ) + { + wastranspose = ae_true; + inplacetranspose(z, 1, n, 1, n, &wtemp, _state); + } + if( zneeded==2 ) + { + wastranspose = ae_true; + ae_matrix_set_length(z, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + for(j=1; j<=n; j++) + { + if( i==j ) + { + z->ptr.pp_double[i][j] = 1; + } + else + { + z->ptr.pp_double[i][j] = 0; + } + } + } + } + if( zneeded==3 ) + { + wastranspose = ae_false; + ae_matrix_set_length(z, 1+1, n+1, _state); + for(j=1; j<=n; j++) + { + if( j==1 ) + { + z->ptr.pp_double[1][j] = 1; + } + else + { + z->ptr.pp_double[1][j] = 0; + } + } + } + nmaxit = n*maxit; + jtot = 0; + + /* + * Determine where the matrix splits and choose QL or QR iteration + * for each block, according to whether top or bottom diagonal + * element is smaller. + */ + l1 = 1; + nm1 = n-1; + for(;;) + { + if( l1>n ) + { + break; + } + if( l1>1 ) + { + e->ptr.p_double[l1-1] = 0; + } + gotoflag = ae_false; + m = l1; + if( l1<=nm1 ) + { + for(m=l1; m<=nm1; m++) + { + tst = ae_fabs(e->ptr.p_double[m], _state); + if( ae_fp_eq(tst,0) ) + { + gotoflag = ae_true; + break; + } + if( ae_fp_less_eq(tst,ae_sqrt(ae_fabs(d->ptr.p_double[m], _state), _state)*ae_sqrt(ae_fabs(d->ptr.p_double[m+1], _state), _state)*eps) ) + { + e->ptr.p_double[m] = 0; + gotoflag = ae_true; + break; + } + } + } + if( !gotoflag ) + { + m = n; + } + + /* + * label 30: + */ + l = l1; + lsv = l; + lend = m; + lendsv = lend; + l1 = m+1; + if( lend==l ) + { + continue; + } + + /* + * Scale submatrix in rows and columns L to LEND + */ + if( l==lend ) + { + anorm = ae_fabs(d->ptr.p_double[l], _state); + } + else + { + anorm = ae_maxreal(ae_fabs(d->ptr.p_double[l], _state)+ae_fabs(e->ptr.p_double[l], _state), ae_fabs(e->ptr.p_double[lend-1], _state)+ae_fabs(d->ptr.p_double[lend], _state), _state); + for(i=l+1; i<=lend-1; i++) + { + anorm = ae_maxreal(anorm, ae_fabs(d->ptr.p_double[i], _state)+ae_fabs(e->ptr.p_double[i], _state)+ae_fabs(e->ptr.p_double[i-1], _state), _state); + } + } + iscale = 0; + if( ae_fp_eq(anorm,0) ) + { + continue; + } + if( ae_fp_greater(anorm,ssfmax) ) + { + iscale = 1; + tmp = ssfmax/anorm; + tmpint = lend-1; + ae_v_muld(&d->ptr.p_double[l], 1, ae_v_len(l,lend), tmp); + ae_v_muld(&e->ptr.p_double[l], 1, ae_v_len(l,tmpint), tmp); + } + if( ae_fp_less(anorm,ssfmin) ) + { + iscale = 2; + tmp = ssfmin/anorm; + tmpint = lend-1; + ae_v_muld(&d->ptr.p_double[l], 1, ae_v_len(l,lend), tmp); + ae_v_muld(&e->ptr.p_double[l], 1, ae_v_len(l,tmpint), tmp); + } + + /* + * Choose between QL and QR iteration + */ + if( ae_fp_less(ae_fabs(d->ptr.p_double[lend], _state),ae_fabs(d->ptr.p_double[l], _state)) ) + { + lend = lsv; + l = lendsv; + } + if( lend>l ) + { + + /* + * QL Iteration + * + * Look for small subdiagonal element. + */ + for(;;) + { + gotoflag = ae_false; + if( l!=lend ) + { + lendm1 = lend-1; + for(m=l; m<=lendm1; m++) + { + tst = ae_sqr(ae_fabs(e->ptr.p_double[m], _state), _state); + if( ae_fp_less_eq(tst,eps2*ae_fabs(d->ptr.p_double[m], _state)*ae_fabs(d->ptr.p_double[m+1], _state)+safmin) ) + { + gotoflag = ae_true; + break; + } + } + } + if( !gotoflag ) + { + m = lend; + } + if( mptr.p_double[m] = 0; + } + p = d->ptr.p_double[l]; + if( m!=l ) + { + + /* + * If remaining matrix is 2-by-2, use DLAE2 or SLAEV2 + * to compute its eigensystem. + */ + if( m==l+1 ) + { + if( zneeded>0 ) + { + evd_tdevdev2(d->ptr.p_double[l], e->ptr.p_double[l], d->ptr.p_double[l+1], &rt1, &rt2, &c, &s, _state); + work1.ptr.p_double[l] = c; + work2.ptr.p_double[l] = s; + workc.ptr.p_double[1] = work1.ptr.p_double[l]; + works.ptr.p_double[1] = work2.ptr.p_double[l]; + if( !wastranspose ) + { + applyrotationsfromtheright(ae_false, 1, zrows, l, l+1, &workc, &works, z, &wtemp, _state); + } + else + { + applyrotationsfromtheleft(ae_false, l, l+1, 1, zrows, &workc, &works, z, &wtemp, _state); + } + } + else + { + evd_tdevde2(d->ptr.p_double[l], e->ptr.p_double[l], d->ptr.p_double[l+1], &rt1, &rt2, _state); + } + d->ptr.p_double[l] = rt1; + d->ptr.p_double[l+1] = rt2; + e->ptr.p_double[l] = 0; + l = l+2; + if( l<=lend ) + { + continue; + } + + /* + * GOTO 140 + */ + break; + } + if( jtot==nmaxit ) + { + + /* + * GOTO 140 + */ + break; + } + jtot = jtot+1; + + /* + * Form shift. + */ + g = (d->ptr.p_double[l+1]-p)/(2*e->ptr.p_double[l]); + r = evd_tdevdpythag(g, 1, _state); + g = d->ptr.p_double[m]-p+e->ptr.p_double[l]/(g+evd_tdevdextsign(r, g, _state)); + s = 1; + c = 1; + p = 0; + + /* + * Inner loop + */ + mm1 = m-1; + for(i=mm1; i>=l; i--) + { + f = s*e->ptr.p_double[i]; + b = c*e->ptr.p_double[i]; + generaterotation(g, f, &c, &s, &r, _state); + if( i!=m-1 ) + { + e->ptr.p_double[i+1] = r; + } + g = d->ptr.p_double[i+1]-p; + r = (d->ptr.p_double[i]-g)*s+2*c*b; + p = s*r; + d->ptr.p_double[i+1] = g+p; + g = c*r-b; + + /* + * If eigenvectors are desired, then save rotations. + */ + if( zneeded>0 ) + { + work1.ptr.p_double[i] = c; + work2.ptr.p_double[i] = -s; + } + } + + /* + * If eigenvectors are desired, then apply saved rotations. + */ + if( zneeded>0 ) + { + for(i=l; i<=m-1; i++) + { + workc.ptr.p_double[i-l+1] = work1.ptr.p_double[i]; + works.ptr.p_double[i-l+1] = work2.ptr.p_double[i]; + } + if( !wastranspose ) + { + applyrotationsfromtheright(ae_false, 1, zrows, l, m, &workc, &works, z, &wtemp, _state); + } + else + { + applyrotationsfromtheleft(ae_false, l, m, 1, zrows, &workc, &works, z, &wtemp, _state); + } + } + d->ptr.p_double[l] = d->ptr.p_double[l]-p; + e->ptr.p_double[l] = g; + continue; + } + + /* + * Eigenvalue found. + */ + d->ptr.p_double[l] = p; + l = l+1; + if( l<=lend ) + { + continue; + } + break; + } + } + else + { + + /* + * QR Iteration + * + * Look for small superdiagonal element. + */ + for(;;) + { + gotoflag = ae_false; + if( l!=lend ) + { + lendp1 = lend+1; + for(m=l; m>=lendp1; m--) + { + tst = ae_sqr(ae_fabs(e->ptr.p_double[m-1], _state), _state); + if( ae_fp_less_eq(tst,eps2*ae_fabs(d->ptr.p_double[m], _state)*ae_fabs(d->ptr.p_double[m-1], _state)+safmin) ) + { + gotoflag = ae_true; + break; + } + } + } + if( !gotoflag ) + { + m = lend; + } + if( m>lend ) + { + e->ptr.p_double[m-1] = 0; + } + p = d->ptr.p_double[l]; + if( m!=l ) + { + + /* + * If remaining matrix is 2-by-2, use DLAE2 or SLAEV2 + * to compute its eigensystem. + */ + if( m==l-1 ) + { + if( zneeded>0 ) + { + evd_tdevdev2(d->ptr.p_double[l-1], e->ptr.p_double[l-1], d->ptr.p_double[l], &rt1, &rt2, &c, &s, _state); + work1.ptr.p_double[m] = c; + work2.ptr.p_double[m] = s; + workc.ptr.p_double[1] = c; + works.ptr.p_double[1] = s; + if( !wastranspose ) + { + applyrotationsfromtheright(ae_true, 1, zrows, l-1, l, &workc, &works, z, &wtemp, _state); + } + else + { + applyrotationsfromtheleft(ae_true, l-1, l, 1, zrows, &workc, &works, z, &wtemp, _state); + } + } + else + { + evd_tdevde2(d->ptr.p_double[l-1], e->ptr.p_double[l-1], d->ptr.p_double[l], &rt1, &rt2, _state); + } + d->ptr.p_double[l-1] = rt1; + d->ptr.p_double[l] = rt2; + e->ptr.p_double[l-1] = 0; + l = l-2; + if( l>=lend ) + { + continue; + } + break; + } + if( jtot==nmaxit ) + { + break; + } + jtot = jtot+1; + + /* + * Form shift. + */ + g = (d->ptr.p_double[l-1]-p)/(2*e->ptr.p_double[l-1]); + r = evd_tdevdpythag(g, 1, _state); + g = d->ptr.p_double[m]-p+e->ptr.p_double[l-1]/(g+evd_tdevdextsign(r, g, _state)); + s = 1; + c = 1; + p = 0; + + /* + * Inner loop + */ + lm1 = l-1; + for(i=m; i<=lm1; i++) + { + f = s*e->ptr.p_double[i]; + b = c*e->ptr.p_double[i]; + generaterotation(g, f, &c, &s, &r, _state); + if( i!=m ) + { + e->ptr.p_double[i-1] = r; + } + g = d->ptr.p_double[i]-p; + r = (d->ptr.p_double[i+1]-g)*s+2*c*b; + p = s*r; + d->ptr.p_double[i] = g+p; + g = c*r-b; + + /* + * If eigenvectors are desired, then save rotations. + */ + if( zneeded>0 ) + { + work1.ptr.p_double[i] = c; + work2.ptr.p_double[i] = s; + } + } + + /* + * If eigenvectors are desired, then apply saved rotations. + */ + if( zneeded>0 ) + { + for(i=m; i<=l-1; i++) + { + workc.ptr.p_double[i-m+1] = work1.ptr.p_double[i]; + works.ptr.p_double[i-m+1] = work2.ptr.p_double[i]; + } + if( !wastranspose ) + { + applyrotationsfromtheright(ae_true, 1, zrows, m, l, &workc, &works, z, &wtemp, _state); + } + else + { + applyrotationsfromtheleft(ae_true, m, l, 1, zrows, &workc, &works, z, &wtemp, _state); + } + } + d->ptr.p_double[l] = d->ptr.p_double[l]-p; + e->ptr.p_double[lm1] = g; + continue; + } + + /* + * Eigenvalue found. + */ + d->ptr.p_double[l] = p; + l = l-1; + if( l>=lend ) + { + continue; + } + break; + } + } + + /* + * Undo scaling if necessary + */ + if( iscale==1 ) + { + tmp = anorm/ssfmax; + tmpint = lendsv-1; + ae_v_muld(&d->ptr.p_double[lsv], 1, ae_v_len(lsv,lendsv), tmp); + ae_v_muld(&e->ptr.p_double[lsv], 1, ae_v_len(lsv,tmpint), tmp); + } + if( iscale==2 ) + { + tmp = anorm/ssfmin; + tmpint = lendsv-1; + ae_v_muld(&d->ptr.p_double[lsv], 1, ae_v_len(lsv,lendsv), tmp); + ae_v_muld(&e->ptr.p_double[lsv], 1, ae_v_len(lsv,tmpint), tmp); + } + + /* + * Check for no convergence to an eigenvalue after a total + * of N*MAXIT iterations. + */ + if( jtot>=nmaxit ) + { + result = ae_false; + if( wastranspose ) + { + inplacetranspose(z, 1, n, 1, n, &wtemp, _state); + } + ae_frame_leave(_state); + return result; + } + } + + /* + * Order eigenvalues and eigenvectors. + */ + if( zneeded==0 ) + { + + /* + * Sort + */ + if( n==1 ) + { + ae_frame_leave(_state); + return result; + } + if( n==2 ) + { + if( ae_fp_greater(d->ptr.p_double[1],d->ptr.p_double[2]) ) + { + tmp = d->ptr.p_double[1]; + d->ptr.p_double[1] = d->ptr.p_double[2]; + d->ptr.p_double[2] = tmp; + } + ae_frame_leave(_state); + return result; + } + i = 2; + do + { + t = i; + while(t!=1) + { + k = t/2; + if( ae_fp_greater_eq(d->ptr.p_double[k],d->ptr.p_double[t]) ) + { + t = 1; + } + else + { + tmp = d->ptr.p_double[k]; + d->ptr.p_double[k] = d->ptr.p_double[t]; + d->ptr.p_double[t] = tmp; + t = k; + } + } + i = i+1; + } + while(i<=n); + i = n-1; + do + { + tmp = d->ptr.p_double[i+1]; + d->ptr.p_double[i+1] = d->ptr.p_double[1]; + d->ptr.p_double[1] = tmp; + t = 1; + while(t!=0) + { + k = 2*t; + if( k>i ) + { + t = 0; + } + else + { + if( kptr.p_double[k+1],d->ptr.p_double[k]) ) + { + k = k+1; + } + } + if( ae_fp_greater_eq(d->ptr.p_double[t],d->ptr.p_double[k]) ) + { + t = 0; + } + else + { + tmp = d->ptr.p_double[k]; + d->ptr.p_double[k] = d->ptr.p_double[t]; + d->ptr.p_double[t] = tmp; + t = k; + } + } + } + i = i-1; + } + while(i>=1); + } + else + { + + /* + * Use Selection Sort to minimize swaps of eigenvectors + */ + for(ii=2; ii<=n; ii++) + { + i = ii-1; + k = i; + p = d->ptr.p_double[i]; + for(j=ii; j<=n; j++) + { + if( ae_fp_less(d->ptr.p_double[j],p) ) + { + k = j; + p = d->ptr.p_double[j]; + } + } + if( k!=i ) + { + d->ptr.p_double[k] = d->ptr.p_double[i]; + d->ptr.p_double[i] = p; + if( wastranspose ) + { + ae_v_move(&wtemp.ptr.p_double[1], 1, &z->ptr.pp_double[i][1], 1, ae_v_len(1,n)); + ae_v_move(&z->ptr.pp_double[i][1], 1, &z->ptr.pp_double[k][1], 1, ae_v_len(1,n)); + ae_v_move(&z->ptr.pp_double[k][1], 1, &wtemp.ptr.p_double[1], 1, ae_v_len(1,n)); + } + else + { + ae_v_move(&wtemp.ptr.p_double[1], 1, &z->ptr.pp_double[1][i], z->stride, ae_v_len(1,zrows)); + ae_v_move(&z->ptr.pp_double[1][i], z->stride, &z->ptr.pp_double[1][k], z->stride, ae_v_len(1,zrows)); + ae_v_move(&z->ptr.pp_double[1][k], z->stride, &wtemp.ptr.p_double[1], 1, ae_v_len(1,zrows)); + } + } + } + if( wastranspose ) + { + inplacetranspose(z, 1, n, 1, n, &wtemp, _state); + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +DLAE2 computes the eigenvalues of a 2-by-2 symmetric matrix + [ A B ] + [ B C ]. +On return, RT1 is the eigenvalue of larger absolute value, and RT2 +is the eigenvalue of smaller absolute value. + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +static void evd_tdevde2(double a, + double b, + double c, + double* rt1, + double* rt2, + ae_state *_state) +{ + double ab; + double acmn; + double acmx; + double adf; + double df; + double rt; + double sm; + double tb; + + *rt1 = 0; + *rt2 = 0; + + sm = a+c; + df = a-c; + adf = ae_fabs(df, _state); + tb = b+b; + ab = ae_fabs(tb, _state); + if( ae_fp_greater(ae_fabs(a, _state),ae_fabs(c, _state)) ) + { + acmx = a; + acmn = c; + } + else + { + acmx = c; + acmn = a; + } + if( ae_fp_greater(adf,ab) ) + { + rt = adf*ae_sqrt(1+ae_sqr(ab/adf, _state), _state); + } + else + { + if( ae_fp_less(adf,ab) ) + { + rt = ab*ae_sqrt(1+ae_sqr(adf/ab, _state), _state); + } + else + { + + /* + * Includes case AB=ADF=0 + */ + rt = ab*ae_sqrt(2, _state); + } + } + if( ae_fp_less(sm,0) ) + { + *rt1 = 0.5*(sm-rt); + + /* + * Order of execution important. + * To get fully accurate smaller eigenvalue, + * next line needs to be executed in higher precision. + */ + *rt2 = acmx/(*rt1)*acmn-b/(*rt1)*b; + } + else + { + if( ae_fp_greater(sm,0) ) + { + *rt1 = 0.5*(sm+rt); + + /* + * Order of execution important. + * To get fully accurate smaller eigenvalue, + * next line needs to be executed in higher precision. + */ + *rt2 = acmx/(*rt1)*acmn-b/(*rt1)*b; + } + else + { + + /* + * Includes case RT1 = RT2 = 0 + */ + *rt1 = 0.5*rt; + *rt2 = -0.5*rt; + } + } +} + + +/************************************************************************* +DLAEV2 computes the eigendecomposition of a 2-by-2 symmetric matrix + + [ A B ] + [ B C ]. + +On return, RT1 is the eigenvalue of larger absolute value, RT2 is the +eigenvalue of smaller absolute value, and (CS1,SN1) is the unit right +eigenvector for RT1, giving the decomposition + + [ CS1 SN1 ] [ A B ] [ CS1 -SN1 ] = [ RT1 0 ] + [-SN1 CS1 ] [ B C ] [ SN1 CS1 ] [ 0 RT2 ]. + + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +static void evd_tdevdev2(double a, + double b, + double c, + double* rt1, + double* rt2, + double* cs1, + double* sn1, + ae_state *_state) +{ + ae_int_t sgn1; + ae_int_t sgn2; + double ab; + double acmn; + double acmx; + double acs; + double adf; + double cs; + double ct; + double df; + double rt; + double sm; + double tb; + double tn; + + *rt1 = 0; + *rt2 = 0; + *cs1 = 0; + *sn1 = 0; + + + /* + * Compute the eigenvalues + */ + sm = a+c; + df = a-c; + adf = ae_fabs(df, _state); + tb = b+b; + ab = ae_fabs(tb, _state); + if( ae_fp_greater(ae_fabs(a, _state),ae_fabs(c, _state)) ) + { + acmx = a; + acmn = c; + } + else + { + acmx = c; + acmn = a; + } + if( ae_fp_greater(adf,ab) ) + { + rt = adf*ae_sqrt(1+ae_sqr(ab/adf, _state), _state); + } + else + { + if( ae_fp_less(adf,ab) ) + { + rt = ab*ae_sqrt(1+ae_sqr(adf/ab, _state), _state); + } + else + { + + /* + * Includes case AB=ADF=0 + */ + rt = ab*ae_sqrt(2, _state); + } + } + if( ae_fp_less(sm,0) ) + { + *rt1 = 0.5*(sm-rt); + sgn1 = -1; + + /* + * Order of execution important. + * To get fully accurate smaller eigenvalue, + * next line needs to be executed in higher precision. + */ + *rt2 = acmx/(*rt1)*acmn-b/(*rt1)*b; + } + else + { + if( ae_fp_greater(sm,0) ) + { + *rt1 = 0.5*(sm+rt); + sgn1 = 1; + + /* + * Order of execution important. + * To get fully accurate smaller eigenvalue, + * next line needs to be executed in higher precision. + */ + *rt2 = acmx/(*rt1)*acmn-b/(*rt1)*b; + } + else + { + + /* + * Includes case RT1 = RT2 = 0 + */ + *rt1 = 0.5*rt; + *rt2 = -0.5*rt; + sgn1 = 1; + } + } + + /* + * Compute the eigenvector + */ + if( ae_fp_greater_eq(df,0) ) + { + cs = df+rt; + sgn2 = 1; + } + else + { + cs = df-rt; + sgn2 = -1; + } + acs = ae_fabs(cs, _state); + if( ae_fp_greater(acs,ab) ) + { + ct = -tb/cs; + *sn1 = 1/ae_sqrt(1+ct*ct, _state); + *cs1 = ct*(*sn1); + } + else + { + if( ae_fp_eq(ab,0) ) + { + *cs1 = 1; + *sn1 = 0; + } + else + { + tn = -cs/tb; + *cs1 = 1/ae_sqrt(1+tn*tn, _state); + *sn1 = tn*(*cs1); + } + } + if( sgn1==sgn2 ) + { + tn = *cs1; + *cs1 = -*sn1; + *sn1 = tn; + } +} + + +/************************************************************************* +Internal routine +*************************************************************************/ +static double evd_tdevdpythag(double a, double b, ae_state *_state) +{ + double result; + + + if( ae_fp_less(ae_fabs(a, _state),ae_fabs(b, _state)) ) + { + result = ae_fabs(b, _state)*ae_sqrt(1+ae_sqr(a/b, _state), _state); + } + else + { + result = ae_fabs(a, _state)*ae_sqrt(1+ae_sqr(b/a, _state), _state); + } + return result; +} + + +/************************************************************************* +Internal routine +*************************************************************************/ +static double evd_tdevdextsign(double a, double b, ae_state *_state) +{ + double result; + + + if( ae_fp_greater_eq(b,0) ) + { + result = ae_fabs(a, _state); + } + else + { + result = -ae_fabs(a, _state); + } + return result; +} + + +static ae_bool evd_internalbisectioneigenvalues(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t irange, + ae_int_t iorder, + double vl, + double vu, + ae_int_t il, + ae_int_t iu, + double abstol, + /* Real */ ae_vector* w, + ae_int_t* m, + ae_int_t* nsplit, + /* Integer */ ae_vector* iblock, + /* Integer */ ae_vector* isplit, + ae_int_t* errorcode, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _d; + ae_vector _e; + double fudge; + double relfac; + ae_bool ncnvrg; + ae_bool toofew; + ae_int_t ib; + ae_int_t ibegin; + ae_int_t idiscl; + ae_int_t idiscu; + ae_int_t ie; + ae_int_t iend; + ae_int_t iinfo; + ae_int_t im; + ae_int_t iin; + ae_int_t ioff; + ae_int_t iout; + ae_int_t itmax; + ae_int_t iw; + ae_int_t iwoff; + ae_int_t j; + ae_int_t itmp1; + ae_int_t jb; + ae_int_t jdisc; + ae_int_t je; + ae_int_t nwl; + ae_int_t nwu; + double atoli; + double bnorm; + double gl; + double gu; + double pivmin; + double rtoli; + double safemn; + double tmp1; + double tmp2; + double tnorm; + double ulp; + double wkill; + double wl; + double wlu; + double wu; + double wul; + double scalefactor; + double t; + ae_vector idumma; + ae_vector work; + ae_vector iwork; + ae_vector ia1s2; + ae_vector ra1s2; + ae_matrix ra1s2x2; + ae_matrix ia1s2x2; + ae_vector ra1siin; + ae_vector ra2siin; + ae_vector ra3siin; + ae_vector ra4siin; + ae_matrix ra1siinx2; + ae_matrix ia1siinx2; + ae_vector iworkspace; + ae_vector rworkspace; + ae_int_t tmpi; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_d, d, _state, ae_true); + d = &_d; + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_clear(w); + *m = 0; + *nsplit = 0; + ae_vector_clear(iblock); + ae_vector_clear(isplit); + *errorcode = 0; + ae_vector_init(&idumma, 0, DT_INT, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&iwork, 0, DT_INT, _state, ae_true); + ae_vector_init(&ia1s2, 0, DT_INT, _state, ae_true); + ae_vector_init(&ra1s2, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ra1s2x2, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ia1s2x2, 0, 0, DT_INT, _state, ae_true); + ae_vector_init(&ra1siin, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ra2siin, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ra3siin, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ra4siin, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ra1siinx2, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ia1siinx2, 0, 0, DT_INT, _state, ae_true); + ae_vector_init(&iworkspace, 0, DT_INT, _state, ae_true); + ae_vector_init(&rworkspace, 0, DT_REAL, _state, ae_true); + + + /* + * Quick return if possible + */ + *m = 0; + if( n==0 ) + { + result = ae_true; + ae_frame_leave(_state); + return result; + } + + /* + * Get machine constants + * NB is the minimum vector length for vector bisection, or 0 + * if only scalar is to be done. + */ + fudge = 2; + relfac = 2; + safemn = ae_minrealnumber; + ulp = 2*ae_machineepsilon; + rtoli = ulp*relfac; + ae_vector_set_length(&idumma, 1+1, _state); + ae_vector_set_length(&work, 4*n+1, _state); + ae_vector_set_length(&iwork, 3*n+1, _state); + ae_vector_set_length(w, n+1, _state); + ae_vector_set_length(iblock, n+1, _state); + ae_vector_set_length(isplit, n+1, _state); + ae_vector_set_length(&ia1s2, 2+1, _state); + ae_vector_set_length(&ra1s2, 2+1, _state); + ae_matrix_set_length(&ra1s2x2, 2+1, 2+1, _state); + ae_matrix_set_length(&ia1s2x2, 2+1, 2+1, _state); + ae_vector_set_length(&ra1siin, n+1, _state); + ae_vector_set_length(&ra2siin, n+1, _state); + ae_vector_set_length(&ra3siin, n+1, _state); + ae_vector_set_length(&ra4siin, n+1, _state); + ae_matrix_set_length(&ra1siinx2, n+1, 2+1, _state); + ae_matrix_set_length(&ia1siinx2, n+1, 2+1, _state); + ae_vector_set_length(&iworkspace, n+1, _state); + ae_vector_set_length(&rworkspace, n+1, _state); + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + wlu = 0; + wul = 0; + + /* + * Check for Errors + */ + result = ae_false; + *errorcode = 0; + if( irange<=0||irange>=4 ) + { + *errorcode = -4; + } + if( iorder<=0||iorder>=3 ) + { + *errorcode = -5; + } + if( n<0 ) + { + *errorcode = -3; + } + if( irange==2&&ae_fp_greater_eq(vl,vu) ) + { + *errorcode = -6; + } + if( irange==3&&(il<1||il>ae_maxint(1, n, _state)) ) + { + *errorcode = -8; + } + if( irange==3&&(iun) ) + { + *errorcode = -9; + } + if( *errorcode!=0 ) + { + ae_frame_leave(_state); + return result; + } + + /* + * Initialize error flags + */ + ncnvrg = ae_false; + toofew = ae_false; + + /* + * Simplifications: + */ + if( (irange==3&&il==1)&&iu==n ) + { + irange = 1; + } + + /* + * Special Case when N=1 + */ + if( n==1 ) + { + *nsplit = 1; + isplit->ptr.p_int[1] = 1; + if( irange==2&&(ae_fp_greater_eq(vl,d->ptr.p_double[1])||ae_fp_less(vu,d->ptr.p_double[1])) ) + { + *m = 0; + } + else + { + w->ptr.p_double[1] = d->ptr.p_double[1]; + iblock->ptr.p_int[1] = 1; + *m = 1; + } + result = ae_true; + ae_frame_leave(_state); + return result; + } + + /* + * Scaling + */ + t = ae_fabs(d->ptr.p_double[n], _state); + for(j=1; j<=n-1; j++) + { + t = ae_maxreal(t, ae_fabs(d->ptr.p_double[j], _state), _state); + t = ae_maxreal(t, ae_fabs(e->ptr.p_double[j], _state), _state); + } + scalefactor = 1; + if( ae_fp_neq(t,0) ) + { + if( ae_fp_greater(t,ae_sqrt(ae_sqrt(ae_minrealnumber, _state), _state)*ae_sqrt(ae_maxrealnumber, _state)) ) + { + scalefactor = t; + } + if( ae_fp_less(t,ae_sqrt(ae_sqrt(ae_maxrealnumber, _state), _state)*ae_sqrt(ae_minrealnumber, _state)) ) + { + scalefactor = t; + } + for(j=1; j<=n-1; j++) + { + d->ptr.p_double[j] = d->ptr.p_double[j]/scalefactor; + e->ptr.p_double[j] = e->ptr.p_double[j]/scalefactor; + } + d->ptr.p_double[n] = d->ptr.p_double[n]/scalefactor; + } + + /* + * Compute Splitting Points + */ + *nsplit = 1; + work.ptr.p_double[n] = 0; + pivmin = 1; + for(j=2; j<=n; j++) + { + tmp1 = ae_sqr(e->ptr.p_double[j-1], _state); + if( ae_fp_greater(ae_fabs(d->ptr.p_double[j]*d->ptr.p_double[j-1], _state)*ae_sqr(ulp, _state)+safemn,tmp1) ) + { + isplit->ptr.p_int[*nsplit] = j-1; + *nsplit = *nsplit+1; + work.ptr.p_double[j-1] = 0; + } + else + { + work.ptr.p_double[j-1] = tmp1; + pivmin = ae_maxreal(pivmin, tmp1, _state); + } + } + isplit->ptr.p_int[*nsplit] = n; + pivmin = pivmin*safemn; + + /* + * Compute Interval and ATOLI + */ + if( irange==3 ) + { + + /* + * RANGE='I': Compute the interval containing eigenvalues + * IL through IU. + * + * Compute Gershgorin interval for entire (split) matrix + * and use it as the initial interval + */ + gu = d->ptr.p_double[1]; + gl = d->ptr.p_double[1]; + tmp1 = 0; + for(j=1; j<=n-1; j++) + { + tmp2 = ae_sqrt(work.ptr.p_double[j], _state); + gu = ae_maxreal(gu, d->ptr.p_double[j]+tmp1+tmp2, _state); + gl = ae_minreal(gl, d->ptr.p_double[j]-tmp1-tmp2, _state); + tmp1 = tmp2; + } + gu = ae_maxreal(gu, d->ptr.p_double[n]+tmp1, _state); + gl = ae_minreal(gl, d->ptr.p_double[n]-tmp1, _state); + tnorm = ae_maxreal(ae_fabs(gl, _state), ae_fabs(gu, _state), _state); + gl = gl-fudge*tnorm*ulp*n-fudge*2*pivmin; + gu = gu+fudge*tnorm*ulp*n+fudge*pivmin; + + /* + * Compute Iteration parameters + */ + itmax = ae_iceil((ae_log(tnorm+pivmin, _state)-ae_log(pivmin, _state))/ae_log(2, _state), _state)+2; + if( ae_fp_less_eq(abstol,0) ) + { + atoli = ulp*tnorm; + } + else + { + atoli = abstol; + } + work.ptr.p_double[n+1] = gl; + work.ptr.p_double[n+2] = gl; + work.ptr.p_double[n+3] = gu; + work.ptr.p_double[n+4] = gu; + work.ptr.p_double[n+5] = gl; + work.ptr.p_double[n+6] = gu; + iwork.ptr.p_int[1] = -1; + iwork.ptr.p_int[2] = -1; + iwork.ptr.p_int[3] = n+1; + iwork.ptr.p_int[4] = n+1; + iwork.ptr.p_int[5] = il-1; + iwork.ptr.p_int[6] = iu; + + /* + * Calling DLAEBZ + * + * DLAEBZ( 3, ITMAX, N, 2, 2, NB, ATOLI, RTOLI, PIVMIN, D, E, + * WORK, IWORK( 5 ), WORK( N+1 ), WORK( N+5 ), IOUT, + * IWORK, W, IBLOCK, IINFO ) + */ + ia1s2.ptr.p_int[1] = iwork.ptr.p_int[5]; + ia1s2.ptr.p_int[2] = iwork.ptr.p_int[6]; + ra1s2.ptr.p_double[1] = work.ptr.p_double[n+5]; + ra1s2.ptr.p_double[2] = work.ptr.p_double[n+6]; + ra1s2x2.ptr.pp_double[1][1] = work.ptr.p_double[n+1]; + ra1s2x2.ptr.pp_double[2][1] = work.ptr.p_double[n+2]; + ra1s2x2.ptr.pp_double[1][2] = work.ptr.p_double[n+3]; + ra1s2x2.ptr.pp_double[2][2] = work.ptr.p_double[n+4]; + ia1s2x2.ptr.pp_int[1][1] = iwork.ptr.p_int[1]; + ia1s2x2.ptr.pp_int[2][1] = iwork.ptr.p_int[2]; + ia1s2x2.ptr.pp_int[1][2] = iwork.ptr.p_int[3]; + ia1s2x2.ptr.pp_int[2][2] = iwork.ptr.p_int[4]; + evd_internaldlaebz(3, itmax, n, 2, 2, atoli, rtoli, pivmin, d, e, &work, &ia1s2, &ra1s2x2, &ra1s2, &iout, &ia1s2x2, w, iblock, &iinfo, _state); + iwork.ptr.p_int[5] = ia1s2.ptr.p_int[1]; + iwork.ptr.p_int[6] = ia1s2.ptr.p_int[2]; + work.ptr.p_double[n+5] = ra1s2.ptr.p_double[1]; + work.ptr.p_double[n+6] = ra1s2.ptr.p_double[2]; + work.ptr.p_double[n+1] = ra1s2x2.ptr.pp_double[1][1]; + work.ptr.p_double[n+2] = ra1s2x2.ptr.pp_double[2][1]; + work.ptr.p_double[n+3] = ra1s2x2.ptr.pp_double[1][2]; + work.ptr.p_double[n+4] = ra1s2x2.ptr.pp_double[2][2]; + iwork.ptr.p_int[1] = ia1s2x2.ptr.pp_int[1][1]; + iwork.ptr.p_int[2] = ia1s2x2.ptr.pp_int[2][1]; + iwork.ptr.p_int[3] = ia1s2x2.ptr.pp_int[1][2]; + iwork.ptr.p_int[4] = ia1s2x2.ptr.pp_int[2][2]; + if( iwork.ptr.p_int[6]==iu ) + { + wl = work.ptr.p_double[n+1]; + wlu = work.ptr.p_double[n+3]; + nwl = iwork.ptr.p_int[1]; + wu = work.ptr.p_double[n+4]; + wul = work.ptr.p_double[n+2]; + nwu = iwork.ptr.p_int[4]; + } + else + { + wl = work.ptr.p_double[n+2]; + wlu = work.ptr.p_double[n+4]; + nwl = iwork.ptr.p_int[2]; + wu = work.ptr.p_double[n+3]; + wul = work.ptr.p_double[n+1]; + nwu = iwork.ptr.p_int[3]; + } + if( ((nwl<0||nwl>=n)||nwu<1)||nwu>n ) + { + *errorcode = 4; + result = ae_false; + ae_frame_leave(_state); + return result; + } + } + else + { + + /* + * RANGE='A' or 'V' -- Set ATOLI + */ + tnorm = ae_maxreal(ae_fabs(d->ptr.p_double[1], _state)+ae_fabs(e->ptr.p_double[1], _state), ae_fabs(d->ptr.p_double[n], _state)+ae_fabs(e->ptr.p_double[n-1], _state), _state); + for(j=2; j<=n-1; j++) + { + tnorm = ae_maxreal(tnorm, ae_fabs(d->ptr.p_double[j], _state)+ae_fabs(e->ptr.p_double[j-1], _state)+ae_fabs(e->ptr.p_double[j], _state), _state); + } + if( ae_fp_less_eq(abstol,0) ) + { + atoli = ulp*tnorm; + } + else + { + atoli = abstol; + } + if( irange==2 ) + { + wl = vl; + wu = vu; + } + else + { + wl = 0; + wu = 0; + } + } + + /* + * Find Eigenvalues -- Loop Over Blocks and recompute NWL and NWU. + * NWL accumulates the number of eigenvalues .le. WL, + * NWU accumulates the number of eigenvalues .le. WU + */ + *m = 0; + iend = 0; + *errorcode = 0; + nwl = 0; + nwu = 0; + for(jb=1; jb<=*nsplit; jb++) + { + ioff = iend; + ibegin = ioff+1; + iend = isplit->ptr.p_int[jb]; + iin = iend-ioff; + if( iin==1 ) + { + + /* + * Special Case -- IIN=1 + */ + if( irange==1||ae_fp_greater_eq(wl,d->ptr.p_double[ibegin]-pivmin) ) + { + nwl = nwl+1; + } + if( irange==1||ae_fp_greater_eq(wu,d->ptr.p_double[ibegin]-pivmin) ) + { + nwu = nwu+1; + } + if( irange==1||(ae_fp_less(wl,d->ptr.p_double[ibegin]-pivmin)&&ae_fp_greater_eq(wu,d->ptr.p_double[ibegin]-pivmin)) ) + { + *m = *m+1; + w->ptr.p_double[*m] = d->ptr.p_double[ibegin]; + iblock->ptr.p_int[*m] = jb; + } + } + else + { + + /* + * General Case -- IIN > 1 + * + * Compute Gershgorin Interval + * and use it as the initial interval + */ + gu = d->ptr.p_double[ibegin]; + gl = d->ptr.p_double[ibegin]; + tmp1 = 0; + for(j=ibegin; j<=iend-1; j++) + { + tmp2 = ae_fabs(e->ptr.p_double[j], _state); + gu = ae_maxreal(gu, d->ptr.p_double[j]+tmp1+tmp2, _state); + gl = ae_minreal(gl, d->ptr.p_double[j]-tmp1-tmp2, _state); + tmp1 = tmp2; + } + gu = ae_maxreal(gu, d->ptr.p_double[iend]+tmp1, _state); + gl = ae_minreal(gl, d->ptr.p_double[iend]-tmp1, _state); + bnorm = ae_maxreal(ae_fabs(gl, _state), ae_fabs(gu, _state), _state); + gl = gl-fudge*bnorm*ulp*iin-fudge*pivmin; + gu = gu+fudge*bnorm*ulp*iin+fudge*pivmin; + + /* + * Compute ATOLI for the current submatrix + */ + if( ae_fp_less_eq(abstol,0) ) + { + atoli = ulp*ae_maxreal(ae_fabs(gl, _state), ae_fabs(gu, _state), _state); + } + else + { + atoli = abstol; + } + if( irange>1 ) + { + if( ae_fp_less(gu,wl) ) + { + nwl = nwl+iin; + nwu = nwu+iin; + continue; + } + gl = ae_maxreal(gl, wl, _state); + gu = ae_minreal(gu, wu, _state); + if( ae_fp_greater_eq(gl,gu) ) + { + continue; + } + } + + /* + * Set Up Initial Interval + */ + work.ptr.p_double[n+1] = gl; + work.ptr.p_double[n+iin+1] = gu; + + /* + * Calling DLAEBZ + * + * CALL DLAEBZ( 1, 0, IN, IN, 1, NB, ATOLI, RTOLI, PIVMIN, + * D( IBEGIN ), E( IBEGIN ), WORK( IBEGIN ), + * IDUMMA, WORK( N+1 ), WORK( N+2*IN+1 ), IM, + * IWORK, W( M+1 ), IBLOCK( M+1 ), IINFO ) + */ + for(tmpi=1; tmpi<=iin; tmpi++) + { + ra1siin.ptr.p_double[tmpi] = d->ptr.p_double[ibegin-1+tmpi]; + if( ibegin-1+tmpiptr.p_double[ibegin-1+tmpi]; + } + ra3siin.ptr.p_double[tmpi] = work.ptr.p_double[ibegin-1+tmpi]; + ra1siinx2.ptr.pp_double[tmpi][1] = work.ptr.p_double[n+tmpi]; + ra1siinx2.ptr.pp_double[tmpi][2] = work.ptr.p_double[n+tmpi+iin]; + ra4siin.ptr.p_double[tmpi] = work.ptr.p_double[n+2*iin+tmpi]; + rworkspace.ptr.p_double[tmpi] = w->ptr.p_double[*m+tmpi]; + iworkspace.ptr.p_int[tmpi] = iblock->ptr.p_int[*m+tmpi]; + ia1siinx2.ptr.pp_int[tmpi][1] = iwork.ptr.p_int[tmpi]; + ia1siinx2.ptr.pp_int[tmpi][2] = iwork.ptr.p_int[tmpi+iin]; + } + evd_internaldlaebz(1, 0, iin, iin, 1, atoli, rtoli, pivmin, &ra1siin, &ra2siin, &ra3siin, &idumma, &ra1siinx2, &ra4siin, &im, &ia1siinx2, &rworkspace, &iworkspace, &iinfo, _state); + for(tmpi=1; tmpi<=iin; tmpi++) + { + work.ptr.p_double[n+tmpi] = ra1siinx2.ptr.pp_double[tmpi][1]; + work.ptr.p_double[n+tmpi+iin] = ra1siinx2.ptr.pp_double[tmpi][2]; + work.ptr.p_double[n+2*iin+tmpi] = ra4siin.ptr.p_double[tmpi]; + w->ptr.p_double[*m+tmpi] = rworkspace.ptr.p_double[tmpi]; + iblock->ptr.p_int[*m+tmpi] = iworkspace.ptr.p_int[tmpi]; + iwork.ptr.p_int[tmpi] = ia1siinx2.ptr.pp_int[tmpi][1]; + iwork.ptr.p_int[tmpi+iin] = ia1siinx2.ptr.pp_int[tmpi][2]; + } + nwl = nwl+iwork.ptr.p_int[1]; + nwu = nwu+iwork.ptr.p_int[iin+1]; + iwoff = *m-iwork.ptr.p_int[1]; + + /* + * Compute Eigenvalues + */ + itmax = ae_iceil((ae_log(gu-gl+pivmin, _state)-ae_log(pivmin, _state))/ae_log(2, _state), _state)+2; + + /* + * Calling DLAEBZ + * + *CALL DLAEBZ( 2, ITMAX, IN, IN, 1, NB, ATOLI, RTOLI, PIVMIN, + * D( IBEGIN ), E( IBEGIN ), WORK( IBEGIN ), + * IDUMMA, WORK( N+1 ), WORK( N+2*IN+1 ), IOUT, + * IWORK, W( M+1 ), IBLOCK( M+1 ), IINFO ) + */ + for(tmpi=1; tmpi<=iin; tmpi++) + { + ra1siin.ptr.p_double[tmpi] = d->ptr.p_double[ibegin-1+tmpi]; + if( ibegin-1+tmpiptr.p_double[ibegin-1+tmpi]; + } + ra3siin.ptr.p_double[tmpi] = work.ptr.p_double[ibegin-1+tmpi]; + ra1siinx2.ptr.pp_double[tmpi][1] = work.ptr.p_double[n+tmpi]; + ra1siinx2.ptr.pp_double[tmpi][2] = work.ptr.p_double[n+tmpi+iin]; + ra4siin.ptr.p_double[tmpi] = work.ptr.p_double[n+2*iin+tmpi]; + rworkspace.ptr.p_double[tmpi] = w->ptr.p_double[*m+tmpi]; + iworkspace.ptr.p_int[tmpi] = iblock->ptr.p_int[*m+tmpi]; + ia1siinx2.ptr.pp_int[tmpi][1] = iwork.ptr.p_int[tmpi]; + ia1siinx2.ptr.pp_int[tmpi][2] = iwork.ptr.p_int[tmpi+iin]; + } + evd_internaldlaebz(2, itmax, iin, iin, 1, atoli, rtoli, pivmin, &ra1siin, &ra2siin, &ra3siin, &idumma, &ra1siinx2, &ra4siin, &iout, &ia1siinx2, &rworkspace, &iworkspace, &iinfo, _state); + for(tmpi=1; tmpi<=iin; tmpi++) + { + work.ptr.p_double[n+tmpi] = ra1siinx2.ptr.pp_double[tmpi][1]; + work.ptr.p_double[n+tmpi+iin] = ra1siinx2.ptr.pp_double[tmpi][2]; + work.ptr.p_double[n+2*iin+tmpi] = ra4siin.ptr.p_double[tmpi]; + w->ptr.p_double[*m+tmpi] = rworkspace.ptr.p_double[tmpi]; + iblock->ptr.p_int[*m+tmpi] = iworkspace.ptr.p_int[tmpi]; + iwork.ptr.p_int[tmpi] = ia1siinx2.ptr.pp_int[tmpi][1]; + iwork.ptr.p_int[tmpi+iin] = ia1siinx2.ptr.pp_int[tmpi][2]; + } + + /* + * Copy Eigenvalues Into W and IBLOCK + * Use -JB for block number for unconverged eigenvalues. + */ + for(j=1; j<=iout; j++) + { + tmp1 = 0.5*(work.ptr.p_double[j+n]+work.ptr.p_double[j+iin+n]); + + /* + * Flag non-convergence. + */ + if( j>iout-iinfo ) + { + ncnvrg = ae_true; + ib = -jb; + } + else + { + ib = jb; + } + for(je=iwork.ptr.p_int[j]+1+iwoff; je<=iwork.ptr.p_int[j+iin]+iwoff; je++) + { + w->ptr.p_double[je] = tmp1; + iblock->ptr.p_int[je] = ib; + } + } + *m = *m+im; + } + } + + /* + * If RANGE='I', then (WL,WU) contains eigenvalues NWL+1,...,NWU + * If NWL+1 < IL or NWU > IU, discard extra eigenvalues. + */ + if( irange==3 ) + { + im = 0; + idiscl = il-1-nwl; + idiscu = nwu-iu; + if( idiscl>0||idiscu>0 ) + { + for(je=1; je<=*m; je++) + { + if( ae_fp_less_eq(w->ptr.p_double[je],wlu)&&idiscl>0 ) + { + idiscl = idiscl-1; + } + else + { + if( ae_fp_greater_eq(w->ptr.p_double[je],wul)&&idiscu>0 ) + { + idiscu = idiscu-1; + } + else + { + im = im+1; + w->ptr.p_double[im] = w->ptr.p_double[je]; + iblock->ptr.p_int[im] = iblock->ptr.p_int[je]; + } + } + } + *m = im; + } + if( idiscl>0||idiscu>0 ) + { + + /* + * Code to deal with effects of bad arithmetic: + * Some low eigenvalues to be discarded are not in (WL,WLU], + * or high eigenvalues to be discarded are not in (WUL,WU] + * so just kill off the smallest IDISCL/largest IDISCU + * eigenvalues, by simply finding the smallest/largest + * eigenvalue(s). + * + * (If N(w) is monotone non-decreasing, this should never + * happen.) + */ + if( idiscl>0 ) + { + wkill = wu; + for(jdisc=1; jdisc<=idiscl; jdisc++) + { + iw = 0; + for(je=1; je<=*m; je++) + { + if( iblock->ptr.p_int[je]!=0&&(ae_fp_less(w->ptr.p_double[je],wkill)||iw==0) ) + { + iw = je; + wkill = w->ptr.p_double[je]; + } + } + iblock->ptr.p_int[iw] = 0; + } + } + if( idiscu>0 ) + { + wkill = wl; + for(jdisc=1; jdisc<=idiscu; jdisc++) + { + iw = 0; + for(je=1; je<=*m; je++) + { + if( iblock->ptr.p_int[je]!=0&&(ae_fp_greater(w->ptr.p_double[je],wkill)||iw==0) ) + { + iw = je; + wkill = w->ptr.p_double[je]; + } + } + iblock->ptr.p_int[iw] = 0; + } + } + im = 0; + for(je=1; je<=*m; je++) + { + if( iblock->ptr.p_int[je]!=0 ) + { + im = im+1; + w->ptr.p_double[im] = w->ptr.p_double[je]; + iblock->ptr.p_int[im] = iblock->ptr.p_int[je]; + } + } + *m = im; + } + if( idiscl<0||idiscu<0 ) + { + toofew = ae_true; + } + } + + /* + * If ORDER='B', do nothing -- the eigenvalues are already sorted + * by block. + * If ORDER='E', sort the eigenvalues from smallest to largest + */ + if( iorder==1&&*nsplit>1 ) + { + for(je=1; je<=*m-1; je++) + { + ie = 0; + tmp1 = w->ptr.p_double[je]; + for(j=je+1; j<=*m; j++) + { + if( ae_fp_less(w->ptr.p_double[j],tmp1) ) + { + ie = j; + tmp1 = w->ptr.p_double[j]; + } + } + if( ie!=0 ) + { + itmp1 = iblock->ptr.p_int[ie]; + w->ptr.p_double[ie] = w->ptr.p_double[je]; + iblock->ptr.p_int[ie] = iblock->ptr.p_int[je]; + w->ptr.p_double[je] = tmp1; + iblock->ptr.p_int[je] = itmp1; + } + } + } + for(j=1; j<=*m; j++) + { + w->ptr.p_double[j] = w->ptr.p_double[j]*scalefactor; + } + *errorcode = 0; + if( ncnvrg ) + { + *errorcode = *errorcode+1; + } + if( toofew ) + { + *errorcode = *errorcode+2; + } + result = *errorcode==0; + ae_frame_leave(_state); + return result; +} + + +static void evd_internaldstein(ae_int_t n, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t m, + /* Real */ ae_vector* w, + /* Integer */ ae_vector* iblock, + /* Integer */ ae_vector* isplit, + /* Real */ ae_matrix* z, + /* Integer */ ae_vector* ifail, + ae_int_t* info, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _e; + ae_vector _w; + ae_int_t maxits; + ae_int_t extra; + ae_int_t b1; + ae_int_t blksiz; + ae_int_t bn; + ae_int_t gpind; + ae_int_t i; + ae_int_t iinfo; + ae_int_t its; + ae_int_t j; + ae_int_t j1; + ae_int_t jblk; + ae_int_t jmax; + ae_int_t nblk; + ae_int_t nrmchk; + double dtpcrt; + double eps; + double eps1; + double nrm; + double onenrm; + double ortol; + double pertol; + double scl; + double sep; + double tol; + double xj; + double xjm; + double ztr; + ae_vector work1; + ae_vector work2; + ae_vector work3; + ae_vector work4; + ae_vector work5; + ae_vector iwork; + ae_bool tmpcriterion; + ae_int_t ti; + ae_int_t i1; + ae_int_t i2; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_e, e, _state, ae_true); + e = &_e; + ae_vector_init_copy(&_w, w, _state, ae_true); + w = &_w; + ae_matrix_clear(z); + ae_vector_clear(ifail); + *info = 0; + ae_vector_init(&work1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work3, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work4, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work5, 0, DT_REAL, _state, ae_true); + ae_vector_init(&iwork, 0, DT_INT, _state, ae_true); + + maxits = 5; + extra = 2; + ae_vector_set_length(&work1, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&work2, ae_maxint(n-1, 1, _state)+1, _state); + ae_vector_set_length(&work3, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&work4, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&work5, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(&iwork, ae_maxint(n, 1, _state)+1, _state); + ae_vector_set_length(ifail, ae_maxint(m, 1, _state)+1, _state); + ae_matrix_set_length(z, ae_maxint(n, 1, _state)+1, ae_maxint(m, 1, _state)+1, _state); + + /* + * these initializers are not really necessary, + * but without them compiler complains about uninitialized locals + */ + gpind = 0; + onenrm = 0; + ortol = 0; + dtpcrt = 0; + xjm = 0; + + /* + * Test the input parameters. + */ + *info = 0; + for(i=1; i<=m; i++) + { + ifail->ptr.p_int[i] = 0; + } + if( n<0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( m<0||m>n ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + for(j=2; j<=m; j++) + { + if( iblock->ptr.p_int[j]ptr.p_int[j-1] ) + { + *info = -6; + break; + } + if( iblock->ptr.p_int[j]==iblock->ptr.p_int[j-1]&&ae_fp_less(w->ptr.p_double[j],w->ptr.p_double[j-1]) ) + { + *info = -5; + break; + } + } + if( *info!=0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Quick return if possible + */ + if( n==0||m==0 ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + z->ptr.pp_double[1][1] = 1; + ae_frame_leave(_state); + return; + } + + /* + * Some preparations + */ + ti = n-1; + ae_v_move(&work1.ptr.p_double[1], 1, &e->ptr.p_double[1], 1, ae_v_len(1,ti)); + ae_vector_set_length(e, n+1, _state); + ae_v_move(&e->ptr.p_double[1], 1, &work1.ptr.p_double[1], 1, ae_v_len(1,ti)); + ae_v_move(&work1.ptr.p_double[1], 1, &w->ptr.p_double[1], 1, ae_v_len(1,m)); + ae_vector_set_length(w, n+1, _state); + ae_v_move(&w->ptr.p_double[1], 1, &work1.ptr.p_double[1], 1, ae_v_len(1,m)); + + /* + * Get machine constants. + */ + eps = ae_machineepsilon; + + /* + * Compute eigenvectors of matrix blocks. + */ + j1 = 1; + for(nblk=1; nblk<=iblock->ptr.p_int[m]; nblk++) + { + + /* + * Find starting and ending indices of block nblk. + */ + if( nblk==1 ) + { + b1 = 1; + } + else + { + b1 = isplit->ptr.p_int[nblk-1]+1; + } + bn = isplit->ptr.p_int[nblk]; + blksiz = bn-b1+1; + if( blksiz!=1 ) + { + + /* + * Compute reorthogonalization criterion and stopping criterion. + */ + gpind = b1; + onenrm = ae_fabs(d->ptr.p_double[b1], _state)+ae_fabs(e->ptr.p_double[b1], _state); + onenrm = ae_maxreal(onenrm, ae_fabs(d->ptr.p_double[bn], _state)+ae_fabs(e->ptr.p_double[bn-1], _state), _state); + for(i=b1+1; i<=bn-1; i++) + { + onenrm = ae_maxreal(onenrm, ae_fabs(d->ptr.p_double[i], _state)+ae_fabs(e->ptr.p_double[i-1], _state)+ae_fabs(e->ptr.p_double[i], _state), _state); + } + ortol = 0.001*onenrm; + dtpcrt = ae_sqrt(0.1/blksiz, _state); + } + + /* + * Loop through eigenvalues of block nblk. + */ + jblk = 0; + for(j=j1; j<=m; j++) + { + if( iblock->ptr.p_int[j]!=nblk ) + { + j1 = j; + break; + } + jblk = jblk+1; + xj = w->ptr.p_double[j]; + if( blksiz==1 ) + { + + /* + * Skip all the work if the block size is one. + */ + work1.ptr.p_double[1] = 1; + } + else + { + + /* + * If eigenvalues j and j-1 are too close, add a relatively + * small perturbation. + */ + if( jblk>1 ) + { + eps1 = ae_fabs(eps*xj, _state); + pertol = 10*eps1; + sep = xj-xjm; + if( ae_fp_less(sep,pertol) ) + { + xj = xjm+pertol; + } + } + its = 0; + nrmchk = 0; + + /* + * Get random starting vector. + */ + for(ti=1; ti<=blksiz; ti++) + { + work1.ptr.p_double[ti] = 2*ae_randomreal(_state)-1; + } + + /* + * Copy the matrix T so it won't be destroyed in factorization. + */ + for(ti=1; ti<=blksiz-1; ti++) + { + work2.ptr.p_double[ti] = e->ptr.p_double[b1+ti-1]; + work3.ptr.p_double[ti] = e->ptr.p_double[b1+ti-1]; + work4.ptr.p_double[ti] = d->ptr.p_double[b1+ti-1]; + } + work4.ptr.p_double[blksiz] = d->ptr.p_double[b1+blksiz-1]; + + /* + * Compute LU factors with partial pivoting ( PT = LU ) + */ + tol = 0; + evd_tdininternaldlagtf(blksiz, &work4, xj, &work2, &work3, tol, &work5, &iwork, &iinfo, _state); + + /* + * Update iteration count. + */ + do + { + its = its+1; + if( its>maxits ) + { + + /* + * If stopping criterion was not satisfied, update info and + * store eigenvector number in array ifail. + */ + *info = *info+1; + ifail->ptr.p_int[*info] = j; + break; + } + + /* + * Normalize and scale the righthand side vector Pb. + */ + v = 0; + for(ti=1; ti<=blksiz; ti++) + { + v = v+ae_fabs(work1.ptr.p_double[ti], _state); + } + scl = blksiz*onenrm*ae_maxreal(eps, ae_fabs(work4.ptr.p_double[blksiz], _state), _state)/v; + ae_v_muld(&work1.ptr.p_double[1], 1, ae_v_len(1,blksiz), scl); + + /* + * Solve the system LU = Pb. + */ + evd_tdininternaldlagts(blksiz, &work4, &work2, &work3, &work5, &iwork, &work1, &tol, &iinfo, _state); + + /* + * Reorthogonalize by modified Gram-Schmidt if eigenvalues are + * close enough. + */ + if( jblk!=1 ) + { + if( ae_fp_greater(ae_fabs(xj-xjm, _state),ortol) ) + { + gpind = j; + } + if( gpind!=j ) + { + for(i=gpind; i<=j-1; i++) + { + i1 = b1; + i2 = b1+blksiz-1; + ztr = ae_v_dotproduct(&work1.ptr.p_double[1], 1, &z->ptr.pp_double[i1][i], z->stride, ae_v_len(1,blksiz)); + ae_v_subd(&work1.ptr.p_double[1], 1, &z->ptr.pp_double[i1][i], z->stride, ae_v_len(1,blksiz), ztr); + touchint(&i2, _state); + } + } + } + + /* + * Check the infinity norm of the iterate. + */ + jmax = vectoridxabsmax(&work1, 1, blksiz, _state); + nrm = ae_fabs(work1.ptr.p_double[jmax], _state); + + /* + * Continue for additional iterations after norm reaches + * stopping criterion. + */ + tmpcriterion = ae_false; + if( ae_fp_less(nrm,dtpcrt) ) + { + tmpcriterion = ae_true; + } + else + { + nrmchk = nrmchk+1; + if( nrmchkptr.pp_double[i][j] = 0; + } + for(i=1; i<=blksiz; i++) + { + z->ptr.pp_double[b1+i-1][j] = work1.ptr.p_double[i]; + } + + /* + * Save the shift to check eigenvalue spacing at next + * iteration. + */ + xjm = xj; + } + } + ae_frame_leave(_state); +} + + +static void evd_tdininternaldlagtf(ae_int_t n, + /* Real */ ae_vector* a, + double lambdav, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + double tol, + /* Real */ ae_vector* d, + /* Integer */ ae_vector* iin, + ae_int_t* info, + ae_state *_state) +{ + ae_int_t k; + double eps; + double mult; + double piv1; + double piv2; + double scale1; + double scale2; + double temp; + double tl; + + *info = 0; + + *info = 0; + if( n<0 ) + { + *info = -1; + return; + } + if( n==0 ) + { + return; + } + a->ptr.p_double[1] = a->ptr.p_double[1]-lambdav; + iin->ptr.p_int[n] = 0; + if( n==1 ) + { + if( ae_fp_eq(a->ptr.p_double[1],0) ) + { + iin->ptr.p_int[1] = 1; + } + return; + } + eps = ae_machineepsilon; + tl = ae_maxreal(tol, eps, _state); + scale1 = ae_fabs(a->ptr.p_double[1], _state)+ae_fabs(b->ptr.p_double[1], _state); + for(k=1; k<=n-1; k++) + { + a->ptr.p_double[k+1] = a->ptr.p_double[k+1]-lambdav; + scale2 = ae_fabs(c->ptr.p_double[k], _state)+ae_fabs(a->ptr.p_double[k+1], _state); + if( kptr.p_double[k+1], _state); + } + if( ae_fp_eq(a->ptr.p_double[k],0) ) + { + piv1 = 0; + } + else + { + piv1 = ae_fabs(a->ptr.p_double[k], _state)/scale1; + } + if( ae_fp_eq(c->ptr.p_double[k],0) ) + { + iin->ptr.p_int[k] = 0; + piv2 = 0; + scale1 = scale2; + if( kptr.p_double[k] = 0; + } + } + else + { + piv2 = ae_fabs(c->ptr.p_double[k], _state)/scale2; + if( ae_fp_less_eq(piv2,piv1) ) + { + iin->ptr.p_int[k] = 0; + scale1 = scale2; + c->ptr.p_double[k] = c->ptr.p_double[k]/a->ptr.p_double[k]; + a->ptr.p_double[k+1] = a->ptr.p_double[k+1]-c->ptr.p_double[k]*b->ptr.p_double[k]; + if( kptr.p_double[k] = 0; + } + } + else + { + iin->ptr.p_int[k] = 1; + mult = a->ptr.p_double[k]/c->ptr.p_double[k]; + a->ptr.p_double[k] = c->ptr.p_double[k]; + temp = a->ptr.p_double[k+1]; + a->ptr.p_double[k+1] = b->ptr.p_double[k]-mult*temp; + if( kptr.p_double[k] = b->ptr.p_double[k+1]; + b->ptr.p_double[k+1] = -mult*d->ptr.p_double[k]; + } + b->ptr.p_double[k] = temp; + c->ptr.p_double[k] = mult; + } + } + if( ae_fp_less_eq(ae_maxreal(piv1, piv2, _state),tl)&&iin->ptr.p_int[n]==0 ) + { + iin->ptr.p_int[n] = k; + } + } + if( ae_fp_less_eq(ae_fabs(a->ptr.p_double[n], _state),scale1*tl)&&iin->ptr.p_int[n]==0 ) + { + iin->ptr.p_int[n] = n; + } +} + + +static void evd_tdininternaldlagts(ae_int_t n, + /* Real */ ae_vector* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* c, + /* Real */ ae_vector* d, + /* Integer */ ae_vector* iin, + /* Real */ ae_vector* y, + double* tol, + ae_int_t* info, + ae_state *_state) +{ + ae_int_t k; + double absak; + double ak; + double bignum; + double eps; + double pert; + double sfmin; + double temp; + + *info = 0; + + *info = 0; + if( n<0 ) + { + *info = -1; + return; + } + if( n==0 ) + { + return; + } + eps = ae_machineepsilon; + sfmin = ae_minrealnumber; + bignum = 1/sfmin; + if( ae_fp_less_eq(*tol,0) ) + { + *tol = ae_fabs(a->ptr.p_double[1], _state); + if( n>1 ) + { + *tol = ae_maxreal(*tol, ae_maxreal(ae_fabs(a->ptr.p_double[2], _state), ae_fabs(b->ptr.p_double[1], _state), _state), _state); + } + for(k=3; k<=n; k++) + { + *tol = ae_maxreal(*tol, ae_maxreal(ae_fabs(a->ptr.p_double[k], _state), ae_maxreal(ae_fabs(b->ptr.p_double[k-1], _state), ae_fabs(d->ptr.p_double[k-2], _state), _state), _state), _state); + } + *tol = *tol*eps; + if( ae_fp_eq(*tol,0) ) + { + *tol = eps; + } + } + for(k=2; k<=n; k++) + { + if( iin->ptr.p_int[k-1]==0 ) + { + y->ptr.p_double[k] = y->ptr.p_double[k]-c->ptr.p_double[k-1]*y->ptr.p_double[k-1]; + } + else + { + temp = y->ptr.p_double[k-1]; + y->ptr.p_double[k-1] = y->ptr.p_double[k]; + y->ptr.p_double[k] = temp-c->ptr.p_double[k-1]*y->ptr.p_double[k]; + } + } + for(k=n; k>=1; k--) + { + if( k<=n-2 ) + { + temp = y->ptr.p_double[k]-b->ptr.p_double[k]*y->ptr.p_double[k+1]-d->ptr.p_double[k]*y->ptr.p_double[k+2]; + } + else + { + if( k==n-1 ) + { + temp = y->ptr.p_double[k]-b->ptr.p_double[k]*y->ptr.p_double[k+1]; + } + else + { + temp = y->ptr.p_double[k]; + } + } + ak = a->ptr.p_double[k]; + pert = ae_fabs(*tol, _state); + if( ae_fp_less(ak,0) ) + { + pert = -pert; + } + for(;;) + { + absak = ae_fabs(ak, _state); + if( ae_fp_less(absak,1) ) + { + if( ae_fp_less(absak,sfmin) ) + { + if( ae_fp_eq(absak,0)||ae_fp_greater(ae_fabs(temp, _state)*sfmin,absak) ) + { + ak = ak+pert; + pert = 2*pert; + continue; + } + else + { + temp = temp*bignum; + ak = ak*bignum; + } + } + else + { + if( ae_fp_greater(ae_fabs(temp, _state),absak*bignum) ) + { + ak = ak+pert; + pert = 2*pert; + continue; + } + } + } + break; + } + y->ptr.p_double[k] = temp/ak; + } +} + + +static void evd_internaldlaebz(ae_int_t ijob, + ae_int_t nitmax, + ae_int_t n, + ae_int_t mmax, + ae_int_t minp, + double abstol, + double reltol, + double pivmin, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + /* Real */ ae_vector* e2, + /* Integer */ ae_vector* nval, + /* Real */ ae_matrix* ab, + /* Real */ ae_vector* c, + ae_int_t* mout, + /* Integer */ ae_matrix* nab, + /* Real */ ae_vector* work, + /* Integer */ ae_vector* iwork, + ae_int_t* info, + ae_state *_state) +{ + ae_int_t itmp1; + ae_int_t itmp2; + ae_int_t j; + ae_int_t ji; + ae_int_t jit; + ae_int_t jp; + ae_int_t kf; + ae_int_t kfnew; + ae_int_t kl; + ae_int_t klnew; + double tmp1; + double tmp2; + + *mout = 0; + *info = 0; + + *info = 0; + if( ijob<1||ijob>3 ) + { + *info = -1; + return; + } + + /* + * Initialize NAB + */ + if( ijob==1 ) + { + + /* + * Compute the number of eigenvalues in the initial intervals. + */ + *mout = 0; + + /* + *DIR$ NOVECTOR + */ + for(ji=1; ji<=minp; ji++) + { + for(jp=1; jp<=2; jp++) + { + tmp1 = d->ptr.p_double[1]-ab->ptr.pp_double[ji][jp]; + if( ae_fp_less(ae_fabs(tmp1, _state),pivmin) ) + { + tmp1 = -pivmin; + } + nab->ptr.pp_int[ji][jp] = 0; + if( ae_fp_less_eq(tmp1,0) ) + { + nab->ptr.pp_int[ji][jp] = 1; + } + for(j=2; j<=n; j++) + { + tmp1 = d->ptr.p_double[j]-e2->ptr.p_double[j-1]/tmp1-ab->ptr.pp_double[ji][jp]; + if( ae_fp_less(ae_fabs(tmp1, _state),pivmin) ) + { + tmp1 = -pivmin; + } + if( ae_fp_less_eq(tmp1,0) ) + { + nab->ptr.pp_int[ji][jp] = nab->ptr.pp_int[ji][jp]+1; + } + } + } + *mout = *mout+nab->ptr.pp_int[ji][2]-nab->ptr.pp_int[ji][1]; + } + return; + } + + /* + * Initialize for loop + * + * KF and KL have the following meaning: + * Intervals 1,...,KF-1 have converged. + * Intervals KF,...,KL still need to be refined. + */ + kf = 1; + kl = minp; + + /* + * If IJOB=2, initialize C. + * If IJOB=3, use the user-supplied starting point. + */ + if( ijob==2 ) + { + for(ji=1; ji<=minp; ji++) + { + c->ptr.p_double[ji] = 0.5*(ab->ptr.pp_double[ji][1]+ab->ptr.pp_double[ji][2]); + } + } + + /* + * Iteration loop + */ + for(jit=1; jit<=nitmax; jit++) + { + + /* + * Loop over intervals + * + * + * Serial Version of the loop + */ + klnew = kl; + for(ji=kf; ji<=kl; ji++) + { + + /* + * Compute N(w), the number of eigenvalues less than w + */ + tmp1 = c->ptr.p_double[ji]; + tmp2 = d->ptr.p_double[1]-tmp1; + itmp1 = 0; + if( ae_fp_less_eq(tmp2,pivmin) ) + { + itmp1 = 1; + tmp2 = ae_minreal(tmp2, -pivmin, _state); + } + + /* + * A series of compiler directives to defeat vectorization + * for the next loop + * + **$PL$ CMCHAR=' ' + *CDIR$ NEXTSCALAR + *C$DIR SCALAR + *CDIR$ NEXT SCALAR + *CVD$L NOVECTOR + *CDEC$ NOVECTOR + *CVD$ NOVECTOR + **VDIR NOVECTOR + **VOCL LOOP,SCALAR + *CIBM PREFER SCALAR + **$PL$ CMCHAR='*' + */ + for(j=2; j<=n; j++) + { + tmp2 = d->ptr.p_double[j]-e2->ptr.p_double[j-1]/tmp2-tmp1; + if( ae_fp_less_eq(tmp2,pivmin) ) + { + itmp1 = itmp1+1; + tmp2 = ae_minreal(tmp2, -pivmin, _state); + } + } + if( ijob<=2 ) + { + + /* + * IJOB=2: Choose all intervals containing eigenvalues. + * + * Insure that N(w) is monotone + */ + itmp1 = ae_minint(nab->ptr.pp_int[ji][2], ae_maxint(nab->ptr.pp_int[ji][1], itmp1, _state), _state); + + /* + * Update the Queue -- add intervals if both halves + * contain eigenvalues. + */ + if( itmp1==nab->ptr.pp_int[ji][2] ) + { + + /* + * No eigenvalue in the upper interval: + * just use the lower interval. + */ + ab->ptr.pp_double[ji][2] = tmp1; + } + else + { + if( itmp1==nab->ptr.pp_int[ji][1] ) + { + + /* + * No eigenvalue in the lower interval: + * just use the upper interval. + */ + ab->ptr.pp_double[ji][1] = tmp1; + } + else + { + if( klnewptr.pp_double[klnew][2] = ab->ptr.pp_double[ji][2]; + nab->ptr.pp_int[klnew][2] = nab->ptr.pp_int[ji][2]; + ab->ptr.pp_double[klnew][1] = tmp1; + nab->ptr.pp_int[klnew][1] = itmp1; + ab->ptr.pp_double[ji][2] = tmp1; + nab->ptr.pp_int[ji][2] = itmp1; + } + else + { + *info = mmax+1; + return; + } + } + } + } + else + { + + /* + * IJOB=3: Binary search. Keep only the interval + * containing w s.t. N(w) = NVAL + */ + if( itmp1<=nval->ptr.p_int[ji] ) + { + ab->ptr.pp_double[ji][1] = tmp1; + nab->ptr.pp_int[ji][1] = itmp1; + } + if( itmp1>=nval->ptr.p_int[ji] ) + { + ab->ptr.pp_double[ji][2] = tmp1; + nab->ptr.pp_int[ji][2] = itmp1; + } + } + } + kl = klnew; + + /* + * Check for convergence + */ + kfnew = kf; + for(ji=kf; ji<=kl; ji++) + { + tmp1 = ae_fabs(ab->ptr.pp_double[ji][2]-ab->ptr.pp_double[ji][1], _state); + tmp2 = ae_maxreal(ae_fabs(ab->ptr.pp_double[ji][2], _state), ae_fabs(ab->ptr.pp_double[ji][1], _state), _state); + if( ae_fp_less(tmp1,ae_maxreal(abstol, ae_maxreal(pivmin, reltol*tmp2, _state), _state))||nab->ptr.pp_int[ji][1]>=nab->ptr.pp_int[ji][2] ) + { + + /* + * Converged -- Swap with position KFNEW, + * then increment KFNEW + */ + if( ji>kfnew ) + { + tmp1 = ab->ptr.pp_double[ji][1]; + tmp2 = ab->ptr.pp_double[ji][2]; + itmp1 = nab->ptr.pp_int[ji][1]; + itmp2 = nab->ptr.pp_int[ji][2]; + ab->ptr.pp_double[ji][1] = ab->ptr.pp_double[kfnew][1]; + ab->ptr.pp_double[ji][2] = ab->ptr.pp_double[kfnew][2]; + nab->ptr.pp_int[ji][1] = nab->ptr.pp_int[kfnew][1]; + nab->ptr.pp_int[ji][2] = nab->ptr.pp_int[kfnew][2]; + ab->ptr.pp_double[kfnew][1] = tmp1; + ab->ptr.pp_double[kfnew][2] = tmp2; + nab->ptr.pp_int[kfnew][1] = itmp1; + nab->ptr.pp_int[kfnew][2] = itmp2; + if( ijob==3 ) + { + itmp1 = nval->ptr.p_int[ji]; + nval->ptr.p_int[ji] = nval->ptr.p_int[kfnew]; + nval->ptr.p_int[kfnew] = itmp1; + } + } + kfnew = kfnew+1; + } + } + kf = kfnew; + + /* + * Choose Midpoints + */ + for(ji=kf; ji<=kl; ji++) + { + c->ptr.p_double[ji] = 0.5*(ab->ptr.pp_double[ji][1]+ab->ptr.pp_double[ji][2]); + } + + /* + * If no more intervals to refine, quit. + */ + if( kf>kl ) + { + break; + } + } + + /* + * Converged + */ + *info = ae_maxint(kl+1-kf, 0, _state); + *mout = kl; +} + + +/************************************************************************* +Internal subroutine + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + June 30, 1999 +*************************************************************************/ +static void evd_internaltrevc(/* Real */ ae_matrix* t, + ae_int_t n, + ae_int_t side, + ae_int_t howmny, + /* Boolean */ ae_vector* vselect, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_int_t* m, + ae_int_t* info, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _vselect; + ae_bool allv; + ae_bool bothv; + ae_bool leftv; + ae_bool over; + ae_bool pair; + ae_bool rightv; + ae_bool somev; + ae_int_t i; + ae_int_t ierr; + ae_int_t ii; + ae_int_t ip; + ae_int_t iis; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + ae_int_t jnxt; + ae_int_t k; + ae_int_t ki; + ae_int_t n2; + double beta; + double bignum; + double emax; + double rec; + double remax; + double scl; + double smin; + double smlnum; + double ulp; + double unfl; + double vcrit; + double vmax; + double wi; + double wr; + double xnorm; + ae_matrix x; + ae_vector work; + ae_vector temp; + ae_matrix temp11; + ae_matrix temp22; + ae_matrix temp11b; + ae_matrix temp21b; + ae_matrix temp12b; + ae_matrix temp22b; + ae_bool skipflag; + ae_int_t k1; + ae_int_t k2; + ae_int_t k3; + ae_int_t k4; + double vt; + ae_vector rswap4; + ae_vector zswap4; + ae_matrix ipivot44; + ae_vector civ4; + ae_vector crv4; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_vselect, vselect, _state, ae_true); + vselect = &_vselect; + *m = 0; + *info = 0; + ae_matrix_init(&x, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + ae_vector_init(&temp, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp11, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp22, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp11b, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp21b, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp12b, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&temp22b, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&rswap4, 0, DT_BOOL, _state, ae_true); + ae_vector_init(&zswap4, 0, DT_BOOL, _state, ae_true); + ae_matrix_init(&ipivot44, 0, 0, DT_INT, _state, ae_true); + ae_vector_init(&civ4, 0, DT_REAL, _state, ae_true); + ae_vector_init(&crv4, 0, DT_REAL, _state, ae_true); + + ae_matrix_set_length(&x, 2+1, 2+1, _state); + ae_matrix_set_length(&temp11, 1+1, 1+1, _state); + ae_matrix_set_length(&temp11b, 1+1, 1+1, _state); + ae_matrix_set_length(&temp21b, 2+1, 1+1, _state); + ae_matrix_set_length(&temp12b, 1+1, 2+1, _state); + ae_matrix_set_length(&temp22b, 2+1, 2+1, _state); + ae_matrix_set_length(&temp22, 2+1, 2+1, _state); + ae_vector_set_length(&work, 3*n+1, _state); + ae_vector_set_length(&temp, n+1, _state); + ae_vector_set_length(&rswap4, 4+1, _state); + ae_vector_set_length(&zswap4, 4+1, _state); + ae_matrix_set_length(&ipivot44, 4+1, 4+1, _state); + ae_vector_set_length(&civ4, 4+1, _state); + ae_vector_set_length(&crv4, 4+1, _state); + if( howmny!=1 ) + { + if( side==1||side==3 ) + { + ae_matrix_set_length(vr, n+1, n+1, _state); + } + if( side==2||side==3 ) + { + ae_matrix_set_length(vl, n+1, n+1, _state); + } + } + + /* + * Decode and test the input parameters + */ + bothv = side==3; + rightv = side==1||bothv; + leftv = side==2||bothv; + allv = howmny==2; + over = howmny==1; + somev = howmny==3; + *info = 0; + if( n<0 ) + { + *info = -2; + ae_frame_leave(_state); + return; + } + if( !rightv&&!leftv ) + { + *info = -3; + ae_frame_leave(_state); + return; + } + if( (!allv&&!over)&&!somev ) + { + *info = -4; + ae_frame_leave(_state); + return; + } + + /* + * Set M to the number of columns required to store the selected + * eigenvectors, standardize the array SELECT if necessary, and + * test MM. + */ + if( somev ) + { + *m = 0; + pair = ae_false; + for(j=1; j<=n; j++) + { + if( pair ) + { + pair = ae_false; + vselect->ptr.p_bool[j] = ae_false; + } + else + { + if( jptr.pp_double[j+1][j],0) ) + { + if( vselect->ptr.p_bool[j] ) + { + *m = *m+1; + } + } + else + { + pair = ae_true; + if( vselect->ptr.p_bool[j]||vselect->ptr.p_bool[j+1] ) + { + vselect->ptr.p_bool[j] = ae_true; + *m = *m+2; + } + } + } + else + { + if( vselect->ptr.p_bool[n] ) + { + *m = *m+1; + } + } + } + } + } + else + { + *m = n; + } + + /* + * Quick return if possible. + */ + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Set the constants to control overflow. + */ + unfl = ae_minrealnumber; + ulp = ae_machineepsilon; + smlnum = unfl*(n/ulp); + bignum = (1-ulp)/smlnum; + + /* + * Compute 1-norm of each column of strictly upper triangular + * part of T to control overflow in triangular solver. + */ + work.ptr.p_double[1] = 0; + for(j=2; j<=n; j++) + { + work.ptr.p_double[j] = 0; + for(i=1; i<=j-1; i++) + { + work.ptr.p_double[j] = work.ptr.p_double[j]+ae_fabs(t->ptr.pp_double[i][j], _state); + } + } + + /* + * Index IP is used to specify the real or complex eigenvalue: + * IP = 0, real eigenvalue, + * 1, first of conjugate complex pair: (wr,wi) + * -1, second of conjugate complex pair: (wr,wi) + */ + n2 = 2*n; + if( rightv ) + { + + /* + * Compute right eigenvectors. + */ + ip = 0; + iis = *m; + for(ki=n; ki>=1; ki--) + { + skipflag = ae_false; + if( ip==1 ) + { + skipflag = ae_true; + } + else + { + if( ki!=1 ) + { + if( ae_fp_neq(t->ptr.pp_double[ki][ki-1],0) ) + { + ip = -1; + } + } + if( somev ) + { + if( ip==0 ) + { + if( !vselect->ptr.p_bool[ki] ) + { + skipflag = ae_true; + } + } + else + { + if( !vselect->ptr.p_bool[ki-1] ) + { + skipflag = ae_true; + } + } + } + } + if( !skipflag ) + { + + /* + * Compute the KI-th eigenvalue (WR,WI). + */ + wr = t->ptr.pp_double[ki][ki]; + wi = 0; + if( ip!=0 ) + { + wi = ae_sqrt(ae_fabs(t->ptr.pp_double[ki][ki-1], _state), _state)*ae_sqrt(ae_fabs(t->ptr.pp_double[ki-1][ki], _state), _state); + } + smin = ae_maxreal(ulp*(ae_fabs(wr, _state)+ae_fabs(wi, _state)), smlnum, _state); + if( ip==0 ) + { + + /* + * Real right eigenvector + */ + work.ptr.p_double[ki+n] = 1; + + /* + * Form right-hand side + */ + for(k=1; k<=ki-1; k++) + { + work.ptr.p_double[k+n] = -t->ptr.pp_double[k][ki]; + } + + /* + * Solve the upper quasi-triangular system: + * (T(1:KI-1,1:KI-1) - WR)*X = SCALE*WORK. + */ + jnxt = ki-1; + for(j=ki-1; j>=1; j--) + { + if( j>jnxt ) + { + continue; + } + j1 = j; + j2 = j; + jnxt = j-1; + if( j>1 ) + { + if( ae_fp_neq(t->ptr.pp_double[j][j-1],0) ) + { + j1 = j-1; + jnxt = j-2; + } + } + if( j1==j2 ) + { + + /* + * 1-by-1 diagonal block + */ + temp11.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp11b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + evd_internalhsevdlaln2(ae_false, 1, 1, smin, 1, &temp11, 1.0, 1.0, &temp11b, wr, 0.0, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale X(1,1) to avoid overflow when updating + * the right-hand side. + */ + if( ae_fp_greater(xnorm,1) ) + { + if( ae_fp_greater(work.ptr.p_double[j],bignum/xnorm) ) + { + x.ptr.pp_double[1][1] = x.ptr.pp_double[1][1]/xnorm; + scl = scl/xnorm; + } + } + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + k1 = n+1; + k2 = n+ki; + ae_v_muld(&work.ptr.p_double[k1], 1, ae_v_len(k1,k2), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + + /* + * Update right-hand side + */ + k1 = 1+n; + k2 = j-1+n; + k3 = j-1; + vt = -x.ptr.pp_double[1][1]; + ae_v_addd(&work.ptr.p_double[k1], 1, &t->ptr.pp_double[1][j], t->stride, ae_v_len(k1,k2), vt); + } + else + { + + /* + * 2-by-2 diagonal block + */ + temp22.ptr.pp_double[1][1] = t->ptr.pp_double[j-1][j-1]; + temp22.ptr.pp_double[1][2] = t->ptr.pp_double[j-1][j]; + temp22.ptr.pp_double[2][1] = t->ptr.pp_double[j][j-1]; + temp22.ptr.pp_double[2][2] = t->ptr.pp_double[j][j]; + temp21b.ptr.pp_double[1][1] = work.ptr.p_double[j-1+n]; + temp21b.ptr.pp_double[2][1] = work.ptr.p_double[j+n]; + evd_internalhsevdlaln2(ae_false, 2, 1, smin, 1.0, &temp22, 1.0, 1.0, &temp21b, wr, 0, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale X(1,1) and X(2,1) to avoid overflow when + * updating the right-hand side. + */ + if( ae_fp_greater(xnorm,1) ) + { + beta = ae_maxreal(work.ptr.p_double[j-1], work.ptr.p_double[j], _state); + if( ae_fp_greater(beta,bignum/xnorm) ) + { + x.ptr.pp_double[1][1] = x.ptr.pp_double[1][1]/xnorm; + x.ptr.pp_double[2][1] = x.ptr.pp_double[2][1]/xnorm; + scl = scl/xnorm; + } + } + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + k1 = 1+n; + k2 = ki+n; + ae_v_muld(&work.ptr.p_double[k1], 1, ae_v_len(k1,k2), scl); + } + work.ptr.p_double[j-1+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+n] = x.ptr.pp_double[2][1]; + + /* + * Update right-hand side + */ + k1 = 1+n; + k2 = j-2+n; + k3 = j-2; + k4 = j-1; + vt = -x.ptr.pp_double[1][1]; + ae_v_addd(&work.ptr.p_double[k1], 1, &t->ptr.pp_double[1][k4], t->stride, ae_v_len(k1,k2), vt); + vt = -x.ptr.pp_double[2][1]; + ae_v_addd(&work.ptr.p_double[k1], 1, &t->ptr.pp_double[1][j], t->stride, ae_v_len(k1,k2), vt); + } + } + + /* + * Copy the vector x or Q*x to VR and normalize. + */ + if( !over ) + { + k1 = 1+n; + k2 = ki+n; + ae_v_move(&vr->ptr.pp_double[1][iis], vr->stride, &work.ptr.p_double[k1], 1, ae_v_len(1,ki)); + ii = columnidxabsmax(vr, 1, ki, iis, _state); + remax = 1/ae_fabs(vr->ptr.pp_double[ii][iis], _state); + ae_v_muld(&vr->ptr.pp_double[1][iis], vr->stride, ae_v_len(1,ki), remax); + for(k=ki+1; k<=n; k++) + { + vr->ptr.pp_double[k][iis] = 0; + } + } + else + { + if( ki>1 ) + { + ae_v_move(&temp.ptr.p_double[1], 1, &vr->ptr.pp_double[1][ki], vr->stride, ae_v_len(1,n)); + matrixvectormultiply(vr, 1, n, 1, ki-1, ae_false, &work, 1+n, ki-1+n, 1.0, &temp, 1, n, work.ptr.p_double[ki+n], _state); + ae_v_move(&vr->ptr.pp_double[1][ki], vr->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + } + ii = columnidxabsmax(vr, 1, n, ki, _state); + remax = 1/ae_fabs(vr->ptr.pp_double[ii][ki], _state); + ae_v_muld(&vr->ptr.pp_double[1][ki], vr->stride, ae_v_len(1,n), remax); + } + } + else + { + + /* + * Complex right eigenvector. + * + * Initial solve + * [ (T(KI-1,KI-1) T(KI-1,KI) ) - (WR + I* WI)]*X = 0. + * [ (T(KI,KI-1) T(KI,KI) ) ] + */ + if( ae_fp_greater_eq(ae_fabs(t->ptr.pp_double[ki-1][ki], _state),ae_fabs(t->ptr.pp_double[ki][ki-1], _state)) ) + { + work.ptr.p_double[ki-1+n] = 1; + work.ptr.p_double[ki+n2] = wi/t->ptr.pp_double[ki-1][ki]; + } + else + { + work.ptr.p_double[ki-1+n] = -wi/t->ptr.pp_double[ki][ki-1]; + work.ptr.p_double[ki+n2] = 1; + } + work.ptr.p_double[ki+n] = 0; + work.ptr.p_double[ki-1+n2] = 0; + + /* + * Form right-hand side + */ + for(k=1; k<=ki-2; k++) + { + work.ptr.p_double[k+n] = -work.ptr.p_double[ki-1+n]*t->ptr.pp_double[k][ki-1]; + work.ptr.p_double[k+n2] = -work.ptr.p_double[ki+n2]*t->ptr.pp_double[k][ki]; + } + + /* + * Solve upper quasi-triangular system: + * (T(1:KI-2,1:KI-2) - (WR+i*WI))*X = SCALE*(WORK+i*WORK2) + */ + jnxt = ki-2; + for(j=ki-2; j>=1; j--) + { + if( j>jnxt ) + { + continue; + } + j1 = j; + j2 = j; + jnxt = j-1; + if( j>1 ) + { + if( ae_fp_neq(t->ptr.pp_double[j][j-1],0) ) + { + j1 = j-1; + jnxt = j-2; + } + } + if( j1==j2 ) + { + + /* + * 1-by-1 diagonal block + */ + temp11.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp12b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + temp12b.ptr.pp_double[1][2] = work.ptr.p_double[j+n+n]; + evd_internalhsevdlaln2(ae_false, 1, 2, smin, 1.0, &temp11, 1.0, 1.0, &temp12b, wr, wi, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale X(1,1) and X(1,2) to avoid overflow when + * updating the right-hand side. + */ + if( ae_fp_greater(xnorm,1) ) + { + if( ae_fp_greater(work.ptr.p_double[j],bignum/xnorm) ) + { + x.ptr.pp_double[1][1] = x.ptr.pp_double[1][1]/xnorm; + x.ptr.pp_double[1][2] = x.ptr.pp_double[1][2]/xnorm; + scl = scl/xnorm; + } + } + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + k1 = 1+n; + k2 = ki+n; + ae_v_muld(&work.ptr.p_double[k1], 1, ae_v_len(k1,k2), scl); + k1 = 1+n2; + k2 = ki+n2; + ae_v_muld(&work.ptr.p_double[k1], 1, ae_v_len(k1,k2), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+n2] = x.ptr.pp_double[1][2]; + + /* + * Update the right-hand side + */ + k1 = 1+n; + k2 = j-1+n; + k3 = 1; + k4 = j-1; + vt = -x.ptr.pp_double[1][1]; + ae_v_addd(&work.ptr.p_double[k1], 1, &t->ptr.pp_double[k3][j], t->stride, ae_v_len(k1,k2), vt); + k1 = 1+n2; + k2 = j-1+n2; + k3 = 1; + k4 = j-1; + vt = -x.ptr.pp_double[1][2]; + ae_v_addd(&work.ptr.p_double[k1], 1, &t->ptr.pp_double[k3][j], t->stride, ae_v_len(k1,k2), vt); + } + else + { + + /* + * 2-by-2 diagonal block + */ + temp22.ptr.pp_double[1][1] = t->ptr.pp_double[j-1][j-1]; + temp22.ptr.pp_double[1][2] = t->ptr.pp_double[j-1][j]; + temp22.ptr.pp_double[2][1] = t->ptr.pp_double[j][j-1]; + temp22.ptr.pp_double[2][2] = t->ptr.pp_double[j][j]; + temp22b.ptr.pp_double[1][1] = work.ptr.p_double[j-1+n]; + temp22b.ptr.pp_double[1][2] = work.ptr.p_double[j-1+n+n]; + temp22b.ptr.pp_double[2][1] = work.ptr.p_double[j+n]; + temp22b.ptr.pp_double[2][2] = work.ptr.p_double[j+n+n]; + evd_internalhsevdlaln2(ae_false, 2, 2, smin, 1.0, &temp22, 1.0, 1.0, &temp22b, wr, wi, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale X to avoid overflow when updating + * the right-hand side. + */ + if( ae_fp_greater(xnorm,1) ) + { + beta = ae_maxreal(work.ptr.p_double[j-1], work.ptr.p_double[j], _state); + if( ae_fp_greater(beta,bignum/xnorm) ) + { + rec = 1/xnorm; + x.ptr.pp_double[1][1] = x.ptr.pp_double[1][1]*rec; + x.ptr.pp_double[1][2] = x.ptr.pp_double[1][2]*rec; + x.ptr.pp_double[2][1] = x.ptr.pp_double[2][1]*rec; + x.ptr.pp_double[2][2] = x.ptr.pp_double[2][2]*rec; + scl = scl*rec; + } + } + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + ae_v_muld(&work.ptr.p_double[1+n], 1, ae_v_len(1+n,ki+n), scl); + ae_v_muld(&work.ptr.p_double[1+n2], 1, ae_v_len(1+n2,ki+n2), scl); + } + work.ptr.p_double[j-1+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+n] = x.ptr.pp_double[2][1]; + work.ptr.p_double[j-1+n2] = x.ptr.pp_double[1][2]; + work.ptr.p_double[j+n2] = x.ptr.pp_double[2][2]; + + /* + * Update the right-hand side + */ + vt = -x.ptr.pp_double[1][1]; + ae_v_addd(&work.ptr.p_double[n+1], 1, &t->ptr.pp_double[1][j-1], t->stride, ae_v_len(n+1,n+j-2), vt); + vt = -x.ptr.pp_double[2][1]; + ae_v_addd(&work.ptr.p_double[n+1], 1, &t->ptr.pp_double[1][j], t->stride, ae_v_len(n+1,n+j-2), vt); + vt = -x.ptr.pp_double[1][2]; + ae_v_addd(&work.ptr.p_double[n2+1], 1, &t->ptr.pp_double[1][j-1], t->stride, ae_v_len(n2+1,n2+j-2), vt); + vt = -x.ptr.pp_double[2][2]; + ae_v_addd(&work.ptr.p_double[n2+1], 1, &t->ptr.pp_double[1][j], t->stride, ae_v_len(n2+1,n2+j-2), vt); + } + } + + /* + * Copy the vector x or Q*x to VR and normalize. + */ + if( !over ) + { + ae_v_move(&vr->ptr.pp_double[1][iis-1], vr->stride, &work.ptr.p_double[n+1], 1, ae_v_len(1,ki)); + ae_v_move(&vr->ptr.pp_double[1][iis], vr->stride, &work.ptr.p_double[n2+1], 1, ae_v_len(1,ki)); + emax = 0; + for(k=1; k<=ki; k++) + { + emax = ae_maxreal(emax, ae_fabs(vr->ptr.pp_double[k][iis-1], _state)+ae_fabs(vr->ptr.pp_double[k][iis], _state), _state); + } + remax = 1/emax; + ae_v_muld(&vr->ptr.pp_double[1][iis-1], vr->stride, ae_v_len(1,ki), remax); + ae_v_muld(&vr->ptr.pp_double[1][iis], vr->stride, ae_v_len(1,ki), remax); + for(k=ki+1; k<=n; k++) + { + vr->ptr.pp_double[k][iis-1] = 0; + vr->ptr.pp_double[k][iis] = 0; + } + } + else + { + if( ki>2 ) + { + ae_v_move(&temp.ptr.p_double[1], 1, &vr->ptr.pp_double[1][ki-1], vr->stride, ae_v_len(1,n)); + matrixvectormultiply(vr, 1, n, 1, ki-2, ae_false, &work, 1+n, ki-2+n, 1.0, &temp, 1, n, work.ptr.p_double[ki-1+n], _state); + ae_v_move(&vr->ptr.pp_double[1][ki-1], vr->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + ae_v_move(&temp.ptr.p_double[1], 1, &vr->ptr.pp_double[1][ki], vr->stride, ae_v_len(1,n)); + matrixvectormultiply(vr, 1, n, 1, ki-2, ae_false, &work, 1+n2, ki-2+n2, 1.0, &temp, 1, n, work.ptr.p_double[ki+n2], _state); + ae_v_move(&vr->ptr.pp_double[1][ki], vr->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + } + else + { + vt = work.ptr.p_double[ki-1+n]; + ae_v_muld(&vr->ptr.pp_double[1][ki-1], vr->stride, ae_v_len(1,n), vt); + vt = work.ptr.p_double[ki+n2]; + ae_v_muld(&vr->ptr.pp_double[1][ki], vr->stride, ae_v_len(1,n), vt); + } + emax = 0; + for(k=1; k<=n; k++) + { + emax = ae_maxreal(emax, ae_fabs(vr->ptr.pp_double[k][ki-1], _state)+ae_fabs(vr->ptr.pp_double[k][ki], _state), _state); + } + remax = 1/emax; + ae_v_muld(&vr->ptr.pp_double[1][ki-1], vr->stride, ae_v_len(1,n), remax); + ae_v_muld(&vr->ptr.pp_double[1][ki], vr->stride, ae_v_len(1,n), remax); + } + } + iis = iis-1; + if( ip!=0 ) + { + iis = iis-1; + } + } + if( ip==1 ) + { + ip = 0; + } + if( ip==-1 ) + { + ip = 1; + } + } + } + if( leftv ) + { + + /* + * Compute left eigenvectors. + */ + ip = 0; + iis = 1; + for(ki=1; ki<=n; ki++) + { + skipflag = ae_false; + if( ip==-1 ) + { + skipflag = ae_true; + } + else + { + if( ki!=n ) + { + if( ae_fp_neq(t->ptr.pp_double[ki+1][ki],0) ) + { + ip = 1; + } + } + if( somev ) + { + if( !vselect->ptr.p_bool[ki] ) + { + skipflag = ae_true; + } + } + } + if( !skipflag ) + { + + /* + * Compute the KI-th eigenvalue (WR,WI). + */ + wr = t->ptr.pp_double[ki][ki]; + wi = 0; + if( ip!=0 ) + { + wi = ae_sqrt(ae_fabs(t->ptr.pp_double[ki][ki+1], _state), _state)*ae_sqrt(ae_fabs(t->ptr.pp_double[ki+1][ki], _state), _state); + } + smin = ae_maxreal(ulp*(ae_fabs(wr, _state)+ae_fabs(wi, _state)), smlnum, _state); + if( ip==0 ) + { + + /* + * Real left eigenvector. + */ + work.ptr.p_double[ki+n] = 1; + + /* + * Form right-hand side + */ + for(k=ki+1; k<=n; k++) + { + work.ptr.p_double[k+n] = -t->ptr.pp_double[ki][k]; + } + + /* + * Solve the quasi-triangular system: + * (T(KI+1:N,KI+1:N) - WR)'*X = SCALE*WORK + */ + vmax = 1; + vcrit = bignum; + jnxt = ki+1; + for(j=ki+1; j<=n; j++) + { + if( jptr.pp_double[j+1][j],0) ) + { + j2 = j+1; + jnxt = j+2; + } + } + if( j1==j2 ) + { + + /* + * 1-by-1 diagonal block + * + * Scale if necessary to avoid overflow when forming + * the right-hand side. + */ + if( ae_fp_greater(work.ptr.p_double[j],vcrit) ) + { + rec = 1/vmax; + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), rec); + vmax = 1; + vcrit = bignum; + } + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+1][j], t->stride, &work.ptr.p_double[ki+1+n], 1, ae_v_len(ki+1,j-1)); + work.ptr.p_double[j+n] = work.ptr.p_double[j+n]-vt; + + /* + * Solve (T(J,J)-WR)'*X = WORK + */ + temp11.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp11b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + evd_internalhsevdlaln2(ae_false, 1, 1, smin, 1.0, &temp11, 1.0, 1.0, &temp11b, wr, 0, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + vmax = ae_maxreal(ae_fabs(work.ptr.p_double[j+n], _state), vmax, _state); + vcrit = bignum/vmax; + } + else + { + + /* + * 2-by-2 diagonal block + * + * Scale if necessary to avoid overflow when forming + * the right-hand side. + */ + beta = ae_maxreal(work.ptr.p_double[j], work.ptr.p_double[j+1], _state); + if( ae_fp_greater(beta,vcrit) ) + { + rec = 1/vmax; + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), rec); + vmax = 1; + vcrit = bignum; + } + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+1][j], t->stride, &work.ptr.p_double[ki+1+n], 1, ae_v_len(ki+1,j-1)); + work.ptr.p_double[j+n] = work.ptr.p_double[j+n]-vt; + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+1][j+1], t->stride, &work.ptr.p_double[ki+1+n], 1, ae_v_len(ki+1,j-1)); + work.ptr.p_double[j+1+n] = work.ptr.p_double[j+1+n]-vt; + + /* + * Solve + * [T(J,J)-WR T(J,J+1) ]'* X = SCALE*( WORK1 ) + * [T(J+1,J) T(J+1,J+1)-WR] ( WORK2 ) + */ + temp22.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp22.ptr.pp_double[1][2] = t->ptr.pp_double[j][j+1]; + temp22.ptr.pp_double[2][1] = t->ptr.pp_double[j+1][j]; + temp22.ptr.pp_double[2][2] = t->ptr.pp_double[j+1][j+1]; + temp21b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + temp21b.ptr.pp_double[2][1] = work.ptr.p_double[j+1+n]; + evd_internalhsevdlaln2(ae_true, 2, 1, smin, 1.0, &temp22, 1.0, 1.0, &temp21b, wr, 0, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+1+n] = x.ptr.pp_double[2][1]; + vmax = ae_maxreal(ae_fabs(work.ptr.p_double[j+n], _state), ae_maxreal(ae_fabs(work.ptr.p_double[j+1+n], _state), vmax, _state), _state); + vcrit = bignum/vmax; + } + } + + /* + * Copy the vector x or Q*x to VL and normalize. + */ + if( !over ) + { + ae_v_move(&vl->ptr.pp_double[ki][iis], vl->stride, &work.ptr.p_double[ki+n], 1, ae_v_len(ki,n)); + ii = columnidxabsmax(vl, ki, n, iis, _state); + remax = 1/ae_fabs(vl->ptr.pp_double[ii][iis], _state); + ae_v_muld(&vl->ptr.pp_double[ki][iis], vl->stride, ae_v_len(ki,n), remax); + for(k=1; k<=ki-1; k++) + { + vl->ptr.pp_double[k][iis] = 0; + } + } + else + { + if( kiptr.pp_double[1][ki], vl->stride, ae_v_len(1,n)); + matrixvectormultiply(vl, 1, n, ki+1, n, ae_false, &work, ki+1+n, n+n, 1.0, &temp, 1, n, work.ptr.p_double[ki+n], _state); + ae_v_move(&vl->ptr.pp_double[1][ki], vl->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + } + ii = columnidxabsmax(vl, 1, n, ki, _state); + remax = 1/ae_fabs(vl->ptr.pp_double[ii][ki], _state); + ae_v_muld(&vl->ptr.pp_double[1][ki], vl->stride, ae_v_len(1,n), remax); + } + } + else + { + + /* + * Complex left eigenvector. + * + * Initial solve: + * ((T(KI,KI) T(KI,KI+1) )' - (WR - I* WI))*X = 0. + * ((T(KI+1,KI) T(KI+1,KI+1)) ) + */ + if( ae_fp_greater_eq(ae_fabs(t->ptr.pp_double[ki][ki+1], _state),ae_fabs(t->ptr.pp_double[ki+1][ki], _state)) ) + { + work.ptr.p_double[ki+n] = wi/t->ptr.pp_double[ki][ki+1]; + work.ptr.p_double[ki+1+n2] = 1; + } + else + { + work.ptr.p_double[ki+n] = 1; + work.ptr.p_double[ki+1+n2] = -wi/t->ptr.pp_double[ki+1][ki]; + } + work.ptr.p_double[ki+1+n] = 0; + work.ptr.p_double[ki+n2] = 0; + + /* + * Form right-hand side + */ + for(k=ki+2; k<=n; k++) + { + work.ptr.p_double[k+n] = -work.ptr.p_double[ki+n]*t->ptr.pp_double[ki][k]; + work.ptr.p_double[k+n2] = -work.ptr.p_double[ki+1+n2]*t->ptr.pp_double[ki+1][k]; + } + + /* + * Solve complex quasi-triangular system: + * ( T(KI+2,N:KI+2,N) - (WR-i*WI) )*X = WORK1+i*WORK2 + */ + vmax = 1; + vcrit = bignum; + jnxt = ki+2; + for(j=ki+2; j<=n; j++) + { + if( jptr.pp_double[j+1][j],0) ) + { + j2 = j+1; + jnxt = j+2; + } + } + if( j1==j2 ) + { + + /* + * 1-by-1 diagonal block + * + * Scale if necessary to avoid overflow when + * forming the right-hand side elements. + */ + if( ae_fp_greater(work.ptr.p_double[j],vcrit) ) + { + rec = 1/vmax; + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), rec); + ae_v_muld(&work.ptr.p_double[ki+n2], 1, ae_v_len(ki+n2,n+n2), rec); + vmax = 1; + vcrit = bignum; + } + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j], t->stride, &work.ptr.p_double[ki+2+n], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+n] = work.ptr.p_double[j+n]-vt; + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j], t->stride, &work.ptr.p_double[ki+2+n2], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+n2] = work.ptr.p_double[j+n2]-vt; + + /* + * Solve (T(J,J)-(WR-i*WI))*(X11+i*X12)= WK+I*WK2 + */ + temp11.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp12b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + temp12b.ptr.pp_double[1][2] = work.ptr.p_double[j+n+n]; + evd_internalhsevdlaln2(ae_false, 1, 2, smin, 1.0, &temp11, 1.0, 1.0, &temp12b, wr, -wi, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), scl); + ae_v_muld(&work.ptr.p_double[ki+n2], 1, ae_v_len(ki+n2,n+n2), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+n2] = x.ptr.pp_double[1][2]; + vmax = ae_maxreal(ae_fabs(work.ptr.p_double[j+n], _state), ae_maxreal(ae_fabs(work.ptr.p_double[j+n2], _state), vmax, _state), _state); + vcrit = bignum/vmax; + } + else + { + + /* + * 2-by-2 diagonal block + * + * Scale if necessary to avoid overflow when forming + * the right-hand side elements. + */ + beta = ae_maxreal(work.ptr.p_double[j], work.ptr.p_double[j+1], _state); + if( ae_fp_greater(beta,vcrit) ) + { + rec = 1/vmax; + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), rec); + ae_v_muld(&work.ptr.p_double[ki+n2], 1, ae_v_len(ki+n2,n+n2), rec); + vmax = 1; + vcrit = bignum; + } + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j], t->stride, &work.ptr.p_double[ki+2+n], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+n] = work.ptr.p_double[j+n]-vt; + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j], t->stride, &work.ptr.p_double[ki+2+n2], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+n2] = work.ptr.p_double[j+n2]-vt; + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j+1], t->stride, &work.ptr.p_double[ki+2+n], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+1+n] = work.ptr.p_double[j+1+n]-vt; + vt = ae_v_dotproduct(&t->ptr.pp_double[ki+2][j+1], t->stride, &work.ptr.p_double[ki+2+n2], 1, ae_v_len(ki+2,j-1)); + work.ptr.p_double[j+1+n2] = work.ptr.p_double[j+1+n2]-vt; + + /* + * Solve 2-by-2 complex linear equation + * ([T(j,j) T(j,j+1) ]'-(wr-i*wi)*I)*X = SCALE*B + * ([T(j+1,j) T(j+1,j+1)] ) + */ + temp22.ptr.pp_double[1][1] = t->ptr.pp_double[j][j]; + temp22.ptr.pp_double[1][2] = t->ptr.pp_double[j][j+1]; + temp22.ptr.pp_double[2][1] = t->ptr.pp_double[j+1][j]; + temp22.ptr.pp_double[2][2] = t->ptr.pp_double[j+1][j+1]; + temp22b.ptr.pp_double[1][1] = work.ptr.p_double[j+n]; + temp22b.ptr.pp_double[1][2] = work.ptr.p_double[j+n+n]; + temp22b.ptr.pp_double[2][1] = work.ptr.p_double[j+1+n]; + temp22b.ptr.pp_double[2][2] = work.ptr.p_double[j+1+n+n]; + evd_internalhsevdlaln2(ae_true, 2, 2, smin, 1.0, &temp22, 1.0, 1.0, &temp22b, wr, -wi, &rswap4, &zswap4, &ipivot44, &civ4, &crv4, &x, &scl, &xnorm, &ierr, _state); + + /* + * Scale if necessary + */ + if( ae_fp_neq(scl,1) ) + { + ae_v_muld(&work.ptr.p_double[ki+n], 1, ae_v_len(ki+n,n+n), scl); + ae_v_muld(&work.ptr.p_double[ki+n2], 1, ae_v_len(ki+n2,n+n2), scl); + } + work.ptr.p_double[j+n] = x.ptr.pp_double[1][1]; + work.ptr.p_double[j+n2] = x.ptr.pp_double[1][2]; + work.ptr.p_double[j+1+n] = x.ptr.pp_double[2][1]; + work.ptr.p_double[j+1+n2] = x.ptr.pp_double[2][2]; + vmax = ae_maxreal(ae_fabs(x.ptr.pp_double[1][1], _state), vmax, _state); + vmax = ae_maxreal(ae_fabs(x.ptr.pp_double[1][2], _state), vmax, _state); + vmax = ae_maxreal(ae_fabs(x.ptr.pp_double[2][1], _state), vmax, _state); + vmax = ae_maxreal(ae_fabs(x.ptr.pp_double[2][2], _state), vmax, _state); + vcrit = bignum/vmax; + } + } + + /* + * Copy the vector x or Q*x to VL and normalize. + */ + if( !over ) + { + ae_v_move(&vl->ptr.pp_double[ki][iis], vl->stride, &work.ptr.p_double[ki+n], 1, ae_v_len(ki,n)); + ae_v_move(&vl->ptr.pp_double[ki][iis+1], vl->stride, &work.ptr.p_double[ki+n2], 1, ae_v_len(ki,n)); + emax = 0; + for(k=ki; k<=n; k++) + { + emax = ae_maxreal(emax, ae_fabs(vl->ptr.pp_double[k][iis], _state)+ae_fabs(vl->ptr.pp_double[k][iis+1], _state), _state); + } + remax = 1/emax; + ae_v_muld(&vl->ptr.pp_double[ki][iis], vl->stride, ae_v_len(ki,n), remax); + ae_v_muld(&vl->ptr.pp_double[ki][iis+1], vl->stride, ae_v_len(ki,n), remax); + for(k=1; k<=ki-1; k++) + { + vl->ptr.pp_double[k][iis] = 0; + vl->ptr.pp_double[k][iis+1] = 0; + } + } + else + { + if( kiptr.pp_double[1][ki], vl->stride, ae_v_len(1,n)); + matrixvectormultiply(vl, 1, n, ki+2, n, ae_false, &work, ki+2+n, n+n, 1.0, &temp, 1, n, work.ptr.p_double[ki+n], _state); + ae_v_move(&vl->ptr.pp_double[1][ki], vl->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + ae_v_move(&temp.ptr.p_double[1], 1, &vl->ptr.pp_double[1][ki+1], vl->stride, ae_v_len(1,n)); + matrixvectormultiply(vl, 1, n, ki+2, n, ae_false, &work, ki+2+n2, n+n2, 1.0, &temp, 1, n, work.ptr.p_double[ki+1+n2], _state); + ae_v_move(&vl->ptr.pp_double[1][ki+1], vl->stride, &temp.ptr.p_double[1], 1, ae_v_len(1,n)); + } + else + { + vt = work.ptr.p_double[ki+n]; + ae_v_muld(&vl->ptr.pp_double[1][ki], vl->stride, ae_v_len(1,n), vt); + vt = work.ptr.p_double[ki+1+n2]; + ae_v_muld(&vl->ptr.pp_double[1][ki+1], vl->stride, ae_v_len(1,n), vt); + } + emax = 0; + for(k=1; k<=n; k++) + { + emax = ae_maxreal(emax, ae_fabs(vl->ptr.pp_double[k][ki], _state)+ae_fabs(vl->ptr.pp_double[k][ki+1], _state), _state); + } + remax = 1/emax; + ae_v_muld(&vl->ptr.pp_double[1][ki], vl->stride, ae_v_len(1,n), remax); + ae_v_muld(&vl->ptr.pp_double[1][ki+1], vl->stride, ae_v_len(1,n), remax); + } + } + iis = iis+1; + if( ip!=0 ) + { + iis = iis+1; + } + } + if( ip==-1 ) + { + ip = 0; + } + if( ip==1 ) + { + ip = -1; + } + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +DLALN2 solves a system of the form (ca A - w D ) X = s B +or (ca A' - w D) X = s B with possible scaling ("s") and +perturbation of A. (A' means A-transpose.) + +A is an NA x NA real matrix, ca is a real scalar, D is an NA x NA +real diagonal matrix, w is a real or complex value, and X and B are +NA x 1 matrices -- real if w is real, complex if w is complex. NA +may be 1 or 2. + +If w is complex, X and B are represented as NA x 2 matrices, +the first column of each being the real part and the second +being the imaginary part. + +"s" is a scaling factor (.LE. 1), computed by DLALN2, which is +so chosen that X can be computed without overflow. X is further +scaled if necessary to assure that norm(ca A - w D)*norm(X) is less +than overflow. + +If both singular values of (ca A - w D) are less than SMIN, +SMIN*identity will be used instead of (ca A - w D). If only one +singular value is less than SMIN, one element of (ca A - w D) will be +perturbed enough to make the smallest singular value roughly SMIN. +If both singular values are at least SMIN, (ca A - w D) will not be +perturbed. In any case, the perturbation will be at most some small +multiple of max( SMIN, ulp*norm(ca A - w D) ). The singular values +are computed by infinity-norm approximations, and thus will only be +correct to a factor of 2 or so. + +Note: all input quantities are assumed to be smaller than overflow +by a reasonable factor. (See BIGNUM.) + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +static void evd_internalhsevdlaln2(ae_bool ltrans, + ae_int_t na, + ae_int_t nw, + double smin, + double ca, + /* Real */ ae_matrix* a, + double d1, + double d2, + /* Real */ ae_matrix* b, + double wr, + double wi, + /* Boolean */ ae_vector* rswap4, + /* Boolean */ ae_vector* zswap4, + /* Integer */ ae_matrix* ipivot44, + /* Real */ ae_vector* civ4, + /* Real */ ae_vector* crv4, + /* Real */ ae_matrix* x, + double* scl, + double* xnorm, + ae_int_t* info, + ae_state *_state) +{ + ae_int_t icmax; + ae_int_t j; + double bbnd; + double bi1; + double bi2; + double bignum; + double bnorm; + double br1; + double br2; + double ci21; + double ci22; + double cmax; + double cnorm; + double cr21; + double cr22; + double csi; + double csr; + double li21; + double lr21; + double smini; + double smlnum; + double temp; + double u22abs; + double ui11; + double ui11r; + double ui12; + double ui12s; + double ui22; + double ur11; + double ur11r; + double ur12; + double ur12s; + double ur22; + double xi1; + double xi2; + double xr1; + double xr2; + double tmp1; + double tmp2; + + *scl = 0; + *xnorm = 0; + *info = 0; + + zswap4->ptr.p_bool[1] = ae_false; + zswap4->ptr.p_bool[2] = ae_false; + zswap4->ptr.p_bool[3] = ae_true; + zswap4->ptr.p_bool[4] = ae_true; + rswap4->ptr.p_bool[1] = ae_false; + rswap4->ptr.p_bool[2] = ae_true; + rswap4->ptr.p_bool[3] = ae_false; + rswap4->ptr.p_bool[4] = ae_true; + ipivot44->ptr.pp_int[1][1] = 1; + ipivot44->ptr.pp_int[2][1] = 2; + ipivot44->ptr.pp_int[3][1] = 3; + ipivot44->ptr.pp_int[4][1] = 4; + ipivot44->ptr.pp_int[1][2] = 2; + ipivot44->ptr.pp_int[2][2] = 1; + ipivot44->ptr.pp_int[3][2] = 4; + ipivot44->ptr.pp_int[4][2] = 3; + ipivot44->ptr.pp_int[1][3] = 3; + ipivot44->ptr.pp_int[2][3] = 4; + ipivot44->ptr.pp_int[3][3] = 1; + ipivot44->ptr.pp_int[4][3] = 2; + ipivot44->ptr.pp_int[1][4] = 4; + ipivot44->ptr.pp_int[2][4] = 3; + ipivot44->ptr.pp_int[3][4] = 2; + ipivot44->ptr.pp_int[4][4] = 1; + smlnum = 2*ae_minrealnumber; + bignum = 1/smlnum; + smini = ae_maxreal(smin, smlnum, _state); + + /* + * Don't check for input errors + */ + *info = 0; + + /* + * Standard Initializations + */ + *scl = 1; + if( na==1 ) + { + + /* + * 1 x 1 (i.e., scalar) system C X = B + */ + if( nw==1 ) + { + + /* + * Real 1x1 system. + * + * C = ca A - w D + */ + csr = ca*a->ptr.pp_double[1][1]-wr*d1; + cnorm = ae_fabs(csr, _state); + + /* + * If | C | < SMINI, use C = SMINI + */ + if( ae_fp_less(cnorm,smini) ) + { + csr = smini; + cnorm = smini; + *info = 1; + } + + /* + * Check scaling for X = B / C + */ + bnorm = ae_fabs(b->ptr.pp_double[1][1], _state); + if( ae_fp_less(cnorm,1)&&ae_fp_greater(bnorm,1) ) + { + if( ae_fp_greater(bnorm,bignum*cnorm) ) + { + *scl = 1/bnorm; + } + } + + /* + * Compute X + */ + x->ptr.pp_double[1][1] = b->ptr.pp_double[1][1]*(*scl)/csr; + *xnorm = ae_fabs(x->ptr.pp_double[1][1], _state); + } + else + { + + /* + * Complex 1x1 system (w is complex) + * + * C = ca A - w D + */ + csr = ca*a->ptr.pp_double[1][1]-wr*d1; + csi = -wi*d1; + cnorm = ae_fabs(csr, _state)+ae_fabs(csi, _state); + + /* + * If | C | < SMINI, use C = SMINI + */ + if( ae_fp_less(cnorm,smini) ) + { + csr = smini; + csi = 0; + cnorm = smini; + *info = 1; + } + + /* + * Check scaling for X = B / C + */ + bnorm = ae_fabs(b->ptr.pp_double[1][1], _state)+ae_fabs(b->ptr.pp_double[1][2], _state); + if( ae_fp_less(cnorm,1)&&ae_fp_greater(bnorm,1) ) + { + if( ae_fp_greater(bnorm,bignum*cnorm) ) + { + *scl = 1/bnorm; + } + } + + /* + * Compute X + */ + evd_internalhsevdladiv(*scl*b->ptr.pp_double[1][1], *scl*b->ptr.pp_double[1][2], csr, csi, &tmp1, &tmp2, _state); + x->ptr.pp_double[1][1] = tmp1; + x->ptr.pp_double[1][2] = tmp2; + *xnorm = ae_fabs(x->ptr.pp_double[1][1], _state)+ae_fabs(x->ptr.pp_double[1][2], _state); + } + } + else + { + + /* + * 2x2 System + * + * Compute the real part of C = ca A - w D (or ca A' - w D ) + */ + crv4->ptr.p_double[1+0] = ca*a->ptr.pp_double[1][1]-wr*d1; + crv4->ptr.p_double[2+2] = ca*a->ptr.pp_double[2][2]-wr*d2; + if( ltrans ) + { + crv4->ptr.p_double[1+2] = ca*a->ptr.pp_double[2][1]; + crv4->ptr.p_double[2+0] = ca*a->ptr.pp_double[1][2]; + } + else + { + crv4->ptr.p_double[2+0] = ca*a->ptr.pp_double[2][1]; + crv4->ptr.p_double[1+2] = ca*a->ptr.pp_double[1][2]; + } + if( nw==1 ) + { + + /* + * Real 2x2 system (w is real) + * + * Find the largest element in C + */ + cmax = 0; + icmax = 0; + for(j=1; j<=4; j++) + { + if( ae_fp_greater(ae_fabs(crv4->ptr.p_double[j], _state),cmax) ) + { + cmax = ae_fabs(crv4->ptr.p_double[j], _state); + icmax = j; + } + } + + /* + * If norm(C) < SMINI, use SMINI*identity. + */ + if( ae_fp_less(cmax,smini) ) + { + bnorm = ae_maxreal(ae_fabs(b->ptr.pp_double[1][1], _state), ae_fabs(b->ptr.pp_double[2][1], _state), _state); + if( ae_fp_less(smini,1)&&ae_fp_greater(bnorm,1) ) + { + if( ae_fp_greater(bnorm,bignum*smini) ) + { + *scl = 1/bnorm; + } + } + temp = *scl/smini; + x->ptr.pp_double[1][1] = temp*b->ptr.pp_double[1][1]; + x->ptr.pp_double[2][1] = temp*b->ptr.pp_double[2][1]; + *xnorm = temp*bnorm; + *info = 1; + return; + } + + /* + * Gaussian elimination with complete pivoting. + */ + ur11 = crv4->ptr.p_double[icmax]; + cr21 = crv4->ptr.p_double[ipivot44->ptr.pp_int[2][icmax]]; + ur12 = crv4->ptr.p_double[ipivot44->ptr.pp_int[3][icmax]]; + cr22 = crv4->ptr.p_double[ipivot44->ptr.pp_int[4][icmax]]; + ur11r = 1/ur11; + lr21 = ur11r*cr21; + ur22 = cr22-ur12*lr21; + + /* + * If smaller pivot < SMINI, use SMINI + */ + if( ae_fp_less(ae_fabs(ur22, _state),smini) ) + { + ur22 = smini; + *info = 1; + } + if( rswap4->ptr.p_bool[icmax] ) + { + br1 = b->ptr.pp_double[2][1]; + br2 = b->ptr.pp_double[1][1]; + } + else + { + br1 = b->ptr.pp_double[1][1]; + br2 = b->ptr.pp_double[2][1]; + } + br2 = br2-lr21*br1; + bbnd = ae_maxreal(ae_fabs(br1*(ur22*ur11r), _state), ae_fabs(br2, _state), _state); + if( ae_fp_greater(bbnd,1)&&ae_fp_less(ae_fabs(ur22, _state),1) ) + { + if( ae_fp_greater_eq(bbnd,bignum*ae_fabs(ur22, _state)) ) + { + *scl = 1/bbnd; + } + } + xr2 = br2*(*scl)/ur22; + xr1 = *scl*br1*ur11r-xr2*(ur11r*ur12); + if( zswap4->ptr.p_bool[icmax] ) + { + x->ptr.pp_double[1][1] = xr2; + x->ptr.pp_double[2][1] = xr1; + } + else + { + x->ptr.pp_double[1][1] = xr1; + x->ptr.pp_double[2][1] = xr2; + } + *xnorm = ae_maxreal(ae_fabs(xr1, _state), ae_fabs(xr2, _state), _state); + + /* + * Further scaling if norm(A) norm(X) > overflow + */ + if( ae_fp_greater(*xnorm,1)&&ae_fp_greater(cmax,1) ) + { + if( ae_fp_greater(*xnorm,bignum/cmax) ) + { + temp = cmax/bignum; + x->ptr.pp_double[1][1] = temp*x->ptr.pp_double[1][1]; + x->ptr.pp_double[2][1] = temp*x->ptr.pp_double[2][1]; + *xnorm = temp*(*xnorm); + *scl = temp*(*scl); + } + } + } + else + { + + /* + * Complex 2x2 system (w is complex) + * + * Find the largest element in C + */ + civ4->ptr.p_double[1+0] = -wi*d1; + civ4->ptr.p_double[2+0] = 0; + civ4->ptr.p_double[1+2] = 0; + civ4->ptr.p_double[2+2] = -wi*d2; + cmax = 0; + icmax = 0; + for(j=1; j<=4; j++) + { + if( ae_fp_greater(ae_fabs(crv4->ptr.p_double[j], _state)+ae_fabs(civ4->ptr.p_double[j], _state),cmax) ) + { + cmax = ae_fabs(crv4->ptr.p_double[j], _state)+ae_fabs(civ4->ptr.p_double[j], _state); + icmax = j; + } + } + + /* + * If norm(C) < SMINI, use SMINI*identity. + */ + if( ae_fp_less(cmax,smini) ) + { + bnorm = ae_maxreal(ae_fabs(b->ptr.pp_double[1][1], _state)+ae_fabs(b->ptr.pp_double[1][2], _state), ae_fabs(b->ptr.pp_double[2][1], _state)+ae_fabs(b->ptr.pp_double[2][2], _state), _state); + if( ae_fp_less(smini,1)&&ae_fp_greater(bnorm,1) ) + { + if( ae_fp_greater(bnorm,bignum*smini) ) + { + *scl = 1/bnorm; + } + } + temp = *scl/smini; + x->ptr.pp_double[1][1] = temp*b->ptr.pp_double[1][1]; + x->ptr.pp_double[2][1] = temp*b->ptr.pp_double[2][1]; + x->ptr.pp_double[1][2] = temp*b->ptr.pp_double[1][2]; + x->ptr.pp_double[2][2] = temp*b->ptr.pp_double[2][2]; + *xnorm = temp*bnorm; + *info = 1; + return; + } + + /* + * Gaussian elimination with complete pivoting. + */ + ur11 = crv4->ptr.p_double[icmax]; + ui11 = civ4->ptr.p_double[icmax]; + cr21 = crv4->ptr.p_double[ipivot44->ptr.pp_int[2][icmax]]; + ci21 = civ4->ptr.p_double[ipivot44->ptr.pp_int[2][icmax]]; + ur12 = crv4->ptr.p_double[ipivot44->ptr.pp_int[3][icmax]]; + ui12 = civ4->ptr.p_double[ipivot44->ptr.pp_int[3][icmax]]; + cr22 = crv4->ptr.p_double[ipivot44->ptr.pp_int[4][icmax]]; + ci22 = civ4->ptr.p_double[ipivot44->ptr.pp_int[4][icmax]]; + if( icmax==1||icmax==4 ) + { + + /* + * Code when off-diagonals of pivoted C are real + */ + if( ae_fp_greater(ae_fabs(ur11, _state),ae_fabs(ui11, _state)) ) + { + temp = ui11/ur11; + ur11r = 1/(ur11*(1+ae_sqr(temp, _state))); + ui11r = -temp*ur11r; + } + else + { + temp = ur11/ui11; + ui11r = -1/(ui11*(1+ae_sqr(temp, _state))); + ur11r = -temp*ui11r; + } + lr21 = cr21*ur11r; + li21 = cr21*ui11r; + ur12s = ur12*ur11r; + ui12s = ur12*ui11r; + ur22 = cr22-ur12*lr21; + ui22 = ci22-ur12*li21; + } + else + { + + /* + * Code when diagonals of pivoted C are real + */ + ur11r = 1/ur11; + ui11r = 0; + lr21 = cr21*ur11r; + li21 = ci21*ur11r; + ur12s = ur12*ur11r; + ui12s = ui12*ur11r; + ur22 = cr22-ur12*lr21+ui12*li21; + ui22 = -ur12*li21-ui12*lr21; + } + u22abs = ae_fabs(ur22, _state)+ae_fabs(ui22, _state); + + /* + * If smaller pivot < SMINI, use SMINI + */ + if( ae_fp_less(u22abs,smini) ) + { + ur22 = smini; + ui22 = 0; + *info = 1; + } + if( rswap4->ptr.p_bool[icmax] ) + { + br2 = b->ptr.pp_double[1][1]; + br1 = b->ptr.pp_double[2][1]; + bi2 = b->ptr.pp_double[1][2]; + bi1 = b->ptr.pp_double[2][2]; + } + else + { + br1 = b->ptr.pp_double[1][1]; + br2 = b->ptr.pp_double[2][1]; + bi1 = b->ptr.pp_double[1][2]; + bi2 = b->ptr.pp_double[2][2]; + } + br2 = br2-lr21*br1+li21*bi1; + bi2 = bi2-li21*br1-lr21*bi1; + bbnd = ae_maxreal((ae_fabs(br1, _state)+ae_fabs(bi1, _state))*(u22abs*(ae_fabs(ur11r, _state)+ae_fabs(ui11r, _state))), ae_fabs(br2, _state)+ae_fabs(bi2, _state), _state); + if( ae_fp_greater(bbnd,1)&&ae_fp_less(u22abs,1) ) + { + if( ae_fp_greater_eq(bbnd,bignum*u22abs) ) + { + *scl = 1/bbnd; + br1 = *scl*br1; + bi1 = *scl*bi1; + br2 = *scl*br2; + bi2 = *scl*bi2; + } + } + evd_internalhsevdladiv(br2, bi2, ur22, ui22, &xr2, &xi2, _state); + xr1 = ur11r*br1-ui11r*bi1-ur12s*xr2+ui12s*xi2; + xi1 = ui11r*br1+ur11r*bi1-ui12s*xr2-ur12s*xi2; + if( zswap4->ptr.p_bool[icmax] ) + { + x->ptr.pp_double[1][1] = xr2; + x->ptr.pp_double[2][1] = xr1; + x->ptr.pp_double[1][2] = xi2; + x->ptr.pp_double[2][2] = xi1; + } + else + { + x->ptr.pp_double[1][1] = xr1; + x->ptr.pp_double[2][1] = xr2; + x->ptr.pp_double[1][2] = xi1; + x->ptr.pp_double[2][2] = xi2; + } + *xnorm = ae_maxreal(ae_fabs(xr1, _state)+ae_fabs(xi1, _state), ae_fabs(xr2, _state)+ae_fabs(xi2, _state), _state); + + /* + * Further scaling if norm(A) norm(X) > overflow + */ + if( ae_fp_greater(*xnorm,1)&&ae_fp_greater(cmax,1) ) + { + if( ae_fp_greater(*xnorm,bignum/cmax) ) + { + temp = cmax/bignum; + x->ptr.pp_double[1][1] = temp*x->ptr.pp_double[1][1]; + x->ptr.pp_double[2][1] = temp*x->ptr.pp_double[2][1]; + x->ptr.pp_double[1][2] = temp*x->ptr.pp_double[1][2]; + x->ptr.pp_double[2][2] = temp*x->ptr.pp_double[2][2]; + *xnorm = temp*(*xnorm); + *scl = temp*(*scl); + } + } + } + } +} + + +/************************************************************************* +performs complex division in real arithmetic + + a + i*b + p + i*q = --------- + c + i*d + +The algorithm is due to Robert L. Smith and can be found +in D. Knuth, The art of Computer Programming, Vol.2, p.195 + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +static void evd_internalhsevdladiv(double a, + double b, + double c, + double d, + double* p, + double* q, + ae_state *_state) +{ + double e; + double f; + + *p = 0; + *q = 0; + + if( ae_fp_less(ae_fabs(d, _state),ae_fabs(c, _state)) ) + { + e = d/c; + f = c+d*e; + *p = (a+b*e)/f; + *q = (b-a*e)/f; + } + else + { + e = c/d; + f = d+c*e; + *p = (b+a*e)/f; + *q = (-a+b*e)/f; + } +} + + +static ae_bool evd_nonsymmetricevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t vneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_matrix s; + ae_vector tau; + ae_vector sel; + ae_int_t i; + ae_int_t info; + ae_int_t m; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_clear(wr); + ae_vector_clear(wi); + ae_matrix_clear(vl); + ae_matrix_clear(vr); + ae_matrix_init(&s, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sel, 0, DT_BOOL, _state, ae_true); + + ae_assert(vneeded>=0&&vneeded<=3, "NonSymmetricEVD: incorrect VNeeded!", _state); + if( vneeded==0 ) + { + + /* + * Eigen values only + */ + evd_toupperhessenberg(a, n, &tau, _state); + internalschurdecomposition(a, n, 0, 0, wr, wi, &s, &info, _state); + result = info==0; + ae_frame_leave(_state); + return result; + } + + /* + * Eigen values and vectors + */ + evd_toupperhessenberg(a, n, &tau, _state); + evd_unpackqfromupperhessenberg(a, n, &tau, &s, _state); + internalschurdecomposition(a, n, 1, 1, wr, wi, &s, &info, _state); + result = info==0; + if( !result ) + { + ae_frame_leave(_state); + return result; + } + if( vneeded==1||vneeded==3 ) + { + ae_matrix_set_length(vr, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&vr->ptr.pp_double[i][1], 1, &s.ptr.pp_double[i][1], 1, ae_v_len(1,n)); + } + } + if( vneeded==2||vneeded==3 ) + { + ae_matrix_set_length(vl, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + ae_v_move(&vl->ptr.pp_double[i][1], 1, &s.ptr.pp_double[i][1], 1, ae_v_len(1,n)); + } + } + evd_internaltrevc(a, n, vneeded, 1, &sel, vl, vr, &m, &info, _state); + result = info==0; + ae_frame_leave(_state); + return result; +} + + +static void evd_toupperhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t ip1; + ae_int_t nmi; + double v; + ae_vector t; + ae_vector work; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(tau); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "ToUpperHessenberg: incorrect N!", _state); + + /* + * Quick return if possible + */ + if( n<=1 ) + { + ae_frame_leave(_state); + return; + } + ae_vector_set_length(tau, n-1+1, _state); + ae_vector_set_length(&t, n+1, _state); + ae_vector_set_length(&work, n+1, _state); + for(i=1; i<=n-1; i++) + { + + /* + * Compute elementary reflector H(i) to annihilate A(i+2:ihi,i) + */ + ip1 = i+1; + nmi = n-i; + ae_v_move(&t.ptr.p_double[1], 1, &a->ptr.pp_double[ip1][i], a->stride, ae_v_len(1,nmi)); + generatereflection(&t, nmi, &v, _state); + ae_v_move(&a->ptr.pp_double[ip1][i], a->stride, &t.ptr.p_double[1], 1, ae_v_len(ip1,n)); + tau->ptr.p_double[i] = v; + t.ptr.p_double[1] = 1; + + /* + * Apply H(i) to A(1:ihi,i+1:ihi) from the right + */ + applyreflectionfromtheright(a, v, &t, 1, n, i+1, n, &work, _state); + + /* + * Apply H(i) to A(i+1:ihi,i+1:n) from the left + */ + applyreflectionfromtheleft(a, v, &t, i+1, n, i+1, n, &work, _state); + } + ae_frame_leave(_state); +} + + +static void evd_unpackqfromupperhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector v; + ae_vector work; + ae_int_t ip1; + ae_int_t nmi; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(q); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + if( n==0 ) + { + ae_frame_leave(_state); + return; + } + + /* + * init + */ + ae_matrix_set_length(q, n+1, n+1, _state); + ae_vector_set_length(&v, n+1, _state); + ae_vector_set_length(&work, n+1, _state); + for(i=1; i<=n; i++) + { + for(j=1; j<=n; j++) + { + if( i==j ) + { + q->ptr.pp_double[i][j] = 1; + } + else + { + q->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * unpack Q + */ + for(i=1; i<=n-1; i++) + { + + /* + * Apply H(i) + */ + ip1 = i+1; + nmi = n-i; + ae_v_move(&v.ptr.p_double[1], 1, &a->ptr.pp_double[ip1][i], a->stride, ae_v_len(1,nmi)); + v.ptr.p_double[1] = 1; + applyreflectionfromtheright(q, tau->ptr.p_double[i], &v, 1, n, i+1, n, &work, _state); + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Generation of a random uniformly distributed (Haar) orthogonal matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonal(ae_int_t n, + /* Real */ ae_matrix* a, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(a); + + ae_assert(n>=1, "RMatrixRndOrthogonal: N<1!", _state); + ae_matrix_set_length(a, n, n, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + a->ptr.pp_double[i][j] = 1; + } + else + { + a->ptr.pp_double[i][j] = 0; + } + } + } + rmatrixrndorthogonalfromtheright(a, n, n, _state); +} + + +/************************************************************************* +Generation of random NxN matrix with given condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&rs, _state, ae_true); + + ae_assert(n>=1&&ae_fp_greater_eq(c,1), "RMatrixRndCond: N<1 or C<1!", _state); + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + + /* + * special case + */ + a->ptr.pp_double[0][0] = 2*ae_randominteger(2, _state)-1; + ae_frame_leave(_state); + return; + } + hqrndrandomize(&rs, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + a->ptr.pp_double[0][0] = ae_exp(l1, _state); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_double[i][i] = ae_exp(hqrnduniformr(&rs, _state)*(l2-l1)+l1, _state); + } + a->ptr.pp_double[n-1][n-1] = ae_exp(l2, _state); + rmatrixrndorthogonalfromtheleft(a, n, n, _state); + rmatrixrndorthogonalfromtheright(a, n, n, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Generation of a random Haar distributed orthogonal complex matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonal(ae_int_t n, + /* Complex */ ae_matrix* a, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + ae_matrix_clear(a); + + ae_assert(n>=1, "CMatrixRndOrthogonal: N<1!", _state); + ae_matrix_set_length(a, n, n, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + if( i==j ) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(1); + } + else + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + cmatrixrndorthogonalfromtheright(a, n, n, _state); +} + + +/************************************************************************* +Generation of random NxN complex matrix with given condition number C and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate state; + ae_complex v; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&state, _state, ae_true); + + ae_assert(n>=1&&ae_fp_greater_eq(c,1), "CMatrixRndCond: N<1 or C<1!", _state); + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + + /* + * special case + */ + hqrndrandomize(&state, _state); + hqrndunit2(&state, &v.x, &v.y, _state); + a->ptr.pp_complex[0][0] = v; + ae_frame_leave(_state); + return; + } + hqrndrandomize(&state, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + a->ptr.pp_complex[0][0] = ae_complex_from_d(ae_exp(l1, _state)); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_complex[i][i] = ae_complex_from_d(ae_exp(hqrnduniformr(&state, _state)*(l2-l1)+l1, _state)); + } + a->ptr.pp_complex[n-1][n-1] = ae_complex_from_d(ae_exp(l2, _state)); + cmatrixrndorthogonalfromtheleft(a, n, n, _state); + cmatrixrndorthogonalfromtheright(a, n, n, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Generation of random NxN symmetric matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&rs, _state, ae_true); + + ae_assert(n>=1&&ae_fp_greater_eq(c,1), "SMatrixRndCond: N<1 or C<1!", _state); + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + + /* + * special case + */ + a->ptr.pp_double[0][0] = 2*ae_randominteger(2, _state)-1; + ae_frame_leave(_state); + return; + } + + /* + * Prepare matrix + */ + hqrndrandomize(&rs, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + a->ptr.pp_double[0][0] = ae_exp(l1, _state); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_double[i][i] = (2*hqrnduniformi(&rs, 2, _state)-1)*ae_exp(hqrnduniformr(&rs, _state)*(l2-l1)+l1, _state); + } + a->ptr.pp_double[n-1][n-1] = ae_exp(l2, _state); + + /* + * Multiply + */ + smatrixrndmultiply(a, n, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Generation of random NxN symmetric positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random SPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&rs, _state, ae_true); + + + /* + * Special cases + */ + if( n<=0||ae_fp_less(c,1) ) + { + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + a->ptr.pp_double[0][0] = 1; + ae_frame_leave(_state); + return; + } + + /* + * Prepare matrix + */ + hqrndrandomize(&rs, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + a->ptr.pp_double[0][0] = ae_exp(l1, _state); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_double[i][i] = ae_exp(hqrnduniformr(&rs, _state)*(l2-l1)+l1, _state); + } + a->ptr.pp_double[n-1][n-1] = ae_exp(l2, _state); + + /* + * Multiply + */ + smatrixrndmultiply(a, n, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Generation of random NxN Hermitian matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&rs, _state, ae_true); + + ae_assert(n>=1&&ae_fp_greater_eq(c,1), "HMatrixRndCond: N<1 or C<1!", _state); + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + + /* + * special case + */ + a->ptr.pp_complex[0][0] = ae_complex_from_d(2*ae_randominteger(2, _state)-1); + ae_frame_leave(_state); + return; + } + + /* + * Prepare matrix + */ + hqrndrandomize(&rs, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + a->ptr.pp_complex[0][0] = ae_complex_from_d(ae_exp(l1, _state)); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_complex[i][i] = ae_complex_from_d((2*hqrnduniformi(&rs, 2, _state)-1)*ae_exp(hqrnduniformr(&rs, _state)*(l2-l1)+l1, _state)); + } + a->ptr.pp_complex[n-1][n-1] = ae_complex_from_d(ae_exp(l2, _state)); + + /* + * Multiply + */ + hmatrixrndmultiply(a, n, _state); + + /* + * post-process to ensure that matrix diagonal is real + */ + for(i=0; i<=n-1; i++) + { + a->ptr.pp_complex[i][i].y = 0; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Generation of random NxN Hermitian positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random HPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double l1; + double l2; + hqrndstate rs; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(a); + _hqrndstate_init(&rs, _state, ae_true); + + + /* + * Special cases + */ + if( n<=0||ae_fp_less(c,1) ) + { + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(a, n, n, _state); + if( n==1 ) + { + a->ptr.pp_complex[0][0] = ae_complex_from_d(1); + ae_frame_leave(_state); + return; + } + + /* + * Prepare matrix + */ + hqrndrandomize(&rs, _state); + l1 = 0; + l2 = ae_log(1/c, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + a->ptr.pp_complex[0][0] = ae_complex_from_d(ae_exp(l1, _state)); + for(i=1; i<=n-2; i++) + { + a->ptr.pp_complex[i][i] = ae_complex_from_d(ae_exp(hqrnduniformr(&rs, _state)*(l2-l1)+l1, _state)); + } + a->ptr.pp_complex[n-1][n-1] = ae_complex_from_d(ae_exp(l2, _state)); + + /* + * Multiply + */ + hmatrixrndmultiply(a, n, _state); + + /* + * post-process to ensure that matrix diagonal is real + */ + for(i=0; i<=n-1; i++) + { + a->ptr.pp_complex[i][i].y = 0; + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Multiplication of MxN matrix by NxN random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheright(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + double tau; + double lambdav; + ae_int_t s; + ae_int_t i; + double u1; + double u2; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + ae_assert(n>=1&&m>=1, "RMatrixRndOrthogonalFromTheRight: N<1 or M<1!", _state); + if( n==1 ) + { + + /* + * Special case + */ + tau = 2*ae_randominteger(2, _state)-1; + for(i=0; i<=m-1; i++) + { + a->ptr.pp_double[i][0] = a->ptr.pp_double[i][0]*tau; + } + ae_frame_leave(_state); + return; + } + + /* + * General case. + * First pass. + */ + ae_vector_set_length(&w, m, _state); + ae_vector_set_length(&v, n+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=n; s++) + { + + /* + * Prepare random normal v + */ + do + { + i = 1; + while(i<=s) + { + hqrndnormal2(&state, &u1, &u2, _state); + v.ptr.p_double[i] = u1; + if( i+1<=s ) + { + v.ptr.p_double[i+1] = u2; + } + i = i+2; + } + lambdav = ae_v_dotproduct(&v.ptr.p_double[1], 1, &v.ptr.p_double[1], 1, ae_v_len(1,s)); + } + while(ae_fp_eq(lambdav,0)); + + /* + * Prepare and apply reflection + */ + generatereflection(&v, s, &tau, _state); + v.ptr.p_double[1] = 1; + applyreflectionfromtheright(a, tau, &v, 0, m-1, n-s, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=n-1; i++) + { + tau = 2*hqrnduniformi(&state, 2, _state)-1; + ae_v_muld(&a->ptr.pp_double[0][i], a->stride, ae_v_len(0,m-1), tau); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Multiplication of MxN matrix by MxM random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheleft(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + double tau; + double lambdav; + ae_int_t s; + ae_int_t i; + ae_int_t j; + double u1; + double u2; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + ae_assert(n>=1&&m>=1, "RMatrixRndOrthogonalFromTheRight: N<1 or M<1!", _state); + if( m==1 ) + { + + /* + * special case + */ + tau = 2*ae_randominteger(2, _state)-1; + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[0][j] = a->ptr.pp_double[0][j]*tau; + } + ae_frame_leave(_state); + return; + } + + /* + * General case. + * First pass. + */ + ae_vector_set_length(&w, n, _state); + ae_vector_set_length(&v, m+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=m; s++) + { + + /* + * Prepare random normal v + */ + do + { + i = 1; + while(i<=s) + { + hqrndnormal2(&state, &u1, &u2, _state); + v.ptr.p_double[i] = u1; + if( i+1<=s ) + { + v.ptr.p_double[i+1] = u2; + } + i = i+2; + } + lambdav = ae_v_dotproduct(&v.ptr.p_double[1], 1, &v.ptr.p_double[1], 1, ae_v_len(1,s)); + } + while(ae_fp_eq(lambdav,0)); + + /* + * Prepare and apply reflection + */ + generatereflection(&v, s, &tau, _state); + v.ptr.p_double[1] = 1; + applyreflectionfromtheleft(a, tau, &v, m-s, m-1, 0, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=m-1; i++) + { + tau = 2*hqrnduniformi(&state, 2, _state)-1; + ae_v_muld(&a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), tau); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Multiplication of MxN complex matrix by NxN random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheright(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_complex lambdav; + ae_complex tau; + ae_int_t s; + ae_int_t i; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&v, 0, DT_COMPLEX, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + ae_assert(n>=1&&m>=1, "CMatrixRndOrthogonalFromTheRight: N<1 or M<1!", _state); + if( n==1 ) + { + + /* + * Special case + */ + hqrndrandomize(&state, _state); + hqrndunit2(&state, &tau.x, &tau.y, _state); + for(i=0; i<=m-1; i++) + { + a->ptr.pp_complex[i][0] = ae_c_mul(a->ptr.pp_complex[i][0],tau); + } + ae_frame_leave(_state); + return; + } + + /* + * General case. + * First pass. + */ + ae_vector_set_length(&w, m, _state); + ae_vector_set_length(&v, n+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=n; s++) + { + + /* + * Prepare random normal v + */ + do + { + for(i=1; i<=s; i++) + { + hqrndnormal2(&state, &tau.x, &tau.y, _state); + v.ptr.p_complex[i] = tau; + } + lambdav = ae_v_cdotproduct(&v.ptr.p_complex[1], 1, "N", &v.ptr.p_complex[1], 1, "Conj", ae_v_len(1,s)); + } + while(ae_c_eq_d(lambdav,0)); + + /* + * Prepare and apply reflection + */ + complexgeneratereflection(&v, s, &tau, _state); + v.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheright(a, tau, &v, 0, m-1, n-s, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=n-1; i++) + { + hqrndunit2(&state, &tau.x, &tau.y, _state); + ae_v_cmulc(&a->ptr.pp_complex[0][i], a->stride, ae_v_len(0,m-1), tau); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Multiplication of MxN complex matrix by MxM random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheleft(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_complex tau; + ae_complex lambdav; + ae_int_t s; + ae_int_t i; + ae_int_t j; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&v, 0, DT_COMPLEX, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + ae_assert(n>=1&&m>=1, "CMatrixRndOrthogonalFromTheRight: N<1 or M<1!", _state); + if( m==1 ) + { + + /* + * special case + */ + hqrndrandomize(&state, _state); + hqrndunit2(&state, &tau.x, &tau.y, _state); + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[0][j] = ae_c_mul(a->ptr.pp_complex[0][j],tau); + } + ae_frame_leave(_state); + return; + } + + /* + * General case. + * First pass. + */ + ae_vector_set_length(&w, n, _state); + ae_vector_set_length(&v, m+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=m; s++) + { + + /* + * Prepare random normal v + */ + do + { + for(i=1; i<=s; i++) + { + hqrndnormal2(&state, &tau.x, &tau.y, _state); + v.ptr.p_complex[i] = tau; + } + lambdav = ae_v_cdotproduct(&v.ptr.p_complex[1], 1, "N", &v.ptr.p_complex[1], 1, "Conj", ae_v_len(1,s)); + } + while(ae_c_eq_d(lambdav,0)); + + /* + * Prepare and apply reflection + */ + complexgeneratereflection(&v, s, &tau, _state); + v.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheleft(a, tau, &v, m-s, m-1, 0, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=m-1; i++) + { + hqrndunit2(&state, &tau.x, &tau.y, _state); + ae_v_cmulc(&a->ptr.pp_complex[i][0], 1, ae_v_len(0,n-1), tau); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Symmetric multiplication of NxN matrix by random Haar distributed +orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q'*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndmultiply(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + double tau; + double lambdav; + ae_int_t s; + ae_int_t i; + double u1; + double u2; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&v, 0, DT_REAL, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + + /* + * General case. + */ + ae_vector_set_length(&w, n, _state); + ae_vector_set_length(&v, n+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=n; s++) + { + + /* + * Prepare random normal v + */ + do + { + i = 1; + while(i<=s) + { + hqrndnormal2(&state, &u1, &u2, _state); + v.ptr.p_double[i] = u1; + if( i+1<=s ) + { + v.ptr.p_double[i+1] = u2; + } + i = i+2; + } + lambdav = ae_v_dotproduct(&v.ptr.p_double[1], 1, &v.ptr.p_double[1], 1, ae_v_len(1,s)); + } + while(ae_fp_eq(lambdav,0)); + + /* + * Prepare and apply reflection + */ + generatereflection(&v, s, &tau, _state); + v.ptr.p_double[1] = 1; + applyreflectionfromtheright(a, tau, &v, 0, n-1, n-s, n-1, &w, _state); + applyreflectionfromtheleft(a, tau, &v, n-s, n-1, 0, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=n-1; i++) + { + tau = 2*hqrnduniformi(&state, 2, _state)-1; + ae_v_muld(&a->ptr.pp_double[0][i], a->stride, ae_v_len(0,n-1), tau); + ae_v_muld(&a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), tau); + } + + /* + * Copy upper triangle to lower + */ + for(i=0; i<=n-2; i++) + { + ae_v_move(&a->ptr.pp_double[i+1][i], a->stride, &a->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1)); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Hermitian multiplication of NxN matrix by random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q^H*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndmultiply(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_complex tau; + ae_complex lambdav; + ae_int_t s; + ae_int_t i; + ae_vector w; + ae_vector v; + hqrndstate state; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&w, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&v, 0, DT_COMPLEX, _state, ae_true); + _hqrndstate_init(&state, _state, ae_true); + + + /* + * General case. + */ + ae_vector_set_length(&w, n, _state); + ae_vector_set_length(&v, n+1, _state); + hqrndrandomize(&state, _state); + for(s=2; s<=n; s++) + { + + /* + * Prepare random normal v + */ + do + { + for(i=1; i<=s; i++) + { + hqrndnormal2(&state, &tau.x, &tau.y, _state); + v.ptr.p_complex[i] = tau; + } + lambdav = ae_v_cdotproduct(&v.ptr.p_complex[1], 1, "N", &v.ptr.p_complex[1], 1, "Conj", ae_v_len(1,s)); + } + while(ae_c_eq_d(lambdav,0)); + + /* + * Prepare and apply reflection + */ + complexgeneratereflection(&v, s, &tau, _state); + v.ptr.p_complex[1] = ae_complex_from_d(1); + complexapplyreflectionfromtheright(a, tau, &v, 0, n-1, n-s, n-1, &w, _state); + complexapplyreflectionfromtheleft(a, ae_c_conj(tau, _state), &v, n-s, n-1, 0, n-1, &w, _state); + } + + /* + * Second pass. + */ + for(i=0; i<=n-1; i++) + { + hqrndunit2(&state, &tau.x, &tau.y, _state); + ae_v_cmulc(&a->ptr.pp_complex[0][i], a->stride, ae_v_len(0,n-1), tau); + tau = ae_c_conj(tau, _state); + ae_v_cmulc(&a->ptr.pp_complex[i][0], 1, ae_v_len(0,n-1), tau); + } + + /* + * Change all values from lower triangle by complex-conjugate values + * from upper one + */ + for(i=0; i<=n-2; i++) + { + ae_v_cmove(&a->ptr.pp_complex[i+1][i], a->stride, &a->ptr.pp_complex[i][i+1], 1, "N", ae_v_len(i+1,n-1)); + } + for(s=0; s<=n-2; s++) + { + for(i=s+1; i<=n-1; i++) + { + a->ptr.pp_complex[i][s].y = -a->ptr.pp_complex[i][s].y; + } + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +LU decomposition of a general real matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. +It is optimized for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlu(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + + ae_vector_clear(pivots); + + ae_assert(m>0, "RMatrixLU: incorrect M!", _state); + ae_assert(n>0, "RMatrixLU: incorrect N!", _state); + rmatrixplu(a, m, n, pivots, _state); +} + + +/************************************************************************* +LU decomposition of a general complex matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. It is optimized +for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlu(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + + ae_vector_clear(pivots); + + ae_assert(m>0, "CMatrixLU: incorrect M!", _state); + ae_assert(n>0, "CMatrixLU: incorrect N!", _state); + cmatrixplu(a, m, n, pivots, _state); +} + + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a Hermitian positive- +definite matrix. The result of an algorithm is a representation of A as +A=U'*U or A=L*L' (here X' detones conj(X^T)). + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U'*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_bool hpdmatrixcholesky(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + if( n<1 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + result = trfac_hpdmatrixcholeskyrec(a, 0, n, isupper, &tmp, _state); + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a symmetric positive- +definite matrix. The result of an algorithm is a representation of A as +A=U^T*U or A=L*L^T + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U^T*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_bool spdmatrixcholesky(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + if( n<1 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + result = spdmatrixcholeskyrec(a, 0, n, isupper, &tmp, _state); + ae_frame_leave(_state); + return result; +} + + +void rmatrixlup(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_int_t i; + ae_int_t j; + double mx; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(pivots); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * Internal LU decomposition subroutine. + * Never call it directly. + */ + ae_assert(m>0, "RMatrixLUP: incorrect M!", _state); + ae_assert(n>0, "RMatrixLUP: incorrect N!", _state); + + /* + * Scale matrix to avoid overflows, + * decompose it, then scale back. + */ + mx = 0; + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + mx = ae_maxreal(mx, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_neq(mx,0) ) + { + v = 1/mx; + for(i=0; i<=m-1; i++) + { + ae_v_muld(&a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + } + ae_vector_set_length(pivots, ae_minint(m, n, _state), _state); + ae_vector_set_length(&tmp, 2*ae_maxint(m, n, _state), _state); + trfac_rmatrixluprec(a, 0, m, n, pivots, &tmp, _state); + if( ae_fp_neq(mx,0) ) + { + v = mx; + for(i=0; i<=m-1; i++) + { + ae_v_muld(&a->ptr.pp_double[i][0], 1, ae_v_len(0,ae_minint(i, n-1, _state)), v); + } + } + ae_frame_leave(_state); +} + + +void cmatrixlup(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_int_t i; + ae_int_t j; + double mx; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(pivots); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + + /* + * Internal LU decomposition subroutine. + * Never call it directly. + */ + ae_assert(m>0, "CMatrixLUP: incorrect M!", _state); + ae_assert(n>0, "CMatrixLUP: incorrect N!", _state); + + /* + * Scale matrix to avoid overflows, + * decompose it, then scale back. + */ + mx = 0; + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + mx = ae_maxreal(mx, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_neq(mx,0) ) + { + v = 1/mx; + for(i=0; i<=m-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[i][0], 1, ae_v_len(0,n-1), v); + } + } + ae_vector_set_length(pivots, ae_minint(m, n, _state), _state); + ae_vector_set_length(&tmp, 2*ae_maxint(m, n, _state), _state); + trfac_cmatrixluprec(a, 0, m, n, pivots, &tmp, _state); + if( ae_fp_neq(mx,0) ) + { + v = mx; + for(i=0; i<=m-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[i][0], 1, ae_v_len(0,ae_minint(i, n-1, _state)), v); + } + } + ae_frame_leave(_state); +} + + +void rmatrixplu(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_int_t i; + ae_int_t j; + double mx; + double v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(pivots); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * Internal LU decomposition subroutine. + * Never call it directly. + */ + ae_assert(m>0, "RMatrixPLU: incorrect M!", _state); + ae_assert(n>0, "RMatrixPLU: incorrect N!", _state); + ae_vector_set_length(&tmp, 2*ae_maxint(m, n, _state), _state); + ae_vector_set_length(pivots, ae_minint(m, n, _state), _state); + + /* + * Scale matrix to avoid overflows, + * decompose it, then scale back. + */ + mx = 0; + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + mx = ae_maxreal(mx, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_neq(mx,0) ) + { + v = 1/mx; + for(i=0; i<=m-1; i++) + { + ae_v_muld(&a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + } + trfac_rmatrixplurec(a, 0, m, n, pivots, &tmp, _state); + if( ae_fp_neq(mx,0) ) + { + v = mx; + for(i=0; i<=ae_minint(m, n, _state)-1; i++) + { + ae_v_muld(&a->ptr.pp_double[i][i], 1, ae_v_len(i,n-1), v); + } + } + ae_frame_leave(_state); +} + + +void cmatrixplu(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tmp; + ae_int_t i; + ae_int_t j; + double mx; + ae_complex v; + + ae_frame_make(_state, &_frame_block); + ae_vector_clear(pivots); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + + /* + * Internal LU decomposition subroutine. + * Never call it directly. + */ + ae_assert(m>0, "CMatrixPLU: incorrect M!", _state); + ae_assert(n>0, "CMatrixPLU: incorrect N!", _state); + ae_vector_set_length(&tmp, 2*ae_maxint(m, n, _state), _state); + ae_vector_set_length(pivots, ae_minint(m, n, _state), _state); + + /* + * Scale matrix to avoid overflows, + * decompose it, then scale back. + */ + mx = 0; + for(i=0; i<=m-1; i++) + { + for(j=0; j<=n-1; j++) + { + mx = ae_maxreal(mx, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_neq(mx,0) ) + { + v = ae_complex_from_d(1/mx); + for(i=0; i<=m-1; i++) + { + ae_v_cmulc(&a->ptr.pp_complex[i][0], 1, ae_v_len(0,n-1), v); + } + } + trfac_cmatrixplurec(a, 0, m, n, pivots, &tmp, _state); + if( ae_fp_neq(mx,0) ) + { + v = ae_complex_from_d(mx); + for(i=0; i<=ae_minint(m, n, _state)-1; i++) + { + ae_v_cmulc(&a->ptr.pp_complex[i][i], 1, ae_v_len(i,n-1), v); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Recursive computational subroutine for SPDMatrixCholesky. + +INPUT PARAMETERS: + A - matrix given by upper or lower triangle + Offs - offset of diagonal block to decompose + N - diagonal block size + IsUpper - what half is given + Tmp - temporary array; allocated by function, if its size is too + small; can be reused on subsequent calls. + +OUTPUT PARAMETERS: + A - upper (or lower) triangle contains Cholesky decomposition + +RESULT: + True, on success + False, on failure + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +ae_bool spdmatrixcholeskyrec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t n1; + ae_int_t n2; + ae_bool result; + + + + /* + * check N + */ + if( n<1 ) + { + result = ae_false; + return result; + } + + /* + * Prepare buffer + */ + if( tmp->cnt<2*n ) + { + ae_vector_set_length(tmp, 2*n, _state); + } + + /* + * special cases + */ + if( n==1 ) + { + if( ae_fp_greater(a->ptr.pp_double[offs][offs],0) ) + { + a->ptr.pp_double[offs][offs] = ae_sqrt(a->ptr.pp_double[offs][offs], _state); + result = ae_true; + } + else + { + result = ae_false; + } + return result; + } + if( n<=ablasblocksize(a, _state) ) + { + result = trfac_spdmatrixcholesky2(a, offs, n, isupper, tmp, _state); + return result; + } + + /* + * general case: split task in cache-oblivious manner + */ + result = ae_true; + ablassplitlength(a, n, &n1, &n2, _state); + result = spdmatrixcholeskyrec(a, offs, n1, isupper, tmp, _state); + if( !result ) + { + return result; + } + if( n2>0 ) + { + if( isupper ) + { + rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, ae_false, 1, a, offs, offs+n1, _state); + rmatrixsyrk(n2, n1, -1.0, a, offs, offs+n1, 1, 1.0, a, offs+n1, offs+n1, isupper, _state); + } + else + { + rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, ae_false, 1, a, offs+n1, offs, _state); + rmatrixsyrk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, a, offs+n1, offs+n1, isupper, _state); + } + result = spdmatrixcholeskyrec(a, offs+n1, n2, isupper, tmp, _state); + if( !result ) + { + return result; + } + } + return result; +} + + +/************************************************************************* +Recurrent complex LU subroutine. +Never call it directly. + + -- ALGLIB routine -- + 04.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_cmatrixluprec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t m1; + ae_int_t m2; + + + + /* + * Kernel case + */ + if( ae_minint(m, n, _state)<=ablascomplexblocksize(a, _state) ) + { + trfac_cmatrixlup2(a, offs, m, n, pivots, tmp, _state); + return; + } + + /* + * Preliminary step, make N>=M + * + * ( A1 ) + * A = ( ), where A1 is square + * ( A2 ) + * + * Factorize A1, update A2 + */ + if( m>n ) + { + trfac_cmatrixluprec(a, offs, n, n, pivots, tmp, _state); + for(i=0; i<=n-1; i++) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+n][offs+i], a->stride, "N", ae_v_len(0,m-n-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+n][offs+i], a->stride, &a->ptr.pp_complex[offs+n][pivots->ptr.p_int[offs+i]], a->stride, "N", ae_v_len(offs+n,offs+m-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+n][pivots->ptr.p_int[offs+i]], a->stride, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs+n,offs+m-1)); + } + cmatrixrighttrsm(m-n, n, a, offs, offs, ae_true, ae_true, 0, a, offs+n, offs, _state); + return; + } + + /* + * Non-kernel case + */ + ablascomplexsplitlength(a, m, &m1, &m2, _state); + trfac_cmatrixluprec(a, offs, m1, n, pivots, tmp, _state); + if( m2>0 ) + { + for(i=0; i<=m1-1; i++) + { + if( offs+i!=pivots->ptr.p_int[offs+i] ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+m1][offs+i], a->stride, "N", ae_v_len(0,m2-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+m1][offs+i], a->stride, &a->ptr.pp_complex[offs+m1][pivots->ptr.p_int[offs+i]], a->stride, "N", ae_v_len(offs+m1,offs+m-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+m1][pivots->ptr.p_int[offs+i]], a->stride, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs+m1,offs+m-1)); + } + } + cmatrixrighttrsm(m2, m1, a, offs, offs, ae_true, ae_true, 0, a, offs+m1, offs, _state); + cmatrixgemm(m-m1, n-m1, m1, ae_complex_from_d(-1.0), a, offs+m1, offs, 0, a, offs, offs+m1, 0, ae_complex_from_d(1.0), a, offs+m1, offs+m1, _state); + trfac_cmatrixluprec(a, offs+m1, m-m1, n-m1, pivots, tmp, _state); + for(i=0; i<=m2-1; i++) + { + if( offs+m1+i!=pivots->ptr.p_int[offs+m1+i] ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs][offs+m1+i], a->stride, "N", ae_v_len(0,m1-1)); + ae_v_cmove(&a->ptr.pp_complex[offs][offs+m1+i], a->stride, &a->ptr.pp_complex[offs][pivots->ptr.p_int[offs+m1+i]], a->stride, "N", ae_v_len(offs,offs+m1-1)); + ae_v_cmove(&a->ptr.pp_complex[offs][pivots->ptr.p_int[offs+m1+i]], a->stride, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs,offs+m1-1)); + } + } + } +} + + +/************************************************************************* +Recurrent real LU subroutine. +Never call it directly. + + -- ALGLIB routine -- + 04.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_rmatrixluprec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t m1; + ae_int_t m2; + + + + /* + * Kernel case + */ + if( ae_minint(m, n, _state)<=ablasblocksize(a, _state) ) + { + trfac_rmatrixlup2(a, offs, m, n, pivots, tmp, _state); + return; + } + + /* + * Preliminary step, make N>=M + * + * ( A1 ) + * A = ( ), where A1 is square + * ( A2 ) + * + * Factorize A1, update A2 + */ + if( m>n ) + { + trfac_rmatrixluprec(a, offs, n, n, pivots, tmp, _state); + for(i=0; i<=n-1; i++) + { + if( offs+i!=pivots->ptr.p_int[offs+i] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+n][offs+i], a->stride, ae_v_len(0,m-n-1)); + ae_v_move(&a->ptr.pp_double[offs+n][offs+i], a->stride, &a->ptr.pp_double[offs+n][pivots->ptr.p_int[offs+i]], a->stride, ae_v_len(offs+n,offs+m-1)); + ae_v_move(&a->ptr.pp_double[offs+n][pivots->ptr.p_int[offs+i]], a->stride, &tmp->ptr.p_double[0], 1, ae_v_len(offs+n,offs+m-1)); + } + } + rmatrixrighttrsm(m-n, n, a, offs, offs, ae_true, ae_true, 0, a, offs+n, offs, _state); + return; + } + + /* + * Non-kernel case + */ + ablassplitlength(a, m, &m1, &m2, _state); + trfac_rmatrixluprec(a, offs, m1, n, pivots, tmp, _state); + if( m2>0 ) + { + for(i=0; i<=m1-1; i++) + { + if( offs+i!=pivots->ptr.p_int[offs+i] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+m1][offs+i], a->stride, ae_v_len(0,m2-1)); + ae_v_move(&a->ptr.pp_double[offs+m1][offs+i], a->stride, &a->ptr.pp_double[offs+m1][pivots->ptr.p_int[offs+i]], a->stride, ae_v_len(offs+m1,offs+m-1)); + ae_v_move(&a->ptr.pp_double[offs+m1][pivots->ptr.p_int[offs+i]], a->stride, &tmp->ptr.p_double[0], 1, ae_v_len(offs+m1,offs+m-1)); + } + } + rmatrixrighttrsm(m2, m1, a, offs, offs, ae_true, ae_true, 0, a, offs+m1, offs, _state); + rmatrixgemm(m-m1, n-m1, m1, -1.0, a, offs+m1, offs, 0, a, offs, offs+m1, 0, 1.0, a, offs+m1, offs+m1, _state); + trfac_rmatrixluprec(a, offs+m1, m-m1, n-m1, pivots, tmp, _state); + for(i=0; i<=m2-1; i++) + { + if( offs+m1+i!=pivots->ptr.p_int[offs+m1+i] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs][offs+m1+i], a->stride, ae_v_len(0,m1-1)); + ae_v_move(&a->ptr.pp_double[offs][offs+m1+i], a->stride, &a->ptr.pp_double[offs][pivots->ptr.p_int[offs+m1+i]], a->stride, ae_v_len(offs,offs+m1-1)); + ae_v_move(&a->ptr.pp_double[offs][pivots->ptr.p_int[offs+m1+i]], a->stride, &tmp->ptr.p_double[0], 1, ae_v_len(offs,offs+m1-1)); + } + } + } +} + + +/************************************************************************* +Recurrent complex LU subroutine. +Never call it directly. + + -- ALGLIB routine -- + 04.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_cmatrixplurec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n1; + ae_int_t n2; + + + + /* + * Kernel case + */ + if( ae_minint(m, n, _state)<=ablascomplexblocksize(a, _state) ) + { + trfac_cmatrixplu2(a, offs, m, n, pivots, tmp, _state); + return; + } + + /* + * Preliminary step, make M>=N. + * + * A = (A1 A2), where A1 is square + * Factorize A1, update A2 + */ + if( n>m ) + { + trfac_cmatrixplurec(a, offs, m, m, pivots, tmp, _state); + for(i=0; i<=m-1; i++) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+i][offs+m], 1, "N", ae_v_len(0,n-m-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+i][offs+m], 1, &a->ptr.pp_complex[pivots->ptr.p_int[offs+i]][offs+m], 1, "N", ae_v_len(offs+m,offs+n-1)); + ae_v_cmove(&a->ptr.pp_complex[pivots->ptr.p_int[offs+i]][offs+m], 1, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs+m,offs+n-1)); + } + cmatrixlefttrsm(m, n-m, a, offs, offs, ae_false, ae_true, 0, a, offs, offs+m, _state); + return; + } + + /* + * Non-kernel case + */ + ablascomplexsplitlength(a, n, &n1, &n2, _state); + trfac_cmatrixplurec(a, offs, m, n1, pivots, tmp, _state); + if( n2>0 ) + { + for(i=0; i<=n1-1; i++) + { + if( offs+i!=pivots->ptr.p_int[offs+i] ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+i][offs+n1], 1, "N", ae_v_len(0,n2-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+i][offs+n1], 1, &a->ptr.pp_complex[pivots->ptr.p_int[offs+i]][offs+n1], 1, "N", ae_v_len(offs+n1,offs+n-1)); + ae_v_cmove(&a->ptr.pp_complex[pivots->ptr.p_int[offs+i]][offs+n1], 1, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs+n1,offs+n-1)); + } + } + cmatrixlefttrsm(n1, n2, a, offs, offs, ae_false, ae_true, 0, a, offs, offs+n1, _state); + cmatrixgemm(m-n1, n-n1, n1, ae_complex_from_d(-1.0), a, offs+n1, offs, 0, a, offs, offs+n1, 0, ae_complex_from_d(1.0), a, offs+n1, offs+n1, _state); + trfac_cmatrixplurec(a, offs+n1, m-n1, n-n1, pivots, tmp, _state); + for(i=0; i<=n2-1; i++) + { + if( offs+n1+i!=pivots->ptr.p_int[offs+n1+i] ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+n1+i][offs], 1, "N", ae_v_len(0,n1-1)); + ae_v_cmove(&a->ptr.pp_complex[offs+n1+i][offs], 1, &a->ptr.pp_complex[pivots->ptr.p_int[offs+n1+i]][offs], 1, "N", ae_v_len(offs,offs+n1-1)); + ae_v_cmove(&a->ptr.pp_complex[pivots->ptr.p_int[offs+n1+i]][offs], 1, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs,offs+n1-1)); + } + } + } +} + + +/************************************************************************* +Recurrent real LU subroutine. +Never call it directly. + + -- ALGLIB routine -- + 04.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_rmatrixplurec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n1; + ae_int_t n2; + + + + /* + * Kernel case + */ + if( ae_minint(m, n, _state)<=ablasblocksize(a, _state) ) + { + trfac_rmatrixplu2(a, offs, m, n, pivots, tmp, _state); + return; + } + + /* + * Preliminary step, make M>=N. + * + * A = (A1 A2), where A1 is square + * Factorize A1, update A2 + */ + if( n>m ) + { + trfac_rmatrixplurec(a, offs, m, m, pivots, tmp, _state); + for(i=0; i<=m-1; i++) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+i][offs+m], 1, ae_v_len(0,n-m-1)); + ae_v_move(&a->ptr.pp_double[offs+i][offs+m], 1, &a->ptr.pp_double[pivots->ptr.p_int[offs+i]][offs+m], 1, ae_v_len(offs+m,offs+n-1)); + ae_v_move(&a->ptr.pp_double[pivots->ptr.p_int[offs+i]][offs+m], 1, &tmp->ptr.p_double[0], 1, ae_v_len(offs+m,offs+n-1)); + } + rmatrixlefttrsm(m, n-m, a, offs, offs, ae_false, ae_true, 0, a, offs, offs+m, _state); + return; + } + + /* + * Non-kernel case + */ + ablassplitlength(a, n, &n1, &n2, _state); + trfac_rmatrixplurec(a, offs, m, n1, pivots, tmp, _state); + if( n2>0 ) + { + for(i=0; i<=n1-1; i++) + { + if( offs+i!=pivots->ptr.p_int[offs+i] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+i][offs+n1], 1, ae_v_len(0,n2-1)); + ae_v_move(&a->ptr.pp_double[offs+i][offs+n1], 1, &a->ptr.pp_double[pivots->ptr.p_int[offs+i]][offs+n1], 1, ae_v_len(offs+n1,offs+n-1)); + ae_v_move(&a->ptr.pp_double[pivots->ptr.p_int[offs+i]][offs+n1], 1, &tmp->ptr.p_double[0], 1, ae_v_len(offs+n1,offs+n-1)); + } + } + rmatrixlefttrsm(n1, n2, a, offs, offs, ae_false, ae_true, 0, a, offs, offs+n1, _state); + rmatrixgemm(m-n1, n-n1, n1, -1.0, a, offs+n1, offs, 0, a, offs, offs+n1, 0, 1.0, a, offs+n1, offs+n1, _state); + trfac_rmatrixplurec(a, offs+n1, m-n1, n-n1, pivots, tmp, _state); + for(i=0; i<=n2-1; i++) + { + if( offs+n1+i!=pivots->ptr.p_int[offs+n1+i] ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+n1+i][offs], 1, ae_v_len(0,n1-1)); + ae_v_move(&a->ptr.pp_double[offs+n1+i][offs], 1, &a->ptr.pp_double[pivots->ptr.p_int[offs+n1+i]][offs], 1, ae_v_len(offs,offs+n1-1)); + ae_v_move(&a->ptr.pp_double[pivots->ptr.p_int[offs+n1+i]][offs], 1, &tmp->ptr.p_double[0], 1, ae_v_len(offs,offs+n1-1)); + } + } + } +} + + +/************************************************************************* +Complex LUP kernel + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_cmatrixlup2(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t jp; + ae_complex s; + + + + /* + * Quick return if possible + */ + if( m==0||n==0 ) + { + return; + } + + /* + * main cycle + */ + for(j=0; j<=ae_minint(m-1, n-1, _state); j++) + { + + /* + * Find pivot, swap columns + */ + jp = j; + for(i=j+1; i<=n-1; i++) + { + if( ae_fp_greater(ae_c_abs(a->ptr.pp_complex[offs+j][offs+i], _state),ae_c_abs(a->ptr.pp_complex[offs+j][offs+jp], _state)) ) + { + jp = i; + } + } + pivots->ptr.p_int[offs+j] = offs+jp; + if( jp!=j ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs][offs+j], a->stride, "N", ae_v_len(0,m-1)); + ae_v_cmove(&a->ptr.pp_complex[offs][offs+j], a->stride, &a->ptr.pp_complex[offs][offs+jp], a->stride, "N", ae_v_len(offs,offs+m-1)); + ae_v_cmove(&a->ptr.pp_complex[offs][offs+jp], a->stride, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs,offs+m-1)); + } + + /* + * LU decomposition of 1x(N-J) matrix + */ + if( ae_c_neq_d(a->ptr.pp_complex[offs+j][offs+j],0)&&j+1<=n-1 ) + { + s = ae_c_d_div(1,a->ptr.pp_complex[offs+j][offs+j]); + ae_v_cmulc(&a->ptr.pp_complex[offs+j][offs+j+1], 1, ae_v_len(offs+j+1,offs+n-1), s); + } + + /* + * Update trailing (M-J-1)x(N-J-1) matrix + */ + if( jptr.p_complex[0], 1, &a->ptr.pp_complex[offs+j+1][offs+j], a->stride, "N", ae_v_len(0,m-j-2)); + ae_v_cmoveneg(&tmp->ptr.p_complex[m], 1, &a->ptr.pp_complex[offs+j][offs+j+1], 1, "N", ae_v_len(m,m+n-j-2)); + cmatrixrank1(m-j-1, n-j-1, a, offs+j+1, offs+j+1, tmp, 0, tmp, m, _state); + } + } +} + + +/************************************************************************* +Real LUP kernel + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +static void trfac_rmatrixlup2(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t jp; + double s; + + + + /* + * Quick return if possible + */ + if( m==0||n==0 ) + { + return; + } + + /* + * main cycle + */ + for(j=0; j<=ae_minint(m-1, n-1, _state); j++) + { + + /* + * Find pivot, swap columns + */ + jp = j; + for(i=j+1; i<=n-1; i++) + { + if( ae_fp_greater(ae_fabs(a->ptr.pp_double[offs+j][offs+i], _state),ae_fabs(a->ptr.pp_double[offs+j][offs+jp], _state)) ) + { + jp = i; + } + } + pivots->ptr.p_int[offs+j] = offs+jp; + if( jp!=j ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs][offs+j], a->stride, ae_v_len(0,m-1)); + ae_v_move(&a->ptr.pp_double[offs][offs+j], a->stride, &a->ptr.pp_double[offs][offs+jp], a->stride, ae_v_len(offs,offs+m-1)); + ae_v_move(&a->ptr.pp_double[offs][offs+jp], a->stride, &tmp->ptr.p_double[0], 1, ae_v_len(offs,offs+m-1)); + } + + /* + * LU decomposition of 1x(N-J) matrix + */ + if( ae_fp_neq(a->ptr.pp_double[offs+j][offs+j],0)&&j+1<=n-1 ) + { + s = 1/a->ptr.pp_double[offs+j][offs+j]; + ae_v_muld(&a->ptr.pp_double[offs+j][offs+j+1], 1, ae_v_len(offs+j+1,offs+n-1), s); + } + + /* + * Update trailing (M-J-1)x(N-J-1) matrix + */ + if( jptr.p_double[0], 1, &a->ptr.pp_double[offs+j+1][offs+j], a->stride, ae_v_len(0,m-j-2)); + ae_v_moveneg(&tmp->ptr.p_double[m], 1, &a->ptr.pp_double[offs+j][offs+j+1], 1, ae_v_len(m,m+n-j-2)); + rmatrixrank1(m-j-1, n-j-1, a, offs+j+1, offs+j+1, tmp, 0, tmp, m, _state); + } + } +} + + +/************************************************************************* +Complex PLU kernel + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + June 30, 1992 +*************************************************************************/ +static void trfac_cmatrixplu2(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t jp; + ae_complex s; + + + + /* + * Quick return if possible + */ + if( m==0||n==0 ) + { + return; + } + for(j=0; j<=ae_minint(m-1, n-1, _state); j++) + { + + /* + * Find pivot and test for singularity. + */ + jp = j; + for(i=j+1; i<=m-1; i++) + { + if( ae_fp_greater(ae_c_abs(a->ptr.pp_complex[offs+i][offs+j], _state),ae_c_abs(a->ptr.pp_complex[offs+jp][offs+j], _state)) ) + { + jp = i; + } + } + pivots->ptr.p_int[offs+j] = offs+jp; + if( ae_c_neq_d(a->ptr.pp_complex[offs+jp][offs+j],0) ) + { + + /* + *Apply the interchange to rows + */ + if( jp!=j ) + { + for(i=0; i<=n-1; i++) + { + s = a->ptr.pp_complex[offs+j][offs+i]; + a->ptr.pp_complex[offs+j][offs+i] = a->ptr.pp_complex[offs+jp][offs+i]; + a->ptr.pp_complex[offs+jp][offs+i] = s; + } + } + + /* + *Compute elements J+1:M of J-th column. + */ + if( j+1<=m-1 ) + { + s = ae_c_d_div(1,a->ptr.pp_complex[offs+j][offs+j]); + ae_v_cmulc(&a->ptr.pp_complex[offs+j+1][offs+j], a->stride, ae_v_len(offs+j+1,offs+m-1), s); + } + } + if( jptr.p_complex[0], 1, &a->ptr.pp_complex[offs+j+1][offs+j], a->stride, "N", ae_v_len(0,m-j-2)); + ae_v_cmoveneg(&tmp->ptr.p_complex[m], 1, &a->ptr.pp_complex[offs+j][offs+j+1], 1, "N", ae_v_len(m,m+n-j-2)); + cmatrixrank1(m-j-1, n-j-1, a, offs+j+1, offs+j+1, tmp, 0, tmp, m, _state); + } + } +} + + +/************************************************************************* +Real PLU kernel + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + June 30, 1992 +*************************************************************************/ +static void trfac_rmatrixplu2(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t jp; + double s; + + + + /* + * Quick return if possible + */ + if( m==0||n==0 ) + { + return; + } + for(j=0; j<=ae_minint(m-1, n-1, _state); j++) + { + + /* + * Find pivot and test for singularity. + */ + jp = j; + for(i=j+1; i<=m-1; i++) + { + if( ae_fp_greater(ae_fabs(a->ptr.pp_double[offs+i][offs+j], _state),ae_fabs(a->ptr.pp_double[offs+jp][offs+j], _state)) ) + { + jp = i; + } + } + pivots->ptr.p_int[offs+j] = offs+jp; + if( ae_fp_neq(a->ptr.pp_double[offs+jp][offs+j],0) ) + { + + /* + *Apply the interchange to rows + */ + if( jp!=j ) + { + for(i=0; i<=n-1; i++) + { + s = a->ptr.pp_double[offs+j][offs+i]; + a->ptr.pp_double[offs+j][offs+i] = a->ptr.pp_double[offs+jp][offs+i]; + a->ptr.pp_double[offs+jp][offs+i] = s; + } + } + + /* + *Compute elements J+1:M of J-th column. + */ + if( j+1<=m-1 ) + { + s = 1/a->ptr.pp_double[offs+j][offs+j]; + ae_v_muld(&a->ptr.pp_double[offs+j+1][offs+j], a->stride, ae_v_len(offs+j+1,offs+m-1), s); + } + } + if( jptr.p_double[0], 1, &a->ptr.pp_double[offs+j+1][offs+j], a->stride, ae_v_len(0,m-j-2)); + ae_v_moveneg(&tmp->ptr.p_double[m], 1, &a->ptr.pp_double[offs+j][offs+j+1], 1, ae_v_len(m,m+n-j-2)); + rmatrixrank1(m-j-1, n-j-1, a, offs+j+1, offs+j+1, tmp, 0, tmp, m, _state); + } + } +} + + +/************************************************************************* +Recursive computational subroutine for HPDMatrixCholesky + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +static ae_bool trfac_hpdmatrixcholeskyrec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t n1; + ae_int_t n2; + ae_bool result; + + + + /* + * check N + */ + if( n<1 ) + { + result = ae_false; + return result; + } + + /* + * Prepare buffer + */ + if( tmp->cnt<2*n ) + { + ae_vector_set_length(tmp, 2*n, _state); + } + + /* + * special cases + */ + if( n==1 ) + { + if( ae_fp_greater(a->ptr.pp_complex[offs][offs].x,0) ) + { + a->ptr.pp_complex[offs][offs] = ae_complex_from_d(ae_sqrt(a->ptr.pp_complex[offs][offs].x, _state)); + result = ae_true; + } + else + { + result = ae_false; + } + return result; + } + if( n<=ablascomplexblocksize(a, _state) ) + { + result = trfac_hpdmatrixcholesky2(a, offs, n, isupper, tmp, _state); + return result; + } + + /* + * general case: split task in cache-oblivious manner + */ + result = ae_true; + ablascomplexsplitlength(a, n, &n1, &n2, _state); + result = trfac_hpdmatrixcholeskyrec(a, offs, n1, isupper, tmp, _state); + if( !result ) + { + return result; + } + if( n2>0 ) + { + if( isupper ) + { + cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, ae_false, 2, a, offs, offs+n1, _state); + cmatrixsyrk(n2, n1, -1.0, a, offs, offs+n1, 2, 1.0, a, offs+n1, offs+n1, isupper, _state); + } + else + { + cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, ae_false, 2, a, offs+n1, offs, _state); + cmatrixsyrk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, a, offs+n1, offs+n1, isupper, _state); + } + result = trfac_hpdmatrixcholeskyrec(a, offs+n1, n2, isupper, tmp, _state); + if( !result ) + { + return result; + } + } + return result; +} + + +/************************************************************************* +Level-2 Hermitian Cholesky subroutine. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static ae_bool trfac_hpdmatrixcholesky2(/* Complex */ ae_matrix* aaa, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double ajj; + ae_complex v; + double r; + ae_bool result; + + + result = ae_true; + if( n<0 ) + { + result = ae_false; + return result; + } + + /* + * Quick return if possible + */ + if( n==0 ) + { + return result; + } + if( isupper ) + { + + /* + * Compute the Cholesky factorization A = U'*U. + */ + for(j=0; j<=n-1; j++) + { + + /* + * Compute U(J,J) and test for non-positive-definiteness. + */ + v = ae_v_cdotproduct(&aaa->ptr.pp_complex[offs][offs+j], aaa->stride, "Conj", &aaa->ptr.pp_complex[offs][offs+j], aaa->stride, "N", ae_v_len(offs,offs+j-1)); + ajj = ae_c_sub(aaa->ptr.pp_complex[offs+j][offs+j],v).x; + if( ae_fp_less_eq(ajj,0) ) + { + aaa->ptr.pp_complex[offs+j][offs+j] = ae_complex_from_d(ajj); + result = ae_false; + return result; + } + ajj = ae_sqrt(ajj, _state); + aaa->ptr.pp_complex[offs+j][offs+j] = ae_complex_from_d(ajj); + + /* + * Compute elements J+1:N-1 of row J. + */ + if( j0 ) + { + ae_v_cmoveneg(&tmp->ptr.p_complex[0], 1, &aaa->ptr.pp_complex[offs][offs+j], aaa->stride, "Conj", ae_v_len(0,j-1)); + cmatrixmv(n-j-1, j, aaa, offs, offs+j+1, 1, tmp, 0, tmp, n, _state); + ae_v_cadd(&aaa->ptr.pp_complex[offs+j][offs+j+1], 1, &tmp->ptr.p_complex[n], 1, "N", ae_v_len(offs+j+1,offs+n-1)); + } + r = 1/ajj; + ae_v_cmuld(&aaa->ptr.pp_complex[offs+j][offs+j+1], 1, ae_v_len(offs+j+1,offs+n-1), r); + } + } + } + else + { + + /* + * Compute the Cholesky factorization A = L*L'. + */ + for(j=0; j<=n-1; j++) + { + + /* + * Compute L(J+1,J+1) and test for non-positive-definiteness. + */ + v = ae_v_cdotproduct(&aaa->ptr.pp_complex[offs+j][offs], 1, "Conj", &aaa->ptr.pp_complex[offs+j][offs], 1, "N", ae_v_len(offs,offs+j-1)); + ajj = ae_c_sub(aaa->ptr.pp_complex[offs+j][offs+j],v).x; + if( ae_fp_less_eq(ajj,0) ) + { + aaa->ptr.pp_complex[offs+j][offs+j] = ae_complex_from_d(ajj); + result = ae_false; + return result; + } + ajj = ae_sqrt(ajj, _state); + aaa->ptr.pp_complex[offs+j][offs+j] = ae_complex_from_d(ajj); + + /* + * Compute elements J+1:N of column J. + */ + if( j0 ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &aaa->ptr.pp_complex[offs+j][offs], 1, "Conj", ae_v_len(0,j-1)); + cmatrixmv(n-j-1, j, aaa, offs+j+1, offs, 0, tmp, 0, tmp, n, _state); + for(i=0; i<=n-j-2; i++) + { + aaa->ptr.pp_complex[offs+j+1+i][offs+j] = ae_c_div_d(ae_c_sub(aaa->ptr.pp_complex[offs+j+1+i][offs+j],tmp->ptr.p_complex[n+i]),ajj); + } + } + else + { + for(i=0; i<=n-j-2; i++) + { + aaa->ptr.pp_complex[offs+j+1+i][offs+j] = ae_c_div_d(aaa->ptr.pp_complex[offs+j+1+i][offs+j],ajj); + } + } + } + } + } + return result; +} + + +/************************************************************************* +Level-2 Cholesky subroutine + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static ae_bool trfac_spdmatrixcholesky2(/* Real */ ae_matrix* aaa, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double ajj; + double v; + double r; + ae_bool result; + + + result = ae_true; + if( n<0 ) + { + result = ae_false; + return result; + } + + /* + * Quick return if possible + */ + if( n==0 ) + { + return result; + } + if( isupper ) + { + + /* + * Compute the Cholesky factorization A = U'*U. + */ + for(j=0; j<=n-1; j++) + { + + /* + * Compute U(J,J) and test for non-positive-definiteness. + */ + v = ae_v_dotproduct(&aaa->ptr.pp_double[offs][offs+j], aaa->stride, &aaa->ptr.pp_double[offs][offs+j], aaa->stride, ae_v_len(offs,offs+j-1)); + ajj = aaa->ptr.pp_double[offs+j][offs+j]-v; + if( ae_fp_less_eq(ajj,0) ) + { + aaa->ptr.pp_double[offs+j][offs+j] = ajj; + result = ae_false; + return result; + } + ajj = ae_sqrt(ajj, _state); + aaa->ptr.pp_double[offs+j][offs+j] = ajj; + + /* + * Compute elements J+1:N-1 of row J. + */ + if( j0 ) + { + ae_v_moveneg(&tmp->ptr.p_double[0], 1, &aaa->ptr.pp_double[offs][offs+j], aaa->stride, ae_v_len(0,j-1)); + rmatrixmv(n-j-1, j, aaa, offs, offs+j+1, 1, tmp, 0, tmp, n, _state); + ae_v_add(&aaa->ptr.pp_double[offs+j][offs+j+1], 1, &tmp->ptr.p_double[n], 1, ae_v_len(offs+j+1,offs+n-1)); + } + r = 1/ajj; + ae_v_muld(&aaa->ptr.pp_double[offs+j][offs+j+1], 1, ae_v_len(offs+j+1,offs+n-1), r); + } + } + } + else + { + + /* + * Compute the Cholesky factorization A = L*L'. + */ + for(j=0; j<=n-1; j++) + { + + /* + * Compute L(J+1,J+1) and test for non-positive-definiteness. + */ + v = ae_v_dotproduct(&aaa->ptr.pp_double[offs+j][offs], 1, &aaa->ptr.pp_double[offs+j][offs], 1, ae_v_len(offs,offs+j-1)); + ajj = aaa->ptr.pp_double[offs+j][offs+j]-v; + if( ae_fp_less_eq(ajj,0) ) + { + aaa->ptr.pp_double[offs+j][offs+j] = ajj; + result = ae_false; + return result; + } + ajj = ae_sqrt(ajj, _state); + aaa->ptr.pp_double[offs+j][offs+j] = ajj; + + /* + * Compute elements J+1:N of column J. + */ + if( j0 ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &aaa->ptr.pp_double[offs+j][offs], 1, ae_v_len(0,j-1)); + rmatrixmv(n-j-1, j, aaa, offs+j+1, offs, 0, tmp, 0, tmp, n, _state); + for(i=0; i<=n-j-2; i++) + { + aaa->ptr.pp_double[offs+j+1+i][offs+j] = (aaa->ptr.pp_double[offs+j+1+i][offs+j]-tmp->ptr.p_double[n+i])/ajj; + } + } + else + { + for(i=0; i<=n-j-2; i++) + { + aaa->ptr.pp_double[offs+j+1+i][offs+j] = aaa->ptr.pp_double[offs+j+1+i][offs+j]/ajj; + } + } + } + } + } + return result; +} + + + + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcond1(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_vector t; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "RMatrixRCond1: N<1!", _state); + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_fabs(a->ptr.pp_double[i][j], _state); + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + rmatrixlu(a, n, n, &pivots, _state); + rcond_rmatrixrcondluinternal(a, n, ae_true, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcondinf(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "RMatrixRCondInf: N<1!", _state); + nrm = 0; + for(i=0; i<=n-1; i++) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+ae_fabs(a->ptr.pp_double[i][j], _state); + } + nrm = ae_maxreal(nrm, v, _state); + } + rmatrixlu(a, n, n, &pivots, _state); + rcond_rmatrixrcondluinternal(a, n, ae_false, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - symmetric positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixrcond(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + double v; + double nrm; + ae_vector t; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + if( i==j ) + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_fabs(a->ptr.pp_double[i][i], _state); + } + else + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_fabs(a->ptr.pp_double[i][j], _state); + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_fabs(a->ptr.pp_double[i][j], _state); + } + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + if( spdmatrixcholesky(a, n, isupper, _state) ) + { + rcond_spdmatrixrcondcholeskyinternal(a, n, isupper, ae_true, nrm, &v, _state); + result = v; + } + else + { + result = -1; + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcond1(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_vector t; + ae_int_t j1; + ae_int_t j2; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "RMatrixTRRCond1: N<1!", _state); + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + for(j=j1; j<=j2; j++) + { + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_fabs(a->ptr.pp_double[i][j], _state); + } + if( isunit ) + { + t.ptr.p_double[i] = t.ptr.p_double[i]+1; + } + else + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_fabs(a->ptr.pp_double[i][i], _state); + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + rcond_rmatrixrcondtrinternal(a, n, isupper, isunit, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcondinf(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_int_t j1; + ae_int_t j2; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "RMatrixTRRCondInf: N<1!", _state); + nrm = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + v = 0; + for(j=j1; j<=j2; j++) + { + v = v+ae_fabs(a->ptr.pp_double[i][j], _state); + } + if( isunit ) + { + v = v+1; + } + else + { + v = v+ae_fabs(a->ptr.pp_double[i][i], _state); + } + nrm = ae_maxreal(nrm, v, _state); + } + rcond_rmatrixrcondtrinternal(a, n, isupper, isunit, ae_false, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - Hermitian positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixrcond(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + double v; + double nrm; + ae_vector t; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + if( i==j ) + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_c_abs(a->ptr.pp_complex[i][i], _state); + } + else + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_c_abs(a->ptr.pp_complex[i][j], _state); + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_c_abs(a->ptr.pp_complex[i][j], _state); + } + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + if( hpdmatrixcholesky(a, n, isupper, _state) ) + { + rcond_hpdmatrixrcondcholeskyinternal(a, n, isupper, ae_true, nrm, &v, _state); + result = v; + } + else + { + result = -1; + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcond1(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_vector t; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "CMatrixRCond1: N<1!", _state); + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_c_abs(a->ptr.pp_complex[i][j], _state); + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + cmatrixlu(a, n, n, &pivots, _state); + rcond_cmatrixrcondluinternal(a, n, ae_true, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcondinf(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "CMatrixRCondInf: N<1!", _state); + nrm = 0; + for(i=0; i<=n-1; i++) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+ae_c_abs(a->ptr.pp_complex[i][j], _state); + } + nrm = ae_maxreal(nrm, v, _state); + } + cmatrixlu(a, n, n, &pivots, _state); + rcond_cmatrixrcondluinternal(a, n, ae_false, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcond1(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_state *_state) +{ + double v; + double result; + + + rcond_rmatrixrcondluinternal(lua, n, ae_true, ae_false, 0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcondinf(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_state *_state) +{ + double v; + double result; + + + rcond_rmatrixrcondluinternal(lua, n, ae_false, ae_false, 0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixcholeskyrcond(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + double v; + double result; + + + rcond_spdmatrixrcondcholeskyinternal(a, n, isupper, ae_false, 0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixcholeskyrcond(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + double v; + double result; + + + rcond_hpdmatrixrcondcholeskyinternal(a, n, isupper, ae_false, 0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcond1(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_state *_state) +{ + double v; + double result; + + + ae_assert(n>=1, "CMatrixLURCond1: N<1!", _state); + rcond_cmatrixrcondluinternal(lua, n, ae_true, ae_false, 0.0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcondinf(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_state *_state) +{ + double v; + double result; + + + ae_assert(n>=1, "CMatrixLURCondInf: N<1!", _state); + rcond_cmatrixrcondluinternal(lua, n, ae_false, ae_false, 0.0, &v, _state); + result = v; + return result; +} + + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcond1(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_vector t; + ae_int_t j1; + ae_int_t j2; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=1, "RMatrixTRRCond1: N<1!", _state); + ae_vector_set_length(&t, n, _state); + for(i=0; i<=n-1; i++) + { + t.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + for(j=j1; j<=j2; j++) + { + t.ptr.p_double[j] = t.ptr.p_double[j]+ae_c_abs(a->ptr.pp_complex[i][j], _state); + } + if( isunit ) + { + t.ptr.p_double[i] = t.ptr.p_double[i]+1; + } + else + { + t.ptr.p_double[i] = t.ptr.p_double[i]+ae_c_abs(a->ptr.pp_complex[i][i], _state); + } + } + nrm = 0; + for(i=0; i<=n-1; i++) + { + nrm = ae_maxreal(nrm, t.ptr.p_double[i], _state); + } + rcond_cmatrixrcondtrinternal(a, n, isupper, isunit, ae_true, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcondinf(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double nrm; + ae_vector pivots; + ae_int_t j1; + ae_int_t j2; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "RMatrixTRRCondInf: N<1!", _state); + nrm = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + v = 0; + for(j=j1; j<=j2; j++) + { + v = v+ae_c_abs(a->ptr.pp_complex[i][j], _state); + } + if( isunit ) + { + v = v+1; + } + else + { + v = v+ae_c_abs(a->ptr.pp_complex[i][i], _state); + } + nrm = ae_maxreal(nrm, v, _state); + } + rcond_cmatrixrcondtrinternal(a, n, isupper, isunit, ae_false, nrm, &v, _state); + result = v; + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Threshold for rcond: matrices with condition number beyond this threshold +are considered singular. + +Threshold must be far enough from underflow, at least Sqr(Threshold) must +be greater than underflow. +*************************************************************************/ +double rcondthreshold(ae_state *_state) +{ + double result; + + + result = ae_sqrt(ae_sqrt(ae_minrealnumber, _state), _state); + return result; +} + + +/************************************************************************* +Internal subroutine for condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static void rcond_rmatrixrcondtrinternal(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_bool onenorm, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector ex; + ae_vector ev; + ae_vector iwork; + ae_vector tmp; + ae_int_t i; + ae_int_t j; + ae_int_t kase; + ae_int_t kase1; + ae_int_t j1; + ae_int_t j2; + double ainvnm; + double maxgrowth; + double s; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&ex, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ev, 0, DT_REAL, _state, ae_true); + ae_vector_init(&iwork, 0, DT_INT, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * RC=0 if something happens + */ + *rc = 0; + + /* + * init + */ + if( onenorm ) + { + kase1 = 1; + } + else + { + kase1 = 2; + } + ae_vector_set_length(&iwork, n+1, _state); + ae_vector_set_length(&tmp, n, _state); + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + s = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + for(j=j1; j<=j2; j++) + { + s = ae_maxreal(s, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + if( isunit ) + { + s = ae_maxreal(s, 1, _state); + } + else + { + s = ae_maxreal(s, ae_fabs(a->ptr.pp_double[i][i], _state), _state); + } + } + if( ae_fp_eq(s,0) ) + { + s = 1; + } + s = 1/s; + + /* + * Scale according to S + */ + anorm = anorm*s; + + /* + * Quick return if possible + * We assume that ANORM<>0 after this block + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + + /* + * Estimate the norm of inv(A). + */ + ainvnm = 0; + kase = 0; + for(;;) + { + rcond_rmatrixestimatenorm(n, &ev, &ex, &iwork, &ainvnm, &kase, _state); + if( kase==0 ) + { + break; + } + + /* + * from 1-based array to 0-based + */ + for(i=0; i<=n-1; i++) + { + ex.ptr.p_double[i] = ex.ptr.p_double[i+1]; + } + + /* + * multiply by inv(A) or inv(A') + */ + if( kase==kase1 ) + { + + /* + * multiply by inv(A) + */ + if( !rmatrixscaledtrsafesolve(a, s, n, &ex, isupper, 0, isunit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * multiply by inv(A') + */ + if( !rmatrixscaledtrsafesolve(a, s, n, &ex, isupper, 1, isunit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + + /* + * from 0-based array to 1-based + */ + for(i=n-1; i>=0; i--) + { + ex.ptr.p_double[i+1] = ex.ptr.p_double[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + *rc = 1/ainvnm; + *rc = *rc/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + March 31, 1993 +*************************************************************************/ +static void rcond_cmatrixrcondtrinternal(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_bool onenorm, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector ex; + ae_vector cwork2; + ae_vector cwork3; + ae_vector cwork4; + ae_vector isave; + ae_vector rsave; + ae_int_t kase; + ae_int_t kase1; + double ainvnm; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + double s; + double maxgrowth; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&ex, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork2, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork3, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork4, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&isave, 0, DT_INT, _state, ae_true); + ae_vector_init(&rsave, 0, DT_REAL, _state, ae_true); + + + /* + * RC=0 if something happens + */ + *rc = 0; + + /* + * init + */ + if( n<=0 ) + { + ae_frame_leave(_state); + return; + } + if( n==0 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&cwork2, n+1, _state); + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + s = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i+1; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i-1; + } + for(j=j1; j<=j2; j++) + { + s = ae_maxreal(s, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + if( isunit ) + { + s = ae_maxreal(s, 1, _state); + } + else + { + s = ae_maxreal(s, ae_c_abs(a->ptr.pp_complex[i][i], _state), _state); + } + } + if( ae_fp_eq(s,0) ) + { + s = 1; + } + s = 1/s; + + /* + * Scale according to S + */ + anorm = anorm*s; + + /* + * Quick return if possible + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Estimate the norm of inv(A). + */ + ainvnm = 0; + if( onenorm ) + { + kase1 = 1; + } + else + { + kase1 = 2; + } + kase = 0; + for(;;) + { + rcond_cmatrixestimatenorm(n, &cwork4, &ex, &ainvnm, &kase, &isave, &rsave, _state); + if( kase==0 ) + { + break; + } + + /* + * From 1-based to 0-based + */ + for(i=0; i<=n-1; i++) + { + ex.ptr.p_complex[i] = ex.ptr.p_complex[i+1]; + } + + /* + * multiply by inv(A) or inv(A') + */ + if( kase==kase1 ) + { + + /* + * multiply by inv(A) + */ + if( !cmatrixscaledtrsafesolve(a, s, n, &ex, isupper, 0, isunit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * multiply by inv(A') + */ + if( !cmatrixscaledtrsafesolve(a, s, n, &ex, isupper, 2, isunit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + + /* + * from 0-based to 1-based + */ + for(i=n-1; i>=0; i--) + { + ex.ptr.p_complex[i+1] = ex.ptr.p_complex[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + *rc = 1/ainvnm; + *rc = *rc/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine for condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static void rcond_spdmatrixrcondcholeskyinternal(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + ae_bool isnormprovided, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t kase; + double ainvnm; + ae_vector ex; + ae_vector ev; + ae_vector tmp; + ae_vector iwork; + double sa; + double v; + double maxgrowth; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&ex, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ev, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&iwork, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "Assertion failed", _state); + ae_vector_set_length(&tmp, n, _state); + + /* + * RC=0 if something happens + */ + *rc = 0; + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + sa = 0; + if( isupper ) + { + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + sa = ae_maxreal(sa, ae_c_abs(ae_complex_from_d(cha->ptr.pp_double[i][j]), _state), _state); + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i; j++) + { + sa = ae_maxreal(sa, ae_c_abs(ae_complex_from_d(cha->ptr.pp_double[i][j]), _state), _state); + } + } + } + if( ae_fp_eq(sa,0) ) + { + sa = 1; + } + sa = 1/sa; + + /* + * Estimate the norm of A. + */ + if( !isnormprovided ) + { + kase = 0; + anorm = 0; + for(;;) + { + rcond_rmatrixestimatenorm(n, &ev, &ex, &iwork, &anorm, &kase, _state); + if( kase==0 ) + { + break; + } + if( isupper ) + { + + /* + * Multiply by U + */ + for(i=1; i<=n; i++) + { + v = ae_v_dotproduct(&cha->ptr.pp_double[i-1][i-1], 1, &ex.ptr.p_double[i], 1, ae_v_len(i-1,n-1)); + ex.ptr.p_double[i] = v; + } + ae_v_muld(&ex.ptr.p_double[1], 1, ae_v_len(1,n), sa); + + /* + * Multiply by U' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_double[i+1]; + ae_v_addd(&tmp.ptr.p_double[i], 1, &cha->ptr.pp_double[i][i], 1, ae_v_len(i,n-1), v); + } + ae_v_move(&ex.ptr.p_double[1], 1, &tmp.ptr.p_double[0], 1, ae_v_len(1,n)); + ae_v_muld(&ex.ptr.p_double[1], 1, ae_v_len(1,n), sa); + } + else + { + + /* + * Multiply by L' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_double[i+1]; + ae_v_addd(&tmp.ptr.p_double[0], 1, &cha->ptr.pp_double[i][0], 1, ae_v_len(0,i), v); + } + ae_v_move(&ex.ptr.p_double[1], 1, &tmp.ptr.p_double[0], 1, ae_v_len(1,n)); + ae_v_muld(&ex.ptr.p_double[1], 1, ae_v_len(1,n), sa); + + /* + * Multiply by L + */ + for(i=n; i>=1; i--) + { + v = ae_v_dotproduct(&cha->ptr.pp_double[i-1][0], 1, &ex.ptr.p_double[1], 1, ae_v_len(0,i-1)); + ex.ptr.p_double[i] = v; + } + ae_v_muld(&ex.ptr.p_double[1], 1, ae_v_len(1,n), sa); + } + } + } + + /* + * Quick return if possible + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + + /* + * Estimate the 1-norm of inv(A). + */ + kase = 0; + for(;;) + { + rcond_rmatrixestimatenorm(n, &ev, &ex, &iwork, &ainvnm, &kase, _state); + if( kase==0 ) + { + break; + } + for(i=0; i<=n-1; i++) + { + ex.ptr.p_double[i] = ex.ptr.p_double[i+1]; + } + if( isupper ) + { + + /* + * Multiply by inv(U'). + */ + if( !rmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 1, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(U). + */ + if( !rmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 0, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * Multiply by inv(L). + */ + if( !rmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 0, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(L'). + */ + if( !rmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 1, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + for(i=n-1; i>=0; i--) + { + ex.ptr.p_double[i+1] = ex.ptr.p_double[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + v = 1/ainvnm; + *rc = v/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine for condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static void rcond_hpdmatrixrcondcholeskyinternal(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + ae_bool isnormprovided, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector isave; + ae_vector rsave; + ae_vector ex; + ae_vector ev; + ae_vector tmp; + ae_int_t kase; + double ainvnm; + ae_complex v; + ae_int_t i; + ae_int_t j; + double sa; + double maxgrowth; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&isave, 0, DT_INT, _state, ae_true); + ae_vector_init(&rsave, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ex, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&ev, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>=1, "Assertion failed", _state); + ae_vector_set_length(&tmp, n, _state); + + /* + * RC=0 if something happens + */ + *rc = 0; + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + sa = 0; + if( isupper ) + { + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + sa = ae_maxreal(sa, ae_c_abs(cha->ptr.pp_complex[i][j], _state), _state); + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i; j++) + { + sa = ae_maxreal(sa, ae_c_abs(cha->ptr.pp_complex[i][j], _state), _state); + } + } + } + if( ae_fp_eq(sa,0) ) + { + sa = 1; + } + sa = 1/sa; + + /* + * Estimate the norm of A + */ + if( !isnormprovided ) + { + anorm = 0; + kase = 0; + for(;;) + { + rcond_cmatrixestimatenorm(n, &ev, &ex, &anorm, &kase, &isave, &rsave, _state); + if( kase==0 ) + { + break; + } + if( isupper ) + { + + /* + * Multiply by U + */ + for(i=1; i<=n; i++) + { + v = ae_v_cdotproduct(&cha->ptr.pp_complex[i-1][i-1], 1, "N", &ex.ptr.p_complex[i], 1, "N", ae_v_len(i-1,n-1)); + ex.ptr.p_complex[i] = v; + } + ae_v_cmuld(&ex.ptr.p_complex[1], 1, ae_v_len(1,n), sa); + + /* + * Multiply by U' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_complex[i+1]; + ae_v_caddc(&tmp.ptr.p_complex[i], 1, &cha->ptr.pp_complex[i][i], 1, "Conj", ae_v_len(i,n-1), v); + } + ae_v_cmove(&ex.ptr.p_complex[1], 1, &tmp.ptr.p_complex[0], 1, "N", ae_v_len(1,n)); + ae_v_cmuld(&ex.ptr.p_complex[1], 1, ae_v_len(1,n), sa); + } + else + { + + /* + * Multiply by L' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_complex[i+1]; + ae_v_caddc(&tmp.ptr.p_complex[0], 1, &cha->ptr.pp_complex[i][0], 1, "Conj", ae_v_len(0,i), v); + } + ae_v_cmove(&ex.ptr.p_complex[1], 1, &tmp.ptr.p_complex[0], 1, "N", ae_v_len(1,n)); + ae_v_cmuld(&ex.ptr.p_complex[1], 1, ae_v_len(1,n), sa); + + /* + * Multiply by L + */ + for(i=n; i>=1; i--) + { + v = ae_v_cdotproduct(&cha->ptr.pp_complex[i-1][0], 1, "N", &ex.ptr.p_complex[1], 1, "N", ae_v_len(0,i-1)); + ex.ptr.p_complex[i] = v; + } + ae_v_cmuld(&ex.ptr.p_complex[1], 1, ae_v_len(1,n), sa); + } + } + } + + /* + * Quick return if possible + * After this block we assume that ANORM<>0 + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + + /* + * Estimate the norm of inv(A). + */ + ainvnm = 0; + kase = 0; + for(;;) + { + rcond_cmatrixestimatenorm(n, &ev, &ex, &ainvnm, &kase, &isave, &rsave, _state); + if( kase==0 ) + { + break; + } + for(i=0; i<=n-1; i++) + { + ex.ptr.p_complex[i] = ex.ptr.p_complex[i+1]; + } + if( isupper ) + { + + /* + * Multiply by inv(U'). + */ + if( !cmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 2, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(U). + */ + if( !cmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 0, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * Multiply by inv(L). + */ + if( !cmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 0, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(L'). + */ + if( !cmatrixscaledtrsafesolve(cha, sa, n, &ex, isupper, 2, ae_false, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + for(i=n-1; i>=0; i--) + { + ex.ptr.p_complex[i+1] = ex.ptr.p_complex[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + *rc = 1/ainvnm; + *rc = *rc/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine for condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static void rcond_rmatrixrcondluinternal(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_bool onenorm, + ae_bool isanormprovided, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector ex; + ae_vector ev; + ae_vector iwork; + ae_vector tmp; + double v; + ae_int_t i; + ae_int_t j; + ae_int_t kase; + ae_int_t kase1; + double ainvnm; + double maxgrowth; + double su; + double sl; + ae_bool mupper; + ae_bool munit; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&ex, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ev, 0, DT_REAL, _state, ae_true); + ae_vector_init(&iwork, 0, DT_INT, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + + + /* + * RC=0 if something happens + */ + *rc = 0; + + /* + * init + */ + if( onenorm ) + { + kase1 = 1; + } + else + { + kase1 = 2; + } + mupper = ae_true; + munit = ae_true; + ae_vector_set_length(&iwork, n+1, _state); + ae_vector_set_length(&tmp, n, _state); + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + su = 0; + sl = 1; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + sl = ae_maxreal(sl, ae_fabs(lua->ptr.pp_double[i][j], _state), _state); + } + for(j=i; j<=n-1; j++) + { + su = ae_maxreal(su, ae_fabs(lua->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(su,0) ) + { + su = 1; + } + su = 1/su; + sl = 1/sl; + + /* + * Estimate the norm of A. + */ + if( !isanormprovided ) + { + kase = 0; + anorm = 0; + for(;;) + { + rcond_rmatrixestimatenorm(n, &ev, &ex, &iwork, &anorm, &kase, _state); + if( kase==0 ) + { + break; + } + if( kase==kase1 ) + { + + /* + * Multiply by U + */ + for(i=1; i<=n; i++) + { + v = ae_v_dotproduct(&lua->ptr.pp_double[i-1][i-1], 1, &ex.ptr.p_double[i], 1, ae_v_len(i-1,n-1)); + ex.ptr.p_double[i] = v; + } + + /* + * Multiply by L + */ + for(i=n; i>=1; i--) + { + if( i>1 ) + { + v = ae_v_dotproduct(&lua->ptr.pp_double[i-1][0], 1, &ex.ptr.p_double[1], 1, ae_v_len(0,i-2)); + } + else + { + v = 0; + } + ex.ptr.p_double[i] = ex.ptr.p_double[i]+v; + } + } + else + { + + /* + * Multiply by L' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_double[i+1]; + if( i>=1 ) + { + ae_v_addd(&tmp.ptr.p_double[0], 1, &lua->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), v); + } + tmp.ptr.p_double[i] = tmp.ptr.p_double[i]+v; + } + ae_v_move(&ex.ptr.p_double[1], 1, &tmp.ptr.p_double[0], 1, ae_v_len(1,n)); + + /* + * Multiply by U' + */ + for(i=0; i<=n-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=n-1; i++) + { + v = ex.ptr.p_double[i+1]; + ae_v_addd(&tmp.ptr.p_double[i], 1, &lua->ptr.pp_double[i][i], 1, ae_v_len(i,n-1), v); + } + ae_v_move(&ex.ptr.p_double[1], 1, &tmp.ptr.p_double[0], 1, ae_v_len(1,n)); + } + } + } + + /* + * Scale according to SU/SL + */ + anorm = anorm*su*sl; + + /* + * Quick return if possible + * We assume that ANORM<>0 after this block + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + if( n==1 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + + /* + * Estimate the norm of inv(A). + */ + ainvnm = 0; + kase = 0; + for(;;) + { + rcond_rmatrixestimatenorm(n, &ev, &ex, &iwork, &ainvnm, &kase, _state); + if( kase==0 ) + { + break; + } + + /* + * from 1-based array to 0-based + */ + for(i=0; i<=n-1; i++) + { + ex.ptr.p_double[i] = ex.ptr.p_double[i+1]; + } + + /* + * multiply by inv(A) or inv(A') + */ + if( kase==kase1 ) + { + + /* + * Multiply by inv(L). + */ + if( !rmatrixscaledtrsafesolve(lua, sl, n, &ex, !mupper, 0, munit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(U). + */ + if( !rmatrixscaledtrsafesolve(lua, su, n, &ex, mupper, 0, !munit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * Multiply by inv(U'). + */ + if( !rmatrixscaledtrsafesolve(lua, su, n, &ex, mupper, 1, !munit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(L'). + */ + if( !rmatrixscaledtrsafesolve(lua, sl, n, &ex, !mupper, 1, munit, maxgrowth, _state) ) + { + ae_frame_leave(_state); + return; + } + } + + /* + * from 0-based array to 1-based + */ + for(i=n-1; i>=0; i--) + { + ex.ptr.p_double[i+1] = ex.ptr.p_double[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + *rc = 1/ainvnm; + *rc = *rc/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Condition number estimation + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + March 31, 1993 +*************************************************************************/ +static void rcond_cmatrixrcondluinternal(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_bool onenorm, + ae_bool isanormprovided, + double anorm, + double* rc, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector ex; + ae_vector cwork2; + ae_vector cwork3; + ae_vector cwork4; + ae_vector isave; + ae_vector rsave; + ae_int_t kase; + ae_int_t kase1; + double ainvnm; + ae_complex v; + ae_int_t i; + ae_int_t j; + double su; + double sl; + double maxgrowth; + + ae_frame_make(_state, &_frame_block); + *rc = 0; + ae_vector_init(&ex, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork2, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork3, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&cwork4, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&isave, 0, DT_INT, _state, ae_true); + ae_vector_init(&rsave, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&cwork2, n+1, _state); + *rc = 0; + if( n==0 ) + { + *rc = 1; + ae_frame_leave(_state); + return; + } + + /* + * prepare parameters for triangular solver + */ + maxgrowth = 1/rcondthreshold(_state); + su = 0; + sl = 1; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + sl = ae_maxreal(sl, ae_c_abs(lua->ptr.pp_complex[i][j], _state), _state); + } + for(j=i; j<=n-1; j++) + { + su = ae_maxreal(su, ae_c_abs(lua->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(su,0) ) + { + su = 1; + } + su = 1/su; + sl = 1/sl; + + /* + * Estimate the norm of SU*SL*A. + */ + if( !isanormprovided ) + { + anorm = 0; + if( onenorm ) + { + kase1 = 1; + } + else + { + kase1 = 2; + } + kase = 0; + do + { + rcond_cmatrixestimatenorm(n, &cwork4, &ex, &anorm, &kase, &isave, &rsave, _state); + if( kase!=0 ) + { + if( kase==kase1 ) + { + + /* + * Multiply by U + */ + for(i=1; i<=n; i++) + { + v = ae_v_cdotproduct(&lua->ptr.pp_complex[i-1][i-1], 1, "N", &ex.ptr.p_complex[i], 1, "N", ae_v_len(i-1,n-1)); + ex.ptr.p_complex[i] = v; + } + + /* + * Multiply by L + */ + for(i=n; i>=1; i--) + { + v = ae_complex_from_d(0); + if( i>1 ) + { + v = ae_v_cdotproduct(&lua->ptr.pp_complex[i-1][0], 1, "N", &ex.ptr.p_complex[1], 1, "N", ae_v_len(0,i-2)); + } + ex.ptr.p_complex[i] = ae_c_add(v,ex.ptr.p_complex[i]); + } + } + else + { + + /* + * Multiply by L' + */ + for(i=1; i<=n; i++) + { + cwork2.ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=1; i<=n; i++) + { + v = ex.ptr.p_complex[i]; + if( i>1 ) + { + ae_v_caddc(&cwork2.ptr.p_complex[1], 1, &lua->ptr.pp_complex[i-1][0], 1, "Conj", ae_v_len(1,i-1), v); + } + cwork2.ptr.p_complex[i] = ae_c_add(cwork2.ptr.p_complex[i],v); + } + + /* + * Multiply by U' + */ + for(i=1; i<=n; i++) + { + ex.ptr.p_complex[i] = ae_complex_from_d(0); + } + for(i=1; i<=n; i++) + { + v = cwork2.ptr.p_complex[i]; + ae_v_caddc(&ex.ptr.p_complex[i], 1, &lua->ptr.pp_complex[i-1][i-1], 1, "Conj", ae_v_len(i,n), v); + } + } + } + } + while(kase!=0); + } + + /* + * Scale according to SU/SL + */ + anorm = anorm*su*sl; + + /* + * Quick return if possible + */ + if( ae_fp_eq(anorm,0) ) + { + ae_frame_leave(_state); + return; + } + + /* + * Estimate the norm of inv(A). + */ + ainvnm = 0; + if( onenorm ) + { + kase1 = 1; + } + else + { + kase1 = 2; + } + kase = 0; + for(;;) + { + rcond_cmatrixestimatenorm(n, &cwork4, &ex, &ainvnm, &kase, &isave, &rsave, _state); + if( kase==0 ) + { + break; + } + + /* + * From 1-based to 0-based + */ + for(i=0; i<=n-1; i++) + { + ex.ptr.p_complex[i] = ex.ptr.p_complex[i+1]; + } + + /* + * multiply by inv(A) or inv(A') + */ + if( kase==kase1 ) + { + + /* + * Multiply by inv(L). + */ + if( !cmatrixscaledtrsafesolve(lua, sl, n, &ex, ae_false, 0, ae_true, maxgrowth, _state) ) + { + *rc = 0; + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(U). + */ + if( !cmatrixscaledtrsafesolve(lua, su, n, &ex, ae_true, 0, ae_false, maxgrowth, _state) ) + { + *rc = 0; + ae_frame_leave(_state); + return; + } + } + else + { + + /* + * Multiply by inv(U'). + */ + if( !cmatrixscaledtrsafesolve(lua, su, n, &ex, ae_true, 2, ae_false, maxgrowth, _state) ) + { + *rc = 0; + ae_frame_leave(_state); + return; + } + + /* + * Multiply by inv(L'). + */ + if( !cmatrixscaledtrsafesolve(lua, sl, n, &ex, ae_false, 2, ae_true, maxgrowth, _state) ) + { + *rc = 0; + ae_frame_leave(_state); + return; + } + } + + /* + * from 0-based to 1-based + */ + for(i=n-1; i>=0; i--) + { + ex.ptr.p_complex[i+1] = ex.ptr.p_complex[i]; + } + } + + /* + * Compute the estimate of the reciprocal condition number. + */ + if( ae_fp_neq(ainvnm,0) ) + { + *rc = 1/ainvnm; + *rc = *rc/anorm; + if( ae_fp_less(*rc,rcondthreshold(_state)) ) + { + *rc = 0; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine for matrix norm estimation + + -- LAPACK auxiliary routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992 +*************************************************************************/ +static void rcond_rmatrixestimatenorm(ae_int_t n, + /* Real */ ae_vector* v, + /* Real */ ae_vector* x, + /* Integer */ ae_vector* isgn, + double* est, + ae_int_t* kase, + ae_state *_state) +{ + ae_int_t itmax; + ae_int_t i; + double t; + ae_bool flg; + ae_int_t positer; + ae_int_t posj; + ae_int_t posjlast; + ae_int_t posjump; + ae_int_t posaltsgn; + ae_int_t posestold; + ae_int_t postemp; + + + itmax = 5; + posaltsgn = n+1; + posestold = n+2; + postemp = n+3; + positer = n+1; + posj = n+2; + posjlast = n+3; + posjump = n+4; + if( *kase==0 ) + { + ae_vector_set_length(v, n+4, _state); + ae_vector_set_length(x, n+1, _state); + ae_vector_set_length(isgn, n+5, _state); + t = (double)1/(double)n; + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = t; + } + *kase = 1; + isgn->ptr.p_int[posjump] = 1; + return; + } + + /* + * ................ ENTRY (JUMP = 1) + * FIRST ITERATION. X HAS BEEN OVERWRITTEN BY A*X. + */ + if( isgn->ptr.p_int[posjump]==1 ) + { + if( n==1 ) + { + v->ptr.p_double[1] = x->ptr.p_double[1]; + *est = ae_fabs(v->ptr.p_double[1], _state); + *kase = 0; + return; + } + *est = 0; + for(i=1; i<=n; i++) + { + *est = *est+ae_fabs(x->ptr.p_double[i], _state); + } + for(i=1; i<=n; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],0) ) + { + x->ptr.p_double[i] = 1; + } + else + { + x->ptr.p_double[i] = -1; + } + isgn->ptr.p_int[i] = ae_sign(x->ptr.p_double[i], _state); + } + *kase = 2; + isgn->ptr.p_int[posjump] = 2; + return; + } + + /* + * ................ ENTRY (JUMP = 2) + * FIRST ITERATION. X HAS BEEN OVERWRITTEN BY TRANDPOSE(A)*X. + */ + if( isgn->ptr.p_int[posjump]==2 ) + { + isgn->ptr.p_int[posj] = 1; + for(i=2; i<=n; i++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[i], _state),ae_fabs(x->ptr.p_double[isgn->ptr.p_int[posj]], _state)) ) + { + isgn->ptr.p_int[posj] = i; + } + } + isgn->ptr.p_int[positer] = 2; + + /* + * MAIN LOOP - ITERATIONS 2,3,...,ITMAX. + */ + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = 0; + } + x->ptr.p_double[isgn->ptr.p_int[posj]] = 1; + *kase = 1; + isgn->ptr.p_int[posjump] = 3; + return; + } + + /* + * ................ ENTRY (JUMP = 3) + * X HAS BEEN OVERWRITTEN BY A*X. + */ + if( isgn->ptr.p_int[posjump]==3 ) + { + ae_v_move(&v->ptr.p_double[1], 1, &x->ptr.p_double[1], 1, ae_v_len(1,n)); + v->ptr.p_double[posestold] = *est; + *est = 0; + for(i=1; i<=n; i++) + { + *est = *est+ae_fabs(v->ptr.p_double[i], _state); + } + flg = ae_false; + for(i=1; i<=n; i++) + { + if( (ae_fp_greater_eq(x->ptr.p_double[i],0)&&isgn->ptr.p_int[i]<0)||(ae_fp_less(x->ptr.p_double[i],0)&&isgn->ptr.p_int[i]>=0) ) + { + flg = ae_true; + } + } + + /* + * REPEATED SIGN VECTOR DETECTED, HENCE ALGORITHM HAS CONVERGED. + * OR MAY BE CYCLING. + */ + if( !flg||ae_fp_less_eq(*est,v->ptr.p_double[posestold]) ) + { + v->ptr.p_double[posaltsgn] = 1; + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = v->ptr.p_double[posaltsgn]*(1+(double)(i-1)/(double)(n-1)); + v->ptr.p_double[posaltsgn] = -v->ptr.p_double[posaltsgn]; + } + *kase = 1; + isgn->ptr.p_int[posjump] = 5; + return; + } + for(i=1; i<=n; i++) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],0) ) + { + x->ptr.p_double[i] = 1; + isgn->ptr.p_int[i] = 1; + } + else + { + x->ptr.p_double[i] = -1; + isgn->ptr.p_int[i] = -1; + } + } + *kase = 2; + isgn->ptr.p_int[posjump] = 4; + return; + } + + /* + * ................ ENTRY (JUMP = 4) + * X HAS BEEN OVERWRITTEN BY TRANDPOSE(A)*X. + */ + if( isgn->ptr.p_int[posjump]==4 ) + { + isgn->ptr.p_int[posjlast] = isgn->ptr.p_int[posj]; + isgn->ptr.p_int[posj] = 1; + for(i=2; i<=n; i++) + { + if( ae_fp_greater(ae_fabs(x->ptr.p_double[i], _state),ae_fabs(x->ptr.p_double[isgn->ptr.p_int[posj]], _state)) ) + { + isgn->ptr.p_int[posj] = i; + } + } + if( ae_fp_neq(x->ptr.p_double[isgn->ptr.p_int[posjlast]],ae_fabs(x->ptr.p_double[isgn->ptr.p_int[posj]], _state))&&isgn->ptr.p_int[positer]ptr.p_int[positer] = isgn->ptr.p_int[positer]+1; + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = 0; + } + x->ptr.p_double[isgn->ptr.p_int[posj]] = 1; + *kase = 1; + isgn->ptr.p_int[posjump] = 3; + return; + } + + /* + * ITERATION COMPLETE. FINAL STAGE. + */ + v->ptr.p_double[posaltsgn] = 1; + for(i=1; i<=n; i++) + { + x->ptr.p_double[i] = v->ptr.p_double[posaltsgn]*(1+(double)(i-1)/(double)(n-1)); + v->ptr.p_double[posaltsgn] = -v->ptr.p_double[posaltsgn]; + } + *kase = 1; + isgn->ptr.p_int[posjump] = 5; + return; + } + + /* + * ................ ENTRY (JUMP = 5) + * X HAS BEEN OVERWRITTEN BY A*X. + */ + if( isgn->ptr.p_int[posjump]==5 ) + { + v->ptr.p_double[postemp] = 0; + for(i=1; i<=n; i++) + { + v->ptr.p_double[postemp] = v->ptr.p_double[postemp]+ae_fabs(x->ptr.p_double[i], _state); + } + v->ptr.p_double[postemp] = 2*v->ptr.p_double[postemp]/(3*n); + if( ae_fp_greater(v->ptr.p_double[postemp],*est) ) + { + ae_v_move(&v->ptr.p_double[1], 1, &x->ptr.p_double[1], 1, ae_v_len(1,n)); + *est = v->ptr.p_double[postemp]; + } + *kase = 0; + return; + } +} + + +static void rcond_cmatrixestimatenorm(ae_int_t n, + /* Complex */ ae_vector* v, + /* Complex */ ae_vector* x, + double* est, + ae_int_t* kase, + /* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_state *_state) +{ + ae_int_t itmax; + ae_int_t i; + ae_int_t iter; + ae_int_t j; + ae_int_t jlast; + ae_int_t jump; + double absxi; + double altsgn; + double estold; + double safmin; + double temp; + + + + /* + *Executable Statements .. + */ + itmax = 5; + safmin = ae_minrealnumber; + if( *kase==0 ) + { + ae_vector_set_length(v, n+1, _state); + ae_vector_set_length(x, n+1, _state); + ae_vector_set_length(isave, 5, _state); + ae_vector_set_length(rsave, 4, _state); + for(i=1; i<=n; i++) + { + x->ptr.p_complex[i] = ae_complex_from_d((double)1/(double)n); + } + *kase = 1; + jump = 1; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + rcond_internalcomplexrcondloadall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + + /* + * ENTRY (JUMP = 1) + * FIRST ITERATION. X HAS BEEN OVERWRITTEN BY A*X. + */ + if( jump==1 ) + { + if( n==1 ) + { + v->ptr.p_complex[1] = x->ptr.p_complex[1]; + *est = ae_c_abs(v->ptr.p_complex[1], _state); + *kase = 0; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + *est = rcond_internalcomplexrcondscsum1(x, n, _state); + for(i=1; i<=n; i++) + { + absxi = ae_c_abs(x->ptr.p_complex[i], _state); + if( ae_fp_greater(absxi,safmin) ) + { + x->ptr.p_complex[i] = ae_c_div_d(x->ptr.p_complex[i],absxi); + } + else + { + x->ptr.p_complex[i] = ae_complex_from_d(1); + } + } + *kase = 2; + jump = 2; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + + /* + * ENTRY (JUMP = 2) + * FIRST ITERATION. X HAS BEEN OVERWRITTEN BY CTRANS(A)*X. + */ + if( jump==2 ) + { + j = rcond_internalcomplexrcondicmax1(x, n, _state); + iter = 2; + + /* + * MAIN LOOP - ITERATIONS 2,3,...,ITMAX. + */ + for(i=1; i<=n; i++) + { + x->ptr.p_complex[i] = ae_complex_from_d(0); + } + x->ptr.p_complex[j] = ae_complex_from_d(1); + *kase = 1; + jump = 3; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + + /* + * ENTRY (JUMP = 3) + * X HAS BEEN OVERWRITTEN BY A*X. + */ + if( jump==3 ) + { + ae_v_cmove(&v->ptr.p_complex[1], 1, &x->ptr.p_complex[1], 1, "N", ae_v_len(1,n)); + estold = *est; + *est = rcond_internalcomplexrcondscsum1(v, n, _state); + + /* + * TEST FOR CYCLING. + */ + if( ae_fp_less_eq(*est,estold) ) + { + + /* + * ITERATION COMPLETE. FINAL STAGE. + */ + altsgn = 1; + for(i=1; i<=n; i++) + { + x->ptr.p_complex[i] = ae_complex_from_d(altsgn*(1+(double)(i-1)/(double)(n-1))); + altsgn = -altsgn; + } + *kase = 1; + jump = 5; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + for(i=1; i<=n; i++) + { + absxi = ae_c_abs(x->ptr.p_complex[i], _state); + if( ae_fp_greater(absxi,safmin) ) + { + x->ptr.p_complex[i] = ae_c_div_d(x->ptr.p_complex[i],absxi); + } + else + { + x->ptr.p_complex[i] = ae_complex_from_d(1); + } + } + *kase = 2; + jump = 4; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + + /* + * ENTRY (JUMP = 4) + * X HAS BEEN OVERWRITTEN BY CTRANS(A)*X. + */ + if( jump==4 ) + { + jlast = j; + j = rcond_internalcomplexrcondicmax1(x, n, _state); + if( ae_fp_neq(ae_c_abs(x->ptr.p_complex[jlast], _state),ae_c_abs(x->ptr.p_complex[j], _state))&&iterptr.p_complex[i] = ae_complex_from_d(0); + } + x->ptr.p_complex[j] = ae_complex_from_d(1); + *kase = 1; + jump = 3; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + + /* + * ITERATION COMPLETE. FINAL STAGE. + */ + altsgn = 1; + for(i=1; i<=n; i++) + { + x->ptr.p_complex[i] = ae_complex_from_d(altsgn*(1+(double)(i-1)/(double)(n-1))); + altsgn = -altsgn; + } + *kase = 1; + jump = 5; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } + + /* + * ENTRY (JUMP = 5) + * X HAS BEEN OVERWRITTEN BY A*X. + */ + if( jump==5 ) + { + temp = 2*(rcond_internalcomplexrcondscsum1(x, n, _state)/(3*n)); + if( ae_fp_greater(temp,*est) ) + { + ae_v_cmove(&v->ptr.p_complex[1], 1, &x->ptr.p_complex[1], 1, "N", ae_v_len(1,n)); + *est = temp; + } + *kase = 0; + rcond_internalcomplexrcondsaveall(isave, rsave, &i, &iter, &j, &jlast, &jump, &absxi, &altsgn, &estold, &temp, _state); + return; + } +} + + +static double rcond_internalcomplexrcondscsum1(/* Complex */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + double result; + + + result = 0; + for(i=1; i<=n; i++) + { + result = result+ae_c_abs(x->ptr.p_complex[i], _state); + } + return result; +} + + +static ae_int_t rcond_internalcomplexrcondicmax1(/* Complex */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + double m; + ae_int_t result; + + + result = 1; + m = ae_c_abs(x->ptr.p_complex[1], _state); + for(i=2; i<=n; i++) + { + if( ae_fp_greater(ae_c_abs(x->ptr.p_complex[i], _state),m) ) + { + result = i; + m = ae_c_abs(x->ptr.p_complex[i], _state); + } + } + return result; +} + + +static void rcond_internalcomplexrcondsaveall(/* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_int_t* i, + ae_int_t* iter, + ae_int_t* j, + ae_int_t* jlast, + ae_int_t* jump, + double* absxi, + double* altsgn, + double* estold, + double* temp, + ae_state *_state) +{ + + + isave->ptr.p_int[0] = *i; + isave->ptr.p_int[1] = *iter; + isave->ptr.p_int[2] = *j; + isave->ptr.p_int[3] = *jlast; + isave->ptr.p_int[4] = *jump; + rsave->ptr.p_double[0] = *absxi; + rsave->ptr.p_double[1] = *altsgn; + rsave->ptr.p_double[2] = *estold; + rsave->ptr.p_double[3] = *temp; +} + + +static void rcond_internalcomplexrcondloadall(/* Integer */ ae_vector* isave, + /* Real */ ae_vector* rsave, + ae_int_t* i, + ae_int_t* iter, + ae_int_t* j, + ae_int_t* jlast, + ae_int_t* jump, + double* absxi, + double* altsgn, + double* estold, + double* temp, + ae_state *_state) +{ + + + *i = isave->ptr.p_int[0]; + *iter = isave->ptr.p_int[1]; + *j = isave->ptr.p_int[2]; + *jlast = isave->ptr.p_int[3]; + *jump = isave->ptr.p_int[4]; + *absxi = rsave->ptr.p_double[0]; + *altsgn = rsave->ptr.p_double[1]; + *estold = rsave->ptr.p_double[2]; + *temp = rsave->ptr.p_double[3]; +} + + + + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of RMatrixLU subroutine). + Pivots - table of permutations + (the output of RMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code: + * -3 A is singular, or VERY close to singular. + it is filled by zeros in such cases. + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + A - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixluinverse(/* Real */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector work; + ae_int_t i; + ae_int_t j; + ae_int_t k; + double v; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _matinvreport_clear(rep); + ae_vector_init(&work, 0, DT_REAL, _state, ae_true); + + ae_assert(n>0, "RMatrixLUInverse: N<=0!", _state); + ae_assert(a->cols>=n, "RMatrixLUInverse: cols(A)rows>=n, "RMatrixLUInverse: rows(A)cnt>=n, "RMatrixLUInverse: len(Pivots)ptr.p_int[i]>n-1||pivots->ptr.p_int[i]0, "RMatrixLUInverse: incorrect Pivots array!", _state); + + /* + * calculate condition numbers + */ + rep->r1 = rmatrixlurcond1(a, n, _state); + rep->rinf = rmatrixlurcondinf(a, n, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Call cache-oblivious code + */ + ae_vector_set_length(&work, n, _state); + matinv_rmatrixluinverserec(a, 0, n, &work, info, rep, _state); + + /* + * apply permutations + */ + for(i=0; i<=n-1; i++) + { + for(j=n-2; j>=0; j--) + { + k = pivots->ptr.p_int[j]; + v = a->ptr.pp_double[i][j]; + a->ptr.pp_double[i][j] = a->ptr.pp_double[i][k]; + a->ptr.pp_double[i][k] = v; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + +Result: + True, if the matrix is not singular. + False, if the matrix is singular. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector pivots; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _matinvreport_clear(rep); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>0, "RMatrixInverse: N<=0!", _state); + ae_assert(a->cols>=n, "RMatrixInverse: cols(A)rows>=n, "RMatrixInverse: rows(A)0, "CMatrixLUInverse: N<=0!", _state); + ae_assert(a->cols>=n, "CMatrixLUInverse: cols(A)rows>=n, "CMatrixLUInverse: rows(A)cnt>=n, "CMatrixLUInverse: len(Pivots)ptr.p_int[i]>n-1||pivots->ptr.p_int[i]0, "CMatrixLUInverse: incorrect Pivots array!", _state); + + /* + * calculate condition numbers + */ + rep->r1 = cmatrixlurcond1(a, n, _state); + rep->rinf = cmatrixlurcondinf(a, n, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Call cache-oblivious code + */ + ae_vector_set_length(&work, n, _state); + matinv_cmatrixluinverserec(a, 0, n, &work, info, rep, _state); + + /* + * apply permutations + */ + for(i=0; i<=n-1; i++) + { + for(j=n-2; j>=0; j--) + { + k = pivots->ptr.p_int[j]; + v = a->ptr.pp_complex[i][j]; + a->ptr.pp_complex[i][j] = a->ptr.pp_complex[i][k]; + a->ptr.pp_complex[i][k] = v; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void cmatrixinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector pivots; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _matinvreport_clear(rep); + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>0, "CRMatrixInverse: N<=0!", _state); + ae_assert(a->cols>=n, "CRMatrixInverse: cols(A)rows>=n, "CRMatrixInverse: rows(A)0, "SPDMatrixCholeskyInverse: N<=0!", _state); + ae_assert(a->cols>=n, "SPDMatrixCholeskyInverse: cols(A)rows>=n, "SPDMatrixCholeskyInverse: rows(A)ptr.pp_double[i][i], _state); + } + ae_assert(f, "SPDMatrixCholeskyInverse: A contains infinite or NaN values!", _state); + + /* + * calculate condition numbers + */ + rep->r1 = spdmatrixcholeskyrcond(a, n, isupper, _state); + rep->rinf = rep->r1; + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + if( isupper ) + { + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Inverse + */ + ae_vector_set_length(&tmp, n, _state); + matinv_spdmatrixcholeskyinverserec(a, 0, n, isupper, &tmp, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Inversion of a symmetric positive definite matrix. + +Given an upper or lower triangle of a symmetric positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + + *info = 0; + _matinvreport_clear(rep); + + ae_assert(n>0, "SPDMatrixInverse: N<=0!", _state); + ae_assert(a->cols>=n, "SPDMatrixInverse: cols(A)rows>=n, "SPDMatrixInverse: rows(A)0, "HPDMatrixCholeskyInverse: N<=0!", _state); + ae_assert(a->cols>=n, "HPDMatrixCholeskyInverse: cols(A)rows>=n, "HPDMatrixCholeskyInverse: rows(A)ptr.pp_complex[i][i].x, _state))&&ae_isfinite(a->ptr.pp_complex[i][i].y, _state); + } + ae_assert(f, "HPDMatrixCholeskyInverse: A contains infinite or NaN values!", _state); + *info = 1; + + /* + * calculate condition numbers + */ + rep->r1 = hpdmatrixcholeskyrcond(a, n, isupper, _state); + rep->rinf = rep->r1; + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + if( isupper ) + { + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Inverse + */ + ae_vector_set_length(&tmp, n, _state); + matinv_hpdmatrixcholeskyinverserec(a, 0, n, isupper, &tmp, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Inversion of a Hermitian positive definite matrix. + +Given an upper or lower triangle of a Hermitian positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + + *info = 0; + _matinvreport_clear(rep); + + ae_assert(n>0, "HPDMatrixInverse: N<=0!", _state); + ae_assert(a->cols>=n, "HPDMatrixInverse: cols(A)rows>=n, "HPDMatrixInverse: rows(A)0, "RMatrixTRInverse: N<=0!", _state); + ae_assert(a->cols>=n, "RMatrixTRInverse: cols(A)rows>=n, "RMatrixTRInverse: rows(A)r1 = rmatrixtrrcond1(a, n, isupper, isunit, _state); + rep->rinf = rmatrixtrrcondinf(a, n, isupper, isunit, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_double[i][j] = 0; + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Invert + */ + ae_vector_set_length(&tmp, n, _state); + matinv_rmatrixtrinverserec(a, 0, n, isupper, isunit, &tmp, info, rep, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Triangular matrix inverse (complex) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixtrinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_vector tmp; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _matinvreport_clear(rep); + ae_vector_init(&tmp, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(n>0, "CMatrixTRInverse: N<=0!", _state); + ae_assert(a->cols>=n, "CMatrixTRInverse: cols(A)rows>=n, "CMatrixTRInverse: rows(A)r1 = cmatrixtrrcond1(a, n, isupper, isunit, _state); + rep->rinf = cmatrixtrrcondinf(a, n, isupper, isunit, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + a->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + + /* + * Invert + */ + ae_vector_set_length(&tmp, n, _state); + matinv_cmatrixtrinverserec(a, 0, n, isupper, isunit, &tmp, info, rep, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Triangular matrix inversion, recursive subroutine + + -- ALGLIB -- + 05.02.2010, Bochkanov Sergey. + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992. +*************************************************************************/ +static void matinv_rmatrixtrinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + /* Real */ ae_vector* tmp, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_int_t n1; + ae_int_t n2; + ae_int_t i; + ae_int_t j; + double v; + double ajj; + + + if( n<1 ) + { + *info = -1; + return; + } + + /* + * Base case + */ + if( n<=ablasblocksize(a, _state) ) + { + if( isupper ) + { + + /* + * Compute inverse of upper triangular matrix. + */ + for(j=0; j<=n-1; j++) + { + if( !isunit ) + { + if( ae_fp_eq(a->ptr.pp_double[offs+j][offs+j],0) ) + { + *info = -3; + return; + } + a->ptr.pp_double[offs+j][offs+j] = 1/a->ptr.pp_double[offs+j][offs+j]; + ajj = -a->ptr.pp_double[offs+j][offs+j]; + } + else + { + ajj = -1; + } + + /* + * Compute elements 1:j-1 of j-th column. + */ + if( j>0 ) + { + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+0][offs+j], a->stride, ae_v_len(0,j-1)); + for(i=0; i<=j-1; i++) + { + if( iptr.pp_double[offs+i][offs+i+1], 1, &tmp->ptr.p_double[i+1], 1, ae_v_len(offs+i+1,offs+j-1)); + } + else + { + v = 0; + } + if( !isunit ) + { + a->ptr.pp_double[offs+i][offs+j] = v+a->ptr.pp_double[offs+i][offs+i]*tmp->ptr.p_double[i]; + } + else + { + a->ptr.pp_double[offs+i][offs+j] = v+tmp->ptr.p_double[i]; + } + } + ae_v_muld(&a->ptr.pp_double[offs+0][offs+j], a->stride, ae_v_len(offs+0,offs+j-1), ajj); + } + } + } + else + { + + /* + * Compute inverse of lower triangular matrix. + */ + for(j=n-1; j>=0; j--) + { + if( !isunit ) + { + if( ae_fp_eq(a->ptr.pp_double[offs+j][offs+j],0) ) + { + *info = -3; + return; + } + a->ptr.pp_double[offs+j][offs+j] = 1/a->ptr.pp_double[offs+j][offs+j]; + ajj = -a->ptr.pp_double[offs+j][offs+j]; + } + else + { + ajj = -1; + } + if( jptr.p_double[j+1], 1, &a->ptr.pp_double[offs+j+1][offs+j], a->stride, ae_v_len(j+1,n-1)); + for(i=j+1; i<=n-1; i++) + { + if( i>j+1 ) + { + v = ae_v_dotproduct(&a->ptr.pp_double[offs+i][offs+j+1], 1, &tmp->ptr.p_double[j+1], 1, ae_v_len(offs+j+1,offs+i-1)); + } + else + { + v = 0; + } + if( !isunit ) + { + a->ptr.pp_double[offs+i][offs+j] = v+a->ptr.pp_double[offs+i][offs+i]*tmp->ptr.p_double[i]; + } + else + { + a->ptr.pp_double[offs+i][offs+j] = v+tmp->ptr.p_double[i]; + } + } + ae_v_muld(&a->ptr.pp_double[offs+j+1][offs+j], a->stride, ae_v_len(offs+j+1,offs+n-1), ajj); + } + } + } + return; + } + + /* + * Recursive case + */ + ablassplitlength(a, n, &n1, &n2, _state); + if( n2>0 ) + { + if( isupper ) + { + for(i=0; i<=n1-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, a, offs, offs+n1, _state); + rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs, offs+n1, _state); + } + else + { + for(i=0; i<=n2-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, a, offs+n1, offs, _state); + rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs+n1, offs, _state); + } + matinv_rmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, rep, _state); + } + matinv_rmatrixtrinverserec(a, offs, n1, isupper, isunit, tmp, info, rep, _state); +} + + +/************************************************************************* +Triangular matrix inversion, recursive subroutine + + -- ALGLIB -- + 05.02.2010, Bochkanov Sergey. + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + February 29, 1992. +*************************************************************************/ +static void matinv_cmatrixtrinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + /* Complex */ ae_vector* tmp, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_int_t n1; + ae_int_t n2; + ae_int_t i; + ae_int_t j; + ae_complex v; + ae_complex ajj; + + + if( n<1 ) + { + *info = -1; + return; + } + + /* + * Base case + */ + if( n<=ablascomplexblocksize(a, _state) ) + { + if( isupper ) + { + + /* + * Compute inverse of upper triangular matrix. + */ + for(j=0; j<=n-1; j++) + { + if( !isunit ) + { + if( ae_c_eq_d(a->ptr.pp_complex[offs+j][offs+j],0) ) + { + *info = -3; + return; + } + a->ptr.pp_complex[offs+j][offs+j] = ae_c_d_div(1,a->ptr.pp_complex[offs+j][offs+j]); + ajj = ae_c_neg(a->ptr.pp_complex[offs+j][offs+j]); + } + else + { + ajj = ae_complex_from_d(-1); + } + + /* + * Compute elements 1:j-1 of j-th column. + */ + if( j>0 ) + { + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+0][offs+j], a->stride, "N", ae_v_len(0,j-1)); + for(i=0; i<=j-1; i++) + { + if( iptr.pp_complex[offs+i][offs+i+1], 1, "N", &tmp->ptr.p_complex[i+1], 1, "N", ae_v_len(offs+i+1,offs+j-1)); + } + else + { + v = ae_complex_from_d(0); + } + if( !isunit ) + { + a->ptr.pp_complex[offs+i][offs+j] = ae_c_add(v,ae_c_mul(a->ptr.pp_complex[offs+i][offs+i],tmp->ptr.p_complex[i])); + } + else + { + a->ptr.pp_complex[offs+i][offs+j] = ae_c_add(v,tmp->ptr.p_complex[i]); + } + } + ae_v_cmulc(&a->ptr.pp_complex[offs+0][offs+j], a->stride, ae_v_len(offs+0,offs+j-1), ajj); + } + } + } + else + { + + /* + * Compute inverse of lower triangular matrix. + */ + for(j=n-1; j>=0; j--) + { + if( !isunit ) + { + if( ae_c_eq_d(a->ptr.pp_complex[offs+j][offs+j],0) ) + { + *info = -3; + return; + } + a->ptr.pp_complex[offs+j][offs+j] = ae_c_d_div(1,a->ptr.pp_complex[offs+j][offs+j]); + ajj = ae_c_neg(a->ptr.pp_complex[offs+j][offs+j]); + } + else + { + ajj = ae_complex_from_d(-1); + } + if( jptr.p_complex[j+1], 1, &a->ptr.pp_complex[offs+j+1][offs+j], a->stride, "N", ae_v_len(j+1,n-1)); + for(i=j+1; i<=n-1; i++) + { + if( i>j+1 ) + { + v = ae_v_cdotproduct(&a->ptr.pp_complex[offs+i][offs+j+1], 1, "N", &tmp->ptr.p_complex[j+1], 1, "N", ae_v_len(offs+j+1,offs+i-1)); + } + else + { + v = ae_complex_from_d(0); + } + if( !isunit ) + { + a->ptr.pp_complex[offs+i][offs+j] = ae_c_add(v,ae_c_mul(a->ptr.pp_complex[offs+i][offs+i],tmp->ptr.p_complex[i])); + } + else + { + a->ptr.pp_complex[offs+i][offs+j] = ae_c_add(v,tmp->ptr.p_complex[i]); + } + } + ae_v_cmulc(&a->ptr.pp_complex[offs+j+1][offs+j], a->stride, ae_v_len(offs+j+1,offs+n-1), ajj); + } + } + } + return; + } + + /* + * Recursive case + */ + ablascomplexsplitlength(a, n, &n1, &n2, _state); + if( n2>0 ) + { + if( isupper ) + { + for(i=0; i<=n1-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, a, offs, offs+n1, _state); + cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs, offs+n1, _state); + } + else + { + for(i=0; i<=n2-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, a, offs+n1, offs, _state); + cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs+n1, offs, _state); + } + matinv_cmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, rep, _state); + } + matinv_cmatrixtrinverserec(a, offs, n1, isupper, isunit, tmp, info, rep, _state); +} + + +static void matinv_rmatrixluinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + /* Real */ ae_vector* work, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + ae_int_t n1; + ae_int_t n2; + + + if( n<1 ) + { + *info = -1; + return; + } + + /* + * Base case + */ + if( n<=ablasblocksize(a, _state) ) + { + + /* + * Form inv(U) + */ + matinv_rmatrixtrinverserec(a, offs, n, ae_true, ae_false, work, info, rep, _state); + if( *info<=0 ) + { + return; + } + + /* + * Solve the equation inv(A)*L = inv(U) for inv(A). + */ + for(j=n-1; j>=0; j--) + { + + /* + * Copy current column of L to WORK and replace with zeros. + */ + for(i=j+1; i<=n-1; i++) + { + work->ptr.p_double[i] = a->ptr.pp_double[offs+i][offs+j]; + a->ptr.pp_double[offs+i][offs+j] = 0; + } + + /* + * Compute current column of inv(A). + */ + if( jptr.pp_double[offs+i][offs+j+1], 1, &work->ptr.p_double[j+1], 1, ae_v_len(offs+j+1,offs+n-1)); + a->ptr.pp_double[offs+i][offs+j] = a->ptr.pp_double[offs+i][offs+j]-v; + } + } + } + return; + } + + /* + * Recursive code: + * + * ( L1 ) ( U1 U12 ) + * A = ( ) * ( ) + * ( L12 L2 ) ( U2 ) + * + * ( W X ) + * A^-1 = ( ) + * ( Y Z ) + */ + ablassplitlength(a, n, &n1, &n2, _state); + ae_assert(n2>0, "LUInverseRec: internal error!", _state); + + /* + * X := inv(U1)*U12*inv(U2) + */ + rmatrixlefttrsm(n1, n2, a, offs, offs, ae_true, ae_false, 0, a, offs, offs+n1, _state); + rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, ae_true, ae_false, 0, a, offs, offs+n1, _state); + + /* + * Y := inv(L2)*L12*inv(L1) + */ + rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, ae_false, ae_true, 0, a, offs+n1, offs, _state); + rmatrixrighttrsm(n2, n1, a, offs, offs, ae_false, ae_true, 0, a, offs+n1, offs, _state); + + /* + * W := inv(L1*U1)+X*Y + */ + matinv_rmatrixluinverserec(a, offs, n1, work, info, rep, _state); + if( *info<=0 ) + { + return; + } + rmatrixgemm(n1, n1, n2, 1.0, a, offs, offs+n1, 0, a, offs+n1, offs, 0, 1.0, a, offs, offs, _state); + + /* + * X := -X*inv(L2) + * Y := -inv(U2)*Y + */ + rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, ae_false, ae_true, 0, a, offs, offs+n1, _state); + for(i=0; i<=n1-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, ae_true, ae_false, 0, a, offs+n1, offs, _state); + for(i=0; i<=n2-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + + /* + * Z := inv(L2*U2) + */ + matinv_rmatrixluinverserec(a, offs+n1, n2, work, info, rep, _state); +} + + +static void matinv_cmatrixluinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + /* Complex */ ae_vector* work, + ae_int_t* info, + matinvreport* rep, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_complex v; + ae_int_t n1; + ae_int_t n2; + + + if( n<1 ) + { + *info = -1; + return; + } + + /* + * Base case + */ + if( n<=ablascomplexblocksize(a, _state) ) + { + + /* + * Form inv(U) + */ + matinv_cmatrixtrinverserec(a, offs, n, ae_true, ae_false, work, info, rep, _state); + if( *info<=0 ) + { + return; + } + + /* + * Solve the equation inv(A)*L = inv(U) for inv(A). + */ + for(j=n-1; j>=0; j--) + { + + /* + * Copy current column of L to WORK and replace with zeros. + */ + for(i=j+1; i<=n-1; i++) + { + work->ptr.p_complex[i] = a->ptr.pp_complex[offs+i][offs+j]; + a->ptr.pp_complex[offs+i][offs+j] = ae_complex_from_d(0); + } + + /* + * Compute current column of inv(A). + */ + if( jptr.pp_complex[offs+i][offs+j+1], 1, "N", &work->ptr.p_complex[j+1], 1, "N", ae_v_len(offs+j+1,offs+n-1)); + a->ptr.pp_complex[offs+i][offs+j] = ae_c_sub(a->ptr.pp_complex[offs+i][offs+j],v); + } + } + } + return; + } + + /* + * Recursive code: + * + * ( L1 ) ( U1 U12 ) + * A = ( ) * ( ) + * ( L12 L2 ) ( U2 ) + * + * ( W X ) + * A^-1 = ( ) + * ( Y Z ) + */ + ablascomplexsplitlength(a, n, &n1, &n2, _state); + ae_assert(n2>0, "LUInverseRec: internal error!", _state); + + /* + * X := inv(U1)*U12*inv(U2) + */ + cmatrixlefttrsm(n1, n2, a, offs, offs, ae_true, ae_false, 0, a, offs, offs+n1, _state); + cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, ae_true, ae_false, 0, a, offs, offs+n1, _state); + + /* + * Y := inv(L2)*L12*inv(L1) + */ + cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, ae_false, ae_true, 0, a, offs+n1, offs, _state); + cmatrixrighttrsm(n2, n1, a, offs, offs, ae_false, ae_true, 0, a, offs+n1, offs, _state); + + /* + * W := inv(L1*U1)+X*Y + */ + matinv_cmatrixluinverserec(a, offs, n1, work, info, rep, _state); + if( *info<=0 ) + { + return; + } + cmatrixgemm(n1, n1, n2, ae_complex_from_d(1.0), a, offs, offs+n1, 0, a, offs+n1, offs, 0, ae_complex_from_d(1.0), a, offs, offs, _state); + + /* + * X := -X*inv(L2) + * Y := -inv(U2)*Y + */ + cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, ae_false, ae_true, 0, a, offs, offs+n1, _state); + for(i=0; i<=n1-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, ae_true, ae_false, 0, a, offs+n1, offs, _state); + for(i=0; i<=n2-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + + /* + * Z := inv(L2*U2) + */ + matinv_cmatrixluinverserec(a, offs+n1, n2, work, info, rep, _state); +} + + +/************************************************************************* +Recursive subroutine for SPD inversion. + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +static void matinv_spdmatrixcholeskyinverserec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + ae_int_t n1; + ae_int_t n2; + ae_int_t info2; + matinvreport rep2; + + ae_frame_make(_state, &_frame_block); + _matinvreport_init(&rep2, _state, ae_true); + + if( n<1 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Base case + */ + if( n<=ablasblocksize(a, _state) ) + { + matinv_rmatrixtrinverserec(a, offs, n, isupper, ae_false, tmp, &info2, &rep2, _state); + if( isupper ) + { + + /* + * Compute the product U * U'. + * NOTE: we never assume that diagonal of U is real + */ + for(i=0; i<=n-1; i++) + { + if( i==0 ) + { + + /* + * 1x1 matrix + */ + a->ptr.pp_double[offs+i][offs+i] = ae_sqr(a->ptr.pp_double[offs+i][offs+i], _state); + } + else + { + + /* + * (I+1)x(I+1) matrix, + * + * ( A11 A12 ) ( A11^H ) ( A11*A11^H+A12*A12^H A12*A22^H ) + * ( ) * ( ) = ( ) + * ( A22 ) ( A12^H A22^H ) ( A22*A12^H A22*A22^H ) + * + * A11 is IxI, A22 is 1x1. + */ + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs][offs+i], a->stride, ae_v_len(0,i-1)); + for(j=0; j<=i-1; j++) + { + v = a->ptr.pp_double[offs+j][offs+i]; + ae_v_addd(&a->ptr.pp_double[offs+j][offs+j], 1, &tmp->ptr.p_double[j], 1, ae_v_len(offs+j,offs+i-1), v); + } + v = a->ptr.pp_double[offs+i][offs+i]; + ae_v_muld(&a->ptr.pp_double[offs][offs+i], a->stride, ae_v_len(offs,offs+i-1), v); + a->ptr.pp_double[offs+i][offs+i] = ae_sqr(a->ptr.pp_double[offs+i][offs+i], _state); + } + } + } + else + { + + /* + * Compute the product L' * L + * NOTE: we never assume that diagonal of L is real + */ + for(i=0; i<=n-1; i++) + { + if( i==0 ) + { + + /* + * 1x1 matrix + */ + a->ptr.pp_double[offs+i][offs+i] = ae_sqr(a->ptr.pp_double[offs+i][offs+i], _state); + } + else + { + + /* + * (I+1)x(I+1) matrix, + * + * ( A11^H A21^H ) ( A11 ) ( A11^H*A11+A21^H*A21 A21^H*A22 ) + * ( ) * ( ) = ( ) + * ( A22^H ) ( A21 A22 ) ( A22^H*A21 A22^H*A22 ) + * + * A11 is IxI, A22 is 1x1. + */ + ae_v_move(&tmp->ptr.p_double[0], 1, &a->ptr.pp_double[offs+i][offs], 1, ae_v_len(0,i-1)); + for(j=0; j<=i-1; j++) + { + v = a->ptr.pp_double[offs+i][offs+j]; + ae_v_addd(&a->ptr.pp_double[offs+j][offs], 1, &tmp->ptr.p_double[0], 1, ae_v_len(offs,offs+j), v); + } + v = a->ptr.pp_double[offs+i][offs+i]; + ae_v_muld(&a->ptr.pp_double[offs+i][offs], 1, ae_v_len(offs,offs+i-1), v); + a->ptr.pp_double[offs+i][offs+i] = ae_sqr(a->ptr.pp_double[offs+i][offs+i], _state); + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * Recursive code: triangular factor inversion merged with + * UU' or L'L multiplication + */ + ablassplitlength(a, n, &n1, &n2, _state); + + /* + * form off-diagonal block of trangular inverse + */ + if( isupper ) + { + for(i=0; i<=n1-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, ae_false, 0, a, offs, offs+n1, _state); + rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, ae_false, 0, a, offs, offs+n1, _state); + } + else + { + for(i=0; i<=n2-1; i++) + { + ae_v_muld(&a->ptr.pp_double[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, ae_false, 0, a, offs+n1, offs, _state); + rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, ae_false, 0, a, offs+n1, offs, _state); + } + + /* + * invert first diagonal block + */ + matinv_spdmatrixcholeskyinverserec(a, offs, n1, isupper, tmp, _state); + + /* + * update first diagonal block with off-diagonal block, + * update off-diagonal block + */ + if( isupper ) + { + rmatrixsyrk(n1, n2, 1.0, a, offs, offs+n1, 0, 1.0, a, offs, offs, isupper, _state); + rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, ae_false, 1, a, offs, offs+n1, _state); + } + else + { + rmatrixsyrk(n1, n2, 1.0, a, offs+n1, offs, 1, 1.0, a, offs, offs, isupper, _state); + rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, ae_false, 1, a, offs+n1, offs, _state); + } + + /* + * invert second diagonal block + */ + matinv_spdmatrixcholeskyinverserec(a, offs+n1, n2, isupper, tmp, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Recursive subroutine for HPD inversion. + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +static void matinv_hpdmatrixcholeskyinverserec(/* Complex */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_complex v; + ae_int_t n1; + ae_int_t n2; + ae_int_t info2; + matinvreport rep2; + + ae_frame_make(_state, &_frame_block); + _matinvreport_init(&rep2, _state, ae_true); + + if( n<1 ) + { + ae_frame_leave(_state); + return; + } + + /* + * Base case + */ + if( n<=ablascomplexblocksize(a, _state) ) + { + matinv_cmatrixtrinverserec(a, offs, n, isupper, ae_false, tmp, &info2, &rep2, _state); + if( isupper ) + { + + /* + * Compute the product U * U'. + * NOTE: we never assume that diagonal of U is real + */ + for(i=0; i<=n-1; i++) + { + if( i==0 ) + { + + /* + * 1x1 matrix + */ + a->ptr.pp_complex[offs+i][offs+i] = ae_complex_from_d(ae_sqr(a->ptr.pp_complex[offs+i][offs+i].x, _state)+ae_sqr(a->ptr.pp_complex[offs+i][offs+i].y, _state)); + } + else + { + + /* + * (I+1)x(I+1) matrix, + * + * ( A11 A12 ) ( A11^H ) ( A11*A11^H+A12*A12^H A12*A22^H ) + * ( ) * ( ) = ( ) + * ( A22 ) ( A12^H A22^H ) ( A22*A12^H A22*A22^H ) + * + * A11 is IxI, A22 is 1x1. + */ + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs][offs+i], a->stride, "Conj", ae_v_len(0,i-1)); + for(j=0; j<=i-1; j++) + { + v = a->ptr.pp_complex[offs+j][offs+i]; + ae_v_caddc(&a->ptr.pp_complex[offs+j][offs+j], 1, &tmp->ptr.p_complex[j], 1, "N", ae_v_len(offs+j,offs+i-1), v); + } + v = ae_c_conj(a->ptr.pp_complex[offs+i][offs+i], _state); + ae_v_cmulc(&a->ptr.pp_complex[offs][offs+i], a->stride, ae_v_len(offs,offs+i-1), v); + a->ptr.pp_complex[offs+i][offs+i] = ae_complex_from_d(ae_sqr(a->ptr.pp_complex[offs+i][offs+i].x, _state)+ae_sqr(a->ptr.pp_complex[offs+i][offs+i].y, _state)); + } + } + } + else + { + + /* + * Compute the product L' * L + * NOTE: we never assume that diagonal of L is real + */ + for(i=0; i<=n-1; i++) + { + if( i==0 ) + { + + /* + * 1x1 matrix + */ + a->ptr.pp_complex[offs+i][offs+i] = ae_complex_from_d(ae_sqr(a->ptr.pp_complex[offs+i][offs+i].x, _state)+ae_sqr(a->ptr.pp_complex[offs+i][offs+i].y, _state)); + } + else + { + + /* + * (I+1)x(I+1) matrix, + * + * ( A11^H A21^H ) ( A11 ) ( A11^H*A11+A21^H*A21 A21^H*A22 ) + * ( ) * ( ) = ( ) + * ( A22^H ) ( A21 A22 ) ( A22^H*A21 A22^H*A22 ) + * + * A11 is IxI, A22 is 1x1. + */ + ae_v_cmove(&tmp->ptr.p_complex[0], 1, &a->ptr.pp_complex[offs+i][offs], 1, "N", ae_v_len(0,i-1)); + for(j=0; j<=i-1; j++) + { + v = ae_c_conj(a->ptr.pp_complex[offs+i][offs+j], _state); + ae_v_caddc(&a->ptr.pp_complex[offs+j][offs], 1, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(offs,offs+j), v); + } + v = ae_c_conj(a->ptr.pp_complex[offs+i][offs+i], _state); + ae_v_cmulc(&a->ptr.pp_complex[offs+i][offs], 1, ae_v_len(offs,offs+i-1), v); + a->ptr.pp_complex[offs+i][offs+i] = ae_complex_from_d(ae_sqr(a->ptr.pp_complex[offs+i][offs+i].x, _state)+ae_sqr(a->ptr.pp_complex[offs+i][offs+i].y, _state)); + } + } + } + ae_frame_leave(_state); + return; + } + + /* + * Recursive code: triangular factor inversion merged with + * UU' or L'L multiplication + */ + ablascomplexsplitlength(a, n, &n1, &n2, _state); + + /* + * form off-diagonal block of trangular inverse + */ + if( isupper ) + { + for(i=0; i<=n1-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+i][offs+n1], 1, ae_v_len(offs+n1,offs+n-1), -1); + } + cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, ae_false, 0, a, offs, offs+n1, _state); + cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, ae_false, 0, a, offs, offs+n1, _state); + } + else + { + for(i=0; i<=n2-1; i++) + { + ae_v_cmuld(&a->ptr.pp_complex[offs+n1+i][offs], 1, ae_v_len(offs,offs+n1-1), -1); + } + cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, ae_false, 0, a, offs+n1, offs, _state); + cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, ae_false, 0, a, offs+n1, offs, _state); + } + + /* + * invert first diagonal block + */ + matinv_hpdmatrixcholeskyinverserec(a, offs, n1, isupper, tmp, _state); + + /* + * update first diagonal block with off-diagonal block, + * update off-diagonal block + */ + if( isupper ) + { + cmatrixsyrk(n1, n2, 1.0, a, offs, offs+n1, 0, 1.0, a, offs, offs, isupper, _state); + cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, ae_false, 2, a, offs, offs+n1, _state); + } + else + { + cmatrixsyrk(n1, n2, 1.0, a, offs+n1, offs, 2, 1.0, a, offs, offs, isupper, _state); + cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, ae_false, 2, a, offs+n1, offs, _state); + } + + /* + * invert second diagonal block + */ + matinv_hpdmatrixcholeskyinverserec(a, offs+n1, n2, isupper, tmp, _state); + ae_frame_leave(_state); +} + + +ae_bool _matinvreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + matinvreport *p = (matinvreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _matinvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + matinvreport *dst = (matinvreport*)_dst; + matinvreport *src = (matinvreport*)_src; + dst->r1 = src->r1; + dst->rinf = src->rinf; + return ae_true; +} + + +void _matinvreport_clear(void* _p) +{ + matinvreport *p = (matinvreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _matinvreport_destroy(void* _p) +{ + matinvreport *p = (matinvreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +This function creates sparse matrix in a Hash-Table format. + +This function creates Hast-Table matrix, which can be converted to CRS +format after its initialization is over. Typical usage scenario for a +sparse matrix is: +1. creation in a Hash-Table format +2. insertion of the matrix elements +3. conversion to the CRS representation +4. matrix is passed to some linear algebra algorithm + +Some information about different matrix formats can be found below, in +the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + K - K>=0, expected number of non-zero elements in a matrix. + K can be inexact approximation, can be less than actual + number of elements (table will grow when needed) or + even zero). + It is important to understand that although hash-table + may grow automatically, it is better to provide good + estimate of data size. + +OUTPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + All elements of the matrix are zero. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreate(ae_int_t m, + ae_int_t n, + ae_int_t k, + sparsematrix* s, + ae_state *_state) +{ + ae_int_t i; + ae_int_t sz; + + _sparsematrix_clear(s); + + ae_assert(m>0, "SparseCreate: M<=0", _state); + ae_assert(n>0, "SparseCreate: N<=0", _state); + ae_assert(k>=0, "SparseCreate: K<0", _state); + sz = ae_round(k/sparse_desiredloadfactor+sparse_additional, _state); + s->matrixtype = 0; + s->m = m; + s->n = n; + s->nfree = sz; + ae_vector_set_length(&s->vals, sz, _state); + ae_vector_set_length(&s->idx, 2*sz, _state); + for(i=0; i<=sz-1; i++) + { + s->idx.ptr.p_int[2*i] = -1; + } +} + + +/************************************************************************* +This function creates sparse matrix in a CRS format (expert function for +situations when you are running out of memory). + +This function creates CRS matrix. Typical usage scenario for a CRS matrix +is: +1. creation (you have to tell number of non-zero elements at each row at + this moment) +2. insertion of the matrix elements (row by row, from left to right) +3. matrix is passed to some linear algebra algorithm + +This function is a memory-efficient alternative to SparseCreate(), but it +is more complex because it requires you to know in advance how large your +matrix is. Some information about different matrix formats can be found +below, in the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + NER - number of elements at each row, array[M], NER[I]>=0 + +OUTPUT PARAMETERS + S - sparse M*N matrix in CRS representation. + You have to fill ALL non-zero elements by calling + SparseSet() BEFORE you try to use this matrix. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreatecrs(ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* ner, + sparsematrix* s, + ae_state *_state) +{ + ae_int_t i; + ae_int_t noe; + + _sparsematrix_clear(s); + + ae_assert(m>0, "SparseCreateCRS: M<=0", _state); + ae_assert(n>0, "SparseCreateCRS: N<=0", _state); + ae_assert(ner->cnt>=m, "SparseCreateCRS: Length(NER)matrixtype = 1; + s->ninitialized = 0; + s->m = m; + s->n = n; + ae_vector_set_length(&s->ridx, s->m+1, _state); + s->ridx.ptr.p_int[0] = 0; + for(i=0; i<=s->m-1; i++) + { + ae_assert(ner->ptr.p_int[i]>=0, "SparseCreateCRS: NER[] contains negative elements", _state); + noe = noe+ner->ptr.p_int[i]; + s->ridx.ptr.p_int[i+1] = s->ridx.ptr.p_int[i]+ner->ptr.p_int[i]; + } + ae_vector_set_length(&s->vals, noe, _state); + ae_vector_set_length(&s->idx, noe, _state); + if( noe==0 ) + { + sparse_sparseinitduidx(s, _state); + } +} + + +/************************************************************************* +This function copies S0 to S1. + +NOTE: this function does not verify its arguments, it just copies all +fields of the structure. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecopy(sparsematrix* s0, sparsematrix* s1, ae_state *_state) +{ + ae_int_t l; + ae_int_t i; + + _sparsematrix_clear(s1); + + s1->matrixtype = s0->matrixtype; + s1->m = s0->m; + s1->n = s0->n; + s1->nfree = s0->nfree; + s1->ninitialized = s0->ninitialized; + + /* + * Initialization for arrays + */ + l = s0->vals.cnt; + ae_vector_set_length(&s1->vals, l, _state); + for(i=0; i<=l-1; i++) + { + s1->vals.ptr.p_double[i] = s0->vals.ptr.p_double[i]; + } + l = s0->ridx.cnt; + ae_vector_set_length(&s1->ridx, l, _state); + for(i=0; i<=l-1; i++) + { + s1->ridx.ptr.p_int[i] = s0->ridx.ptr.p_int[i]; + } + l = s0->idx.cnt; + ae_vector_set_length(&s1->idx, l, _state); + for(i=0; i<=l-1; i++) + { + s1->idx.ptr.p_int[i] = s0->idx.ptr.p_int[i]; + } + + /* + * Initalization for CRS-parameters + */ + l = s0->uidx.cnt; + ae_vector_set_length(&s1->uidx, l, _state); + for(i=0; i<=l-1; i++) + { + s1->uidx.ptr.p_int[i] = s0->uidx.ptr.p_int[i]; + } + l = s0->didx.cnt; + ae_vector_set_length(&s1->didx, l, _state); + for(i=0; i<=l-1; i++) + { + s1->didx.ptr.p_int[i] = s0->didx.ptr.p_int[i]; + } +} + + +/************************************************************************* +This function adds value to S[i,j] - element of the sparse matrix. Matrix +must be in a Hash-Table mode. + +In case S[i,j] already exists in the table, V i added to its value. In +case S[i,j] is non-existent, it is inserted in the table. Table +automatically grows when necessary. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - row index of the element to modify, 0<=Imatrixtype==0, "SparseAdd: matrix must be in the Hash-Table mode to do this operation", _state); + ae_assert(i>=0, "SparseAdd: I<0", _state); + ae_assert(im, "SparseAdd: I>=M", _state); + ae_assert(j>=0, "SparseAdd: J<0", _state); + ae_assert(jn, "SparseAdd: J>=N", _state); + ae_assert(ae_isfinite(v, _state), "SparseAdd: V is not finite number", _state); + if( ae_fp_eq(v,0) ) + { + return; + } + tcode = -1; + k = s->vals.cnt; + if( ae_fp_greater_eq((1-sparse_maxloadfactor)*k,s->nfree) ) + { + sparseresizematrix(s, _state); + k = s->vals.cnt; + } + hashcode = sparse_hash(i, j, k, _state); + for(;;) + { + if( s->idx.ptr.p_int[2*hashcode]==-1 ) + { + if( tcode!=-1 ) + { + hashcode = tcode; + } + s->vals.ptr.p_double[hashcode] = v; + s->idx.ptr.p_int[2*hashcode] = i; + s->idx.ptr.p_int[2*hashcode+1] = j; + if( tcode==-1 ) + { + s->nfree = s->nfree-1; + } + return; + } + else + { + if( s->idx.ptr.p_int[2*hashcode]==i&&s->idx.ptr.p_int[2*hashcode+1]==j ) + { + s->vals.ptr.p_double[hashcode] = s->vals.ptr.p_double[hashcode]+v; + if( ae_fp_eq(s->vals.ptr.p_double[hashcode],0) ) + { + s->idx.ptr.p_int[2*hashcode] = -2; + } + return; + } + + /* + * Is it deleted element? + */ + if( tcode==-1&&s->idx.ptr.p_int[2*hashcode]==-2 ) + { + tcode = hashcode; + } + + /* + * Next step + */ + hashcode = (hashcode+1)%k; + } + } +} + + +/************************************************************************* +This function modifies S[i,j] - element of the sparse matrix. + +For Hash-based storage format: +* new value can be zero or non-zero. In case new value of S[i,j] is zero, + this element is deleted from the table. +* this function has no effect when called with zero V for non-existent + element. + +For CRS-bases storage format: +* new value MUST be non-zero. Exception will be thrown for zero V. +* elements must be initialized in correct order - from top row to bottom, + within row - from left to right. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + I - row index of the element to modify, 0<=I=0, "SparseSet: I<0", _state); + ae_assert(im, "SparseSet: I>=M", _state); + ae_assert(j>=0, "SparseSet: J<0", _state); + ae_assert(jn, "SparseSet: J>=N", _state); + ae_assert(ae_isfinite(v, _state), "SparseSet: V is not finite number", _state); + + /* + * Hash-table matrix + */ + if( s->matrixtype==0 ) + { + tcode = -1; + k = s->vals.cnt; + if( ae_fp_greater_eq((1-sparse_maxloadfactor)*k,s->nfree) ) + { + sparseresizematrix(s, _state); + k = s->vals.cnt; + } + hashcode = sparse_hash(i, j, k, _state); + for(;;) + { + if( s->idx.ptr.p_int[2*hashcode]==-1 ) + { + if( ae_fp_neq(v,0) ) + { + if( tcode!=-1 ) + { + hashcode = tcode; + } + s->vals.ptr.p_double[hashcode] = v; + s->idx.ptr.p_int[2*hashcode] = i; + s->idx.ptr.p_int[2*hashcode+1] = j; + if( tcode==-1 ) + { + s->nfree = s->nfree-1; + } + } + return; + } + else + { + if( s->idx.ptr.p_int[2*hashcode]==i&&s->idx.ptr.p_int[2*hashcode+1]==j ) + { + if( ae_fp_eq(v,0) ) + { + s->idx.ptr.p_int[2*hashcode] = -2; + } + else + { + s->vals.ptr.p_double[hashcode] = v; + } + return; + } + if( tcode==-1&&s->idx.ptr.p_int[2*hashcode]==-2 ) + { + tcode = hashcode; + } + + /* + * Next step + */ + hashcode = (hashcode+1)%k; + } + } + } + + /* + * CRS matrix + */ + if( s->matrixtype==1 ) + { + ae_assert(ae_fp_neq(v,0), "SparseSet: CRS format does not allow you to write zero elements", _state); + ae_assert(s->ridx.ptr.p_int[i]<=s->ninitialized, "SparseSet: too few initialized elements at some row (you have promised more when called SparceCreateCRS)", _state); + ae_assert(s->ridx.ptr.p_int[i+1]>s->ninitialized, "SparseSet: too many initialized elements at some row (you have promised less when called SparceCreateCRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[i]||s->idx.ptr.p_int[s->ninitialized-1]vals.ptr.p_double[s->ninitialized] = v; + s->idx.ptr.p_int[s->ninitialized] = j; + s->ninitialized = s->ninitialized+1; + + /* + * If matrix has been created then + * initiale 'S.UIdx' and 'S.DIdx' + */ + if( s->ninitialized==s->ridx.ptr.p_int[s->m] ) + { + sparse_sparseinitduidx(s, _state); + } + } +} + + +/************************************************************************* +This function returns S[i,j] - element of the sparse matrix. Matrix can +be in any mode (Hash-Table or CRS), but this function is less efficient +for CRS matrices. Hash-Table matrices can find element in O(1) time, +while CRS matrices need O(log(RS)) time, where RS is an number of non- +zero elements in a row. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - row index of the element to modify, 0<=I=0, "SparseGet: I<0", _state); + ae_assert(im, "SparseGet: I>=M", _state); + ae_assert(j>=0, "SparseGet: J<0", _state); + ae_assert(jn, "SparseGet: J>=N", _state); + k = s->vals.cnt; + result = 0; + if( s->matrixtype==0 ) + { + hashcode = sparse_hash(i, j, k, _state); + for(;;) + { + if( s->idx.ptr.p_int[2*hashcode]==-1 ) + { + return result; + } + if( s->idx.ptr.p_int[2*hashcode]==i&&s->idx.ptr.p_int[2*hashcode+1]==j ) + { + result = s->vals.ptr.p_double[hashcode]; + return result; + } + hashcode = (hashcode+1)%k; + } + } + if( s->matrixtype==1 ) + { + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseGet: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + k0 = s->ridx.ptr.p_int[i]; + k1 = s->ridx.ptr.p_int[i+1]-1; + while(k0<=k1) + { + k = (k0+k1)/2; + if( s->idx.ptr.p_int[k]==j ) + { + result = s->vals.ptr.p_double[k]; + return result; + } + if( s->idx.ptr.p_int[k]=0, "SparseGetDiagonal: I<0", _state); + ae_assert(im, "SparseGetDiagonal: I>=M", _state); + ae_assert(in, "SparseGetDiagonal: I>=N", _state); + result = 0; + if( s->matrixtype==0 ) + { + result = sparseget(s, i, i, _state); + return result; + } + if( s->matrixtype==1 ) + { + if( s->didx.ptr.p_int[i]!=s->uidx.ptr.p_int[i] ) + { + result = s->vals.ptr.p_double[s->didx.ptr.p_int[i]]; + } + return result; + } + return result; +} + + +/************************************************************************* +This function converts matrix to CRS format. + +Some algorithms (linear algebra ones, for example) require matrices in +CRS format. + +INPUT PARAMETERS + S - sparse M*N matrix in any format + +OUTPUT PARAMETERS + S - matrix in CRS format + +NOTE: this function has no effect when called with matrix which is +already in CRS mode. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparseconverttocrs(sparsematrix* s, ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_vector tvals; + ae_vector tidx; + ae_vector temp; + ae_int_t nonne; + ae_int_t k; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tvals, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tidx, 0, DT_INT, _state, ae_true); + ae_vector_init(&temp, 0, DT_INT, _state, ae_true); + + ae_assert(s->matrixtype==0||s->matrixtype==1, "SparseConvertToCRS: invalid matrix type", _state); + if( s->matrixtype==1 ) + { + ae_frame_leave(_state); + return; + } + s->matrixtype = 1; + nonne = 0; + k = s->vals.cnt; + ae_swap_vectors(&s->vals, &tvals); + ae_swap_vectors(&s->idx, &tidx); + ae_vector_set_length(&s->ridx, s->m+1, _state); + for(i=0; i<=s->m; i++) + { + s->ridx.ptr.p_int[i] = 0; + } + ae_vector_set_length(&temp, s->m, _state); + for(i=0; i<=s->m-1; i++) + { + temp.ptr.p_int[i] = 0; + } + + /* + * Number of elements per row + */ + for(i=0; i<=k-1; i++) + { + if( tidx.ptr.p_int[2*i]>=0 ) + { + s->ridx.ptr.p_int[tidx.ptr.p_int[2*i]+1] = s->ridx.ptr.p_int[tidx.ptr.p_int[2*i]+1]+1; + nonne = nonne+1; + } + } + + /* + * Fill RIdx (offsets of rows) + */ + for(i=0; i<=s->m-1; i++) + { + s->ridx.ptr.p_int[i+1] = s->ridx.ptr.p_int[i+1]+s->ridx.ptr.p_int[i]; + } + + /* + * Allocate memory + */ + ae_vector_set_length(&s->vals, nonne, _state); + ae_vector_set_length(&s->idx, nonne, _state); + for(i=0; i<=k-1; i++) + { + if( tidx.ptr.p_int[2*i]>=0 ) + { + s->vals.ptr.p_double[s->ridx.ptr.p_int[tidx.ptr.p_int[2*i]]+temp.ptr.p_int[tidx.ptr.p_int[2*i]]] = tvals.ptr.p_double[i]; + s->idx.ptr.p_int[s->ridx.ptr.p_int[tidx.ptr.p_int[2*i]]+temp.ptr.p_int[tidx.ptr.p_int[2*i]]] = tidx.ptr.p_int[2*i+1]; + temp.ptr.p_int[tidx.ptr.p_int[2*i]] = temp.ptr.p_int[tidx.ptr.p_int[2*i]]+1; + } + } + + /* + * Set NInitialized + */ + s->ninitialized = s->ridx.ptr.p_int[s->m]; + + /* + * Sorting of elements + */ + for(i=0; i<=s->m-1; i++) + { + tagsortmiddleir(&s->idx, &s->vals, s->ridx.ptr.p_int[i], s->ridx.ptr.p_int[i+1]-s->ridx.ptr.p_int[i], _state); + } + + /* + * Initialization 'S.UIdx' and 'S.DIdx' + */ + sparse_sparseinitduidx(s, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This function calculates matrix-vector product S*x. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[M], S*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemv(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + double tval; + ae_int_t i; + ae_int_t j; + ae_int_t lt; + ae_int_t rt; + + + ae_assert(s->matrixtype==1, "SparseMV: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(x->cnt>=s->n, "SparseMV: length(X)m, _state); + for(i=0; i<=s->m-1; i++) + { + tval = 0; + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + tval = tval+x->ptr.p_double[s->idx.ptr.p_int[j]]*s->vals.ptr.p_double[j]; + } + y->ptr.p_double[i] = tval; + } +} + + +/************************************************************************* +This function calculates matrix-vector product S^T*x. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[M], input vector. For performance reasons we + make only quick checks - we check that array size is + at least M, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[N], S^T*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemtv(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t lt; + ae_int_t rt; + ae_int_t ct; + double v; + + + ae_assert(s->matrixtype==1, "SparseMTV: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMTV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(x->cnt>=s->m, "SparseMTV: Length(X)n, _state); + for(i=0; i<=s->n-1; i++) + { + y->ptr.p_double[i] = 0; + } + for(i=0; i<=s->m-1; i++) + { + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + v = x->ptr.p_double[i]; + for(j=lt; j<=rt-1; j++) + { + ct = s->idx.ptr.p_int[j]; + y->ptr.p_double[ct] = y->ptr.p_double[ct]+v*s->vals.ptr.p_double[j]; + } + } +} + + +/************************************************************************* +This function simultaneously calculates two matrix-vector products: + S*x and S^T*x. +S must be square (non-rectangular) matrix stored in CRS format (exception +will be thrown otherwise). + +INPUT PARAMETERS + S - sparse N*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y0 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + Y1 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y0 - array[N], S*x + Y1 - array[N], S^T*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. It also throws exception when S is non-square. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemv2(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y0, + /* Real */ ae_vector* y1, + ae_state *_state) +{ + ae_int_t l; + double tval; + ae_int_t i; + ae_int_t j; + double vx; + double vs; + ae_int_t vi; + ae_int_t j0; + ae_int_t j1; + + + ae_assert(s->matrixtype==1, "SparseMV2: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(s->m==s->n, "SparseMV2: matrix is non-square", _state); + l = x->cnt; + ae_assert(l>=s->n, "SparseMV2: Length(X)n-1; i++) + { + y1->ptr.p_double[i] = 0; + } + for(i=0; i<=s->m-1; i++) + { + tval = 0; + vx = x->ptr.p_double[i]; + j0 = s->ridx.ptr.p_int[i]; + j1 = s->ridx.ptr.p_int[i+1]-1; + for(j=j0; j<=j1; j++) + { + vi = s->idx.ptr.p_int[j]; + vs = s->vals.ptr.p_double[j]; + tval = tval+x->ptr.p_double[vi]*vs; + y1->ptr.p_double[vi] = y1->ptr.p_double[vi]+vx*vs; + } + y0->ptr.p_double[i] = tval; + } +} + + +/************************************************************************* +This function calculates matrix-vector product S*x, when S is symmetric +matrix. Matrix S must be stored in CRS format (exception will be +thrown otherwise). + +INPUT PARAMETERS + S - sparse M*M matrix in CRS format (you MUST convert it + to CRS before calling this function). + IsUpper - whether upper or lower triangle of S is given: + * if upper triangle is given, only S[i,j] for j>=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[M], S*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmv(sparsematrix* s, + ae_bool isupper, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t id; + ae_int_t lt; + ae_int_t rt; + double v; + double vy; + double vx; + + + ae_assert(s->matrixtype==1, "SparseSMV: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseSMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(x->cnt>=s->n, "SparseSMV: length(X)m==s->n, "SparseSMV: non-square matrix", _state); + rvectorsetlengthatleast(y, s->m, _state); + for(i=0; i<=s->m-1; i++) + { + y->ptr.p_double[i] = 0; + } + for(i=0; i<=s->m-1; i++) + { + if( s->didx.ptr.p_int[i]!=s->uidx.ptr.p_int[i] ) + { + y->ptr.p_double[i] = y->ptr.p_double[i]+s->vals.ptr.p_double[s->didx.ptr.p_int[i]]*x->ptr.p_double[s->idx.ptr.p_int[s->didx.ptr.p_int[i]]]; + } + if( isupper ) + { + lt = s->uidx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + vy = 0; + vx = x->ptr.p_double[i]; + for(j=lt; j<=rt-1; j++) + { + id = s->idx.ptr.p_int[j]; + v = s->vals.ptr.p_double[j]; + vy = vy+x->ptr.p_double[id]*v; + y->ptr.p_double[id] = y->ptr.p_double[id]+vx*v; + } + y->ptr.p_double[i] = y->ptr.p_double[i]+vy; + } + else + { + lt = s->ridx.ptr.p_int[i]; + rt = s->didx.ptr.p_int[i]; + vy = 0; + vx = x->ptr.p_double[i]; + for(j=lt; j<=rt-1; j++) + { + id = s->idx.ptr.p_int[j]; + v = s->vals.ptr.p_double[j]; + vy = vy+x->ptr.p_double[id]*v; + y->ptr.p_double[id] = y->ptr.p_double[id]+vx*v; + } + y->ptr.p_double[i] = y->ptr.p_double[i]+vy; + } + } +} + + +/************************************************************************* +This function calculates matrix-matrix product S*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size + is at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state) +{ + double tval; + double v; + ae_int_t id; + ae_int_t i; + ae_int_t j; + ae_int_t k0; + ae_int_t lt; + ae_int_t rt; + + + ae_assert(s->matrixtype==1, "SparseMV: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(a->rows>=s->n, "SparseMV: Rows(A)0, "SparseMV: K<=0", _state); + rmatrixsetlengthatleast(b, s->m, k, _state); + if( km-1; i++) + { + for(j=0; j<=k-1; j++) + { + tval = 0; + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(k0=lt; k0<=rt-1; k0++) + { + tval = tval+s->vals.ptr.p_double[k0]*a->ptr.pp_double[s->idx.ptr.p_int[k0]][j]; + } + b->ptr.pp_double[i][j] = tval; + } + } + } + else + { + for(i=0; i<=s->m-1; i++) + { + for(j=0; j<=k-1; j++) + { + b->ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=s->m-1; i++) + { + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + id = s->idx.ptr.p_int[j]; + v = s->vals.ptr.p_double[j]; + ae_v_addd(&b->ptr.pp_double[i][0], 1, &a->ptr.pp_double[id][0], 1, ae_v_len(0,k-1), v); + } + } + } +} + + +/************************************************************************* +This function calculates matrix-matrix product S^T*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[M][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least M, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemtm(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k0; + ae_int_t lt; + ae_int_t rt; + ae_int_t ct; + double v; + + + ae_assert(s->matrixtype==1, "SparseMTM: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMTM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(a->rows>=s->m, "SparseMTM: Rows(A)0, "SparseMTM: K<=0", _state); + rmatrixsetlengthatleast(b, s->n, k, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=k-1; j++) + { + b->ptr.pp_double[i][j] = 0; + } + } + if( km-1; i++) + { + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(k0=lt; k0<=rt-1; k0++) + { + v = s->vals.ptr.p_double[k0]; + ct = s->idx.ptr.p_int[k0]; + for(j=0; j<=k-1; j++) + { + b->ptr.pp_double[ct][j] = b->ptr.pp_double[ct][j]+v*a->ptr.pp_double[i][j]; + } + } + } + } + else + { + for(i=0; i<=s->m-1; i++) + { + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + v = s->vals.ptr.p_double[j]; + ct = s->idx.ptr.p_int[j]; + ae_v_addd(&b->ptr.pp_double[ct][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,k-1), v); + } + } + } +} + + +/************************************************************************* +This function simultaneously calculates two matrix-matrix products: + S*A and S^T*A. +S must be square (non-rectangular) matrix stored in CRS format (exception +will be thrown otherwise). + +INPUT PARAMETERS + S - sparse N*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B0 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + B1 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B0 - array[N][K], S*A + B1 - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. It also throws exception when S is non-square. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm2(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b0, + /* Real */ ae_matrix* b1, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k0; + ae_int_t lt; + ae_int_t rt; + ae_int_t ct; + double v; + double tval; + + + ae_assert(s->matrixtype==1, "SparseMM2: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseMM2: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(s->m==s->n, "SparseMM2: matrix is non-square", _state); + ae_assert(a->rows>=s->n, "SparseMM2: Rows(A)0, "SparseMM2: K<=0", _state); + rmatrixsetlengthatleast(b0, s->m, k, _state); + rmatrixsetlengthatleast(b1, s->n, k, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=0; j<=k-1; j++) + { + b1->ptr.pp_double[i][j] = 0; + } + } + if( km-1; i++) + { + for(j=0; j<=k-1; j++) + { + tval = 0; + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + v = a->ptr.pp_double[i][j]; + for(k0=lt; k0<=rt-1; k0++) + { + ct = s->idx.ptr.p_int[k0]; + b1->ptr.pp_double[ct][j] = b1->ptr.pp_double[ct][j]+s->vals.ptr.p_double[k0]*v; + tval = tval+s->vals.ptr.p_double[k0]*a->ptr.pp_double[ct][j]; + } + b0->ptr.pp_double[i][j] = tval; + } + } + } + else + { + for(i=0; i<=s->m-1; i++) + { + for(j=0; j<=k-1; j++) + { + b0->ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=s->m-1; i++) + { + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + v = s->vals.ptr.p_double[j]; + ct = s->idx.ptr.p_int[j]; + ae_v_addd(&b0->ptr.pp_double[i][0], 1, &a->ptr.pp_double[ct][0], 1, ae_v_len(0,k-1), v); + ae_v_addd(&b1->ptr.pp_double[ct][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,k-1), v); + } + } + } +} + + +/************************************************************************* +This function calculates matrix-matrix product S*A, when S is symmetric +matrix. Matrix S must be stored in CRS format (exception will be +thrown otherwise). + +INPUT PARAMETERS + S - sparse M*M matrix in CRS format (you MUST convert it + to CRS before calling this function). + IsUpper - whether upper or lower triangle of S is given: + * if upper triangle is given, only S[i,j] for j>=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmm(sparsematrix* s, + ae_bool isupper, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t k0; + ae_int_t id; + ae_int_t lt; + ae_int_t rt; + double v; + double vb; + double va; + + + ae_assert(s->matrixtype==1, "SparseSMM: incorrect matrix type (convert your matrix to CRS)", _state); + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseSMM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + ae_assert(a->rows>=s->n, "SparseSMM: Rows(X)m==s->n, "SparseSMM: matrix is non-square", _state); + rmatrixsetlengthatleast(b, s->m, k, _state); + for(i=0; i<=s->m-1; i++) + { + for(j=0; j<=k-1; j++) + { + b->ptr.pp_double[i][j] = 0; + } + } + if( k>sparse_linalgswitch ) + { + for(i=0; i<=s->m-1; i++) + { + for(j=0; j<=k-1; j++) + { + if( s->didx.ptr.p_int[i]!=s->uidx.ptr.p_int[i] ) + { + id = s->didx.ptr.p_int[i]; + b->ptr.pp_double[i][j] = b->ptr.pp_double[i][j]+s->vals.ptr.p_double[id]*a->ptr.pp_double[s->idx.ptr.p_int[id]][j]; + } + if( isupper ) + { + lt = s->uidx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + vb = 0; + va = a->ptr.pp_double[i][j]; + for(k0=lt; k0<=rt-1; k0++) + { + id = s->idx.ptr.p_int[k0]; + v = s->vals.ptr.p_double[k0]; + vb = vb+a->ptr.pp_double[id][j]*v; + b->ptr.pp_double[id][j] = b->ptr.pp_double[id][j]+va*v; + } + b->ptr.pp_double[i][j] = b->ptr.pp_double[i][j]+vb; + } + else + { + lt = s->ridx.ptr.p_int[i]; + rt = s->didx.ptr.p_int[i]; + vb = 0; + va = a->ptr.pp_double[i][j]; + for(k0=lt; k0<=rt-1; k0++) + { + id = s->idx.ptr.p_int[k0]; + v = s->vals.ptr.p_double[k0]; + vb = vb+a->ptr.pp_double[id][j]*v; + b->ptr.pp_double[id][j] = b->ptr.pp_double[id][j]+va*v; + } + b->ptr.pp_double[i][j] = b->ptr.pp_double[i][j]+vb; + } + } + } + } + else + { + for(i=0; i<=s->m-1; i++) + { + if( s->didx.ptr.p_int[i]!=s->uidx.ptr.p_int[i] ) + { + id = s->didx.ptr.p_int[i]; + v = s->vals.ptr.p_double[id]; + ae_v_addd(&b->ptr.pp_double[i][0], 1, &a->ptr.pp_double[s->idx.ptr.p_int[id]][0], 1, ae_v_len(0,k-1), v); + } + if( isupper ) + { + lt = s->uidx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + id = s->idx.ptr.p_int[j]; + v = s->vals.ptr.p_double[j]; + ae_v_addd(&b->ptr.pp_double[i][0], 1, &a->ptr.pp_double[id][0], 1, ae_v_len(0,k-1), v); + ae_v_addd(&b->ptr.pp_double[id][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,k-1), v); + } + } + else + { + lt = s->ridx.ptr.p_int[i]; + rt = s->didx.ptr.p_int[i]; + for(j=lt; j<=rt-1; j++) + { + id = s->idx.ptr.p_int[j]; + v = s->vals.ptr.p_double[j]; + ae_v_addd(&b->ptr.pp_double[i][0], 1, &a->ptr.pp_double[id][0], 1, ae_v_len(0,k-1), v); + ae_v_addd(&b->ptr.pp_double[id][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,k-1), v); + } + } + } + } +} + + +/************************************************************************* +This procedure resizes Hash-Table matrix. It can be called when you have +deleted too many elements from the matrix, and you want to free unneeded +memory. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparseresizematrix(sparsematrix* s, ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t k; + ae_int_t k1; + ae_int_t i; + ae_vector tvals; + ae_vector tidx; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tvals, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tidx, 0, DT_INT, _state, ae_true); + + ae_assert(s->matrixtype==0, "SparseResizeMatrix: incorrect matrix type", _state); + + /* + * Initialization for length and number of non-null elementd + */ + k = s->vals.cnt; + k1 = 0; + + /* + * Calculating number of non-null elements + */ + for(i=0; i<=k-1; i++) + { + if( s->idx.ptr.p_int[2*i]>=0 ) + { + k1 = k1+1; + } + } + + /* + * Initialization value for free space + */ + s->nfree = ae_round(k1/sparse_desiredloadfactor*sparse_growfactor+sparse_additional, _state)-k1; + ae_vector_set_length(&tvals, s->nfree+k1, _state); + ae_vector_set_length(&tidx, 2*(s->nfree+k1), _state); + ae_swap_vectors(&s->vals, &tvals); + ae_swap_vectors(&s->idx, &tidx); + for(i=0; i<=s->nfree+k1-1; i++) + { + s->idx.ptr.p_int[2*i] = -1; + } + for(i=0; i<=k-1; i++) + { + if( tidx.ptr.p_int[2*i]>=0 ) + { + sparseset(s, tidx.ptr.p_int[2*i], tidx.ptr.p_int[2*i+1], tvals.ptr.p_double[i], _state); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function return average length of chain at hash-table. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +double sparsegetaveragelengthofchain(sparsematrix* s, ae_state *_state) +{ + ae_int_t nchains; + ae_int_t talc; + ae_int_t l; + ae_int_t i; + ae_int_t ind0; + ae_int_t ind1; + ae_int_t hashcode; + double result; + + + + /* + * If matrix represent in CRS then return zero and exit + */ + if( s->matrixtype==1 ) + { + result = 0; + return result; + } + nchains = 0; + talc = 0; + l = s->vals.cnt; + for(i=0; i<=l-1; i++) + { + ind0 = 2*i; + if( s->idx.ptr.p_int[ind0]!=-1 ) + { + nchains = nchains+1; + hashcode = sparse_hash(s->idx.ptr.p_int[ind0], s->idx.ptr.p_int[ind0+1], l, _state); + for(;;) + { + talc = talc+1; + ind1 = 2*hashcode; + if( s->idx.ptr.p_int[ind0]==s->idx.ptr.p_int[ind1]&&s->idx.ptr.p_int[ind0+1]==s->idx.ptr.p_int[ind1+1] ) + { + break; + } + hashcode = (hashcode+1)%l; + } + } + } + if( nchains==0 ) + { + result = 0; + } + else + { + result = (double)talc/(double)nchains; + } + return result; +} + + +/************************************************************************* +This function is used to enumerate all elements of the sparse matrix. +Before first call user initializes T0 and T1 counters by zero. These +counters are used to remember current position in a matrix; after each +call they are updated by the function. + +Subsequent calls to this function return non-zero elements of the sparse +matrix, one by one. If you enumerate CRS matrix, matrix is traversed from +left to right, from top to bottom. In case you enumerate matrix stored as +Hash table, elements are returned in random order. + +EXAMPLE + > T0=0 + > T1=0 + > while SparseEnumerate(S,T0,T1,I,J,V) do + > ....do something with I,J,V + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + T0 - internal counter + T1 - internal counter + +OUTPUT PARAMETERS + T0 - new value of the internal counter + T1 - new value of the internal counter + I - row index of non-zero element, 0<=Imatrixtype==1&&*t1<0) ) + { + result = ae_false; + return result; + } + + /* + * Hash-table matrix + */ + if( s->matrixtype==0 ) + { + sz = s->vals.cnt; + for(i0=*t0; i0<=sz-1; i0++) + { + if( s->idx.ptr.p_int[2*i0]==-1||s->idx.ptr.p_int[2*i0]==-2 ) + { + continue; + } + else + { + *i = s->idx.ptr.p_int[2*i0]; + *j = s->idx.ptr.p_int[2*i0+1]; + *v = s->vals.ptr.p_double[i0]; + *t0 = i0+1; + result = ae_true; + return result; + } + } + *t0 = 0; + result = ae_false; + return result; + } + + /* + * CRS matrix + */ + if( s->matrixtype==1&&*t0ninitialized ) + { + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseEnumerate: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + while(*t0>s->ridx.ptr.p_int[*t1+1]-1&&*t1m) + { + *t1 = *t1+1; + } + *i = *t1; + *j = s->idx.ptr.p_int[*t0]; + *v = s->vals.ptr.p_double[*t0]; + *t0 = *t0+1; + result = ae_true; + return result; + } + *t0 = 0; + *t1 = 0; + result = ae_false; + return result; +} + + +/************************************************************************* +This function rewrites existing (non-zero) element. It returns True if +element exists or False, when it is called for non-existing (zero) +element. + +The purpose of this function is to provide convenient thread-safe way to +modify sparse matrix. Such modification (already existing element is +rewritten) is guaranteed to be thread-safe without any synchronization, as +long as different threads modify different elements. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + I - row index of non-zero element to modify, 0<=Im, "SparseRewriteExisting: invalid argument I(either I<0 or I>=S.M)", _state); + ae_assert(0<=j&&jn, "SparseRewriteExisting: invalid argument J(either J<0 or J>=S.N)", _state); + ae_assert(ae_isfinite(v, _state), "SparseRewriteExisting: invalid argument V(either V is infinite or V is NaN)", _state); + result = ae_false; + + /* + * Hash-table matrix + */ + if( s->matrixtype==0 ) + { + k = s->vals.cnt; + hashcode = sparse_hash(i, j, k, _state); + for(;;) + { + if( s->idx.ptr.p_int[2*hashcode]==-1 ) + { + return result; + } + if( s->idx.ptr.p_int[2*hashcode]==i&&s->idx.ptr.p_int[2*hashcode+1]==j ) + { + s->vals.ptr.p_double[hashcode] = v; + result = ae_true; + return result; + } + hashcode = (hashcode+1)%k; + } + } + + /* + * CRS matrix + */ + if( s->matrixtype==1 ) + { + ae_assert(s->ninitialized==s->ridx.ptr.p_int[s->m], "SparseRewriteExisting: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)", _state); + k0 = s->ridx.ptr.p_int[i]; + k1 = s->ridx.ptr.p_int[i+1]-1; + while(k0<=k1) + { + k = (k0+k1)/2; + if( s->idx.ptr.p_int[k]==j ) + { + s->vals.ptr.p_double[k] = v; + result = ae_true; + return result; + } + if( s->idx.ptr.p_int[k]matrixtype==1, "SparseGetRow: S must be CRS-based matrix", _state); + ae_assert(i>=0&&im, "SparseGetRow: I<0 or I>=M", _state); + rvectorsetlengthatleast(irow, s->n, _state); + for(i0=0; i0<=s->n-1; i0++) + { + irow->ptr.p_double[i0] = 0; + } + for(i0=s->ridx.ptr.p_int[i]; i0<=s->ridx.ptr.p_int[i+1]-1; i0++) + { + irow->ptr.p_double[s->idx.ptr.p_int[i0]] = s->vals.ptr.p_double[i0]; + } +} + + +/************************************************************************* +This function performs in-place conversion from CRS format to Hash table +storage. + +INPUT PARAMETERS + S - sparse matrix in CRS format. + +OUTPUT PARAMETERS + S - sparse matrix in Hash table format. + +NOTE: this function has no effect when called with matrix which is +already in Hash table mode. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparseconverttohash(sparsematrix* s, ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tidx; + ae_vector tridx; + ae_vector tvals; + ae_int_t tn; + ae_int_t tm; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&tidx, 0, DT_INT, _state, ae_true); + ae_vector_init(&tridx, 0, DT_INT, _state, ae_true); + ae_vector_init(&tvals, 0, DT_REAL, _state, ae_true); + + ae_assert(s->matrixtype==0||s->matrixtype==1, "SparseConvertToHash: invalid matrix type", _state); + if( s->matrixtype==0 ) + { + ae_frame_leave(_state); + return; + } + s->matrixtype = 0; + tm = s->m; + tn = s->n; + ae_swap_vectors(&s->idx, &tidx); + ae_swap_vectors(&s->ridx, &tridx); + ae_swap_vectors(&s->vals, &tvals); + + /* + * Delete RIdx + */ + ae_vector_set_length(&s->ridx, 0, _state); + sparsecreate(tm, tn, tridx.ptr.p_int[tm], s, _state); + + /* + * Fill the matrix + */ + for(i=0; i<=tm-1; i++) + { + for(j=tridx.ptr.p_int[i]; j<=tridx.ptr.p_int[i+1]-1; j++) + { + sparseset(s, i, tidx.ptr.p_int[j], tvals.ptr.p_double[j], _state); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function performs out-of-place conversion to Hash table storage +format. S0 is copied to S1 and converted on-the-fly. + +INPUT PARAMETERS + S0 - sparse matrix in any format. + +OUTPUT PARAMETERS + S1 - sparse matrix in Hash table format. + +NOTE: if S0 is stored as Hash-table, it is just copied without conversion. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsecopytohash(sparsematrix* s0, + sparsematrix* s1, + ae_state *_state) +{ + double val; + ae_int_t t0; + ae_int_t t1; + ae_int_t i; + ae_int_t j; + + _sparsematrix_clear(s1); + + ae_assert(s0->matrixtype==0||s0->matrixtype==1, "SparseCopyToHash: invalid matrix type", _state); + if( s0->matrixtype==0 ) + { + sparsecopy(s0, s1, _state); + } + else + { + t0 = 0; + t1 = 0; + sparsecreate(s0->m, s0->n, s0->ridx.ptr.p_int[s0->m], s1, _state); + while(sparseenumerate(s0, &t0, &t1, &i, &j, &val, _state)) + { + sparseset(s1, i, j, val, _state); + } + } +} + + +/************************************************************************* +This function performs out-of-place conversion to CRS format. S0 is +copied to S1 and converted on-the-fly. + +INPUT PARAMETERS + S0 - sparse matrix in any format. + +OUTPUT PARAMETERS + S1 - sparse matrix in CRS format. + +NOTE: if S0 is stored as CRS, it is just copied without conversion. + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsecopytocrs(sparsematrix* s0, sparsematrix* s1, ae_state *_state) +{ + ae_frame _frame_block; + ae_vector temp; + ae_int_t nonne; + ae_int_t i; + ae_int_t k; + + ae_frame_make(_state, &_frame_block); + _sparsematrix_clear(s1); + ae_vector_init(&temp, 0, DT_INT, _state, ae_true); + + ae_assert(s0->matrixtype==0||s0->matrixtype==1, "SparseCopyToCRS: invalid matrix type", _state); + if( s0->matrixtype==1 ) + { + sparsecopy(s0, s1, _state); + } + else + { + + /* + * Done like ConvertToCRS function + */ + s1->matrixtype = 1; + s1->m = s0->m; + s1->n = s0->n; + s1->nfree = s0->nfree; + nonne = 0; + k = s0->vals.cnt; + ae_vector_set_length(&s1->ridx, s1->m+1, _state); + for(i=0; i<=s1->m; i++) + { + s1->ridx.ptr.p_int[i] = 0; + } + ae_vector_set_length(&temp, s1->m, _state); + for(i=0; i<=s1->m-1; i++) + { + temp.ptr.p_int[i] = 0; + } + + /* + * Number of elements per row + */ + for(i=0; i<=k-1; i++) + { + if( s0->idx.ptr.p_int[2*i]>=0 ) + { + s1->ridx.ptr.p_int[s0->idx.ptr.p_int[2*i]+1] = s1->ridx.ptr.p_int[s0->idx.ptr.p_int[2*i]+1]+1; + nonne = nonne+1; + } + } + + /* + * Fill RIdx (offsets of rows) + */ + for(i=0; i<=s1->m-1; i++) + { + s1->ridx.ptr.p_int[i+1] = s1->ridx.ptr.p_int[i+1]+s1->ridx.ptr.p_int[i]; + } + + /* + * Allocate memory + */ + ae_vector_set_length(&s1->vals, nonne, _state); + ae_vector_set_length(&s1->idx, nonne, _state); + for(i=0; i<=k-1; i++) + { + if( s0->idx.ptr.p_int[2*i]>=0 ) + { + s1->vals.ptr.p_double[s1->ridx.ptr.p_int[s0->idx.ptr.p_int[2*i]]+temp.ptr.p_int[s0->idx.ptr.p_int[2*i]]] = s0->vals.ptr.p_double[i]; + s1->idx.ptr.p_int[s1->ridx.ptr.p_int[s0->idx.ptr.p_int[2*i]]+temp.ptr.p_int[s0->idx.ptr.p_int[2*i]]] = s0->idx.ptr.p_int[2*i+1]; + temp.ptr.p_int[s0->idx.ptr.p_int[2*i]] = temp.ptr.p_int[s0->idx.ptr.p_int[2*i]]+1; + } + } + + /* + * Set NInitialized + */ + s1->ninitialized = s1->ridx.ptr.p_int[s1->m]; + + /* + * Sorting of elements + */ + for(i=0; i<=s1->m-1; i++) + { + tagsortmiddleir(&s1->idx, &s1->vals, s1->ridx.ptr.p_int[i], s1->ridx.ptr.p_int[i+1]-s1->ridx.ptr.p_int[i], _state); + } + + /* + * Initialization 'S.UIdx' and 'S.DIdx' + */ + sparse_sparseinitduidx(s1, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function returns type of the matrix storage format. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + sparse storage format used by matrix: + 0 - Hash-table + 1 - CRS-format + +NOTE: future versions of ALGLIB may include additional sparse storage + formats. + + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetmatrixtype(sparsematrix* s, ae_state *_state) +{ + ae_int_t result; + + + ae_assert(s->matrixtype==0||s->matrixtype==1, "SparseGetMatrixType: invalid matrix type", _state); + result = s->matrixtype; + return result; +} + + +/************************************************************************* +This function checks matrix storage format and returns True when matrix is +stored using Hash table representation. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + True if matrix type is Hash table + False if matrix type is not Hash table + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool sparseishash(sparsematrix* s, ae_state *_state) +{ + ae_bool result; + + + ae_assert(s->matrixtype==0||s->matrixtype==1, "SparseIsHash: invalid matrix type", _state); + result = s->matrixtype==0; + return result; +} + + +/************************************************************************* +This function checks matrix storage format and returns True when matrix is +stored using CRS representation. + +INPUT PARAMETERS: + S - sparse matrix. + +RESULT: + True if matrix type is CRS + False if matrix type is not CRS + + -- ALGLIB PROJECT -- + Copyright 20.07.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool sparseiscrs(sparsematrix* s, ae_state *_state) +{ + ae_bool result; + + + ae_assert(s->matrixtype==0||s->matrixtype==1, "SparseIsCRS: invalid matrix type", _state); + result = s->matrixtype==1; + return result; +} + + +/************************************************************************* +The function frees all memory occupied by sparse matrix. Sparse matrix +structure becomes unusable after this call. + +OUTPUT PARAMETERS + S - sparse matrix to delete + + -- ALGLIB PROJECT -- + Copyright 24.07.2012 by Bochkanov Sergey +*************************************************************************/ +void sparsefree(sparsematrix* s, ae_state *_state) +{ + + _sparsematrix_clear(s); + + s->matrixtype = -1; + s->m = 0; + s->n = 0; + s->nfree = 0; + s->ninitialized = 0; +} + + +/************************************************************************* +The function returns number of rows of a sparse matrix. + +RESULT: number of rows of a sparse matrix. + + -- ALGLIB PROJECT -- + Copyright 23.08.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetnrows(sparsematrix* s, ae_state *_state) +{ + ae_int_t result; + + + result = s->m; + return result; +} + + +/************************************************************************* +The function returns number of columns of a sparse matrix. + +RESULT: number of columns of a sparse matrix. + + -- ALGLIB PROJECT -- + Copyright 23.08.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sparsegetncols(sparsematrix* s, ae_state *_state) +{ + ae_int_t result; + + + result = s->n; + return result; +} + + +/************************************************************************* +Procedure for initialization 'S.DIdx' and 'S.UIdx' + + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +static void sparse_sparseinitduidx(sparsematrix* s, ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t lt; + ae_int_t rt; + + + ae_vector_set_length(&s->didx, s->m, _state); + ae_vector_set_length(&s->uidx, s->m, _state); + for(i=0; i<=s->m-1; i++) + { + s->uidx.ptr.p_int[i] = -1; + s->didx.ptr.p_int[i] = -1; + lt = s->ridx.ptr.p_int[i]; + rt = s->ridx.ptr.p_int[i+1]; + for(j=lt; j<=rt-1; j++) + { + if( iidx.ptr.p_int[j]&&s->uidx.ptr.p_int[i]==-1 ) + { + s->uidx.ptr.p_int[i] = j; + break; + } + else + { + if( i==s->idx.ptr.p_int[j] ) + { + s->didx.ptr.p_int[i] = j; + } + } + } + if( s->uidx.ptr.p_int[i]==-1 ) + { + s->uidx.ptr.p_int[i] = s->ridx.ptr.p_int[i+1]; + } + if( s->didx.ptr.p_int[i]==-1 ) + { + s->didx.ptr.p_int[i] = s->uidx.ptr.p_int[i]; + } + } +} + + +/************************************************************************* +This is hash function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +static ae_int_t sparse_hash(ae_int_t i, + ae_int_t j, + ae_int_t tabsize, + ae_state *_state) +{ + ae_frame _frame_block; + hqrndstate r; + ae_int_t result; + + ae_frame_make(_state, &_frame_block); + _hqrndstate_init(&r, _state, ae_true); + + hqrndseed(i, j, &r, _state); + result = hqrnduniformi(&r, tabsize, _state); + ae_frame_leave(_state); + return result; +} + + +ae_bool _sparsematrix_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sparsematrix *p = (sparsematrix*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->vals, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->idx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ridx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->didx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->uidx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _sparsematrix_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sparsematrix *dst = (sparsematrix*)_dst; + sparsematrix *src = (sparsematrix*)_src; + if( !ae_vector_init_copy(&dst->vals, &src->vals, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->idx, &src->idx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ridx, &src->ridx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->didx, &src->didx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->uidx, &src->uidx, _state, make_automatic) ) + return ae_false; + dst->matrixtype = src->matrixtype; + dst->m = src->m; + dst->n = src->n; + dst->nfree = src->nfree; + dst->ninitialized = src->ninitialized; + return ae_true; +} + + +void _sparsematrix_clear(void* _p) +{ + sparsematrix *p = (sparsematrix*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->vals); + ae_vector_clear(&p->idx); + ae_vector_clear(&p->ridx); + ae_vector_clear(&p->didx); + ae_vector_clear(&p->uidx); +} + + +void _sparsematrix_destroy(void* _p) +{ + sparsematrix *p = (sparsematrix*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->vals); + ae_vector_destroy(&p->idx); + ae_vector_destroy(&p->ridx); + ae_vector_destroy(&p->didx); + ae_vector_destroy(&p->uidx); +} + + + + +/************************************************************************* +Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y. + +This subroutine assumes that: +* A*ScaleA is well scaled +* A is well-conditioned, so no zero divisions or overflow may occur + +INPUT PARAMETERS: + CHA - Cholesky decomposition of A + SqrtScaleA- square root of scale factor ScaleA + N - matrix size, N>=0. + IsUpper - storage type + XB - right part + Tmp - buffer; function automatically allocates it, if it is too + small. It can be reused if function is called several + times. + +OUTPUT PARAMETERS: + XB - solution + +NOTE 1: no assertion or tests are done during algorithm operation +NOTE 2: N=0 will force algorithm to silently return + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void fblscholeskysolve(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + double v; + + + if( n==0 ) + { + return; + } + if( tmp->cntptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + if( iptr.p_double[i]; + ae_v_moved(&tmp->ptr.p_double[i+1], 1, &cha->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sqrtscalea); + ae_v_subd(&xb->ptr.p_double[i+1], 1, &tmp->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1), v); + } + } + + /* + * Solve U*x=y then. + */ + for(i=n-1; i>=0; i--) + { + if( iptr.p_double[i+1], 1, &cha->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sqrtscalea); + v = ae_v_dotproduct(&tmp->ptr.p_double[i+1], 1, &xb->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1)); + xb->ptr.p_double[i] = xb->ptr.p_double[i]-v; + } + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + } + } + else + { + + /* + * Solve L*y=b first + */ + for(i=0; i<=n-1; i++) + { + if( i>0 ) + { + ae_v_moved(&tmp->ptr.p_double[0], 1, &cha->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sqrtscalea); + v = ae_v_dotproduct(&tmp->ptr.p_double[0], 1, &xb->ptr.p_double[0], 1, ae_v_len(0,i-1)); + xb->ptr.p_double[i] = xb->ptr.p_double[i]-v; + } + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + } + + /* + * Solve L'*x=y then. + */ + for(i=n-1; i>=0; i--) + { + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + if( i>0 ) + { + v = xb->ptr.p_double[i]; + ae_v_moved(&tmp->ptr.p_double[0], 1, &cha->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sqrtscalea); + ae_v_subd(&xb->ptr.p_double[0], 1, &tmp->ptr.p_double[0], 1, ae_v_len(0,i-1), v); + } + } + } +} + + +/************************************************************************* +Fast basic linear solver: linear SPD CG + +Solves (A^T*A + alpha*I)*x = b where: +* A is MxN matrix +* alpha>0 is a scalar +* I is NxN identity matrix +* b is Nx1 vector +* X is Nx1 unknown vector. + +N iterations of linear conjugate gradient are used to solve problem. + +INPUT PARAMETERS: + A - array[M,N], matrix + M - number of rows + N - number of unknowns + B - array[N], right part + X - initial approxumation, array[N] + Buf - buffer; function automatically allocates it, if it is too + small. It can be reused if function is called several times + with same M and N. + +OUTPUT PARAMETERS: + X - improved solution + +NOTES: +* solver checks quality of improved solution. If (because of problem + condition number, numerical noise, etc.) new solution is WORSE than + original approximation, then original approximation is returned. +* solver assumes that both A, B, Alpha are well scaled (i.e. they are + less than sqrt(overflow) and greater than sqrt(underflow)). + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void fblssolvecgx(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + double alpha, + /* Real */ ae_vector* b, + /* Real */ ae_vector* x, + /* Real */ ae_vector* buf, + ae_state *_state) +{ + ae_int_t k; + ae_int_t offsrk; + ae_int_t offsrk1; + ae_int_t offsxk; + ae_int_t offsxk1; + ae_int_t offspk; + ae_int_t offspk1; + ae_int_t offstmp1; + ae_int_t offstmp2; + ae_int_t bs; + double e1; + double e2; + double rk2; + double rk12; + double pap; + double s; + double betak; + double v1; + double v2; + + + + /* + * Test for special case: B=0 + */ + v1 = ae_v_dotproduct(&b->ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_eq(v1,0) ) + { + for(k=0; k<=n-1; k++) + { + x->ptr.p_double[k] = 0; + } + return; + } + + /* + * Offsets inside Buf for: + * * R[K], R[K+1] + * * X[K], X[K+1] + * * P[K], P[K+1] + * * Tmp1 - array[M], Tmp2 - array[N] + */ + offsrk = 0; + offsrk1 = offsrk+n; + offsxk = offsrk1+n; + offsxk1 = offsxk+n; + offspk = offsxk1+n; + offspk1 = offspk+n; + offstmp1 = offspk1+n; + offstmp2 = offstmp1+m; + bs = offstmp2+n; + if( buf->cntptr.p_double[offsxk], 1, &x->ptr.p_double[0], 1, ae_v_len(offsxk,offsxk+n-1)); + + /* + * r(0) = b-A*x(0) + * RK2 = r(0)'*r(0) + */ + rmatrixmv(m, n, a, 0, 0, 0, buf, offsxk, buf, offstmp1, _state); + rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, buf, offstmp2, _state); + ae_v_addd(&buf->ptr.p_double[offstmp2], 1, &buf->ptr.p_double[offsxk], 1, ae_v_len(offstmp2,offstmp2+n-1), alpha); + ae_v_move(&buf->ptr.p_double[offsrk], 1, &b->ptr.p_double[0], 1, ae_v_len(offsrk,offsrk+n-1)); + ae_v_sub(&buf->ptr.p_double[offsrk], 1, &buf->ptr.p_double[offstmp2], 1, ae_v_len(offsrk,offsrk+n-1)); + rk2 = ae_v_dotproduct(&buf->ptr.p_double[offsrk], 1, &buf->ptr.p_double[offsrk], 1, ae_v_len(offsrk,offsrk+n-1)); + ae_v_move(&buf->ptr.p_double[offspk], 1, &buf->ptr.p_double[offsrk], 1, ae_v_len(offspk,offspk+n-1)); + e1 = ae_sqrt(rk2, _state); + + /* + * Cycle + */ + for(k=0; k<=n-1; k++) + { + + /* + * Calculate A*p(k) - store in Buf[OffsTmp2:OffsTmp2+N-1] + * and p(k)'*A*p(k) - store in PAP + * + * If PAP=0, break (iteration is over) + */ + rmatrixmv(m, n, a, 0, 0, 0, buf, offspk, buf, offstmp1, _state); + v1 = ae_v_dotproduct(&buf->ptr.p_double[offstmp1], 1, &buf->ptr.p_double[offstmp1], 1, ae_v_len(offstmp1,offstmp1+m-1)); + v2 = ae_v_dotproduct(&buf->ptr.p_double[offspk], 1, &buf->ptr.p_double[offspk], 1, ae_v_len(offspk,offspk+n-1)); + pap = v1+alpha*v2; + rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, buf, offstmp2, _state); + ae_v_addd(&buf->ptr.p_double[offstmp2], 1, &buf->ptr.p_double[offspk], 1, ae_v_len(offstmp2,offstmp2+n-1), alpha); + if( ae_fp_eq(pap,0) ) + { + break; + } + + /* + * S = (r(k)'*r(k))/(p(k)'*A*p(k)) + */ + s = rk2/pap; + + /* + * x(k+1) = x(k) + S*p(k) + */ + ae_v_move(&buf->ptr.p_double[offsxk1], 1, &buf->ptr.p_double[offsxk], 1, ae_v_len(offsxk1,offsxk1+n-1)); + ae_v_addd(&buf->ptr.p_double[offsxk1], 1, &buf->ptr.p_double[offspk], 1, ae_v_len(offsxk1,offsxk1+n-1), s); + + /* + * r(k+1) = r(k) - S*A*p(k) + * RK12 = r(k+1)'*r(k+1) + * + * Break if r(k+1) small enough (when compared to r(k)) + */ + ae_v_move(&buf->ptr.p_double[offsrk1], 1, &buf->ptr.p_double[offsrk], 1, ae_v_len(offsrk1,offsrk1+n-1)); + ae_v_subd(&buf->ptr.p_double[offsrk1], 1, &buf->ptr.p_double[offstmp2], 1, ae_v_len(offsrk1,offsrk1+n-1), s); + rk12 = ae_v_dotproduct(&buf->ptr.p_double[offsrk1], 1, &buf->ptr.p_double[offsrk1], 1, ae_v_len(offsrk1,offsrk1+n-1)); + if( ae_fp_less_eq(ae_sqrt(rk12, _state),100*ae_machineepsilon*ae_sqrt(rk2, _state)) ) + { + + /* + * X(k) = x(k+1) before exit - + * - because we expect to find solution at x(k) + */ + ae_v_move(&buf->ptr.p_double[offsxk], 1, &buf->ptr.p_double[offsxk1], 1, ae_v_len(offsxk,offsxk+n-1)); + break; + } + + /* + * BetaK = RK12/RK2 + * p(k+1) = r(k+1)+betak*p(k) + */ + betak = rk12/rk2; + ae_v_move(&buf->ptr.p_double[offspk1], 1, &buf->ptr.p_double[offsrk1], 1, ae_v_len(offspk1,offspk1+n-1)); + ae_v_addd(&buf->ptr.p_double[offspk1], 1, &buf->ptr.p_double[offspk], 1, ae_v_len(offspk1,offspk1+n-1), betak); + + /* + * r(k) := r(k+1) + * x(k) := x(k+1) + * p(k) := p(k+1) + */ + ae_v_move(&buf->ptr.p_double[offsrk], 1, &buf->ptr.p_double[offsrk1], 1, ae_v_len(offsrk,offsrk+n-1)); + ae_v_move(&buf->ptr.p_double[offsxk], 1, &buf->ptr.p_double[offsxk1], 1, ae_v_len(offsxk,offsxk+n-1)); + ae_v_move(&buf->ptr.p_double[offspk], 1, &buf->ptr.p_double[offspk1], 1, ae_v_len(offspk,offspk+n-1)); + rk2 = rk12; + } + + /* + * Calculate E2 + */ + rmatrixmv(m, n, a, 0, 0, 0, buf, offsxk, buf, offstmp1, _state); + rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, buf, offstmp2, _state); + ae_v_addd(&buf->ptr.p_double[offstmp2], 1, &buf->ptr.p_double[offsxk], 1, ae_v_len(offstmp2,offstmp2+n-1), alpha); + ae_v_move(&buf->ptr.p_double[offsrk], 1, &b->ptr.p_double[0], 1, ae_v_len(offsrk,offsrk+n-1)); + ae_v_sub(&buf->ptr.p_double[offsrk], 1, &buf->ptr.p_double[offstmp2], 1, ae_v_len(offsrk,offsrk+n-1)); + v1 = ae_v_dotproduct(&buf->ptr.p_double[offsrk], 1, &buf->ptr.p_double[offsrk], 1, ae_v_len(offsrk,offsrk+n-1)); + e2 = ae_sqrt(v1, _state); + + /* + * Output result (if it was improved) + */ + if( ae_fp_less(e2,e1) ) + { + ae_v_move(&x->ptr.p_double[0], 1, &buf->ptr.p_double[offsxk], 1, ae_v_len(0,n-1)); + } +} + + +/************************************************************************* +Construction of linear conjugate gradient solver. + +State parameter passed using "var" semantics (i.e. previous state is NOT +erased). When it is already initialized, we can reause prevously allocated +memory. + +INPUT PARAMETERS: + X - initial solution + B - right part + N - system size + State - structure; may be preallocated, if we want to reuse memory + +OUTPUT PARAMETERS: + State - structure which is used by FBLSCGIteration() to store + algorithm state between subsequent calls. + +NOTE: no error checking is done; caller must check all parameters, prevent + overflows, and so on. + + -- ALGLIB -- + Copyright 22.10.2009 by Bochkanov Sergey +*************************************************************************/ +void fblscgcreate(/* Real */ ae_vector* x, + /* Real */ ae_vector* b, + ae_int_t n, + fblslincgstate* state, + ae_state *_state) +{ + + + if( state->b.cntb, n, _state); + } + if( state->rk.cntrk, n, _state); + } + if( state->rk1.cntrk1, n, _state); + } + if( state->xk.cntxk, n, _state); + } + if( state->xk1.cntxk1, n, _state); + } + if( state->pk.cntpk, n, _state); + } + if( state->pk1.cntpk1, n, _state); + } + if( state->tmp2.cnttmp2, n, _state); + } + if( state->x.cntx, n, _state); + } + if( state->ax.cntax, n, _state); + } + state->n = n; + ae_v_move(&state->xk.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->b.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_vector_set_length(&state->rstate.ia, 1+1, _state); + ae_vector_set_length(&state->rstate.ra, 6+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +Linear CG solver, function relying on reverse communication to calculate +matrix-vector products. + +See comments for FBLSLinCGState structure for more info. + + -- ALGLIB -- + Copyright 22.10.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool fblscgiteration(fblslincgstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t k; + double rk2; + double rk12; + double pap; + double s; + double betak; + double v1; + double v2; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + k = state->rstate.ia.ptr.p_int[1]; + rk2 = state->rstate.ra.ptr.p_double[0]; + rk12 = state->rstate.ra.ptr.p_double[1]; + pap = state->rstate.ra.ptr.p_double[2]; + s = state->rstate.ra.ptr.p_double[3]; + betak = state->rstate.ra.ptr.p_double[4]; + v1 = state->rstate.ra.ptr.p_double[5]; + v2 = state->rstate.ra.ptr.p_double[6]; + } + else + { + n = -983; + k = -989; + rk2 = -834; + rk12 = 900; + pap = -287; + s = 364; + betak = 214; + v1 = -338; + v2 = -686; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + + /* + * Routine body + */ + + /* + * prepare locals + */ + n = state->n; + + /* + * Test for special case: B=0 + */ + v1 = ae_v_dotproduct(&state->b.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_eq(v1,0) ) + { + for(k=0; k<=n-1; k++) + { + state->xk.ptr.p_double[k] = 0; + } + result = ae_false; + return result; + } + + /* + * r(0) = b-A*x(0) + * RK2 = r(0)'*r(0) + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + ae_v_move(&state->rk.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_sub(&state->rk.ptr.p_double[0], 1, &state->ax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + rk2 = ae_v_dotproduct(&state->rk.ptr.p_double[0], 1, &state->rk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->pk.ptr.p_double[0], 1, &state->rk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->e1 = ae_sqrt(rk2, _state); + + /* + * Cycle + */ + k = 0; +lbl_3: + if( k>n-1 ) + { + goto lbl_5; + } + + /* + * Calculate A*p(k) - store in State.Tmp2 + * and p(k)'*A*p(k) - store in PAP + * + * If PAP=0, break (iteration is over) + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->pk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + ae_v_move(&state->tmp2.ptr.p_double[0], 1, &state->ax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + pap = state->xax; + if( !ae_isfinite(pap, _state) ) + { + goto lbl_5; + } + if( ae_fp_less_eq(pap,0) ) + { + goto lbl_5; + } + + /* + * S = (r(k)'*r(k))/(p(k)'*A*p(k)) + */ + s = rk2/pap; + + /* + * x(k+1) = x(k) + S*p(k) + */ + ae_v_move(&state->xk1.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->xk1.ptr.p_double[0], 1, &state->pk.ptr.p_double[0], 1, ae_v_len(0,n-1), s); + + /* + * r(k+1) = r(k) - S*A*p(k) + * RK12 = r(k+1)'*r(k+1) + * + * Break if r(k+1) small enough (when compared to r(k)) + */ + ae_v_move(&state->rk1.ptr.p_double[0], 1, &state->rk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_subd(&state->rk1.ptr.p_double[0], 1, &state->tmp2.ptr.p_double[0], 1, ae_v_len(0,n-1), s); + rk12 = ae_v_dotproduct(&state->rk1.ptr.p_double[0], 1, &state->rk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_less_eq(ae_sqrt(rk12, _state),100*ae_machineepsilon*state->e1) ) + { + + /* + * X(k) = x(k+1) before exit - + * - because we expect to find solution at x(k) + */ + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->xk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + goto lbl_5; + } + + /* + * BetaK = RK12/RK2 + * p(k+1) = r(k+1)+betak*p(k) + * + * NOTE: we expect that BetaK won't overflow because of + * "Sqrt(RK12)<=100*MachineEpsilon*E1" test above. + */ + betak = rk12/rk2; + ae_v_move(&state->pk1.ptr.p_double[0], 1, &state->rk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->pk1.ptr.p_double[0], 1, &state->pk.ptr.p_double[0], 1, ae_v_len(0,n-1), betak); + + /* + * r(k) := r(k+1) + * x(k) := x(k+1) + * p(k) := p(k+1) + */ + ae_v_move(&state->rk.ptr.p_double[0], 1, &state->rk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->xk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->pk.ptr.p_double[0], 1, &state->pk1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + rk2 = rk12; + k = k+1; + goto lbl_3; +lbl_5: + + /* + * Calculate E2 + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + ae_v_move(&state->rk.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_sub(&state->rk.ptr.p_double[0], 1, &state->ax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v1 = ae_v_dotproduct(&state->rk.ptr.p_double[0], 1, &state->rk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->e2 = ae_sqrt(v1, _state); + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = k; + state->rstate.ra.ptr.p_double[0] = rk2; + state->rstate.ra.ptr.p_double[1] = rk12; + state->rstate.ra.ptr.p_double[2] = pap; + state->rstate.ra.ptr.p_double[3] = s; + state->rstate.ra.ptr.p_double[4] = betak; + state->rstate.ra.ptr.p_double[5] = v1; + state->rstate.ra.ptr.p_double[6] = v2; + return result; +} + + +/************************************************************************* +Fast least squares solver, solves well conditioned system without +performing any checks for degeneracy, and using user-provided buffers +(which are automatically reallocated if too small). + +This function is intended for solution of moderately sized systems. It +uses factorization algorithms based on Level 2 BLAS operations, thus it +won't work efficiently on large scale systems. + +INPUT PARAMETERS: + A - array[M,N], system matrix. + Contents of A is destroyed during solution. + B - array[M], right part + M - number of equations + N - number of variables, N<=M + Tmp0, Tmp1, Tmp2- + buffers; function automatically allocates them, if they are + too small. They can be reused if function is called + several times. + +OUTPUT PARAMETERS: + B - solution (first N components, next M-N are zero) + + -- ALGLIB -- + Copyright 20.01.2012 by Bochkanov Sergey +*************************************************************************/ +void fblssolvels(/* Real */ ae_matrix* a, + /* Real */ ae_vector* b, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tmp0, + /* Real */ ae_vector* tmp1, + /* Real */ ae_vector* tmp2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + double v; + + + ae_assert(n>0, "FBLSSolveLS: N<=0", _state); + ae_assert(m>=n, "FBLSSolveLS: Mrows>=m, "FBLSSolveLS: Rows(A)cols>=n, "FBLSSolveLS: Cols(A)cnt>=m, "FBLSSolveLS: Length(B)ptr.p_double[i] = 0; + } + ae_v_move(&tmp0->ptr.p_double[k], 1, &a->ptr.pp_double[k][k], a->stride, ae_v_len(k,m-1)); + tmp0->ptr.p_double[k] = 1; + v = ae_v_dotproduct(&tmp0->ptr.p_double[k], 1, &b->ptr.p_double[k], 1, ae_v_len(k,m-1)); + v = v*tmp2->ptr.p_double[k]; + ae_v_subd(&b->ptr.p_double[k], 1, &tmp0->ptr.p_double[k], 1, ae_v_len(k,m-1), v); + } + + /* + * Solve triangular system + */ + b->ptr.p_double[n-1] = b->ptr.p_double[n-1]/a->ptr.pp_double[n-1][n-1]; + for(i=n-2; i>=0; i--) + { + v = ae_v_dotproduct(&a->ptr.pp_double[i][i+1], 1, &b->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1)); + b->ptr.p_double[i] = (b->ptr.p_double[i]-v)/a->ptr.pp_double[i][i]; + } + for(i=n; i<=m-1; i++) + { + b->ptr.p_double[i] = 0.0; + } +} + + +ae_bool _fblslincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + fblslincgstate *p = (fblslincgstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ax, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rk1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xk1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->pk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->pk1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp2, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _fblslincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + fblslincgstate *dst = (fblslincgstate*)_dst; + fblslincgstate *src = (fblslincgstate*)_src; + dst->e1 = src->e1; + dst->e2 = src->e2; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ax, &src->ax, _state, make_automatic) ) + return ae_false; + dst->xax = src->xax; + dst->n = src->n; + if( !ae_vector_init_copy(&dst->rk, &src->rk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rk1, &src->rk1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xk, &src->xk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xk1, &src->xk1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->pk, &src->pk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->pk1, &src->pk1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp2, &src->tmp2, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _fblslincgstate_clear(void* _p) +{ + fblslincgstate *p = (fblslincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->ax); + ae_vector_clear(&p->rk); + ae_vector_clear(&p->rk1); + ae_vector_clear(&p->xk); + ae_vector_clear(&p->xk1); + ae_vector_clear(&p->pk); + ae_vector_clear(&p->pk1); + ae_vector_clear(&p->b); + _rcommstate_clear(&p->rstate); + ae_vector_clear(&p->tmp2); +} + + +void _fblslincgstate_destroy(void* _p) +{ + fblslincgstate *p = (fblslincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->ax); + ae_vector_destroy(&p->rk); + ae_vector_destroy(&p->rk1); + ae_vector_destroy(&p->xk); + ae_vector_destroy(&p->xk1); + ae_vector_destroy(&p->pk); + ae_vector_destroy(&p->pk1); + ae_vector_destroy(&p->b); + _rcommstate_destroy(&p->rstate); + ae_vector_destroy(&p->tmp2); +} + + + + +/************************************************************************* +This procedure initializes matrix norm estimator. + +USAGE: +1. User initializes algorithm state with NormEstimatorCreate() call +2. User calls NormEstimatorEstimateSparse() (or NormEstimatorIteration()) +3. User calls NormEstimatorResults() to get solution. + +INPUT PARAMETERS: + M - number of rows in the matrix being estimated, M>0 + N - number of columns in the matrix being estimated, N>0 + NStart - number of random starting vectors + recommended value - at least 5. + NIts - number of iterations to do with best starting vector + recommended value - at least 5. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTE: this algorithm is effectively deterministic, i.e. it always returns +same result when repeatedly called for the same matrix. In fact, algorithm +uses randomized starting vectors, but internal random numbers generator +always generates same sequence of the random values (it is a feature, not +bug). + +Algorithm can be made non-deterministic with NormEstimatorSetSeed(0) call. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorcreate(ae_int_t m, + ae_int_t n, + ae_int_t nstart, + ae_int_t nits, + normestimatorstate* state, + ae_state *_state) +{ + + _normestimatorstate_clear(state); + + ae_assert(m>0, "NormEstimatorCreate: M<=0", _state); + ae_assert(n>0, "NormEstimatorCreate: N<=0", _state); + ae_assert(nstart>0, "NormEstimatorCreate: NStart<=0", _state); + ae_assert(nits>0, "NormEstimatorCreate: NIts<=0", _state); + state->m = m; + state->n = n; + state->nstart = nstart; + state->nits = nits; + state->seedval = 11; + hqrndrandomize(&state->r, _state); + ae_vector_set_length(&state->x0, state->n, _state); + ae_vector_set_length(&state->t, state->m, _state); + ae_vector_set_length(&state->x1, state->n, _state); + ae_vector_set_length(&state->xbest, state->n, _state); + ae_vector_set_length(&state->x, ae_maxint(state->n, state->m, _state), _state); + ae_vector_set_length(&state->mv, state->m, _state); + ae_vector_set_length(&state->mtv, state->n, _state); + ae_vector_set_length(&state->rstate.ia, 3+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +This function changes seed value used by algorithm. In some cases we need +deterministic processing, i.e. subsequent calls must return equal results, +in other cases we need non-deterministic algorithm which returns different +results for the same matrix on every pass. + +Setting zero seed will lead to non-deterministic algorithm, while non-zero +value will make our algorithm deterministic. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + SeedVal - seed value, >=0. Zero value = non-deterministic algo. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorsetseed(normestimatorstate* state, + ae_int_t seedval, + ae_state *_state) +{ + + + ae_assert(seedval>=0, "NormEstimatorSetSeed: SeedVal<0", _state); + state->seedval = seedval; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +ae_bool normestimatoriteration(normestimatorstate* state, + ae_state *_state) +{ + ae_int_t n; + ae_int_t m; + ae_int_t i; + ae_int_t itcnt; + double v; + double growth; + double bestgrowth; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + i = state->rstate.ia.ptr.p_int[2]; + itcnt = state->rstate.ia.ptr.p_int[3]; + v = state->rstate.ra.ptr.p_double[0]; + growth = state->rstate.ra.ptr.p_double[1]; + bestgrowth = state->rstate.ra.ptr.p_double[2]; + } + else + { + n = -983; + m = -989; + i = -834; + itcnt = 900; + v = -287; + growth = 364; + bestgrowth = 214; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + + /* + * Routine body + */ + n = state->n; + m = state->m; + if( state->seedval>0 ) + { + hqrndseed(state->seedval, state->seedval+2, &state->r, _state); + } + bestgrowth = 0; + state->xbest.ptr.p_double[0] = 1; + for(i=1; i<=n-1; i++) + { + state->xbest.ptr.p_double[i] = 0; + } + itcnt = 0; +lbl_4: + if( itcnt>state->nstart-1 ) + { + goto lbl_6; + } + do + { + v = 0; + for(i=0; i<=n-1; i++) + { + state->x0.ptr.p_double[i] = hqrndnormal(&state->r, _state); + v = v+ae_sqr(state->x0.ptr.p_double[i], _state); + } + } + while(ae_fp_eq(v,0)); + v = 1/ae_sqrt(v, _state); + ae_v_muld(&state->x0.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->x0.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->needmv = ae_true; + state->needmtv = ae_false; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + ae_v_move(&state->x.ptr.p_double[0], 1, &state->mv.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->needmv = ae_false; + state->needmtv = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + ae_v_move(&state->x1.ptr.p_double[0], 1, &state->mtv.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->x1.ptr.p_double[i], _state); + } + growth = ae_sqrt(ae_sqrt(v, _state), _state); + if( ae_fp_greater(growth,bestgrowth) ) + { + v = 1/ae_sqrt(v, _state); + ae_v_moved(&state->xbest.ptr.p_double[0], 1, &state->x1.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + bestgrowth = growth; + } + itcnt = itcnt+1; + goto lbl_4; +lbl_6: + ae_v_move(&state->x0.ptr.p_double[0], 1, &state->xbest.ptr.p_double[0], 1, ae_v_len(0,n-1)); + itcnt = 0; +lbl_7: + if( itcnt>state->nits-1 ) + { + goto lbl_9; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->x0.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->needmv = ae_true; + state->needmtv = ae_false; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + ae_v_move(&state->x.ptr.p_double[0], 1, &state->mv.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->needmv = ae_false; + state->needmtv = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + ae_v_move(&state->x1.ptr.p_double[0], 1, &state->mtv.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->x1.ptr.p_double[i], _state); + } + state->repnorm = ae_sqrt(ae_sqrt(v, _state), _state); + if( ae_fp_neq(v,0) ) + { + v = 1/ae_sqrt(v, _state); + ae_v_moved(&state->x0.ptr.p_double[0], 1, &state->x1.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + itcnt = itcnt+1; + goto lbl_7; +lbl_9: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = i; + state->rstate.ia.ptr.p_int[3] = itcnt; + state->rstate.ra.ptr.p_double[0] = v; + state->rstate.ra.ptr.p_double[1] = growth; + state->rstate.ra.ptr.p_double[2] = bestgrowth; + return result; +} + + +/************************************************************************* +This function estimates norm of the sparse M*N matrix A. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + A - sparse M*N matrix, must be converted to CRS format + prior to calling this function. + +After this function is over you can call NormEstimatorResults() to get +estimate of the norm(A). + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorestimatesparse(normestimatorstate* state, + sparsematrix* a, + ae_state *_state) +{ + + + normestimatorrestart(state, _state); + while(normestimatoriteration(state, _state)) + { + if( state->needmv ) + { + sparsemv(a, &state->x, &state->mv, _state); + continue; + } + if( state->needmtv ) + { + sparsemtv(a, &state->x, &state->mtv, _state); + continue; + } + } +} + + +/************************************************************************* +Matrix norm estimation results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Nrm - estimate of the matrix norm, Nrm>=0 + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorresults(normestimatorstate* state, + double* nrm, + ae_state *_state) +{ + + *nrm = 0; + + *nrm = state->repnorm; +} + + +/************************************************************************* +This function restarts estimator and prepares it for the next estimation +round. + +INPUT PARAMETERS: + State - algorithm state + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorrestart(normestimatorstate* state, ae_state *_state) +{ + + + ae_vector_set_length(&state->rstate.ia, 3+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; +} + + +ae_bool _normestimatorstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + normestimatorstate *p = (normestimatorstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->t, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xbest, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_hqrndstate_init(&p->r, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mtv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _normestimatorstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + normestimatorstate *dst = (normestimatorstate*)_dst; + normestimatorstate *src = (normestimatorstate*)_src; + dst->n = src->n; + dst->m = src->m; + dst->nstart = src->nstart; + dst->nits = src->nits; + dst->seedval = src->seedval; + if( !ae_vector_init_copy(&dst->x0, &src->x0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x1, &src->x1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->t, &src->t, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xbest, &src->xbest, _state, make_automatic) ) + return ae_false; + if( !_hqrndstate_init_copy(&dst->r, &src->r, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mv, &src->mv, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mtv, &src->mtv, _state, make_automatic) ) + return ae_false; + dst->needmv = src->needmv; + dst->needmtv = src->needmtv; + dst->repnorm = src->repnorm; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _normestimatorstate_clear(void* _p) +{ + normestimatorstate *p = (normestimatorstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x0); + ae_vector_clear(&p->x1); + ae_vector_clear(&p->t); + ae_vector_clear(&p->xbest); + _hqrndstate_clear(&p->r); + ae_vector_clear(&p->x); + ae_vector_clear(&p->mv); + ae_vector_clear(&p->mtv); + _rcommstate_clear(&p->rstate); +} + + +void _normestimatorstate_destroy(void* _p) +{ + normestimatorstate *p = (normestimatorstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x0); + ae_vector_destroy(&p->x1); + ae_vector_destroy(&p->t); + ae_vector_destroy(&p->xbest); + _hqrndstate_destroy(&p->r); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->mv); + ae_vector_destroy(&p->mtv); + _rcommstate_destroy(&p->rstate); +} + + + + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixludet(/* Real */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t s; + double result; + + + ae_assert(n>=1, "RMatrixLUDet: N<1!", _state); + ae_assert(pivots->cnt>=n, "RMatrixLUDet: Pivots array is too short!", _state); + ae_assert(a->rows>=n, "RMatrixLUDet: rows(A)cols>=n, "RMatrixLUDet: cols(A)ptr.pp_double[i][i]; + if( pivots->ptr.p_int[i]!=i ) + { + s = -s; + } + } + result = result*s; + return result; +} + + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixdet(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector pivots; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "RMatrixDet: N<1!", _state); + ae_assert(a->rows>=n, "RMatrixDet: rows(A)cols>=n, "RMatrixDet: cols(A)=1, "CMatrixLUDet: N<1!", _state); + ae_assert(pivots->cnt>=n, "CMatrixLUDet: Pivots array is too short!", _state); + ae_assert(a->rows>=n, "CMatrixLUDet: rows(A)cols>=n, "CMatrixLUDet: cols(A)ptr.pp_complex[i][i]); + if( pivots->ptr.p_int[i]!=i ) + { + s = -s; + } + } + result = ae_c_mul_d(result,s); + return result; +} + + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +ae_complex cmatrixdet(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_vector pivots; + ae_complex result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + ae_vector_init(&pivots, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "CMatrixDet: N<1!", _state); + ae_assert(a->rows>=n, "CMatrixDet: rows(A)cols>=n, "CMatrixDet: cols(A)=1, "SPDMatrixCholeskyDet: N<1!", _state); + ae_assert(a->rows>=n, "SPDMatrixCholeskyDet: rows(A)cols>=n, "SPDMatrixCholeskyDet: cols(A)ptr.pp_double[i][i], _state); + } + ae_assert(f, "SPDMatrixCholeskyDet: A contains infinite or NaN values!", _state); + result = 1; + for(i=0; i<=n-1; i++) + { + result = result*ae_sqr(a->ptr.pp_double[i][i], _state); + } + return result; +} + + +/************************************************************************* +Determinant calculation of the symmetric positive definite matrix. + +Input parameters: + A - matrix. Array with elements [0..N-1, 0..N-1]. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Result: + determinant of matrix A. + If matrix A is not positive definite, exception is thrown. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixdet(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _a; + ae_bool b; + double result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_a, a, _state, ae_true); + a = &_a; + + ae_assert(n>=1, "SPDMatrixDet: N<1!", _state); + ae_assert(a->rows>=n, "SPDMatrixDet: rows(A)cols>=n, "SPDMatrixDet: cols(A)ptr.pp_double[0][j] = 0.0; + } + for(i=1; i<=n-1; i++) + { + ae_v_move(&z->ptr.pp_double[i][0], 1, &z->ptr.pp_double[0][0], 1, ae_v_len(0,n-1)); + } + + /* + * Setup R properties + */ + if( isupperr ) + { + j1 = 0; + j2 = n-1; + j1inc = 1; + j2inc = 0; + } + else + { + j1 = 0; + j2 = 0; + j1inc = 0; + j2inc = 1; + } + + /* + * Calculate R*Z + */ + for(i=0; i<=n-1; i++) + { + for(j=j1; j<=j2; j++) + { + v = r.ptr.pp_double[i][j]; + ae_v_addd(&z->ptr.pp_double[i][0], 1, &t.ptr.pp_double[j][0], 1, ae_v_len(0,n-1), v); + } + j1 = j1+j1inc; + j2 = j2+j2inc; + } + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* +Algorithm for reduction of the following generalized symmetric positive- +definite eigenvalue problem: + A*x = lambda*B*x (1) or + A*B*x = lambda*x (2) or + B*A*x = lambda*x (3) +to the symmetric eigenvalues problem C*y = lambda*y (eigenvalues of this and +the given problems are the same, and the eigenvectors of the given problem +could be obtained by multiplying the obtained eigenvectors by the +transformation matrix x = R*y). + +Here A is a symmetric matrix, B - symmetric positive-definite matrix. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrices A and B. + IsUpperA - storage format of matrix A. + B - symmetric positive-definite matrix which is given by + its upper or lower triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperB - storage format of matrix B. + ProblemType - if ProblemType is equal to: + * 1, the following problem is solved: A*x = lambda*B*x; + * 2, the following problem is solved: A*B*x = lambda*x; + * 3, the following problem is solved: B*A*x = lambda*x. + +Output parameters: + A - symmetric matrix which is given by its upper or lower + triangle depending on IsUpperA. Contains matrix C. + Array whose indexes range within [0..N-1, 0..N-1]. + R - upper triangular or low triangular transformation matrix + which is used to obtain the eigenvectors of a given problem + as the product of eigenvectors of C (from the right) and + matrix R (from the left). If the matrix is upper + triangular, the elements below the main diagonal + are equal to 0 (and vice versa). Thus, we can perform + the multiplication without taking into account the + internal structure (which is an easier though less + effective way). + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperR - type of matrix R (upper or lower triangular). + +Result: + True, if the problem was reduced successfully. + False, if the error occurred during the Cholesky decomposition of + matrix B (the matrix is not positive-definite). + + -- ALGLIB -- + Copyright 1.28.2006 by Bochkanov Sergey +*************************************************************************/ +ae_bool smatrixgevdreduce(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isuppera, + /* Real */ ae_matrix* b, + ae_bool isupperb, + ae_int_t problemtype, + /* Real */ ae_matrix* r, + ae_bool* isupperr, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix t; + ae_vector w1; + ae_vector w2; + ae_vector w3; + ae_int_t i; + ae_int_t j; + double v; + matinvreport rep; + ae_int_t info; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(r); + *isupperr = ae_false; + ae_matrix_init(&t, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w2, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w3, 0, DT_REAL, _state, ae_true); + _matinvreport_init(&rep, _state, ae_true); + + ae_assert(n>0, "SMatrixGEVDReduce: N<=0!", _state); + ae_assert((problemtype==1||problemtype==2)||problemtype==3, "SMatrixGEVDReduce: incorrect ProblemType!", _state); + result = ae_true; + + /* + * Problem 1: A*x = lambda*B*x + * + * Reducing to: + * C*y = lambda*y + * C = L^(-1) * A * L^(-T) + * x = L^(-T) * y + */ + if( problemtype==1 ) + { + + /* + * Factorize B in T: B = LL' + */ + ae_matrix_set_length(&t, n-1+1, n-1+1, _state); + if( isupperb ) + { + for(i=0; i<=n-1; i++) + { + ae_v_move(&t.ptr.pp_double[i][i], t.stride, &b->ptr.pp_double[i][i], 1, ae_v_len(i,n-1)); + } + } + else + { + for(i=0; i<=n-1; i++) + { + ae_v_move(&t.ptr.pp_double[i][0], 1, &b->ptr.pp_double[i][0], 1, ae_v_len(0,i)); + } + } + if( !spdmatrixcholesky(&t, n, ae_false, _state) ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Invert L in T + */ + rmatrixtrinverse(&t, n, ae_false, ae_false, &info, &rep, _state); + if( info<=0 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Build L^(-1) * A * L^(-T) in R + */ + ae_vector_set_length(&w1, n+1, _state); + ae_vector_set_length(&w2, n+1, _state); + ae_matrix_set_length(r, n-1+1, n-1+1, _state); + for(j=1; j<=n; j++) + { + + /* + * Form w2 = A * l'(j) (here l'(j) is j-th column of L^(-T)) + */ + ae_v_move(&w1.ptr.p_double[1], 1, &t.ptr.pp_double[j-1][0], 1, ae_v_len(1,j)); + symmetricmatrixvectormultiply(a, isuppera, 0, j-1, &w1, 1.0, &w2, _state); + if( isuppera ) + { + matrixvectormultiply(a, 0, j-1, j, n-1, ae_true, &w1, 1, j, 1.0, &w2, j+1, n, 0.0, _state); + } + else + { + matrixvectormultiply(a, j, n-1, 0, j-1, ae_false, &w1, 1, j, 1.0, &w2, j+1, n, 0.0, _state); + } + + /* + * Form l(i)*w2 (here l(i) is i-th row of L^(-1)) + */ + for(i=1; i<=n; i++) + { + v = ae_v_dotproduct(&t.ptr.pp_double[i-1][0], 1, &w2.ptr.p_double[1], 1, ae_v_len(0,i-1)); + r->ptr.pp_double[i-1][j-1] = v; + } + } + + /* + * Copy R to A + */ + for(i=0; i<=n-1; i++) + { + ae_v_move(&a->ptr.pp_double[i][0], 1, &r->ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + } + + /* + * Copy L^(-1) from T to R and transpose + */ + *isupperr = ae_true; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + r->ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=n-1; i++) + { + ae_v_move(&r->ptr.pp_double[i][i], 1, &t.ptr.pp_double[i][i], t.stride, ae_v_len(i,n-1)); + } + ae_frame_leave(_state); + return result; + } + + /* + * Problem 2: A*B*x = lambda*x + * or + * problem 3: B*A*x = lambda*x + * + * Reducing to: + * C*y = lambda*y + * C = U * A * U' + * B = U'* U + */ + if( problemtype==2||problemtype==3 ) + { + + /* + * Factorize B in T: B = U'*U + */ + ae_matrix_set_length(&t, n-1+1, n-1+1, _state); + if( isupperb ) + { + for(i=0; i<=n-1; i++) + { + ae_v_move(&t.ptr.pp_double[i][i], 1, &b->ptr.pp_double[i][i], 1, ae_v_len(i,n-1)); + } + } + else + { + for(i=0; i<=n-1; i++) + { + ae_v_move(&t.ptr.pp_double[i][i], 1, &b->ptr.pp_double[i][i], b->stride, ae_v_len(i,n-1)); + } + } + if( !spdmatrixcholesky(&t, n, ae_true, _state) ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Build U * A * U' in R + */ + ae_vector_set_length(&w1, n+1, _state); + ae_vector_set_length(&w2, n+1, _state); + ae_vector_set_length(&w3, n+1, _state); + ae_matrix_set_length(r, n-1+1, n-1+1, _state); + for(j=1; j<=n; j++) + { + + /* + * Form w2 = A * u'(j) (here u'(j) is j-th column of U') + */ + ae_v_move(&w1.ptr.p_double[1], 1, &t.ptr.pp_double[j-1][j-1], 1, ae_v_len(1,n-j+1)); + symmetricmatrixvectormultiply(a, isuppera, j-1, n-1, &w1, 1.0, &w3, _state); + ae_v_move(&w2.ptr.p_double[j], 1, &w3.ptr.p_double[1], 1, ae_v_len(j,n)); + ae_v_move(&w1.ptr.p_double[j], 1, &t.ptr.pp_double[j-1][j-1], 1, ae_v_len(j,n)); + if( isuppera ) + { + matrixvectormultiply(a, 0, j-2, j-1, n-1, ae_false, &w1, j, n, 1.0, &w2, 1, j-1, 0.0, _state); + } + else + { + matrixvectormultiply(a, j-1, n-1, 0, j-2, ae_true, &w1, j, n, 1.0, &w2, 1, j-1, 0.0, _state); + } + + /* + * Form u(i)*w2 (here u(i) is i-th row of U) + */ + for(i=1; i<=n; i++) + { + v = ae_v_dotproduct(&t.ptr.pp_double[i-1][i-1], 1, &w2.ptr.p_double[i], 1, ae_v_len(i-1,n-1)); + r->ptr.pp_double[i-1][j-1] = v; + } + } + + /* + * Copy R to A + */ + for(i=0; i<=n-1; i++) + { + ae_v_move(&a->ptr.pp_double[i][0], 1, &r->ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + } + if( problemtype==2 ) + { + + /* + * Invert U in T + */ + rmatrixtrinverse(&t, n, ae_true, ae_false, &info, &rep, _state); + if( info<=0 ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + + /* + * Copy U^-1 from T to R + */ + *isupperr = ae_true; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=i-1; j++) + { + r->ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=n-1; i++) + { + ae_v_move(&r->ptr.pp_double[i][i], 1, &t.ptr.pp_double[i][i], 1, ae_v_len(i,n-1)); + } + } + else + { + + /* + * Copy U from T to R and transpose + */ + *isupperr = ae_false; + for(i=0; i<=n-1; i++) + { + for(j=i+1; j<=n-1; j++) + { + r->ptr.pp_double[i][j] = 0; + } + } + for(i=0; i<=n-1; i++) + { + ae_v_move(&r->ptr.pp_double[i][i], r->stride, &t.ptr.pp_double[i][i], 1, ae_v_len(i,n-1)); + } + } + } + ae_frame_leave(_state); + return result; +} + + + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a number to an element +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - row where the element to be updated is stored. + UpdColumn - column where the element to be updated is stored. + UpdVal - a number to be added to the element. + + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatesimple(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updrow, + ae_int_t updcolumn, + double updval, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector t1; + ae_vector t2; + ae_int_t i; + double lambdav; + double vt; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&t1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t2, 0, DT_REAL, _state, ae_true); + + ae_assert(updrow>=0&&updrow=0&&updcolumnptr.pp_double[0][updrow], inva->stride, ae_v_len(0,n-1)); + + /* + * T2 = v*InvA + */ + ae_v_move(&t2.ptr.p_double[0], 1, &inva->ptr.pp_double[updcolumn][0], 1, ae_v_len(0,n-1)); + + /* + * Lambda = v * InvA * U + */ + lambdav = updval*inva->ptr.pp_double[updcolumn][updrow]; + + /* + * InvA = InvA - correction + */ + for(i=0; i<=n-1; i++) + { + vt = updval*t1.ptr.p_double[i]; + vt = vt/(1+lambdav); + ae_v_subd(&inva->ptr.pp_double[i][0], 1, &t2.ptr.p_double[0], 1, ae_v_len(0,n-1), vt); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a row +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - the row of A whose vector V was added. + 0 <= Row <= N-1 + V - the vector to be added to a row. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdaterow(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updrow, + /* Real */ ae_vector* v, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector t1; + ae_vector t2; + ae_int_t i; + ae_int_t j; + double lambdav; + double vt; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&t1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t2, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&t1, n-1+1, _state); + ae_vector_set_length(&t2, n-1+1, _state); + + /* + * T1 = InvA * U + */ + ae_v_move(&t1.ptr.p_double[0], 1, &inva->ptr.pp_double[0][updrow], inva->stride, ae_v_len(0,n-1)); + + /* + * T2 = v*InvA + * Lambda = v * InvA * U + */ + for(j=0; j<=n-1; j++) + { + vt = ae_v_dotproduct(&v->ptr.p_double[0], 1, &inva->ptr.pp_double[0][j], inva->stride, ae_v_len(0,n-1)); + t2.ptr.p_double[j] = vt; + } + lambdav = t2.ptr.p_double[updrow]; + + /* + * InvA = InvA - correction + */ + for(i=0; i<=n-1; i++) + { + vt = t1.ptr.p_double[i]/(1+lambdav); + ae_v_subd(&inva->ptr.pp_double[i][0], 1, &t2.ptr.p_double[0], 1, ae_v_len(0,n-1), vt); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a column +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdColumn - the column of A whose vector U was added. + 0 <= UpdColumn <= N-1 + U - the vector to be added to a column. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatecolumn(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updcolumn, + /* Real */ ae_vector* u, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector t1; + ae_vector t2; + ae_int_t i; + double lambdav; + double vt; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&t1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t2, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&t1, n-1+1, _state); + ae_vector_set_length(&t2, n-1+1, _state); + + /* + * T1 = InvA * U + * Lambda = v * InvA * U + */ + for(i=0; i<=n-1; i++) + { + vt = ae_v_dotproduct(&inva->ptr.pp_double[i][0], 1, &u->ptr.p_double[0], 1, ae_v_len(0,n-1)); + t1.ptr.p_double[i] = vt; + } + lambdav = t1.ptr.p_double[updcolumn]; + + /* + * T2 = v*InvA + */ + ae_v_move(&t2.ptr.p_double[0], 1, &inva->ptr.pp_double[updcolumn][0], 1, ae_v_len(0,n-1)); + + /* + * InvA = InvA - correction + */ + for(i=0; i<=n-1; i++) + { + vt = t1.ptr.p_double[i]/(1+lambdav); + ae_v_subd(&inva->ptr.pp_double[i][0], 1, &t2.ptr.p_double[0], 1, ae_v_len(0,n-1), vt); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm computes the inverse of matrix A+u*v’ by using the given matrix +A^-1 and the vectors u and v. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + U - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + V - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of matrix A + u*v'. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdateuv(/* Real */ ae_matrix* inva, + ae_int_t n, + /* Real */ ae_vector* u, + /* Real */ ae_vector* v, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector t1; + ae_vector t2; + ae_int_t i; + ae_int_t j; + double lambdav; + double vt; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&t1, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t2, 0, DT_REAL, _state, ae_true); + + ae_vector_set_length(&t1, n-1+1, _state); + ae_vector_set_length(&t2, n-1+1, _state); + + /* + * T1 = InvA * U + * Lambda = v * T1 + */ + for(i=0; i<=n-1; i++) + { + vt = ae_v_dotproduct(&inva->ptr.pp_double[i][0], 1, &u->ptr.p_double[0], 1, ae_v_len(0,n-1)); + t1.ptr.p_double[i] = vt; + } + lambdav = ae_v_dotproduct(&v->ptr.p_double[0], 1, &t1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * T2 = v*InvA + */ + for(j=0; j<=n-1; j++) + { + vt = ae_v_dotproduct(&v->ptr.p_double[0], 1, &inva->ptr.pp_double[0][j], inva->stride, ae_v_len(0,n-1)); + t2.ptr.p_double[j] = vt; + } + + /* + * InvA = InvA - correction + */ + for(i=0; i<=n-1; i++) + { + vt = t1.ptr.p_double[i]/(1+lambdav); + ae_v_subd(&inva->ptr.pp_double[i][0], 1, &t2.ptr.p_double[0], 1, ae_v_len(0,n-1), vt); + } + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Subroutine performing the Schur decomposition of a general matrix by using +the QR algorithm with multiple shifts. + +The source matrix A is represented as S'*A*S = T, where S is an orthogonal +matrix (Schur vectors), T - upper quasi-triangular matrix (with blocks of +sizes 1x1 and 2x2 on the main diagonal). + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of A, N>=0. + + +Output parameters: + A - contains matrix T. + Array whose indexes range within [0..N-1, 0..N-1]. + S - contains Schur vectors. + Array whose indexes range within [0..N-1, 0..N-1]. + +Note 1: + The block structure of matrix T can be easily recognized: since all + the elements below the blocks are zeros, the elements a[i+1,i] which + are equal to 0 show the block border. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms in linear algebra). If you require maximum performance on + your machine, it is recommended to adjust this parameter manually. + +Result: + True, + if the algorithm has converged and parameters A and S contain the result. + False, + if the algorithm has not converged. + +Algorithm implemented on the basis of the DHSEQR subroutine (LAPACK 3.0 library). +*************************************************************************/ +ae_bool rmatrixschur(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* s, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector tau; + ae_vector wi; + ae_vector wr; + ae_matrix a1; + ae_matrix s1; + ae_int_t info; + ae_int_t i; + ae_int_t j; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(s); + ae_vector_init(&tau, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wi, 0, DT_REAL, _state, ae_true); + ae_vector_init(&wr, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a1, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&s1, 0, 0, DT_REAL, _state, ae_true); + + + /* + * Upper Hessenberg form of the 0-based matrix + */ + rmatrixhessenberg(a, n, &tau, _state); + rmatrixhessenbergunpackq(a, n, &tau, s, _state); + + /* + * Convert from 0-based arrays to 1-based, + * then call InternalSchurDecomposition + * Awkward, of course, but Schur decompisiton subroutine + * is too complex to fix it. + * + */ + ae_matrix_set_length(&a1, n+1, n+1, _state); + ae_matrix_set_length(&s1, n+1, n+1, _state); + for(i=1; i<=n; i++) + { + for(j=1; j<=n; j++) + { + a1.ptr.pp_double[i][j] = a->ptr.pp_double[i-1][j-1]; + s1.ptr.pp_double[i][j] = s->ptr.pp_double[i-1][j-1]; + } + } + internalschurdecomposition(&a1, n, 1, 1, &wr, &wi, &s1, &info, _state); + result = info==0; + + /* + * convert from 1-based arrays to -based + */ + for(i=1; i<=n; i++) + { + for(j=1; j<=n; j++) + { + a->ptr.pp_double[i-1][j-1] = a1.ptr.pp_double[i][j]; + s->ptr.pp_double[i-1][j-1] = s1.ptr.pp_double[i][j]; + } + } + ae_frame_leave(_state); + return result; +} + + + +} + diff --git a/src/inc/alglib/linalg.h b/src/inc/alglib/linalg.h new file mode 100644 index 0000000..e6364c1 --- /dev/null +++ b/src/inc/alglib/linalg.h @@ -0,0 +1,5187 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _linalg_pkg_h +#define _linalg_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "alglibmisc.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + double r1; + double rinf; +} matinvreport; +typedef struct +{ + ae_vector vals; + ae_vector idx; + ae_vector ridx; + ae_vector didx; + ae_vector uidx; + ae_int_t matrixtype; + ae_int_t m; + ae_int_t n; + ae_int_t nfree; + ae_int_t ninitialized; +} sparsematrix; +typedef struct +{ + double e1; + double e2; + ae_vector x; + ae_vector ax; + double xax; + ae_int_t n; + ae_vector rk; + ae_vector rk1; + ae_vector xk; + ae_vector xk1; + ae_vector pk; + ae_vector pk1; + ae_vector b; + rcommstate rstate; + ae_vector tmp2; +} fblslincgstate; +typedef struct +{ + ae_int_t n; + ae_int_t m; + ae_int_t nstart; + ae_int_t nits; + ae_int_t seedval; + ae_vector x0; + ae_vector x1; + ae_vector t; + ae_vector xbest; + hqrndstate r; + ae_vector x; + ae_vector mv; + ae_vector mtv; + ae_bool needmv; + ae_bool needmtv; + double repnorm; + rcommstate rstate; +} normestimatorstate; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + + + + + + + + + + + + + + + + +/************************************************************************* +Matrix inverse report: +* R1 reciprocal of condition number in 1-norm +* RInf reciprocal of condition number in inf-norm +*************************************************************************/ +class _matinvreport_owner +{ +public: + _matinvreport_owner(); + _matinvreport_owner(const _matinvreport_owner &rhs); + _matinvreport_owner& operator=(const _matinvreport_owner &rhs); + virtual ~_matinvreport_owner(); + alglib_impl::matinvreport* c_ptr(); + alglib_impl::matinvreport* c_ptr() const; +protected: + alglib_impl::matinvreport *p_struct; +}; +class matinvreport : public _matinvreport_owner +{ +public: + matinvreport(); + matinvreport(const matinvreport &rhs); + matinvreport& operator=(const matinvreport &rhs); + virtual ~matinvreport(); + double &r1; + double &rinf; + +}; + +/************************************************************************* +Sparse matrix + +You should use ALGLIB functions to work with sparse matrix. +Never try to access its fields directly! +*************************************************************************/ +class _sparsematrix_owner +{ +public: + _sparsematrix_owner(); + _sparsematrix_owner(const _sparsematrix_owner &rhs); + _sparsematrix_owner& operator=(const _sparsematrix_owner &rhs); + virtual ~_sparsematrix_owner(); + alglib_impl::sparsematrix* c_ptr(); + alglib_impl::sparsematrix* c_ptr() const; +protected: + alglib_impl::sparsematrix *p_struct; +}; +class sparsematrix : public _sparsematrix_owner +{ +public: + sparsematrix(); + sparsematrix(const sparsematrix &rhs); + sparsematrix& operator=(const sparsematrix &rhs); + virtual ~sparsematrix(); + +}; + + + +/************************************************************************* +This object stores state of the iterative norm estimation algorithm. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +class _normestimatorstate_owner +{ +public: + _normestimatorstate_owner(); + _normestimatorstate_owner(const _normestimatorstate_owner &rhs); + _normestimatorstate_owner& operator=(const _normestimatorstate_owner &rhs); + virtual ~_normestimatorstate_owner(); + alglib_impl::normestimatorstate* c_ptr(); + alglib_impl::normestimatorstate* c_ptr() const; +protected: + alglib_impl::normestimatorstate *p_struct; +}; +class normestimatorstate : public _normestimatorstate_owner +{ +public: + normestimatorstate(); + normestimatorstate(const normestimatorstate &rhs); + normestimatorstate& operator=(const normestimatorstate &rhs); + virtual ~normestimatorstate(); + +}; + +/************************************************************************* +Cache-oblivous complex "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixtranspose(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_2d_array &b, const ae_int_t ib, const ae_int_t jb); + + +/************************************************************************* +Cache-oblivous real "copy-and-transpose" + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixtranspose(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_2d_array &b, const ae_int_t ib, const ae_int_t jb); + + +/************************************************************************* +This code enforces symmetricy of the matrix by copying Upper part to lower +one (or vice versa). + +INPUT PARAMETERS: + A - matrix + N - number of rows/columns + IsUpper - whether we want to copy upper triangle to lower one (True) + or vice versa (False). +*************************************************************************/ +void rmatrixenforcesymmetricity(const real_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void cmatrixcopy(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_2d_array &b, const ae_int_t ib, const ae_int_t jb); + + +/************************************************************************* +Copy + +Input parameters: + M - number of rows + N - number of columns + A - source matrix, MxN submatrix is copied and transposed + IA - submatrix offset (row index) + JA - submatrix offset (column index) + B - destination matrix, must be large enough to store result + IB - submatrix offset (row index) + JB - submatrix offset (column index) +*************************************************************************/ +void rmatrixcopy(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_2d_array &b, const ae_int_t ib, const ae_int_t jb); + + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void cmatrixrank1(const ae_int_t m, const ae_int_t n, complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, complex_1d_array &u, const ae_int_t iu, complex_1d_array &v, const ae_int_t iv); + + +/************************************************************************* +Rank-1 correction: A := A + u*v' + +INPUT PARAMETERS: + M - number of rows + N - number of columns + A - target matrix, MxN submatrix is updated + IA - submatrix offset (row index) + JA - submatrix offset (column index) + U - vector #1 + IU - subvector offset + V - vector #2 + IV - subvector offset +*************************************************************************/ +void rmatrixrank1(const ae_int_t m, const ae_int_t n, real_2d_array &a, const ae_int_t ia, const ae_int_t ja, real_1d_array &u, const ae_int_t iu, real_1d_array &v, const ae_int_t iv); + + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + M>=0 + N - number of columns of op(A) + N>=0 + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + * OpA=2 => op(A) = A^H + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixmv(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t opa, const complex_1d_array &x, const ae_int_t ix, complex_1d_array &y, const ae_int_t iy); + + +/************************************************************************* +Matrix-vector product: y := op(A)*x + +INPUT PARAMETERS: + M - number of rows of op(A) + N - number of columns of op(A) + A - target matrix + IA - submatrix offset (row index) + JA - submatrix offset (column index) + OpA - operation type: + * OpA=0 => op(A) = A + * OpA=1 => op(A) = A^T + X - input vector + IX - subvector offset + IY - subvector offset + Y - preallocated matrix, must be large enough to store result + +OUTPUT PARAMETERS: + Y - vector which stores result + +if M=0, then subroutine does nothing. +if N=0, Y is filled by zeros. + + + -- ALGLIB routine -- + + 28.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixmv(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t opa, const real_1d_array &x, const ae_int_t ix, real_1d_array &y, const ae_int_t iy); + + +/************************************************************************* + +*************************************************************************/ +void cmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2); +void smp_cmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2); + + +/************************************************************************* + +*************************************************************************/ +void cmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2); +void smp_cmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const complex_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const complex_2d_array &x, const ae_int_t i2, const ae_int_t j2); + + +/************************************************************************* + +*************************************************************************/ +void rmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2); +void smp_rmatrixrighttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2); + + +/************************************************************************* + +*************************************************************************/ +void rmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2); +void smp_rmatrixlefttrsm(const ae_int_t m, const ae_int_t n, const real_2d_array &a, const ae_int_t i1, const ae_int_t j1, const bool isupper, const bool isunit, const ae_int_t optype, const real_2d_array &x, const ae_int_t i2, const ae_int_t j2); + + +/************************************************************************* + +*************************************************************************/ +void cmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper); +void smp_cmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper); + + +/************************************************************************* + +*************************************************************************/ +void rmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper); +void smp_rmatrixsyrk(const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc, const bool isupper); + + +/************************************************************************* + +*************************************************************************/ +void cmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const alglib::complex alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const complex_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const alglib::complex beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc); +void smp_cmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const alglib::complex alpha, const complex_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const complex_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const alglib::complex beta, const complex_2d_array &c, const ae_int_t ic, const ae_int_t jc); + + +/************************************************************************* + +*************************************************************************/ +void rmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const real_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc); +void smp_rmatrixgemm(const ae_int_t m, const ae_int_t n, const ae_int_t k, const double alpha, const real_2d_array &a, const ae_int_t ia, const ae_int_t ja, const ae_int_t optypea, const real_2d_array &b, const ae_int_t ib, const ae_int_t jb, const ae_int_t optypeb, const double beta, const real_2d_array &c, const ae_int_t ic, const ae_int_t jc); + +/************************************************************************* +QR decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form (see below). + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0.. Min(M-1,N-1)]. + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size M x N. + +The elements of matrix R are located on and above the main diagonal of +matrix A. The elements which are located in Tau array and below the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(k-1), + +where k = min(m,n), and each H(i) is in the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, +so that v(0:i-1) = 0, v(i) = 1, v(i+1:m-1) stored in A(i+1:m-1,i). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqr(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tau); + + +/************************************************************************* +LQ decomposition of a rectangular matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices L and Q in compact form (see below) + Tau - array of scalar factors which are used to form + matrix Q. Array whose index ranges within [0..Min(M,N)-1]. + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size M x N. + +The elements of matrix L are located on and below the main diagonal of +matrix A. The elements which are located in Tau array and above the main +diagonal of matrix A are used to form matrix Q as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(k-1)*H(k-2)*...*H(1)*H(0), + +where k = min(m,n), and each H(i) is of the form + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - real vector, so that v(0:i-1)=0, +v(i) = 1, v(i+1:n-1) stored in A(i,i+1:n-1). + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlq(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tau); + + +/************************************************************************* +QR decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and R in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = QR, where Q is an orthogonal matrix of size +MxM, R - upper triangular (or upper trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixqr(complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_1d_array &tau); + + +/************************************************************************* +LQ decomposition of a rectangular complex matrix of size MxN + +Input parameters: + A - matrix A whose indexes range within [0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q and L in compact form + Tau - array of scalar factors which are used to form matrix Q. Array + whose indexes range within [0.. Min(M,N)-1] + +Matrix A is represented as A = LQ, where Q is an orthogonal matrix of size +MxM, L - lower triangular (or lower trapezoid) matrix of size MxN. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +void cmatrixlq(complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_1d_array &tau); + + +/************************************************************************* +Partial unpacking of matrix Q from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixQR subroutine. + QColumns - required number of columns of matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose indexes range within [0..M-1, 0..QColumns-1]. + If QColumns=0, the array remains unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackq(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const real_1d_array &tau, const ae_int_t qcolumns, real_2d_array &q); + + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of RMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixqrunpackr(const real_2d_array &a, const ae_int_t m, const ae_int_t n, real_2d_array &r); + + +/************************************************************************* +Partial unpacking of matrix Q from the LQ decomposition of a matrix A + +Input parameters: + A - matrices L and Q in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of the RMatrixLQ subroutine. + QRows - required number of rows in matrix Q. N>=QRows>=0. + +Output parameters: + Q - first QRows rows of matrix Q. Array whose indexes range + within [0..QRows-1, 0..N-1]. If QRows=0, the array remains + unchanged. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackq(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const real_1d_array &tau, const ae_int_t qrows, real_2d_array &q); + + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of RMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlqunpackl(const real_2d_array &a, const ae_int_t m, const ae_int_t n, real_2d_array &l); + + +/************************************************************************* +Partial unpacking of matrix Q from QR decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixQR subroutine . + QColumns - required number of columns in matrix Q. M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array whose index ranges within [0..M-1, 0..QColumns-1]. + If QColumns=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackq(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, const complex_1d_array &tau, const ae_int_t qcolumns, complex_2d_array &q); + + +/************************************************************************* +Unpacking of matrix R from the QR decomposition of a matrix A + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixQR subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + R - matrix R, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixqrunpackr(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_2d_array &r); + + +/************************************************************************* +Partial unpacking of matrix Q from LQ decomposition of a complex matrix A. + +Input parameters: + A - matrices Q and R in compact form. + Output of CMatrixLQ subroutine . + M - number of rows in matrix A. M>=0. + N - number of columns in matrix A. N>=0. + Tau - scalar factors which are used to form Q. + Output of CMatrixLQ subroutine . + QRows - required number of rows in matrix Q. N>=QColumns>=0. + +Output parameters: + Q - first QRows rows of matrix Q. + Array whose index ranges within [0..QRows-1, 0..N-1]. + If QRows=0, array isn't changed. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackq(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, const complex_1d_array &tau, const ae_int_t qrows, complex_2d_array &q); + + +/************************************************************************* +Unpacking of matrix L from the LQ decomposition of a matrix A + +Input parameters: + A - matrices Q and L in compact form. + Output of CMatrixLQ subroutine. + M - number of rows in given matrix A. M>=0. + N - number of columns in given matrix A. N>=0. + +Output parameters: + L - matrix L, array[0..M-1, 0..N-1]. + + -- ALGLIB routine -- + 17.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlqunpackl(const complex_2d_array &a, const ae_int_t m, const ae_int_t n, complex_2d_array &l); + + +/************************************************************************* +Reduction of a rectangular matrix to bidiagonal form + +The algorithm reduces the rectangular matrix A to bidiagonal form by +orthogonal transformations P and Q: A = Q*B*P. + +Input parameters: + A - source matrix. array[0..M-1, 0..N-1] + M - number of rows in matrix A. + N - number of columns in matrix A. + +Output parameters: + A - matrices Q, B, P in compact form (see below). + TauQ - scalar factors which are used to form matrix Q. + TauP - scalar factors which are used to form matrix P. + +The main diagonal and one of the secondary diagonals of matrix A are +replaced with bidiagonal matrix B. Other elements contain elementary +reflections which form MxM matrix Q and NxN matrix P, respectively. + +If M>=N, B is the upper bidiagonal MxN matrix and is stored in the +corresponding elements of matrix A. Matrix Q is represented as a +product of elementary reflections Q = H(0)*H(1)*...*H(n-1), where +H(i) = 1-tau*v*v'. Here tau is a scalar which is stored in TauQ[i], and +vector v has the following structure: v(0:i-1)=0, v(i)=1, v(i+1:m-1) is +stored in elements A(i+1:m-1,i). Matrix P is as follows: P = +G(0)*G(1)*...*G(n-2), where G(i) = 1 - tau*u*u'. Tau is stored in TauP[i], +u(0:i)=0, u(i+1)=1, u(i+2:n-1) is stored in elements A(i,i+2:n-1). + +If M n): m=5, n=6 (m < n): + +( d e u1 u1 u1 ) ( d u1 u1 u1 u1 u1 ) +( v1 d e u2 u2 ) ( e d u2 u2 u2 u2 ) +( v1 v2 d e u3 ) ( v1 e d u3 u3 u3 ) +( v1 v2 v3 d e ) ( v1 v2 e d u4 u4 ) +( v1 v2 v3 v4 d ) ( v1 v2 v3 e d u5 ) +( v1 v2 v3 v4 v5 ) + +Here vi and ui are vectors which form H(i) and G(i), and d and e - +are the diagonal and off-diagonal elements of matrix B. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994. + Sergey Bochkanov, ALGLIB project, translation from FORTRAN to + pseudocode, 2007-2010. +*************************************************************************/ +void rmatrixbd(real_2d_array &a, const ae_int_t m, const ae_int_t n, real_1d_array &tauq, real_1d_array &taup); + + +/************************************************************************* +Unpacking matrix Q which reduces a matrix to bidiagonal form. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + QColumns - required number of columns in matrix Q. + M>=QColumns>=0. + +Output parameters: + Q - first QColumns columns of matrix Q. + Array[0..M-1, 0..QColumns-1] + If QColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackq(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &tauq, const ae_int_t qcolumns, real_2d_array &q); + + +/************************************************************************* +Multiplication by matrix Q which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by Q or Q'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUQ - scalar factors which are used to form Q. + Output of ToBidiagonal subroutine. + Z - multiplied matrix. + array[0..ZRows-1,0..ZColumns-1] + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=M, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=M, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by Q or Q'. + +Output parameters: + Z - product of Z and Q. + Array[0..ZRows-1,0..ZColumns-1] + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyq(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &tauq, real_2d_array &z, const ae_int_t zrows, const ae_int_t zcolumns, const bool fromtheright, const bool dotranspose); + + +/************************************************************************* +Unpacking matrix P which reduces matrix A to bidiagonal form. +The subroutine returns transposed matrix P. + +Input parameters: + QP - matrices Q and P in compact form. + Output of ToBidiagonal subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of ToBidiagonal subroutine. + PTRows - required number of rows of matrix P^T. N >= PTRows >= 0. + +Output parameters: + PT - first PTRows columns of matrix P^T + Array[0..PTRows-1, 0..N-1] + If PTRows=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackpt(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &taup, const ae_int_t ptrows, real_2d_array &pt); + + +/************************************************************************* +Multiplication by matrix P which reduces matrix A to bidiagonal form. + +The algorithm allows pre- or post-multiply by P or P'. + +Input parameters: + QP - matrices Q and P in compact form. + Output of RMatrixBD subroutine. + M - number of rows in matrix A. + N - number of columns in matrix A. + TAUP - scalar factors which are used to form P. + Output of RMatrixBD subroutine. + Z - multiplied matrix. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + ZRows - number of rows in matrix Z. If FromTheRight=False, + ZRows=N, otherwise ZRows can be arbitrary. + ZColumns - number of columns in matrix Z. If FromTheRight=True, + ZColumns=N, otherwise ZColumns can be arbitrary. + FromTheRight - pre- or post-multiply. + DoTranspose - multiply by P or P'. + +Output parameters: + Z - product of Z and P. + Array whose indexes range within [0..ZRows-1,0..ZColumns-1]. + If ZRows=0 or ZColumns=0, the array is not modified. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdmultiplybyp(const real_2d_array &qp, const ae_int_t m, const ae_int_t n, const real_1d_array &taup, real_2d_array &z, const ae_int_t zrows, const ae_int_t zcolumns, const bool fromtheright, const bool dotranspose); + + +/************************************************************************* +Unpacking of the main and secondary diagonals of bidiagonal decomposition +of matrix A. + +Input parameters: + B - output of RMatrixBD subroutine. + M - number of rows in matrix B. + N - number of columns in matrix B. + +Output parameters: + IsUpper - True, if the matrix is upper bidiagonal. + otherwise IsUpper is False. + D - the main diagonal. + Array whose index ranges within [0..Min(M,N)-1]. + E - the secondary diagonal (upper or lower, depending on + the value of IsUpper). + Array index ranges within [0..Min(M,N)-1], the last + element is not used. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixbdunpackdiagonals(const real_2d_array &b, const ae_int_t m, const ae_int_t n, bool &isupper, real_1d_array &d, real_1d_array &e); + + +/************************************************************************* +Reduction of a square matrix to upper Hessenberg form: Q'*A*Q = H, +where Q is an orthogonal matrix, H - Hessenberg matrix. + +Input parameters: + A - matrix A with elements [0..N-1, 0..N-1] + N - size of matrix A. + +Output parameters: + A - matrices Q and P in compact form (see below). + Tau - array of scalar factors which are used to form matrix Q. + Array whose index ranges within [0..N-2] + +Matrix H is located on the main diagonal, on the lower secondary diagonal +and above the main diagonal of matrix A. The elements which are used to +form matrix Q are situated in array Tau and below the lower secondary +diagonal of matrix A as follows: + +Matrix Q is represented as a product of elementary reflections + +Q = H(0)*H(2)*...*H(n-2), + +where each H(i) is given by + +H(i) = 1 - tau * v * (v^T) + +where tau is a scalar stored in Tau[I]; v - is a real vector, +so that v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) stored in A(i+2:n-1,i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void rmatrixhessenberg(real_2d_array &a, const ae_int_t n, real_1d_array &tau); + + +/************************************************************************* +Unpacking matrix Q which reduces matrix A to upper Hessenberg form + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + Tau - scalar factors which are used to form Q. + Output of RMatrixHessenberg subroutine. + +Output parameters: + Q - matrix Q. + Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackq(const real_2d_array &a, const ae_int_t n, const real_1d_array &tau, real_2d_array &q); + + +/************************************************************************* +Unpacking matrix H (the result of matrix A reduction to upper Hessenberg form) + +Input parameters: + A - output of RMatrixHessenberg subroutine. + N - size of matrix A. + +Output parameters: + H - matrix H. Array whose indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + 2005-2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixhessenbergunpackh(const real_2d_array &a, const ae_int_t n, real_2d_array &h); + + +/************************************************************************* +Reduction of a symmetric matrix which is given by its higher or lower +triangular part to a tridiagonal matrix using orthogonal similarity +transformation: Q'*A*Q=T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a real scalar, and v is a real vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + + where d and e denote diagonal and off-diagonal elements of T, and vi + denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void smatrixtd(real_2d_array &a, const ae_int_t n, const bool isupper, real_1d_array &tau, real_1d_array &d, real_1d_array &e); + + +/************************************************************************* +Unpacking matrix Q which reduces symmetric matrix to a tridiagonal +form. + +Input parameters: + A - the result of a SMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of SMatrixTD subroutine) + Tau - the result of a SMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void smatrixtdunpackq(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_1d_array &tau, real_2d_array &q); + + +/************************************************************************* +Reduction of a Hermitian matrix which is given by its higher or lower +triangular part to a real tridiagonal matrix using unitary similarity +transformation: Q'*A*Q = T. + +Input parameters: + A - matrix to be transformed + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. If IsUpper = True, then matrix A is given + by its upper triangle, and the lower triangle is not used + and not modified by the algorithm, and vice versa + if IsUpper = False. + +Output parameters: + A - matrices T and Q in compact form (see lower) + Tau - array of factors which are forming matrices H(i) + array with elements [0..N-2]. + D - main diagonal of real symmetric matrix T. + array with elements [0..N-1]. + E - secondary diagonal of real symmetric matrix T. + array with elements [0..N-2]. + + + If IsUpper=True, the matrix Q is represented as a product of elementary + reflectors + + Q = H(n-2) . . . H(2) H(0). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(i+1:n-1) = 0, v(i) = 1, v(0:i-1) is stored on exit in + A(0:i-1,i+1), and tau in TAU(i). + + If IsUpper=False, the matrix Q is represented as a product of elementary + reflectors + + Q = H(0) H(2) . . . H(n-2). + + Each H(i) has the form + + H(i) = I - tau * v * v' + + where tau is a complex scalar, and v is a complex vector with + v(0:i) = 0, v(i+1) = 1, v(i+2:n-1) is stored on exit in A(i+2:n-1,i), + and tau in TAU(i). + + The contents of A on exit are illustrated by the following examples + with n = 5: + + if UPLO = 'U': if UPLO = 'L': + + ( d e v1 v2 v3 ) ( d ) + ( d e v2 v3 ) ( e d ) + ( d e v3 ) ( v0 e d ) + ( d e ) ( v0 v1 e d ) + ( d ) ( v0 v1 v2 e d ) + +where d and e denote diagonal and off-diagonal elements of T, and vi +denotes an element of the vector defining H(i). + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1992 +*************************************************************************/ +void hmatrixtd(complex_2d_array &a, const ae_int_t n, const bool isupper, complex_1d_array &tau, real_1d_array &d, real_1d_array &e); + + +/************************************************************************* +Unpacking matrix Q which reduces a Hermitian matrix to a real tridiagonal +form. + +Input parameters: + A - the result of a HMatrixTD subroutine + N - size of matrix A. + IsUpper - storage format (a parameter of HMatrixTD subroutine) + Tau - the result of a HMatrixTD subroutine + +Output parameters: + Q - transformation matrix. + array with elements [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void hmatrixtdunpackq(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_1d_array &tau, complex_2d_array &q); + +/************************************************************************* +Singular value decomposition of a bidiagonal matrix (extended algorithm) + +The algorithm performs the singular value decomposition of a bidiagonal +matrix B (upper or lower) representing it as B = Q*S*P^T, where Q and P - +orthogonal matrices, S - diagonal matrix with non-negative elements on the +main diagonal, in descending order. + +The algorithm finds singular values. In addition, the algorithm can +calculate matrices Q and P (more precisely, not the matrices, but their +product with given matrices U and VT - U*Q and (P^T)*VT)). Of course, +matrices U and VT can be of any type, including identity. Furthermore, the +algorithm can calculate Q'*C (this product is calculated more effectively +than U*Q, because this calculation operates with rows instead of matrix +columns). + +The feature of the algorithm is its ability to find all singular values +including those which are arbitrarily close to 0 with relative accuracy +close to machine precision. If the parameter IsFractionalAccuracyRequired +is set to True, all singular values will have high relative accuracy close +to machine precision. If the parameter is set to False, only the biggest +singular value will have relative accuracy close to machine precision. +The absolute error of other singular values is equal to the absolute error +of the biggest singular value. + +Input parameters: + D - main diagonal of matrix B. + Array whose index ranges within [0..N-1]. + E - superdiagonal (or subdiagonal) of matrix B. + Array whose index ranges within [0..N-2]. + N - size of matrix B. + IsUpper - True, if the matrix is upper bidiagonal. + IsFractionalAccuracyRequired - + THIS PARAMETER IS IGNORED SINCE ALGLIB 3.5.0 + SINGULAR VALUES ARE ALWAYS SEARCHED WITH HIGH ACCURACY. + U - matrix to be multiplied by Q. + Array whose indexes range within [0..NRU-1, 0..N-1]. + The matrix can be bigger, in that case only the submatrix + [0..NRU-1, 0..N-1] will be multiplied by Q. + NRU - number of rows in matrix U. + C - matrix to be multiplied by Q'. + Array whose indexes range within [0..N-1, 0..NCC-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCC-1] will be multiplied by Q'. + NCC - number of columns in matrix C. + VT - matrix to be multiplied by P^T. + Array whose indexes range within [0..N-1, 0..NCVT-1]. + The matrix can be bigger, in that case only the submatrix + [0..N-1, 0..NCVT-1] will be multiplied by P^T. + NCVT - number of columns in matrix VT. + +Output parameters: + D - singular values of matrix B in descending order. + U - if NRU>0, contains matrix U*Q. + VT - if NCVT>0, contains matrix (P^T)*VT. + C - if NCC>0, contains matrix Q'*C. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Additional information: + The type of convergence is controlled by the internal parameter TOL. + If the parameter is greater than 0, the singular values will have + relative accuracy TOL. If TOL<0, the singular values will have + absolute accuracy ABS(TOL)*norm(B). + By default, |TOL| falls within the range of 10*Epsilon and 100*Epsilon, + where Epsilon is the machine precision. It is not recommended to use + TOL less than 10*Epsilon since this will considerably slow down the + algorithm and may not lead to error decreasing. +History: + * 31 March, 2007. + changed MAXITR from 6 to 12. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + October 31, 1999. +*************************************************************************/ +bool rmatrixbdsvd(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const bool isupper, const bool isfractionalaccuracyrequired, real_2d_array &u, const ae_int_t nru, real_2d_array &c, const ae_int_t ncc, real_2d_array &vt, const ae_int_t ncvt); + +/************************************************************************* +Singular value decomposition of a rectangular matrix. + +The algorithm calculates the singular value decomposition of a matrix of +size MxN: A = U * S * V^T + +The algorithm finds the singular values and, optionally, matrices U and V^T. +The algorithm can find both first min(M,N) columns of matrix U and rows of +matrix V^T (singular vectors), and matrices U and V^T wholly (of sizes MxM +and NxN respectively). + +Take into account that the subroutine does not return matrix V but V^T. + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + UNeeded - 0, 1 or 2. See the description of the parameter U. + VTNeeded - 0, 1 or 2. See the description of the parameter VT. + AdditionalMemory - + If the parameter: + * equals 0, the algorithm doesn’t use additional + memory (lower requirements, lower performance). + * equals 1, the algorithm uses additional + memory of size min(M,N)*min(M,N) of real numbers. + It often speeds up the algorithm. + * equals 2, the algorithm uses additional + memory of size M*min(M,N) of real numbers. + It allows to get a maximum performance. + The recommended value of the parameter is 2. + +Output parameters: + W - contains singular values in descending order. + U - if UNeeded=0, U isn't changed, the left singular vectors + are not calculated. + if Uneeded=1, U contains left singular vectors (first + min(M,N) columns of matrix U). Array whose indexes range + within [0..M-1, 0..Min(M,N)-1]. + if UNeeded=2, U contains matrix U wholly. Array whose + indexes range within [0..M-1, 0..M-1]. + VT - if VTNeeded=0, VT isn’t changed, the right singular vectors + are not calculated. + if VTNeeded=1, VT contains right singular vectors (first + min(M,N) rows of matrix V^T). Array whose indexes range + within [0..min(M,N)-1, 0..N-1]. + if VTNeeded=2, VT contains matrix V^T wholly. Array whose + indexes range within [0..N-1, 0..N-1]. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +bool rmatrixsvd(const real_2d_array &a, const ae_int_t m, const ae_int_t n, const ae_int_t uneeded, const ae_int_t vtneeded, const ae_int_t additionalmemory, real_1d_array &w, real_2d_array &u, real_2d_array &vt); + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a symmetric matrix + +The algorithm finds eigen pairs of a symmetric matrix by reducing it to +tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpper - storage format. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevd(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, real_1d_array &d, real_2d_array &z); + + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a symmetric +matrix in a given half open interval (A, B] by using a bisection and +inverse iteration + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half open interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval (M>=0). + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, + M is equal to 0. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevdr(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const double b1, const double b2, ae_int_t &m, real_1d_array &w, real_2d_array &z); + + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a symmetric +matrix with given indexes by using bisection and inverse iteration methods. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 07.01.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixevdi(const real_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const ae_int_t i1, const ae_int_t i2, real_1d_array &w, real_2d_array &z); + + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a Hermitian matrix + +The algorithm finds eigen pairs of a Hermitian matrix by reducing it to +real tridiagonal form and using the QL/QR algorithm. + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged (rare case). + +Note: + eigenvectors of Hermitian matrix are defined up to multiplication by + a complex number L, such that |L|=1. + + -- ALGLIB -- + Copyright 2005, 23 March 2007 by Bochkanov Sergey +*************************************************************************/ +bool hmatrixevd(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, real_1d_array &d, complex_2d_array &z); + + +/************************************************************************* +Subroutine for finding the eigenvalues (and eigenvectors) of a Hermitian +matrix in a given half-interval (A, B] by using a bisection and inverse +iteration + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. Array whose indexes range within + [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + B1, B2 - half-interval (B1, B2] to search eigenvalues in. + +Output parameters: + M - number of eigenvalues found in a given half-interval, M>=0 + W - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..M-1]. + The eigenvectors are stored in the matrix columns. + +Result: + True, if successful. M contains the number of eigenvalues in the given + half-interval (could be equal to 0), W contains the eigenvalues, + Z contains the eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned, M is + equal to 0. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +bool hmatrixevdr(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const double b1, const double b2, ae_int_t &m, real_1d_array &w, complex_2d_array &z); + + +/************************************************************************* +Subroutine for finding the eigenvalues and eigenvectors of a Hermitian +matrix with given indexes by using bisection and inverse iteration methods + +Input parameters: + A - Hermitian matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or + not. If ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + IsUpperA - storage format of matrix A. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + +Output parameters: + W - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..I2-I1]. + In that case, the eigenvectors are stored in the matrix + columns. + +Result: + True, if successful. W contains the eigenvalues, Z contains the + eigenvectors (if needed). + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration + subroutine wasn't able to find all the corresponding eigenvectors. + In that case, the eigenvalues and eigenvectors are not returned. + +Note: + eigen vectors of Hermitian matrix are defined up to multiplication by + a complex number L, such as |L|=1. + + -- ALGLIB -- + Copyright 07.01.2006, 24.03.2007 by Bochkanov Sergey. +*************************************************************************/ +bool hmatrixevdi(const complex_2d_array &a, const ae_int_t n, const ae_int_t zneeded, const bool isupper, const ae_int_t i1, const ae_int_t i2, real_1d_array &w, complex_2d_array &z); + + +/************************************************************************* +Finding the eigenvalues and eigenvectors of a tridiagonal symmetric matrix + +The algorithm finds the eigen pairs of a tridiagonal symmetric matrix by +using an QL/QR algorithm with implicit shifts. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix A. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix + are multiplied by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity + transformation of a symmetric matrix; + * 2, the eigenvectors of a tridiagonal matrix replace the + square matrix Z; + * 3, matrix Z contains the first row of the eigenvectors + matrix. + Z - if ZNeeded=1, Z contains the square matrix by which the + eigenvectors are multiplied. + Array whose indexes range within [0..N-1, 0..N-1]. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains the product of a given matrix (from the left) + and the eigenvectors matrix (from the right); + * 2, Z contains the eigenvectors. + * 3, Z contains the first row of the eigenvectors matrix. + If ZNeeded<3, Z is the array whose indexes range within [0..N-1, 0..N-1]. + In that case, the eigenvectors are stored in the matrix columns. + If ZNeeded=3, Z is the array whose indexes range within [0..0, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm hasn't converged. + + -- LAPACK routine (version 3.0) -- + Univ. of Tennessee, Univ. of California Berkeley, NAG Ltd., + Courant Institute, Argonne National Lab, and Rice University + September 30, 1994 +*************************************************************************/ +bool smatrixtdevd(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, real_2d_array &z); + + +/************************************************************************* +Subroutine for finding the tridiagonal matrix eigenvalues/vectors in a +given half-interval (A, B] by using bisection and inverse iteration. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix, N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the tridiagonal + matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace matrix Z. + A, B - half-interval (A, B] to search eigenvalues in. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range + within [0..N-1, 0..N-1]) which reduces the given symmetric + matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..M-1]. + M - number of eigenvalues found in the given half-interval (M>=0). + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the + left) and NxM matrix of the eigenvectors found (from the + right). Array whose indexes range within [0..N-1, 0..M-1]. + * 2, contains the matrix of the eigenvectors found. + Array whose indexes range within [0..N-1, 0..M-1]. + +Result: + + True, if successful. In that case, M contains the number of eigenvalues + in the given half-interval (could be equal to 0), D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the + eigenvalues in the given interval or if the inverse iteration subroutine + wasn't able to find all the corresponding eigenvectors. In that case, + the eigenvalues and eigenvectors are not returned, M is equal to 0. + + -- ALGLIB -- + Copyright 31.03.2008 by Bochkanov Sergey +*************************************************************************/ +bool smatrixtdevdr(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, const double a, const double b, ae_int_t &m, real_2d_array &z); + + +/************************************************************************* +Subroutine for finding tridiagonal matrix eigenvalues/vectors with given +indexes (in ascending order) by using the bisection and inverse iteraion. + +Input parameters: + D - the main diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-1]. + E - the secondary diagonal of a tridiagonal matrix. + Array whose index ranges within [0..N-2]. + N - size of matrix. N>=0. + ZNeeded - flag controlling whether the eigenvectors are needed or not. + If ZNeeded is equal to: + * 0, the eigenvectors are not needed; + * 1, the eigenvectors of a tridiagonal matrix are multiplied + by the square matrix Z. It is used if the + tridiagonal matrix is obtained by the similarity transformation + of a symmetric matrix. + * 2, the eigenvectors of a tridiagonal matrix replace + matrix Z. + I1, I2 - index interval for searching (from I1 to I2). + 0 <= I1 <= I2 <= N-1. + Z - if ZNeeded is equal to: + * 0, Z isn't used and remains unchanged; + * 1, Z contains the square matrix (array whose indexes range within [0..N-1, 0..N-1]) + which reduces the given symmetric matrix to tridiagonal form; + * 2, Z isn't used (but changed on the exit). + +Output parameters: + D - array of the eigenvalues found. + Array whose index ranges within [0..I2-I1]. + Z - if ZNeeded is equal to: + * 0, doesn't contain any information; + * 1, contains the product of a given NxN matrix Z (from the left) and + Nx(I2-I1) matrix of the eigenvectors found (from the right). + Array whose indexes range within [0..N-1, 0..I2-I1]. + * 2, contains the matrix of the eigenvalues found. + Array whose indexes range within [0..N-1, 0..I2-I1]. + + +Result: + + True, if successful. In that case, D contains the eigenvalues, + Z contains the eigenvectors (if needed). + It should be noted that the subroutine changes the size of arrays D and Z. + + False, if the bisection method subroutine wasn't able to find the eigenvalues + in the given interval or if the inverse iteration subroutine wasn't able + to find all the corresponding eigenvectors. In that case, the eigenvalues + and eigenvectors are not returned. + + -- ALGLIB -- + Copyright 25.12.2005 by Bochkanov Sergey +*************************************************************************/ +bool smatrixtdevdi(real_1d_array &d, const real_1d_array &e, const ae_int_t n, const ae_int_t zneeded, const ae_int_t i1, const ae_int_t i2, real_2d_array &z); + + +/************************************************************************* +Finding eigenvalues and eigenvectors of a general matrix + +The algorithm finds eigenvalues and eigenvectors of a general matrix by +using the QR algorithm with multiple shifts. The algorithm can find +eigenvalues and both left and right eigenvectors. + +The right eigenvector is a vector x such that A*x = w*x, and the left +eigenvector is a vector y such that y'*A = w*y' (here y' implies a complex +conjugate transposition of vector y). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + VNeeded - flag controlling whether eigenvectors are needed or not. + If VNeeded is equal to: + * 0, eigenvectors are not returned; + * 1, right eigenvectors are returned; + * 2, left eigenvectors are returned; + * 3, both left and right eigenvectors are returned. + +Output parameters: + WR - real parts of eigenvalues. + Array whose index ranges within [0..N-1]. + WR - imaginary parts of eigenvalues. + Array whose index ranges within [0..N-1]. + VL, VR - arrays of left and right eigenvectors (if they are needed). + If WI[i]=0, the respective eigenvalue is a real number, + and it corresponds to the column number I of matrices VL/VR. + If WI[i]>0, we have a pair of complex conjugate numbers with + positive and negative imaginary parts: + the first eigenvalue WR[i] + sqrt(-1)*WI[i]; + the second eigenvalue WR[i+1] + sqrt(-1)*WI[i+1]; + WI[i]>0 + WI[i+1] = -WI[i] < 0 + In that case, the eigenvector corresponding to the first + eigenvalue is located in i and i+1 columns of matrices + VL/VR (the column number i contains the real part, and the + column number i+1 contains the imaginary part), and the vector + corresponding to the second eigenvalue is a complex conjugate to + the first vector. + Arrays whose indexes range within [0..N-1, 0..N-1]. + +Result: + True, if the algorithm has converged. + False, if the algorithm has not converged. + +Note 1: + Some users may ask the following question: what if WI[N-1]>0? + WI[N] must contain an eigenvalue which is complex conjugate to the + N-th eigenvalue, but the array has only size N? + The answer is as follows: such a situation cannot occur because the + algorithm finds a pairs of eigenvalues, therefore, if WI[i]>0, I is + strictly less than N-1. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms of linear algebra). If you require maximum performance + on your machine, it is recommended to adjust this parameter manually. + + +See also the InternalTREVC subroutine. + +The algorithm is based on the LAPACK 3.0 library. +*************************************************************************/ +bool rmatrixevd(const real_2d_array &a, const ae_int_t n, const ae_int_t vneeded, real_1d_array &wr, real_1d_array &wi, real_2d_array &vl, real_2d_array &vr); + +/************************************************************************* +Generation of a random uniformly distributed (Haar) orthogonal matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonal(const ae_int_t n, real_2d_array &a); + + +/************************************************************************* +Generation of random NxN matrix with given condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndcond(const ae_int_t n, const double c, real_2d_array &a); + + +/************************************************************************* +Generation of a random Haar distributed orthogonal complex matrix + +INPUT PARAMETERS: + N - matrix size, N>=1 + +OUTPUT PARAMETERS: + A - orthogonal NxN matrix, array[0..N-1,0..N-1] + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonal(const ae_int_t n, complex_2d_array &a); + + +/************************************************************************* +Generation of random NxN complex matrix with given condition number C and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a); + + +/************************************************************************* +Generation of random NxN symmetric matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndcond(const ae_int_t n, const double c, real_2d_array &a); + + +/************************************************************************* +Generation of random NxN symmetric positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random SPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixrndcond(const ae_int_t n, const double c, real_2d_array &a); + + +/************************************************************************* +Generation of random NxN Hermitian matrix with given condition number and +norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a); + + +/************************************************************************* +Generation of random NxN Hermitian positive definite matrix with given +condition number and norm2(A)=1 + +INPUT PARAMETERS: + N - matrix size + C - condition number (in 2-norm) + +OUTPUT PARAMETERS: + A - random HPD matrix with norm2(A)=1 and cond(A)=C + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixrndcond(const ae_int_t n, const double c, complex_2d_array &a); + + +/************************************************************************* +Multiplication of MxN matrix by NxN random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheright(real_2d_array &a, const ae_int_t m, const ae_int_t n); + + +/************************************************************************* +Multiplication of MxN matrix by MxM random Haar distributed orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void rmatrixrndorthogonalfromtheleft(real_2d_array &a, const ae_int_t m, const ae_int_t n); + + +/************************************************************************* +Multiplication of MxN complex matrix by NxN random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheright(complex_2d_array &a, const ae_int_t m, const ae_int_t n); + + +/************************************************************************* +Multiplication of MxN complex matrix by MxM random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..M-1, 0..N-1] + M, N- matrix size + +OUTPUT PARAMETERS: + A - Q*A, where Q is random MxM orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void cmatrixrndorthogonalfromtheleft(complex_2d_array &a, const ae_int_t m, const ae_int_t n); + + +/************************************************************************* +Symmetric multiplication of NxN matrix by random Haar distributed +orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q'*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void smatrixrndmultiply(real_2d_array &a, const ae_int_t n); + + +/************************************************************************* +Hermitian multiplication of NxN matrix by random Haar distributed +complex orthogonal matrix + +INPUT PARAMETERS: + A - matrix, array[0..N-1, 0..N-1] + N - matrix size + +OUTPUT PARAMETERS: + A - Q^H*A*Q, where Q is random NxN orthogonal matrix + + -- ALGLIB routine -- + 04.12.2009 + Bochkanov Sergey +*************************************************************************/ +void hmatrixrndmultiply(complex_2d_array &a, const ae_int_t n); + +/************************************************************************* +LU decomposition of a general real matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. +It is optimized for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixlu(real_2d_array &a, const ae_int_t m, const ae_int_t n, integer_1d_array &pivots); + + +/************************************************************************* +LU decomposition of a general complex matrix with row pivoting + +A is represented as A = P*L*U, where: +* L is lower unitriangular matrix +* U is upper triangular matrix +* P = P0*P1*...*PK, K=min(M,N)-1, + Pi - permutation matrix for I and Pivots[I] + +This is cache-oblivous implementation of LU decomposition. It is optimized +for square matrices. As for rectangular matrices: +* best case - M>>N +* worst case - N>>M, small M, large N, matrix does not fit in CPU cache + +INPUT PARAMETERS: + A - array[0..M-1, 0..N-1]. + M - number of rows in matrix A. + N - number of columns in matrix A. + + +OUTPUT PARAMETERS: + A - matrices L and U in compact form: + * L is stored under main diagonal + * U is stored on and above main diagonal + Pivots - permutation matrix in compact form. + array[0..Min(M-1,N-1)]. + + -- ALGLIB routine -- + 10.01.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixlu(complex_2d_array &a, const ae_int_t m, const ae_int_t n, integer_1d_array &pivots); + + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a Hermitian positive- +definite matrix. The result of an algorithm is a representation of A as +A=U'*U or A=L*L' (here X' detones conj(X^T)). + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U'*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +bool hpdmatrixcholesky(complex_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Cache-oblivious Cholesky decomposition + +The algorithm computes Cholesky decomposition of a symmetric positive- +definite matrix. The result of an algorithm is a representation of A as +A=U^T*U or A=L*L^T + +INPUT PARAMETERS: + A - upper or lower triangle of a factorized matrix. + array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - if IsUpper=True, then A contains an upper triangle of + a symmetric matrix, otherwise A contains a lower one. + +OUTPUT PARAMETERS: + A - the result of factorization. If IsUpper=True, then + the upper triangle contains matrix U, so that A = U^T*U, + and the elements below the main diagonal are not modified. + Similarly, if IsUpper = False. + +RESULT: + If the matrix is positive-definite, the function returns True. + Otherwise, the function returns False. Contents of A is not determined + in such case. + + -- ALGLIB routine -- + 15.12.2009 + Bochkanov Sergey +*************************************************************************/ +bool spdmatrixcholesky(real_2d_array &a, const ae_int_t n, const bool isupper); + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcond1(const real_2d_array &a, const ae_int_t n); + + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixrcondinf(const real_2d_array &a, const ae_int_t n); + + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - symmetric positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixrcond(const real_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcond1(const real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit); + + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixtrrcondinf(const real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit); + + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix. + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm of condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + A - Hermitian positive definite matrix which is given by its + upper or lower triangle depending on the value of + IsUpper. Array with elements [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - storage format. + +Result: + 1/LowerBound(cond(A)), if matrix A is positive definite, + -1, if matrix A is not positive definite, and its condition number + could not be found by this algorithm. + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixrcond(const complex_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Estimate of a matrix condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcond1(const complex_2d_array &a, const ae_int_t n); + + +/************************************************************************* +Estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixrcondinf(const complex_2d_array &a, const ae_int_t n); + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcond1(const real_2d_array &lua, const ae_int_t n); + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the RMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double rmatrixlurcondinf(const real_2d_array &lua, const ae_int_t n); + + +/************************************************************************* +Condition number estimate of a symmetric positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double spdmatrixcholeskyrcond(const real_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Condition number estimate of a Hermitian positive definite matrix given by +Cholesky decomposition. + +The algorithm calculates a lower bound of the condition number. In this +case, the algorithm does not return a lower bound of the condition number, +but an inverse number (to avoid an overflow in case of a singular matrix). + +It should be noted that 1-norm and inf-norm condition numbers of symmetric +matrices are equal, so the algorithm doesn't take into account the +differences between these types of norms. + +Input parameters: + CD - Cholesky decomposition of matrix A, + output of SMatrixCholesky subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double hpdmatrixcholeskyrcond(const complex_2d_array &a, const ae_int_t n, const bool isupper); + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcond1(const complex_2d_array &lua, const ae_int_t n); + + +/************************************************************************* +Estimate of the condition number of a matrix given by its LU decomposition +(infinity norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + LUA - LU decomposition of a matrix in compact form. Output of + the CMatrixLU subroutine. + N - size of matrix A. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixlurcondinf(const complex_2d_array &lua, const ae_int_t n); + + +/************************************************************************* +Triangular matrix: estimate of a condition number (1-norm) + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array[0..N-1, 0..N-1]. + N - size of A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcond1(const complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit); + + +/************************************************************************* +Triangular matrix: estimate of a matrix condition number (infinity-norm). + +The algorithm calculates a lower bound of the condition number. In this case, +the algorithm does not return a lower bound of the condition number, but an +inverse number (to avoid an overflow in case of a singular matrix). + +Input parameters: + A - matrix. Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + IsUpper - True, if the matrix is upper triangular. + IsUnit - True, if the matrix has a unit diagonal. + +Result: 1/LowerBound(cond(A)) + +NOTE: + if k(A) is very large, then matrix is assumed degenerate, k(A)=INF, + 0.0 is returned in such cases. +*************************************************************************/ +double cmatrixtrrcondinf(const complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit); + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of RMatrixLU subroutine). + Pivots - table of permutations + (the output of RMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code: + * -3 A is singular, or VERY close to singular. + it is filled by zeros in such cases. + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + A - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void rmatrixluinverse(real_2d_array &a, const integer_1d_array &pivots, const ae_int_t n, ae_int_t &info, matinvreport &rep); +void rmatrixluinverse(real_2d_array &a, const integer_1d_array &pivots, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + +Result: + True, if the matrix is not singular. + False, if the matrix is singular. + + -- ALGLIB -- + Copyright 2005-2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinverse(real_2d_array &a, const ae_int_t n, ae_int_t &info, matinvreport &rep); +void rmatrixinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a matrix given by its LU decomposition. + +INPUT PARAMETERS: + A - LU decomposition of the matrix + (output of CMatrixLU subroutine). + Pivots - table of permutations + (the output of CMatrixLU subroutine). + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +OUTPUT PARAMETERS: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 05.02.2010 + Bochkanov Sergey +*************************************************************************/ +void cmatrixluinverse(complex_2d_array &a, const integer_1d_array &pivots, const ae_int_t n, ae_int_t &info, matinvreport &rep); +void cmatrixluinverse(complex_2d_array &a, const integer_1d_array &pivots, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a general matrix. + +Input parameters: + A - matrix + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void cmatrixinverse(complex_2d_array &a, const ae_int_t n, ae_int_t &info, matinvreport &rep); +void cmatrixinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a symmetric positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of SPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskyinverse(real_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep); +void spdmatrixcholeskyinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a symmetric positive definite matrix. + +Given an upper or lower triangle of a symmetric positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void spdmatrixinverse(real_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep); +void spdmatrixinverse(real_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a Hermitian positive definite matrix which is given +by Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition of the matrix to be inverted: + A=U’*U or A = L*L'. + Output of HPDMatrixCholesky subroutine. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, lower half is used. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskyinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep); +void hpdmatrixcholeskyinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Inversion of a Hermitian positive definite matrix. + +Given an upper or lower triangle of a Hermitian positive definite matrix, +the algorithm generates matrix A^-1 and saves the upper or lower triangle +depending on the input. + +Input parameters: + A - matrix to be inverted (upper or lower triangle). + Array with elements [0..N-1,0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - storage type (optional): + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Output parameters: + Info - return code, same as in RMatrixLUInverse + Rep - solver report, same as in RMatrixLUInverse + A - inverse of matrix A, same as in RMatrixLUInverse + + -- ALGLIB routine -- + 10.02.2010 + Bochkanov Sergey +*************************************************************************/ +void hpdmatrixinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, ae_int_t &info, matinvreport &rep); +void hpdmatrixinverse(complex_2d_array &a, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Triangular matrix inverse (real) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixtrinverse(real_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit, ae_int_t &info, matinvreport &rep); +void rmatrixtrinverse(real_2d_array &a, const bool isupper, ae_int_t &info, matinvreport &rep); + + +/************************************************************************* +Triangular matrix inverse (complex) + +The subroutine inverts the following types of matrices: + * upper triangular + * upper triangular with unit diagonal + * lower triangular + * lower triangular with unit diagonal + +In case of an upper (lower) triangular matrix, the inverse matrix will +also be upper (lower) triangular, and after the end of the algorithm, the +inverse matrix replaces the source matrix. The elements below (above) the +main diagonal are not changed by the algorithm. + +If the matrix has a unit diagonal, the inverse matrix also has a unit +diagonal, and the diagonal elements are not passed to the algorithm. + +Input parameters: + A - matrix, array[0..N-1, 0..N-1]. + N - size of matrix A (optional) : + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, size is automatically determined from + matrix size (A must be square matrix) + IsUpper - True, if the matrix is upper triangular. + IsUnit - diagonal type (optional): + * if True, matrix has unit diagonal (a[i,i] are NOT used) + * if False, matrix diagonal is arbitrary + * if not given, False is assumed + +Output parameters: + Info - same as for RMatrixLUInverse + Rep - same as for RMatrixLUInverse + A - same as for RMatrixLUInverse. + + -- ALGLIB -- + Copyright 05.02.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixtrinverse(complex_2d_array &a, const ae_int_t n, const bool isupper, const bool isunit, ae_int_t &info, matinvreport &rep); +void cmatrixtrinverse(complex_2d_array &a, const bool isupper, ae_int_t &info, matinvreport &rep); + +/************************************************************************* +This function creates sparse matrix in a Hash-Table format. + +This function creates Hast-Table matrix, which can be converted to CRS +format after its initialization is over. Typical usage scenario for a +sparse matrix is: +1. creation in a Hash-Table format +2. insertion of the matrix elements +3. conversion to the CRS representation +4. matrix is passed to some linear algebra algorithm + +Some information about different matrix formats can be found below, in +the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + K - K>=0, expected number of non-zero elements in a matrix. + K can be inexact approximation, can be less than actual + number of elements (table will grow when needed) or + even zero). + It is important to understand that although hash-table + may grow automatically, it is better to provide good + estimate of data size. + +OUTPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + All elements of the matrix are zero. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreate(const ae_int_t m, const ae_int_t n, const ae_int_t k, sparsematrix &s); +void sparsecreate(const ae_int_t m, const ae_int_t n, sparsematrix &s); + + +/************************************************************************* +This function creates sparse matrix in a CRS format (expert function for +situations when you are running out of memory). + +This function creates CRS matrix. Typical usage scenario for a CRS matrix +is: +1. creation (you have to tell number of non-zero elements at each row at + this moment) +2. insertion of the matrix elements (row by row, from left to right) +3. matrix is passed to some linear algebra algorithm + +This function is a memory-efficient alternative to SparseCreate(), but it +is more complex because it requires you to know in advance how large your +matrix is. Some information about different matrix formats can be found +below, in the "NOTES" section. + +INPUT PARAMETERS + M - number of rows in a matrix, M>=1 + N - number of columns in a matrix, N>=1 + NER - number of elements at each row, array[M], NER[I]>=0 + +OUTPUT PARAMETERS + S - sparse M*N matrix in CRS representation. + You have to fill ALL non-zero elements by calling + SparseSet() BEFORE you try to use this matrix. + +NOTE 1. + +Sparse matrices can be stored using either Hash-Table representation or +Compressed Row Storage representation. Hast-table is better suited for +querying and dynamic operations (thus, it is used for matrix +initialization), but it is inefficient when you want to make some linear +algebra operations. + +From the other side, CRS is better suited for linear algebra operations, +but initialization is less convenient - you have to tell row sizes at the +initialization, and you can fill matrix only row by row, from left to +right. CRS is also very inefficient when you want to find matrix element +by its index. + +Thus, Hash-Table representation does not support linear algebra +operations, while CRS format does not support modification of the table. +Tables below outline information about these two formats: + + OPERATIONS WITH MATRIX HASH CRS + create + + + read element + + + modify element + + add value to element + + A*x (dense vector) + + A'*x (dense vector) + + A*X (dense matrix) + + A'*X (dense matrix) + + +NOTE 2. + +Hash-tables use memory inefficiently, and they have to keep some amount +of the "spare memory" in order to have good performance. Hash table for +matrix with K non-zero elements will need C*K*(8+2*sizeof(int)) bytes, +where C is a small constant, about 1.5-2 in magnitude. + +CRS storage, from the other side, is more memory-efficient, and needs +just K*(8+sizeof(int))+M*sizeof(int) bytes, where M is a number of rows +in a matrix. + +When you convert from the Hash-Table to CRS representation, all unneeded +memory will be freed. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecreatecrs(const ae_int_t m, const ae_int_t n, const integer_1d_array &ner, sparsematrix &s); + + +/************************************************************************* +This function copies S0 to S1. + +NOTE: this function does not verify its arguments, it just copies all +fields of the structure. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsecopy(const sparsematrix &s0, sparsematrix &s1); + + +/************************************************************************* +This function adds value to S[i,j] - element of the sparse matrix. Matrix +must be in a Hash-Table mode. + +In case S[i,j] already exists in the table, V i added to its value. In +case S[i,j] is non-existent, it is inserted in the table. Table +automatically grows when necessary. + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table representation. + Exception will be thrown for CRS matrix. + I - row index of the element to modify, 0<=I=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + X - array[N], input vector. For performance reasons we + make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + Y - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + Y - array[M], S*x + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmv(const sparsematrix &s, const bool isupper, const real_1d_array &x, real_1d_array &y); + + +/************************************************************************* +This function calculates matrix-matrix product S*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size + is at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b); + + +/************************************************************************* +This function calculates matrix-matrix product S^T*A. Matrix S must be +stored in CRS format (exception will be thrown otherwise). + +INPUT PARAMETERS + S - sparse M*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[M][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least M, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemtm(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b); + + +/************************************************************************* +This function simultaneously calculates two matrix-matrix products: + S*A and S^T*A. +S must be square (non-rectangular) matrix stored in CRS format (exception +will be thrown otherwise). + +INPUT PARAMETERS + S - sparse N*N matrix in CRS format (you MUST convert it + to CRS before calling this function). + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B0 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + B1 - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B0 - array[N][K], S*A + B1 - array[N][K], S^T*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. It also throws exception when S is non-square. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsemm2(const sparsematrix &s, const real_2d_array &a, const ae_int_t k, real_2d_array &b0, real_2d_array &b1); + + +/************************************************************************* +This function calculates matrix-matrix product S*A, when S is symmetric +matrix. Matrix S must be stored in CRS format (exception will be +thrown otherwise). + +INPUT PARAMETERS + S - sparse M*M matrix in CRS format (you MUST convert it + to CRS before calling this function). + IsUpper - whether upper or lower triangle of S is given: + * if upper triangle is given, only S[i,j] for j>=i + are used, and lower triangle is ignored (it can be + empty - these elements are not referenced at all). + * if lower triangle is given, only S[i,j] for j<=i + are used, and upper triangle is ignored. + A - array[N][K], input dense matrix. For performance reasons + we make only quick checks - we check that array size is + at least N, but we do not check for NAN's or INF's. + K - number of columns of matrix (A). + B - output buffer, possibly preallocated. In case buffer + size is too small to store result, this buffer is + automatically resized. + +OUTPUT PARAMETERS + B - array[M][K], S*A + +NOTE: this function throws exception when called for non-CRS matrix. You +must convert your matrix with SparseConvertToCRS() before using this +function. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparsesmm(const sparsematrix &s, const bool isupper, const real_2d_array &a, const ae_int_t k, real_2d_array &b); + + +/************************************************************************* +This procedure resizes Hash-Table matrix. It can be called when you have +deleted too many elements from the matrix, and you want to free unneeded +memory. + + -- ALGLIB PROJECT -- + Copyright 14.10.2011 by Bochkanov Sergey +*************************************************************************/ +void sparseresizematrix(const sparsematrix &s); + + +/************************************************************************* +This function is used to enumerate all elements of the sparse matrix. +Before first call user initializes T0 and T1 counters by zero. These +counters are used to remember current position in a matrix; after each +call they are updated by the function. + +Subsequent calls to this function return non-zero elements of the sparse +matrix, one by one. If you enumerate CRS matrix, matrix is traversed from +left to right, from top to bottom. In case you enumerate matrix stored as +Hash table, elements are returned in random order. + +EXAMPLE + > T0=0 + > T1=0 + > while SparseEnumerate(S,T0,T1,I,J,V) do + > ....do something with I,J,V + +INPUT PARAMETERS + S - sparse M*N matrix in Hash-Table or CRS representation. + T0 - internal counter + T1 - internal counter + +OUTPUT PARAMETERS + T0 - new value of the internal counter + T1 - new value of the internal counter + I - row index of non-zero element, 0<=I0 + N - number of columns in the matrix being estimated, N>0 + NStart - number of random starting vectors + recommended value - at least 5. + NIts - number of iterations to do with best starting vector + recommended value - at least 5. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTE: this algorithm is effectively deterministic, i.e. it always returns +same result when repeatedly called for the same matrix. In fact, algorithm +uses randomized starting vectors, but internal random numbers generator +always generates same sequence of the random values (it is a feature, not +bug). + +Algorithm can be made non-deterministic with NormEstimatorSetSeed(0) call. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorcreate(const ae_int_t m, const ae_int_t n, const ae_int_t nstart, const ae_int_t nits, normestimatorstate &state); + + +/************************************************************************* +This function changes seed value used by algorithm. In some cases we need +deterministic processing, i.e. subsequent calls must return equal results, +in other cases we need non-deterministic algorithm which returns different +results for the same matrix on every pass. + +Setting zero seed will lead to non-deterministic algorithm, while non-zero +value will make our algorithm deterministic. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + SeedVal - seed value, >=0. Zero value = non-deterministic algo. + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorsetseed(const normestimatorstate &state, const ae_int_t seedval); + + +/************************************************************************* +This function estimates norm of the sparse M*N matrix A. + +INPUT PARAMETERS: + State - norm estimator state, must be initialized with a call + to NormEstimatorCreate() + A - sparse M*N matrix, must be converted to CRS format + prior to calling this function. + +After this function is over you can call NormEstimatorResults() to get +estimate of the norm(A). + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorestimatesparse(const normestimatorstate &state, const sparsematrix &a); + + +/************************************************************************* +Matrix norm estimation results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + Nrm - estimate of the matrix norm, Nrm>=0 + + -- ALGLIB -- + Copyright 06.12.2011 by Bochkanov Sergey +*************************************************************************/ +void normestimatorresults(const normestimatorstate &state, double &nrm); + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixludet(const real_2d_array &a, const integer_1d_array &pivots, const ae_int_t n); +double rmatrixludet(const real_2d_array &a, const integer_1d_array &pivots); + + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +double rmatrixdet(const real_2d_array &a, const ae_int_t n); +double rmatrixdet(const real_2d_array &a); + + +/************************************************************************* +Determinant calculation of the matrix given by its LU decomposition. + +Input parameters: + A - LU decomposition of the matrix (output of + RMatrixLU subroutine). + Pivots - table of permutations which were made during + the LU decomposition. + Output of RMatrixLU subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: matrix determinant. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixludet(const complex_2d_array &a, const integer_1d_array &pivots, const ae_int_t n); +alglib::complex cmatrixludet(const complex_2d_array &a, const integer_1d_array &pivots); + + +/************************************************************************* +Calculation of the determinant of a general matrix + +Input parameters: + A - matrix, array[0..N-1, 0..N-1] + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +Result: determinant of matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +alglib::complex cmatrixdet(const complex_2d_array &a, const ae_int_t n); +alglib::complex cmatrixdet(const complex_2d_array &a); + + +/************************************************************************* +Determinant calculation of the matrix given by the Cholesky decomposition. + +Input parameters: + A - Cholesky decomposition, + output of SMatrixCholesky subroutine. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + +As the determinant is equal to the product of squares of diagonal elements, +it’s not necessary to specify which triangle - lower or upper - the matrix +is stored in. + +Result: + matrix determinant. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixcholeskydet(const real_2d_array &a, const ae_int_t n); +double spdmatrixcholeskydet(const real_2d_array &a); + + +/************************************************************************* +Determinant calculation of the symmetric positive definite matrix. + +Input parameters: + A - matrix. Array with elements [0..N-1, 0..N-1]. + N - (optional) size of matrix A: + * if given, only principal NxN submatrix is processed and + overwritten. other elements are unchanged. + * if not given, automatically determined from matrix size + (A must be square matrix) + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used/changed by + function + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used/changed by + function + * if not given, both lower and upper triangles must be + filled. + +Result: + determinant of matrix A. + If matrix A is not positive definite, exception is thrown. + + -- ALGLIB -- + Copyright 2005-2008 by Bochkanov Sergey +*************************************************************************/ +double spdmatrixdet(const real_2d_array &a, const ae_int_t n, const bool isupper); +double spdmatrixdet(const real_2d_array &a); + +/************************************************************************* +Algorithm for solving the following generalized symmetric positive-definite +eigenproblem: + A*x = lambda*B*x (1) or + A*B*x = lambda*x (2) or + B*A*x = lambda*x (3). +where A is a symmetric matrix, B - symmetric positive-definite matrix. +The problem is solved by reducing it to an ordinary symmetric eigenvalue +problem. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrices A and B. + IsUpperA - storage format of matrix A. + B - symmetric positive-definite matrix which is given by + its upper or lower triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperB - storage format of matrix B. + ZNeeded - if ZNeeded is equal to: + * 0, the eigenvectors are not returned; + * 1, the eigenvectors are returned. + ProblemType - if ProblemType is equal to: + * 1, the following problem is solved: A*x = lambda*B*x; + * 2, the following problem is solved: A*B*x = lambda*x; + * 3, the following problem is solved: B*A*x = lambda*x. + +Output parameters: + D - eigenvalues in ascending order. + Array whose index ranges within [0..N-1]. + Z - if ZNeeded is equal to: + * 0, Z hasn’t changed; + * 1, Z contains eigenvectors. + Array whose indexes range within [0..N-1, 0..N-1]. + The eigenvectors are stored in matrix columns. It should + be noted that the eigenvectors in such problems do not + form an orthogonal system. + +Result: + True, if the problem was solved successfully. + False, if the error occurred during the Cholesky decomposition of matrix + B (the matrix isn’t positive-definite) or during the work of the iterative + algorithm for solving the symmetric eigenproblem. + +See also the GeneralizedSymmetricDefiniteEVDReduce subroutine. + + -- ALGLIB -- + Copyright 1.28.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixgevd(const real_2d_array &a, const ae_int_t n, const bool isuppera, const real_2d_array &b, const bool isupperb, const ae_int_t zneeded, const ae_int_t problemtype, real_1d_array &d, real_2d_array &z); + + +/************************************************************************* +Algorithm for reduction of the following generalized symmetric positive- +definite eigenvalue problem: + A*x = lambda*B*x (1) or + A*B*x = lambda*x (2) or + B*A*x = lambda*x (3) +to the symmetric eigenvalues problem C*y = lambda*y (eigenvalues of this and +the given problems are the same, and the eigenvectors of the given problem +could be obtained by multiplying the obtained eigenvectors by the +transformation matrix x = R*y). + +Here A is a symmetric matrix, B - symmetric positive-definite matrix. + +Input parameters: + A - symmetric matrix which is given by its upper or lower + triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrices A and B. + IsUpperA - storage format of matrix A. + B - symmetric positive-definite matrix which is given by + its upper or lower triangular part. + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperB - storage format of matrix B. + ProblemType - if ProblemType is equal to: + * 1, the following problem is solved: A*x = lambda*B*x; + * 2, the following problem is solved: A*B*x = lambda*x; + * 3, the following problem is solved: B*A*x = lambda*x. + +Output parameters: + A - symmetric matrix which is given by its upper or lower + triangle depending on IsUpperA. Contains matrix C. + Array whose indexes range within [0..N-1, 0..N-1]. + R - upper triangular or low triangular transformation matrix + which is used to obtain the eigenvectors of a given problem + as the product of eigenvectors of C (from the right) and + matrix R (from the left). If the matrix is upper + triangular, the elements below the main diagonal + are equal to 0 (and vice versa). Thus, we can perform + the multiplication without taking into account the + internal structure (which is an easier though less + effective way). + Array whose indexes range within [0..N-1, 0..N-1]. + IsUpperR - type of matrix R (upper or lower triangular). + +Result: + True, if the problem was reduced successfully. + False, if the error occurred during the Cholesky decomposition of + matrix B (the matrix is not positive-definite). + + -- ALGLIB -- + Copyright 1.28.2006 by Bochkanov Sergey +*************************************************************************/ +bool smatrixgevdreduce(real_2d_array &a, const ae_int_t n, const bool isuppera, const real_2d_array &b, const bool isupperb, const ae_int_t problemtype, real_2d_array &r, bool &isupperr); + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a number to an element +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - row where the element to be updated is stored. + UpdColumn - column where the element to be updated is stored. + UpdVal - a number to be added to the element. + + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatesimple(real_2d_array &inva, const ae_int_t n, const ae_int_t updrow, const ae_int_t updcolumn, const double updval); + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a row +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdRow - the row of A whose vector V was added. + 0 <= Row <= N-1 + V - the vector to be added to a row. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdaterow(real_2d_array &inva, const ae_int_t n, const ae_int_t updrow, const real_1d_array &v); + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm updates matrix A^-1 when adding a vector to a column +of matrix A. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + UpdColumn - the column of A whose vector U was added. + 0 <= UpdColumn <= N-1 + U - the vector to be added to a column. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of modified matrix A. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdatecolumn(real_2d_array &inva, const ae_int_t n, const ae_int_t updcolumn, const real_1d_array &u); + + +/************************************************************************* +Inverse matrix update by the Sherman-Morrison formula + +The algorithm computes the inverse of matrix A+u*v’ by using the given matrix +A^-1 and the vectors u and v. + +Input parameters: + InvA - inverse of matrix A. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of matrix A. + U - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + V - the vector modifying the matrix. + Array whose index ranges within [0..N-1]. + +Output parameters: + InvA - inverse of matrix A + u*v'. + + -- ALGLIB -- + Copyright 2005 by Bochkanov Sergey +*************************************************************************/ +void rmatrixinvupdateuv(real_2d_array &inva, const ae_int_t n, const real_1d_array &u, const real_1d_array &v); + +/************************************************************************* +Subroutine performing the Schur decomposition of a general matrix by using +the QR algorithm with multiple shifts. + +The source matrix A is represented as S'*A*S = T, where S is an orthogonal +matrix (Schur vectors), T - upper quasi-triangular matrix (with blocks of +sizes 1x1 and 2x2 on the main diagonal). + +Input parameters: + A - matrix to be decomposed. + Array whose indexes range within [0..N-1, 0..N-1]. + N - size of A, N>=0. + + +Output parameters: + A - contains matrix T. + Array whose indexes range within [0..N-1, 0..N-1]. + S - contains Schur vectors. + Array whose indexes range within [0..N-1, 0..N-1]. + +Note 1: + The block structure of matrix T can be easily recognized: since all + the elements below the blocks are zeros, the elements a[i+1,i] which + are equal to 0 show the block border. + +Note 2: + The algorithm performance depends on the value of the internal parameter + NS of the InternalSchurDecomposition subroutine which defines the number + of shifts in the QR algorithm (similarly to the block width in block-matrix + algorithms in linear algebra). If you require maximum performance on + your machine, it is recommended to adjust this parameter manually. + +Result: + True, + if the algorithm has converged and parameters A and S contain the result. + False, + if the algorithm has not converged. + +Algorithm implemented on the basis of the DHSEQR subroutine (LAPACK 3.0 library). +*************************************************************************/ +bool rmatrixschur(real_2d_array &a, const ae_int_t n, real_2d_array &s); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void ablassplitlength(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state); +void ablascomplexsplitlength(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t* n1, + ae_int_t* n2, + ae_state *_state); +ae_int_t ablasblocksize(/* Real */ ae_matrix* a, ae_state *_state); +ae_int_t ablascomplexblocksize(/* Complex */ ae_matrix* a, + ae_state *_state); +ae_int_t ablasmicroblocksize(ae_state *_state); +void cmatrixtranspose(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state); +void rmatrixtranspose(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state); +void rmatrixenforcesymmetricity(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +void cmatrixcopy(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state); +void rmatrixcopy(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_state *_state); +void cmatrixrank1(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Complex */ ae_vector* u, + ae_int_t iu, + /* Complex */ ae_vector* v, + ae_int_t iv, + ae_state *_state); +void rmatrixrank1(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + /* Real */ ae_vector* u, + ae_int_t iu, + /* Real */ ae_vector* v, + ae_int_t iv, + ae_state *_state); +void cmatrixmv(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Complex */ ae_vector* x, + ae_int_t ix, + /* Complex */ ae_vector* y, + ae_int_t iy, + ae_state *_state); +void rmatrixmv(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t opa, + /* Real */ ae_vector* x, + ae_int_t ix, + /* Real */ ae_vector* y, + ae_int_t iy, + ae_state *_state); +void cmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +void _pexec_cmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state); +void cmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +void _pexec_cmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Complex */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state); +void rmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +void _pexec_rmatrixrighttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state); +void rmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, + ae_state *_state); +void _pexec_rmatrixlefttrsm(ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_int_t i1, + ae_int_t j1, + ae_bool isupper, + ae_bool isunit, + ae_int_t optype, + /* Real */ ae_matrix* x, + ae_int_t i2, + ae_int_t j2, ae_state *_state); +void cmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +void _pexec_cmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, ae_state *_state); +void rmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, + ae_state *_state); +void _pexec_rmatrixsyrk(ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_bool isupper, ae_state *_state); +void cmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void _pexec_cmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + ae_complex alpha, + /* Complex */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Complex */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + ae_complex beta, + /* Complex */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, ae_state *_state); +void rmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, + ae_state *_state); +void _pexec_rmatrixgemm(ae_int_t m, + ae_int_t n, + ae_int_t k, + double alpha, + /* Real */ ae_matrix* a, + ae_int_t ia, + ae_int_t ja, + ae_int_t optypea, + /* Real */ ae_matrix* b, + ae_int_t ib, + ae_int_t jb, + ae_int_t optypeb, + double beta, + /* Real */ ae_matrix* c, + ae_int_t ic, + ae_int_t jc, ae_state *_state); +void rmatrixqr(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state); +void rmatrixlq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state); +void cmatrixqr(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_state *_state); +void cmatrixlq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_state *_state); +void rmatrixqrunpackq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_int_t qcolumns, + /* Real */ ae_matrix* q, + ae_state *_state); +void rmatrixqrunpackr(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* r, + ae_state *_state); +void rmatrixlqunpackq(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_int_t qrows, + /* Real */ ae_matrix* q, + ae_state *_state); +void rmatrixlqunpackl(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_matrix* l, + ae_state *_state); +void cmatrixqrunpackq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_int_t qcolumns, + /* Complex */ ae_matrix* q, + ae_state *_state); +void cmatrixqrunpackr(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* r, + ae_state *_state); +void cmatrixlqunpackq(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_vector* tau, + ae_int_t qrows, + /* Complex */ ae_matrix* q, + ae_state *_state); +void cmatrixlqunpackl(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Complex */ ae_matrix* l, + ae_state *_state); +void rmatrixqrbasecase(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* work, + /* Real */ ae_vector* t, + /* Real */ ae_vector* tau, + ae_state *_state); +void rmatrixlqbasecase(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* work, + /* Real */ ae_vector* t, + /* Real */ ae_vector* tau, + ae_state *_state); +void rmatrixbd(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + /* Real */ ae_vector* taup, + ae_state *_state); +void rmatrixbdunpackq(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + ae_int_t qcolumns, + /* Real */ ae_matrix* q, + ae_state *_state); +void rmatrixbdmultiplybyq(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tauq, + /* Real */ ae_matrix* z, + ae_int_t zrows, + ae_int_t zcolumns, + ae_bool fromtheright, + ae_bool dotranspose, + ae_state *_state); +void rmatrixbdunpackpt(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* taup, + ae_int_t ptrows, + /* Real */ ae_matrix* pt, + ae_state *_state); +void rmatrixbdmultiplybyp(/* Real */ ae_matrix* qp, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* taup, + /* Real */ ae_matrix* z, + ae_int_t zrows, + ae_int_t zcolumns, + ae_bool fromtheright, + ae_bool dotranspose, + ae_state *_state); +void rmatrixbdunpackdiagonals(/* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t n, + ae_bool* isupper, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state); +void rmatrixhessenberg(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + ae_state *_state); +void rmatrixhessenbergunpackq(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state); +void rmatrixhessenbergunpackh(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* h, + ae_state *_state); +void smatrixtd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tau, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state); +void smatrixtdunpackq(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tau, + /* Real */ ae_matrix* q, + ae_state *_state); +void hmatrixtd(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tau, + /* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_state *_state); +void hmatrixtdunpackq(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* tau, + /* Complex */ ae_matrix* q, + ae_state *_state); +ae_bool rmatrixbdsvd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t ncvt, + ae_state *_state); +ae_bool bidiagonalsvddecomposition(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_bool isupper, + ae_bool isfractionalaccuracyrequired, + /* Real */ ae_matrix* u, + ae_int_t nru, + /* Real */ ae_matrix* c, + ae_int_t ncc, + /* Real */ ae_matrix* vt, + ae_int_t ncvt, + ae_state *_state); +ae_bool rmatrixsvd(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_int_t uneeded, + ae_int_t vtneeded, + ae_int_t additionalmemory, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* u, + /* Real */ ae_matrix* vt, + ae_state *_state); +ae_bool smatrixevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + /* Real */ ae_vector* d, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixevdr(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + double b1, + double b2, + ae_int_t* m, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixevdi(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* w, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool hmatrixevd(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + /* Real */ ae_vector* d, + /* Complex */ ae_matrix* z, + ae_state *_state); +ae_bool hmatrixevdr(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + double b1, + double b2, + ae_int_t* m, + /* Real */ ae_vector* w, + /* Complex */ ae_matrix* z, + ae_state *_state); +ae_bool hmatrixevdi(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t zneeded, + ae_bool isupper, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_vector* w, + /* Complex */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixtdevd(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixtdevdr(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + double a, + double b, + ae_int_t* m, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixtdevdi(/* Real */ ae_vector* d, + /* Real */ ae_vector* e, + ae_int_t n, + ae_int_t zneeded, + ae_int_t i1, + ae_int_t i2, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool rmatrixevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t vneeded, + /* Real */ ae_vector* wr, + /* Real */ ae_vector* wi, + /* Real */ ae_matrix* vl, + /* Real */ ae_matrix* vr, + ae_state *_state); +void rmatrixrndorthogonal(ae_int_t n, + /* Real */ ae_matrix* a, + ae_state *_state); +void rmatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state); +void cmatrixrndorthogonal(ae_int_t n, + /* Complex */ ae_matrix* a, + ae_state *_state); +void cmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state); +void smatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state); +void spdmatrixrndcond(ae_int_t n, + double c, + /* Real */ ae_matrix* a, + ae_state *_state); +void hmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state); +void hpdmatrixrndcond(ae_int_t n, + double c, + /* Complex */ ae_matrix* a, + ae_state *_state); +void rmatrixrndorthogonalfromtheright(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void rmatrixrndorthogonalfromtheleft(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void cmatrixrndorthogonalfromtheright(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void cmatrixrndorthogonalfromtheleft(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + ae_state *_state); +void smatrixrndmultiply(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +void hmatrixrndmultiply(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +void rmatrixlu(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +void cmatrixlu(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +ae_bool hpdmatrixcholesky(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +ae_bool spdmatrixcholesky(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +void rmatrixlup(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +void cmatrixlup(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +void rmatrixplu(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +void cmatrixplu(/* Complex */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* pivots, + ae_state *_state); +ae_bool spdmatrixcholeskyrec(/* Real */ ae_matrix* a, + ae_int_t offs, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* tmp, + ae_state *_state); +double rmatrixrcond1(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double rmatrixrcondinf(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double spdmatrixrcond(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +double rmatrixtrrcond1(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state); +double rmatrixtrrcondinf(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state); +double hpdmatrixrcond(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +double cmatrixrcond1(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double cmatrixrcondinf(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double rmatrixlurcond1(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_state *_state); +double rmatrixlurcondinf(/* Real */ ae_matrix* lua, + ae_int_t n, + ae_state *_state); +double spdmatrixcholeskyrcond(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +double hpdmatrixcholeskyrcond(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +double cmatrixlurcond1(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_state *_state); +double cmatrixlurcondinf(/* Complex */ ae_matrix* lua, + ae_int_t n, + ae_state *_state); +double cmatrixtrrcond1(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state); +double cmatrixtrrcondinf(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_state *_state); +double rcondthreshold(ae_state *_state); +void rmatrixluinverse(/* Real */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void rmatrixinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void cmatrixluinverse(/* Complex */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void cmatrixinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void spdmatrixcholeskyinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void spdmatrixinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void hpdmatrixcholeskyinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void hpdmatrixinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void rmatrixtrinverse(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +void cmatrixtrinverse(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_bool isunit, + ae_int_t* info, + matinvreport* rep, + ae_state *_state); +ae_bool _matinvreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _matinvreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _matinvreport_clear(void* _p); +void _matinvreport_destroy(void* _p); +void sparsecreate(ae_int_t m, + ae_int_t n, + ae_int_t k, + sparsematrix* s, + ae_state *_state); +void sparsecreatecrs(ae_int_t m, + ae_int_t n, + /* Integer */ ae_vector* ner, + sparsematrix* s, + ae_state *_state); +void sparsecopy(sparsematrix* s0, sparsematrix* s1, ae_state *_state); +void sparseadd(sparsematrix* s, + ae_int_t i, + ae_int_t j, + double v, + ae_state *_state); +void sparseset(sparsematrix* s, + ae_int_t i, + ae_int_t j, + double v, + ae_state *_state); +double sparseget(sparsematrix* s, + ae_int_t i, + ae_int_t j, + ae_state *_state); +double sparsegetdiagonal(sparsematrix* s, ae_int_t i, ae_state *_state); +void sparseconverttocrs(sparsematrix* s, ae_state *_state); +void sparsemv(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void sparsemtv(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void sparsemv2(sparsematrix* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y0, + /* Real */ ae_vector* y1, + ae_state *_state); +void sparsesmv(sparsematrix* s, + ae_bool isupper, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +void sparsemm(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state); +void sparsemtm(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state); +void sparsemm2(sparsematrix* s, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b0, + /* Real */ ae_matrix* b1, + ae_state *_state); +void sparsesmm(sparsematrix* s, + ae_bool isupper, + /* Real */ ae_matrix* a, + ae_int_t k, + /* Real */ ae_matrix* b, + ae_state *_state); +void sparseresizematrix(sparsematrix* s, ae_state *_state); +double sparsegetaveragelengthofchain(sparsematrix* s, ae_state *_state); +ae_bool sparseenumerate(sparsematrix* s, + ae_int_t* t0, + ae_int_t* t1, + ae_int_t* i, + ae_int_t* j, + double* v, + ae_state *_state); +ae_bool sparserewriteexisting(sparsematrix* s, + ae_int_t i, + ae_int_t j, + double v, + ae_state *_state); +void sparsegetrow(sparsematrix* s, + ae_int_t i, + /* Real */ ae_vector* irow, + ae_state *_state); +void sparseconverttohash(sparsematrix* s, ae_state *_state); +void sparsecopytohash(sparsematrix* s0, + sparsematrix* s1, + ae_state *_state); +void sparsecopytocrs(sparsematrix* s0, sparsematrix* s1, ae_state *_state); +ae_int_t sparsegetmatrixtype(sparsematrix* s, ae_state *_state); +ae_bool sparseishash(sparsematrix* s, ae_state *_state); +ae_bool sparseiscrs(sparsematrix* s, ae_state *_state); +void sparsefree(sparsematrix* s, ae_state *_state); +ae_int_t sparsegetnrows(sparsematrix* s, ae_state *_state); +ae_int_t sparsegetncols(sparsematrix* s, ae_state *_state); +ae_bool _sparsematrix_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sparsematrix_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sparsematrix_clear(void* _p); +void _sparsematrix_destroy(void* _p); +void fblscholeskysolve(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state); +void fblssolvecgx(/* Real */ ae_matrix* a, + ae_int_t m, + ae_int_t n, + double alpha, + /* Real */ ae_vector* b, + /* Real */ ae_vector* x, + /* Real */ ae_vector* buf, + ae_state *_state); +void fblscgcreate(/* Real */ ae_vector* x, + /* Real */ ae_vector* b, + ae_int_t n, + fblslincgstate* state, + ae_state *_state); +ae_bool fblscgiteration(fblslincgstate* state, ae_state *_state); +void fblssolvels(/* Real */ ae_matrix* a, + /* Real */ ae_vector* b, + ae_int_t m, + ae_int_t n, + /* Real */ ae_vector* tmp0, + /* Real */ ae_vector* tmp1, + /* Real */ ae_vector* tmp2, + ae_state *_state); +ae_bool _fblslincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _fblslincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _fblslincgstate_clear(void* _p); +void _fblslincgstate_destroy(void* _p); +void normestimatorcreate(ae_int_t m, + ae_int_t n, + ae_int_t nstart, + ae_int_t nits, + normestimatorstate* state, + ae_state *_state); +void normestimatorsetseed(normestimatorstate* state, + ae_int_t seedval, + ae_state *_state); +ae_bool normestimatoriteration(normestimatorstate* state, + ae_state *_state); +void normestimatorestimatesparse(normestimatorstate* state, + sparsematrix* a, + ae_state *_state); +void normestimatorresults(normestimatorstate* state, + double* nrm, + ae_state *_state); +void normestimatorrestart(normestimatorstate* state, ae_state *_state); +ae_bool _normestimatorstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _normestimatorstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _normestimatorstate_clear(void* _p); +void _normestimatorstate_destroy(void* _p); +double rmatrixludet(/* Real */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_state *_state); +double rmatrixdet(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +ae_complex cmatrixludet(/* Complex */ ae_matrix* a, + /* Integer */ ae_vector* pivots, + ae_int_t n, + ae_state *_state); +ae_complex cmatrixdet(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double spdmatrixcholeskydet(/* Real */ ae_matrix* a, + ae_int_t n, + ae_state *_state); +double spdmatrixdet(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + ae_state *_state); +ae_bool smatrixgevd(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isuppera, + /* Real */ ae_matrix* b, + ae_bool isupperb, + ae_int_t zneeded, + ae_int_t problemtype, + /* Real */ ae_vector* d, + /* Real */ ae_matrix* z, + ae_state *_state); +ae_bool smatrixgevdreduce(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isuppera, + /* Real */ ae_matrix* b, + ae_bool isupperb, + ae_int_t problemtype, + /* Real */ ae_matrix* r, + ae_bool* isupperr, + ae_state *_state); +void rmatrixinvupdatesimple(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updrow, + ae_int_t updcolumn, + double updval, + ae_state *_state); +void rmatrixinvupdaterow(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updrow, + /* Real */ ae_vector* v, + ae_state *_state); +void rmatrixinvupdatecolumn(/* Real */ ae_matrix* inva, + ae_int_t n, + ae_int_t updcolumn, + /* Real */ ae_vector* u, + ae_state *_state); +void rmatrixinvupdateuv(/* Real */ ae_matrix* inva, + ae_int_t n, + /* Real */ ae_vector* u, + /* Real */ ae_vector* v, + ae_state *_state); +ae_bool rmatrixschur(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* s, + ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/optimization.cpp b/src/inc/alglib/optimization.cpp new file mode 100644 index 0000000..0b43330 --- /dev/null +++ b/src/inc/alglib/optimization.cpp @@ -0,0 +1,25034 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "optimization.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + + + + + + + + + +/************************************************************************* +This object stores state of the nonlinear CG optimizer. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +_mincgstate_owner::_mincgstate_owner() +{ + p_struct = (alglib_impl::mincgstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mincgstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mincgstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mincgstate_owner::_mincgstate_owner(const _mincgstate_owner &rhs) +{ + p_struct = (alglib_impl::mincgstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::mincgstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mincgstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mincgstate_owner& _mincgstate_owner::operator=(const _mincgstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mincgstate_clear(p_struct); + if( !alglib_impl::_mincgstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mincgstate_owner::~_mincgstate_owner() +{ + alglib_impl::_mincgstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mincgstate* _mincgstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mincgstate* _mincgstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mincgstate::mincgstate() : _mincgstate_owner() ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +mincgstate::mincgstate(const mincgstate &rhs):_mincgstate_owner(rhs) ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +mincgstate& mincgstate::operator=(const mincgstate &rhs) +{ + if( this==&rhs ) + return *this; + _mincgstate_owner::operator=(rhs); + return *this; +} + +mincgstate::~mincgstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_mincgreport_owner::_mincgreport_owner() +{ + p_struct = (alglib_impl::mincgreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mincgreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mincgreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mincgreport_owner::_mincgreport_owner(const _mincgreport_owner &rhs) +{ + p_struct = (alglib_impl::mincgreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::mincgreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_mincgreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_mincgreport_owner& _mincgreport_owner::operator=(const _mincgreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_mincgreport_clear(p_struct); + if( !alglib_impl::_mincgreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_mincgreport_owner::~_mincgreport_owner() +{ + alglib_impl::_mincgreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::mincgreport* _mincgreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::mincgreport* _mincgreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +mincgreport::mincgreport() : _mincgreport_owner() ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype) +{ +} + +mincgreport::mincgreport(const mincgreport &rhs):_mincgreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype) +{ +} + +mincgreport& mincgreport::operator=(const mincgreport &rhs) +{ + if( this==&rhs ) + return *this; + _mincgreport_owner::operator=(rhs); + return *this; +} + +mincgreport::~mincgreport() +{ +} + +/************************************************************************* + NONLINEAR CONJUGATE GRADIENT METHOD + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using one of the +nonlinear conjugate gradient methods. + +These CG methods are globally convergent (even on non-convex functions) as +long as grad(f) is Lipschitz continuous in a some neighborhood of the +L = { x : f(x)<=f(x0) }. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinCGCreate() call +2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and + other functions +3. User calls MinCGOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinCGResults() to get solution +5. Optionally, user may call MinCGRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinCGRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgcreate(const ae_int_t n, const real_1d_array &x, mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgcreate(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + NONLINEAR CONJUGATE GRADIENT METHOD + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using one of the +nonlinear conjugate gradient methods. + +These CG methods are globally convergent (even on non-convex functions) as +long as grad(f) is Lipschitz continuous in a some neighborhood of the +L = { x : f(x)<=f(x0) }. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinCGCreate() call +2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and + other functions +3. User calls MinCGOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinCGResults() to get solution +5. Optionally, user may call MinCGRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinCGRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgcreate(const real_1d_array &x, mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgcreate(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinCGCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinCGCreate() in order to get more +information about creation of CG optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinCGSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. L-BFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgcreatef(const ae_int_t n, const real_1d_array &x, const double diffstep, mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgcreatef(n, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinCGCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinCGCreate() in order to get more +information about creation of CG optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinCGSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. L-BFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgcreatef(const real_1d_array &x, const double diffstep, mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgcreatef(n, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping conditions for CG optimization algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinCGSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcond(const mincgstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetcond(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets scaling coefficients for CG 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 + +Scaling is also used by finite difference variant of CG optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the CG too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinCGSetPrec...() functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgsetscale(const mincgstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetxrep(const mincgstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets CG algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + CGType - algorithm type: + * -1 automatic selection of the best algorithm + * 0 DY (Dai and Yuan) algorithm + * 1 Hybrid DY-HS algorithm + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcgtype(const mincgstate &state, const ae_int_t cgtype) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetcgtype(const_cast(state.c_ptr()), cgtype, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetstpmax(const mincgstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function allows to suggest initial step length to the CG algorithm. + +Suggested step length is used as starting point for the line search. It +can be useful when you have badly scaled problem, i.e. when ||grad|| +(which is used as initial estimate for the first step) is many orders of +magnitude different from the desired step. + +Line search may fail on such problems without good estimate of initial +step length. Imagine, for example, problem with ||grad||=10^50 and desired +step equal to 0.1 Line search function will use 10^50 as initial step, +then it will decrease step length by 2 (up to 20 attempts) and will get +10^44, which is still too large. + +This function allows us to tell than line search should be started from +some moderate step length, like 1.0, so algorithm will be able to detect +desired step length in a several searches. + +Default behavior (when no step is suggested) is to use preconditioner, if +it is available, to generate initial estimate of step length. + +This function influences only first iteration of algorithm. It should be +called between MinCGCreate/MinCGRestartFrom() call and MinCGOptimize call. +Suggested step is ignored if you have preconditioner. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + Stp - initial estimate of the step length. + Can be zero (no estimate). + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsuggeststep(const mincgstate &state, const double stp) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsuggeststep(const_cast(state.c_ptr()), stp, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdefault(const mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetprecdefault(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdiag(const mincgstate &state, const real_1d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetprecdiag(const_cast(state.c_ptr()), const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinCGSetScale() call +(before or after MinCGSetPrecScale() call). Without knowledge of the scale +of your variables scale-based preconditioner will be just unit matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecscale(const mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetprecscale(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool mincgiteration(const mincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::mincgiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void mincgoptimize(mincgstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'mincgoptimize()' (func is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::mincgiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'mincgoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void mincgoptimize(mincgstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'mincgoptimize()' (grad is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::mincgiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'mincgoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +Conjugate gradient results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinCGSetGradientCheck() for more information. + * 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, + we return best X found so far + * 8 terminated by user + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresults(const mincgstate &state, real_1d_array &x, mincgreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Conjugate gradient results + +Buffered implementation of MinCGResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresultsbuf(const mincgstate &state, real_1d_array &x, mincgreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgrestartfrom(const mincgstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgrestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinCGOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinCGSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 31.05.2012 by Bochkanov Sergey +*************************************************************************/ +void mincgsetgradientcheck(const mincgstate &state, const double teststep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mincgsetgradientcheck(const_cast(state.c_ptr()), teststep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This object stores nonlinear optimizer state. +You should use functions provided by MinBLEIC subpackage to work with this +object +*************************************************************************/ +_minbleicstate_owner::_minbleicstate_owner() +{ + p_struct = (alglib_impl::minbleicstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minbleicstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minbleicstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minbleicstate_owner::_minbleicstate_owner(const _minbleicstate_owner &rhs) +{ + p_struct = (alglib_impl::minbleicstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minbleicstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minbleicstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minbleicstate_owner& _minbleicstate_owner::operator=(const _minbleicstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minbleicstate_clear(p_struct); + if( !alglib_impl::_minbleicstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minbleicstate_owner::~_minbleicstate_owner() +{ + alglib_impl::_minbleicstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minbleicstate* _minbleicstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minbleicstate* _minbleicstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minbleicstate::minbleicstate() : _minbleicstate_owner() ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minbleicstate::minbleicstate(const minbleicstate &rhs):_minbleicstate_owner(rhs) ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minbleicstate& minbleicstate::operator=(const minbleicstate &rhs) +{ + if( this==&rhs ) + return *this; + _minbleicstate_owner::operator=(rhs); + return *this; +} + +minbleicstate::~minbleicstate() +{ +} + + +/************************************************************************* +This structure stores optimization report: +* IterationsCount number of iterations +* NFEV number of gradient evaluations +* TerminationType termination type (see below) + +TERMINATION CODES + +TerminationType field contains completion code, which can be: + -7 gradient verification failed. + See MinBLEICSetGradientCheck() for more information. + -3 inconsistent constraints. Feasible point is + either nonexistent or too hard to find. Try to + restart optimizer with better initial approximation + 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, + X contains best point found so far. + +ADDITIONAL FIELDS + +There are additional fields which can be used for debugging: +* DebugEqErr error in the equality constraints (2-norm) +* DebugFS f, calculated at projection of initial point + to the feasible set +* DebugFF f, calculated at the final point +* DebugDX |X_start-X_final| +*************************************************************************/ +_minbleicreport_owner::_minbleicreport_owner() +{ + p_struct = (alglib_impl::minbleicreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minbleicreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minbleicreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minbleicreport_owner::_minbleicreport_owner(const _minbleicreport_owner &rhs) +{ + p_struct = (alglib_impl::minbleicreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minbleicreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minbleicreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minbleicreport_owner& _minbleicreport_owner::operator=(const _minbleicreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minbleicreport_clear(p_struct); + if( !alglib_impl::_minbleicreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minbleicreport_owner::~_minbleicreport_owner() +{ + alglib_impl::_minbleicreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minbleicreport* _minbleicreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minbleicreport* _minbleicreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minbleicreport::minbleicreport() : _minbleicreport_owner() ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype),debugeqerr(p_struct->debugeqerr),debugfs(p_struct->debugfs),debugff(p_struct->debugff),debugdx(p_struct->debugdx),debugfeasqpits(p_struct->debugfeasqpits),debugfeasgpaits(p_struct->debugfeasgpaits),inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount) +{ +} + +minbleicreport::minbleicreport(const minbleicreport &rhs):_minbleicreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype),debugeqerr(p_struct->debugeqerr),debugfs(p_struct->debugfs),debugff(p_struct->debugff),debugdx(p_struct->debugdx),debugfeasqpits(p_struct->debugfeasqpits),debugfeasgpaits(p_struct->debugfeasgpaits),inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount) +{ +} + +minbleicreport& minbleicreport::operator=(const minbleicreport &rhs) +{ + if( this==&rhs ) + return *this; + _minbleicreport_owner::operator=(rhs); + return *this; +} + +minbleicreport::~minbleicreport() +{ +} + +/************************************************************************* + BOUND CONSTRAINED OPTIMIZATION + WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments subject to any +combination of: +* bound constraints +* linear inequality constraints +* linear equality constraints + +REQUIREMENTS: +* user must provide function value and gradient +* starting point X0 must be feasible or + not too far away from the feasible set +* grad(f) must be Lipschitz continuous on a level set: + L = { x : f(x)<=f(x0) } +* function must be defined everywhere on the feasible set F + +USAGE: + +Constrained optimization if far more complex than the unconstrained one. +Here we give very brief outline of the BLEIC optimizer. We strongly recommend +you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on optimization, which is available at http://www.alglib.net/optimization/ + +1. User initializes algorithm state with MinBLEICCreate() call + +2. USer adds boundary and/or linear constraints by calling + MinBLEICSetBC() and MinBLEICSetLC() functions. + +3. User sets stopping conditions with MinBLEICSetCond(). + +4. User calls MinBLEICOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +5. User calls MinBLEICResults() to get solution + +6. Optionally user may call MinBLEICRestartFrom() to solve another problem + with same N but another starting point. + MinBLEICRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size ofX + X - starting point, array[N]: + * it is better to set X to a feasible point + * but X can be infeasible, in which case algorithm will try + to find feasible point first, using X as initial + approximation. + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreate(const ae_int_t n, const real_1d_array &x, minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleiccreate(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + BOUND CONSTRAINED OPTIMIZATION + WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments subject to any +combination of: +* bound constraints +* linear inequality constraints +* linear equality constraints + +REQUIREMENTS: +* user must provide function value and gradient +* starting point X0 must be feasible or + not too far away from the feasible set +* grad(f) must be Lipschitz continuous on a level set: + L = { x : f(x)<=f(x0) } +* function must be defined everywhere on the feasible set F + +USAGE: + +Constrained optimization if far more complex than the unconstrained one. +Here we give very brief outline of the BLEIC optimizer. We strongly recommend +you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on optimization, which is available at http://www.alglib.net/optimization/ + +1. User initializes algorithm state with MinBLEICCreate() call + +2. USer adds boundary and/or linear constraints by calling + MinBLEICSetBC() and MinBLEICSetLC() functions. + +3. User sets stopping conditions with MinBLEICSetCond(). + +4. User calls MinBLEICOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +5. User calls MinBLEICResults() to get solution + +6. Optionally user may call MinBLEICRestartFrom() to solve another problem + with same N but another starting point. + MinBLEICRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size ofX + X - starting point, array[N]: + * it is better to set X to a feasible point + * but X can be infeasible, in which case algorithm will try + to find feasible point first, using X as initial + approximation. + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreate(const real_1d_array &x, minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleiccreate(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinBLEICCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinBLEICCreate() in order to get +more information about creation of BLEIC optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinBLEICSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. CG needs exact gradient values. Imprecise + gradient may slow down convergence, especially on highly nonlinear + problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreatef(const ae_int_t n, const real_1d_array &x, const double diffstep, minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleiccreatef(n, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinBLEICCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinBLEICCreate() in order to get +more information about creation of BLEIC optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinBLEICSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. CG needs exact gradient values. Imprecise + gradient may slow down convergence, especially on highly nonlinear + problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreatef(const real_1d_array &x, const double diffstep, minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleiccreatef(n, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets boundary constraints for BLEIC optimizer. + +Boundary constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + If some (all) variables are unbounded, you may specify + very small number or -INF. + BndU - upper bounds, array[N]. + If some (all) variables are unbounded, you may specify + very large number or +INF. + +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: this solver has following useful properties: +* bound constraints are always satisfied exactly +* function is evaluated only INSIDE area specified by bound constraints, + even when numerical differentiation is used (algorithm adjusts nodes + according to boundary constraints) + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbc(const minbleicstate &state, const real_1d_array &bndl, const real_1d_array &bndu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetbc(const_cast(state.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear constraints for BLEIC optimizer. + +Linear constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetlc(const minbleicstate &state, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetlc(const_cast(state.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear constraints for BLEIC optimizer. + +Linear constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetlc(const minbleicstate &state, const real_2d_array &c, const integer_1d_array &ct) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t k; + if( (c.rows()!=ct.length())) + throw ap_error("Error while calling 'minbleicsetlc': looks like one of arguments has wrong size"); + k = c.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetlc(const_cast(state.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping conditions for the optimizer. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinBLEICSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection. + +NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver may perform + slightly more than MaxIts iterations. I.e., MaxIts sets non-strict + limit on iterations count. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetcond(const minbleicstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetcond(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets scaling coefficients for BLEIC 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the BLEIC too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinBLEICSetPrec...() +functions. + +There is a special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetscale(const minbleicstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdefault(const minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetprecdefault(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE 1: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdiag(const minbleicstate &state, const real_1d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetprecdiag(const_cast(state.c_ptr()), const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinBLEICSetScale() +call (before or after MinBLEICSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecscale(const minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetprecscale(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinBLEICOptimize(). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetxrep(const minbleicstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets maximum step length + +IMPORTANT: this feature is hard to combine with preconditioning. You can't +set upper limit on step length, when you solve optimization problem with +linear (non-boundary) constraints AND preconditioner turned on. + +When non-boundary constraints are present, you have to either a) use +preconditioner, or b) use upper limit on step length. YOU CAN'T USE BOTH! +In this case algorithm will terminate with appropriate error code. + +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 lead 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetstpmax(const minbleicstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minbleiciteration(const minbleicstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::minbleiciteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minbleicoptimize(minbleicstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'minbleicoptimize()' (func is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minbleiciteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minbleicoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minbleicoptimize(minbleicstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'minbleicoptimize()' (grad is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minbleiciteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minbleicoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +BLEIC results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one: + * -7 gradient verification failed. + See MinBLEICSetGradientCheck() for more information. + * -3 inconsistent constraints. Feasible point is + either nonexistent or too hard to find. Try to + restart optimizer with better initial approximation + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + More information about fields of this structure can be + found in the comments on MinBLEICReport datatype. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresults(const minbleicstate &state, real_1d_array &x, minbleicreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +BLEIC results + +Buffered implementation of MinBLEICResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresultsbuf(const minbleicstate &state, real_1d_array &x, minbleicreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine restarts algorithm from new point. +All optimization parameters (including constraints) are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + X - new starting point. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicrestartfrom(const minbleicstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicrestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinBLEICOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinBLEICSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetgradientcheck(const minbleicstate &state, const double teststep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetgradientcheck(const_cast(state.c_ptr()), teststep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_minlbfgsstate_owner::_minlbfgsstate_owner() +{ + p_struct = (alglib_impl::minlbfgsstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlbfgsstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlbfgsstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlbfgsstate_owner::_minlbfgsstate_owner(const _minlbfgsstate_owner &rhs) +{ + p_struct = (alglib_impl::minlbfgsstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlbfgsstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlbfgsstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlbfgsstate_owner& _minlbfgsstate_owner::operator=(const _minlbfgsstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minlbfgsstate_clear(p_struct); + if( !alglib_impl::_minlbfgsstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minlbfgsstate_owner::~_minlbfgsstate_owner() +{ + alglib_impl::_minlbfgsstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minlbfgsstate* _minlbfgsstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minlbfgsstate* _minlbfgsstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minlbfgsstate::minlbfgsstate() : _minlbfgsstate_owner() ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minlbfgsstate::minlbfgsstate(const minlbfgsstate &rhs):_minlbfgsstate_owner(rhs) ,needf(p_struct->needf),needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minlbfgsstate& minlbfgsstate::operator=(const minlbfgsstate &rhs) +{ + if( this==&rhs ) + return *this; + _minlbfgsstate_owner::operator=(rhs); + return *this; +} + +minlbfgsstate::~minlbfgsstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_minlbfgsreport_owner::_minlbfgsreport_owner() +{ + p_struct = (alglib_impl::minlbfgsreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlbfgsreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlbfgsreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlbfgsreport_owner::_minlbfgsreport_owner(const _minlbfgsreport_owner &rhs) +{ + p_struct = (alglib_impl::minlbfgsreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlbfgsreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlbfgsreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlbfgsreport_owner& _minlbfgsreport_owner::operator=(const _minlbfgsreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minlbfgsreport_clear(p_struct); + if( !alglib_impl::_minlbfgsreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minlbfgsreport_owner::~_minlbfgsreport_owner() +{ + alglib_impl::_minlbfgsreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minlbfgsreport* _minlbfgsreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minlbfgsreport* _minlbfgsreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minlbfgsreport::minlbfgsreport() : _minlbfgsreport_owner() ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype) +{ +} + +minlbfgsreport::minlbfgsreport(const minlbfgsreport &rhs):_minlbfgsreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),varidx(p_struct->varidx),terminationtype(p_struct->terminationtype) +{ +} + +minlbfgsreport& minlbfgsreport::operator=(const minlbfgsreport &rhs) +{ + if( this==&rhs ) + return *this; + _minlbfgsreport_owner::operator=(rhs); + return *this; +} + +minlbfgsreport::~minlbfgsreport() +{ +} + +/************************************************************************* + LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using a quasi- +Newton method (LBFGS scheme) which is optimized to use a minimum amount +of memory. +The subroutine generates the approximation of an inverse Hessian matrix by +using information about the last M steps of the algorithm (instead of N). +It lessens a required amount of memory from a value of order N^2 to a +value of order 2*N*M. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinLBFGSCreate() call +2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax() + and other functions +3. User calls MinLBFGSOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinLBFGSResults() to get solution +5. Optionally user may call MinLBFGSRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLBFGSRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension. N>0 + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - initial solution approximation, array[0..N-1]. + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with MinLBFGSSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLBFGSSetStpMax() function to bound algorithm's steps. However, + L-BFGS rarely needs such a tuning. + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreate(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgscreate(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using a quasi- +Newton method (LBFGS scheme) which is optimized to use a minimum amount +of memory. +The subroutine generates the approximation of an inverse Hessian matrix by +using information about the last M steps of the algorithm (instead of N). +It lessens a required amount of memory from a value of order N^2 to a +value of order 2*N*M. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinLBFGSCreate() call +2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax() + and other functions +3. User calls MinLBFGSOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinLBFGSResults() to get solution +5. Optionally user may call MinLBFGSRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLBFGSRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension. N>0 + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - initial solution approximation, array[0..N-1]. + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with MinLBFGSSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLBFGSSetStpMax() function to bound algorithm's steps. However, + L-BFGS rarely needs such a tuning. + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreate(const ae_int_t m, const real_1d_array &x, minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgscreate(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinLBFGSCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinLBFGSCreate() in order to get +more information about creation of LBFGS optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinLBFGSSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. LBFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreatef(const ae_int_t n, const ae_int_t m, const real_1d_array &x, const double diffstep, minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgscreatef(n, m, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +The subroutine is finite difference variant of MinLBFGSCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinLBFGSCreate() in order to get +more information about creation of LBFGS optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinLBFGSSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. LBFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreatef(const ae_int_t m, const real_1d_array &x, const double diffstep, minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgscreatef(n, m, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping conditions for L-BFGS optimization algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLBFGSSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcond(const minlbfgsstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetcond(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLBFGSOptimize(). + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetxrep(const minlbfgsstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets maximum step length + +INPUT PARAMETERS: + State - structure which stores algorithm state + StpMax - maximum step length, >=0. Set StpMax to 0.0 (default), 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetstpmax(const minlbfgsstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets scaling coefficients for LBFGS 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the LBFGS too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinLBFGSSetPrec...() +functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetscale(const minlbfgsstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: default preconditioner (simple +scaling, same for all elements of X) is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdefault(const minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetprecdefault(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: Cholesky factorization of approximate +Hessian is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + P - triangular preconditioner, Cholesky factorization of + the approximate Hessian. array[0..N-1,0..N-1], + (if larger, only leading N elements are used). + IsUpper - whether upper or lower triangle of P is given + (other triangle is not referenced) + +After call to this function preconditioner is changed to P (P is copied +into the internal buffer). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: P should be nonsingular. Exception will be thrown otherwise. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetpreccholesky(const minlbfgsstate &state, const real_2d_array &p, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetpreccholesky(const_cast(state.c_ptr()), const_cast(p.c_ptr()), isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdiag(const minlbfgsstate &state, const real_1d_array &d) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetprecdiag(const_cast(state.c_ptr()), const_cast(d.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinLBFGSSetScale() +call (before or after MinLBFGSSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecscale(const minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetprecscale(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minlbfgsiteration(const minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::minlbfgsiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlbfgsoptimize(minlbfgsstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'minlbfgsoptimize()' (func is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlbfgsiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlbfgsoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlbfgsoptimize(minlbfgsstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'minlbfgsoptimize()' (grad is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlbfgsiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlbfgsoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +L-BFGS algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinLBFGSSetGradientCheck() for more information. + * -2 rounding errors prevent further improvement. + X contains best point found. + * -1 incorrect parameters were specified + * 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 + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresults(const minlbfgsstate &state, real_1d_array &x, minlbfgsreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgsresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +L-BFGS algorithm results + +Buffered implementation of MinLBFGSResults which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresultsbuf(const minlbfgsstate &state, real_1d_array &x, minlbfgsreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgsresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine restarts LBFGS algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsrestartfrom(const minlbfgsstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgsrestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLBFGSOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLBFGSSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 24.05.2012 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetgradientcheck(const minlbfgsstate &state, const double teststep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetgradientcheck(const_cast(state.c_ptr()), teststep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This object stores nonlinear optimizer state. +You should use functions provided by MinQP subpackage to work with this +object +*************************************************************************/ +_minqpstate_owner::_minqpstate_owner() +{ + p_struct = (alglib_impl::minqpstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minqpstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minqpstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minqpstate_owner::_minqpstate_owner(const _minqpstate_owner &rhs) +{ + p_struct = (alglib_impl::minqpstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minqpstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minqpstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minqpstate_owner& _minqpstate_owner::operator=(const _minqpstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minqpstate_clear(p_struct); + if( !alglib_impl::_minqpstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minqpstate_owner::~_minqpstate_owner() +{ + alglib_impl::_minqpstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minqpstate* _minqpstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minqpstate* _minqpstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minqpstate::minqpstate() : _minqpstate_owner() +{ +} + +minqpstate::minqpstate(const minqpstate &rhs):_minqpstate_owner(rhs) +{ +} + +minqpstate& minqpstate::operator=(const minqpstate &rhs) +{ + if( this==&rhs ) + return *this; + _minqpstate_owner::operator=(rhs); + return *this; +} + +minqpstate::~minqpstate() +{ +} + + +/************************************************************************* +This structure stores optimization report: +* InnerIterationsCount number of inner iterations +* OuterIterationsCount number of outer iterations +* NCholesky number of Cholesky decomposition +* NMV number of matrix-vector products + (only products calculated as part of iterative + process are counted) +* TerminationType completion code (see below) + +Completion codes: +* -5 inappropriate solver was used: + * Cholesky solver for semidefinite or indefinite problems + * Cholesky solver for problems with non-boundary constraints +* -4 BLEIC-QP algorithm found unconstrained direction + of negative curvature (function is unbounded from + below even under constraints), no meaningful + minimum can be found. +* -3 inconsistent constraints (or, maybe, feasible point is + too hard to find). If you are sure that constraints are feasible, + try to restart optimizer with better initial approximation. +* -1 solver error +* 4 successful completion +* 5 MaxIts steps was taken +* 7 stopping conditions are too stringent, + further improvement is impossible, + X contains best point found so far. +*************************************************************************/ +_minqpreport_owner::_minqpreport_owner() +{ + p_struct = (alglib_impl::minqpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minqpreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minqpreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minqpreport_owner::_minqpreport_owner(const _minqpreport_owner &rhs) +{ + p_struct = (alglib_impl::minqpreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minqpreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minqpreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minqpreport_owner& _minqpreport_owner::operator=(const _minqpreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minqpreport_clear(p_struct); + if( !alglib_impl::_minqpreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minqpreport_owner::~_minqpreport_owner() +{ + alglib_impl::_minqpreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minqpreport* _minqpreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minqpreport* _minqpreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minqpreport::minqpreport() : _minqpreport_owner() ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nmv(p_struct->nmv),ncholesky(p_struct->ncholesky),terminationtype(p_struct->terminationtype) +{ +} + +minqpreport::minqpreport(const minqpreport &rhs):_minqpreport_owner(rhs) ,inneriterationscount(p_struct->inneriterationscount),outeriterationscount(p_struct->outeriterationscount),nmv(p_struct->nmv),ncholesky(p_struct->ncholesky),terminationtype(p_struct->terminationtype) +{ +} + +minqpreport& minqpreport::operator=(const minqpreport &rhs) +{ + if( this==&rhs ) + return *this; + _minqpreport_owner::operator=(rhs); + return *this; +} + +minqpreport::~minqpreport() +{ +} + +/************************************************************************* + CONSTRAINED QUADRATIC PROGRAMMING + +The subroutine creates QP optimizer. After initial creation, it contains +default optimization problem with zero quadratic and linear terms and no +constraints. You should set quadratic/linear terms with calls to functions +provided by MinQP subpackage. + +INPUT PARAMETERS: + N - problem size + +OUTPUT PARAMETERS: + State - optimizer with zero quadratic/linear terms + and no constraints + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpcreate(const ae_int_t n, minqpstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpcreate(n, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear term for QP solver. + +By default, linear term is zero. + +INPUT PARAMETERS: + State - structure which stores algorithm state + B - linear term, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlinearterm(const minqpstate &state, const real_1d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetlinearterm(const_cast(state.c_ptr()), const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets dense quadratic term for QP solver. By default, +quadratic term is zero. + +SUPPORT BY ALGLIB QP ALGORITHMS: + +Dense quadratic term can be handled by any of the QP algorithms supported +by ALGLIB QP Solver. + +IMPORTANT: + +This solver minimizes following function: + f(x) = 0.5*x'*A*x + b'*x. +Note that quadratic term has 0.5 before it. So if you want to minimize + f(x) = x^2 + x +you should rewrite your problem as follows: + f(x) = 0.5*(2*x^2) + x +and your matrix A will be equal to [[2.0]], not to [[1.0]] + +INPUT PARAMETERS: + State - structure which stores algorithm state + A - matrix, array[N,N] + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used + * if not given, both lower and upper triangles must be + filled. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadraticterm(const minqpstate &state, const real_2d_array &a, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetquadraticterm(const_cast(state.c_ptr()), const_cast(a.c_ptr()), isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets dense quadratic term for QP solver. By default, +quadratic term is zero. + +SUPPORT BY ALGLIB QP ALGORITHMS: + +Dense quadratic term can be handled by any of the QP algorithms supported +by ALGLIB QP Solver. + +IMPORTANT: + +This solver minimizes following function: + f(x) = 0.5*x'*A*x + b'*x. +Note that quadratic term has 0.5 before it. So if you want to minimize + f(x) = x^2 + x +you should rewrite your problem as follows: + f(x) = 0.5*(2*x^2) + x +and your matrix A will be equal to [[2.0]], not to [[1.0]] + +INPUT PARAMETERS: + State - structure which stores algorithm state + A - matrix, array[N,N] + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used + * if not given, both lower and upper triangles must be + filled. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadraticterm(const minqpstate &state, const real_2d_array &a) +{ + alglib_impl::ae_state _alglib_env_state; + bool isupper; + if( !alglib_impl::ae_is_symmetric(const_cast(a.c_ptr())) ) + throw ap_error("'a' parameter is not symmetric matrix"); + isupper = false; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetquadraticterm(const_cast(state.c_ptr()), const_cast(a.c_ptr()), isupper, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets sparse quadratic term for QP solver. By default, +quadratic term is zero. + +SUPPORT BY ALGLIB QP ALGORITHMS: + +Sparse quadratic term is supported only by BLEIC-based QP algorithm (one +which is activated by MinQPSetAlgoBLEIC function). Cholesky-based QP algo +won't be able to deal with sparse quadratic term and will terminate +abnormally. + +IF YOU CALLED THIS FUNCTION, YOU MUST SWITCH TO BLEIC-BASED QP ALGORITHM +BEFORE CALLING MINQPOPTIMIZE() FUNCTION. + +IMPORTANT: + +This solver minimizes following function: + f(x) = 0.5*x'*A*x + b'*x. +Note that quadratic term has 0.5 before it. So if you want to minimize + f(x) = x^2 + x +you should rewrite your problem as follows: + f(x) = 0.5*(2*x^2) + x +and your matrix A will be equal to [[2.0]], not to [[1.0]] + +INPUT PARAMETERS: + State - structure which stores algorithm state + A - matrix, array[N,N] + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used + * if not given, both lower and upper triangles must be + filled. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadratictermsparse(const minqpstate &state, const sparsematrix &a, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetquadratictermsparse(const_cast(state.c_ptr()), const_cast(a.c_ptr()), isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets starting point for QP solver. It is useful to have +good initial approximation to the solution, because it will increase +speed of convergence and identification of active constraints. + +INPUT PARAMETERS: + State - structure which stores algorithm state + X - starting point, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetstartingpoint(const minqpstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetstartingpoint(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets origin for QP solver. By default, following QP program +is solved: + + min(0.5*x'*A*x+b'*x) + +This function allows to solve different problem: + + min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) + +INPUT PARAMETERS: + State - structure which stores algorithm state + XOrigin - origin, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetorigin(const minqpstate &state, const real_1d_array &xorigin) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetorigin(const_cast(state.c_ptr()), const_cast(xorigin.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets scaling coefficients. + +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 + +BLEIC-based QP solver uses scale for two purposes: +* to evaluate stopping conditions +* for preconditioning of the underlying BLEIC solver + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetscale(const minqpstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function tells solver to use Cholesky-based algorithm. This algorithm +is active by default. + +DESCRIPTION: + +Cholesky-based algorithm can be used only for problems which: +* have dense quadratic term, set by MinQPSetQuadraticTerm(), sparse or + structured problems are not supported. +* are strictly convex, i.e. quadratic term is symmetric positive definite, + indefinite or semidefinite problems are not supported by this algorithm. + +If anything of what listed above is violated, you may use BLEIC-based QP +algorithm which can be activated by MinQPSetAlgoBLEIC(). + +BENEFITS AND DRAWBACKS: + +This algorithm gives best precision amongst all QP solvers provided by +ALGLIB (Newton iterations have much higher precision than any other +optimization algorithm). This solver also gracefully handles problems with +very large amount of constraints. + +Performance of the algorithm is good because internally it uses Level 3 +Dense BLAS for its performance-critical parts. + + +From the other side, algorithm has O(N^3) complexity for unconstrained +problems and up to orders of magnitude slower on constrained problems +(these additional iterations are needed to identify active constraints). +So, its running time depends on number of constraints active at solution. + +Furthermore, this algorithm can not solve problems with sparse matrices or +problems with semidefinite/indefinite matrices of any kind (dense/sparse). + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgocholesky(const minqpstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetalgocholesky(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function tells solver to use BLEIC-based algorithm and sets stopping +criteria for the algorithm. + +DESCRIPTION: + +BLEIC-based QP algorithm can be used for any kind of QP problems: +* problems with both dense and sparse quadratic terms +* problems with positive definite, semidefinite, indefinite terms + +BLEIC-based algorithm can solve even indefinite problems - as long as they +are bounded from below on the feasible set. Of course, global minimum is +found only for positive definite and semidefinite problems. As for +indefinite ones - only local minimum is found. + +BENEFITS AND DRAWBACKS: + +This algorithm can be used to solve both convex and indefinite QP problems +and it can utilize sparsity of the quadratic term (algorithm calculates +matrix-vector products, which can be performed efficiently in case of +sparse matrix). + +Algorithm has iteration cost, which (assuming fixed amount of non-boundary +linear constraints) linearly depends on problem size. Boundary constraints +does not significantly change iteration cost. + +Thus, it outperforms Cholesky-based QP algorithm (CQP) on high-dimensional +sparse problems with moderate amount of constraints. + + +From the other side, unlike CQP solver, this algorithm does NOT make use +of Level 3 Dense BLAS. Thus, its performance on dense problems is inferior +to that of CQP solver. + +Its precision is also inferior to that of CQP. CQP performs Newton steps +which are know to achieve very good precision. In many cases Newton step +leads us exactly to the solution. BLEIC-QP performs LBFGS steps, which are +good at detecting neighborhood of the solution, buy need many iterations +to find solution with 6 digits of precision. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + EpsX - >=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: + * |.| means Euclidian norm + * v - scaled step vector, v[i]=dx[i]/s[i] + * dx - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinQPSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection (presently it is small step +length, but it may change in the future versions of ALGLIB). + +IT IS VERY IMPORTANT THAT YOU CALL MinQPSetScale() WHEN YOU USE THIS ALGO! + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgobleic(const minqpstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetalgobleic(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets boundary constraints for QP solver + +Boundary constraints are inactive by default (after initial creation). +After being set, they are preserved until explicitly turned off with +another SetBC() call. + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + 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[N]. + 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: 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]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetbc(const minqpstate &state, const real_1d_array &bndl, const real_1d_array &bndu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetbc(const_cast(state.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear constraints for QP optimizer. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - structure previously allocated with MinQPCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately - + there always exists some minor violation (about 10^-10...10^-13) + due to numerical errors. + + -- ALGLIB -- + Copyright 19.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlc(const minqpstate &state, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetlc(const_cast(state.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets linear constraints for QP optimizer. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - structure previously allocated with MinQPCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately - + there always exists some minor violation (about 10^-10...10^-13) + due to numerical errors. + + -- ALGLIB -- + Copyright 19.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlc(const minqpstate &state, const real_2d_array &c, const integer_1d_array &ct) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t k; + if( (c.rows()!=ct.length())) + throw ap_error("Error while calling 'minqpsetlc': looks like one of arguments has wrong size"); + k = c.rows(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpsetlc(const_cast(state.c_ptr()), const_cast(c.c_ptr()), const_cast(ct.c_ptr()), k, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function solves quadratic programming problem. +You should call it after setting solver options with MinQPSet...() calls. + +INPUT PARAMETERS: + State - algorithm state + +You should use MinQPResults() function to access results after calls +to this function. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey. + Special thanks to Elvira Illarionova for important suggestions on + the linearly constrained QP algorithm. +*************************************************************************/ +void minqpoptimize(const minqpstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpoptimize(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +QP solver results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution. + This array is allocated and initialized only when + Rep.TerminationType parameter is positive (success). + Rep - optimization report. You should check Rep.TerminationType, + which contains completion code, and you may check another + fields which contain another information about algorithm + functioning. + + Failure codes returned by algorithm are: + * -5 inappropriate solver was used: + * Cholesky solver for (semi)indefinite problems + * Cholesky solver for problems with sparse matrix + * -4 BLEIC-QP algorithm found unconstrained direction + of negative curvature (function is unbounded from + below even under constraints), no meaningful + minimum can be found. + * -3 inconsistent constraints (or maybe feasible point + is too hard to find). If you are sure that + constraints are feasible, try to restart optimizer + with better initial approximation. + + Completion codes specific for Cholesky algorithm: + * 4 successful completion + + Completion codes specific for BLEIC-based algorithm: + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresults(const minqpstate &state, real_1d_array &x, minqpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +QP results + +Buffered implementation of MinQPResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresultsbuf(const minqpstate &state, real_1d_array &x, minqpreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minqpresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Levenberg-Marquardt optimizer. + +This structure should be created using one of the MinLMCreate???() +functions. You should not access its fields directly; use ALGLIB functions +to work with it. +*************************************************************************/ +_minlmstate_owner::_minlmstate_owner() +{ + p_struct = (alglib_impl::minlmstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlmstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlmstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlmstate_owner::_minlmstate_owner(const _minlmstate_owner &rhs) +{ + p_struct = (alglib_impl::minlmstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlmstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlmstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlmstate_owner& _minlmstate_owner::operator=(const _minlmstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minlmstate_clear(p_struct); + if( !alglib_impl::_minlmstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minlmstate_owner::~_minlmstate_owner() +{ + alglib_impl::_minlmstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minlmstate* _minlmstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minlmstate* _minlmstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minlmstate::minlmstate() : _minlmstate_owner() ,needf(p_struct->needf),needfg(p_struct->needfg),needfgh(p_struct->needfgh),needfi(p_struct->needfi),needfij(p_struct->needfij),xupdated(p_struct->xupdated),f(p_struct->f),fi(&p_struct->fi),g(&p_struct->g),h(&p_struct->h),j(&p_struct->j),x(&p_struct->x) +{ +} + +minlmstate::minlmstate(const minlmstate &rhs):_minlmstate_owner(rhs) ,needf(p_struct->needf),needfg(p_struct->needfg),needfgh(p_struct->needfgh),needfi(p_struct->needfi),needfij(p_struct->needfij),xupdated(p_struct->xupdated),f(p_struct->f),fi(&p_struct->fi),g(&p_struct->g),h(&p_struct->h),j(&p_struct->j),x(&p_struct->x) +{ +} + +minlmstate& minlmstate::operator=(const minlmstate &rhs) +{ + if( this==&rhs ) + return *this; + _minlmstate_owner::operator=(rhs); + return *this; +} + +minlmstate::~minlmstate() +{ +} + + +/************************************************************************* +Optimization report, filled by MinLMResults() function + +FIELDS: +* TerminationType, completetion code: + * -7 derivative correctness check failed; + see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for + more information. + * 1 relative function improvement is no more than + EpsF. + * 2 relative step is no more than EpsX. + * 4 gradient is no more than EpsG. + * 5 MaxIts steps was taken + * 7 stopping conditions are too stringent, + further improvement is impossible +* IterationsCount, contains iterations count +* NFunc, number of function calculations +* NJac, number of Jacobi matrix calculations +* NGrad, number of gradient calculations +* NHess, number of Hessian calculations +* NCholesky, number of Cholesky decomposition calculations +*************************************************************************/ +_minlmreport_owner::_minlmreport_owner() +{ + p_struct = (alglib_impl::minlmreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlmreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlmreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlmreport_owner::_minlmreport_owner(const _minlmreport_owner &rhs) +{ + p_struct = (alglib_impl::minlmreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minlmreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minlmreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minlmreport_owner& _minlmreport_owner::operator=(const _minlmreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minlmreport_clear(p_struct); + if( !alglib_impl::_minlmreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minlmreport_owner::~_minlmreport_owner() +{ + alglib_impl::_minlmreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minlmreport* _minlmreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minlmreport* _minlmreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minlmreport::minlmreport() : _minlmreport_owner() ,iterationscount(p_struct->iterationscount),terminationtype(p_struct->terminationtype),funcidx(p_struct->funcidx),varidx(p_struct->varidx),nfunc(p_struct->nfunc),njac(p_struct->njac),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky) +{ +} + +minlmreport::minlmreport(const minlmreport &rhs):_minlmreport_owner(rhs) ,iterationscount(p_struct->iterationscount),terminationtype(p_struct->terminationtype),funcidx(p_struct->funcidx),varidx(p_struct->varidx),nfunc(p_struct->nfunc),njac(p_struct->njac),ngrad(p_struct->ngrad),nhess(p_struct->nhess),ncholesky(p_struct->ncholesky) +{ +} + +minlmreport& minlmreport::operator=(const minlmreport &rhs) +{ + if( this==&rhs ) + return *this; + _minlmreport_owner::operator=(rhs); + return *this; +} + +minlmreport::~minlmreport() +{ +} + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] and Jacobian of f[]. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function vector f[] at given point X +* function vector f[] and Jacobian of f[] (simultaneously) at given point + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() and jac() callbacks. +First one is used to calculate f[] at given point, second one calculates +f[] and Jacobian df[i]/dx[j]. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not provide Jacobian), but it +will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateVJ() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatevj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] and Jacobian of f[]. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function vector f[] at given point X +* function vector f[] and Jacobian of f[] (simultaneously) at given point + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() and jac() callbacks. +First one is used to calculate f[] at given point, second one calculates +f[] and Jacobian df[i]/dx[j]. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not provide Jacobian), but it +will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateVJ() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevj(const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatevj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] only. Finite differences are used to +calculate Jacobian. + + +REQUIREMENTS: +This algorithm will request following information during its operation: +* function vector f[] at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() callback. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not accept function vector), but +it will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateV() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +See also MinLMIteration, MinLMResults. + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatev(const ae_int_t n, const ae_int_t m, const real_1d_array &x, const double diffstep, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatev(n, m, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] only. Finite differences are used to +calculate Jacobian. + + +REQUIREMENTS: +This algorithm will request following information during its operation: +* function vector f[] at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() callback. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not accept function vector), but +it will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateV() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +See also MinLMIteration, MinLMResults. + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatev(const ae_int_t m, const real_1d_array &x, const double diffstep, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatev(n, m, const_cast(x.c_ptr()), diffstep, const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of general form (not "sum-of- +-squares") function + F = F(x[0], ..., x[n-1]) +using its gradient and Hessian. Levenberg-Marquardt modification with +L-BFGS pre-optimization and internal pre-conditioned L-BFGS optimization +after each Levenberg-Marquardt step is used. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function value F at given point X +* F and gradient G (simultaneously) at given point X +* F, G and Hessian H (simultaneously) at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts func(), grad() and hess() +function pointers. First pointer is used to calculate F at given point, +second one calculates F(x) and grad F(x), third one calculates F(x), +grad F(x), hess F(x). + +You can try to initialize MinLMState structure with FGH-function and then +use incorrect version of MinLMOptimize() (for example, version which does +not provide Hessian matrix), but it will lead to exception being thrown +after first attempt to calculate Hessian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateFGH() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + pointers (delegates, etc.) to callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgh(const ae_int_t n, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefgh(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of general form (not "sum-of- +-squares") function + F = F(x[0], ..., x[n-1]) +using its gradient and Hessian. Levenberg-Marquardt modification with +L-BFGS pre-optimization and internal pre-conditioned L-BFGS optimization +after each Levenberg-Marquardt step is used. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function value F at given point X +* F and gradient G (simultaneously) at given point X +* F, G and Hessian H (simultaneously) at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts func(), grad() and hess() +function pointers. First pointer is used to calculate F at given point, +second one calculates F(x) and grad F(x), third one calculates F(x), +grad F(x), hess F(x). + +You can try to initialize MinLMState structure with FGH-function and then +use incorrect version of MinLMOptimize() (for example, version which does +not provide Hessian matrix), but it will lead to exception being thrown +after first attempt to calculate Hessian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateFGH() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + pointers (delegates, etc.) to callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgh(const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefgh(n, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping conditions for Levenberg-Marquardt optimization +algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLMSetScale() + 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). + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetcond(const minlmstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetcond(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLMOptimize(). Both Levenberg-Marquardt and internal L-BFGS +iterations are reported. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetxrep(const minlmstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetstpmax(const minlmstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets scaling coefficients for LM 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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetscale(const minlmstate &state, const real_1d_array &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetscale(const_cast(state.c_ptr()), const_cast(s.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets boundary constraints for LM 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[N]. + 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[N]. + 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: this solver has following useful properties: +* bound constraints are always satisfied exactly +* function is evaluated only INSIDE area specified by bound constraints + or at its boundary + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetbc(const minlmstate &state, const real_1d_array &bndl, const real_1d_array &bndu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetbc(const_cast(state.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is used to change acceleration settings + +You can choose between three acceleration strategies: +* AccType=0, no acceleration. +* AccType=1, secant updates are used to update quadratic model after each + iteration. After fixed number of iterations (or after model breakdown) + we recalculate quadratic model using analytic Jacobian or finite + differences. Number of secant-based iterations depends on optimization + settings: about 3 iterations - when we have analytic Jacobian, up to 2*N + iterations - when we use finite differences to calculate Jacobian. + +AccType=1 is recommended when Jacobian calculation cost is prohibitive +high (several Mx1 function vector calculations followed by several NxN +Cholesky factorizations are faster than calculation of one M*N Jacobian). +It should also be used when we have no Jacobian, because finite difference +approximation takes too much time to compute. + +Table below list optimization protocols (XYZ protocol corresponds to +MinLMCreateXYZ) and acceleration types they support (and use by default). + +ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS: + +protocol 0 1 comment +V + + +VJ + + +FGH + + +DAFAULT VALUES: + +protocol 0 1 comment +V x without acceleration it is so slooooooooow +VJ x +FGH x + +NOTE: this function should be called before optimization. Attempt to call +it during algorithm iterations may result in unexpected behavior. + +NOTE: attempt to call this function with unsupported protocol/acceleration +combination will result in exception being thrown. + + -- ALGLIB -- + Copyright 14.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetacctype(const minlmstate &state, const ae_int_t acctype) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetacctype(const_cast(state.c_ptr()), acctype, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minlmiteration(const minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::minlmiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlmoptimize(minlmstate &state, + void (*fvec)(const real_1d_array &x, real_1d_array &fi, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( fvec==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (fvec is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlmiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfi ) + { + fvec(state.x, state.fi, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlmoptimize(minlmstate &state, + void (*fvec)(const real_1d_array &x, real_1d_array &fi, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( fvec==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (fvec is NULL)"); + if( jac==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (jac is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlmiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfi ) + { + fvec(state.x, state.fi, ptr); + continue; + } + if( state.needfij ) + { + jac(state.x, state.fi, state.j, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*hess)(const real_1d_array &x, double &func, real_1d_array &grad, real_2d_array &hess, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (func is NULL)"); + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (grad is NULL)"); + if( hess==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (hess is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlmiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.needfgh ) + { + hess(state.x, state.f, state.g, state.h, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (func is NULL)"); + if( jac==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (jac is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlmiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.needfij ) + { + jac(state.x, state.fi, state.j, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (func is NULL)"); + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (grad is NULL)"); + if( jac==NULL ) + throw ap_error("ALGLIB: error in 'minlmoptimize()' (jac is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minlmiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.needfij ) + { + jac(state.x, state.fi, state.j, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minlmoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +Levenberg-Marquardt algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report; + see comments for this structure for more info. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresults(const minlmstate &state, real_1d_array &x, minlmreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Levenberg-Marquardt algorithm results + +Buffered implementation of MinLMResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresultsbuf(const minlmstate &state, real_1d_array &x, minlmreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine restarts LM algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinLMCreateXXX call. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmrestartfrom(const minlmstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmrestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevgj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatevgj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevgj(const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatevgj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefgj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgj(const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefgj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is considered obsolete since ALGLIB 3.1.0 and is present for +backward compatibility only. We recommend to use MinLMCreateVJ, which +provides similar, but more consistent and feature-rich interface. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function is considered obsolete since ALGLIB 3.1.0 and is present for +backward compatibility only. We recommend to use MinLMCreateVJ, which +provides similar, but more consistent and feature-rich interface. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefj(const ae_int_t m, const real_1d_array &x, minlmstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmcreatefj(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLMOptimize() is called +* prior to actual optimization, for each function Fi and each component + of parameters being optimized X[j] algorithm performs following steps: + * two trial steps are made to X[j]-TestStep*S[j] and X[j]+TestStep*S[j], + where X[j] is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * Fi(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative, + Rep.FuncIdx is set to index of the function. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) Jacobian evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided + by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLMSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minlmsetgradientcheck(const minlmstate &state, const double teststep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlmsetgradientcheck(const_cast(state.c_ptr()), teststep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_minasastate_owner::_minasastate_owner() +{ + p_struct = (alglib_impl::minasastate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minasastate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minasastate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minasastate_owner::_minasastate_owner(const _minasastate_owner &rhs) +{ + p_struct = (alglib_impl::minasastate*)alglib_impl::ae_malloc(sizeof(alglib_impl::minasastate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minasastate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minasastate_owner& _minasastate_owner::operator=(const _minasastate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minasastate_clear(p_struct); + if( !alglib_impl::_minasastate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minasastate_owner::~_minasastate_owner() +{ + alglib_impl::_minasastate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minasastate* _minasastate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minasastate* _minasastate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minasastate::minasastate() : _minasastate_owner() ,needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minasastate::minasastate(const minasastate &rhs):_minasastate_owner(rhs) ,needfg(p_struct->needfg),xupdated(p_struct->xupdated),f(p_struct->f),g(&p_struct->g),x(&p_struct->x) +{ +} + +minasastate& minasastate::operator=(const minasastate &rhs) +{ + if( this==&rhs ) + return *this; + _minasastate_owner::operator=(rhs); + return *this; +} + +minasastate::~minasastate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_minasareport_owner::_minasareport_owner() +{ + p_struct = (alglib_impl::minasareport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minasareport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minasareport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minasareport_owner::_minasareport_owner(const _minasareport_owner &rhs) +{ + p_struct = (alglib_impl::minasareport*)alglib_impl::ae_malloc(sizeof(alglib_impl::minasareport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_minasareport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_minasareport_owner& _minasareport_owner::operator=(const _minasareport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_minasareport_clear(p_struct); + if( !alglib_impl::_minasareport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_minasareport_owner::~_minasareport_owner() +{ + alglib_impl::_minasareport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::minasareport* _minasareport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::minasareport* _minasareport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +minasareport::minasareport() : _minasareport_owner() ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype),activeconstraints(p_struct->activeconstraints) +{ +} + +minasareport::minasareport(const minasareport &rhs):_minasareport_owner(rhs) ,iterationscount(p_struct->iterationscount),nfev(p_struct->nfev),terminationtype(p_struct->terminationtype),activeconstraints(p_struct->activeconstraints) +{ +} + +minasareport& minasareport::operator=(const minasareport &rhs) +{ + if( this==&rhs ) + return *this; + _minasareport_owner::operator=(rhs); + return *this; +} + +minasareport::~minasareport() +{ +} + +/************************************************************************* +Obsolete function, use MinLBFGSSetPrecDefault() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetdefaultpreconditioner(const minlbfgsstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetdefaultpreconditioner(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcholeskypreconditioner(const minlbfgsstate &state, const real_2d_array &p, const bool isupper) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minlbfgssetcholeskypreconditioner(const_cast(state.c_ptr()), const_cast(p.c_ptr()), isupper, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierwidth(const minbleicstate &state, const double mu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetbarrierwidth(const_cast(state.c_ptr()), mu, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierdecay(const minbleicstate &state, const double mudecay) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minbleicsetbarrierdecay(const_cast(state.c_ptr()), mudecay, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void minasacreate(const ae_int_t n, const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu, minasastate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasacreate(n, const_cast(x.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void minasacreate(const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu, minasastate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=bndl.length()) || (x.length()!=bndu.length())) + throw ap_error("Error while calling 'minasacreate': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasacreate(n, const_cast(x.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetcond(const minasastate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasasetcond(const_cast(state.c_ptr()), epsg, epsf, epsx, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetxrep(const minasastate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasasetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetalgorithm(const minasastate &state, const ae_int_t algotype) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasasetalgorithm(const_cast(state.c_ptr()), algotype, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetstpmax(const minasastate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasasetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minasaiteration(const minasastate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::minasaiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void minasaoptimize(minasastate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( grad==NULL ) + throw ap_error("ALGLIB: error in 'minasaoptimize()' (grad is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::minasaiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needfg ) + { + grad(state.x, state.f, state.g, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'minasaoptimize' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresults(const minasastate &state, real_1d_array &x, minasareport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasaresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresultsbuf(const minasastate &state, real_1d_array &x, minasareport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasaresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minasarestartfrom(const minasastate &state, const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::minasarestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(bndl.c_ptr()), const_cast(bndu.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + + +static ae_int_t cqmodels_newtonrefinementits = 3; +static ae_bool cqmodels_cqmrebuild(convexquadraticmodel* s, + ae_state *_state); +static void cqmodels_cqmsolveea(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* tmp, + ae_state *_state); + + +static ae_int_t snnls_iterativerefinementits = 3; +static ae_bool snnls_boundedstepandactivation(/* Real */ ae_vector* xc, + /* Real */ ae_vector* xn, + /* Boolean */ ae_vector* nnc, + ae_int_t n, + ae_state *_state); + + +static void sactivesets_constraineddescent(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* h, + /* Real */ ae_matrix* ha, + ae_bool normalize, + /* Real */ ae_vector* d, + ae_state *_state); +static void sactivesets_reactivateconstraints(sactiveset* state, + /* Real */ ae_vector* gc, + /* Real */ ae_vector* h, + ae_state *_state); + + +static ae_int_t mincg_rscountdownlen = 10; +static double mincg_gtol = 0.3; +static void mincg_clearrequestfields(mincgstate* state, ae_state *_state); +static void mincg_preconditionedmultiply(mincgstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* work0, + /* Real */ ae_vector* work1, + ae_state *_state); +static double mincg_preconditionedmultiply2(mincgstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* work0, + /* Real */ ae_vector* work1, + ae_state *_state); +static void mincg_mincginitinternal(ae_int_t n, + double diffstep, + mincgstate* state, + ae_state *_state); + + +static double minbleic_gtol = 0.4; +static double minbleic_maxnonmonotoniclen = 1.0E-5; +static double minbleic_initialdecay = 0.5; +static double minbleic_mindecay = 0.1; +static double minbleic_decaycorrection = 0.8; +static double minbleic_penaltyfactor = 100; +static void minbleic_clearrequestfields(minbleicstate* state, + ae_state *_state); +static void minbleic_minbleicinitinternal(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + minbleicstate* state, + ae_state *_state); +static void minbleic_updateestimateofgoodstep(double* estimate, + double newstep, + ae_state *_state); + + +static double minlbfgs_gtol = 0.4; +static void minlbfgs_clearrequestfields(minlbfgsstate* state, + ae_state *_state); + + +static ae_int_t minqp_maxlagrangeits = 10; +static ae_int_t minqp_maxbadnewtonits = 7; +static double minqp_penaltyfactor = 100.0; +static ae_int_t minqp_minqpboundedstepandactivation(minqpstate* state, + /* Real */ ae_vector* xn, + /* Real */ ae_vector* buf, + ae_state *_state); +static double minqp_minqpmodelvalue(convexquadraticmodel* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* xc, + ae_int_t n, + /* Real */ ae_vector* tmp, + ae_state *_state); +static ae_bool minqp_minqpconstrainedoptimum(minqpstate* state, + convexquadraticmodel* a, + double anorm, + /* Real */ ae_vector* b, + /* Real */ ae_vector* xn, + /* Real */ ae_vector* tmp, + /* Boolean */ ae_vector* tmpb, + /* Real */ ae_vector* lagrangec, + ae_state *_state); + + +static double minlm_lambdaup = 2.0; +static double minlm_lambdadown = 0.33; +static double minlm_suspiciousnu = 16; +static ae_int_t minlm_smallmodelage = 3; +static ae_int_t minlm_additers = 5; +static void minlm_lmprepare(ae_int_t n, + ae_int_t m, + ae_bool havegrad, + minlmstate* state, + ae_state *_state); +static void minlm_clearrequestfields(minlmstate* state, ae_state *_state); +static ae_bool minlm_increaselambda(double* lambdav, + double* nu, + ae_state *_state); +static void minlm_decreaselambda(double* lambdav, + double* nu, + ae_state *_state); +static double minlm_boundedscaledantigradnorm(minlmstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* g, + ae_state *_state); + + +static ae_int_t mincomp_n1 = 2; +static ae_int_t mincomp_n2 = 2; +static double mincomp_stpmin = 1.0E-300; +static double mincomp_gtol = 0.3; +static double mincomp_gpaftol = 0.0001; +static double mincomp_gpadecay = 0.5; +static double mincomp_asarho = 0.5; +static double mincomp_asaboundedantigradnorm(minasastate* state, + ae_state *_state); +static double mincomp_asaginorm(minasastate* state, ae_state *_state); +static double mincomp_asad1norm(minasastate* state, ae_state *_state); +static ae_bool mincomp_asauisempty(minasastate* state, ae_state *_state); +static void mincomp_clearrequestfields(minasastate* state, + ae_state *_state); + + + + + +/************************************************************************* +This subroutine is used to prepare threshold value which will be used for +trimming of the target function (see comments on TrimFunction() for more +information). + +This function accepts only one parameter: function value at the starting +point. It returns threshold which will be used for trimming. + + -- ALGLIB -- + Copyright 10.05.2011 by Bochkanov Sergey +*************************************************************************/ +void trimprepare(double f, double* threshold, ae_state *_state) +{ + + *threshold = 0; + + *threshold = 10*(ae_fabs(f, _state)+1); +} + + +/************************************************************************* +This subroutine is used to "trim" target function, i.e. to do following +transformation: + + { {F,G} if F=Threshold + +Such transformation allows us to solve problems with singularities by +redefining function in such way that it becomes bounded from above. + + -- ALGLIB -- + Copyright 10.05.2011 by Bochkanov Sergey +*************************************************************************/ +void trimfunction(double* f, + /* Real */ ae_vector* g, + ae_int_t n, + double threshold, + ae_state *_state) +{ + ae_int_t i; + + + if( ae_fp_greater_eq(*f,threshold) ) + { + *f = threshold; + for(i=0; i<=n-1; i++) + { + g->ptr.p_double[i] = 0.0; + } + } +} + + +/************************************************************************* +This function enforces boundary constraints in the X. + +This function correctly (although a bit inefficient) handles BL[i] which +are -INF and BU[i] which are +INF. + +We have NMain+NSlack dimensional X, with first NMain components bounded +by BL/BU, and next NSlack ones bounded by non-negativity constraints. + +INPUT PARAMETERS + X - array[NMain+NSlack], point + BL - array[NMain], lower bounds + (may contain -INF, when bound is not present) + HaveBL - array[NMain], if HaveBL[i] is False, + then i-th bound is not present + BU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBU - array[NMain], if HaveBU[i] is False, + then i-th bound is not present + +OUTPUT PARAMETERS + X - X with all constraints being enforced + +It returns True when constraints are consistent, +False - when constraints are inconsistent. + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool enforceboundaryconstraints(/* Real */ ae_vector* x, + /* Real */ ae_vector* bl, + /* Boolean */ ae_vector* havebl, + /* Real */ ae_vector* bu, + /* Boolean */ ae_vector* havebu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state) +{ + ae_int_t i; + ae_bool result; + + + result = ae_false; + for(i=0; i<=nmain-1; i++) + { + if( (havebl->ptr.p_bool[i]&&havebu->ptr.p_bool[i])&&ae_fp_greater(bl->ptr.p_double[i],bu->ptr.p_double[i]) ) + { + return result; + } + if( havebl->ptr.p_bool[i]&&ae_fp_less(x->ptr.p_double[i],bl->ptr.p_double[i]) ) + { + x->ptr.p_double[i] = bl->ptr.p_double[i]; + } + if( havebu->ptr.p_bool[i]&&ae_fp_greater(x->ptr.p_double[i],bu->ptr.p_double[i]) ) + { + x->ptr.p_double[i] = bu->ptr.p_double[i]; + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_less(x->ptr.p_double[nmain+i],0) ) + { + x->ptr.p_double[nmain+i] = 0; + } + } + result = ae_true; + return result; +} + + +/************************************************************************* +This function projects gradient into feasible area of boundary constrained +optimization problem. X can be infeasible with respect to boundary +constraints. We have NMain+NSlack dimensional X, with first NMain +components bounded by BL/BU, and next NSlack ones bounded by non-negativity +constraints. + +INPUT PARAMETERS + X - array[NMain+NSlack], point + G - array[NMain+NSlack], gradient + BL - lower bounds (may contain -INF, when bound is not present) + HaveBL - if HaveBL[i] is False, then i-th bound is not present + BU - upper bounds (may contain +INF, when bound is not present) + HaveBU - if HaveBU[i] is False, then i-th bound is not present + +OUTPUT PARAMETERS + G - projection of G. Components of G which satisfy one of the + following + (1) (X[I]<=BndL[I]) and (G[I]>0), OR + (2) (X[I]>=BndU[I]) and (G[I]<0) + are replaced by zeros. + +NOTE 1: this function assumes that constraints are feasible. It throws +exception otherwise. + +NOTE 2: in fact, projection of ANTI-gradient is calculated, because this +function trims components of -G which points outside of the feasible area. +However, working with -G is considered confusing, because all optimization +source work with G. + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +void projectgradientintobc(/* Real */ ae_vector* x, + /* Real */ ae_vector* g, + /* Real */ ae_vector* bl, + /* Boolean */ ae_vector* havebl, + /* Real */ ae_vector* bu, + /* Boolean */ ae_vector* havebu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state) +{ + ae_int_t i; + + + for(i=0; i<=nmain-1; i++) + { + ae_assert((!havebl->ptr.p_bool[i]||!havebu->ptr.p_bool[i])||ae_fp_less_eq(bl->ptr.p_double[i],bu->ptr.p_double[i]), "ProjectGradientIntoBC: internal error (infeasible constraints)", _state); + if( (havebl->ptr.p_bool[i]&&ae_fp_less_eq(x->ptr.p_double[i],bl->ptr.p_double[i]))&&ae_fp_greater(g->ptr.p_double[i],0) ) + { + g->ptr.p_double[i] = 0; + } + if( (havebu->ptr.p_bool[i]&&ae_fp_greater_eq(x->ptr.p_double[i],bu->ptr.p_double[i]))&&ae_fp_less(g->ptr.p_double[i],0) ) + { + g->ptr.p_double[i] = 0; + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_less_eq(x->ptr.p_double[nmain+i],0)&&ae_fp_greater(g->ptr.p_double[nmain+i],0) ) + { + g->ptr.p_double[nmain+i] = 0; + } + } +} + + +/************************************************************************* +Given + a) initial point X0[NMain+NSlack] + (feasible with respect to bound constraints) + b) step vector alpha*D[NMain+NSlack] + c) boundary constraints BndL[NMain], BndU[NMain] + d) implicit non-negativity constraints for slack variables +this function calculates bound on the step length subject to boundary +constraints. + +It returns: + * MaxStepLen - such step length that X0+MaxStepLen*alpha*D is exactly + at the boundary given by constraints + * VariableToFreeze - index of the constraint to be activated, + 0 <= VariableToFreeze < NMain+NSlack + * ValueToFreeze - value of the corresponding constraint. + +Notes: + * it is possible that several constraints can be activated by the step + at once. In such cases only one constraint is returned. It is caller + responsibility to check other constraints. This function makes sure + that we activate at least one constraint, and everything else is the + responsibility of the caller. + * steps smaller than MaxStepLen still can activate constraints due to + numerical errors. Thus purpose of this function is not to guard + against accidental activation of the constraints - quite the reverse, + its purpose is to activate at least constraint upon performing step + which is too long. + * in case there is no constraints to activate, we return negative + VariableToFreeze and zero MaxStepLen and ValueToFreeze. + * this function assumes that constraints are consistent; it throws + exception otherwise. + +INPUT PARAMETERS + X - array[NMain+NSlack], point. Must be feasible with respect + to bound constraints (exception will be thrown otherwise) + D - array[NMain+NSlack], step direction + alpha - scalar multiplier before D, alpha<>0 + BndL - lower bounds, array[NMain] + (may contain -INF, when bound is not present) + HaveBndL - array[NMain], if HaveBndL[i] is False, + then i-th bound is not present + BndU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBndU - array[NMain], if HaveBndU[i] is False, + then i-th bound is not present + NMain - number of main variables + NSlack - number of slack variables + +OUTPUT PARAMETERS + VariableToFreeze: + * negative value = step is unbounded, ValueToFreeze=0, + MaxStepLen=0. + * non-negative value = at least one constraint, given by + this parameter, will be activated + upon performing maximum step. + ValueToFreeze- value of the variable which will be constrained + MaxStepLen - maximum length of the step. Can be zero when step vector + looks outside of the feasible area. + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +void calculatestepbound(/* Real */ ae_vector* x, + /* Real */ ae_vector* d, + double alpha, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_int_t* variabletofreeze, + double* valuetofreeze, + double* maxsteplen, + ae_state *_state) +{ + ae_int_t i; + double prevmax; + double initval; + + *variabletofreeze = 0; + *valuetofreeze = 0; + *maxsteplen = 0; + + ae_assert(ae_fp_neq(alpha,0), "CalculateStepBound: zero alpha", _state); + *variabletofreeze = -1; + initval = ae_maxrealnumber; + *maxsteplen = initval; + for(i=0; i<=nmain-1; i++) + { + if( havebndl->ptr.p_bool[i]&&ae_fp_less(alpha*d->ptr.p_double[i],0) ) + { + ae_assert(ae_fp_greater_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]), "CalculateStepBound: infeasible X", _state); + prevmax = *maxsteplen; + *maxsteplen = safeminposrv(x->ptr.p_double[i]-bndl->ptr.p_double[i], -alpha*d->ptr.p_double[i], *maxsteplen, _state); + if( ae_fp_less(*maxsteplen,prevmax) ) + { + *variabletofreeze = i; + *valuetofreeze = bndl->ptr.p_double[i]; + } + } + if( havebndu->ptr.p_bool[i]&&ae_fp_greater(alpha*d->ptr.p_double[i],0) ) + { + ae_assert(ae_fp_less_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]), "CalculateStepBound: infeasible X", _state); + prevmax = *maxsteplen; + *maxsteplen = safeminposrv(bndu->ptr.p_double[i]-x->ptr.p_double[i], alpha*d->ptr.p_double[i], *maxsteplen, _state); + if( ae_fp_less(*maxsteplen,prevmax) ) + { + *variabletofreeze = i; + *valuetofreeze = bndu->ptr.p_double[i]; + } + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_less(alpha*d->ptr.p_double[nmain+i],0) ) + { + ae_assert(ae_fp_greater_eq(x->ptr.p_double[nmain+i],0), "CalculateStepBound: infeasible X", _state); + prevmax = *maxsteplen; + *maxsteplen = safeminposrv(x->ptr.p_double[nmain+i], -alpha*d->ptr.p_double[nmain+i], *maxsteplen, _state); + if( ae_fp_less(*maxsteplen,prevmax) ) + { + *variabletofreeze = nmain+i; + *valuetofreeze = 0; + } + } + } + if( ae_fp_eq(*maxsteplen,initval) ) + { + *valuetofreeze = 0; + *maxsteplen = 0; + } +} + + +/************************************************************************* +This function postprocesses bounded step by: +* analysing step length (whether it is equal to MaxStepLen) and activating + constraint given by VariableToFreeze if needed +* checking for additional bound constraints to activate + +This function uses final point of the step, quantities calculated by the +CalculateStepBound() function. As result, it returns point which is +exactly feasible with respect to boundary constraints. + +NOTE 1: this function does NOT handle and check linear equality constraints +NOTE 2: when StepTaken=MaxStepLen we always activate at least one constraint + +INPUT PARAMETERS + X - array[NMain+NSlack], final point to postprocess + XPrev - array[NMain+NSlack], initial point + BndL - lower bounds, array[NMain] + (may contain -INF, when bound is not present) + HaveBndL - array[NMain], if HaveBndL[i] is False, + then i-th bound is not present + BndU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBndU - array[NMain], if HaveBndU[i] is False, + then i-th bound is not present + NMain - number of main variables + NSlack - number of slack variables + VariableToFreeze-result of CalculateStepBound() + ValueToFreeze- result of CalculateStepBound() + StepTaken - actual step length (actual step is equal to the possibly + non-unit step direction vector times this parameter). + StepTaken<=MaxStepLen. + MaxStepLen - result of CalculateStepBound() + +OUTPUT PARAMETERS + X - point bounded with respect to constraints. + components corresponding to active constraints are exactly + equal to the boundary values. + +RESULT: + number of constraints activated in addition to previously active ones. + Constraints which were DEACTIVATED are ignored (do not influence + function value). + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t postprocessboundedstep(/* Real */ ae_vector* x, + /* Real */ ae_vector* xprev, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_int_t variabletofreeze, + double valuetofreeze, + double steptaken, + double maxsteplen, + ae_state *_state) +{ + ae_int_t i; + ae_bool wasactivated; + ae_int_t result; + + + ae_assert(variabletofreeze<0||ae_fp_less_eq(steptaken,maxsteplen), "Assertion failed", _state); + + /* + * Activate constraints + */ + if( variabletofreeze>=0&&ae_fp_eq(steptaken,maxsteplen) ) + { + x->ptr.p_double[variabletofreeze] = valuetofreeze; + } + for(i=0; i<=nmain-1; i++) + { + if( havebndl->ptr.p_bool[i]&&ae_fp_less(x->ptr.p_double[i],bndl->ptr.p_double[i]) ) + { + x->ptr.p_double[i] = bndl->ptr.p_double[i]; + } + if( havebndu->ptr.p_bool[i]&&ae_fp_greater(x->ptr.p_double[i],bndu->ptr.p_double[i]) ) + { + x->ptr.p_double[i] = bndu->ptr.p_double[i]; + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_less_eq(x->ptr.p_double[nmain+i],0) ) + { + x->ptr.p_double[nmain+i] = 0; + } + } + + /* + * Calculate number of constraints being activated + */ + result = 0; + for(i=0; i<=nmain-1; i++) + { + wasactivated = ae_fp_neq(x->ptr.p_double[i],xprev->ptr.p_double[i])&&((havebndl->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]))||(havebndu->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]))); + wasactivated = wasactivated||variabletofreeze==i; + if( wasactivated ) + { + result = result+1; + } + } + for(i=0; i<=nslack-1; i++) + { + wasactivated = ae_fp_neq(x->ptr.p_double[nmain+i],xprev->ptr.p_double[nmain+i])&&ae_fp_eq(x->ptr.p_double[nmain+i],0.0); + wasactivated = wasactivated||variabletofreeze==nmain+i; + if( wasactivated ) + { + result = result+1; + } + } + return result; +} + + +/************************************************************************* +The purpose of this function is to prevent algorithm from "unsticking" +from the active bound constraints because of numerical noise in the +gradient or Hessian. + +It is done by zeroing some components of the search direction D. D[i] is +zeroed when both (a) and (b) are true: +a) corresponding X[i] is exactly at the boundary +b) |D[i]*S[i]| <= DropTol*Sqrt(SUM(D[i]^2*S[I]^2)) + +D can be step direction , antigradient, gradient, or anything similar. +Sign of D does not matter, nor matters step length. + +NOTE 1: boundary constraints are expected to be consistent, as well as X + is expected to be feasible. Exception will be thrown otherwise. + +INPUT PARAMETERS + D - array[NMain+NSlack], direction + X - array[NMain+NSlack], current point + BndL - lower bounds, array[NMain] + (may contain -INF, when bound is not present) + HaveBndL - array[NMain], if HaveBndL[i] is False, + then i-th bound is not present + BndU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBndU - array[NMain], if HaveBndU[i] is False, + then i-th bound is not present + S - array[NMain+NSlack], scaling of the variables + NMain - number of main variables + NSlack - number of slack variables + DropTol - drop tolerance, >=0 + +OUTPUT PARAMETERS + X - point bounded with respect to constraints. + components corresponding to active constraints are exactly + equal to the boundary values. + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +void filterdirection(/* Real */ ae_vector* d, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + /* Real */ ae_vector* s, + ae_int_t nmain, + ae_int_t nslack, + double droptol, + ae_state *_state) +{ + ae_int_t i; + double scalednorm; + ae_bool isactive; + + + scalednorm = 0.0; + for(i=0; i<=nmain+nslack-1; i++) + { + scalednorm = scalednorm+ae_sqr(d->ptr.p_double[i]*s->ptr.p_double[i], _state); + } + scalednorm = ae_sqrt(scalednorm, _state); + for(i=0; i<=nmain-1; i++) + { + ae_assert(!havebndl->ptr.p_bool[i]||ae_fp_greater_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]), "FilterDirection: infeasible point", _state); + ae_assert(!havebndu->ptr.p_bool[i]||ae_fp_less_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]), "FilterDirection: infeasible point", _state); + isactive = (havebndl->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]))||(havebndu->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndu->ptr.p_double[i])); + if( isactive&&ae_fp_less_eq(ae_fabs(d->ptr.p_double[i]*s->ptr.p_double[i], _state),droptol*scalednorm) ) + { + d->ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=nslack-1; i++) + { + ae_assert(ae_fp_greater_eq(x->ptr.p_double[nmain+i],0), "FilterDirection: infeasible point", _state); + if( ae_fp_eq(x->ptr.p_double[nmain+i],0)&&ae_fp_less_eq(ae_fabs(d->ptr.p_double[nmain+i]*s->ptr.p_double[nmain+i], _state),droptol*scalednorm) ) + { + d->ptr.p_double[nmain+i] = 0.0; + } + } +} + + +/************************************************************************* +This function returns number of bound constraints whose state was changed +(either activated or deactivated) when making step from XPrev to X. + +Constraints are considered: +* active - when we are exactly at the boundary +* inactive - when we are not at the boundary + +You should note that antigradient direction is NOT taken into account when +we make decions on the constraint status. + +INPUT PARAMETERS + X - array[NMain+NSlack], final point. + Must be feasible with respect to bound constraints. + XPrev - array[NMain+NSlack], initial point. + Must be feasible with respect to bound constraints. + BndL - lower bounds, array[NMain] + (may contain -INF, when bound is not present) + HaveBndL - array[NMain], if HaveBndL[i] is False, + then i-th bound is not present + BndU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBndU - array[NMain], if HaveBndU[i] is False, + then i-th bound is not present + NMain - number of main variables + NSlack - number of slack variables + +RESULT: + number of constraints whose state was changed. + + -- ALGLIB -- + Copyright 10.01.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t numberofchangedconstraints(/* Real */ ae_vector* x, + /* Real */ ae_vector* xprev, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state) +{ + ae_int_t i; + ae_bool statuschanged; + ae_int_t result; + + + result = 0; + for(i=0; i<=nmain-1; i++) + { + if( ae_fp_neq(x->ptr.p_double[i],xprev->ptr.p_double[i]) ) + { + statuschanged = ae_false; + if( havebndl->ptr.p_bool[i]&&(ae_fp_eq(x->ptr.p_double[i],bndl->ptr.p_double[i])||ae_fp_eq(xprev->ptr.p_double[i],bndl->ptr.p_double[i])) ) + { + statuschanged = ae_true; + } + if( havebndu->ptr.p_bool[i]&&(ae_fp_eq(x->ptr.p_double[i],bndu->ptr.p_double[i])||ae_fp_eq(xprev->ptr.p_double[i],bndu->ptr.p_double[i])) ) + { + statuschanged = ae_true; + } + if( statuschanged ) + { + result = result+1; + } + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_neq(x->ptr.p_double[nmain+i],xprev->ptr.p_double[nmain+i])&&(ae_fp_eq(x->ptr.p_double[nmain+i],0)||ae_fp_eq(xprev->ptr.p_double[nmain+i],0)) ) + { + result = result+1; + } + } + return result; +} + + +/************************************************************************* +This function finds feasible point of (NMain+NSlack)-dimensional problem +subject to NMain explicit boundary constraints (some constraints can be +omitted), NSlack implicit non-negativity constraints, K linear equality +constraints. + +INPUT PARAMETERS + X - array[NMain+NSlack], initial point. + BndL - lower bounds, array[NMain] + (may contain -INF, when bound is not present) + HaveBndL - array[NMain], if HaveBndL[i] is False, + then i-th bound is not present + BndU - array[NMain], upper bounds + (may contain +INF, when bound is not present) + HaveBndU - array[NMain], if HaveBndU[i] is False, + then i-th bound is not present + NMain - number of main variables + NSlack - number of slack variables + CE - array[K,NMain+NSlack+1], equality constraints CE*x=b. + Rows contain constraints, first NMain+NSlack columns + contain coefficients before X[], last column contain + right part. + K - number of linear constraints + EpsI - infeasibility (error in the right part) allowed in the + solution + +OUTPUT PARAMETERS: + X - feasible point or best infeasible point found before + algorithm termination + QPIts - number of QP iterations (for debug purposes) + GPAIts - number of GPA iterations (for debug purposes) + +RESULT: + True in case X is feasible, False - if it is infeasible. + + -- ALGLIB -- + Copyright 20.01.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool findfeasiblepoint(/* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + /* Real */ ae_matrix* ce, + ae_int_t k, + double epsi, + ae_int_t* qpits, + ae_int_t* gpaits, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _ce; + ae_int_t i; + ae_int_t j; + ae_int_t idx0; + ae_int_t idx1; + ae_vector permx; + ae_vector xn; + ae_vector xa; + ae_vector newtonstep; + ae_vector g; + ae_vector pg; + ae_matrix a; + double armijostep; + double armijobeststep; + double armijobestfeas; + double v; + double mx; + double feaserr; + double feasold; + double feasnew; + double pgnorm; + double vn; + double vd; + double stp; + ae_int_t vartofreeze; + double valtofreeze; + double maxsteplen; + ae_bool werechangesinconstraints; + ae_bool stage1isover; + ae_bool converged; + ae_vector activeconstraints; + ae_vector tmpk; + ae_vector colnorms; + ae_int_t nactive; + ae_int_t nfree; + ae_int_t nsvd; + ae_vector p1; + ae_vector p2; + apbuffers buf; + ae_vector w; + ae_vector s; + ae_matrix u; + ae_matrix vt; + ae_int_t itscount; + ae_int_t itswithintolerance; + ae_int_t maxitswithintolerance; + ae_int_t gparuns; + ae_int_t maxarmijoruns; + ae_bool result; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_ce, ce, _state, ae_true); + ce = &_ce; + *qpits = 0; + *gpaits = 0; + ae_vector_init(&permx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xn, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xa, 0, DT_REAL, _state, ae_true); + ae_vector_init(&newtonstep, 0, DT_REAL, _state, ae_true); + ae_vector_init(&g, 0, DT_REAL, _state, ae_true); + ae_vector_init(&pg, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&a, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&activeconstraints, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmpk, 0, DT_REAL, _state, ae_true); + ae_vector_init(&colnorms, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p1, 0, DT_INT, _state, ae_true); + ae_vector_init(&p2, 0, DT_INT, _state, ae_true); + _apbuffers_init(&buf, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + ae_vector_init(&s, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true); + + maxitswithintolerance = 3; + maxarmijoruns = 5; + *qpits = 0; + *gpaits = 0; + + /* + * Initial enforcement of the feasibility with respect to boundary constraints + * NOTE: after this block we assume that boundary constraints are consistent. + */ + if( !enforceboundaryconstraints(x, bndl, havebndl, bndu, havebndu, nmain, nslack, _state) ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + if( k==0 ) + { + + /* + * No linear constraints, we can exit right now + */ + result = ae_true; + ae_frame_leave(_state); + return result; + } + + /* + * Scale rows of CE in such way that max(CE[i,0..nmain+nslack-1])=1 for any i=0..k-1 + */ + for(i=0; i<=k-1; i++) + { + v = 0.0; + for(j=0; j<=nmain+nslack-1; j++) + { + v = ae_maxreal(v, ae_fabs(ce->ptr.pp_double[i][j], _state), _state); + } + if( ae_fp_neq(v,0) ) + { + v = 1/v; + ae_v_muld(&ce->ptr.pp_double[i][0], 1, ae_v_len(0,nmain+nslack), v); + } + } + + /* + * Allocate temporaries + */ + ae_vector_set_length(&xn, nmain+nslack, _state); + ae_vector_set_length(&xa, nmain+nslack, _state); + ae_vector_set_length(&permx, nmain+nslack, _state); + ae_vector_set_length(&g, nmain+nslack, _state); + ae_vector_set_length(&pg, nmain+nslack, _state); + ae_vector_set_length(&tmpk, k, _state); + ae_matrix_set_length(&a, k, nmain+nslack, _state); + ae_vector_set_length(&activeconstraints, nmain+nslack, _state); + ae_vector_set_length(&newtonstep, nmain+nslack, _state); + ae_vector_set_length(&s, nmain+nslack, _state); + ae_vector_set_length(&colnorms, nmain+nslack, _state); + for(i=0; i<=nmain+nslack-1; i++) + { + s.ptr.p_double[i] = 1.0; + colnorms.ptr.p_double[i] = 0.0; + for(j=0; j<=k-1; j++) + { + colnorms.ptr.p_double[i] = colnorms.ptr.p_double[i]+ae_sqr(ce->ptr.pp_double[j][i], _state); + } + } + + /* + * K>0, we have linear equality constraints combined with bound constraints. + * + * Try to find feasible point as minimizer of the quadratic function + * F(x) = 0.5*||CE*x-b||^2 = 0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b + * subject to boundary constraints given by BL, BU and non-negativity of + * the slack variables. BTW, we drop constant term because it does not + * actually influences on the solution. + * + * Below we will assume that K>0. + */ + itswithintolerance = 0; + itscount = 0; + for(;;) + { + + /* + * Stage 0: check for exact convergence + */ + converged = ae_true; + feaserr = 0; + for(i=0; i<=k-1; i++) + { + + /* + * Calculate: + * * V - error in the right part + * * MX - maximum term in the left part + * + * Terminate if error in the right part is not greater than 100*Eps*MX. + * + * IMPORTANT: we must perform check for non-strict inequality, i.e. to use <= instead of <. + * it will allow us to easily handle situations with zero rows of CE. + */ + mx = 0; + v = -ce->ptr.pp_double[i][nmain+nslack]; + for(j=0; j<=nmain+nslack-1; j++) + { + mx = ae_maxreal(mx, ae_fabs(ce->ptr.pp_double[i][j]*x->ptr.p_double[j], _state), _state); + v = v+ce->ptr.pp_double[i][j]*x->ptr.p_double[j]; + } + feaserr = feaserr+ae_sqr(v, _state); + converged = converged&&ae_fp_less_eq(ae_fabs(v, _state),100*ae_machineepsilon*mx); + } + feaserr = ae_sqrt(feaserr, _state); + if( converged ) + { + result = ae_fp_less_eq(feaserr,epsi); + ae_frame_leave(_state); + return result; + } + + /* + * Stage 1: equality constrained quadratic programming + * + * * treat active bound constraints as equality ones (constraint is considered + * active when we are at the boundary, independently of the antigradient direction) + * * calculate unrestricted Newton step to point XM (which may be infeasible) + * calculate MaxStepLen = largest step in direction of XM which retains feasibility. + * * perform bounded step from X to XN: + * a) XN=XM (if XM is feasible) + * b) XN=X-MaxStepLen*(XM-X) (otherwise) + * * X := XN + * * if XM (Newton step subject to currently active constraints) was feasible, goto Stage 2 + * * repeat Stage 1 + * + * NOTE 1: in order to solve constrained qudratic subproblem we will have to reorder + * variables in such way that ones corresponding to inactive constraints will + * be first, and active ones will be last in the list. CE and X are now + * [ xi ] + * separated into two parts: CE = [CEi CEa], x = [ ], where CEi/Xi correspond + * [ xa ] + * to INACTIVE constraints, and CEa/Xa correspond to the ACTIVE ones. + * + * Now, instead of F=0.5*x'*(CE'*CE)*x - (b'*CE)*x + 0.5*b'*b, we have + * F(xi) = 0.5*(CEi*xi,CEi*xi) + (CEa*xa-b,CEi*xi) + (0.5*CEa*xa-b,CEa*xa). + * Here xa is considered constant, i.e. we optimize with respect to xi, leaving xa fixed. + * + * We can solve it by performing SVD of CEi and calculating pseudoinverse of the + * Hessian matrix. Of course, we do NOT calculate pseudoinverse explicitly - we + * just use singular vectors to perform implicit multiplication by it. + * + */ + for(;;) + { + + /* + * Calculate G - gradient subject to equality constraints, + * multiply it by inverse of the Hessian diagonal to obtain initial + * step vector. + * + * Bound step subject to constraints which can be activated, + * run Armijo search with increasing step size. + * Search is terminated when feasibility error stops to decrease. + * + * NOTE: it is important to test for "stops to decrease" instead + * of "starts to increase" in order to correctly handle cases with + * zero CE. + */ + armijobeststep = 0.0; + armijobestfeas = 0.0; + for(i=0; i<=nmain+nslack-1; i++) + { + g.ptr.p_double[i] = 0; + } + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + armijobestfeas = armijobestfeas+ae_sqr(v, _state); + ae_v_addd(&g.ptr.p_double[0], 1, &ce->ptr.pp_double[i][0], 1, ae_v_len(0,nmain+nslack-1), v); + } + armijobestfeas = ae_sqrt(armijobestfeas, _state); + for(i=0; i<=nmain-1; i++) + { + if( havebndl->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]) ) + { + g.ptr.p_double[i] = 0.0; + } + if( havebndu->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]) ) + { + g.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=nslack-1; i++) + { + if( ae_fp_eq(x->ptr.p_double[nmain+i],0.0) ) + { + g.ptr.p_double[nmain+i] = 0.0; + } + } + v = 0.0; + for(i=0; i<=nmain+nslack-1; i++) + { + if( ae_fp_neq(ae_sqr(colnorms.ptr.p_double[i], _state),0) ) + { + newtonstep.ptr.p_double[i] = -g.ptr.p_double[i]/ae_sqr(colnorms.ptr.p_double[i], _state); + } + else + { + newtonstep.ptr.p_double[i] = 0.0; + } + v = v+ae_sqr(newtonstep.ptr.p_double[i], _state); + } + if( ae_fp_eq(v,0) ) + { + + /* + * Constrained gradient is zero, QP iterations are over + */ + break; + } + calculatestepbound(x, &newtonstep, 1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, &vartofreeze, &valtofreeze, &maxsteplen, _state); + if( vartofreeze>=0&&ae_fp_eq(maxsteplen,0) ) + { + + /* + * Can not perform step, QP iterations are over + */ + break; + } + if( vartofreeze>=0 ) + { + armijostep = ae_minreal(1.0, maxsteplen, _state); + } + else + { + armijostep = 1; + } + for(;;) + { + ae_v_move(&xa.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + ae_v_addd(&xa.ptr.p_double[0], 1, &newtonstep.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), armijostep); + enforceboundaryconstraints(&xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _state); + feaserr = 0.0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &xa.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + feaserr = feaserr+ae_sqr(v, _state); + } + feaserr = ae_sqrt(feaserr, _state); + if( ae_fp_greater_eq(feaserr,armijobestfeas) ) + { + break; + } + armijobestfeas = feaserr; + armijobeststep = armijostep; + armijostep = 2.0*armijostep; + } + ae_v_addd(&x->ptr.p_double[0], 1, &newtonstep.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), armijobeststep); + enforceboundaryconstraints(x, bndl, havebndl, bndu, havebndu, nmain, nslack, _state); + + /* + * Determine number of active and free constraints + */ + nactive = 0; + for(i=0; i<=nmain-1; i++) + { + activeconstraints.ptr.p_double[i] = 0; + if( havebndl->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndl->ptr.p_double[i]) ) + { + activeconstraints.ptr.p_double[i] = 1; + } + if( havebndu->ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]) ) + { + activeconstraints.ptr.p_double[i] = 1; + } + if( ae_fp_greater(activeconstraints.ptr.p_double[i],0) ) + { + nactive = nactive+1; + } + } + for(i=0; i<=nslack-1; i++) + { + activeconstraints.ptr.p_double[nmain+i] = 0; + if( ae_fp_eq(x->ptr.p_double[nmain+i],0.0) ) + { + activeconstraints.ptr.p_double[nmain+i] = 1; + } + if( ae_fp_greater(activeconstraints.ptr.p_double[nmain+i],0) ) + { + nactive = nactive+1; + } + } + nfree = nmain+nslack-nactive; + if( nfree==0 ) + { + break; + } + *qpits = *qpits+1; + + /* + * Reorder variables + */ + tagsortbuf(&activeconstraints, nmain+nslack, &p1, &p2, &buf, _state); + for(i=0; i<=k-1; i++) + { + for(j=0; j<=nmain+nslack-1; j++) + { + a.ptr.pp_double[i][j] = ce->ptr.pp_double[i][j]; + } + } + for(j=0; j<=nmain+nslack-1; j++) + { + permx.ptr.p_double[j] = x->ptr.p_double[j]; + } + for(j=0; j<=nmain+nslack-1; j++) + { + if( p2.ptr.p_int[j]!=j ) + { + idx0 = p2.ptr.p_int[j]; + idx1 = j; + for(i=0; i<=k-1; i++) + { + v = a.ptr.pp_double[i][idx0]; + a.ptr.pp_double[i][idx0] = a.ptr.pp_double[i][idx1]; + a.ptr.pp_double[i][idx1] = v; + } + v = permx.ptr.p_double[idx0]; + permx.ptr.p_double[idx0] = permx.ptr.p_double[idx1]; + permx.ptr.p_double[idx1] = v; + } + } + + /* + * Calculate (unprojected) gradient: + * G(xi) = CEi'*(CEi*xi + CEa*xa - b) + */ + for(i=0; i<=nfree-1; i++) + { + g.ptr.p_double[i] = 0; + } + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&a.ptr.pp_double[i][0], 1, &permx.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + tmpk.ptr.p_double[i] = v-ce->ptr.pp_double[i][nmain+nslack]; + } + for(i=0; i<=k-1; i++) + { + v = tmpk.ptr.p_double[i]; + ae_v_addd(&g.ptr.p_double[0], 1, &a.ptr.pp_double[i][0], 1, ae_v_len(0,nfree-1), v); + } + + /* + * Calculate Newton step using SVD of CEi: + * F(xi) = 0.5*xi'*H*xi + g'*xi (Taylor decomposition) + * XN = -H^(-1)*g (new point, solution of the QP subproblem) + * H = CEi'*CEi + * CEi = U*W*V' (SVD of CEi) + * H = V*W^2*V' + * H^(-1) = V*W^(-2)*V' + * step = -V*W^(-2)*V'*g (it is better to perform multiplication from right to left) + * + * NOTE 1: we do NOT need left singular vectors to perform Newton step. + */ + nsvd = ae_minint(k, nfree, _state); + if( !rmatrixsvd(&a, k, nfree, 0, 1, 2, &w, &u, &vt, _state) ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + for(i=0; i<=nsvd-1; i++) + { + v = ae_v_dotproduct(&vt.ptr.pp_double[i][0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,nfree-1)); + tmpk.ptr.p_double[i] = v; + } + for(i=0; i<=nsvd-1; i++) + { + + /* + * It is important to have strict ">" in order to correctly + * handle zero singular values. + */ + if( ae_fp_greater(ae_sqr(w.ptr.p_double[i], _state),ae_sqr(w.ptr.p_double[0], _state)*(nmain+nslack)*ae_machineepsilon) ) + { + tmpk.ptr.p_double[i] = tmpk.ptr.p_double[i]/ae_sqr(w.ptr.p_double[i], _state); + } + else + { + tmpk.ptr.p_double[i] = 0; + } + } + for(i=0; i<=nmain+nslack-1; i++) + { + newtonstep.ptr.p_double[i] = 0; + } + for(i=0; i<=nsvd-1; i++) + { + v = tmpk.ptr.p_double[i]; + ae_v_subd(&newtonstep.ptr.p_double[0], 1, &vt.ptr.pp_double[i][0], 1, ae_v_len(0,nfree-1), v); + } + for(j=nmain+nslack-1; j>=0; j--) + { + if( p2.ptr.p_int[j]!=j ) + { + idx0 = p2.ptr.p_int[j]; + idx1 = j; + v = newtonstep.ptr.p_double[idx0]; + newtonstep.ptr.p_double[idx0] = newtonstep.ptr.p_double[idx1]; + newtonstep.ptr.p_double[idx1] = v; + } + } + + /* + * NewtonStep contains Newton step subject to active bound constraints. + * + * Such step leads us to the minimizer of the equality constrained F, + * but such minimizer may be infeasible because some constraints which + * are inactive at the initial point can be violated at the solution. + * + * Thus, we perform optimization in two stages: + * a) perform bounded Newton step, i.e. step in the Newton direction + * until activation of the first constraint + * b) in case (MaxStepLen>0)and(MaxStepLen<1), perform additional iteration + * of the Armijo line search in the rest of the Newton direction. + */ + calculatestepbound(x, &newtonstep, 1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, &vartofreeze, &valtofreeze, &maxsteplen, _state); + if( vartofreeze>=0&&ae_fp_eq(maxsteplen,0) ) + { + + /* + * Activation of the constraints prevent us from performing step, + * QP iterations are over + */ + break; + } + if( vartofreeze>=0 ) + { + v = ae_minreal(1.0, maxsteplen, _state); + } + else + { + v = 1.0; + } + ae_v_moved(&xn.ptr.p_double[0], 1, &newtonstep.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), v); + ae_v_add(&xn.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + postprocessboundedstep(&xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, vartofreeze, valtofreeze, v, maxsteplen, _state); + if( ae_fp_greater(maxsteplen,0)&&ae_fp_less(maxsteplen,1) ) + { + + /* + * Newton step was restricted by activation of the constraints, + * perform Armijo iteration. + * + * Initial estimate for best step is zero step. We try different + * step sizes, from the 1-MaxStepLen (residual of the full Newton + * step) to progressively smaller and smaller steps. + */ + armijobeststep = 0.0; + armijobestfeas = 0.0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &xn.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + armijobestfeas = armijobestfeas+ae_sqr(v, _state); + } + armijobestfeas = ae_sqrt(armijobestfeas, _state); + armijostep = 1-maxsteplen; + for(j=0; j<=maxarmijoruns-1; j++) + { + ae_v_move(&xa.ptr.p_double[0], 1, &xn.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + ae_v_addd(&xa.ptr.p_double[0], 1, &newtonstep.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), armijostep); + enforceboundaryconstraints(&xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _state); + feaserr = 0.0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &xa.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + feaserr = feaserr+ae_sqr(v, _state); + } + feaserr = ae_sqrt(feaserr, _state); + if( ae_fp_less(feaserr,armijobestfeas) ) + { + armijobestfeas = feaserr; + armijobeststep = armijostep; + } + armijostep = 0.5*armijostep; + } + ae_v_move(&xa.ptr.p_double[0], 1, &xn.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + ae_v_addd(&xa.ptr.p_double[0], 1, &newtonstep.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), armijobeststep); + enforceboundaryconstraints(&xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _state); + } + else + { + + /* + * Armijo iteration is not performed + */ + ae_v_move(&xa.ptr.p_double[0], 1, &xn.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + } + stage1isover = ae_fp_greater_eq(maxsteplen,1)||ae_fp_eq(maxsteplen,0); + + /* + * Calculate feasibility errors for old and new X. + * These quantinies are used for debugging purposes only. + * However, we can leave them in release code because performance impact is insignificant. + * + * Update X. Exit if needed. + */ + feasold = 0; + feasnew = 0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + feasold = feasold+ae_sqr(v-ce->ptr.pp_double[i][nmain+nslack], _state); + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &xa.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + feasnew = feasnew+ae_sqr(v-ce->ptr.pp_double[i][nmain+nslack], _state); + } + feasold = ae_sqrt(feasold, _state); + feasnew = ae_sqrt(feasnew, _state); + if( ae_fp_greater_eq(feasnew,feasold) ) + { + break; + } + ae_v_move(&x->ptr.p_double[0], 1, &xa.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + if( stage1isover ) + { + break; + } + } + + /* + * Stage 2: gradient projection algorithm (GPA) + * + * * calculate feasibility error (with respect to linear equality constraints) + * * calculate gradient G of F, project it into feasible area (G => PG) + * * exit if norm(PG) is exactly zero or feasibility error is smaller than EpsC + * * let XM be exact minimum of F along -PG (XM may be infeasible). + * calculate MaxStepLen = largest step in direction of -PG which retains feasibility. + * * perform bounded step from X to XN: + * a) XN=XM (if XM is feasible) + * b) XN=X-MaxStepLen*PG (otherwise) + * * X := XN + * * stop after specified number of iterations or when no new constraints was activated + * + * NOTES: + * * grad(F) = (CE'*CE)*x - (b'*CE)^T + * * CE[i] denotes I-th row of CE + * * XM = X+stp*(-PG) where stp=(grad(F(X)),PG)/(CE*PG,CE*PG). + * Here PG is a projected gradient, but in fact it can be arbitrary non-zero + * direction vector - formula for minimum of F along PG still will be correct. + */ + werechangesinconstraints = ae_false; + for(gparuns=1; gparuns<=k; gparuns++) + { + + /* + * calculate feasibility error and G + */ + feaserr = 0; + for(i=0; i<=nmain+nslack-1; i++) + { + g.ptr.p_double[i] = 0; + } + for(i=0; i<=k-1; i++) + { + + /* + * G += CE[i]^T * (CE[i]*x-b[i]) + */ + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + feaserr = feaserr+ae_sqr(v, _state); + ae_v_addd(&g.ptr.p_double[0], 1, &ce->ptr.pp_double[i][0], 1, ae_v_len(0,nmain+nslack-1), v); + } + + /* + * project G, filter it (strip numerical noise) + */ + ae_v_move(&pg.ptr.p_double[0], 1, &g.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + projectgradientintobc(x, &pg, bndl, havebndl, bndu, havebndu, nmain, nslack, _state); + filterdirection(&pg, x, bndl, havebndl, bndu, havebndu, &s, nmain, nslack, 1.0E-9, _state); + for(i=0; i<=nmain+nslack-1; i++) + { + if( ae_fp_neq(ae_sqr(colnorms.ptr.p_double[i], _state),0) ) + { + pg.ptr.p_double[i] = pg.ptr.p_double[i]/ae_sqr(colnorms.ptr.p_double[i], _state); + } + else + { + pg.ptr.p_double[i] = 0.0; + } + } + + /* + * Check GNorm and feasibility. + * Exit when GNorm is exactly zero. + */ + pgnorm = ae_v_dotproduct(&pg.ptr.p_double[0], 1, &pg.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + feaserr = ae_sqrt(feaserr, _state); + pgnorm = ae_sqrt(pgnorm, _state); + if( ae_fp_eq(pgnorm,0) ) + { + result = ae_fp_less_eq(feaserr,epsi); + ae_frame_leave(_state); + return result; + } + + /* + * calculate planned step length + */ + vn = ae_v_dotproduct(&g.ptr.p_double[0], 1, &pg.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + vd = 0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &pg.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + vd = vd+ae_sqr(v, _state); + } + stp = vn/vd; + + /* + * Calculate step bound. + * Perform bounded step and post-process it + */ + calculatestepbound(x, &pg, -1.0, bndl, havebndl, bndu, havebndu, nmain, nslack, &vartofreeze, &valtofreeze, &maxsteplen, _state); + if( vartofreeze>=0&&ae_fp_eq(maxsteplen,0) ) + { + result = ae_false; + ae_frame_leave(_state); + return result; + } + if( vartofreeze>=0 ) + { + v = ae_minreal(stp, maxsteplen, _state); + } + else + { + v = stp; + } + ae_v_move(&xn.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + ae_v_subd(&xn.ptr.p_double[0], 1, &pg.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1), v); + postprocessboundedstep(&xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, vartofreeze, valtofreeze, v, maxsteplen, _state); + + /* + * update X + * check stopping criteria + */ + werechangesinconstraints = werechangesinconstraints||numberofchangedconstraints(&xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, _state)>0; + ae_v_move(&x->ptr.p_double[0], 1, &xn.ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + *gpaits = *gpaits+1; + if( !werechangesinconstraints ) + { + break; + } + } + + /* + * Stage 3: decide to stop algorithm or not to stop + * + * 1. we can stop when last GPA run did NOT changed constraints status. + * It means that we've found final set of the active constraints even + * before GPA made its run. And it means that Newton step moved us to + * the minimum subject to the present constraints. + * Depending on feasibility error, True or False is returned. + */ + feaserr = 0; + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&ce->ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,nmain+nslack-1)); + v = v-ce->ptr.pp_double[i][nmain+nslack]; + feaserr = feaserr+ae_sqr(v, _state); + } + feaserr = ae_sqrt(feaserr, _state); + if( ae_fp_less_eq(feaserr,epsi) ) + { + itswithintolerance = itswithintolerance+1; + } + else + { + itswithintolerance = 0; + } + if( !werechangesinconstraints||itswithintolerance>=maxitswithintolerance ) + { + result = ae_fp_less_eq(feaserr,epsi); + ae_frame_leave(_state); + return result; + } + itscount = itscount+1; + } + ae_frame_leave(_state); + return result; +} + + +/************************************************************************* + This function check, that input derivatives are right. First it scale +parameters DF0 and DF1 from segment [A;B] to [0;1]. Than it build Hermite +spline and derivative of it in 0,5. Search scale as Max(DF0,DF1, |F0-F1|). +Right derivative has to satisfy condition: + |H-F|/S<=0,01, |H'-F'|/S<=0,01. + +INPUT PARAMETERS: + F0 - function's value in X-TestStep point; + DF0 - derivative's value in X-TestStep point; + F1 - function's value in X+TestStep point; + DF1 - derivative's value in X+TestStep point; + F - testing function's value; + DF - testing derivative's value; + Width- width of verification segment. + +RESULT: + If input derivatives is right then function returns true, else + function returns false. + + -- ALGLIB -- + Copyright 29.05.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool derivativecheck(double f0, + double df0, + double f1, + double df1, + double f, + double df, + double width, + ae_state *_state) +{ + double s; + double h; + double dh; + ae_bool result; + + + df = width*df; + df0 = width*df0; + df1 = width*df1; + s = ae_maxreal(ae_maxreal(ae_fabs(df0, _state), ae_fabs(df1, _state), _state), ae_fabs(f1-f0, _state), _state); + h = 0.5*f0+0.125*df0+0.5*f1-0.125*df1; + dh = -1.5*f0-0.25*df0+1.5*f1-0.25*df1; + if( ae_fp_neq(s,0) ) + { + if( ae_fp_greater(ae_fabs(h-f, _state)/s,0.001)||ae_fp_greater(ae_fabs(dh-df, _state)/s,0.001) ) + { + result = ae_false; + return result; + } + } + else + { + if( ae_fp_neq(h-f,0.0)||ae_fp_neq(dh-df,0.0) ) + { + result = ae_false; + return result; + } + } + result = ae_true; + return result; +} + + + + +/************************************************************************* +This subroutine is used to initialize CQM. By default, empty NxN model is +generated, with Alpha=Lambda=Theta=0.0 and zero b. + +Previously allocated buffer variables are reused as much as possible. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqminit(ae_int_t n, convexquadraticmodel* s, ae_state *_state) +{ + ae_int_t i; + + + s->n = n; + s->k = 0; + s->nfree = n; + s->ecakind = -1; + s->alpha = 0.0; + s->tau = 0.0; + s->theta = 0.0; + s->ismaintermchanged = ae_true; + s->issecondarytermchanged = ae_true; + s->islineartermchanged = ae_true; + s->isactivesetchanged = ae_true; + bvectorsetlengthatleast(&s->activeset, n, _state); + rvectorsetlengthatleast(&s->xc, n, _state); + rvectorsetlengthatleast(&s->eb, n, _state); + rvectorsetlengthatleast(&s->tq1, n, _state); + rvectorsetlengthatleast(&s->txc, n, _state); + rvectorsetlengthatleast(&s->tb, n, _state); + rvectorsetlengthatleast(&s->b, s->n, _state); + rvectorsetlengthatleast(&s->tk1, s->n, _state); + for(i=0; i<=n-1; i++) + { + s->activeset.ptr.p_bool[i] = ae_false; + s->xc.ptr.p_double[i] = 0.0; + s->b.ptr.p_double[i] = 0.0; + } +} + + +/************************************************************************* +This subroutine changes main quadratic term of the model. + +INPUT PARAMETERS: + S - model + A - NxN matrix, only upper or lower triangle is referenced + IsUpper - True, when matrix is stored in upper triangle + Alpha - multiplier; when Alpha=0, A is not referenced at all + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmseta(convexquadraticmodel* s, + /* Real */ ae_matrix* a, + ae_bool isupper, + double alpha, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + double v; + + + ae_assert(ae_isfinite(alpha, _state)&&ae_fp_greater_eq(alpha,0), "CQMSetA: Alpha<0 or is not finite number", _state); + ae_assert(ae_fp_eq(alpha,0)||isfinitertrmatrix(a, s->n, isupper, _state), "CQMSetA: A is not finite NxN matrix", _state); + s->alpha = alpha; + if( ae_fp_greater(alpha,0) ) + { + rmatrixsetlengthatleast(&s->a, s->n, s->n, _state); + rmatrixsetlengthatleast(&s->ecadense, s->n, s->n, _state); + rmatrixsetlengthatleast(&s->tq2dense, s->n, s->n, _state); + for(i=0; i<=s->n-1; i++) + { + for(j=i; j<=s->n-1; j++) + { + if( isupper ) + { + v = a->ptr.pp_double[i][j]; + } + else + { + v = a->ptr.pp_double[j][i]; + } + s->a.ptr.pp_double[i][j] = v; + s->a.ptr.pp_double[j][i] = v; + } + } + } + s->ismaintermchanged = ae_true; +} + + +/************************************************************************* +This subroutine rewrites diagonal of the main quadratic term of the model +(dense A) by vector Z/Alpha (current value of the Alpha coefficient is +used). + +IMPORTANT: in case model has no dense quadratic term, this function + allocates N*N dense matrix of zeros, and fills its diagonal by + non-zero values. + +INPUT PARAMETERS: + S - model + Z - new diagonal, array[N] + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmrewritedensediagonal(convexquadraticmodel* s, + /* Real */ ae_vector* z, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + + + n = s->n; + if( ae_fp_eq(s->alpha,0) ) + { + rmatrixsetlengthatleast(&s->a, s->n, s->n, _state); + rmatrixsetlengthatleast(&s->ecadense, s->n, s->n, _state); + rmatrixsetlengthatleast(&s->tq2dense, s->n, s->n, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + s->a.ptr.pp_double[i][j] = 0.0; + } + } + s->alpha = 1.0; + } + for(i=0; i<=s->n-1; i++) + { + s->a.ptr.pp_double[i][i] = z->ptr.p_double[i]/s->alpha; + } + s->ismaintermchanged = ae_true; +} + + +/************************************************************************* +This subroutine changes diagonal quadratic term of the model. + +INPUT PARAMETERS: + S - model + D - array[N], semidefinite diagonal matrix + Tau - multiplier; when Tau=0, D is not referenced at all + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmsetd(convexquadraticmodel* s, + /* Real */ ae_vector* d, + double tau, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(ae_isfinite(tau, _state)&&ae_fp_greater_eq(tau,0), "CQMSetD: Tau<0 or is not finite number", _state); + ae_assert(ae_fp_eq(tau,0)||isfinitevector(d, s->n, _state), "CQMSetD: D is not finite Nx1 vector", _state); + s->tau = tau; + if( ae_fp_greater(tau,0) ) + { + rvectorsetlengthatleast(&s->d, s->n, _state); + rvectorsetlengthatleast(&s->ecadiag, s->n, _state); + rvectorsetlengthatleast(&s->tq2diag, s->n, _state); + for(i=0; i<=s->n-1; i++) + { + ae_assert(ae_fp_greater_eq(d->ptr.p_double[i],0), "CQMSetD: D[i]<0", _state); + s->d.ptr.p_double[i] = d->ptr.p_double[i]; + } + } + s->ismaintermchanged = ae_true; +} + + +/************************************************************************* +This subroutine drops main quadratic term A from the model. It is same as +call to CQMSetA() with zero A, but gives better performance because +algorithm knows that matrix is zero and can optimize subsequent +calculations. + +INPUT PARAMETERS: + S - model + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmdropa(convexquadraticmodel* s, ae_state *_state) +{ + + + s->alpha = 0.0; + s->ismaintermchanged = ae_true; +} + + +/************************************************************************* +This subroutine changes linear term of the model + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmsetb(convexquadraticmodel* s, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(isfinitevector(b, s->n, _state), "CQMSetB: B is not finite vector", _state); + rvectorsetlengthatleast(&s->b, s->n, _state); + for(i=0; i<=s->n-1; i++) + { + s->b.ptr.p_double[i] = b->ptr.p_double[i]; + } + s->islineartermchanged = ae_true; +} + + +/************************************************************************* +This subroutine changes linear term of the model + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmsetq(convexquadraticmodel* s, + /* Real */ ae_matrix* q, + /* Real */ ae_vector* r, + ae_int_t k, + double theta, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + + + ae_assert(k>=0, "CQMSetQ: K<0", _state); + ae_assert((k==0||ae_fp_eq(theta,0))||apservisfinitematrix(q, k, s->n, _state), "CQMSetQ: Q is not finite matrix", _state); + ae_assert((k==0||ae_fp_eq(theta,0))||isfinitevector(r, k, _state), "CQMSetQ: R is not finite vector", _state); + ae_assert(ae_isfinite(theta, _state)&&ae_fp_greater_eq(theta,0), "CQMSetQ: Theta<0 or is not finite number", _state); + + /* + * degenerate case: K=0 or Theta=0 + */ + if( k==0||ae_fp_eq(theta,0) ) + { + s->k = 0; + s->theta = 0; + s->issecondarytermchanged = ae_true; + return; + } + + /* + * General case: both Theta>0 and K>0 + */ + s->k = k; + s->theta = theta; + rmatrixsetlengthatleast(&s->q, s->k, s->n, _state); + rvectorsetlengthatleast(&s->r, s->k, _state); + rmatrixsetlengthatleast(&s->eq, s->k, s->n, _state); + rmatrixsetlengthatleast(&s->eccm, s->k, s->k, _state); + rmatrixsetlengthatleast(&s->tk2, s->k, s->n, _state); + for(i=0; i<=s->k-1; i++) + { + for(j=0; j<=s->n-1; j++) + { + s->q.ptr.pp_double[i][j] = q->ptr.pp_double[i][j]; + } + s->r.ptr.p_double[i] = r->ptr.p_double[i]; + } + s->issecondarytermchanged = ae_true; +} + + +/************************************************************************* +This subroutine changes active set + +INPUT PARAMETERS + S - model + X - array[N], constraint values + ActiveSet- array[N], active set. If ActiveSet[I]=True, then I-th + variables is constrained to X[I]. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmsetactiveset(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Boolean */ ae_vector* activeset, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(x->cnt>=s->n, "CQMSetActiveSet: Length(X)cnt>=s->n, "CQMSetActiveSet: Length(ActiveSet)n-1; i++) + { + s->isactivesetchanged = s->isactivesetchanged||(s->activeset.ptr.p_bool[i]&&!activeset->ptr.p_bool[i]); + s->isactivesetchanged = s->isactivesetchanged||(activeset->ptr.p_bool[i]&&!s->activeset.ptr.p_bool[i]); + s->activeset.ptr.p_bool[i] = activeset->ptr.p_bool[i]; + if( activeset->ptr.p_bool[i] ) + { + ae_assert(ae_isfinite(x->ptr.p_double[i], _state), "CQMSetActiveSet: X[] contains infinite constraints", _state); + s->isactivesetchanged = s->isactivesetchanged||ae_fp_neq(s->xc.ptr.p_double[i],x->ptr.p_double[i]); + s->xc.ptr.p_double[i] = x->ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This subroutine evaluates model at X. Active constraints are ignored. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +double cqmeval(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double v; + double result; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMEval: X is not finite vector", _state); + result = 0.0; + + /* + * main quadratic term + */ + if( ae_fp_greater(s->alpha,0) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + result = result+s->alpha*0.5*x->ptr.p_double[i]*s->a.ptr.pp_double[i][j]*x->ptr.p_double[j]; + } + } + } + if( ae_fp_greater(s->tau,0) ) + { + for(i=0; i<=n-1; i++) + { + result = result+0.5*ae_sqr(x->ptr.p_double[i], _state)*s->tau*s->d.ptr.p_double[i]; + } + } + + /* + * secondary quadratic term + */ + if( ae_fp_greater(s->theta,0) ) + { + for(i=0; i<=s->k-1; i++) + { + v = ae_v_dotproduct(&s->q.ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + result = result+0.5*s->theta*ae_sqr(v-s->r.ptr.p_double[i], _state); + } + } + + /* + * linear term + */ + for(i=0; i<=s->n-1; i++) + { + result = result+x->ptr.p_double[i]*s->b.ptr.p_double[i]; + } + return result; +} + + +/************************************************************************* +This subroutine evaluates model at X. Active constraints are ignored. +It returns: + R - model value + Noise- estimate of the numerical noise in data + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmevalx(convexquadraticmodel* s, + /* Real */ ae_vector* x, + double* r, + double* noise, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double v; + double v2; + double mxq; + double eps; + + *r = 0; + *noise = 0; + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMEval: X is not finite vector", _state); + *r = 0.0; + *noise = 0.0; + eps = 2*ae_machineepsilon; + mxq = 0.0; + + /* + * Main quadratic term. + * + * Noise from the main quadratic term is equal to the + * maximum summand in the term. + */ + if( ae_fp_greater(s->alpha,0) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + v = s->alpha*0.5*x->ptr.p_double[i]*s->a.ptr.pp_double[i][j]*x->ptr.p_double[j]; + *r = *r+v; + *noise = ae_maxreal(*noise, eps*ae_fabs(v, _state), _state); + } + } + } + if( ae_fp_greater(s->tau,0) ) + { + for(i=0; i<=n-1; i++) + { + v = 0.5*ae_sqr(x->ptr.p_double[i], _state)*s->tau*s->d.ptr.p_double[i]; + *r = *r+v; + *noise = ae_maxreal(*noise, eps*ae_fabs(v, _state), _state); + } + } + + /* + * secondary quadratic term + * + * Noise from the secondary quadratic term is estimated as follows: + * * noise in qi*x-r[i] is estimated as + * Eps*MXQ = Eps*max(|r[i]|, |q[i,j]*x[j]|) + * * noise in (qi*x-r[i])^2 is estimated as + * NOISE = (|qi*x-r[i]|+Eps*MXQ)^2-(|qi*x-r[i]|)^2 + * = Eps*MXQ*(2*|qi*x-r[i]|+Eps*MXQ) + */ + if( ae_fp_greater(s->theta,0) ) + { + for(i=0; i<=s->k-1; i++) + { + v = 0.0; + mxq = ae_fabs(s->r.ptr.p_double[i], _state); + for(j=0; j<=n-1; j++) + { + v2 = s->q.ptr.pp_double[i][j]*x->ptr.p_double[j]; + v = v+v2; + mxq = ae_maxreal(mxq, ae_fabs(v2, _state), _state); + } + *r = *r+0.5*s->theta*ae_sqr(v-s->r.ptr.p_double[i], _state); + *noise = ae_maxreal(*noise, eps*mxq*(2*ae_fabs(v-s->r.ptr.p_double[i], _state)+eps*mxq), _state); + } + } + + /* + * linear term + */ + for(i=0; i<=s->n-1; i++) + { + *r = *r+x->ptr.p_double[i]*s->b.ptr.p_double[i]; + *noise = ae_maxreal(*noise, eps*ae_fabs(x->ptr.p_double[i]*s->b.ptr.p_double[i], _state), _state); + } + + /* + * Final update of the noise + */ + *noise = n*(*noise); +} + + +/************************************************************************* +This subroutine evaluates gradient of the model; active constraints are +ignored. + +INPUT PARAMETERS: + S - convex model + X - point, array[N] + G - possibly preallocated buffer; resized, if too small + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmgradunconstrained(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* g, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double v; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMEvalGradUnconstrained: X is not finite vector", _state); + rvectorsetlengthatleast(g, n, _state); + for(i=0; i<=n-1; i++) + { + g->ptr.p_double[i] = 0; + } + + /* + * main quadratic term + */ + if( ae_fp_greater(s->alpha,0) ) + { + for(i=0; i<=n-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+s->alpha*s->a.ptr.pp_double[i][j]*x->ptr.p_double[j]; + } + g->ptr.p_double[i] = g->ptr.p_double[i]+v; + } + } + if( ae_fp_greater(s->tau,0) ) + { + for(i=0; i<=n-1; i++) + { + g->ptr.p_double[i] = g->ptr.p_double[i]+x->ptr.p_double[i]*s->tau*s->d.ptr.p_double[i]; + } + } + + /* + * secondary quadratic term + */ + if( ae_fp_greater(s->theta,0) ) + { + for(i=0; i<=s->k-1; i++) + { + v = ae_v_dotproduct(&s->q.ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = s->theta*(v-s->r.ptr.p_double[i]); + ae_v_addd(&g->ptr.p_double[0], 1, &s->q.ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + } + + /* + * linear term + */ + for(i=0; i<=n-1; i++) + { + g->ptr.p_double[i] = g->ptr.p_double[i]+s->b.ptr.p_double[i]; + } +} + + +/************************************************************************* +This subroutine evaluates x'*(0.5*alpha*A+tau*D)*x + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +double cqmxtadx2(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double result; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMEval: X is not finite vector", _state); + result = 0.0; + + /* + * main quadratic term + */ + if( ae_fp_greater(s->alpha,0) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + result = result+s->alpha*0.5*x->ptr.p_double[i]*s->a.ptr.pp_double[i][j]*x->ptr.p_double[j]; + } + } + } + if( ae_fp_greater(s->tau,0) ) + { + for(i=0; i<=n-1; i++) + { + result = result+0.5*ae_sqr(x->ptr.p_double[i], _state)*s->tau*s->d.ptr.p_double[i]; + } + } + return result; +} + + +/************************************************************************* +This subroutine evaluates (0.5*alpha*A+tau*D)*x + +Y is automatically resized if needed + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmadx(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double v; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMEval: X is not finite vector", _state); + rvectorsetlengthatleast(y, n, _state); + + /* + * main quadratic term + */ + for(i=0; i<=n-1; i++) + { + y->ptr.p_double[i] = 0; + } + if( ae_fp_greater(s->alpha,0) ) + { + for(i=0; i<=n-1; i++) + { + v = ae_v_dotproduct(&s->a.ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + y->ptr.p_double[i] = y->ptr.p_double[i]+s->alpha*v; + } + } + if( ae_fp_greater(s->tau,0) ) + { + for(i=0; i<=n-1; i++) + { + y->ptr.p_double[i] = y->ptr.p_double[i]+x->ptr.p_double[i]*s->tau*s->d.ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This subroutine finds optimum of the model. It returns False on failure +(indefinite/semidefinite matrix). Optimum is found subject to active +constraints. + +INPUT PARAMETERS + S - model + X - possibly preallocated buffer; automatically resized, if + too small enough. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool cqmconstrainedoptimum(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nfree; + ae_int_t k; + ae_int_t i; + double v; + ae_int_t cidx0; + ae_int_t itidx; + ae_bool result; + + + + /* + * Rebuild internal structures + */ + if( !cqmodels_cqmrebuild(s, _state) ) + { + result = ae_false; + return result; + } + n = s->n; + k = s->k; + nfree = s->nfree; + result = ae_true; + + /* + * Calculate initial point for the iterative refinement: + * * free components are set to zero + * * constrained components are set to their constrained values + */ + rvectorsetlengthatleast(x, n, _state); + for(i=0; i<=n-1; i++) + { + if( s->activeset.ptr.p_bool[i] ) + { + x->ptr.p_double[i] = s->xc.ptr.p_double[i]; + } + else + { + x->ptr.p_double[i] = 0; + } + } + + /* + * Iterative refinement. + * + * In an ideal world without numerical errors it would be enough + * to make just one Newton step from initial point: + * x_new = -H^(-1)*grad(x=0) + * However, roundoff errors can significantly deteriorate quality + * of the solution. So we have to recalculate gradient and to + * perform Newton steps several times. + * + * Below we perform fixed number of Newton iterations. + */ + for(itidx=0; itidx<=cqmodels_newtonrefinementits-1; itidx++) + { + + /* + * Calculate gradient at the current point. + * Move free components of the gradient in the beginning. + */ + cqmgradunconstrained(s, x, &s->tmpg, _state); + cidx0 = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + s->tmpg.ptr.p_double[cidx0] = s->tmpg.ptr.p_double[i]; + cidx0 = cidx0+1; + } + } + + /* + * Free components of the extrema are calculated in the first NFree elements of TXC. + * + * First, we have to calculate original Newton step, without rank-K perturbations + */ + ae_v_moveneg(&s->txc.ptr.p_double[0], 1, &s->tmpg.ptr.p_double[0], 1, ae_v_len(0,nfree-1)); + cqmodels_cqmsolveea(s, &s->txc, &s->tmp0, _state); + + /* + * Then, we account for rank-K correction. + * Woodbury matrix identity is used. + */ + if( s->k>0&&ae_fp_greater(s->theta,0) ) + { + rvectorsetlengthatleast(&s->tmp0, ae_maxint(nfree, k, _state), _state); + rvectorsetlengthatleast(&s->tmp1, ae_maxint(nfree, k, _state), _state); + ae_v_moveneg(&s->tmp1.ptr.p_double[0], 1, &s->tmpg.ptr.p_double[0], 1, ae_v_len(0,nfree-1)); + cqmodels_cqmsolveea(s, &s->tmp1, &s->tmp0, _state); + for(i=0; i<=k-1; i++) + { + v = ae_v_dotproduct(&s->eq.ptr.pp_double[i][0], 1, &s->tmp1.ptr.p_double[0], 1, ae_v_len(0,nfree-1)); + s->tmp0.ptr.p_double[i] = v; + } + fblscholeskysolve(&s->eccm, 1.0, k, ae_true, &s->tmp0, &s->tmp1, _state); + for(i=0; i<=nfree-1; i++) + { + s->tmp1.ptr.p_double[i] = 0.0; + } + for(i=0; i<=k-1; i++) + { + v = s->tmp0.ptr.p_double[i]; + ae_v_addd(&s->tmp1.ptr.p_double[0], 1, &s->eq.ptr.pp_double[i][0], 1, ae_v_len(0,nfree-1), v); + } + cqmodels_cqmsolveea(s, &s->tmp1, &s->tmp0, _state); + ae_v_sub(&s->txc.ptr.p_double[0], 1, &s->tmp1.ptr.p_double[0], 1, ae_v_len(0,nfree-1)); + } + + /* + * Unpack components from TXC into X. We pass through all + * free components of X and add our step. + */ + cidx0 = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + x->ptr.p_double[i] = x->ptr.p_double[i]+s->txc.ptr.p_double[cidx0]; + cidx0 = cidx0+1; + } + } + } + return result; +} + + +/************************************************************************* +This function scales vector by multiplying it by inverse of the diagonal +of the Hessian matrix. It should be used to accelerate steepest descent +phase of the QP solver. + +Although it is called "scale-grad", it can be called for any vector, +whether it is gradient, anti-gradient, or just some vector. + +This function does NOT takes into account current set of constraints, it +just performs matrix-vector multiplication without taking into account +constraints. + +INPUT PARAMETERS: + S - model + X - vector to scale + +OUTPUT PARAMETERS: + X - scaled vector + +NOTE: + when called for non-SPD matrices, it silently skips components of X + which correspond to zero or negative diagonal elements. + +NOTE: + this function uses diagonals of A and D; it ignores Q - rank-K term of + the quadratic model. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +void cqmscalevector(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double v; + + + n = s->n; + for(i=0; i<=n-1; i++) + { + v = 0.0; + if( ae_fp_greater(s->alpha,0) ) + { + v = v+s->a.ptr.pp_double[i][i]; + } + if( ae_fp_greater(s->tau,0) ) + { + v = v+s->d.ptr.p_double[i]; + } + if( ae_fp_greater(v,0) ) + { + x->ptr.p_double[i] = x->ptr.p_double[i]/v; + } + } +} + + +/************************************************************************* +This subroutine calls CQMRebuild() and evaluates model at X subject to +active constraints. + +It is intended for debug purposes only, because it evaluates model by +means of temporaries, which were calculated by CQMRebuild(). The only +purpose of this function is to check correctness of CQMRebuild() by +comparing results of this function with ones obtained by CQMEval(), which +is used as reference point. The idea is that significant deviation in +results of these two functions is evidence of some error in the +CQMRebuild(). + +NOTE: suffix T denotes that temporaries marked by T-prefix are used. There + is one more variant of this function, which uses "effective" model + built by CQMRebuild(). + +NOTE2: in case CQMRebuild() fails (due to model non-convexity), this + function returns NAN. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +double cqmdebugconstrainedevalt(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nfree; + ae_int_t i; + ae_int_t j; + double v; + double result; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMDebugConstrainedEvalT: X is not finite vector", _state); + if( !cqmodels_cqmrebuild(s, _state) ) + { + result = _state->v_nan; + return result; + } + result = 0.0; + nfree = s->nfree; + + /* + * Reorder variables + */ + j = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + ae_assert(jtxc.ptr.p_double[j] = x->ptr.p_double[i]; + j = j+1; + } + } + + /* + * TQ2, TQ1, TQ0 + * + */ + if( ae_fp_greater(s->alpha,0) ) + { + + /* + * Dense TQ2 + */ + for(i=0; i<=nfree-1; i++) + { + for(j=0; j<=nfree-1; j++) + { + result = result+0.5*s->txc.ptr.p_double[i]*s->tq2dense.ptr.pp_double[i][j]*s->txc.ptr.p_double[j]; + } + } + } + else + { + + /* + * Diagonal TQ2 + */ + for(i=0; i<=nfree-1; i++) + { + result = result+0.5*s->tq2diag.ptr.p_double[i]*ae_sqr(s->txc.ptr.p_double[i], _state); + } + } + for(i=0; i<=nfree-1; i++) + { + result = result+s->tq1.ptr.p_double[i]*s->txc.ptr.p_double[i]; + } + result = result+s->tq0; + + /* + * TK2, TK1, TK0 + */ + if( s->k>0&&ae_fp_greater(s->theta,0) ) + { + for(i=0; i<=s->k-1; i++) + { + v = 0; + for(j=0; j<=nfree-1; j++) + { + v = v+s->tk2.ptr.pp_double[i][j]*s->txc.ptr.p_double[j]; + } + result = result+0.5*ae_sqr(v, _state); + } + for(i=0; i<=nfree-1; i++) + { + result = result+s->tk1.ptr.p_double[i]*s->txc.ptr.p_double[i]; + } + result = result+s->tk0; + } + + /* + * TB (Bf and Bc parts) + */ + for(i=0; i<=n-1; i++) + { + result = result+s->tb.ptr.p_double[i]*s->txc.ptr.p_double[i]; + } + return result; +} + + +/************************************************************************* +This subroutine calls CQMRebuild() and evaluates model at X subject to +active constraints. + +It is intended for debug purposes only, because it evaluates model by +means of "effective" matrices built by CQMRebuild(). The only purpose of +this function is to check correctness of CQMRebuild() by comparing results +of this function with ones obtained by CQMEval(), which is used as +reference point. The idea is that significant deviation in results of +these two functions is evidence of some error in the CQMRebuild(). + +NOTE: suffix E denotes that effective matrices. There is one more variant + of this function, which uses temporary matrices built by + CQMRebuild(). + +NOTE2: in case CQMRebuild() fails (due to model non-convexity), this + function returns NAN. + + -- ALGLIB -- + Copyright 12.06.2012 by Bochkanov Sergey +*************************************************************************/ +double cqmdebugconstrainedevale(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nfree; + ae_int_t i; + ae_int_t j; + double v; + double result; + + + n = s->n; + ae_assert(isfinitevector(x, n, _state), "CQMDebugConstrainedEvalE: X is not finite vector", _state); + if( !cqmodels_cqmrebuild(s, _state) ) + { + result = _state->v_nan; + return result; + } + result = 0.0; + nfree = s->nfree; + + /* + * Reorder variables + */ + j = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + ae_assert(jtxc.ptr.p_double[j] = x->ptr.p_double[i]; + j = j+1; + } + } + + /* + * ECA + */ + ae_assert((s->ecakind==0||s->ecakind==1)||(s->ecakind==-1&&nfree==0), "CQMDebugConstrainedEvalE: unexpected ECAKind", _state); + if( s->ecakind==0 ) + { + + /* + * Dense ECA + */ + for(i=0; i<=nfree-1; i++) + { + v = 0.0; + for(j=i; j<=nfree-1; j++) + { + v = v+s->ecadense.ptr.pp_double[i][j]*s->txc.ptr.p_double[j]; + } + result = result+0.5*ae_sqr(v, _state); + } + } + if( s->ecakind==1 ) + { + + /* + * Diagonal ECA + */ + for(i=0; i<=nfree-1; i++) + { + result = result+0.5*ae_sqr(s->ecadiag.ptr.p_double[i]*s->txc.ptr.p_double[i], _state); + } + } + + /* + * EQ + */ + for(i=0; i<=s->k-1; i++) + { + v = 0.0; + for(j=0; j<=nfree-1; j++) + { + v = v+s->eq.ptr.pp_double[i][j]*s->txc.ptr.p_double[j]; + } + result = result+0.5*ae_sqr(v, _state); + } + + /* + * EB + */ + for(i=0; i<=nfree-1; i++) + { + result = result+s->eb.ptr.p_double[i]*s->txc.ptr.p_double[i]; + } + + /* + * EC + */ + result = result+s->ec; + return result; +} + + +/************************************************************************* +Internal function, rebuilds "effective" model subject to constraints. +Returns False on failure (non-SPD main quadratic term) + + -- ALGLIB -- + Copyright 10.05.2011 by Bochkanov Sergey +*************************************************************************/ +static ae_bool cqmodels_cqmrebuild(convexquadraticmodel* s, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nfree; + ae_int_t k; + ae_int_t i; + ae_int_t j; + ae_int_t ridx0; + ae_int_t ridx1; + ae_int_t cidx0; + ae_int_t cidx1; + double v; + ae_bool result; + + + if( ae_fp_eq(s->alpha,0)&&ae_fp_eq(s->tau,0) ) + { + + /* + * Non-SPD model, quick exit + */ + result = ae_false; + return result; + } + result = ae_true; + n = s->n; + k = s->k; + + /* + * Determine number of free variables. + * Fill TXC - array whose last N-NFree elements store constraints. + */ + if( s->isactivesetchanged ) + { + s->nfree = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + s->nfree = s->nfree+1; + } + } + j = s->nfree; + for(i=0; i<=n-1; i++) + { + if( s->activeset.ptr.p_bool[i] ) + { + s->txc.ptr.p_double[j] = s->xc.ptr.p_double[i]; + j = j+1; + } + } + } + nfree = s->nfree; + + /* + * Re-evaluate TQ2/TQ1/TQ0, if needed + */ + if( s->isactivesetchanged||s->ismaintermchanged ) + { + + /* + * Handle cases Alpha>0 and Alpha=0 separately: + * * in the first case we have dense matrix + * * in the second one we have diagonal matrix, which can be + * handled more efficiently + */ + if( ae_fp_greater(s->alpha,0) ) + { + + /* + * Alpha>0, dense QP + * + * Split variables into two groups - free (F) and constrained (C). Reorder + * variables in such way that free vars come first, constrained are last: + * x = [xf, xc]. + * + * Main quadratic term x'*(alpha*A+tau*D)*x now splits into quadratic part, + * linear part and constant part: + * ( alpha*Aff+tau*Df alpha*Afc ) ( xf ) + * 0.5*( xf' xc' )*( )*( ) = + * ( alpha*Acf alpha*Acc+tau*Dc ) ( xc ) + * + * = 0.5*xf'*(alpha*Aff+tau*Df)*xf + (alpha*Afc*xc)'*xf + 0.5*xc'(alpha*Acc+tau*Dc)*xc + * + * We store these parts into temporary variables: + * * alpha*Aff+tau*Df, alpha*Afc, alpha*Acc+tau*Dc are stored into upper + * triangle of TQ2 + * * alpha*Afc*xc is stored into TQ1 + * * 0.5*xc'(alpha*Acc+tau*Dc)*xc is stored into TQ0 + * + * Below comes first part of the work - generation of TQ2: + * * we pass through rows of A and copy I-th row into upper block (Aff/Afc) or + * lower one (Acf/Acc) of TQ2, depending on presence of X[i] in the active set. + * RIdx0 variable contains current position for insertion into upper block, + * RIdx1 contains current position for insertion into lower one. + * * within each row, we copy J-th element into left half (Aff/Acf) or right + * one (Afc/Acc), depending on presence of X[j] in the active set. CIdx0 + * contains current position for insertion into left block, CIdx1 contains + * position for insertion into right one. + * * during copying, we multiply elements by alpha and add diagonal matrix D. + */ + ridx0 = 0; + ridx1 = s->nfree; + for(i=0; i<=n-1; i++) + { + cidx0 = 0; + cidx1 = s->nfree; + for(j=0; j<=n-1; j++) + { + if( !s->activeset.ptr.p_bool[i]&&!s->activeset.ptr.p_bool[j] ) + { + + /* + * Element belongs to Aff + */ + v = s->alpha*s->a.ptr.pp_double[i][j]; + if( i==j&&ae_fp_greater(s->tau,0) ) + { + v = v+s->tau*s->d.ptr.p_double[i]; + } + s->tq2dense.ptr.pp_double[ridx0][cidx0] = v; + } + if( !s->activeset.ptr.p_bool[i]&&s->activeset.ptr.p_bool[j] ) + { + + /* + * Element belongs to Afc + */ + s->tq2dense.ptr.pp_double[ridx0][cidx1] = s->alpha*s->a.ptr.pp_double[i][j]; + } + if( s->activeset.ptr.p_bool[i]&&!s->activeset.ptr.p_bool[j] ) + { + + /* + * Element belongs to Acf + */ + s->tq2dense.ptr.pp_double[ridx1][cidx0] = s->alpha*s->a.ptr.pp_double[i][j]; + } + if( s->activeset.ptr.p_bool[i]&&s->activeset.ptr.p_bool[j] ) + { + + /* + * Element belongs to Acc + */ + v = s->alpha*s->a.ptr.pp_double[i][j]; + if( i==j&&ae_fp_greater(s->tau,0) ) + { + v = v+s->tau*s->d.ptr.p_double[i]; + } + s->tq2dense.ptr.pp_double[ridx1][cidx1] = v; + } + if( s->activeset.ptr.p_bool[j] ) + { + cidx1 = cidx1+1; + } + else + { + cidx0 = cidx0+1; + } + } + if( s->activeset.ptr.p_bool[i] ) + { + ridx1 = ridx1+1; + } + else + { + ridx0 = ridx0+1; + } + } + + /* + * Now we have TQ2, and we can evaluate TQ1. + * In the special case when we have Alpha=0, NFree=0 or NFree=N, + * TQ1 is filled by zeros. + */ + for(i=0; i<=n-1; i++) + { + s->tq1.ptr.p_double[i] = 0.0; + } + if( s->nfree>0&&s->nfreenfree, n-s->nfree, &s->tq2dense, 0, s->nfree, 0, &s->txc, s->nfree, &s->tq1, 0, _state); + } + + /* + * And finally, we evaluate TQ0. + */ + v = 0.0; + for(i=s->nfree; i<=n-1; i++) + { + for(j=s->nfree; j<=n-1; j++) + { + v = v+0.5*s->txc.ptr.p_double[i]*s->tq2dense.ptr.pp_double[i][j]*s->txc.ptr.p_double[j]; + } + } + s->tq0 = v; + } + else + { + + /* + * Alpha=0, diagonal QP + * + * Split variables into two groups - free (F) and constrained (C). Reorder + * variables in such way that free vars come first, constrained are last: + * x = [xf, xc]. + * + * Main quadratic term x'*(tau*D)*x now splits into quadratic and constant + * parts: + * ( tau*Df ) ( xf ) + * 0.5*( xf' xc' )*( )*( ) = + * ( tau*Dc ) ( xc ) + * + * = 0.5*xf'*(tau*Df)*xf + 0.5*xc'(tau*Dc)*xc + * + * We store these parts into temporary variables: + * * tau*Df is stored in TQ2Diag + * * 0.5*xc'(tau*Dc)*xc is stored into TQ0 + */ + s->tq0 = 0.0; + ridx0 = 0; + for(i=0; i<=n-1; i++) + { + if( !s->activeset.ptr.p_bool[i] ) + { + s->tq2diag.ptr.p_double[ridx0] = s->tau*s->d.ptr.p_double[i]; + ridx0 = ridx0+1; + } + else + { + s->tq0 = s->tq0+0.5*s->tau*s->d.ptr.p_double[i]*ae_sqr(s->xc.ptr.p_double[i], _state); + } + } + for(i=0; i<=n-1; i++) + { + s->tq1.ptr.p_double[i] = 0.0; + } + } + } + + /* + * Re-evaluate TK2/TK1/TK0, if needed + */ + if( s->isactivesetchanged||s->issecondarytermchanged ) + { + + /* + * Split variables into two groups - free (F) and constrained (C). Reorder + * variables in such way that free vars come first, constrained are last: + * x = [xf, xc]. + * + * Secondary term theta*(Q*x-r)'*(Q*x-r) now splits into quadratic part, + * linear part and constant part: + * ( ( xf ) )' ( ( xf ) ) + * 0.5*theta*( (Qf Qc)'*( ) - r ) * ( (Qf Qc)'*( ) - r ) = + * ( ( xc ) ) ( ( xc ) ) + * + * = 0.5*theta*xf'*(Qf'*Qf)*xf + theta*((Qc*xc-r)'*Qf)*xf + + * + theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) + * + * We store these parts into temporary variables: + * * sqrt(theta)*Qf is stored into TK2 + * * theta*((Qc*xc-r)'*Qf) is stored into TK1 + * * theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) is stored into TK0 + * + * We use several other temporaries to store intermediate results: + * * Tmp0 - to store Qc*xc-r + * * Tmp1 - to store Qc*xc + * + * Generation of TK2/TK1/TK0 is performed as follows: + * * we fill TK2/TK1/TK0 (to handle K=0 or Theta=0) + * * other steps are performed only for K>0 and Theta>0 + * * we pass through columns of Q and copy I-th column into left block (Qf) or + * right one (Qc) of TK2, depending on presence of X[i] in the active set. + * CIdx0 variable contains current position for insertion into upper block, + * CIdx1 contains current position for insertion into lower one. + * * we calculate Qc*xc-r and store it into Tmp0 + * * we calculate TK0 and TK1 + * * we multiply leading part of TK2 which stores Qf by sqrt(theta) + * it is important to perform this step AFTER calculation of TK0 and TK1, + * because we need original (non-modified) Qf to calculate TK0 and TK1. + */ + for(j=0; j<=n-1; j++) + { + for(i=0; i<=k-1; i++) + { + s->tk2.ptr.pp_double[i][j] = 0.0; + } + s->tk1.ptr.p_double[j] = 0.0; + } + s->tk0 = 0.0; + if( s->k>0&&ae_fp_greater(s->theta,0) ) + { + + /* + * Split Q into Qf and Qc + * Calculate Qc*xc-r, store in Tmp0 + */ + rvectorsetlengthatleast(&s->tmp0, k, _state); + rvectorsetlengthatleast(&s->tmp1, k, _state); + cidx0 = 0; + cidx1 = nfree; + for(i=0; i<=k-1; i++) + { + s->tmp1.ptr.p_double[i] = 0.0; + } + for(j=0; j<=n-1; j++) + { + if( s->activeset.ptr.p_bool[j] ) + { + for(i=0; i<=k-1; i++) + { + s->tk2.ptr.pp_double[i][cidx1] = s->q.ptr.pp_double[i][j]; + s->tmp1.ptr.p_double[i] = s->tmp1.ptr.p_double[i]+s->q.ptr.pp_double[i][j]*s->txc.ptr.p_double[cidx1]; + } + cidx1 = cidx1+1; + } + else + { + for(i=0; i<=k-1; i++) + { + s->tk2.ptr.pp_double[i][cidx0] = s->q.ptr.pp_double[i][j]; + } + cidx0 = cidx0+1; + } + } + for(i=0; i<=k-1; i++) + { + s->tmp0.ptr.p_double[i] = s->tmp1.ptr.p_double[i]-s->r.ptr.p_double[i]; + } + + /* + * Calculate TK0 + */ + v = 0.0; + for(i=0; i<=k-1; i++) + { + v = v+s->theta*(0.5*ae_sqr(s->tmp1.ptr.p_double[i], _state)-s->r.ptr.p_double[i]*s->tmp0.ptr.p_double[i]-0.5*ae_sqr(s->r.ptr.p_double[i], _state)); + } + s->tk0 = v; + + /* + * Calculate TK1 + */ + if( nfree>0 ) + { + for(i=0; i<=k-1; i++) + { + v = s->theta*s->tmp0.ptr.p_double[i]; + ae_v_addd(&s->tk1.ptr.p_double[0], 1, &s->tk2.ptr.pp_double[i][0], 1, ae_v_len(0,nfree-1), v); + } + } + + /* + * Calculate TK2 + */ + if( nfree>0 ) + { + v = ae_sqrt(s->theta, _state); + for(i=0; i<=k-1; i++) + { + ae_v_muld(&s->tk2.ptr.pp_double[i][0], 1, ae_v_len(0,nfree-1), v); + } + } + } + } + + /* + * Re-evaluate TB + */ + if( s->isactivesetchanged||s->islineartermchanged ) + { + ridx0 = 0; + ridx1 = nfree; + for(i=0; i<=n-1; i++) + { + if( s->activeset.ptr.p_bool[i] ) + { + s->tb.ptr.p_double[ridx1] = s->b.ptr.p_double[i]; + ridx1 = ridx1+1; + } + else + { + s->tb.ptr.p_double[ridx0] = s->b.ptr.p_double[i]; + ridx0 = ridx0+1; + } + } + } + + /* + * Compose ECA: either dense ECA or diagonal ECA + */ + if( (s->isactivesetchanged||s->ismaintermchanged)&&nfree>0 ) + { + if( ae_fp_greater(s->alpha,0) ) + { + + /* + * Dense ECA + */ + s->ecakind = 0; + for(i=0; i<=nfree-1; i++) + { + for(j=i; j<=nfree-1; j++) + { + s->ecadense.ptr.pp_double[i][j] = s->tq2dense.ptr.pp_double[i][j]; + } + } + if( !spdmatrixcholeskyrec(&s->ecadense, 0, nfree, ae_true, &s->tmp0, _state) ) + { + result = ae_false; + return result; + } + } + else + { + + /* + * Diagonal ECA + */ + s->ecakind = 1; + for(i=0; i<=nfree-1; i++) + { + if( ae_fp_less(s->tq2diag.ptr.p_double[i],0) ) + { + result = ae_false; + return result; + } + s->ecadiag.ptr.p_double[i] = ae_sqrt(s->tq2diag.ptr.p_double[i], _state); + } + } + } + + /* + * Compose EQ + */ + if( s->isactivesetchanged||s->issecondarytermchanged ) + { + for(i=0; i<=k-1; i++) + { + for(j=0; j<=nfree-1; j++) + { + s->eq.ptr.pp_double[i][j] = s->tk2.ptr.pp_double[i][j]; + } + } + } + + /* + * Calculate ECCM + */ + if( ((((s->isactivesetchanged||s->ismaintermchanged)||s->issecondarytermchanged)&&s->k>0)&&ae_fp_greater(s->theta,0))&&nfree>0 ) + { + + /* + * Calculate ECCM - Cholesky factor of the "effective" capacitance + * matrix CM = I + EQ*inv(EffectiveA)*EQ'. + * + * We calculate CM as follows: + * CM = I + EQ*inv(EffectiveA)*EQ' + * = I + EQ*ECA^(-1)*ECA^(-T)*EQ' + * = I + (EQ*ECA^(-1))*(EQ*ECA^(-1))' + * + * Then we perform Cholesky decomposition of CM. + */ + rmatrixsetlengthatleast(&s->tmp2, k, n, _state); + rmatrixcopy(k, nfree, &s->eq, 0, 0, &s->tmp2, 0, 0, _state); + ae_assert(s->ecakind==0||s->ecakind==1, "CQMRebuild: unexpected ECAKind", _state); + if( s->ecakind==0 ) + { + rmatrixrighttrsm(k, nfree, &s->ecadense, 0, 0, ae_true, ae_false, 0, &s->tmp2, 0, 0, _state); + } + if( s->ecakind==1 ) + { + for(i=0; i<=k-1; i++) + { + for(j=0; j<=nfree-1; j++) + { + s->tmp2.ptr.pp_double[i][j] = s->tmp2.ptr.pp_double[i][j]/s->ecadiag.ptr.p_double[j]; + } + } + } + for(i=0; i<=k-1; i++) + { + for(j=0; j<=k-1; j++) + { + s->eccm.ptr.pp_double[i][j] = 0.0; + } + s->eccm.ptr.pp_double[i][i] = 1.0; + } + rmatrixsyrk(k, nfree, 1.0, &s->tmp2, 0, 0, 0, 1.0, &s->eccm, 0, 0, ae_true, _state); + if( !spdmatrixcholeskyrec(&s->eccm, 0, k, ae_true, &s->tmp0, _state) ) + { + result = ae_false; + return result; + } + } + + /* + * Compose EB and EC + * + * NOTE: because these quantities are cheap to compute, we do not + * use caching here. + */ + for(i=0; i<=nfree-1; i++) + { + s->eb.ptr.p_double[i] = s->tq1.ptr.p_double[i]+s->tk1.ptr.p_double[i]+s->tb.ptr.p_double[i]; + } + s->ec = s->tq0+s->tk0; + for(i=nfree; i<=n-1; i++) + { + s->ec = s->ec+s->tb.ptr.p_double[i]*s->txc.ptr.p_double[i]; + } + + /* + * Change cache status - everything is cached + */ + s->ismaintermchanged = ae_false; + s->issecondarytermchanged = ae_false; + s->islineartermchanged = ae_false; + s->isactivesetchanged = ae_false; + return result; +} + + +/************************************************************************* +Internal function, solves system Effective_A*x = b. +It should be called after successful completion of CQMRebuild(). + +INPUT PARAMETERS: + S - quadratic model, after call to CQMRebuild() + X - right part B, array[S.NFree] + Tmp - temporary array, automatically reallocated if needed + +OUTPUT PARAMETERS: + X - solution, array[S.NFree] + +NOTE: when called with zero S.NFree, returns silently +NOTE: this function assumes that EA is non-degenerate + + -- ALGLIB -- + Copyright 10.05.2011 by Bochkanov Sergey +*************************************************************************/ +static void cqmodels_cqmsolveea(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert((s->ecakind==0||s->ecakind==1)||(s->ecakind==-1&&s->nfree==0), "CQMSolveEA: unexpected ECAKind", _state); + if( s->ecakind==0 ) + { + + /* + * Dense ECA, use FBLSCholeskySolve() dense solver. + */ + fblscholeskysolve(&s->ecadense, 1.0, s->nfree, ae_true, x, tmp, _state); + } + if( s->ecakind==1 ) + { + + /* + * Diagonal ECA + */ + for(i=0; i<=s->nfree-1; i++) + { + x->ptr.p_double[i] = x->ptr.p_double[i]/ae_sqr(s->ecadiag.ptr.p_double[i], _state); + } + } +} + + +ae_bool _convexquadraticmodel_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + convexquadraticmodel *p = (convexquadraticmodel*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->a, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->q, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->r, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->activeset, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tq2dense, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tk2, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tq2diag, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tq1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tk1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->txc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tb, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->ecadense, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->eq, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->eccm, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ecadiag, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->eb, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmp2, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _convexquadraticmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + convexquadraticmodel *dst = (convexquadraticmodel*)_dst; + convexquadraticmodel *src = (convexquadraticmodel*)_src; + dst->n = src->n; + dst->k = src->k; + dst->alpha = src->alpha; + dst->tau = src->tau; + dst->theta = src->theta; + if( !ae_matrix_init_copy(&dst->a, &src->a, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->q, &src->q, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->r, &src->r, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xc, &src->xc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->activeset, &src->activeset, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tq2dense, &src->tq2dense, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tk2, &src->tk2, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tq2diag, &src->tq2diag, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tq1, &src->tq1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tk1, &src->tk1, _state, make_automatic) ) + return ae_false; + dst->tq0 = src->tq0; + dst->tk0 = src->tk0; + if( !ae_vector_init_copy(&dst->txc, &src->txc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tb, &src->tb, _state, make_automatic) ) + return ae_false; + dst->nfree = src->nfree; + dst->ecakind = src->ecakind; + if( !ae_matrix_init_copy(&dst->ecadense, &src->ecadense, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->eq, &src->eq, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->eccm, &src->eccm, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->ecadiag, &src->ecadiag, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->eb, &src->eb, _state, make_automatic) ) + return ae_false; + dst->ec = src->ec; + if( !ae_vector_init_copy(&dst->tmp0, &src->tmp0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp1, &src->tmp1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpg, &src->tmpg, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmp2, &src->tmp2, _state, make_automatic) ) + return ae_false; + dst->ismaintermchanged = src->ismaintermchanged; + dst->issecondarytermchanged = src->issecondarytermchanged; + dst->islineartermchanged = src->islineartermchanged; + dst->isactivesetchanged = src->isactivesetchanged; + return ae_true; +} + + +void _convexquadraticmodel_clear(void* _p) +{ + convexquadraticmodel *p = (convexquadraticmodel*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->a); + ae_matrix_clear(&p->q); + ae_vector_clear(&p->b); + ae_vector_clear(&p->r); + ae_vector_clear(&p->xc); + ae_vector_clear(&p->d); + ae_vector_clear(&p->activeset); + ae_matrix_clear(&p->tq2dense); + ae_matrix_clear(&p->tk2); + ae_vector_clear(&p->tq2diag); + ae_vector_clear(&p->tq1); + ae_vector_clear(&p->tk1); + ae_vector_clear(&p->txc); + ae_vector_clear(&p->tb); + ae_matrix_clear(&p->ecadense); + ae_matrix_clear(&p->eq); + ae_matrix_clear(&p->eccm); + ae_vector_clear(&p->ecadiag); + ae_vector_clear(&p->eb); + ae_vector_clear(&p->tmp0); + ae_vector_clear(&p->tmp1); + ae_vector_clear(&p->tmpg); + ae_matrix_clear(&p->tmp2); +} + + +void _convexquadraticmodel_destroy(void* _p) +{ + convexquadraticmodel *p = (convexquadraticmodel*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->a); + ae_matrix_destroy(&p->q); + ae_vector_destroy(&p->b); + ae_vector_destroy(&p->r); + ae_vector_destroy(&p->xc); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->activeset); + ae_matrix_destroy(&p->tq2dense); + ae_matrix_destroy(&p->tk2); + ae_vector_destroy(&p->tq2diag); + ae_vector_destroy(&p->tq1); + ae_vector_destroy(&p->tk1); + ae_vector_destroy(&p->txc); + ae_vector_destroy(&p->tb); + ae_matrix_destroy(&p->ecadense); + ae_matrix_destroy(&p->eq); + ae_matrix_destroy(&p->eccm); + ae_vector_destroy(&p->ecadiag); + ae_vector_destroy(&p->eb); + ae_vector_destroy(&p->tmp0); + ae_vector_destroy(&p->tmp1); + ae_vector_destroy(&p->tmpg); + ae_matrix_destroy(&p->tmp2); +} + + + + +/************************************************************************* +This subroutine is used to initialize SNNLS solver. + +By default, empty NNLS problem is produced, but we allocated enough space +to store problems with NSMax+NDMax columns and NRMax rows. It is good +place to provide algorithm with initial estimate of the space requirements, +although you may underestimate problem size or even pass zero estimates - +in this case buffer variables will be resized automatically when you set +NNLS problem. + +Previously allocated buffer variables are reused as much as possible. This +function does not clear structure completely, it tries to preserve as much +dynamically allocated memory as possible. + + -- ALGLIB -- + Copyright 10.10.2012 by Bochkanov Sergey +*************************************************************************/ +void snnlsinit(ae_int_t nsmax, + ae_int_t ndmax, + ae_int_t nrmax, + snnlssolver* s, + ae_state *_state) +{ + + + s->ns = 0; + s->nd = 0; + s->nr = 0; + rmatrixsetlengthatleast(&s->densea, nrmax, ndmax, _state); + rmatrixsetlengthatleast(&s->tmpca, nrmax, ndmax, _state); + rmatrixsetlengthatleast(&s->tmpz, ndmax, ndmax, _state); + rvectorsetlengthatleast(&s->b, nrmax, _state); + bvectorsetlengthatleast(&s->nnc, nsmax+ndmax, _state); + s->debugflops = 0.0; + s->debugmaxnewton = 0; + s->refinementits = snnls_iterativerefinementits; +} + + +/************************************************************************* +This subroutine is used to set NNLS problem: + + ( [ 1 | ] [ ] [ ] )^2 + ( [ 1 | ] [ ] [ ] ) + min ( [ 1 | Ad ] * [ x ] - [ b ] ) s.t. x>=0 + ( [ | ] [ ] [ ] ) + ( [ | ] [ ] [ ] ) + +where: +* identity matrix has NS*NS size (NS<=NR, NS can be zero) +* dense matrix Ad has NR*ND size +* b is NR*1 vector +* x is (NS+ND)*1 vector +* all elements of x are non-negative (this constraint can be removed later + by calling SNNLSDropNNC() function) + +Previously allocated buffer variables are reused as much as possible. +After you set problem, you can solve it with SNNLSSolve(). + +INPUT PARAMETERS: + S - SNNLS solver, must be initialized with SNNLSInit() call + A - array[NR,ND], dense part of the system + B - array[NR], right part + NS - size of the sparse part of the system, 0<=NS<=NR + ND - size of the dense part of the system, ND>=0 + NR - rows count, NR>0 + +NOTE: + 1. You can have NS+ND=0, solver will correctly accept such combination + and return empty array as problem solution. + + -- ALGLIB -- + Copyright 10.10.2012 by Bochkanov Sergey +*************************************************************************/ +void snnlssetproblem(snnlssolver* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* b, + ae_int_t ns, + ae_int_t nd, + ae_int_t nr, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(nd>=0, "SNNLSSetProblem: ND<0", _state); + ae_assert(ns>=0, "SNNLSSetProblem: NS<0", _state); + ae_assert(nr>0, "SNNLSSetProblem: NR<=0", _state); + ae_assert(ns<=nr, "SNNLSSetProblem: NS>NR", _state); + ae_assert(a->rows>=nr||nd==0, "SNNLSSetProblem: rows(A)cols>=nd, "SNNLSSetProblem: cols(A)cnt>=nr, "SNNLSSetProblem: length(B)ns = ns; + s->nd = nd; + s->nr = nr; + if( nd>0 ) + { + rmatrixsetlengthatleast(&s->densea, nr, nd, _state); + for(i=0; i<=nr-1; i++) + { + ae_v_move(&s->densea.ptr.pp_double[i][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,nd-1)); + } + } + rvectorsetlengthatleast(&s->b, nr, _state); + ae_v_move(&s->b.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,nr-1)); + bvectorsetlengthatleast(&s->nnc, ns+nd, _state); + for(i=0; i<=ns+nd-1; i++) + { + s->nnc.ptr.p_bool[i] = ae_true; + } +} + + +/************************************************************************* +This subroutine drops non-negativity constraint from the problem set by +SNNLSSetProblem() call. This function must be called AFTER problem is set, +because each SetProblem() call resets constraints to their default state +(all constraints are present). + +INPUT PARAMETERS: + S - SNNLS solver, must be initialized with SNNLSInit() call, + problem must be set with SNNLSSetProblem() call. + Idx - constraint index, 0<=IDX=0, "SNNLSDropNNC: Idx<0", _state); + ae_assert(idxns+s->nd, "SNNLSDropNNC: Idx>=NS+ND", _state); + s->nnc.ptr.p_bool[idx] = ae_false; +} + + +/************************************************************************* +This subroutine is used to solve NNLS problem. + +INPUT PARAMETERS: + S - SNNLS solver, must be initialized with SNNLSInit() call and + problem must be set up with SNNLSSetProblem() call. + X - possibly preallocated buffer, automatically resized if needed + +OUTPUT PARAMETERS: + X - array[NS+ND], solution + +NOTE: + 1. You can have NS+ND=0, solver will correctly accept such combination + and return empty array as problem solution. + + 2. Internal field S.DebugFLOPS contains rough estimate of FLOPs used + to solve problem. It can be used for debugging purposes. This field + is real-valued. + + -- ALGLIB -- + Copyright 10.10.2012 by Bochkanov Sergey +*************************************************************************/ +void snnlssolve(snnlssolver* s, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t ns; + ae_int_t nd; + ae_int_t nr; + ae_int_t nsc; + ae_int_t ndc; + ae_int_t newtoncnt; + ae_bool terminationneeded; + double eps; + double fcur; + double fprev; + double fcand; + double noiselevel; + double noisetolerance; + double stplen; + double d2; + double d1; + double d0; + ae_bool wasactivation; + ae_int_t rfsits; + double lambdav; + double v0; + double v1; + double v; + + + + /* + * Prepare + */ + ns = s->ns; + nd = s->nd; + nr = s->nr; + s->debugflops = 0.0; + + /* + * Handle special cases: + * * NS+ND=0 + * * ND=0 + */ + if( ns+nd==0 ) + { + return; + } + if( nd==0 ) + { + rvectorsetlengthatleast(x, ns, _state); + for(i=0; i<=ns-1; i++) + { + x->ptr.p_double[i] = s->b.ptr.p_double[i]; + if( s->nnc.ptr.p_bool[i] ) + { + x->ptr.p_double[i] = ae_maxreal(x->ptr.p_double[i], 0.0, _state); + } + } + return; + } + + /* + * Main cycle of BLEIC-SNNLS algorithm. + * Below we assume that ND>0. + */ + rvectorsetlengthatleast(x, ns+nd, _state); + rvectorsetlengthatleast(&s->xn, ns+nd, _state); + rvectorsetlengthatleast(&s->g, ns+nd, _state); + rvectorsetlengthatleast(&s->d, ns+nd, _state); + rvectorsetlengthatleast(&s->r, nr, _state); + rvectorsetlengthatleast(&s->diagaa, nd, _state); + rvectorsetlengthatleast(&s->dx, ns+nd, _state); + for(i=0; i<=ns+nd-1; i++) + { + x->ptr.p_double[i] = 0.0; + } + eps = 2*ae_machineepsilon; + noisetolerance = 10.0; + lambdav = 1.0E6*ae_machineepsilon; + newtoncnt = 0; + for(;;) + { + + /* + * Phase 1: perform steepest descent step. + * + * TerminationNeeded control variable is set on exit from this loop: + * * TerminationNeeded=False in case we have to proceed to Phase 2 (Newton step) + * * TerminationNeeded=True in case we found solution (step along projected gradient is small enough) + * + * Temporaries used: + * * R (I|A)*x-b + * + * NOTE 1. It is assumed that initial point X is feasible. This feasibility + * is retained during all iterations. + */ + terminationneeded = ae_false; + for(;;) + { + + /* + * Calculate gradient G and constrained descent direction D + */ + for(i=0; i<=nr-1; i++) + { + v = ae_v_dotproduct(&s->densea.ptr.pp_double[i][0], 1, &x->ptr.p_double[ns], 1, ae_v_len(0,nd-1)); + if( iptr.p_double[i]; + } + s->r.ptr.p_double[i] = v-s->b.ptr.p_double[i]; + } + for(i=0; i<=ns-1; i++) + { + s->g.ptr.p_double[i] = s->r.ptr.p_double[i]; + } + for(i=ns; i<=ns+nd-1; i++) + { + s->g.ptr.p_double[i] = 0.0; + } + for(i=0; i<=nr-1; i++) + { + v = s->r.ptr.p_double[i]; + ae_v_addd(&s->g.ptr.p_double[ns], 1, &s->densea.ptr.pp_double[i][0], 1, ae_v_len(ns,ns+nd-1), v); + } + for(i=0; i<=ns+nd-1; i++) + { + if( (s->nnc.ptr.p_bool[i]&&ae_fp_less_eq(x->ptr.p_double[i],0))&&ae_fp_greater(s->g.ptr.p_double[i],0) ) + { + s->d.ptr.p_double[i] = 0.0; + } + else + { + s->d.ptr.p_double[i] = -s->g.ptr.p_double[i]; + } + } + s->debugflops = s->debugflops+2*2*nr*nd; + + /* + * Build quadratic model of F along descent direction: + * F(x+alpha*d) = D2*alpha^2 + D1*alpha + D0 + * + * Estimate numerical noise in the X (noise level is used + * to classify step as singificant or insignificant). Noise + * comes from two sources: + * * noise when calculating rows of (I|A)*x + * * noise when calculating norm of residual + * + * In case function curvature is negative or product of descent + * direction and gradient is non-negative, iterations are terminated. + * + * NOTE: D0 is not actually used, but we prefer to maintain it. + */ + fprev = ae_v_dotproduct(&s->r.ptr.p_double[0], 1, &s->r.ptr.p_double[0], 1, ae_v_len(0,nr-1)); + fprev = fprev/2; + noiselevel = 0.0; + for(i=0; i<=nr-1; i++) + { + + /* + * Estimate noise introduced by I-th row of (I|A)*x + */ + v = 0.0; + if( iptr.p_double[i]; + } + for(j=0; j<=nd-1; j++) + { + v = ae_maxreal(v, eps*ae_fabs(s->densea.ptr.pp_double[i][j]*x->ptr.p_double[ns+j], _state), _state); + } + v = 2*ae_fabs(s->r.ptr.p_double[i]*v, _state)+v*v; + + /* + * Add to summary noise in the model + */ + noiselevel = noiselevel+v; + } + noiselevel = ae_maxreal(noiselevel, eps*fprev, _state); + d2 = 0.0; + for(i=0; i<=nr-1; i++) + { + v = ae_v_dotproduct(&s->densea.ptr.pp_double[i][0], 1, &s->d.ptr.p_double[ns], 1, ae_v_len(0,nd-1)); + if( id.ptr.p_double[i]; + } + d2 = d2+0.5*ae_sqr(v, _state); + } + v = ae_v_dotproduct(&s->d.ptr.p_double[0], 1, &s->g.ptr.p_double[0], 1, ae_v_len(0,ns+nd-1)); + d1 = v; + d0 = fprev; + if( ae_fp_less_eq(d2,0)||ae_fp_greater_eq(d1,0) ) + { + terminationneeded = ae_true; + break; + } + s->debugflops = s->debugflops+2*nr*nd; + touchreal(&d0, _state); + + /* + * Perform full (unconstrained) step with length StpLen in direction D. + * + * We can terminate iterations in case one of two criteria is met: + * 1. function change is dominated by noise (or function actually increased + * instead of decreasing) + * 2. relative change in X is small enough + * + * First condition is not enough to guarantee algorithm termination because + * sometimes our noise estimate is too optimistic (say, in situations when + * function value at solition is zero). + */ + stplen = -d1/(2*d2); + ae_v_move(&s->xn.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,ns+nd-1)); + ae_v_addd(&s->xn.ptr.p_double[0], 1, &s->d.ptr.p_double[0], 1, ae_v_len(0,ns+nd-1), stplen); + fcand = 0.0; + for(i=0; i<=nr-1; i++) + { + v = ae_v_dotproduct(&s->densea.ptr.pp_double[i][0], 1, &s->xn.ptr.p_double[ns], 1, ae_v_len(0,nd-1)); + if( ixn.ptr.p_double[i]; + } + fcand = fcand+0.5*ae_sqr(v-s->b.ptr.p_double[i], _state); + } + s->debugflops = s->debugflops+2*nr*nd; + if( ae_fp_greater_eq(fcand,fprev-noiselevel*noisetolerance) ) + { + terminationneeded = ae_true; + break; + } + v = 0; + for(i=0; i<=ns+nd-1; i++) + { + v0 = ae_fabs(x->ptr.p_double[i], _state); + v1 = ae_fabs(s->xn.ptr.p_double[i], _state); + if( ae_fp_neq(v0,0)||ae_fp_neq(v1,0) ) + { + v = ae_maxreal(v, ae_fabs(x->ptr.p_double[i]-s->xn.ptr.p_double[i], _state)/ae_maxreal(v0, v1, _state), _state); + } + } + if( ae_fp_less_eq(v,eps*noisetolerance) ) + { + terminationneeded = ae_true; + break; + } + + /* + * Perform step one more time, now with non-negativity constraints. + * + * NOTE: complicated code below which deals with VarIdx temporary makes + * sure that in case unconstrained step leads us outside of feasible + * area, we activate at least one constraint. + */ + wasactivation = snnls_boundedstepandactivation(x, &s->xn, &s->nnc, ns+nd, _state); + fcur = 0.0; + for(i=0; i<=nr-1; i++) + { + v = ae_v_dotproduct(&s->densea.ptr.pp_double[i][0], 1, &x->ptr.p_double[ns], 1, ae_v_len(0,nd-1)); + if( iptr.p_double[i]; + } + fcur = fcur+0.5*ae_sqr(v-s->b.ptr.p_double[i], _state); + } + s->debugflops = s->debugflops+2*nr*nd; + + /* + * Depending on results, decide what to do: + * 1. In case step was performed without activation of constraints, + * we proceed to Newton method + * 2. In case there was activated at least one constraint, we repeat + * steepest descent step. + */ + if( !wasactivation ) + { + + /* + * Step without activation, proceed to Newton + */ + break; + } + } + if( terminationneeded ) + { + break; + } + + /* + * Phase 2: Newton method. + */ + rvectorsetlengthatleast(&s->cx, ns+nd, _state); + ivectorsetlengthatleast(&s->columnmap, ns+nd, _state); + ivectorsetlengthatleast(&s->rowmap, nr, _state); + rmatrixsetlengthatleast(&s->tmpca, nr, nd, _state); + rmatrixsetlengthatleast(&s->tmpz, nd, nd, _state); + rvectorsetlengthatleast(&s->cborg, nr, _state); + rvectorsetlengthatleast(&s->cb, nr, _state); + terminationneeded = ae_false; + for(;;) + { + + /* + * Prepare equality constrained subproblem with NSC<=NS "sparse" + * variables and NDC<=ND "dense" variables. + * + * First, we reorder variables (columns) and move all unconstrained + * variables "to the left", ColumnMap stores this permutation. + * + * Then, we reorder first NS rows of A and first NS elements of B in + * such way that we still have identity matrix in first NSC columns + * of problem. This permutation is stored in RowMap. + */ + nsc = 0; + ndc = 0; + for(i=0; i<=ns-1; i++) + { + if( !(s->nnc.ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],0)) ) + { + s->columnmap.ptr.p_int[nsc] = i; + nsc = nsc+1; + } + } + for(i=ns; i<=ns+nd-1; i++) + { + if( !(s->nnc.ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],0)) ) + { + s->columnmap.ptr.p_int[nsc+ndc] = i; + ndc = ndc+1; + } + } + for(i=0; i<=nsc-1; i++) + { + s->rowmap.ptr.p_int[i] = s->columnmap.ptr.p_int[i]; + } + j = nsc; + for(i=0; i<=ns-1; i++) + { + if( s->nnc.ptr.p_bool[i]&&ae_fp_eq(x->ptr.p_double[i],0) ) + { + s->rowmap.ptr.p_int[j] = i; + j = j+1; + } + } + for(i=ns; i<=nr-1; i++) + { + s->rowmap.ptr.p_int[i] = i; + } + + /* + * Now, permutations are ready, and we can copy/reorder + * A, B and X to CA, CB and CX. + */ + for(i=0; i<=nsc+ndc-1; i++) + { + s->cx.ptr.p_double[i] = x->ptr.p_double[s->columnmap.ptr.p_int[i]]; + } + for(i=0; i<=nr-1; i++) + { + for(j=0; j<=ndc-1; j++) + { + s->tmpca.ptr.pp_double[i][j] = s->densea.ptr.pp_double[s->rowmap.ptr.p_int[i]][s->columnmap.ptr.p_int[nsc+j]-ns]; + } + s->cb.ptr.p_double[i] = s->b.ptr.p_double[s->rowmap.ptr.p_int[i]]; + } + + /* + * Solve equality constrained subproblem. + */ + if( ndc>0 ) + { + + /* + * NDC>0. + * + * Solve subproblem using Newton-type algorithm. We have a + * NR*(NSC+NDC) linear least squares subproblem + * + * | ( I AU ) ( XU ) ( BU ) |^2 + * min | ( ) * ( ) - ( ) | + * | ( 0 AL ) ( XL ) ( BL ) | + * + * where: + * * I is a NSC*NSC identity matrix + * * AU is NSC*NDC dense matrix (first NSC rows of CA) + * * AL is (NR-NSC)*NDC dense matrix (next NR-NSC rows of CA) + * * BU and BL are correspondingly sized parts of CB + * + * After conversion to normal equations and small regularization, + * we get: + * + * ( I AU ) ( XU ) ( BU ) + * ( )*( ) = ( ) + * ( AU' Y ) ( XL ) ( AU'*BU+AL'*BL ) + * + * where Y = AU'*AU + AL'*AL + lambda*diag(AU'*AU+AL'*AL). + * + * With Schur Complement Method this system can be solved in + * O(NR*NDC^2+NDC^3) operations. In order to solve it we multiply + * first row by AU' and subtract it from the second one. As result, + * we get system + * + * Z*XL = AL'*BL, where Z=AL'*AL+lambda*diag(AU'*AU+AL'*AL) + * + * We can easily solve it for XL, and we can get XU as XU = BU-AU*XL. + * + * We will start solution from calculating Cholesky decomposition of Z. + */ + for(i=0; i<=nr-1; i++) + { + s->cborg.ptr.p_double[i] = s->cb.ptr.p_double[i]; + } + for(i=0; i<=ndc-1; i++) + { + s->diagaa.ptr.p_double[i] = 0; + } + for(i=0; i<=nr-1; i++) + { + for(j=0; j<=ndc-1; j++) + { + s->diagaa.ptr.p_double[j] = s->diagaa.ptr.p_double[j]+ae_sqr(s->tmpca.ptr.pp_double[i][j], _state); + } + } + for(j=0; j<=ndc-1; j++) + { + if( ae_fp_eq(s->diagaa.ptr.p_double[j],0) ) + { + s->diagaa.ptr.p_double[j] = 1; + } + } + for(;;) + { + + /* + * NOTE: we try to factorize Z. In case of failure we increase + * regularization parameter and try again. + */ + s->debugflops = s->debugflops+2*(nr-nsc)*ae_sqr(ndc, _state)+ae_pow(ndc, 3, _state)/3; + for(i=0; i<=ndc-1; i++) + { + for(j=0; j<=ndc-1; j++) + { + s->tmpz.ptr.pp_double[i][j] = 0.0; + } + } + rmatrixsyrk(ndc, nr-nsc, 1.0, &s->tmpca, nsc, 0, 2, 0.0, &s->tmpz, 0, 0, ae_true, _state); + for(i=0; i<=ndc-1; i++) + { + s->tmpz.ptr.pp_double[i][i] = s->tmpz.ptr.pp_double[i][i]+lambdav*s->diagaa.ptr.p_double[i]; + } + if( spdmatrixcholeskyrec(&s->tmpz, 0, ndc, ae_true, &s->tmpcholesky, _state) ) + { + break; + } + lambdav = lambdav*10; + } + + /* + * We have Cholesky decomposition of Z, now we can solve system: + * * we start from initial point CX + * * we perform several iterations of refinement: + * * BU_new := BU_orig - XU_cur - AU*XL_cur + * * BL_new := BL_orig - AL*XL_cur + * * solve for BU_new/BL_new, obtain solution dx + * * XU_cur := XU_cur + dx_u + * * XL_cur := XL_cur + dx_l + * * BU_new/BL_new are stored in CB, original right part is + * stored in CBOrg, correction to X is stored in DX, current + * X is stored in CX + */ + for(rfsits=1; rfsits<=s->refinementits; rfsits++) + { + for(i=0; i<=nr-1; i++) + { + v = ae_v_dotproduct(&s->tmpca.ptr.pp_double[i][0], 1, &s->cx.ptr.p_double[nsc], 1, ae_v_len(0,ndc-1)); + s->cb.ptr.p_double[i] = s->cborg.ptr.p_double[i]-v; + if( icb.ptr.p_double[i] = s->cb.ptr.p_double[i]-s->cx.ptr.p_double[i]; + } + } + s->debugflops = s->debugflops+2*nr*ndc; + for(i=0; i<=ndc-1; i++) + { + s->dx.ptr.p_double[i] = 0.0; + } + for(i=nsc; i<=nr-1; i++) + { + v = s->cb.ptr.p_double[i]; + ae_v_addd(&s->dx.ptr.p_double[0], 1, &s->tmpca.ptr.pp_double[i][0], 1, ae_v_len(0,ndc-1), v); + } + fblscholeskysolve(&s->tmpz, 1.0, ndc, ae_true, &s->dx, &s->tmpcholesky, _state); + s->debugflops = s->debugflops+2*ndc*ndc; + ae_v_add(&s->cx.ptr.p_double[nsc], 1, &s->dx.ptr.p_double[0], 1, ae_v_len(nsc,nsc+ndc-1)); + for(i=0; i<=nsc-1; i++) + { + v = ae_v_dotproduct(&s->tmpca.ptr.pp_double[i][0], 1, &s->dx.ptr.p_double[0], 1, ae_v_len(0,ndc-1)); + s->cx.ptr.p_double[i] = s->cx.ptr.p_double[i]+s->cb.ptr.p_double[i]-v; + } + s->debugflops = s->debugflops+2*nsc*ndc; + } + } + else + { + + /* + * NDC=0. + * + * We have a NR*NSC linear least squares subproblem + * + * min |XU-BU|^2 + * + * solution is easy to find - it is XU=BU! + */ + for(i=0; i<=nsc-1; i++) + { + s->cx.ptr.p_double[i] = s->cb.ptr.p_double[i]; + } + } + for(i=0; i<=ns+nd-1; i++) + { + s->xn.ptr.p_double[i] = x->ptr.p_double[i]; + } + for(i=0; i<=nsc+ndc-1; i++) + { + s->xn.ptr.p_double[s->columnmap.ptr.p_int[i]] = s->cx.ptr.p_double[i]; + } + newtoncnt = newtoncnt+1; + + /* + * Step to candidate point. + * If no constraints was added, accept candidate point XN and move to next phase. + * Terminate, if number of Newton iterations exceeded DebugMaxNewton counter. + */ + terminationneeded = s->debugmaxnewton>0&&newtoncnt>=s->debugmaxnewton; + if( !snnls_boundedstepandactivation(x, &s->xn, &s->nnc, ns+nd, _state) ) + { + break; + } + if( terminationneeded ) + { + break; + } + } + if( terminationneeded ) + { + break; + } + } +} + + +/************************************************************************* +Having feasible current point XC and possibly infeasible candidate point +XN, this function performs longest step from XC to XN which retains +feasibility. In case XN is found to be infeasible, at least one constraint +is activated. + +For example, if we have: + XC=0.5 + XN=-1.2 + x>=0 +then this function will move us to X=0 and activate constraint "x>=0". + +INPUT PARAMETERS: + XC - current point, must be feasible with respect to + all constraints + XN - candidate point, can be infeasible with respect to some + constraints + NNC - NNC[i] is True when I-th variable is non-negatively + constrained + N - variable count + +OUTPUT PARAMETERS: + XC - new position + +RESULT: + True in case at least one constraint was activated by step + + -- ALGLIB -- + Copyright 19.10.2012 by Bochkanov Sergey +*************************************************************************/ +static ae_bool snnls_boundedstepandactivation(/* Real */ ae_vector* xc, + /* Real */ ae_vector* xn, + /* Boolean */ ae_vector* nnc, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + ae_int_t varidx; + double vmax; + double v; + double stplen; + ae_bool result; + + + + /* + * Check constraints. + * + * NOTE: it is important to test for XN[i]ptr.p_bool[i]&&ae_fp_less(xn->ptr.p_double[i],xc->ptr.p_double[i]))&&ae_fp_less_eq(xn->ptr.p_double[i],0.0) ) + { + v = vmax; + vmax = safeminposrv(xc->ptr.p_double[i], xc->ptr.p_double[i]-xn->ptr.p_double[i], vmax, _state); + if( ae_fp_less(vmax,v) ) + { + varidx = i; + } + } + } + stplen = ae_minreal(vmax, 1.0, _state); + + /* + * Perform step with activation. + * + * NOTE: it is important to use (1-StpLen)*XC + StpLen*XN because + * it allows us to step exactly to XN when StpLen=1, even in + * the presence of numerical errors. + */ + for(i=0; i<=n-1; i++) + { + xc->ptr.p_double[i] = (1-stplen)*xc->ptr.p_double[i]+stplen*xn->ptr.p_double[i]; + } + if( varidx>=0 ) + { + xc->ptr.p_double[varidx] = 0.0; + result = ae_true; + } + for(i=0; i<=n-1; i++) + { + if( nnc->ptr.p_bool[i]&&ae_fp_less(xc->ptr.p_double[i],0.0) ) + { + xc->ptr.p_double[i] = 0.0; + result = ae_true; + } + } + return result; +} + + +ae_bool _snnlssolver_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + snnlssolver *p = (snnlssolver*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->densea, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->nnc, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpz, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpca, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->diagaa, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cb, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cborg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->columnmap, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rowmap, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpcholesky, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->r, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _snnlssolver_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + snnlssolver *dst = (snnlssolver*)_dst; + snnlssolver *src = (snnlssolver*)_src; + dst->ns = src->ns; + dst->nd = src->nd; + dst->nr = src->nr; + if( !ae_matrix_init_copy(&dst->densea, &src->densea, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->nnc, &src->nnc, _state, make_automatic) ) + return ae_false; + dst->refinementits = src->refinementits; + dst->debugflops = src->debugflops; + dst->debugmaxnewton = src->debugmaxnewton; + if( !ae_vector_init_copy(&dst->xn, &src->xn, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpz, &src->tmpz, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpca, &src->tmpca, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dx, &src->dx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->diagaa, &src->diagaa, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cb, &src->cb, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cx, &src->cx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cborg, &src->cborg, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->columnmap, &src->columnmap, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rowmap, &src->rowmap, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpcholesky, &src->tmpcholesky, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->r, &src->r, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _snnlssolver_clear(void* _p) +{ + snnlssolver *p = (snnlssolver*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->densea); + ae_vector_clear(&p->b); + ae_vector_clear(&p->nnc); + ae_vector_clear(&p->xn); + ae_matrix_clear(&p->tmpz); + ae_matrix_clear(&p->tmpca); + ae_vector_clear(&p->g); + ae_vector_clear(&p->d); + ae_vector_clear(&p->dx); + ae_vector_clear(&p->diagaa); + ae_vector_clear(&p->cb); + ae_vector_clear(&p->cx); + ae_vector_clear(&p->cborg); + ae_vector_clear(&p->columnmap); + ae_vector_clear(&p->rowmap); + ae_vector_clear(&p->tmpcholesky); + ae_vector_clear(&p->r); +} + + +void _snnlssolver_destroy(void* _p) +{ + snnlssolver *p = (snnlssolver*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->densea); + ae_vector_destroy(&p->b); + ae_vector_destroy(&p->nnc); + ae_vector_destroy(&p->xn); + ae_matrix_destroy(&p->tmpz); + ae_matrix_destroy(&p->tmpca); + ae_vector_destroy(&p->g); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->dx); + ae_vector_destroy(&p->diagaa); + ae_vector_destroy(&p->cb); + ae_vector_destroy(&p->cx); + ae_vector_destroy(&p->cborg); + ae_vector_destroy(&p->columnmap); + ae_vector_destroy(&p->rowmap); + ae_vector_destroy(&p->tmpcholesky); + ae_vector_destroy(&p->r); +} + + + + +/************************************************************************* +This subroutine is used to initialize active set. By default, empty +N-variable model with no constraints is generated. Previously allocated +buffer variables are reused as much as possible. + +Two use cases for this object are described below. + +CASE 1 - STEEPEST DESCENT: + + SASInit() + repeat: + SASReactivateConstraints() + SASDescentDirection() + SASExploreDirection() + SASMoveTo() + until convergence + +CASE 1 - PRECONDITIONED STEEPEST DESCENT: + + SASInit() + repeat: + SASReactivateConstraintsPrec() + SASDescentDirectionPrec() + SASExploreDirection() + SASMoveTo() + until convergence + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasinit(ae_int_t n, sactiveset* s, ae_state *_state) +{ + ae_int_t i; + + + s->n = n; + s->algostate = 0; + + /* + * Constraints + */ + s->constraintschanged = ae_true; + s->nec = 0; + s->nic = 0; + rvectorsetlengthatleast(&s->bndl, n, _state); + bvectorsetlengthatleast(&s->hasbndl, n, _state); + rvectorsetlengthatleast(&s->bndu, n, _state); + bvectorsetlengthatleast(&s->hasbndu, n, _state); + for(i=0; i<=n-1; i++) + { + s->bndl.ptr.p_double[i] = _state->v_neginf; + s->bndu.ptr.p_double[i] = _state->v_posinf; + s->hasbndl.ptr.p_bool[i] = ae_false; + s->hasbndu.ptr.p_bool[i] = ae_false; + } + + /* + * current point, scale + */ + s->hasxc = ae_false; + rvectorsetlengthatleast(&s->xc, n, _state); + rvectorsetlengthatleast(&s->s, n, _state); + rvectorsetlengthatleast(&s->h, n, _state); + for(i=0; i<=n-1; i++) + { + s->xc.ptr.p_double[i] = 0.0; + s->s.ptr.p_double[i] = 1.0; + s->h.ptr.p_double[i] = 1.0; + } + + /* + * Other + */ + rvectorsetlengthatleast(&s->unitdiagonal, n, _state); + for(i=0; i<=n-1; i++) + { + s->unitdiagonal.ptr.p_double[i] = 1.0; + } +} + + +/************************************************************************* +This function sets scaling coefficients for SAS object. + +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 + +During orthogonalization phase, scale is used to calculate drop tolerances +(whether vector is significantly non-zero or not). + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sassetscale(sactiveset* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(state->algostate==0, "SASSetScale: you may change scale only in modification mode", _state); + ae_assert(s->cnt>=state->n, "SASSetScale: Length(S)n-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "SASSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "SASSetScale: S contains zero elements", _state); + } + for(i=0; i<=state->n-1; i++) + { + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE 1: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sassetprecdiag(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(state->algostate==0, "SASSetPrecDiag: you may change preconditioner only in modification mode", _state); + ae_assert(d->cnt>=state->n, "SASSetPrecDiag: D is too short", _state); + for(i=0; i<=state->n-1; i++) + { + ae_assert(ae_isfinite(d->ptr.p_double[i], _state), "SASSetPrecDiag: D contains infinite or NAN elements", _state); + ae_assert(ae_fp_greater(d->ptr.p_double[i],0), "SASSetPrecDiag: D contains non-positive elements", _state); + } + for(i=0; i<=state->n-1; i++) + { + state->h.ptr.p_double[i] = d->ptr.p_double[i]; + } +} + + +/************************************************************************* +This function sets/changes boundary constraints. + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + If some (all) variables are unbounded, you may specify + very small number or -INF. + BndU - upper bounds, array[N]. + If some (all) variables are unbounded, you may specify + very large number or +INF. + +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]. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sassetbc(sactiveset* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + + + ae_assert(state->algostate==0, "SASSetBC: you may change constraints only in modification mode", _state); + n = state->n; + ae_assert(bndl->cnt>=n, "SASSetBC: Length(BndL)cnt>=n, "SASSetBC: Length(BndU)ptr.p_double[i], _state)||ae_isneginf(bndl->ptr.p_double[i], _state), "SASSetBC: BndL contains NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.p_double[i], _state)||ae_isposinf(bndu->ptr.p_double[i], _state), "SASSetBC: BndL contains NAN or -INF", _state); + state->bndl.ptr.p_double[i] = bndl->ptr.p_double[i]; + state->hasbndl.ptr.p_bool[i] = ae_isfinite(bndl->ptr.p_double[i], _state); + state->bndu.ptr.p_double[i] = bndu->ptr.p_double[i]; + state->hasbndu.ptr.p_bool[i] = ae_isfinite(bndu->ptr.p_double[i], _state); + } + state->constraintschanged = ae_true; +} + + +/************************************************************************* +This function sets linear constraints for SAS object. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - SAS structure + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0 + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void sassetlc(sactiveset* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + + + ae_assert(state->algostate==0, "SASSetLC: you may change constraints only in modification mode", _state); + n = state->n; + + /* + * First, check for errors in the inputs + */ + ae_assert(k>=0, "SASSetLC: K<0", _state); + ae_assert(c->cols>=n+1||k==0, "SASSetLC: Cols(C)rows>=k, "SASSetLC: Rows(C)cnt>=k, "SASSetLC: Length(CT)nec = 0; + state->nic = 0; + state->constraintschanged = ae_true; + return; + } + + /* + * Equality constraints are stored first, in the upper + * NEC rows of State.CLEIC matrix. Inequality constraints + * are stored in the next NIC rows. + * + * NOTE: we convert inequality constraints to the form + * A*x<=b before copying them. + */ + rmatrixsetlengthatleast(&state->cleic, k, n+1, _state); + state->nec = 0; + state->nic = 0; + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]==0 ) + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + state->nec = state->nec+1; + } + } + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]!=0 ) + { + if( ct->ptr.p_int[i]>0 ) + { + ae_v_moveneg(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + else + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + state->nic = state->nic+1; + } + } + + /* + * Mark state as changed + */ + state->constraintschanged = ae_true; +} + + +/************************************************************************* +Another variation of SASSetLC(), which accepts linear constraints using +another representation. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - SAS structure + CLEIC - linear constraints, array[NEC+NIC,N+1]. + Each row of C represents one constraint: + * first N elements correspond to coefficients, + * last element corresponds to the right part. + First NEC rows store equality constraints, next NIC - are + inequality ones. + All elements of C (including right part) must be finite. + NEC - number of equality constraints, NEC>=0 + NIC - number of inequality constraints, NIC>=0 + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void sassetlcx(sactiveset* state, + /* Real */ ae_matrix* cleic, + ae_int_t nec, + ae_int_t nic, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + + + ae_assert(state->algostate==0, "SASSetLCX: you may change constraints only in modification mode", _state); + n = state->n; + + /* + * First, check for errors in the inputs + */ + ae_assert(nec>=0, "SASSetLCX: NEC<0", _state); + ae_assert(nic>=0, "SASSetLCX: NIC<0", _state); + ae_assert(cleic->cols>=n+1||nec+nic==0, "SASSetLCX: Cols(CLEIC)rows>=nec+nic, "SASSetLCX: Rows(CLEIC)cleic, nec+nic, n+1, _state); + state->nec = nec; + state->nic = nic; + for(i=0; i<=nec+nic-1; i++) + { + for(j=0; j<=n; j++) + { + state->cleic.ptr.pp_double[i][j] = cleic->ptr.pp_double[i][j]; + } + } + + /* + * Mark state as changed + */ + state->constraintschanged = ae_true; +} + + +/************************************************************************* +This subroutine turns on optimization mode: +1. feasibility in X is enforced (in case X=S.XC and constraints have not + changed, algorithm just uses X without any modifications at all) +2. constraints are marked as "candidate" or "inactive" + +INPUT PARAMETERS: + S - active set object + X - initial point (candidate), array[N]. It is expected that X + contains only finite values (we do not check it). + +OUTPUT PARAMETERS: + S - state is changed + X - initial point can be changed to enforce feasibility + +RESULT: + True in case feasible point was found (mode was changed to "optimization") + False in case no feasible point was found (mode was not changed) + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +ae_bool sasstartoptimization(sactiveset* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + ae_int_t i; + ae_int_t j; + double v; + ae_bool result; + + + ae_assert(state->algostate==0, "SASStartOptimization: already in optimization mode", _state); + result = ae_false; + n = state->n; + nec = state->nec; + nic = state->nic; + + /* + * Enforce feasibility and calculate set of "candidate"/"active" constraints. + * Always active equality constraints are marked as "active", all other constraints + * are marked as "candidate". + */ + ivectorsetlengthatleast(&state->activeset, n+nec+nic, _state); + for(i=0; i<=n-1; i++) + { + if( state->hasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i] ) + { + if( ae_fp_greater(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + return result; + } + } + } + ae_v_move(&state->xc.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( state->nec+state->nic>0 ) + { + + /* + * General linear constraints are present; general code is used. + */ + rvectorsetlengthatleast(&state->tmp0, n, _state); + rvectorsetlengthatleast(&state->tmpfeas, n+state->nic, _state); + rmatrixsetlengthatleast(&state->tmpm0, state->nec+state->nic, n+state->nic+1, _state); + for(i=0; i<=state->nec+state->nic-1; i++) + { + ae_v_move(&state->tmpm0.ptr.pp_double[i][0], 1, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + for(j=n; j<=n+state->nic-1; j++) + { + state->tmpm0.ptr.pp_double[i][j] = 0; + } + if( i>=state->nec ) + { + state->tmpm0.ptr.pp_double[i][n+i-state->nec] = 1.0; + } + state->tmpm0.ptr.pp_double[i][n+state->nic] = state->cleic.ptr.pp_double[i][n]; + } + ae_v_move(&state->tmpfeas.ptr.p_double[0], 1, &state->xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=state->nic-1; i++) + { + v = ae_v_dotproduct(&state->cleic.ptr.pp_double[i+state->nec][0], 1, &state->xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->tmpfeas.ptr.p_double[i+n] = ae_maxreal(state->cleic.ptr.pp_double[i+state->nec][n]-v, 0.0, _state); + } + if( !findfeasiblepoint(&state->tmpfeas, &state->bndl, &state->hasbndl, &state->bndu, &state->hasbndu, n, state->nic, &state->tmpm0, state->nec+state->nic, 1.0E-6, &i, &j, _state) ) + { + return result; + } + ae_v_move(&state->xc.ptr.p_double[0], 1, &state->tmpfeas.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + if( (state->hasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i])&&ae_fp_eq(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + if( (state->hasbndl.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]))||(state->hasbndu.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i])) ) + { + state->activeset.ptr.p_int[i] = 0; + continue; + } + state->activeset.ptr.p_int[i] = -1; + } + for(i=0; i<=state->nec-1; i++) + { + state->activeset.ptr.p_int[n+i] = 1; + } + for(i=0; i<=state->nic-1; i++) + { + if( ae_fp_eq(state->tmpfeas.ptr.p_double[n+i],0) ) + { + state->activeset.ptr.p_int[n+state->nec+i] = 0; + } + else + { + state->activeset.ptr.p_int[n+state->nec+i] = -1; + } + } + } + else + { + + /* + * Only bound constraints are present, quick code can be used + */ + for(i=0; i<=n-1; i++) + { + state->activeset.ptr.p_int[i] = -1; + if( (state->hasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i])&&ae_fp_eq(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->activeset.ptr.p_int[i] = 1; + state->xc.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + continue; + } + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]) ) + { + state->xc.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + state->activeset.ptr.p_int[i] = 0; + continue; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->xc.ptr.p_double[i] = state->bndu.ptr.p_double[i]; + state->activeset.ptr.p_int[i] = 0; + continue; + } + } + } + + /* + * Change state, allocate temporaries + */ + result = ae_true; + state->algostate = 1; + state->basisisready = ae_false; + state->hasxc = ae_true; + rmatrixsetlengthatleast(&state->pbasis, ae_minint(nec+nic, n, _state), n+1, _state); + rmatrixsetlengthatleast(&state->ibasis, ae_minint(nec+nic, n, _state), n+1, _state); + rmatrixsetlengthatleast(&state->sbasis, ae_minint(nec+nic, n, _state), n+1, _state); + return result; +} + + +/************************************************************************* +This function explores search direction and calculates bound for step as +well as information for activation of constraints. + +INPUT PARAMETERS: + State - SAS structure which stores current point and all other + active set related information + D - descent direction to explore + +OUTPUT PARAMETERS: + StpMax - upper limit on step length imposed by yet inactive + constraints. Can be zero in case some constraints + can be activated by zero step. Equal to some large + value in case step is unlimited. + CIdx - -1 for unlimited step, in [0,N+NEC+NIC) in case of + limited step. + VVal - value which is assigned to X[CIdx] during activation. + For CIdx<0 or CIdx>=N some dummy value is assigned to + this parameter. +*************************************************************************/ +void sasexploredirection(sactiveset* state, + /* Real */ ae_vector* d, + double* stpmax, + ae_int_t* cidx, + double* vval, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + ae_int_t i; + double prevmax; + double vc; + double vd; + + *stpmax = 0; + *cidx = 0; + *vval = 0; + + ae_assert(state->algostate==1, "SASExploreDirection: is not in optimization mode", _state); + n = state->n; + nec = state->nec; + nic = state->nic; + *cidx = -1; + *vval = 0; + *stpmax = 1.0E50; + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]<=0 ) + { + ae_assert(!state->hasbndl.ptr.p_bool[i]||ae_fp_greater_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]), "SASExploreDirection: internal error - infeasible X", _state); + ae_assert(!state->hasbndu.ptr.p_bool[i]||ae_fp_less_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]), "SASExploreDirection: internal error - infeasible X", _state); + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(d->ptr.p_double[i],0) ) + { + prevmax = *stpmax; + *stpmax = safeminposrv(state->xc.ptr.p_double[i]-state->bndl.ptr.p_double[i], -d->ptr.p_double[i], *stpmax, _state); + if( ae_fp_less(*stpmax,prevmax) ) + { + *cidx = i; + *vval = state->bndl.ptr.p_double[i]; + } + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(d->ptr.p_double[i],0) ) + { + prevmax = *stpmax; + *stpmax = safeminposrv(state->bndu.ptr.p_double[i]-state->xc.ptr.p_double[i], d->ptr.p_double[i], *stpmax, _state); + if( ae_fp_less(*stpmax,prevmax) ) + { + *cidx = i; + *vval = state->bndu.ptr.p_double[i]; + } + } + } + } + for(i=nec; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]<=0 ) + { + vc = ae_v_dotproduct(&state->cleic.ptr.pp_double[i][0], 1, &state->xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + vc = vc-state->cleic.ptr.pp_double[i][n]; + vd = ae_v_dotproduct(&state->cleic.ptr.pp_double[i][0], 1, &d->ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_less_eq(vd,0) ) + { + continue; + } + if( ae_fp_less(vc,0) ) + { + + /* + * XC is strictly feasible with respect to I-th constraint, + * we can perform non-zero step because there is non-zero distance + * between XC and bound. + */ + prevmax = *stpmax; + *stpmax = safeminposrv(-vc, vd, *stpmax, _state); + if( ae_fp_less(*stpmax,prevmax) ) + { + *cidx = n+i; + } + } + else + { + + /* + * XC is at the boundary (or slightly beyond it), and step vector + * points beyond the boundary. + * + * The only thing we can do is to perform zero step and activate + * I-th constraint. + */ + *stpmax = 0; + *cidx = n+i; + } + } + } +} + + +/************************************************************************* +This subroutine moves current point to XN, in the direction previously +explored with SASExploreDirection() function. + +Step may activate one constraint. It is assumed than XN is approximately +feasible (small error as large as several ulps is possible). Strict +feasibility with respect to bound constraints is enforced during +activation, feasibility with respect to general linear constraints is not +enforced. + +INPUT PARAMETERS: + S - active set object + XN - new point. + NeedAct - True in case one constraint needs activation + CIdx - index of constraint, in [0,N+NEC+NIC). + Ignored if NeedAct is false. + This value is calculated by SASExploreDirection(). + CVal - for CIdx in [0,N) this field stores value which is + assigned to XC[CIdx] during activation. CVal is ignored in + other cases. + This value is calculated by SASExploreDirection(). + +OUTPUT PARAMETERS: + S - current point and list of active constraints are changed. + +RESULT: + >0, in case at least one inactive non-candidate constraint was activated + =0, in case only "candidate" constraints were activated + <0, in case no constraints were activated by the step + +NOTE: in general case State.XC<>XN because activation of constraints may + slightly change current point (to enforce feasibility). + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +ae_int_t sasmoveto(sactiveset* state, + /* Real */ ae_vector* xn, + ae_bool needact, + ae_int_t cidx, + double cval, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + ae_int_t i; + ae_bool wasactivation; + ae_int_t result; + + + ae_assert(state->algostate==1, "SASMoveTo: is not in optimization mode", _state); + n = state->n; + nec = state->nec; + nic = state->nic; + + /* + * Save previous state, update current point + */ + rvectorsetlengthatleast(&state->mtx, n, _state); + ivectorsetlengthatleast(&state->mtas, n+nec+nic, _state); + for(i=0; i<=n-1; i++) + { + state->mtx.ptr.p_double[i] = state->xc.ptr.p_double[i]; + state->xc.ptr.p_double[i] = xn->ptr.p_double[i]; + } + for(i=0; i<=n+nec+nic-1; i++) + { + state->mtas.ptr.p_int[i] = state->activeset.ptr.p_int[i]; + } + + /* + * Activate constraints + */ + wasactivation = ae_false; + if( needact ) + { + + /* + * Activation + */ + ae_assert(cidx>=0&&cidxxc.ptr.p_double[cidx] = cval; + } + state->activeset.ptr.p_int[cidx] = 1; + wasactivation = ae_true; + } + for(i=0; i<=n-1; i++) + { + + /* + * Post-check (some constraints may be activated because of numerical errors) + */ + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]) ) + { + state->xc.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + state->activeset.ptr.p_int[i] = 1; + wasactivation = ae_true; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->xc.ptr.p_double[i] = state->bndu.ptr.p_double[i]; + state->activeset.ptr.p_int[i] = 1; + wasactivation = ae_true; + } + } + + /* + * Determine return status: + * * -1 in case no constraints were activated + * * 0 in case only "candidate" constraints were activated + * * +1 in case at least one "non-candidate" constraint was activated + */ + if( wasactivation ) + { + + /* + * Step activated one/several constraints, but sometimes it is spurious + * activation - RecalculateConstraints() tells us that constraint is + * inactive (negative Largrange multiplier), but step activates it + * because of numerical noise. + * + * This block of code checks whether step activated truly new constraints + * (ones which were not in the active set at the solution): + * + * * for non-boundary constraint it is enough to check that previous value + * of ActiveSet[i] is negative (=far from boundary), and new one is + * positive (=we are at the boundary, constraint is activated). + * + * * for boundary constraints previous criterion won't work. Each variable + * has two constraints, and simply checking their status is not enough - + * we have to correctly identify cases when we leave one boundary + * (PrevActiveSet[i]=0) and move to another boundary (ActiveSet[i]>0). + * Such cases can be identified if we compare previous X with new X. + * + * In case only "candidate" constraints were activated, result variable + * is set to 0. In case at least one new constraint was activated, result + * is set to 1. + */ + result = 0; + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>0&&ae_fp_neq(state->xc.ptr.p_double[i],state->mtx.ptr.p_double[i]) ) + { + result = 1; + } + } + for(i=n; i<=n+state->nec+state->nic-1; i++) + { + if( state->mtas.ptr.p_int[i]<0&&state->activeset.ptr.p_int[i]>0 ) + { + result = 1; + } + } + } + else + { + + /* + * No activation, return -1 + */ + result = -1; + } + + /* + * Invalidate basis + */ + state->basisisready = ae_false; + return result; +} + + +/************************************************************************* +This subroutine performs immediate activation of one constraint: +* "immediate" means that we do not have to move to activate it +* in case boundary constraint is activated, we enforce current point to be + exactly at the boundary + +INPUT PARAMETERS: + S - active set object + CIdx - index of constraint, in [0,N+NEC+NIC). + This value is calculated by SASExploreDirection(). + CVal - for CIdx in [0,N) this field stores value which is + assigned to XC[CIdx] during activation. CVal is ignored in + other cases. + This value is calculated by SASExploreDirection(). + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasimmediateactivation(sactiveset* state, + ae_int_t cidx, + double cval, + ae_state *_state) +{ + + + ae_assert(state->algostate==1, "SASMoveTo: is not in optimization mode", _state); + if( cidxn ) + { + state->xc.ptr.p_double[cidx] = cval; + } + state->activeset.ptr.p_int[cidx] = 1; + state->basisisready = ae_false; +} + + +/************************************************************************* +This subroutine calculates descent direction subject to current active set. + +INPUT PARAMETERS: + S - active set object + G - array[N], gradient + D - possibly prealocated buffer; + automatically resized if needed. + +OUTPUT PARAMETERS: + D - descent direction projected onto current active set. + Components of D which correspond to active boundary + constraints are forced to be exactly zero. + In case D is non-zero, it is normalized to have unit norm. + +NOTE: in case active set has N active constraints (or more), descent + direction is forced to be exactly zero. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasconstraineddescent(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* d, + ae_state *_state) +{ + + + ae_assert(state->algostate==1, "SASConstrainedDescent: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + sactivesets_constraineddescent(state, g, &state->unitdiagonal, &state->ibasis, ae_true, d, _state); +} + + +/************************************************************************* +This subroutine calculates preconditioned descent direction subject to +current active set. + +INPUT PARAMETERS: + S - active set object + G - array[N], gradient + D - possibly prealocated buffer; + automatically resized if needed. + +OUTPUT PARAMETERS: + D - descent direction projected onto current active set. + Components of D which correspond to active boundary + constraints are forced to be exactly zero. + In case D is non-zero, it is normalized to have unit norm. + +NOTE: in case active set has N active constraints (or more), descent + direction is forced to be exactly zero. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasconstraineddescentprec(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* d, + ae_state *_state) +{ + + + ae_assert(state->algostate==1, "SASConstrainedDescentPrec: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + sactivesets_constraineddescent(state, g, &state->h, &state->pbasis, ae_true, d, _state); +} + + +/************************************************************************* +This subroutine calculates product of direction vector and preconditioner +multiplied subject to current active set. + +INPUT PARAMETERS: + S - active set object + D - array[N], direction + +OUTPUT PARAMETERS: + D - preconditioned direction projected onto current active set. + Components of D which correspond to active boundary + constraints are forced to be exactly zero. + +NOTE: in case active set has N active constraints (or more), descent + direction is forced to be exactly zero. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasconstraineddirection(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(state->algostate==1, "SASConstrainedAntigradientPrec: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + sactivesets_constraineddescent(state, d, &state->unitdiagonal, &state->ibasis, ae_false, &state->cdtmp, _state); + for(i=0; i<=state->n-1; i++) + { + d->ptr.p_double[i] = -state->cdtmp.ptr.p_double[i]; + } +} + + +/************************************************************************* +This subroutine calculates product of direction vector and preconditioner +multiplied subject to current active set. + +INPUT PARAMETERS: + S - active set object + D - array[N], direction + +OUTPUT PARAMETERS: + D - preconditioned direction projected onto current active set. + Components of D which correspond to active boundary + constraints are forced to be exactly zero. + +NOTE: in case active set has N active constraints (or more), descent + direction is forced to be exactly zero. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasconstraineddirectionprec(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(state->algostate==1, "SASConstrainedAntigradientPrec: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + sactivesets_constraineddescent(state, d, &state->h, &state->pbasis, ae_false, &state->cdtmp, _state); + for(i=0; i<=state->n-1; i++) + { + d->ptr.p_double[i] = -state->cdtmp.ptr.p_double[i]; + } +} + + +/************************************************************************* +This subroutine performs correction of some (possibly infeasible) point +with respect to a) current active set, b) all boundary constraints, both +active and inactive: + +0) we calculate L1 penalty term for violation of active linear constraints + (one which is returned by SASActiveLCPenalty1() function). +1) first, it performs projection (orthogonal with respect to scale matrix + S) of X into current active set: X -> X1. +2) next, we perform projection with respect to ALL boundary constraints + which are violated at X1: X1 -> X2. +3) X is replaced by X2. + +The idea is that this function can preserve and enforce feasibility during +optimization, and additional penalty parameter can be used to prevent algo +from leaving feasible set because of rounding errors. + +INPUT PARAMETERS: + S - active set object + X - array[N], candidate point + +OUTPUT PARAMETERS: + X - "improved" candidate point: + a) feasible with respect to all boundary constraints + b) feasibility with respect to active set is retained at + good level. + Penalty - penalty term, which can be added to function value if user + wants to penalize violation of constraints (recommended). + +NOTE: this function is not intended to find exact projection (i.e. best + approximation) of X into feasible set. It just improves situation a + bit. + Regular use of this function will help you to retain feasibility + - if you already have something to start with and constrain your + steps is such way that the only source of infeasibility are roundoff + errors. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sascorrection(sactiveset* state, + /* Real */ ae_vector* x, + double* penalty, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + double v; + + *penalty = 0; + + ae_assert(state->algostate==1, "SASCorrection: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + n = state->n; + rvectorsetlengthatleast(&state->corrtmp, n, _state); + + /* + * Calculate penalty term. + */ + *penalty = sasactivelcpenalty1(state, x, _state); + + /* + * Perform projection 1. + * + * This projecton is given by: + * + * x_proj = x - S*S*As'*(As*x-b) + * + * where x is original x before projection, S is a scale matrix, + * As is a matrix of equality constraints (active set) which were + * orthogonalized with respect to inner product given by S (i.e. we + * have As*S*S'*As'=I), b is a right part of the orthogonalized + * constraints. + * + * NOTE: you can verify that x_proj is strictly feasible w.r.t. + * active set by multiplying it by As - you will get + * As*x_proj = As*x - As*x + b = b. + * + * This formula for projection can be obtained by solving + * following minimization problem. + * + * min ||inv(S)*(x_proj-x)||^2 s.t. As*x_proj=b + * + */ + ae_v_move(&state->corrtmp.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=state->basissize-1; i++) + { + v = -state->sbasis.ptr.pp_double[i][n]; + for(j=0; j<=n-1; j++) + { + v = v+state->sbasis.ptr.pp_double[i][j]*state->corrtmp.ptr.p_double[j]; + } + for(j=0; j<=n-1; j++) + { + state->corrtmp.ptr.p_double[j] = state->corrtmp.ptr.p_double[j]-v*state->sbasis.ptr.pp_double[i][j]*ae_sqr(state->s.ptr.p_double[j], _state); + } + } + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>0 ) + { + state->corrtmp.ptr.p_double[i] = state->xc.ptr.p_double[i]; + } + } + + /* + * Perform projection 2 + */ + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = state->corrtmp.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(x->ptr.p_double[i],state->bndl.ptr.p_double[i]) ) + { + x->ptr.p_double[i] = state->bndl.ptr.p_double[i]; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(x->ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + x->ptr.p_double[i] = state->bndu.ptr.p_double[i]; + } + } +} + + +/************************************************************************* +This subroutine returns L1 penalty for violation of active general linear +constraints (violation of boundary or inactive linear constraints is not +added to penalty). + +Penalty term is equal to: + + Penalty = SUM( Abs((C_i*x-R_i)/Alpha_i) ) + +Here: +* summation is performed for I=0...NEC+NIC-1, ActiveSet[N+I]>0 + (only for rows of CLEIC which are in active set) +* C_i is I-th row of CLEIC +* R_i is corresponding right part +* S is a scale matrix +* Alpha_i = ||S*C_i|| - is a scaling coefficient which "normalizes" + I-th summation term according to its scale. + +INPUT PARAMETERS: + S - active set object + X - array[N], candidate point + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +double sasactivelcpenalty1(sactiveset* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + double v; + double alpha; + double p; + double result; + + + ae_assert(state->algostate==1, "SASActiveLCPenalty1: is not in optimization mode", _state); + sasrebuildbasis(state, _state); + n = state->n; + nec = state->nec; + nic = state->nic; + + /* + * Calculate penalty term. + */ + result = 0; + for(i=0; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]>0 ) + { + alpha = 0; + p = -state->cleic.ptr.pp_double[i][n]; + for(j=0; j<=n-1; j++) + { + v = state->cleic.ptr.pp_double[i][j]; + p = p+v*x->ptr.p_double[j]; + alpha = alpha+ae_sqr(v*state->s.ptr.p_double[j], _state); + } + alpha = ae_sqrt(alpha, _state); + if( ae_fp_neq(alpha,0) ) + { + result = result+ae_fabs(p/alpha, _state); + } + } + } + return result; +} + + +/************************************************************************* +This subroutine calculates scaled norm of vector after projection onto +subspace of active constraints. Most often this function is used to test +stopping conditions. + +INPUT PARAMETERS: + S - active set object + D - vector whose norm is calculated + +RESULT: + Vector norm (after projection and scaling) + +NOTE: projection is performed first, scaling is performed after projection + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +double sasscaledconstrainednorm(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + double v; + double result; + + + ae_assert(state->algostate==1, "SASMoveTo: is not in optimization mode", _state); + n = state->n; + rvectorsetlengthatleast(&state->scntmp, n, _state); + + /* + * Prepare basis (if needed) + */ + sasrebuildbasis(state, _state); + + /* + * Calculate descent direction + */ + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>0 ) + { + state->scntmp.ptr.p_double[i] = 0; + } + else + { + state->scntmp.ptr.p_double[i] = d->ptr.p_double[i]; + } + } + for(i=0; i<=state->basissize-1; i++) + { + v = ae_v_dotproduct(&state->ibasis.ptr.pp_double[i][0], 1, &state->scntmp.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_subd(&state->scntmp.ptr.p_double[0], 1, &state->ibasis.ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->s.ptr.p_double[i]*state->scntmp.ptr.p_double[i], _state); + } + result = ae_sqrt(v, _state); + return result; +} + + +/************************************************************************* +This subroutine turns off optimization mode. + +INPUT PARAMETERS: + S - active set object + +OUTPUT PARAMETERS: + S - state is changed + +NOTE: this function can be called many times for optimizer which was + already stopped. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +void sasstopoptimization(sactiveset* state, ae_state *_state) +{ + + + state->algostate = 0; +} + + +/************************************************************************* +This function recalculates constraints - activates and deactivates them +according to gradient value at current point. Algorithm assumes that we +want to make steepest descent step from current point; constraints are +activated and deactivated in such way that we won't violate any constraint +by steepest descent step. + +After call to this function active set is ready to try steepest descent +step (SASDescentDirection-SASExploreDirection-SASMoveTo). + +Only already "active" and "candidate" elements of ActiveSet are examined; +constraints which are not active are not examined. + +INPUT PARAMETERS: + State - active set object + GC - array[N], gradient at XC + +OUTPUT PARAMETERS: + State - active set object, with new set of constraint + + -- ALGLIB -- + Copyright 26.09.2012 by Bochkanov Sergey +*************************************************************************/ +void sasreactivateconstraints(sactiveset* state, + /* Real */ ae_vector* gc, + ae_state *_state) +{ + + + ae_assert(state->algostate==1, "SASReactivateConstraints: must be in optimization mode", _state); + sactivesets_reactivateconstraints(state, gc, &state->unitdiagonal, _state); +} + + +/************************************************************************* +This function recalculates constraints - activates and deactivates them +according to gradient value at current point. + +Algorithm assumes that we want to make Quasi-Newton step from current +point with diagonal Quasi-Newton matrix H. Constraints are activated and +deactivated in such way that we won't violate any constraint by step. + +After call to this function active set is ready to try preconditioned +steepest descent step (SASDescentDirection-SASExploreDirection-SASMoveTo). + +Only already "active" and "candidate" elements of ActiveSet are examined; +constraints which are not active are not examined. + +INPUT PARAMETERS: + State - active set object + GC - array[N], gradient at XC + +OUTPUT PARAMETERS: + State - active set object, with new set of constraint + + -- ALGLIB -- + Copyright 26.09.2012 by Bochkanov Sergey +*************************************************************************/ +void sasreactivateconstraintsprec(sactiveset* state, + /* Real */ ae_vector* gc, + ae_state *_state) +{ + + + ae_assert(state->algostate==1, "SASReactivateConstraintsPrec: must be in optimization mode", _state); + sactivesets_reactivateconstraints(state, gc, &state->h, _state); +} + + +/************************************************************************* +This function builds three orthonormal basises for current active set: +* P-orthogonal one, which is orthogonalized with inner product + (x,y) = x'*P*y, where P=inv(H) is current preconditioner +* S-orthogonal one, which is orthogonalized with inner product + (x,y) = x'*S'*S*y, where S is diagonal scaling matrix +* I-orthogonal one, which is orthogonalized with standard dot product + +NOTE: all sets of orthogonal vectors are guaranteed to have same size. + P-orthogonal basis is built first, I/S-orthogonal basises are forced + to have same number of vectors as P-orthogonal one (padded by zero + vectors if needed). + +NOTE: this function tracks changes in active set; first call will result + in reorthogonalization + +INPUT PARAMETERS: + State - active set object + H - diagonal preconditioner, H[i]>0 + +OUTPUT PARAMETERS: + State - active set object with new basis + + -- ALGLIB -- + Copyright 20.06.2012 by Bochkanov Sergey +*************************************************************************/ +void sasrebuildbasis(sactiveset* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + ae_int_t i; + ae_int_t j; + ae_int_t t; + ae_int_t nactivelin; + ae_int_t nactivebnd; + double v; + double vmax; + ae_int_t kmax; + + + if( state->basisisready ) + { + return; + } + n = state->n; + nec = state->nec; + nic = state->nic; + rmatrixsetlengthatleast(&state->tmpbasis, nec+nic, n+1, _state); + state->basissize = 0; + state->basisisready = ae_true; + + /* + * Determine number of active boundary and non-boundary + * constraints, move them to TmpBasis. Quick exit if no + * non-boundary constraints were detected. + */ + nactivelin = 0; + nactivebnd = 0; + for(i=0; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]>0 ) + { + nactivelin = nactivelin+1; + } + } + for(j=0; j<=n-1; j++) + { + if( state->activeset.ptr.p_int[j]>0 ) + { + nactivebnd = nactivebnd+1; + } + } + if( nactivelin==0 ) + { + return; + } + + /* + * Orthogonalize linear constraints (inner product is given by preconditioner) + * with respect to each other and boundary ones: + * * normalize all constraints + * * orthogonalize with respect to boundary ones + * * repeat: + * * if basisSize+nactivebnd=n - TERMINATE + * * choose largest row from TmpBasis + * * if row norm is too small - TERMINATE + * * add row to basis, normalize + * * remove from TmpBasis, orthogonalize other constraints with respect to this one + */ + nactivelin = 0; + for(i=0; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]>0 ) + { + ae_v_move(&state->tmpbasis.ptr.pp_double[nactivelin][0], 1, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n)); + nactivelin = nactivelin+1; + } + } + for(i=0; i<=nactivelin-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->tmpbasis.ptr.pp_double[i][j], _state)/state->h.ptr.p_double[j]; + } + if( ae_fp_greater(v,0) ) + { + v = 1/ae_sqrt(v, _state); + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[i][j] = state->tmpbasis.ptr.pp_double[i][j]*v; + } + } + } + for(j=0; j<=n-1; j++) + { + if( state->activeset.ptr.p_int[j]>0 ) + { + for(i=0; i<=nactivelin-1; i++) + { + state->tmpbasis.ptr.pp_double[i][n] = state->tmpbasis.ptr.pp_double[i][n]-state->tmpbasis.ptr.pp_double[i][j]*state->xc.ptr.p_double[j]; + state->tmpbasis.ptr.pp_double[i][j] = 0.0; + } + } + } + while(state->basissize+nactivebndtmpbasis.ptr.pp_double[i][j], _state)/state->h.ptr.p_double[j]; + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(v,vmax) ) + { + vmax = v; + kmax = i; + } + } + if( ae_fp_less(vmax,1.0E4*ae_machineepsilon) ) + { + break; + } + v = 1/vmax; + ae_v_moved(&state->pbasis.ptr.pp_double[state->basissize][0], 1, &state->tmpbasis.ptr.pp_double[kmax][0], 1, ae_v_len(0,n), v); + state->basissize = state->basissize+1; + + /* + * Reorthogonalize other vectors with respect to chosen one. + * Remove it from the array. + */ + for(i=0; i<=nactivelin-1; i++) + { + if( i!=kmax ) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+state->pbasis.ptr.pp_double[state->basissize-1][j]*state->tmpbasis.ptr.pp_double[i][j]/state->h.ptr.p_double[j]; + } + ae_v_subd(&state->tmpbasis.ptr.pp_double[i][0], 1, &state->pbasis.ptr.pp_double[state->basissize-1][0], 1, ae_v_len(0,n), v); + } + } + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[kmax][j] = 0; + } + } + + /* + * Orthogonalize linear constraints using traditional dot product + * with respect to each other and boundary ones. + * + * NOTE: we force basis size to be equal to one which was computed + * at the previous step, with preconditioner-based inner product. + */ + nactivelin = 0; + for(i=0; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]>0 ) + { + ae_v_move(&state->tmpbasis.ptr.pp_double[nactivelin][0], 1, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n)); + nactivelin = nactivelin+1; + } + } + for(i=0; i<=nactivelin-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->tmpbasis.ptr.pp_double[i][j], _state); + } + if( ae_fp_greater(v,0) ) + { + v = 1/ae_sqrt(v, _state); + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[i][j] = state->tmpbasis.ptr.pp_double[i][j]*v; + } + } + } + for(j=0; j<=n-1; j++) + { + if( state->activeset.ptr.p_int[j]>0 ) + { + for(i=0; i<=nactivelin-1; i++) + { + state->tmpbasis.ptr.pp_double[i][n] = state->tmpbasis.ptr.pp_double[i][n]-state->tmpbasis.ptr.pp_double[i][j]*state->xc.ptr.p_double[j]; + state->tmpbasis.ptr.pp_double[i][j] = 0.0; + } + } + } + for(t=0; t<=state->basissize-1; t++) + { + + /* + * Find largest vector, add to basis. + */ + vmax = -1; + kmax = -1; + for(i=0; i<=nactivelin-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->tmpbasis.ptr.pp_double[i][j], _state); + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(v,vmax) ) + { + vmax = v; + kmax = i; + } + } + if( ae_fp_eq(vmax,0) ) + { + for(j=0; j<=n; j++) + { + state->ibasis.ptr.pp_double[t][j] = 0.0; + } + continue; + } + v = 1/vmax; + ae_v_moved(&state->ibasis.ptr.pp_double[t][0], 1, &state->tmpbasis.ptr.pp_double[kmax][0], 1, ae_v_len(0,n), v); + + /* + * Reorthogonalize other vectors with respect to chosen one. + * Remove it from the array. + */ + for(i=0; i<=nactivelin-1; i++) + { + if( i!=kmax ) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+state->ibasis.ptr.pp_double[t][j]*state->tmpbasis.ptr.pp_double[i][j]; + } + ae_v_subd(&state->tmpbasis.ptr.pp_double[i][0], 1, &state->ibasis.ptr.pp_double[t][0], 1, ae_v_len(0,n), v); + } + } + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[kmax][j] = 0; + } + } + + /* + * Orthogonalize linear constraints using inner product given by + * scale matrix. + * + * NOTE: we force basis size to be equal to one which was computed + * with preconditioner-based inner product. + */ + nactivelin = 0; + for(i=0; i<=nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[n+i]>0 ) + { + ae_v_move(&state->tmpbasis.ptr.pp_double[nactivelin][0], 1, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n)); + nactivelin = nactivelin+1; + } + } + for(i=0; i<=nactivelin-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->tmpbasis.ptr.pp_double[i][j]*state->s.ptr.p_double[j], _state); + } + if( ae_fp_greater(v,0) ) + { + v = 1/ae_sqrt(v, _state); + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[i][j] = state->tmpbasis.ptr.pp_double[i][j]*v; + } + } + } + for(j=0; j<=n-1; j++) + { + if( state->activeset.ptr.p_int[j]>0 ) + { + for(i=0; i<=nactivelin-1; i++) + { + state->tmpbasis.ptr.pp_double[i][n] = state->tmpbasis.ptr.pp_double[i][n]-state->tmpbasis.ptr.pp_double[i][j]*state->xc.ptr.p_double[j]; + state->tmpbasis.ptr.pp_double[i][j] = 0.0; + } + } + } + for(t=0; t<=state->basissize-1; t++) + { + + /* + * Find largest vector, add to basis. + */ + vmax = -1; + kmax = -1; + for(i=0; i<=nactivelin-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->tmpbasis.ptr.pp_double[i][j]*state->s.ptr.p_double[j], _state); + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(v,vmax) ) + { + vmax = v; + kmax = i; + } + } + if( ae_fp_eq(vmax,0) ) + { + for(j=0; j<=n; j++) + { + state->sbasis.ptr.pp_double[t][j] = 0.0; + } + continue; + } + v = 1/vmax; + ae_v_moved(&state->sbasis.ptr.pp_double[t][0], 1, &state->tmpbasis.ptr.pp_double[kmax][0], 1, ae_v_len(0,n), v); + + /* + * Reorthogonalize other vectors with respect to chosen one. + * Remove it from the array. + */ + for(i=0; i<=nactivelin-1; i++) + { + if( i!=kmax ) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+state->sbasis.ptr.pp_double[t][j]*state->tmpbasis.ptr.pp_double[i][j]*ae_sqr(state->s.ptr.p_double[j], _state); + } + ae_v_subd(&state->tmpbasis.ptr.pp_double[i][0], 1, &state->sbasis.ptr.pp_double[t][0], 1, ae_v_len(0,n), v); + } + } + for(j=0; j<=n; j++) + { + state->tmpbasis.ptr.pp_double[kmax][j] = 0; + } + } +} + + +/************************************************************************* +This subroutine calculates preconditioned descent direction subject to +current active set. + +INPUT PARAMETERS: + State - active set object + G - array[N], gradient + H - array[N], Hessian matrix + HA - active constraints orthogonalized in such way + that HA*inv(H)*HA'= I. + Normalize- whether we need normalized descent or not + D - possibly preallocated buffer; automatically resized. + +OUTPUT PARAMETERS: + D - descent direction projected onto current active set. + Components of D which correspond to active boundary + constraints are forced to be exactly zero. + In case D is non-zero and Normalize is True, it is + normalized to have unit norm. + + -- ALGLIB -- + Copyright 21.12.2012 by Bochkanov Sergey +*************************************************************************/ +static void sactivesets_constraineddescent(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* h, + /* Real */ ae_matrix* ha, + ae_bool normalize, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + double v; + ae_int_t nactive; + + + ae_assert(state->algostate==1, "SAS: internal error in ConstrainedDescent() - not in optimization mode", _state); + ae_assert(state->basisisready, "SAS: internal error in ConstrainedDescent() - no basis", _state); + n = state->n; + rvectorsetlengthatleast(d, n, _state); + + /* + * Calculate preconditioned constrained descent direction: + * + * d := -inv(H)*( g - HA'*(HA*inv(H)*g) ) + * + * Formula above always gives direction which is orthogonal to rows of HA. + * You can verify it by multiplication of both sides by HA[i] (I-th row), + * taking into account that HA*inv(H)*HA'= I (by definition of HA - it is + * orthogonal basis with inner product given by inv(H)). + */ + nactive = 0; + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>0 ) + { + d->ptr.p_double[i] = 0; + nactive = nactive+1; + } + else + { + d->ptr.p_double[i] = g->ptr.p_double[i]; + } + } + for(i=0; i<=state->basissize-1; i++) + { + v = 0.0; + for(j=0; j<=n-1; j++) + { + v = v+ha->ptr.pp_double[i][j]*d->ptr.p_double[j]/h->ptr.p_double[j]; + } + ae_v_subd(&d->ptr.p_double[0], 1, &ha->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + nactive = nactive+1; + } + v = 0.0; + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>0 ) + { + d->ptr.p_double[i] = 0; + } + else + { + d->ptr.p_double[i] = -d->ptr.p_double[i]/h->ptr.p_double[i]; + v = v+ae_sqr(d->ptr.p_double[i], _state); + } + } + v = ae_sqrt(v, _state); + if( nactive>=n ) + { + v = 0; + for(i=0; i<=n-1; i++) + { + d->ptr.p_double[i] = 0; + } + } + if( normalize&&ae_fp_greater(v,0) ) + { + for(i=0; i<=n-1; i++) + { + d->ptr.p_double[i] = d->ptr.p_double[i]/v; + } + } +} + + +/************************************************************************* +This function recalculates constraints - activates and deactivates them +according to gradient value at current point. + +Algorithm assumes that we want to make Quasi-Newton step from current +point with diagonal Quasi-Newton matrix H. Constraints are activated and +deactivated in such way that we won't violate any constraint by step. + +Only already "active" and "candidate" elements of ActiveSet are examined; +constraints which are not active are not examined. + +INPUT PARAMETERS: + State - active set object + GC - array[N], gradient at XC + H - array[N], Hessian matrix + +OUTPUT PARAMETERS: + State - active set object, with new set of constraint + + -- ALGLIB -- + Copyright 26.09.2012 by Bochkanov Sergey +*************************************************************************/ +static void sactivesets_reactivateconstraints(sactiveset* state, + /* Real */ ae_vector* gc, + /* Real */ ae_vector* h, + ae_state *_state) +{ + ae_int_t n; + ae_int_t nec; + ae_int_t nic; + ae_int_t i; + ae_int_t j; + ae_int_t idx0; + ae_int_t idx1; + double v; + ae_int_t nactivebnd; + ae_int_t nactivelin; + ae_int_t nactiveconstraints; + double rowscale; + + + ae_assert(state->algostate==1, "SASReactivateConstraintsPrec: must be in optimization mode", _state); + + /* + * Prepare + */ + n = state->n; + nec = state->nec; + nic = state->nic; + state->basisisready = ae_false; + + /* + * Handle important special case - no linear constraints, + * only boundary constraints are present + */ + if( nec+nic==0 ) + { + for(i=0; i<=n-1; i++) + { + if( (state->hasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i])&&ae_fp_eq(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + if( (state->hasbndl.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]))&&ae_fp_greater_eq(gc->ptr.p_double[i],0) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + if( (state->hasbndu.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]))&&ae_fp_less_eq(gc->ptr.p_double[i],0) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + state->activeset.ptr.p_int[i] = -1; + } + return; + } + + /* + * General case. + * Allocate temporaries. + */ + rvectorsetlengthatleast(&state->rctmpg, n, _state); + rvectorsetlengthatleast(&state->rctmprightpart, n, _state); + rvectorsetlengthatleast(&state->rctmps, n, _state); + rmatrixsetlengthatleast(&state->rctmpdense0, n, nec+nic, _state); + rmatrixsetlengthatleast(&state->rctmpdense1, n, nec+nic, _state); + bvectorsetlengthatleast(&state->rctmpisequality, n+nec+nic, _state); + ivectorsetlengthatleast(&state->rctmpconstraintidx, n+nec+nic, _state); + + /* + * Calculate descent direction + */ + ae_v_moveneg(&state->rctmpg.ptr.p_double[0], 1, &gc->ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Determine candidates to the active set. + * + * After this block constraints become either "inactive" (ActiveSet[i]<0) + * or "candidates" (ActiveSet[i]=0). Previously active constraints always + * become "candidates". + */ + for(i=0; i<=n+nec+nic-1; i++) + { + if( state->activeset.ptr.p_int[i]>0 ) + { + state->activeset.ptr.p_int[i] = 0; + } + else + { + state->activeset.ptr.p_int[i] = -1; + } + } + nactiveconstraints = 0; + nactivebnd = 0; + nactivelin = 0; + for(i=0; i<=n-1; i++) + { + + /* + * Activate boundary constraints: + * * copy constraint index to RCTmpConstraintIdx + * * set corresponding element of ActiveSet[] to "candidate" + * * fill RCTmpS by either +1 (lower bound) or -1 (upper bound) + * * set RCTmpIsEquality to False (BndLhasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i])&&ae_fp_eq(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + + /* + * Equality constraint is activated + */ + state->rctmpconstraintidx.ptr.p_int[nactiveconstraints] = i; + state->activeset.ptr.p_int[i] = 0; + state->rctmps.ptr.p_double[i] = 1.0; + state->rctmpisequality.ptr.p_bool[nactiveconstraints] = ae_true; + nactiveconstraints = nactiveconstraints+1; + nactivebnd = nactivebnd+1; + continue; + } + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]) ) + { + + /* + * Lower bound is activated + */ + state->rctmpconstraintidx.ptr.p_int[nactiveconstraints] = i; + state->activeset.ptr.p_int[i] = 0; + state->rctmps.ptr.p_double[i] = -1.0; + state->rctmpisequality.ptr.p_bool[nactiveconstraints] = ae_false; + nactiveconstraints = nactiveconstraints+1; + nactivebnd = nactivebnd+1; + continue; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + + /* + * Upper bound is activated + */ + state->rctmpconstraintidx.ptr.p_int[nactiveconstraints] = i; + state->activeset.ptr.p_int[i] = 0; + state->rctmps.ptr.p_double[i] = 1.0; + state->rctmpisequality.ptr.p_bool[nactiveconstraints] = ae_false; + nactiveconstraints = nactiveconstraints+1; + nactivebnd = nactivebnd+1; + continue; + } + } + for(i=0; i<=nec+nic-1; i++) + { + if( i>=nec ) + { + + /* + * Inequality constraints are skipped if we too far away from + * the boundary. + */ + rowscale = 0.0; + v = -state->cleic.ptr.pp_double[i][n]; + for(j=0; j<=n-1; j++) + { + v = v+state->cleic.ptr.pp_double[i][j]*state->xc.ptr.p_double[j]; + rowscale = ae_maxreal(rowscale, ae_fabs(state->cleic.ptr.pp_double[i][j]*state->s.ptr.p_double[j], _state), _state); + } + if( ae_fp_less_eq(v,-1.0E5*ae_machineepsilon*rowscale) ) + { + + /* + * NOTE: it is important to check for non-strict inequality + * because we have to correctly handle zero constraint + * 0*x<=0 + */ + continue; + } + } + ae_v_move(&state->rctmpdense0.ptr.pp_double[0][nactivelin], state->rctmpdense0.stride, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + state->rctmpconstraintidx.ptr.p_int[nactiveconstraints] = n+i; + state->activeset.ptr.p_int[n+i] = 0; + state->rctmpisequality.ptr.p_bool[nactiveconstraints] = ihasbndl.ptr.p_bool[i]&&state->hasbndu.ptr.p_bool[i])&&ae_fp_eq(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + if( (state->hasbndl.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndl.ptr.p_double[i]))&&ae_fp_greater_eq(gc->ptr.p_double[i],0) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + if( (state->hasbndu.ptr.p_bool[i]&&ae_fp_eq(state->xc.ptr.p_double[i],state->bndu.ptr.p_double[i]))&&ae_fp_less_eq(gc->ptr.p_double[i],0) ) + { + state->activeset.ptr.p_int[i] = 1; + continue; + } + } + return; + } + + /* + * General case. + * + * APPROACH TO CONSTRAINTS ACTIVATION/DEACTIVATION + * + * We have NActiveConstraints "candidates": NActiveBnd boundary candidates, + * NActiveLin linear candidates. Indexes of boundary constraints are stored + * in RCTmpConstraintIdx[0:NActiveBnd-1], indexes of linear ones are stored + * in RCTmpConstraintIdx[NActiveBnd:NActiveBnd+NActiveLin-1]. Some of the + * constraints are equality ones, some are inequality - as specified by + * RCTmpIsEquality[i]. + * + * Now we have to determine active subset of "candidates" set. In order to + * do so we solve following constrained minimization problem: + * ( )^2 + * min ( SUM(lambda[i]*A[i]) + G ) + * ( ) + * Here: + * * G is a gradient (column vector) + * * A[i] is a column vector, linear (left) part of I-th constraint. + * I=0..NActiveConstraints-1, first NActiveBnd elements of A are just + * subset of identity matrix (boundary constraints), next NActiveLin + * elements are subset of rows of the matrix of general linear constraints. + * * lambda[i] is a Lagrange multiplier corresponding to I-th constraint + * + * NOTE: for preconditioned setting A is replaced by A*H^(-0.5), G is + * replaced by G*H^(-0.5). We apply this scaling at the last stage, + * before passing data to NNLS solver. + * + * Minimization is performed subject to non-negativity constraints on + * lambda[i] corresponding to inequality constraints. Inequality constraints + * which correspond to non-zero lambda are activated, equality constraints + * are always considered active. + * + * Informally speaking, we "decompose" descent direction -G and represent + * it as sum of constraint vectors and "residual" part (which is equal to + * the actual descent direction subject to constraints). + * + * SOLUTION OF THE NNLS PROBLEM + * + * We solve this optimization problem with Non-Negative Least Squares solver, + * which can efficiently solve least squares problems of the form + * + * ( [ I | AU ] )^2 + * min ( [ | ]*x-b ) s.t. non-negativity constraints on some x[i] + * ( [ 0 | AL ] ) + * + * In order to use this solver we have to rearrange rows of A[] and G in + * such way that first NActiveBnd columns of A store identity matrix (before + * sorting non-zero elements are randomly distributed in the first NActiveBnd + * columns of A, during sorting we move them to first NActiveBnd rows). + * + * Then we create instance of NNLS solver (we reuse instance left from the + * previous run of the optimization problem) and solve NNLS problem. + */ + idx0 = 0; + idx1 = nactivebnd; + for(i=0; i<=n-1; i++) + { + if( state->activeset.ptr.p_int[i]>=0 ) + { + v = 1/ae_sqrt(h->ptr.p_double[i], _state); + for(j=0; j<=nactivelin-1; j++) + { + state->rctmpdense1.ptr.pp_double[idx0][j] = state->rctmpdense0.ptr.pp_double[i][j]/state->rctmps.ptr.p_double[i]*v; + } + state->rctmprightpart.ptr.p_double[idx0] = state->rctmpg.ptr.p_double[i]/state->rctmps.ptr.p_double[i]*v; + idx0 = idx0+1; + } + else + { + v = 1/ae_sqrt(h->ptr.p_double[i], _state); + for(j=0; j<=nactivelin-1; j++) + { + state->rctmpdense1.ptr.pp_double[idx1][j] = state->rctmpdense0.ptr.pp_double[i][j]*v; + } + state->rctmprightpart.ptr.p_double[idx1] = state->rctmpg.ptr.p_double[i]*v; + idx1 = idx1+1; + } + } + snnlsinit(n, nec+nic, n, &state->solver, _state); + snnlssetproblem(&state->solver, &state->rctmpdense1, &state->rctmprightpart, nactivebnd, nactiveconstraints-nactivebnd, n, _state); + for(i=0; i<=nactiveconstraints-1; i++) + { + if( state->rctmpisequality.ptr.p_bool[i] ) + { + snnlsdropnnc(&state->solver, i, _state); + } + } + snnlssolve(&state->solver, &state->rctmplambdas, _state); + + /* + * After solution of the problem we activate equality constraints (always active) + * and inequality constraints with non-zero Lagrange multipliers. Then we reorthogonalize + * active constraints. + */ + for(i=0; i<=nactiveconstraints-1; i++) + { + if( state->rctmpisequality.ptr.p_bool[i]||ae_fp_greater(state->rctmplambdas.ptr.p_double[i],0) ) + { + state->activeset.ptr.p_int[state->rctmpconstraintidx.ptr.p_int[i]] = 1; + } + else + { + state->activeset.ptr.p_int[state->rctmpconstraintidx.ptr.p_int[i]] = 0; + } + } + sasrebuildbasis(state, _state); +} + + +ae_bool _sactiveset_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + sactiveset *p = (sactiveset*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->xc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->h, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->activeset, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->sbasis, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->pbasis, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->ibasis, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hasbndl, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hasbndu, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->cleic, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mtx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mtas, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cdtmp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->corrtmp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->unitdiagonal, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_snnlssolver_init(&p->solver, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->scntmp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpfeas, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpm0, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmps, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmpg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmprightpart, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->rctmpdense0, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->rctmpdense1, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmpisequality, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmpconstraintidx, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmplambdas, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->tmpbasis, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _sactiveset_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + sactiveset *dst = (sactiveset*)_dst; + sactiveset *src = (sactiveset*)_src; + dst->n = src->n; + dst->algostate = src->algostate; + if( !ae_vector_init_copy(&dst->xc, &src->xc, _state, make_automatic) ) + return ae_false; + dst->hasxc = src->hasxc; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->h, &src->h, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->activeset, &src->activeset, _state, make_automatic) ) + return ae_false; + dst->basisisready = src->basisisready; + if( !ae_matrix_init_copy(&dst->sbasis, &src->sbasis, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->pbasis, &src->pbasis, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->ibasis, &src->ibasis, _state, make_automatic) ) + return ae_false; + dst->basissize = src->basissize; + dst->constraintschanged = src->constraintschanged; + if( !ae_vector_init_copy(&dst->hasbndl, &src->hasbndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->hasbndu, &src->hasbndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->cleic, &src->cleic, _state, make_automatic) ) + return ae_false; + dst->nec = src->nec; + dst->nic = src->nic; + if( !ae_vector_init_copy(&dst->mtx, &src->mtx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mtas, &src->mtas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cdtmp, &src->cdtmp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->corrtmp, &src->corrtmp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->unitdiagonal, &src->unitdiagonal, _state, make_automatic) ) + return ae_false; + if( !_snnlssolver_init_copy(&dst->solver, &src->solver, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->scntmp, &src->scntmp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp0, &src->tmp0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpfeas, &src->tmpfeas, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpm0, &src->tmpm0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmps, &src->rctmps, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmpg, &src->rctmpg, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmprightpart, &src->rctmprightpart, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->rctmpdense0, &src->rctmpdense0, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->rctmpdense1, &src->rctmpdense1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmpisequality, &src->rctmpisequality, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmpconstraintidx, &src->rctmpconstraintidx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmplambdas, &src->rctmplambdas, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->tmpbasis, &src->tmpbasis, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _sactiveset_clear(void* _p) +{ + sactiveset *p = (sactiveset*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->xc); + ae_vector_clear(&p->s); + ae_vector_clear(&p->h); + ae_vector_clear(&p->activeset); + ae_matrix_clear(&p->sbasis); + ae_matrix_clear(&p->pbasis); + ae_matrix_clear(&p->ibasis); + ae_vector_clear(&p->hasbndl); + ae_vector_clear(&p->hasbndu); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_matrix_clear(&p->cleic); + ae_vector_clear(&p->mtx); + ae_vector_clear(&p->mtas); + ae_vector_clear(&p->cdtmp); + ae_vector_clear(&p->corrtmp); + ae_vector_clear(&p->unitdiagonal); + _snnlssolver_clear(&p->solver); + ae_vector_clear(&p->scntmp); + ae_vector_clear(&p->tmp0); + ae_vector_clear(&p->tmpfeas); + ae_matrix_clear(&p->tmpm0); + ae_vector_clear(&p->rctmps); + ae_vector_clear(&p->rctmpg); + ae_vector_clear(&p->rctmprightpart); + ae_matrix_clear(&p->rctmpdense0); + ae_matrix_clear(&p->rctmpdense1); + ae_vector_clear(&p->rctmpisequality); + ae_vector_clear(&p->rctmpconstraintidx); + ae_vector_clear(&p->rctmplambdas); + ae_matrix_clear(&p->tmpbasis); +} + + +void _sactiveset_destroy(void* _p) +{ + sactiveset *p = (sactiveset*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->xc); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->h); + ae_vector_destroy(&p->activeset); + ae_matrix_destroy(&p->sbasis); + ae_matrix_destroy(&p->pbasis); + ae_matrix_destroy(&p->ibasis); + ae_vector_destroy(&p->hasbndl); + ae_vector_destroy(&p->hasbndu); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_matrix_destroy(&p->cleic); + ae_vector_destroy(&p->mtx); + ae_vector_destroy(&p->mtas); + ae_vector_destroy(&p->cdtmp); + ae_vector_destroy(&p->corrtmp); + ae_vector_destroy(&p->unitdiagonal); + _snnlssolver_destroy(&p->solver); + ae_vector_destroy(&p->scntmp); + ae_vector_destroy(&p->tmp0); + ae_vector_destroy(&p->tmpfeas); + ae_matrix_destroy(&p->tmpm0); + ae_vector_destroy(&p->rctmps); + ae_vector_destroy(&p->rctmpg); + ae_vector_destroy(&p->rctmprightpart); + ae_matrix_destroy(&p->rctmpdense0); + ae_matrix_destroy(&p->rctmpdense1); + ae_vector_destroy(&p->rctmpisequality); + ae_vector_destroy(&p->rctmpconstraintidx); + ae_vector_destroy(&p->rctmplambdas); + ae_matrix_destroy(&p->tmpbasis); +} + + + + +/************************************************************************* + NONLINEAR CONJUGATE GRADIENT METHOD + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using one of the +nonlinear conjugate gradient methods. + +These CG methods are globally convergent (even on non-convex functions) as +long as grad(f) is Lipschitz continuous in a some neighborhood of the +L = { x : f(x)<=f(x0) }. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinCGCreate() call +2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and + other functions +3. User calls MinCGOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinCGResults() to get solution +5. Optionally, user may call MinCGRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinCGRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgcreate(ae_int_t n, + /* Real */ ae_vector* x, + mincgstate* state, + ae_state *_state) +{ + + _mincgstate_clear(state); + + ae_assert(n>=1, "MinCGCreate: N too small!", _state); + ae_assert(x->cnt>=n, "MinCGCreate: Length(X)0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinCGSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. L-BFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgcreatef(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + mincgstate* state, + ae_state *_state) +{ + + _mincgstate_clear(state); + + ae_assert(n>=1, "MinCGCreateF: N too small!", _state); + ae_assert(x->cnt>=n, "MinCGCreateF: Length(X)=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinCGSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcond(mincgstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinCGSetCond: EpsG is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinCGSetCond: negative EpsG!", _state); + ae_assert(ae_isfinite(epsf, _state), "MinCGSetCond: EpsF is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinCGSetCond: negative EpsF!", _state); + ae_assert(ae_isfinite(epsx, _state), "MinCGSetCond: EpsX is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinCGSetCond: negative EpsX!", _state); + ae_assert(maxits>=0, "MinCGSetCond: negative MaxIts!", _state); + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->epsg = epsg; + state->epsf = epsf; + state->epsx = epsx; + state->maxits = maxits; +} + + +/************************************************************************* +This function sets scaling coefficients for CG 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 + +Scaling is also used by finite difference variant of CG optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the CG too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinCGSetPrec...() functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgsetscale(mincgstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(s->cnt>=state->n, "MinCGSetScale: Length(S)n-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "MinCGSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "MinCGSetScale: S contains zero elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetxrep(mincgstate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +This function turns on/off line search reports. +These reports are described in more details in developer-only comments on +MinCGState object. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedDRep- whether line search reports are needed or not + +This function is intended for private use only. Turning it on artificially +may cause program failure. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetdrep(mincgstate* state, ae_bool needdrep, ae_state *_state) +{ + + + state->drep = needdrep; +} + + +/************************************************************************* +This function sets CG algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + CGType - algorithm type: + * -1 automatic selection of the best algorithm + * 0 DY (Dai and Yuan) algorithm + * 1 Hybrid DY-HS algorithm + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcgtype(mincgstate* state, ae_int_t cgtype, ae_state *_state) +{ + + + ae_assert(cgtype>=-1&&cgtype<=1, "MinCGSetCGType: incorrect CGType!", _state); + if( cgtype==-1 ) + { + cgtype = 1; + } + state->cgtype = cgtype; +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetstpmax(mincgstate* state, double stpmax, ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "MinCGSetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "MinCGSetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* +This function allows to suggest initial step length to the CG algorithm. + +Suggested step length is used as starting point for the line search. It +can be useful when you have badly scaled problem, i.e. when ||grad|| +(which is used as initial estimate for the first step) is many orders of +magnitude different from the desired step. + +Line search may fail on such problems without good estimate of initial +step length. Imagine, for example, problem with ||grad||=10^50 and desired +step equal to 0.1 Line search function will use 10^50 as initial step, +then it will decrease step length by 2 (up to 20 attempts) and will get +10^44, which is still too large. + +This function allows us to tell than line search should be started from +some moderate step length, like 1.0, so algorithm will be able to detect +desired step length in a several searches. + +Default behavior (when no step is suggested) is to use preconditioner, if +it is available, to generate initial estimate of step length. + +This function influences only first iteration of algorithm. It should be +called between MinCGCreate/MinCGRestartFrom() call and MinCGOptimize call. +Suggested step is ignored if you have preconditioner. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + Stp - initial estimate of the step length. + Can be zero (no estimate). + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsuggeststep(mincgstate* state, double stp, ae_state *_state) +{ + + + ae_assert(ae_isfinite(stp, _state), "MinCGSuggestStep: Stp is infinite or NAN", _state); + ae_assert(ae_fp_greater_eq(stp,0), "MinCGSuggestStep: Stp<0", _state); + state->suggestedstep = stp; +} + + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdefault(mincgstate* state, ae_state *_state) +{ + + + state->prectype = 0; + state->innerresetneeded = ae_true; +} + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdiag(mincgstate* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(d->cnt>=state->n, "MinCGSetPrecDiag: D is too short", _state); + for(i=0; i<=state->n-1; i++) + { + ae_assert(ae_isfinite(d->ptr.p_double[i], _state), "MinCGSetPrecDiag: D contains infinite or NAN elements", _state); + ae_assert(ae_fp_greater(d->ptr.p_double[i],0), "MinCGSetPrecDiag: D contains non-positive elements", _state); + } + mincgsetprecdiagfast(state, d, _state); +} + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinCGSetScale() call +(before or after MinCGSetPrecScale() call). Without knowledge of the scale +of your variables scale-based preconditioner will be just unit matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecscale(mincgstate* state, ae_state *_state) +{ + + + state->prectype = 3; + state->innerresetneeded = ae_true; +} + + +/************************************************************************* +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinCGCreate() for analytical gradient or MinCGCreateF() for + numerical differentiation) you should choose appropriate variant of + MinCGOptimize() - one which accepts function AND gradient or one which + accepts function ONLY. + + Be careful to choose variant of MinCGOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinCGOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinCGOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinCGCreateF() | work FAIL + MinCGCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinCGOptimize() version. Attemps to use such combination + (for example, to create optimizer with MinCGCreateF() and to pass + gradient information to MinCGOptimize()) will lead to exception being + thrown. Either you did not pass gradient when it WAS needed or you + passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool mincgiteration(mincgstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double betak; + double v; + double vv; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + i = state->rstate.ia.ptr.p_int[1]; + betak = state->rstate.ra.ptr.p_double[0]; + v = state->rstate.ra.ptr.p_double[1]; + vv = state->rstate.ra.ptr.p_double[2]; + } + else + { + n = -983; + i = -989; + betak = -834; + v = 900; + vv = -287; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + if( state->rstate.stage==14 ) + { + goto lbl_14; + } + if( state->rstate.stage==15 ) + { + goto lbl_15; + } + if( state->rstate.stage==16 ) + { + goto lbl_16; + } + if( state->rstate.stage==17 ) + { + goto lbl_17; + } + if( state->rstate.stage==18 ) + { + goto lbl_18; + } + if( state->rstate.stage==19 ) + { + goto lbl_19; + } + + /* + * Routine body + */ + + /* + * Prepare + */ + n = state->n; + state->repterminationtype = 0; + state->repiterationscount = 0; + state->repvaridx = -1; + state->repnfev = 0; + state->debugrestartscount = 0; + + /* + * Check, that transferred derivative value is right + */ + mincg_clearrequestfields(state, _state); + if( !(ae_fp_eq(state->diffstep,0)&&ae_fp_greater(state->teststep,0)) ) + { + goto lbl_20; + } + state->needfg = ae_true; + i = 0; +lbl_22: + if( i>n-1 ) + { + goto lbl_24; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->teststep*state->s.ptr.p_double[i]; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->fm1 = state->f; + state->fp1 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = v+state->teststep*state->s.ptr.p_double[i]; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->fm2 = state->f; + state->fp2 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = v; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + + /* + * 2*State.TestStep - scale parameter + * width of segment [Xi-TestStep;Xi+TestStep] + */ + if( !derivativecheck(state->fm1, state->fp1, state->fm2, state->fp2, state->f, state->g.ptr.p_double[i], 2*state->teststep, _state) ) + { + state->repvaridx = i; + state->repterminationtype = -7; + result = ae_false; + return result; + } + i = i+1; + goto lbl_22; +lbl_24: + state->needfg = ae_false; +lbl_20: + + /* + * Preparations continue: + * * set XK + * * calculate F/G + * * set DK to -G + * * powerup algo (it may change preconditioner) + * * apply preconditioner to DK + * * report update of X + * * check stopping conditions for G + */ + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->terminationneeded = ae_false; + mincg_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_25; + } + state->needfg = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needfg = ae_false; + goto lbl_26; +lbl_25: + state->needf = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->fbase = state->f; + i = 0; +lbl_27: + if( i>n-1 ) + { + goto lbl_29; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->fp2 = state->f; + state->x.ptr.p_double[i] = v; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + i = i+1; + goto lbl_27; +lbl_29: + state->f = state->fbase; + state->needf = ae_false; +lbl_26: + if( !state->drep ) + { + goto lbl_30; + } + + /* + * Report algorithm powerup (if needed) + */ + mincg_clearrequestfields(state, _state); + state->algpowerup = ae_true; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->algpowerup = ae_false; +lbl_30: + trimprepare(state->f, &state->trimthreshold, _state); + ae_v_moveneg(&state->dk.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + mincg_preconditionedmultiply(state, &state->dk, &state->work0, &state->work1, _state); + if( !state->xrep ) + { + goto lbl_32; + } + mincg_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->xupdated = ae_false; +lbl_32: + if( state->terminationneeded ) + { + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repterminationtype = 8; + result = ae_false; + return result; + } + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->g.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->epsg) ) + { + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repterminationtype = 4; + result = ae_false; + return result; + } + state->repnfev = 1; + state->k = 0; + state->fold = state->f; + + /* + * Choose initial step. + * Apply preconditioner, if we have something other than default. + */ + if( state->prectype==2||state->prectype==3 ) + { + + /* + * because we use preconditioner, step length must be equal + * to the norm of DK + */ + v = ae_v_dotproduct(&state->dk.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->lastgoodstep = ae_sqrt(v, _state); + } + else + { + + /* + * No preconditioner is used, we try to use suggested step + */ + if( ae_fp_greater(state->suggestedstep,0) ) + { + state->lastgoodstep = state->suggestedstep; + } + else + { + state->lastgoodstep = 1.0; + } + } + + /* + * Main cycle + */ + state->rstimer = mincg_rscountdownlen; +lbl_34: + if( ae_false ) + { + goto lbl_35; + } + + /* + * * clear reset flag + * * clear termination flag + * * store G[k] for later calculation of Y[k] + * * prepare starting point and direction and step length for line search + */ + state->innerresetneeded = ae_false; + state->terminationneeded = ae_false; + ae_v_moveneg(&state->yk.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->d.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->mcstage = 0; + state->stp = 1.0; + linminnormalized(&state->d, &state->stp, n, _state); + if( ae_fp_neq(state->lastgoodstep,0) ) + { + state->stp = state->lastgoodstep; + } + state->curstpmax = state->stpmax; + + /* + * Report beginning of line search (if needed) + * Terminate algorithm, if user request was detected + */ + if( !state->drep ) + { + goto lbl_36; + } + mincg_clearrequestfields(state, _state); + state->lsstart = ae_true; + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + state->lsstart = ae_false; +lbl_36: + if( state->terminationneeded ) + { + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repterminationtype = 8; + result = ae_false; + return result; + } + + /* + * Minimization along D + */ + mcsrch(n, &state->x, &state->f, &state->g, &state->d, &state->stp, state->curstpmax, mincg_gtol, &state->mcinfo, &state->nfev, &state->work0, &state->lstate, &state->mcstage, _state); +lbl_38: + if( state->mcstage==0 ) + { + goto lbl_39; + } + + /* + * Calculate function/gradient using either + * analytical gradient supplied by user + * or finite difference approximation. + * + * "Trim" function in order to handle near-singularity points. + */ + mincg_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_40; + } + state->needfg = ae_true; + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + state->needfg = ae_false; + goto lbl_41; +lbl_40: + state->needf = ae_true; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->fbase = state->f; + i = 0; +lbl_42: + if( i>n-1 ) + { + goto lbl_44; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 14; + goto lbl_rcomm; +lbl_14: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 15; + goto lbl_rcomm; +lbl_15: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 16; + goto lbl_rcomm; +lbl_16: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 17; + goto lbl_rcomm; +lbl_17: + state->fp2 = state->f; + state->x.ptr.p_double[i] = v; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + i = i+1; + goto lbl_42; +lbl_44: + state->f = state->fbase; + state->needf = ae_false; +lbl_41: + trimfunction(&state->f, &state->g, n, state->trimthreshold, _state); + + /* + * Call MCSRCH again + */ + mcsrch(n, &state->x, &state->f, &state->g, &state->d, &state->stp, state->curstpmax, mincg_gtol, &state->mcinfo, &state->nfev, &state->work0, &state->lstate, &state->mcstage, _state); + goto lbl_38; +lbl_39: + + /* + * * report end of line search + * * store current point to XN + * * report iteration + * * terminate algorithm if user request was detected + */ + if( !state->drep ) + { + goto lbl_45; + } + + /* + * Report end of line search (if needed) + */ + mincg_clearrequestfields(state, _state); + state->lsend = ae_true; + state->rstate.stage = 18; + goto lbl_rcomm; +lbl_18: + state->lsend = ae_false; +lbl_45: + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( !state->xrep ) + { + goto lbl_47; + } + mincg_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 19; + goto lbl_rcomm; +lbl_19: + state->xupdated = ae_false; +lbl_47: + if( state->terminationneeded ) + { + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repterminationtype = 8; + result = ae_false; + return result; + } + + /* + * Line search is finished. + * * calculate BetaK + * * calculate DN + * * update timers + * * calculate step length: + * * LastScaledStep is ALWAYS calculated because it is used in the stopping criteria + * * LastGoodStep is updated only when MCINFO is equal to 1 (Wolfe conditions hold). + * See below for more explanation. + */ + if( state->mcinfo==1&&!state->innerresetneeded ) + { + + /* + * Standard Wolfe conditions hold + * Calculate Y[K] and D[K]'*Y[K] + */ + ae_v_add(&state->yk.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + vv = ae_v_dotproduct(&state->yk.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Calculate BetaK according to DY formula + */ + v = mincg_preconditionedmultiply2(state, &state->g, &state->g, &state->work0, &state->work1, _state); + state->betady = v/vv; + + /* + * Calculate BetaK according to HS formula + */ + v = mincg_preconditionedmultiply2(state, &state->g, &state->yk, &state->work0, &state->work1, _state); + state->betahs = v/vv; + + /* + * Choose BetaK + */ + if( state->cgtype==0 ) + { + betak = state->betady; + } + if( state->cgtype==1 ) + { + betak = ae_maxreal(0, ae_minreal(state->betady, state->betahs, _state), _state); + } + } + else + { + + /* + * Something is wrong (may be function is too wild or too flat) + * or we just have to restart algo. + * + * We'll set BetaK=0, which will restart CG algorithm. + * We can stop later (during normal checks) if stopping conditions are met. + */ + betak = 0; + state->debugrestartscount = state->debugrestartscount+1; + } + if( state->repiterationscount>0&&state->repiterationscount%(3+n)==0 ) + { + + /* + * clear Beta every N iterations + */ + betak = 0; + } + if( state->mcinfo==1||state->mcinfo==5 ) + { + state->rstimer = mincg_rscountdownlen; + } + else + { + state->rstimer = state->rstimer-1; + } + ae_v_moveneg(&state->dn.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + mincg_preconditionedmultiply(state, &state->dn, &state->work0, &state->work1, _state); + ae_v_addd(&state->dn.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1), betak); + state->lastscaledstep = 0.0; + for(i=0; i<=n-1; i++) + { + state->lastscaledstep = state->lastscaledstep+ae_sqr(state->d.ptr.p_double[i]/state->s.ptr.p_double[i], _state); + } + state->lastscaledstep = state->stp*ae_sqrt(state->lastscaledstep, _state); + if( state->mcinfo==1 ) + { + + /* + * Step is good (Wolfe conditions hold), update LastGoodStep. + * + * This check for MCINFO=1 is essential because sometimes in the + * constrained optimization setting we may take very short steps + * (like 1E-15) because we were very close to boundary of the + * feasible area. Such short step does not mean that we've converged + * to the solution - it was so short because we were close to the + * boundary and there was a limit on step length. + * + * So having such short step is quite normal situation. However, we + * should NOT start next iteration from step whose initial length is + * estimated as 1E-15 because it may lead to the failure of the + * linear minimizer (step is too short, function does not changes, + * line search stagnates). + */ + state->lastgoodstep = 0; + for(i=0; i<=n-1; i++) + { + state->lastgoodstep = state->lastgoodstep+ae_sqr(state->d.ptr.p_double[i], _state); + } + state->lastgoodstep = state->stp*ae_sqrt(state->lastgoodstep, _state); + } + + /* + * Update information. + * Check stopping conditions. + */ + state->repnfev = state->repnfev+state->nfev; + state->repiterationscount = state->repiterationscount+1; + if( state->repiterationscount>=state->maxits&&state->maxits>0 ) + { + + /* + * Too many iterations + */ + state->repterminationtype = 5; + result = ae_false; + return result; + } + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->g.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->epsg) ) + { + + /* + * Gradient is small enough + */ + state->repterminationtype = 4; + result = ae_false; + return result; + } + if( !state->innerresetneeded ) + { + + /* + * These conditions are checked only when no inner reset was requested by user + */ + if( ae_fp_less_eq(state->fold-state->f,state->epsf*ae_maxreal(ae_fabs(state->fold, _state), ae_maxreal(ae_fabs(state->f, _state), 1.0, _state), _state)) ) + { + + /* + * F(k+1)-F(k) is small enough + */ + state->repterminationtype = 1; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->lastscaledstep,state->epsx) ) + { + + /* + * X(k+1)-X(k) is small enough + */ + state->repterminationtype = 2; + result = ae_false; + return result; + } + } + if( state->rstimer<=0 ) + { + + /* + * Too many subsequent restarts + */ + state->repterminationtype = 7; + result = ae_false; + return result; + } + + /* + * Shift Xk/Dk, update other information + */ + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->dk.ptr.p_double[0], 1, &state->dn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fold = state->f; + state->k = state->k+1; + goto lbl_34; +lbl_35: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = i; + state->rstate.ra.ptr.p_double[0] = betak; + state->rstate.ra.ptr.p_double[1] = v; + state->rstate.ra.ptr.p_double[2] = vv; + return result; +} + + +/************************************************************************* +Conjugate gradient results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinCGSetGradientCheck() for more information. + * 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, + we return best X found so far + * 8 terminated by user + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresults(mincgstate* state, + /* Real */ ae_vector* x, + mincgreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _mincgreport_clear(rep); + + mincgresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +Conjugate gradient results + +Buffered implementation of MinCGResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresultsbuf(mincgstate* state, + /* Real */ ae_vector* x, + mincgreport* rep, + ae_state *_state) +{ + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nfev = state->repnfev; + rep->varidx = state->repvaridx; + rep->terminationtype = state->repterminationtype; +} + + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgrestartfrom(mincgstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + + + ae_assert(x->cnt>=state->n, "MinCGRestartFrom: Length(X)n, _state), "MinCGCreate: X contains infinite or NaN values!", _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + mincgsuggeststep(state, 0.0, _state); + ae_vector_set_length(&state->rstate.ia, 1+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; + mincg_clearrequestfields(state, _state); +} + + +/************************************************************************* +Faster version of MinCGSetPrecDiag(), for time-critical parts of code, +without safety checks. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdiagfast(mincgstate* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + rvectorsetlengthatleast(&state->diagh, state->n, _state); + rvectorsetlengthatleast(&state->diaghl2, state->n, _state); + state->prectype = 2; + state->vcnt = 0; + state->innerresetneeded = ae_true; + for(i=0; i<=state->n-1; i++) + { + state->diagh.ptr.p_double[i] = d->ptr.p_double[i]; + state->diaghl2.ptr.p_double[i] = 0.0; + } +} + + +/************************************************************************* +This function sets low-rank preconditioner for Hessian matrix H=D+V'*C*V, +where: +* H is a Hessian matrix, which is approximated by D/V/C +* D=D1+D2 is a diagonal matrix, which includes two positive definite terms: + * constant term D1 (is not updated or infrequently updated) + * variable term D2 (can be cheaply updated from iteration to iteration) +* V is a low-rank correction +* C is a diagonal factor of low-rank correction + +Preconditioner P is calculated using approximate Woodburry formula: + P = D^(-1) - D^(-1)*V'*(C^(-1)+V*D1^(-1)*V')^(-1)*V*D^(-1) + = D^(-1) - D^(-1)*VC'*VC*D^(-1), +where + VC = sqrt(B)*V + B = (C^(-1)+V*D1^(-1)*V')^(-1) + +Note that B is calculated using constant term (D1) only, which allows us +to update D2 without recalculation of B or VC. Such preconditioner is +exact when D2 is zero. When D2 is non-zero, it is only approximation, but +very good and cheap one. + +This function accepts D1, V, C. +D2 is set to zero by default. + +Cost of this update is O(N*VCnt*VCnt), but D2 can be updated in just O(N) +by MinCGSetPrecVarPart. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetpreclowrankfast(mincgstate* state, + /* Real */ ae_vector* d1, + /* Real */ ae_vector* c, + /* Real */ ae_matrix* v, + ae_int_t vcnt, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t n; + double t; + ae_matrix b; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init(&b, 0, 0, DT_REAL, _state, ae_true); + + if( vcnt==0 ) + { + mincgsetprecdiagfast(state, d1, _state); + ae_frame_leave(_state); + return; + } + n = state->n; + ae_matrix_set_length(&b, vcnt, vcnt, _state); + rvectorsetlengthatleast(&state->diagh, n, _state); + rvectorsetlengthatleast(&state->diaghl2, n, _state); + rmatrixsetlengthatleast(&state->vcorr, vcnt, n, _state); + state->prectype = 2; + state->vcnt = vcnt; + state->innerresetneeded = ae_true; + for(i=0; i<=n-1; i++) + { + state->diagh.ptr.p_double[i] = d1->ptr.p_double[i]; + state->diaghl2.ptr.p_double[i] = 0.0; + } + for(i=0; i<=vcnt-1; i++) + { + for(j=i; j<=vcnt-1; j++) + { + t = 0; + for(k=0; k<=n-1; k++) + { + t = t+v->ptr.pp_double[i][k]*v->ptr.pp_double[j][k]/d1->ptr.p_double[k]; + } + b.ptr.pp_double[i][j] = t; + } + b.ptr.pp_double[i][i] = b.ptr.pp_double[i][i]+1.0/c->ptr.p_double[i]; + } + if( !spdmatrixcholeskyrec(&b, 0, vcnt, ae_true, &state->work0, _state) ) + { + state->vcnt = 0; + ae_frame_leave(_state); + return; + } + for(i=0; i<=vcnt-1; i++) + { + ae_v_move(&state->vcorr.ptr.pp_double[i][0], 1, &v->ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + for(j=0; j<=i-1; j++) + { + t = b.ptr.pp_double[j][i]; + ae_v_subd(&state->vcorr.ptr.pp_double[i][0], 1, &state->vcorr.ptr.pp_double[j][0], 1, ae_v_len(0,n-1), t); + } + t = 1/b.ptr.pp_double[i][i]; + ae_v_muld(&state->vcorr.ptr.pp_double[i][0], 1, ae_v_len(0,n-1), t); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +This function updates variable part (diagonal matrix D2) +of low-rank preconditioner. + +This update is very cheap and takes just O(N) time. + +It has no effect with default preconditioner. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecvarpart(mincgstate* state, + /* Real */ ae_vector* d2, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + + + n = state->n; + for(i=0; i<=n-1; i++) + { + state->diaghl2.ptr.p_double[i] = d2->ptr.p_double[i]; + } +} + + +/************************************************************************* + +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinCGOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinCGSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 31.05.2012 by Bochkanov Sergey +*************************************************************************/ +void mincgsetgradientcheck(mincgstate* state, + double teststep, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(teststep, _state), "MinCGSetGradientCheck: TestStep contains NaN or Infinite", _state); + ae_assert(ae_fp_greater_eq(teststep,0), "MinCGSetGradientCheck: invalid argument TestStep(TestStep<0)", _state); + state->teststep = teststep; +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void mincg_clearrequestfields(mincgstate* state, ae_state *_state) +{ + + + state->needf = ae_false; + state->needfg = ae_false; + state->xupdated = ae_false; + state->lsstart = ae_false; + state->lsend = ae_false; + state->algpowerup = ae_false; +} + + +/************************************************************************* +This function calculates preconditioned product H^(-1)*x and stores result +back into X. Work0[] and Work1[] are used as temporaries (size must be at +least N; this function doesn't allocate arrays). + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +static void mincg_preconditionedmultiply(mincgstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* work0, + /* Real */ ae_vector* work1, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + ae_int_t vcnt; + double v; + + + n = state->n; + vcnt = state->vcnt; + if( state->prectype==0 ) + { + return; + } + if( state->prectype==3 ) + { + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = x->ptr.p_double[i]*state->s.ptr.p_double[i]*state->s.ptr.p_double[i]; + } + return; + } + ae_assert(state->prectype==2, "MinCG: internal error (unexpected PrecType)", _state); + + /* + * handle part common for VCnt=0 and VCnt<>0 + */ + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = x->ptr.p_double[i]/(state->diagh.ptr.p_double[i]+state->diaghl2.ptr.p_double[i]); + } + + /* + * if VCnt>0 + */ + if( vcnt>0 ) + { + for(i=0; i<=vcnt-1; i++) + { + v = ae_v_dotproduct(&state->vcorr.ptr.pp_double[i][0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + work0->ptr.p_double[i] = v; + } + for(i=0; i<=n-1; i++) + { + work1->ptr.p_double[i] = 0; + } + for(i=0; i<=vcnt-1; i++) + { + v = work0->ptr.p_double[i]; + ae_v_addd(&state->work1.ptr.p_double[0], 1, &state->vcorr.ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + for(i=0; i<=n-1; i++) + { + x->ptr.p_double[i] = x->ptr.p_double[i]-state->work1.ptr.p_double[i]/(state->diagh.ptr.p_double[i]+state->diaghl2.ptr.p_double[i]); + } + } +} + + +/************************************************************************* +This function calculates preconditioned product x'*H^(-1)*y. Work0[] and +Work1[] are used as temporaries (size must be at least N; this function +doesn't allocate arrays). + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +static double mincg_preconditionedmultiply2(mincgstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + /* Real */ ae_vector* work0, + /* Real */ ae_vector* work1, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + ae_int_t vcnt; + double v0; + double v1; + double result; + + + n = state->n; + vcnt = state->vcnt; + + /* + * no preconditioning + */ + if( state->prectype==0 ) + { + v0 = ae_v_dotproduct(&x->ptr.p_double[0], 1, &y->ptr.p_double[0], 1, ae_v_len(0,n-1)); + result = v0; + return result; + } + if( state->prectype==3 ) + { + result = 0; + for(i=0; i<=n-1; i++) + { + result = result+x->ptr.p_double[i]*state->s.ptr.p_double[i]*state->s.ptr.p_double[i]*y->ptr.p_double[i]; + } + return result; + } + ae_assert(state->prectype==2, "MinCG: internal error (unexpected PrecType)", _state); + + /* + * low rank preconditioning + */ + result = 0.0; + for(i=0; i<=n-1; i++) + { + result = result+x->ptr.p_double[i]*y->ptr.p_double[i]/(state->diagh.ptr.p_double[i]+state->diaghl2.ptr.p_double[i]); + } + if( vcnt>0 ) + { + for(i=0; i<=n-1; i++) + { + work0->ptr.p_double[i] = x->ptr.p_double[i]/(state->diagh.ptr.p_double[i]+state->diaghl2.ptr.p_double[i]); + work1->ptr.p_double[i] = y->ptr.p_double[i]/(state->diagh.ptr.p_double[i]+state->diaghl2.ptr.p_double[i]); + } + for(i=0; i<=vcnt-1; i++) + { + v0 = ae_v_dotproduct(&work0->ptr.p_double[0], 1, &state->vcorr.ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + v1 = ae_v_dotproduct(&work1->ptr.p_double[0], 1, &state->vcorr.ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + result = result-v0*v1; + } + } + return result; +} + + +/************************************************************************* +Internal initialization subroutine + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +static void mincg_mincginitinternal(ae_int_t n, + double diffstep, + mincgstate* state, + ae_state *_state) +{ + ae_int_t i; + + + + /* + * Initialize + */ + state->teststep = 0; + state->n = n; + state->diffstep = diffstep; + mincgsetcond(state, 0, 0, 0, 0, _state); + mincgsetxrep(state, ae_false, _state); + mincgsetdrep(state, ae_false, _state); + mincgsetstpmax(state, 0, _state); + mincgsetcgtype(state, -1, _state); + mincgsetprecdefault(state, _state); + ae_vector_set_length(&state->xk, n, _state); + ae_vector_set_length(&state->dk, n, _state); + ae_vector_set_length(&state->xn, n, _state); + ae_vector_set_length(&state->dn, n, _state); + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->d, n, _state); + ae_vector_set_length(&state->g, n, _state); + ae_vector_set_length(&state->work0, n, _state); + ae_vector_set_length(&state->work1, n, _state); + ae_vector_set_length(&state->yk, n, _state); + ae_vector_set_length(&state->s, n, _state); + for(i=0; i<=n-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + } +} + + +ae_bool _mincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mincgstate *p = (mincgstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->diagh, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->diaghl2, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->vcorr, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->yk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !_linminstate_init(&p->lstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->work0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->work1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _mincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mincgstate *dst = (mincgstate*)_dst; + mincgstate *src = (mincgstate*)_src; + dst->n = src->n; + dst->epsg = src->epsg; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->stpmax = src->stpmax; + dst->suggestedstep = src->suggestedstep; + dst->xrep = src->xrep; + dst->drep = src->drep; + dst->cgtype = src->cgtype; + dst->prectype = src->prectype; + if( !ae_vector_init_copy(&dst->diagh, &src->diagh, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->diaghl2, &src->diaghl2, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->vcorr, &src->vcorr, _state, make_automatic) ) + return ae_false; + dst->vcnt = src->vcnt; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + dst->diffstep = src->diffstep; + dst->nfev = src->nfev; + dst->mcstage = src->mcstage; + dst->k = src->k; + if( !ae_vector_init_copy(&dst->xk, &src->xk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dk, &src->dk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xn, &src->xn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dn, &src->dn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + dst->fold = src->fold; + dst->stp = src->stp; + dst->curstpmax = src->curstpmax; + if( !ae_vector_init_copy(&dst->yk, &src->yk, _state, make_automatic) ) + return ae_false; + dst->lastgoodstep = src->lastgoodstep; + dst->lastscaledstep = src->lastscaledstep; + dst->mcinfo = src->mcinfo; + dst->innerresetneeded = src->innerresetneeded; + dst->terminationneeded = src->terminationneeded; + dst->trimthreshold = src->trimthreshold; + dst->rstimer = src->rstimer; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + dst->needf = src->needf; + dst->needfg = src->needfg; + dst->xupdated = src->xupdated; + dst->algpowerup = src->algpowerup; + dst->lsstart = src->lsstart; + dst->lsend = src->lsend; + dst->teststep = src->teststep; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->repiterationscount = src->repiterationscount; + dst->repnfev = src->repnfev; + dst->repvaridx = src->repvaridx; + dst->repterminationtype = src->repterminationtype; + dst->debugrestartscount = src->debugrestartscount; + if( !_linminstate_init_copy(&dst->lstate, &src->lstate, _state, make_automatic) ) + return ae_false; + dst->fbase = src->fbase; + dst->fm2 = src->fm2; + dst->fm1 = src->fm1; + dst->fp1 = src->fp1; + dst->fp2 = src->fp2; + dst->betahs = src->betahs; + dst->betady = src->betady; + if( !ae_vector_init_copy(&dst->work0, &src->work0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->work1, &src->work1, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _mincgstate_clear(void* _p) +{ + mincgstate *p = (mincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->diagh); + ae_vector_clear(&p->diaghl2); + ae_matrix_clear(&p->vcorr); + ae_vector_clear(&p->s); + ae_vector_clear(&p->xk); + ae_vector_clear(&p->dk); + ae_vector_clear(&p->xn); + ae_vector_clear(&p->dn); + ae_vector_clear(&p->d); + ae_vector_clear(&p->yk); + ae_vector_clear(&p->x); + ae_vector_clear(&p->g); + _rcommstate_clear(&p->rstate); + _linminstate_clear(&p->lstate); + ae_vector_clear(&p->work0); + ae_vector_clear(&p->work1); +} + + +void _mincgstate_destroy(void* _p) +{ + mincgstate *p = (mincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->diagh); + ae_vector_destroy(&p->diaghl2); + ae_matrix_destroy(&p->vcorr); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->xk); + ae_vector_destroy(&p->dk); + ae_vector_destroy(&p->xn); + ae_vector_destroy(&p->dn); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->yk); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->g); + _rcommstate_destroy(&p->rstate); + _linminstate_destroy(&p->lstate); + ae_vector_destroy(&p->work0); + ae_vector_destroy(&p->work1); +} + + +ae_bool _mincgreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + mincgreport *p = (mincgreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _mincgreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + mincgreport *dst = (mincgreport*)_dst; + mincgreport *src = (mincgreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nfev = src->nfev; + dst->varidx = src->varidx; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _mincgreport_clear(void* _p) +{ + mincgreport *p = (mincgreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _mincgreport_destroy(void* _p) +{ + mincgreport *p = (mincgreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* + BOUND CONSTRAINED OPTIMIZATION + WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments subject to any +combination of: +* bound constraints +* linear inequality constraints +* linear equality constraints + +REQUIREMENTS: +* user must provide function value and gradient +* starting point X0 must be feasible or + not too far away from the feasible set +* grad(f) must be Lipschitz continuous on a level set: + L = { x : f(x)<=f(x0) } +* function must be defined everywhere on the feasible set F + +USAGE: + +Constrained optimization if far more complex than the unconstrained one. +Here we give very brief outline of the BLEIC optimizer. We strongly recommend +you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on optimization, which is available at http://www.alglib.net/optimization/ + +1. User initializes algorithm state with MinBLEICCreate() call + +2. USer adds boundary and/or linear constraints by calling + MinBLEICSetBC() and MinBLEICSetLC() functions. + +3. User sets stopping conditions with MinBLEICSetCond(). + +4. User calls MinBLEICOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +5. User calls MinBLEICResults() to get solution + +6. Optionally user may call MinBLEICRestartFrom() to solve another problem + with same N but another starting point. + MinBLEICRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size ofX + X - starting point, array[N]: + * it is better to set X to a feasible point + * but X can be infeasible, in which case algorithm will try + to find feasible point first, using X as initial + approximation. + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreate(ae_int_t n, + /* Real */ ae_vector* x, + minbleicstate* state, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix c; + ae_vector ct; + + ae_frame_make(_state, &_frame_block); + _minbleicstate_clear(state); + ae_matrix_init(&c, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ct, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "MinBLEICCreate: N<1", _state); + ae_assert(x->cnt>=n, "MinBLEICCreate: Length(X)0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinBLEICSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. CG needs exact gradient values. Imprecise + gradient may slow down convergence, especially on highly nonlinear + problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreatef(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + minbleicstate* state, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix c; + ae_vector ct; + + ae_frame_make(_state, &_frame_block); + _minbleicstate_clear(state); + ae_matrix_init(&c, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ct, 0, DT_INT, _state, ae_true); + + ae_assert(n>=1, "MinBLEICCreateF: N<1", _state); + ae_assert(x->cnt>=n, "MinBLEICCreateF: Length(X)nmain; + ae_assert(bndl->cnt>=n, "MinBLEICSetBC: Length(BndL)cnt>=n, "MinBLEICSetBC: Length(BndU)ptr.p_double[i], _state)||ae_isneginf(bndl->ptr.p_double[i], _state), "MinBLEICSetBC: BndL contains NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.p_double[i], _state)||ae_isposinf(bndu->ptr.p_double[i], _state), "MinBLEICSetBC: BndL contains NAN or -INF", _state); + state->bndl.ptr.p_double[i] = bndl->ptr.p_double[i]; + state->hasbndl.ptr.p_bool[i] = ae_isfinite(bndl->ptr.p_double[i], _state); + state->bndu.ptr.p_double[i] = bndu->ptr.p_double[i]; + state->hasbndu.ptr.p_bool[i] = ae_isfinite(bndu->ptr.p_double[i], _state); + } + sassetbc(&state->sas, bndl, bndu, _state); +} + + +/************************************************************************* +This function sets linear constraints for BLEIC optimizer. + +Linear constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetlc(minbleicstate* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double v; + + + n = state->nmain; + + /* + * First, check for errors in the inputs + */ + ae_assert(k>=0, "MinBLEICSetLC: K<0", _state); + ae_assert(c->cols>=n+1||k==0, "MinBLEICSetLC: Cols(C)rows>=k, "MinBLEICSetLC: Rows(C)cnt>=k, "MinBLEICSetLC: Length(CT)nec = 0; + state->nic = 0; + return; + } + + /* + * Equality constraints are stored first, in the upper + * NEC rows of State.CLEIC matrix. Inequality constraints + * are stored in the next NIC rows. + * + * NOTE: we convert inequality constraints to the form + * A*x<=b before copying them. + */ + rmatrixsetlengthatleast(&state->cleic, k, n+1, _state); + state->nec = 0; + state->nic = 0; + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]==0 ) + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + state->nec = state->nec+1; + } + } + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]!=0 ) + { + if( ct->ptr.p_int[i]>0 ) + { + ae_v_moveneg(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + else + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + state->nic = state->nic+1; + } + } + + /* + * Normalize rows of State.CLEIC: each row must have unit norm. + * Norm is calculated using first N elements (i.e. right part is + * not counted when we calculate norm). + */ + for(i=0; i<=k-1; i++) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->cleic.ptr.pp_double[i][j], _state); + } + if( ae_fp_eq(v,0) ) + { + continue; + } + v = 1/ae_sqrt(v, _state); + ae_v_muld(&state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n), v); + } + sassetlc(&state->sas, c, ct, k, _state); +} + + +/************************************************************************* +This function sets stopping conditions for the optimizer. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinBLEICSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection. + +NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver may perform + slightly more than MaxIts iterations. I.e., MaxIts sets non-strict + limit on iterations count. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetcond(minbleicstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinBLEICSetCond: EpsG is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinBLEICSetCond: negative EpsG", _state); + ae_assert(ae_isfinite(epsf, _state), "MinBLEICSetCond: EpsF is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinBLEICSetCond: negative EpsF", _state); + ae_assert(ae_isfinite(epsx, _state), "MinBLEICSetCond: EpsX is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinBLEICSetCond: negative EpsX", _state); + ae_assert(maxits>=0, "MinBLEICSetCond: negative MaxIts!", _state); + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->epsg = epsg; + state->epsf = epsf; + state->epsx = epsx; + state->maxits = maxits; +} + + +/************************************************************************* +This function sets scaling coefficients for BLEIC 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the BLEIC too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinBLEICSetPrec...() +functions. + +There is a special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetscale(minbleicstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(s->cnt>=state->nmain, "MinBLEICSetScale: Length(S)nmain-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "MinBLEICSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "MinBLEICSetScale: S contains zero elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } + sassetscale(&state->sas, s, _state); +} + + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdefault(minbleicstate* state, ae_state *_state) +{ + + + state->prectype = 0; +} + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE 1: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdiag(minbleicstate* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(d->cnt>=state->nmain, "MinBLEICSetPrecDiag: D is too short", _state); + for(i=0; i<=state->nmain-1; i++) + { + ae_assert(ae_isfinite(d->ptr.p_double[i], _state), "MinBLEICSetPrecDiag: D contains infinite or NAN elements", _state); + ae_assert(ae_fp_greater(d->ptr.p_double[i],0), "MinBLEICSetPrecDiag: D contains non-positive elements", _state); + } + rvectorsetlengthatleast(&state->diagh, state->nmain, _state); + state->prectype = 2; + for(i=0; i<=state->nmain-1; i++) + { + state->diagh.ptr.p_double[i] = d->ptr.p_double[i]; + } +} + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinBLEICSetScale() +call (before or after MinBLEICSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecscale(minbleicstate* state, ae_state *_state) +{ + + + state->prectype = 3; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinBLEICOptimize(). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetxrep(minbleicstate* state, + ae_bool needxrep, + ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +This function turns on/off line search reports. +These reports are described in more details in developer-only comments on +MinBLEICState object. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedDRep- whether line search reports are needed or not + +This function is intended for private use only. Turning it on artificially +may cause program failure. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetdrep(minbleicstate* state, + ae_bool needdrep, + ae_state *_state) +{ + + + state->drep = needdrep; +} + + +/************************************************************************* +This function sets maximum step length + +IMPORTANT: this feature is hard to combine with preconditioning. You can't +set upper limit on step length, when you solve optimization problem with +linear (non-boundary) constraints AND preconditioner turned on. + +When non-boundary constraints are present, you have to either a) use +preconditioner, or b) use upper limit on step length. YOU CAN'T USE BOTH! +In this case algorithm will terminate with appropriate error code. + +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 lead 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetstpmax(minbleicstate* state, + double stpmax, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "MinBLEICSetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "MinBLEICSetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinBLEICCreate() for analytical gradient or MinBLEICCreateF() + for numerical differentiation) you should choose appropriate variant of + MinBLEICOptimize() - one which accepts function AND gradient or one + which accepts function ONLY. + + Be careful to choose variant of MinBLEICOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinBLEICOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinBLEICOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinBLEICCreateF() | work FAIL + MinBLEICCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinBLEICOptimize() version. Attemps to use such + combination (for example, to create optimizer with MinBLEICCreateF() + and to pass gradient information to MinCGOptimize()) will lead to + exception being thrown. Either you did not pass gradient when it WAS + needed or you passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +ae_bool minbleiciteration(minbleicstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t m; + ae_int_t i; + ae_int_t j; + double v; + double vv; + ae_int_t badbfgsits; + ae_bool b; + ae_int_t nextaction; + ae_int_t mcinfo; + ae_int_t actstatus; + ae_int_t ic; + double penalty; + double ginit; + double gdecay; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + i = state->rstate.ia.ptr.p_int[2]; + j = state->rstate.ia.ptr.p_int[3]; + badbfgsits = state->rstate.ia.ptr.p_int[4]; + nextaction = state->rstate.ia.ptr.p_int[5]; + mcinfo = state->rstate.ia.ptr.p_int[6]; + actstatus = state->rstate.ia.ptr.p_int[7]; + ic = state->rstate.ia.ptr.p_int[8]; + b = state->rstate.ba.ptr.p_bool[0]; + v = state->rstate.ra.ptr.p_double[0]; + vv = state->rstate.ra.ptr.p_double[1]; + penalty = state->rstate.ra.ptr.p_double[2]; + ginit = state->rstate.ra.ptr.p_double[3]; + gdecay = state->rstate.ra.ptr.p_double[4]; + } + else + { + n = -983; + m = -989; + i = -834; + j = 900; + badbfgsits = -287; + nextaction = 364; + mcinfo = 214; + actstatus = -338; + ic = -686; + b = ae_false; + v = 585; + vv = 497; + penalty = -271; + ginit = -581; + gdecay = 745; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + if( state->rstate.stage==14 ) + { + goto lbl_14; + } + if( state->rstate.stage==15 ) + { + goto lbl_15; + } + if( state->rstate.stage==16 ) + { + goto lbl_16; + } + if( state->rstate.stage==17 ) + { + goto lbl_17; + } + if( state->rstate.stage==18 ) + { + goto lbl_18; + } + if( state->rstate.stage==19 ) + { + goto lbl_19; + } + if( state->rstate.stage==20 ) + { + goto lbl_20; + } + if( state->rstate.stage==21 ) + { + goto lbl_21; + } + if( state->rstate.stage==22 ) + { + goto lbl_22; + } + if( state->rstate.stage==23 ) + { + goto lbl_23; + } + if( state->rstate.stage==24 ) + { + goto lbl_24; + } + if( state->rstate.stage==25 ) + { + goto lbl_25; + } + if( state->rstate.stage==26 ) + { + goto lbl_26; + } + if( state->rstate.stage==27 ) + { + goto lbl_27; + } + if( state->rstate.stage==28 ) + { + goto lbl_28; + } + if( state->rstate.stage==29 ) + { + goto lbl_29; + } + if( state->rstate.stage==30 ) + { + goto lbl_30; + } + if( state->rstate.stage==31 ) + { + goto lbl_31; + } + if( state->rstate.stage==32 ) + { + goto lbl_32; + } + if( state->rstate.stage==33 ) + { + goto lbl_33; + } + if( state->rstate.stage==34 ) + { + goto lbl_34; + } + if( state->rstate.stage==35 ) + { + goto lbl_35; + } + if( state->rstate.stage==36 ) + { + goto lbl_36; + } + if( state->rstate.stage==37 ) + { + goto lbl_37; + } + if( state->rstate.stage==38 ) + { + goto lbl_38; + } + if( state->rstate.stage==39 ) + { + goto lbl_39; + } + if( state->rstate.stage==40 ) + { + goto lbl_40; + } + if( state->rstate.stage==41 ) + { + goto lbl_41; + } + + /* + * Routine body + */ + + /* + * Algorithm parameters: + * * M number of L-BFGS corrections. + * This coefficient remains fixed during iterations. + * * GDecay desired decrease of constrained gradient during L-BFGS iterations. + * This coefficient is decreased after each L-BFGS round until + * it reaches minimum decay. + */ + m = ae_minint(5, state->nmain, _state); + gdecay = minbleic_initialdecay; + + /* + * Init + */ + n = state->nmain; + state->repterminationtype = 0; + state->repinneriterationscount = 0; + state->repouteriterationscount = 0; + state->repnfev = 0; + state->repvaridx = -1; + state->repdebugeqerr = 0.0; + state->repdebugfs = _state->v_nan; + state->repdebugff = _state->v_nan; + state->repdebugdx = _state->v_nan; + if( ae_fp_neq(state->stpmax,0)&&state->prectype!=0 ) + { + state->repterminationtype = -10; + result = ae_false; + return result; + } + rvectorsetlengthatleast(&state->rho, m, _state); + rvectorsetlengthatleast(&state->theta, m, _state); + rmatrixsetlengthatleast(&state->yk, m, n, _state); + rmatrixsetlengthatleast(&state->sk, m, n, _state); + + /* + * Fill TmpPrec with current preconditioner + */ + rvectorsetlengthatleast(&state->tmpprec, n, _state); + for(i=0; i<=n-1; i++) + { + if( state->prectype==2 ) + { + state->tmpprec.ptr.p_double[i] = state->diagh.ptr.p_double[i]; + continue; + } + if( state->prectype==3 ) + { + state->tmpprec.ptr.p_double[i] = 1/ae_sqr(state->s.ptr.p_double[i], _state); + continue; + } + state->tmpprec.ptr.p_double[i] = 1; + } + sassetprecdiag(&state->sas, &state->tmpprec, _state); + + /* + * Start optimization + */ + if( !sasstartoptimization(&state->sas, &state->xstart, _state) ) + { + state->repterminationtype = -3; + result = ae_false; + return result; + } + + /* + * Check correctness of user-supplied gradient + */ + if( !(ae_fp_eq(state->diffstep,0)&&ae_fp_greater(state->teststep,0)) ) + { + goto lbl_42; + } + minbleic_clearrequestfields(state, _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->needfg = ae_true; + i = 0; +lbl_44: + if( i>n-1 ) + { + goto lbl_46; + } + ae_assert(!state->hasbndl.ptr.p_bool[i]||ae_fp_greater_eq(state->sas.xc.ptr.p_double[i],state->bndl.ptr.p_double[i]), "MinBLEICIteration: internal error(State.X is out of bounds)", _state); + ae_assert(!state->hasbndu.ptr.p_bool[i]||ae_fp_less_eq(state->sas.xc.ptr.p_double[i],state->bndu.ptr.p_double[i]), "MinBLEICIteration: internal error(State.X is out of bounds)", _state); + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->teststep*state->s.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_maxreal(state->x.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + state->xm1 = state->x.ptr.p_double[i]; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->fm1 = state->f; + state->gm1 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = v+state->teststep*state->s.ptr.p_double[i]; + if( state->hasbndu.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_minreal(state->x.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + state->xp1 = state->x.ptr.p_double[i]; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->fp1 = state->f; + state->gp1 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = (state->xm1+state->xp1)/2; + if( state->hasbndl.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_maxreal(state->x.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + if( state->hasbndu.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_minreal(state->x.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->x.ptr.p_double[i] = v; + if( !derivativecheck(state->fm1, state->gm1, state->fp1, state->gp1, state->f, state->g.ptr.p_double[i], state->xp1-state->xm1, _state) ) + { + state->repvaridx = i; + state->repterminationtype = -7; + sasstopoptimization(&state->sas, _state); + result = ae_false; + return result; + } + i = i+1; + goto lbl_44; +lbl_46: + state->needfg = ae_false; +lbl_42: + + /* + * Main cycle of BLEIC-PG algorithm + */ + state->repterminationtype = 4; + badbfgsits = 0; + state->lastgoodstep = 0; + state->lastscaledgoodstep = 0; + state->maxscaledgrad = 0; + state->nonmonotoniccnt = n+state->nic; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minbleic_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_47; + } + state->needfg = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needfg = ae_false; + goto lbl_48; +lbl_47: + state->needf = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->needf = ae_false; +lbl_48: + state->fc = state->f; + trimprepare(state->f, &state->trimthreshold, _state); + state->repnfev = state->repnfev+1; + if( !state->xrep ) + { + goto lbl_49; + } + + /* + * Report current point + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fc; + state->xupdated = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->xupdated = ae_false; +lbl_49: +lbl_51: + if( ae_false ) + { + goto lbl_52; + } + + /* + * Phase 1 + * + * (a) calculate unconstrained gradient + * (b) determine active set + * (c) update MaxScaledGrad + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minbleic_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_53; + } + + /* + * Analytic gradient + */ + state->needfg = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->needfg = ae_false; + goto lbl_54; +lbl_53: + + /* + * Numerical differentiation + */ + state->needf = ae_true; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->fbase = state->f; + i = 0; +lbl_55: + if( i>n-1 ) + { + goto lbl_57; + } + v = state->x.ptr.p_double[i]; + b = ae_false; + if( state->hasbndl.ptr.p_bool[i] ) + { + b = b||ae_fp_less(v-state->diffstep*state->s.ptr.p_double[i],state->bndl.ptr.p_double[i]); + } + if( state->hasbndu.ptr.p_bool[i] ) + { + b = b||ae_fp_greater(v+state->diffstep*state->s.ptr.p_double[i],state->bndu.ptr.p_double[i]); + } + if( b ) + { + goto lbl_58; + } + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + state->fp2 = state->f; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + goto lbl_59; +lbl_58: + state->xm1 = v-state->diffstep*state->s.ptr.p_double[i]; + state->xp1 = v+state->diffstep*state->s.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(state->xm1,state->bndl.ptr.p_double[i]) ) + { + state->xm1 = state->bndl.ptr.p_double[i]; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(state->xp1,state->bndu.ptr.p_double[i]) ) + { + state->xp1 = state->bndu.ptr.p_double[i]; + } + state->x.ptr.p_double[i] = state->xm1; + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + state->fm1 = state->f; + state->x.ptr.p_double[i] = state->xp1; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->fp1 = state->f; + if( ae_fp_neq(state->xm1,state->xp1) ) + { + state->g.ptr.p_double[i] = (state->fp1-state->fm1)/(state->xp1-state->xm1); + } + else + { + state->g.ptr.p_double[i] = 0; + } +lbl_59: + state->x.ptr.p_double[i] = v; + i = i+1; + goto lbl_55; +lbl_57: + state->f = state->fbase; + state->needf = ae_false; +lbl_54: + state->fc = state->f; + ae_v_move(&state->gc.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasreactivateconstraintsprec(&state->sas, &state->gc, _state); + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->gc.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + state->maxscaledgrad = ae_maxreal(state->maxscaledgrad, ae_sqrt(v, _state), _state); + + /* + * Phase 2: perform steepest descent step. + * + * NextAction control variable is set on exit from this loop: + * * NextAction>0 in case we have to proceed to Phase 3 (L-BFGS step) + * * NextAction<0 in case we have to proceed to Phase 1 (recalculate active set) + * * NextAction=0 in case we found solution (step size or function change are small enough) + */ + nextaction = 0; +lbl_60: + if( ae_false ) + { + goto lbl_61; + } + + /* + * Check gradient-based stopping criteria + */ + if( ae_fp_less_eq(sasscaledconstrainednorm(&state->sas, &state->gc, _state),state->epsg) ) + { + + /* + * Gradient is small enough, stop iterations + */ + state->repterminationtype = 4; + nextaction = 0; + goto lbl_61; + } + + /* + * Calculate normalized constrained descent direction, store to D. + * Try to use previous scaled step length as initial estimate for new step. + * + * NOTE: D can be exactly zero, in this case Stp is set to 1.0 + */ + sasconstraineddescentprec(&state->sas, &state->gc, &state->d, _state); + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->d.ptr.p_double[i]/state->s.ptr.p_double[i], _state); + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(state->lastscaledgoodstep,0)&&ae_fp_greater(v,0) ) + { + state->stp = state->lastscaledgoodstep/v; + } + else + { + state->stp = 1.0; + } + + /* + * Calculate bound on step length. + * Enforce user-supplied limit on step length. + */ + sasexploredirection(&state->sas, &state->d, &state->curstpmax, &state->cidx, &state->cval, _state); + state->activationstep = state->curstpmax; + if( state->cidx>=0&&ae_fp_eq(state->activationstep,0) ) + { + sasimmediateactivation(&state->sas, state->cidx, state->cval, _state); + goto lbl_60; + } + if( ae_fp_greater(state->stpmax,0) ) + { + state->curstpmax = ae_minreal(state->curstpmax, state->stpmax, _state); + } + + /* + * Report beginning of line search (if requested by caller). + * See description of the MinBLEICState for more information + * about fields accessible to caller. + * + * Caller may do following: + * * change State.Stp and load better initial estimate of + * the step length. + */ + if( !state->drep ) + { + goto lbl_62; + } + minbleic_clearrequestfields(state, _state); + state->lsstart = ae_true; + state->lbfgssearch = ae_false; + state->boundedstep = state->cidx>=0; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->g.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fc; + state->rstate.stage = 14; + goto lbl_rcomm; +lbl_14: + state->lsstart = ae_false; +lbl_62: + + /* + * Perform optimization of F along XC+alpha*D. + */ + state->mcstage = 0; + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->gn.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fn = state->fc; + mcsrch(n, &state->xn, &state->fn, &state->gn, &state->d, &state->stp, state->curstpmax, minbleic_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); +lbl_64: + if( state->mcstage==0 ) + { + goto lbl_65; + } + + /* + * Enforce constraints (correction) in XN. + * Copy current point from XN to X. + */ + sascorrection(&state->sas, &state->xn, &penalty, _state); + for(i=0; i<=n-1; i++) + { + state->x.ptr.p_double[i] = state->xn.ptr.p_double[i]; + } + + /* + * Gradient, either user-provided or numerical differentiation + */ + minbleic_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_66; + } + + /* + * Analytic gradient + */ + state->needfg = ae_true; + state->rstate.stage = 15; + goto lbl_rcomm; +lbl_15: + state->needfg = ae_false; + state->repnfev = state->repnfev+1; + goto lbl_67; +lbl_66: + + /* + * Numerical differentiation + */ + state->needf = ae_true; + state->rstate.stage = 16; + goto lbl_rcomm; +lbl_16: + state->fbase = state->f; + i = 0; +lbl_68: + if( i>n-1 ) + { + goto lbl_70; + } + v = state->x.ptr.p_double[i]; + b = ae_false; + if( state->hasbndl.ptr.p_bool[i] ) + { + b = b||ae_fp_less(v-state->diffstep*state->s.ptr.p_double[i],state->bndl.ptr.p_double[i]); + } + if( state->hasbndu.ptr.p_bool[i] ) + { + b = b||ae_fp_greater(v+state->diffstep*state->s.ptr.p_double[i],state->bndu.ptr.p_double[i]); + } + if( b ) + { + goto lbl_71; + } + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 17; + goto lbl_rcomm; +lbl_17: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 18; + goto lbl_rcomm; +lbl_18: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 19; + goto lbl_rcomm; +lbl_19: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 20; + goto lbl_rcomm; +lbl_20: + state->fp2 = state->f; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + state->repnfev = state->repnfev+4; + goto lbl_72; +lbl_71: + state->xm1 = v-state->diffstep*state->s.ptr.p_double[i]; + state->xp1 = v+state->diffstep*state->s.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(state->xm1,state->bndl.ptr.p_double[i]) ) + { + state->xm1 = state->bndl.ptr.p_double[i]; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(state->xp1,state->bndu.ptr.p_double[i]) ) + { + state->xp1 = state->bndu.ptr.p_double[i]; + } + state->x.ptr.p_double[i] = state->xm1; + state->rstate.stage = 21; + goto lbl_rcomm; +lbl_21: + state->fm1 = state->f; + state->x.ptr.p_double[i] = state->xp1; + state->rstate.stage = 22; + goto lbl_rcomm; +lbl_22: + state->fp1 = state->f; + if( ae_fp_neq(state->xm1,state->xp1) ) + { + state->g.ptr.p_double[i] = (state->fp1-state->fm1)/(state->xp1-state->xm1); + } + else + { + state->g.ptr.p_double[i] = 0; + } + state->repnfev = state->repnfev+2; +lbl_72: + state->x.ptr.p_double[i] = v; + i = i+1; + goto lbl_68; +lbl_70: + state->f = state->fbase; + state->needf = ae_false; +lbl_67: + + /* + * Back to MCSRCH + * + * NOTE: penalty term from correction is added to FN in order + * to penalize increase in infeasibility. + */ + state->fn = state->f+minbleic_penaltyfactor*state->maxscaledgrad*penalty; + ae_v_move(&state->gn.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + trimfunction(&state->fn, &state->gn, n, state->trimthreshold, _state); + mcsrch(n, &state->xn, &state->fn, &state->gn, &state->d, &state->stp, state->curstpmax, minbleic_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); + goto lbl_64; +lbl_65: + + /* + * Handle possible failure of the line search + */ + if( mcinfo!=1&&mcinfo!=5 ) + { + + /* + * We can not find step which decreases function value. We have + * two possibilities: + * (a) numerical properties of the function do not allow us to + * find good solution. + * (b) we are close to activation of some constraint, and it is + * so close that step which activates it leads to change in + * target function which is smaller than numerical noise. + * + * Optimization algorithm must be able to handle case (b), because + * inability to handle it will cause failure when algorithm + * started very close to boundary of the feasible area. + * + * In order to correctly handle such cases we allow limited amount + * of small steps which increase function value. + */ + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->d.ptr.p_double[i]*state->curstpmax/state->s.ptr.p_double[i], _state); + } + v = ae_sqrt(v, _state); + if( (state->cidx>=0&&ae_fp_less_eq(v,minbleic_maxnonmonotoniclen))&&state->nonmonotoniccnt>0 ) + { + + /* + * We enforce non-monotonic step: + * * Stp := CurStpMax + * * MCINFO := 5 + * * XN := XC+CurStpMax*D + * * non-monotonic counter is decreased + */ + state->stp = state->curstpmax; + mcinfo = 5; + v = state->curstpmax; + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->xn.ptr.p_double[0], 1, &state->d.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + state->nonmonotoniccnt = state->nonmonotoniccnt-1; + } + else + { + + /* + * Numerical properties of the function does not allow us to solve problem + */ + state->repterminationtype = 7; + nextaction = 0; + goto lbl_61; + } + } + + /* + * Current point is updated. + */ + ae_v_move(&state->xp.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->gp.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fp = state->fc; + actstatus = sasmoveto(&state->sas, &state->xn, state->cidx>=0&&ae_fp_greater_eq(state->stp,state->activationstep), state->cidx, state->cval, _state); + ae_v_move(&state->gc.ptr.p_double[0], 1, &state->gn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fc = state->fn; + state->repinneriterationscount = state->repinneriterationscount+1; + if( !state->xrep ) + { + goto lbl_73; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minbleic_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 23; + goto lbl_rcomm; +lbl_23: + state->xupdated = ae_false; +lbl_73: + + /* + * Check for stopping. + * + * Step, gradient and function-based stopping criteria are tested only + * for steps which satisfy Wolfe conditions. + * + * MaxIts-based stopping condition is checked for all steps + */ + if( mcinfo==1 ) + { + + /* + * Step is small enough + */ + v = 0; + vv = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr((state->sas.xc.ptr.p_double[i]-state->xp.ptr.p_double[i])/state->s.ptr.p_double[i], _state); + vv = vv+ae_sqr(state->sas.xc.ptr.p_double[i]-state->xp.ptr.p_double[i], _state); + } + v = ae_sqrt(v, _state); + vv = ae_sqrt(vv, _state); + if( ae_fp_less_eq(v,state->epsx) ) + { + state->repterminationtype = 2; + nextaction = 0; + goto lbl_61; + } + state->lastgoodstep = vv; + minbleic_updateestimateofgoodstep(&state->lastscaledgoodstep, v, _state); + + /* + * Function change is small enough + */ + if( ae_fp_less_eq(ae_fabs(state->fp-state->fc, _state),state->epsf*ae_maxreal(ae_fabs(state->fc, _state), ae_maxreal(ae_fabs(state->fp, _state), 1.0, _state), _state)) ) + { + + /* + * Function change is small enough + */ + state->repterminationtype = 1; + nextaction = 0; + goto lbl_61; + } + } + if( state->maxits>0&&state->repinneriterationscount>=state->maxits ) + { + + /* + * Required number of iterations was performed + */ + state->repterminationtype = 5; + nextaction = 0; + goto lbl_61; + } + + /* + * Decide where to move: + * * in case only "candidate" constraints were activated, repeat stage 2 + * * in case no constraints was activated, move to stage 3 + * * otherwise, move to stage 1 (re-evaluation of the active set) + */ + if( actstatus==0 ) + { + goto lbl_60; + } + if( actstatus<0 ) + { + nextaction = 1; + } + else + { + nextaction = -1; + } + goto lbl_61; + goto lbl_60; +lbl_61: + if( nextaction<0 ) + { + goto lbl_51; + } + if( nextaction==0 ) + { + goto lbl_52; + } + + /* + * Phase 3: L-BFGS step + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minbleic_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_75; + } + + /* + * Analytic gradient + */ + state->needfg = ae_true; + state->rstate.stage = 24; + goto lbl_rcomm; +lbl_24: + state->needfg = ae_false; + state->repnfev = state->repnfev+1; + goto lbl_76; +lbl_75: + + /* + * Numerical differentiation + */ + state->needf = ae_true; + state->rstate.stage = 25; + goto lbl_rcomm; +lbl_25: + state->fbase = state->f; + i = 0; +lbl_77: + if( i>n-1 ) + { + goto lbl_79; + } + v = state->x.ptr.p_double[i]; + b = ae_false; + if( state->hasbndl.ptr.p_bool[i] ) + { + b = b||ae_fp_less(v-state->diffstep*state->s.ptr.p_double[i],state->bndl.ptr.p_double[i]); + } + if( state->hasbndu.ptr.p_bool[i] ) + { + b = b||ae_fp_greater(v+state->diffstep*state->s.ptr.p_double[i],state->bndu.ptr.p_double[i]); + } + if( b ) + { + goto lbl_80; + } + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 26; + goto lbl_rcomm; +lbl_26: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 27; + goto lbl_rcomm; +lbl_27: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 28; + goto lbl_rcomm; +lbl_28: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 29; + goto lbl_rcomm; +lbl_29: + state->fp2 = state->f; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + state->repnfev = state->repnfev+4; + goto lbl_81; +lbl_80: + state->xm1 = v-state->diffstep*state->s.ptr.p_double[i]; + state->xp1 = v+state->diffstep*state->s.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(state->xm1,state->bndl.ptr.p_double[i]) ) + { + state->xm1 = state->bndl.ptr.p_double[i]; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(state->xp1,state->bndu.ptr.p_double[i]) ) + { + state->xp1 = state->bndu.ptr.p_double[i]; + } + state->x.ptr.p_double[i] = state->xm1; + state->rstate.stage = 30; + goto lbl_rcomm; +lbl_30: + state->fm1 = state->f; + state->x.ptr.p_double[i] = state->xp1; + state->rstate.stage = 31; + goto lbl_rcomm; +lbl_31: + state->fp1 = state->f; + if( ae_fp_neq(state->xm1,state->xp1) ) + { + state->g.ptr.p_double[i] = (state->fp1-state->fm1)/(state->xp1-state->xm1); + } + else + { + state->g.ptr.p_double[i] = 0; + } + state->repnfev = state->repnfev+2; +lbl_81: + state->x.ptr.p_double[i] = v; + i = i+1; + goto lbl_77; +lbl_79: + state->f = state->fbase; + state->needf = ae_false; +lbl_76: + state->fc = state->f; + trimprepare(state->fc, &state->trimthreshold, _state); + ae_v_move(&state->gc.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_moveneg(&state->d.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasconstraineddirection(&state->sas, &state->gc, _state); + sasconstraineddirectionprec(&state->sas, &state->d, _state); + ginit = 0.0; + for(i=0; i<=n-1; i++) + { + ginit = ginit+ae_sqr(state->gc.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + ginit = ae_sqrt(ginit, _state); + state->k = 0; +lbl_82: + if( state->k>n ) + { + goto lbl_83; + } + + /* + * Main cycle: prepare to 1-D line search + */ + state->p = state->k%m; + state->q = ae_minint(state->k, m-1, _state); + + /* + * Store X[k], G[k] + */ + ae_v_moveneg(&state->sk.ptr.pp_double[state->p][0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_moveneg(&state->yk.ptr.pp_double[state->p][0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Try to use previous scaled step length as initial estimate for new step. + */ + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->d.ptr.p_double[i]/state->s.ptr.p_double[i], _state); + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(state->lastscaledgoodstep,0)&&ae_fp_greater(v,0) ) + { + state->stp = state->lastscaledgoodstep/v; + } + else + { + state->stp = 1.0; + } + + /* + * Calculate bound on step length + */ + sasexploredirection(&state->sas, &state->d, &state->curstpmax, &state->cidx, &state->cval, _state); + state->activationstep = state->curstpmax; + if( state->cidx>=0&&ae_fp_eq(state->activationstep,0) ) + { + goto lbl_83; + } + if( ae_fp_greater(state->stpmax,0) ) + { + v = ae_v_dotproduct(&state->d.ptr.p_double[0], 1, &state->d.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = ae_sqrt(v, _state); + if( ae_fp_greater(v,0) ) + { + state->curstpmax = ae_minreal(state->curstpmax, state->stpmax/v, _state); + } + } + + /* + * Report beginning of line search (if requested by caller). + * See description of the MinBLEICState for more information + * about fields accessible to caller. + * + * Caller may do following: + * * change State.Stp and load better initial estimate of + * the step length. + * Caller may not terminate algorithm. + */ + if( !state->drep ) + { + goto lbl_84; + } + minbleic_clearrequestfields(state, _state); + state->lsstart = ae_true; + state->lbfgssearch = ae_true; + state->boundedstep = state->cidx>=0; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 32; + goto lbl_rcomm; +lbl_32: + state->lsstart = ae_false; +lbl_84: + + /* + * Minimize F(x+alpha*d) + */ + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->gn.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fn = state->fc; + state->mcstage = 0; + mcsrch(n, &state->xn, &state->fn, &state->gn, &state->d, &state->stp, state->curstpmax, minbleic_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); +lbl_86: + if( state->mcstage==0 ) + { + goto lbl_87; + } + + /* + * Perform correction (constraints are enforced) + * Copy XN to X + */ + sascorrection(&state->sas, &state->xn, &penalty, _state); + for(i=0; i<=n-1; i++) + { + state->x.ptr.p_double[i] = state->xn.ptr.p_double[i]; + } + + /* + * Gradient, either user-provided or numerical differentiation + */ + minbleic_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_88; + } + + /* + * Analytic gradient + */ + state->needfg = ae_true; + state->rstate.stage = 33; + goto lbl_rcomm; +lbl_33: + state->needfg = ae_false; + state->repnfev = state->repnfev+1; + goto lbl_89; +lbl_88: + + /* + * Numerical differentiation + */ + state->needf = ae_true; + state->rstate.stage = 34; + goto lbl_rcomm; +lbl_34: + state->fbase = state->f; + i = 0; +lbl_90: + if( i>n-1 ) + { + goto lbl_92; + } + v = state->x.ptr.p_double[i]; + b = ae_false; + if( state->hasbndl.ptr.p_bool[i] ) + { + b = b||ae_fp_less(v-state->diffstep*state->s.ptr.p_double[i],state->bndl.ptr.p_double[i]); + } + if( state->hasbndu.ptr.p_bool[i] ) + { + b = b||ae_fp_greater(v+state->diffstep*state->s.ptr.p_double[i],state->bndu.ptr.p_double[i]); + } + if( b ) + { + goto lbl_93; + } + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 35; + goto lbl_rcomm; +lbl_35: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 36; + goto lbl_rcomm; +lbl_36: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 37; + goto lbl_rcomm; +lbl_37: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 38; + goto lbl_rcomm; +lbl_38: + state->fp2 = state->f; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + state->repnfev = state->repnfev+4; + goto lbl_94; +lbl_93: + state->xm1 = v-state->diffstep*state->s.ptr.p_double[i]; + state->xp1 = v+state->diffstep*state->s.ptr.p_double[i]; + if( state->hasbndl.ptr.p_bool[i]&&ae_fp_less(state->xm1,state->bndl.ptr.p_double[i]) ) + { + state->xm1 = state->bndl.ptr.p_double[i]; + } + if( state->hasbndu.ptr.p_bool[i]&&ae_fp_greater(state->xp1,state->bndu.ptr.p_double[i]) ) + { + state->xp1 = state->bndu.ptr.p_double[i]; + } + state->x.ptr.p_double[i] = state->xm1; + state->rstate.stage = 39; + goto lbl_rcomm; +lbl_39: + state->fm1 = state->f; + state->x.ptr.p_double[i] = state->xp1; + state->rstate.stage = 40; + goto lbl_rcomm; +lbl_40: + state->fp1 = state->f; + if( ae_fp_neq(state->xm1,state->xp1) ) + { + state->g.ptr.p_double[i] = (state->fp1-state->fm1)/(state->xp1-state->xm1); + } + else + { + state->g.ptr.p_double[i] = 0; + } + state->repnfev = state->repnfev+2; +lbl_94: + state->x.ptr.p_double[i] = v; + i = i+1; + goto lbl_90; +lbl_92: + state->f = state->fbase; + state->needf = ae_false; +lbl_89: + + /* + * Back to MCSRCH + * + * NOTE: penalty term from correction is added to FN in order + * to penalize increase in infeasibility. + */ + state->fn = state->f+minbleic_penaltyfactor*state->maxscaledgrad*penalty; + ae_v_move(&state->gn.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasconstraineddirection(&state->sas, &state->gn, _state); + trimfunction(&state->fn, &state->gn, n, state->trimthreshold, _state); + mcsrch(n, &state->xn, &state->fn, &state->gn, &state->d, &state->stp, state->curstpmax, minbleic_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); + goto lbl_86; +lbl_87: + ae_v_add(&state->sk.ptr.pp_double[state->p][0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->yk.ptr.pp_double[state->p][0], 1, &state->gn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Handle possible failure of the line search + */ + if( mcinfo!=1&&mcinfo!=5 ) + { + goto lbl_83; + } + + /* + * Current point is updated. + */ + ae_v_move(&state->xp.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->gp.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fp = state->fc; + actstatus = sasmoveto(&state->sas, &state->xn, state->cidx>=0&&ae_fp_greater_eq(state->stp,state->activationstep), state->cidx, state->cval, _state); + ae_v_move(&state->gc.ptr.p_double[0], 1, &state->gn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fc = state->fn; + if( !state->xrep ) + { + goto lbl_95; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minbleic_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 41; + goto lbl_rcomm; +lbl_41: + state->xupdated = ae_false; +lbl_95: + state->repinneriterationscount = state->repinneriterationscount+1; + + /* + * Update length of the good step + */ + if( mcinfo==1 ) + { + v = 0; + vv = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr((state->sas.xc.ptr.p_double[i]-state->xp.ptr.p_double[i])/state->s.ptr.p_double[i], _state); + vv = vv+ae_sqr(state->sas.xc.ptr.p_double[i]-state->xp.ptr.p_double[i], _state); + } + state->lastgoodstep = ae_sqrt(vv, _state); + minbleic_updateestimateofgoodstep(&state->lastscaledgoodstep, ae_sqrt(v, _state), _state); + } + + /* + * Termination of the L-BFGS algorithm: + * a) line search was performed with activation of constraint + * b) scaled gradient decreased below GDecay + * c) iterations counter >= MaxIts + */ + if( actstatus>=0 ) + { + goto lbl_83; + } + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->gc.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + if( ae_fp_less(ae_sqrt(v, _state),gdecay*ginit) ) + { + goto lbl_83; + } + if( state->maxits>0&&state->repinneriterationscount>=state->maxits ) + { + goto lbl_83; + } + + /* + * Update L-BFGS model: + * * calculate Rho[k] + * * calculate d(k+1) = -H(k+1)*g(k+1) + * (use constrained preconditioner to perform multiplication) + */ + v = ae_v_dotproduct(&state->yk.ptr.pp_double[state->p][0], 1, &state->sk.ptr.pp_double[state->p][0], 1, ae_v_len(0,n-1)); + vv = ae_v_dotproduct(&state->yk.ptr.pp_double[state->p][0], 1, &state->yk.ptr.pp_double[state->p][0], 1, ae_v_len(0,n-1)); + if( ae_fp_eq(v,0)||ae_fp_eq(vv,0) ) + { + goto lbl_83; + } + state->rho.ptr.p_double[state->p] = 1/v; + ae_v_move(&state->work.ptr.p_double[0], 1, &state->gn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=state->k; i>=state->k-state->q; i--) + { + ic = i%m; + v = ae_v_dotproduct(&state->sk.ptr.pp_double[ic][0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->theta.ptr.p_double[ic] = v; + vv = v*state->rho.ptr.p_double[ic]; + ae_v_subd(&state->work.ptr.p_double[0], 1, &state->yk.ptr.pp_double[ic][0], 1, ae_v_len(0,n-1), vv); + } + sasconstraineddirectionprec(&state->sas, &state->work, _state); + for(i=state->k-state->q; i<=state->k; i++) + { + ic = i%m; + v = ae_v_dotproduct(&state->yk.ptr.pp_double[ic][0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + vv = state->rho.ptr.p_double[ic]*(-v+state->theta.ptr.p_double[ic]); + ae_v_addd(&state->work.ptr.p_double[0], 1, &state->sk.ptr.pp_double[ic][0], 1, ae_v_len(0,n-1), vv); + } + ae_v_moveneg(&state->d.ptr.p_double[0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->k = state->k+1; + goto lbl_82; +lbl_83: + + /* + * Decrease decay coefficient. Subsequent L-BFGS stages will + * have more stringent stopping criteria. + */ + gdecay = ae_maxreal(gdecay*minbleic_decaycorrection, minbleic_mindecay, _state); + goto lbl_51; +lbl_52: + sasstopoptimization(&state->sas, _state); + state->repouteriterationscount = 1; + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = i; + state->rstate.ia.ptr.p_int[3] = j; + state->rstate.ia.ptr.p_int[4] = badbfgsits; + state->rstate.ia.ptr.p_int[5] = nextaction; + state->rstate.ia.ptr.p_int[6] = mcinfo; + state->rstate.ia.ptr.p_int[7] = actstatus; + state->rstate.ia.ptr.p_int[8] = ic; + state->rstate.ba.ptr.p_bool[0] = b; + state->rstate.ra.ptr.p_double[0] = v; + state->rstate.ra.ptr.p_double[1] = vv; + state->rstate.ra.ptr.p_double[2] = penalty; + state->rstate.ra.ptr.p_double[3] = ginit; + state->rstate.ra.ptr.p_double[4] = gdecay; + return result; +} + + +/************************************************************************* +BLEIC results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one: + * -7 gradient verification failed. + See MinBLEICSetGradientCheck() for more information. + * -3 inconsistent constraints. Feasible point is + either nonexistent or too hard to find. Try to + restart optimizer with better initial approximation + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + More information about fields of this structure can be + found in the comments on MinBLEICReport datatype. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresults(minbleicstate* state, + /* Real */ ae_vector* x, + minbleicreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _minbleicreport_clear(rep); + + minbleicresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +BLEIC results + +Buffered implementation of MinBLEICResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresultsbuf(minbleicstate* state, + /* Real */ ae_vector* x, + minbleicreport* rep, + ae_state *_state) +{ + ae_int_t i; + + + if( x->cntnmain ) + { + ae_vector_set_length(x, state->nmain, _state); + } + rep->iterationscount = state->repinneriterationscount; + rep->inneriterationscount = state->repinneriterationscount; + rep->outeriterationscount = state->repouteriterationscount; + rep->nfev = state->repnfev; + rep->varidx = state->repvaridx; + rep->terminationtype = state->repterminationtype; + if( state->repterminationtype>0 ) + { + ae_v_move(&x->ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,state->nmain-1)); + } + else + { + for(i=0; i<=state->nmain-1; i++) + { + x->ptr.p_double[i] = _state->v_nan; + } + } + rep->debugeqerr = state->repdebugeqerr; + rep->debugfs = state->repdebugfs; + rep->debugff = state->repdebugff; + rep->debugdx = state->repdebugdx; + rep->debugfeasqpits = state->repdebugfeasqpits; + rep->debugfeasgpaits = state->repdebugfeasgpaits; +} + + +/************************************************************************* +This subroutine restarts algorithm from new point. +All optimization parameters (including constraints) are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + X - new starting point. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicrestartfrom(minbleicstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + + + n = state->nmain; + + /* + * First, check for errors in the inputs + */ + ae_assert(x->cnt>=n, "MinBLEICRestartFrom: Length(X)xstart.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * prepare RComm facilities + */ + ae_vector_set_length(&state->rstate.ia, 8+1, _state); + ae_vector_set_length(&state->rstate.ba, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 4+1, _state); + state->rstate.stage = -1; + minbleic_clearrequestfields(state, _state); + sasstopoptimization(&state->sas, _state); +} + + +/************************************************************************* +This subroutine finalizes internal structures after emergency termination +from State.LSStart report (see comments on MinBLEICState for more information). + +INPUT PARAMETERS: + State - structure after exit from LSStart report + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicemergencytermination(minbleicstate* state, ae_state *_state) +{ + + + sasstopoptimization(&state->sas, _state); +} + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinBLEICOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinBLEICSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetgradientcheck(minbleicstate* state, + double teststep, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(teststep, _state), "MinBLEICSetGradientCheck: TestStep contains NaN or Infinite", _state); + ae_assert(ae_fp_greater_eq(teststep,0), "MinBLEICSetGradientCheck: invalid argument TestStep(TestStep<0)", _state); + state->teststep = teststep; +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forget to clear something) +*************************************************************************/ +static void minbleic_clearrequestfields(minbleicstate* state, + ae_state *_state) +{ + + + state->needf = ae_false; + state->needfg = ae_false; + state->xupdated = ae_false; + state->lsstart = ae_false; +} + + +/************************************************************************* +Internal initialization subroutine +*************************************************************************/ +static void minbleic_minbleicinitinternal(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + minbleicstate* state, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_matrix c; + ae_vector ct; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init(&c, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ct, 0, DT_INT, _state, ae_true); + + + /* + * Initialize + */ + state->teststep = 0; + state->nmain = n; + state->diffstep = diffstep; + sasinit(n, &state->sas, _state); + ae_vector_set_length(&state->bndl, n, _state); + ae_vector_set_length(&state->hasbndl, n, _state); + ae_vector_set_length(&state->bndu, n, _state); + ae_vector_set_length(&state->hasbndu, n, _state); + ae_vector_set_length(&state->xstart, n, _state); + ae_vector_set_length(&state->gc, n, _state); + ae_vector_set_length(&state->xn, n, _state); + ae_vector_set_length(&state->gn, n, _state); + ae_vector_set_length(&state->xp, n, _state); + ae_vector_set_length(&state->gp, n, _state); + ae_vector_set_length(&state->d, n, _state); + ae_vector_set_length(&state->s, n, _state); + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->g, n, _state); + ae_vector_set_length(&state->work, n, _state); + for(i=0; i<=n-1; i++) + { + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->hasbndl.ptr.p_bool[i] = ae_false; + state->bndu.ptr.p_double[i] = _state->v_posinf; + state->hasbndu.ptr.p_bool[i] = ae_false; + state->s.ptr.p_double[i] = 1.0; + } + minbleicsetlc(state, &c, &ct, 0, _state); + minbleicsetcond(state, 0.0, 0.0, 0.0, 0, _state); + minbleicsetxrep(state, ae_false, _state); + minbleicsetdrep(state, ae_false, _state); + minbleicsetstpmax(state, 0.0, _state); + minbleicsetprecdefault(state, _state); + minbleicrestartfrom(state, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +This subroutine updates estimate of the good step length given: +1) previous estimate +2) new length of the good step + +It makes sure that estimate does not change too rapidly - ratio of new and +old estimates will be at least 0.01, at most 100.0 + +In case previous estimate of good step is zero (no estimate), new estimate +is used unconditionally. + + -- ALGLIB -- + Copyright 16.01.2013 by Bochkanov Sergey +*************************************************************************/ +static void minbleic_updateestimateofgoodstep(double* estimate, + double newstep, + ae_state *_state) +{ + + + if( ae_fp_eq(*estimate,0) ) + { + *estimate = newstep; + return; + } + if( ae_fp_less(newstep,*estimate*0.01) ) + { + *estimate = *estimate*0.01; + return; + } + if( ae_fp_greater(newstep,*estimate*100) ) + { + *estimate = *estimate*100; + return; + } + *estimate = newstep; +} + + +ae_bool _minbleicstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minbleicstate *p = (minbleicstate*)_p; + ae_touch_ptr((void*)p); + if( !_sactiveset_init(&p->sas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->diagh, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gp, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->cleic, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hasbndl, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->hasbndu, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xstart, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_snnlssolver_init(&p->solver, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpprec, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->work, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_linminstate_init(&p->lstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rho, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->yk, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->sk, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->theta, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _minbleicstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minbleicstate *dst = (minbleicstate*)_dst; + minbleicstate *src = (minbleicstate*)_src; + dst->nmain = src->nmain; + dst->nslack = src->nslack; + dst->epsg = src->epsg; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->drep = src->drep; + dst->stpmax = src->stpmax; + dst->diffstep = src->diffstep; + if( !_sactiveset_init_copy(&dst->sas, &src->sas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + dst->prectype = src->prectype; + if( !ae_vector_init_copy(&dst->diagh, &src->diagh, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + dst->needf = src->needf; + dst->needfg = src->needfg; + dst->xupdated = src->xupdated; + dst->lsstart = src->lsstart; + dst->lbfgssearch = src->lbfgssearch; + dst->boundedstep = src->boundedstep; + dst->teststep = src->teststep; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gc, &src->gc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xn, &src->xn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gn, &src->gn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xp, &src->xp, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gp, &src->gp, _state, make_automatic) ) + return ae_false; + dst->fc = src->fc; + dst->fn = src->fn; + dst->fp = src->fp; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->cleic, &src->cleic, _state, make_automatic) ) + return ae_false; + dst->nec = src->nec; + dst->nic = src->nic; + dst->lastgoodstep = src->lastgoodstep; + dst->lastscaledgoodstep = src->lastscaledgoodstep; + dst->maxscaledgrad = src->maxscaledgrad; + if( !ae_vector_init_copy(&dst->hasbndl, &src->hasbndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->hasbndu, &src->hasbndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + dst->repinneriterationscount = src->repinneriterationscount; + dst->repouteriterationscount = src->repouteriterationscount; + dst->repnfev = src->repnfev; + dst->repvaridx = src->repvaridx; + dst->repterminationtype = src->repterminationtype; + dst->repdebugeqerr = src->repdebugeqerr; + dst->repdebugfs = src->repdebugfs; + dst->repdebugff = src->repdebugff; + dst->repdebugdx = src->repdebugdx; + dst->repdebugfeasqpits = src->repdebugfeasqpits; + dst->repdebugfeasgpaits = src->repdebugfeasgpaits; + if( !ae_vector_init_copy(&dst->xstart, &src->xstart, _state, make_automatic) ) + return ae_false; + if( !_snnlssolver_init_copy(&dst->solver, &src->solver, _state, make_automatic) ) + return ae_false; + dst->fbase = src->fbase; + dst->fm2 = src->fm2; + dst->fm1 = src->fm1; + dst->fp1 = src->fp1; + dst->fp2 = src->fp2; + dst->xm1 = src->xm1; + dst->xp1 = src->xp1; + dst->gm1 = src->gm1; + dst->gp1 = src->gp1; + dst->cidx = src->cidx; + dst->cval = src->cval; + if( !ae_vector_init_copy(&dst->tmpprec, &src->tmpprec, _state, make_automatic) ) + return ae_false; + dst->nfev = src->nfev; + dst->mcstage = src->mcstage; + dst->stp = src->stp; + dst->curstpmax = src->curstpmax; + dst->activationstep = src->activationstep; + if( !ae_vector_init_copy(&dst->work, &src->work, _state, make_automatic) ) + return ae_false; + if( !_linminstate_init_copy(&dst->lstate, &src->lstate, _state, make_automatic) ) + return ae_false; + dst->trimthreshold = src->trimthreshold; + dst->nonmonotoniccnt = src->nonmonotoniccnt; + dst->k = src->k; + dst->q = src->q; + dst->p = src->p; + if( !ae_vector_init_copy(&dst->rho, &src->rho, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->yk, &src->yk, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->sk, &src->sk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->theta, &src->theta, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _minbleicstate_clear(void* _p) +{ + minbleicstate *p = (minbleicstate*)_p; + ae_touch_ptr((void*)p); + _sactiveset_clear(&p->sas); + ae_vector_clear(&p->s); + ae_vector_clear(&p->diagh); + ae_vector_clear(&p->x); + ae_vector_clear(&p->g); + _rcommstate_clear(&p->rstate); + ae_vector_clear(&p->gc); + ae_vector_clear(&p->xn); + ae_vector_clear(&p->gn); + ae_vector_clear(&p->xp); + ae_vector_clear(&p->gp); + ae_vector_clear(&p->d); + ae_matrix_clear(&p->cleic); + ae_vector_clear(&p->hasbndl); + ae_vector_clear(&p->hasbndu); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_vector_clear(&p->xstart); + _snnlssolver_clear(&p->solver); + ae_vector_clear(&p->tmpprec); + ae_vector_clear(&p->work); + _linminstate_clear(&p->lstate); + ae_vector_clear(&p->rho); + ae_matrix_clear(&p->yk); + ae_matrix_clear(&p->sk); + ae_vector_clear(&p->theta); +} + + +void _minbleicstate_destroy(void* _p) +{ + minbleicstate *p = (minbleicstate*)_p; + ae_touch_ptr((void*)p); + _sactiveset_destroy(&p->sas); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->diagh); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->g); + _rcommstate_destroy(&p->rstate); + ae_vector_destroy(&p->gc); + ae_vector_destroy(&p->xn); + ae_vector_destroy(&p->gn); + ae_vector_destroy(&p->xp); + ae_vector_destroy(&p->gp); + ae_vector_destroy(&p->d); + ae_matrix_destroy(&p->cleic); + ae_vector_destroy(&p->hasbndl); + ae_vector_destroy(&p->hasbndu); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_vector_destroy(&p->xstart); + _snnlssolver_destroy(&p->solver); + ae_vector_destroy(&p->tmpprec); + ae_vector_destroy(&p->work); + _linminstate_destroy(&p->lstate); + ae_vector_destroy(&p->rho); + ae_matrix_destroy(&p->yk); + ae_matrix_destroy(&p->sk); + ae_vector_destroy(&p->theta); +} + + +ae_bool _minbleicreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minbleicreport *p = (minbleicreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _minbleicreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minbleicreport *dst = (minbleicreport*)_dst; + minbleicreport *src = (minbleicreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nfev = src->nfev; + dst->varidx = src->varidx; + dst->terminationtype = src->terminationtype; + dst->debugeqerr = src->debugeqerr; + dst->debugfs = src->debugfs; + dst->debugff = src->debugff; + dst->debugdx = src->debugdx; + dst->debugfeasqpits = src->debugfeasqpits; + dst->debugfeasgpaits = src->debugfeasgpaits; + dst->inneriterationscount = src->inneriterationscount; + dst->outeriterationscount = src->outeriterationscount; + return ae_true; +} + + +void _minbleicreport_clear(void* _p) +{ + minbleicreport *p = (minbleicreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _minbleicreport_destroy(void* _p) +{ + minbleicreport *p = (minbleicreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* + LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using a quasi- +Newton method (LBFGS scheme) which is optimized to use a minimum amount +of memory. +The subroutine generates the approximation of an inverse Hessian matrix by +using information about the last M steps of the algorithm (instead of N). +It lessens a required amount of memory from a value of order N^2 to a +value of order 2*N*M. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinLBFGSCreate() call +2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax() + and other functions +3. User calls MinLBFGSOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinLBFGSResults() to get solution +5. Optionally user may call MinLBFGSRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLBFGSRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension. N>0 + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - initial solution approximation, array[0..N-1]. + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with MinLBFGSSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLBFGSSetStpMax() function to bound algorithm's steps. However, + L-BFGS rarely needs such a tuning. + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreate(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlbfgsstate* state, + ae_state *_state) +{ + + _minlbfgsstate_clear(state); + + ae_assert(n>=1, "MinLBFGSCreate: N<1!", _state); + ae_assert(m>=1, "MinLBFGSCreate: M<1", _state); + ae_assert(m<=n, "MinLBFGSCreate: M>N", _state); + ae_assert(x->cnt>=n, "MinLBFGSCreate: Length(X)0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinLBFGSSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. LBFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreatef(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + double diffstep, + minlbfgsstate* state, + ae_state *_state) +{ + + _minlbfgsstate_clear(state); + + ae_assert(n>=1, "MinLBFGSCreateF: N too small!", _state); + ae_assert(m>=1, "MinLBFGSCreateF: M<1", _state); + ae_assert(m<=n, "MinLBFGSCreateF: M>N", _state); + ae_assert(x->cnt>=n, "MinLBFGSCreateF: Length(X)=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLBFGSSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcond(minlbfgsstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinLBFGSSetCond: EpsG is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinLBFGSSetCond: negative EpsG!", _state); + ae_assert(ae_isfinite(epsf, _state), "MinLBFGSSetCond: EpsF is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinLBFGSSetCond: negative EpsF!", _state); + ae_assert(ae_isfinite(epsx, _state), "MinLBFGSSetCond: EpsX is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinLBFGSSetCond: negative EpsX!", _state); + ae_assert(maxits>=0, "MinLBFGSSetCond: negative MaxIts!", _state); + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->epsg = epsg; + state->epsf = epsf; + state->epsx = epsx; + state->maxits = maxits; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLBFGSOptimize(). + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetxrep(minlbfgsstate* state, + ae_bool needxrep, + ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +This function sets maximum step length + +INPUT PARAMETERS: + State - structure which stores algorithm state + StpMax - maximum step length, >=0. Set StpMax to 0.0 (default), 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetstpmax(minlbfgsstate* state, + double stpmax, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "MinLBFGSSetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "MinLBFGSSetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* +This function sets scaling coefficients for LBFGS 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the LBFGS too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinLBFGSSetPrec...() +functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetscale(minlbfgsstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(s->cnt>=state->n, "MinLBFGSSetScale: Length(S)n-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "MinLBFGSSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "MinLBFGSSetScale: S contains zero elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +Extended subroutine for internal use only. + +Accepts additional parameters: + + Flags - additional settings: + * Flags = 0 means no additional settings + * Flags = 1 "do not allocate memory". used when solving + a many subsequent tasks with same N/M values. + First call MUST be without this flag bit set, + subsequent calls of MinLBFGS with same + MinLBFGSState structure can set Flags to 1. + DiffStep - numerical differentiation step + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreatex(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + ae_int_t flags, + double diffstep, + minlbfgsstate* state, + ae_state *_state) +{ + ae_bool allocatemem; + ae_int_t i; + + + ae_assert(n>=1, "MinLBFGS: N too small!", _state); + ae_assert(m>=1, "MinLBFGS: M too small!", _state); + ae_assert(m<=n, "MinLBFGS: M too large!", _state); + + /* + * Initialize + */ + state->teststep = 0; + state->diffstep = diffstep; + state->n = n; + state->m = m; + allocatemem = flags%2==0; + flags = flags/2; + if( allocatemem ) + { + ae_vector_set_length(&state->rho, m, _state); + ae_vector_set_length(&state->theta, m, _state); + ae_matrix_set_length(&state->yk, m, n, _state); + ae_matrix_set_length(&state->sk, m, n, _state); + ae_vector_set_length(&state->d, n, _state); + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->s, n, _state); + ae_vector_set_length(&state->g, n, _state); + ae_vector_set_length(&state->work, n, _state); + } + minlbfgssetcond(state, 0, 0, 0, 0, _state); + minlbfgssetxrep(state, ae_false, _state); + minlbfgssetstpmax(state, 0, _state); + minlbfgsrestartfrom(state, x, _state); + for(i=0; i<=n-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + } + state->prectype = 0; +} + + +/************************************************************************* +Modification of the preconditioner: default preconditioner (simple +scaling, same for all elements of X) is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdefault(minlbfgsstate* state, ae_state *_state) +{ + + + state->prectype = 0; +} + + +/************************************************************************* +Modification of the preconditioner: Cholesky factorization of approximate +Hessian is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + P - triangular preconditioner, Cholesky factorization of + the approximate Hessian. array[0..N-1,0..N-1], + (if larger, only leading N elements are used). + IsUpper - whether upper or lower triangle of P is given + (other triangle is not referenced) + +After call to this function preconditioner is changed to P (P is copied +into the internal buffer). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: P should be nonsingular. Exception will be thrown otherwise. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetpreccholesky(minlbfgsstate* state, + /* Real */ ae_matrix* p, + ae_bool isupper, + ae_state *_state) +{ + ae_int_t i; + double mx; + + + ae_assert(isfinitertrmatrix(p, state->n, isupper, _state), "MinLBFGSSetPrecCholesky: P contains infinite or NAN values!", _state); + mx = 0; + for(i=0; i<=state->n-1; i++) + { + mx = ae_maxreal(mx, ae_fabs(p->ptr.pp_double[i][i], _state), _state); + } + ae_assert(ae_fp_greater(mx,0), "MinLBFGSSetPrecCholesky: P is strictly singular!", _state); + if( state->denseh.rowsn||state->denseh.colsn ) + { + ae_matrix_set_length(&state->denseh, state->n, state->n, _state); + } + state->prectype = 1; + if( isupper ) + { + rmatrixcopy(state->n, state->n, p, 0, 0, &state->denseh, 0, 0, _state); + } + else + { + rmatrixtranspose(state->n, state->n, p, 0, 0, &state->denseh, 0, 0, _state); + } +} + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdiag(minlbfgsstate* state, + /* Real */ ae_vector* d, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(d->cnt>=state->n, "MinLBFGSSetPrecDiag: D is too short", _state); + for(i=0; i<=state->n-1; i++) + { + ae_assert(ae_isfinite(d->ptr.p_double[i], _state), "MinLBFGSSetPrecDiag: D contains infinite or NAN elements", _state); + ae_assert(ae_fp_greater(d->ptr.p_double[i],0), "MinLBFGSSetPrecDiag: D contains non-positive elements", _state); + } + rvectorsetlengthatleast(&state->diagh, state->n, _state); + state->prectype = 2; + for(i=0; i<=state->n-1; i++) + { + state->diagh.ptr.p_double[i] = d->ptr.p_double[i]; + } +} + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinLBFGSSetScale() +call (before or after MinLBFGSSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecscale(minlbfgsstate* state, ae_state *_state) +{ + + + state->prectype = 3; +} + + +/************************************************************************* +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinLBFGSCreate() for analytical gradient or MinLBFGSCreateF() + for numerical differentiation) you should choose appropriate variant of + MinLBFGSOptimize() - one which accepts function AND gradient or one + which accepts function ONLY. + + Be careful to choose variant of MinLBFGSOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinLBFGSOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinLBFGSOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinLBFGSCreateF() | work FAIL + MinLBFGSCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinLBFGSOptimize() version. Attemps to use such + combination (for example, to create optimizer with MinLBFGSCreateF() and + to pass gradient information to MinCGOptimize()) will lead to exception + being thrown. Either you did not pass gradient when it WAS needed or + you passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool minlbfgsiteration(minlbfgsstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t m; + ae_int_t i; + ae_int_t j; + ae_int_t ic; + ae_int_t mcinfo; + double v; + double vv; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + i = state->rstate.ia.ptr.p_int[2]; + j = state->rstate.ia.ptr.p_int[3]; + ic = state->rstate.ia.ptr.p_int[4]; + mcinfo = state->rstate.ia.ptr.p_int[5]; + v = state->rstate.ra.ptr.p_double[0]; + vv = state->rstate.ra.ptr.p_double[1]; + } + else + { + n = -983; + m = -989; + i = -834; + j = 900; + ic = -287; + mcinfo = 364; + v = 214; + vv = -338; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + if( state->rstate.stage==14 ) + { + goto lbl_14; + } + if( state->rstate.stage==15 ) + { + goto lbl_15; + } + if( state->rstate.stage==16 ) + { + goto lbl_16; + } + + /* + * Routine body + */ + + /* + * Unload frequently used variables from State structure + * (just for typing convinience) + */ + n = state->n; + m = state->m; + state->repterminationtype = 0; + state->repiterationscount = 0; + state->repvaridx = -1; + state->repnfev = 0; + + /* + * Check, that transferred derivative value is right + */ + minlbfgs_clearrequestfields(state, _state); + if( !(ae_fp_eq(state->diffstep,0)&&ae_fp_greater(state->teststep,0)) ) + { + goto lbl_17; + } + state->needfg = ae_true; + i = 0; +lbl_19: + if( i>n-1 ) + { + goto lbl_21; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->teststep*state->s.ptr.p_double[i]; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->fm1 = state->f; + state->fp1 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = v+state->teststep*state->s.ptr.p_double[i]; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->fm2 = state->f; + state->fp2 = state->g.ptr.p_double[i]; + state->x.ptr.p_double[i] = v; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + + /* + * 2*State.TestStep - scale parameter + * width of segment [Xi-TestStep;Xi+TestStep] + */ + if( !derivativecheck(state->fm1, state->fp1, state->fm2, state->fp2, state->f, state->g.ptr.p_double[i], 2*state->teststep, _state) ) + { + state->repvaridx = i; + state->repterminationtype = -7; + result = ae_false; + return result; + } + i = i+1; + goto lbl_19; +lbl_21: + state->needfg = ae_false; +lbl_17: + + /* + * Calculate F/G at the initial point + */ + minlbfgs_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_22; + } + state->needfg = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needfg = ae_false; + goto lbl_23; +lbl_22: + state->needf = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->fbase = state->f; + i = 0; +lbl_24: + if( i>n-1 ) + { + goto lbl_26; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->fp2 = state->f; + state->x.ptr.p_double[i] = v; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + i = i+1; + goto lbl_24; +lbl_26: + state->f = state->fbase; + state->needf = ae_false; +lbl_23: + trimprepare(state->f, &state->trimthreshold, _state); + if( !state->xrep ) + { + goto lbl_27; + } + minlbfgs_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->xupdated = ae_false; +lbl_27: + state->repnfev = 1; + state->fold = state->f; + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->g.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->epsg) ) + { + state->repterminationtype = 4; + result = ae_false; + return result; + } + + /* + * Choose initial step and direction. + * Apply preconditioner, if we have something other than default. + */ + ae_v_moveneg(&state->d.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( state->prectype==0 ) + { + + /* + * Default preconditioner is used, but we can't use it before iterations will start + */ + v = ae_v_dotproduct(&state->g.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = ae_sqrt(v, _state); + if( ae_fp_eq(state->stpmax,0) ) + { + state->stp = ae_minreal(1.0/v, 1, _state); + } + else + { + state->stp = ae_minreal(1.0/v, state->stpmax, _state); + } + } + if( state->prectype==1 ) + { + + /* + * Cholesky preconditioner is used + */ + fblscholeskysolve(&state->denseh, 1.0, n, ae_true, &state->d, &state->autobuf, _state); + state->stp = 1; + } + if( state->prectype==2 ) + { + + /* + * diagonal approximation is used + */ + for(i=0; i<=n-1; i++) + { + state->d.ptr.p_double[i] = state->d.ptr.p_double[i]/state->diagh.ptr.p_double[i]; + } + state->stp = 1; + } + if( state->prectype==3 ) + { + + /* + * scale-based preconditioner is used + */ + for(i=0; i<=n-1; i++) + { + state->d.ptr.p_double[i] = state->d.ptr.p_double[i]*state->s.ptr.p_double[i]*state->s.ptr.p_double[i]; + } + state->stp = 1; + } + + /* + * Main cycle + */ + state->k = 0; +lbl_29: + if( ae_false ) + { + goto lbl_30; + } + + /* + * Main cycle: prepare to 1-D line search + */ + state->p = state->k%m; + state->q = ae_minint(state->k, m-1, _state); + + /* + * Store X[k], G[k] + */ + ae_v_moveneg(&state->sk.ptr.pp_double[state->p][0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_moveneg(&state->yk.ptr.pp_double[state->p][0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Minimize F(x+alpha*d) + * Calculate S[k], Y[k] + */ + state->mcstage = 0; + if( state->k!=0 ) + { + state->stp = 1.0; + } + linminnormalized(&state->d, &state->stp, n, _state); + mcsrch(n, &state->x, &state->f, &state->g, &state->d, &state->stp, state->stpmax, minlbfgs_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); +lbl_31: + if( state->mcstage==0 ) + { + goto lbl_32; + } + minlbfgs_clearrequestfields(state, _state); + if( ae_fp_neq(state->diffstep,0) ) + { + goto lbl_33; + } + state->needfg = ae_true; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->needfg = ae_false; + goto lbl_34; +lbl_33: + state->needf = ae_true; + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + state->fbase = state->f; + i = 0; +lbl_35: + if( i>n-1 ) + { + goto lbl_37; + } + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + state->fm2 = state->f; + state->x.ptr.p_double[i] = v-0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->fm1 = state->f; + state->x.ptr.p_double[i] = v+0.5*state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 14; + goto lbl_rcomm; +lbl_14: + state->fp1 = state->f; + state->x.ptr.p_double[i] = v+state->diffstep*state->s.ptr.p_double[i]; + state->rstate.stage = 15; + goto lbl_rcomm; +lbl_15: + state->fp2 = state->f; + state->x.ptr.p_double[i] = v; + state->g.ptr.p_double[i] = (8*(state->fp1-state->fm1)-(state->fp2-state->fm2))/(6*state->diffstep*state->s.ptr.p_double[i]); + i = i+1; + goto lbl_35; +lbl_37: + state->f = state->fbase; + state->needf = ae_false; +lbl_34: + trimfunction(&state->f, &state->g, n, state->trimthreshold, _state); + mcsrch(n, &state->x, &state->f, &state->g, &state->d, &state->stp, state->stpmax, minlbfgs_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); + goto lbl_31; +lbl_32: + if( !state->xrep ) + { + goto lbl_38; + } + + /* + * report + */ + minlbfgs_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 16; + goto lbl_rcomm; +lbl_16: + state->xupdated = ae_false; +lbl_38: + state->repnfev = state->repnfev+state->nfev; + state->repiterationscount = state->repiterationscount+1; + ae_v_add(&state->sk.ptr.pp_double[state->p][0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->yk.ptr.pp_double[state->p][0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Stopping conditions + */ + if( state->repiterationscount>=state->maxits&&state->maxits>0 ) + { + + /* + * Too many iterations + */ + state->repterminationtype = 5; + result = ae_false; + return result; + } + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->g.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->epsg) ) + { + + /* + * Gradient is small enough + */ + state->repterminationtype = 4; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->fold-state->f,state->epsf*ae_maxreal(ae_fabs(state->fold, _state), ae_maxreal(ae_fabs(state->f, _state), 1.0, _state), _state)) ) + { + + /* + * F(k+1)-F(k) is small enough + */ + state->repterminationtype = 1; + result = ae_false; + return result; + } + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->sk.ptr.pp_double[state->p][i]/state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->epsx) ) + { + + /* + * X(k+1)-X(k) is small enough + */ + state->repterminationtype = 2; + result = ae_false; + return result; + } + + /* + * If Wolfe conditions are satisfied, we can update + * limited memory model. + * + * However, if conditions are not satisfied (NFEV limit is met, + * function is too wild, ...), we'll skip L-BFGS update + */ + if( mcinfo!=1 ) + { + + /* + * Skip update. + * + * In such cases we'll initialize search direction by + * antigradient vector, because it leads to more + * transparent code with less number of special cases + */ + state->fold = state->f; + ae_v_moveneg(&state->d.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + } + else + { + + /* + * Calculate Rho[k], GammaK + */ + v = ae_v_dotproduct(&state->yk.ptr.pp_double[state->p][0], 1, &state->sk.ptr.pp_double[state->p][0], 1, ae_v_len(0,n-1)); + vv = ae_v_dotproduct(&state->yk.ptr.pp_double[state->p][0], 1, &state->yk.ptr.pp_double[state->p][0], 1, ae_v_len(0,n-1)); + if( ae_fp_eq(v,0)||ae_fp_eq(vv,0) ) + { + + /* + * Rounding errors make further iterations impossible. + */ + state->repterminationtype = -2; + result = ae_false; + return result; + } + state->rho.ptr.p_double[state->p] = 1/v; + state->gammak = v/vv; + + /* + * Calculate d(k+1) = -H(k+1)*g(k+1) + * + * for I:=K downto K-Q do + * V = s(i)^T * work(iteration:I) + * theta(i) = V + * work(iteration:I+1) = work(iteration:I) - V*Rho(i)*y(i) + * work(last iteration) = H0*work(last iteration) - preconditioner + * for I:=K-Q to K do + * V = y(i)^T*work(iteration:I) + * work(iteration:I+1) = work(iteration:I) +(-V+theta(i))*Rho(i)*s(i) + * + * NOW WORK CONTAINS d(k+1) + */ + ae_v_move(&state->work.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=state->k; i>=state->k-state->q; i--) + { + ic = i%m; + v = ae_v_dotproduct(&state->sk.ptr.pp_double[ic][0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->theta.ptr.p_double[ic] = v; + vv = v*state->rho.ptr.p_double[ic]; + ae_v_subd(&state->work.ptr.p_double[0], 1, &state->yk.ptr.pp_double[ic][0], 1, ae_v_len(0,n-1), vv); + } + if( state->prectype==0 ) + { + + /* + * Simple preconditioner is used + */ + v = state->gammak; + ae_v_muld(&state->work.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + if( state->prectype==1 ) + { + + /* + * Cholesky preconditioner is used + */ + fblscholeskysolve(&state->denseh, 1, n, ae_true, &state->work, &state->autobuf, _state); + } + if( state->prectype==2 ) + { + + /* + * diagonal approximation is used + */ + for(i=0; i<=n-1; i++) + { + state->work.ptr.p_double[i] = state->work.ptr.p_double[i]/state->diagh.ptr.p_double[i]; + } + } + if( state->prectype==3 ) + { + + /* + * scale-based preconditioner is used + */ + for(i=0; i<=n-1; i++) + { + state->work.ptr.p_double[i] = state->work.ptr.p_double[i]*state->s.ptr.p_double[i]*state->s.ptr.p_double[i]; + } + } + for(i=state->k-state->q; i<=state->k; i++) + { + ic = i%m; + v = ae_v_dotproduct(&state->yk.ptr.pp_double[ic][0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + vv = state->rho.ptr.p_double[ic]*(-v+state->theta.ptr.p_double[ic]); + ae_v_addd(&state->work.ptr.p_double[0], 1, &state->sk.ptr.pp_double[ic][0], 1, ae_v_len(0,n-1), vv); + } + ae_v_moveneg(&state->d.ptr.p_double[0], 1, &state->work.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Next step + */ + state->fold = state->f; + state->k = state->k+1; + } + goto lbl_29; +lbl_30: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = i; + state->rstate.ia.ptr.p_int[3] = j; + state->rstate.ia.ptr.p_int[4] = ic; + state->rstate.ia.ptr.p_int[5] = mcinfo; + state->rstate.ra.ptr.p_double[0] = v; + state->rstate.ra.ptr.p_double[1] = vv; + return result; +} + + +/************************************************************************* +L-BFGS algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinLBFGSSetGradientCheck() for more information. + * -2 rounding errors prevent further improvement. + X contains best point found. + * -1 incorrect parameters were specified + * 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 + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresults(minlbfgsstate* state, + /* Real */ ae_vector* x, + minlbfgsreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _minlbfgsreport_clear(rep); + + minlbfgsresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +L-BFGS algorithm results + +Buffered implementation of MinLBFGSResults which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresultsbuf(minlbfgsstate* state, + /* Real */ ae_vector* x, + minlbfgsreport* rep, + ae_state *_state) +{ + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nfev = state->repnfev; + rep->varidx = state->repvaridx; + rep->terminationtype = state->repterminationtype; +} + + +/************************************************************************* +This subroutine restarts LBFGS algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsrestartfrom(minlbfgsstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + + + ae_assert(x->cnt>=state->n, "MinLBFGSRestartFrom: Length(X)n, _state), "MinLBFGSRestartFrom: X contains infinite or NaN values!", _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_vector_set_length(&state->rstate.ia, 5+1, _state); + ae_vector_set_length(&state->rstate.ra, 1+1, _state); + state->rstate.stage = -1; + minlbfgs_clearrequestfields(state, _state); +} + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLBFGSOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLBFGSSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 24.05.2012 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetgradientcheck(minlbfgsstate* state, + double teststep, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(teststep, _state), "MinLBFGSSetGradientCheck: TestStep contains NaN or Infinite", _state); + ae_assert(ae_fp_greater_eq(teststep,0), "MinLBFGSSetGradientCheck: invalid argument TestStep(TestStep<0)", _state); + state->teststep = teststep; +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void minlbfgs_clearrequestfields(minlbfgsstate* state, + ae_state *_state) +{ + + + state->needf = ae_false; + state->needfg = ae_false; + state->xupdated = ae_false; +} + + +ae_bool _minlbfgsstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minlbfgsstate *p = (minlbfgsstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rho, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->yk, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->sk, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->theta, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->work, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->denseh, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->diagh, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->autobuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !_linminstate_init(&p->lstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _minlbfgsstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minlbfgsstate *dst = (minlbfgsstate*)_dst; + minlbfgsstate *src = (minlbfgsstate*)_src; + dst->n = src->n; + dst->m = src->m; + dst->epsg = src->epsg; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->stpmax = src->stpmax; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + dst->diffstep = src->diffstep; + dst->nfev = src->nfev; + dst->mcstage = src->mcstage; + dst->k = src->k; + dst->q = src->q; + dst->p = src->p; + if( !ae_vector_init_copy(&dst->rho, &src->rho, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->yk, &src->yk, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->sk, &src->sk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->theta, &src->theta, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + dst->stp = src->stp; + if( !ae_vector_init_copy(&dst->work, &src->work, _state, make_automatic) ) + return ae_false; + dst->fold = src->fold; + dst->trimthreshold = src->trimthreshold; + dst->prectype = src->prectype; + dst->gammak = src->gammak; + if( !ae_matrix_init_copy(&dst->denseh, &src->denseh, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->diagh, &src->diagh, _state, make_automatic) ) + return ae_false; + dst->fbase = src->fbase; + dst->fm2 = src->fm2; + dst->fm1 = src->fm1; + dst->fp1 = src->fp1; + dst->fp2 = src->fp2; + if( !ae_vector_init_copy(&dst->autobuf, &src->autobuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + dst->needf = src->needf; + dst->needfg = src->needfg; + dst->xupdated = src->xupdated; + dst->teststep = src->teststep; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->repiterationscount = src->repiterationscount; + dst->repnfev = src->repnfev; + dst->repvaridx = src->repvaridx; + dst->repterminationtype = src->repterminationtype; + if( !_linminstate_init_copy(&dst->lstate, &src->lstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _minlbfgsstate_clear(void* _p) +{ + minlbfgsstate *p = (minlbfgsstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->s); + ae_vector_clear(&p->rho); + ae_matrix_clear(&p->yk); + ae_matrix_clear(&p->sk); + ae_vector_clear(&p->theta); + ae_vector_clear(&p->d); + ae_vector_clear(&p->work); + ae_matrix_clear(&p->denseh); + ae_vector_clear(&p->diagh); + ae_vector_clear(&p->autobuf); + ae_vector_clear(&p->x); + ae_vector_clear(&p->g); + _rcommstate_clear(&p->rstate); + _linminstate_clear(&p->lstate); +} + + +void _minlbfgsstate_destroy(void* _p) +{ + minlbfgsstate *p = (minlbfgsstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->rho); + ae_matrix_destroy(&p->yk); + ae_matrix_destroy(&p->sk); + ae_vector_destroy(&p->theta); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->work); + ae_matrix_destroy(&p->denseh); + ae_vector_destroy(&p->diagh); + ae_vector_destroy(&p->autobuf); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->g); + _rcommstate_destroy(&p->rstate); + _linminstate_destroy(&p->lstate); +} + + +ae_bool _minlbfgsreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minlbfgsreport *p = (minlbfgsreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _minlbfgsreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minlbfgsreport *dst = (minlbfgsreport*)_dst; + minlbfgsreport *src = (minlbfgsreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nfev = src->nfev; + dst->varidx = src->varidx; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _minlbfgsreport_clear(void* _p) +{ + minlbfgsreport *p = (minlbfgsreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _minlbfgsreport_destroy(void* _p) +{ + minlbfgsreport *p = (minlbfgsreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* + CONSTRAINED QUADRATIC PROGRAMMING + +The subroutine creates QP optimizer. After initial creation, it contains +default optimization problem with zero quadratic and linear terms and no +constraints. You should set quadratic/linear terms with calls to functions +provided by MinQP subpackage. + +INPUT PARAMETERS: + N - problem size + +OUTPUT PARAMETERS: + State - optimizer with zero quadratic/linear terms + and no constraints + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpcreate(ae_int_t n, minqpstate* state, ae_state *_state) +{ + ae_int_t i; + + _minqpstate_clear(state); + + ae_assert(n>=1, "MinQPCreate: N<1", _state); + + /* + * initialize QP solver + */ + state->n = n; + state->nec = 0; + state->nic = 0; + state->repterminationtype = 0; + state->anorm = 1; + state->akind = 0; + cqminit(n, &state->a, _state); + sasinit(n, &state->sas, _state); + ae_vector_set_length(&state->b, n, _state); + ae_vector_set_length(&state->bndl, n, _state); + ae_vector_set_length(&state->bndu, n, _state); + ae_vector_set_length(&state->workbndl, n, _state); + ae_vector_set_length(&state->workbndu, n, _state); + ae_vector_set_length(&state->havebndl, n, _state); + ae_vector_set_length(&state->havebndu, n, _state); + ae_vector_set_length(&state->s, n, _state); + ae_vector_set_length(&state->startx, n, _state); + ae_vector_set_length(&state->xorigin, n, _state); + ae_vector_set_length(&state->xs, n, _state); + ae_vector_set_length(&state->xn, n, _state); + ae_vector_set_length(&state->gc, n, _state); + ae_vector_set_length(&state->pg, n, _state); + for(i=0; i<=n-1; i++) + { + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->bndu.ptr.p_double[i] = _state->v_posinf; + state->havebndl.ptr.p_bool[i] = ae_false; + state->havebndu.ptr.p_bool[i] = ae_false; + state->b.ptr.p_double[i] = 0.0; + state->startx.ptr.p_double[i] = 0.0; + state->xorigin.ptr.p_double[i] = 0.0; + state->s.ptr.p_double[i] = 1.0; + } + state->havex = ae_false; + minqpsetalgocholesky(state, _state); + normestimatorcreate(n, n, 5, 5, &state->estimator, _state); + minbleiccreate(n, &state->startx, &state->solver, _state); +} + + +/************************************************************************* +This function sets linear term for QP solver. + +By default, linear term is zero. + +INPUT PARAMETERS: + State - structure which stores algorithm state + B - linear term, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlinearterm(minqpstate* state, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t n; + + + n = state->n; + ae_assert(b->cnt>=n, "MinQPSetLinearTerm: Length(B)n; + ae_assert(a->rows>=n, "MinQPSetQuadraticTerm: Rows(A)cols>=n, "MinQPSetQuadraticTerm: Cols(A)n; + ae_assert(sparsegetnrows(a, _state)>=n, "MinQPSetQuadraticTermSparse: Rows(A)=n, "MinQPSetQuadraticTermSparse: Cols(A)sparsea, _state); + state->sparseaupper = isupper; + state->akind = 1; +} + + +/************************************************************************* +This function sets starting point for QP solver. It is useful to have +good initial approximation to the solution, because it will increase +speed of convergence and identification of active constraints. + +INPUT PARAMETERS: + State - structure which stores algorithm state + X - starting point, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetstartingpoint(minqpstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + + + n = state->n; + ae_assert(x->cnt>=n, "MinQPSetStartingPoint: Length(B)n; + ae_assert(xorigin->cnt>=n, "MinQPSetOrigin: Length(B)cnt>=state->n, "MinQPSetScale: Length(S)n-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "MinQPSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "MinQPSetScale: S contains zero elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +This function tells solver to use Cholesky-based algorithm. This algorithm +is active by default. + +DESCRIPTION: + +Cholesky-based algorithm can be used only for problems which: +* have dense quadratic term, set by MinQPSetQuadraticTerm(), sparse or + structured problems are not supported. +* are strictly convex, i.e. quadratic term is symmetric positive definite, + indefinite or semidefinite problems are not supported by this algorithm. + +If anything of what listed above is violated, you may use BLEIC-based QP +algorithm which can be activated by MinQPSetAlgoBLEIC(). + +BENEFITS AND DRAWBACKS: + +This algorithm gives best precision amongst all QP solvers provided by +ALGLIB (Newton iterations have much higher precision than any other +optimization algorithm). This solver also gracefully handles problems with +very large amount of constraints. + +Performance of the algorithm is good because internally it uses Level 3 +Dense BLAS for its performance-critical parts. + + +From the other side, algorithm has O(N^3) complexity for unconstrained +problems and up to orders of magnitude slower on constrained problems +(these additional iterations are needed to identify active constraints). +So, its running time depends on number of constraints active at solution. + +Furthermore, this algorithm can not solve problems with sparse matrices or +problems with semidefinite/indefinite matrices of any kind (dense/sparse). + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgocholesky(minqpstate* state, ae_state *_state) +{ + + + state->algokind = 1; +} + + +/************************************************************************* +This function tells solver to use BLEIC-based algorithm and sets stopping +criteria for the algorithm. + +DESCRIPTION: + +BLEIC-based QP algorithm can be used for any kind of QP problems: +* problems with both dense and sparse quadratic terms +* problems with positive definite, semidefinite, indefinite terms + +BLEIC-based algorithm can solve even indefinite problems - as long as they +are bounded from below on the feasible set. Of course, global minimum is +found only for positive definite and semidefinite problems. As for +indefinite ones - only local minimum is found. + +BENEFITS AND DRAWBACKS: + +This algorithm can be used to solve both convex and indefinite QP problems +and it can utilize sparsity of the quadratic term (algorithm calculates +matrix-vector products, which can be performed efficiently in case of +sparse matrix). + +Algorithm has iteration cost, which (assuming fixed amount of non-boundary +linear constraints) linearly depends on problem size. Boundary constraints +does not significantly change iteration cost. + +Thus, it outperforms Cholesky-based QP algorithm (CQP) on high-dimensional +sparse problems with moderate amount of constraints. + + +From the other side, unlike CQP solver, this algorithm does NOT make use +of Level 3 Dense BLAS. Thus, its performance on dense problems is inferior +to that of CQP solver. + +Its precision is also inferior to that of CQP. CQP performs Newton steps +which are know to achieve very good precision. In many cases Newton step +leads us exactly to the solution. BLEIC-QP performs LBFGS steps, which are +good at detecting neighborhood of the solution, buy need many iterations +to find solution with 6 digits of precision. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + EpsX - >=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: + * |.| means Euclidian norm + * v - scaled step vector, v[i]=dx[i]/s[i] + * dx - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinQPSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection (presently it is small step +length, but it may change in the future versions of ALGLIB). + +IT IS VERY IMPORTANT THAT YOU CALL MinQPSetScale() WHEN YOU USE THIS ALGO! + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgobleic(minqpstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinQPSetAlgoBLEIC: EpsG is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinQPSetAlgoBLEIC: negative EpsG", _state); + ae_assert(ae_isfinite(epsf, _state), "MinQPSetAlgoBLEIC: EpsF is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinQPSetAlgoBLEIC: negative EpsF", _state); + ae_assert(ae_isfinite(epsx, _state), "MinQPSetAlgoBLEIC: EpsX is not finite number", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinQPSetAlgoBLEIC: negative EpsX", _state); + ae_assert(maxits>=0, "MinQPSetAlgoBLEIC: negative MaxIts!", _state); + state->algokind = 2; + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->bleicepsg = epsg; + state->bleicepsf = epsf; + state->bleicepsx = epsx; + state->bleicmaxits = maxits; +} + + +/************************************************************************* +This function sets boundary constraints for QP solver + +Boundary constraints are inactive by default (after initial creation). +After being set, they are preserved until explicitly turned off with +another SetBC() call. + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + 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[N]. + 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: 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]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetbc(minqpstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + + + n = state->n; + ae_assert(bndl->cnt>=n, "MinQPSetBC: Length(BndL)cnt>=n, "MinQPSetBC: Length(BndU)ptr.p_double[i], _state)||ae_isneginf(bndl->ptr.p_double[i], _state), "MinQPSetBC: BndL contains NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.p_double[i], _state)||ae_isposinf(bndu->ptr.p_double[i], _state), "MinQPSetBC: BndU contains NAN or -INF", _state); + state->bndl.ptr.p_double[i] = bndl->ptr.p_double[i]; + state->havebndl.ptr.p_bool[i] = ae_isfinite(bndl->ptr.p_double[i], _state); + state->bndu.ptr.p_double[i] = bndu->ptr.p_double[i]; + state->havebndu.ptr.p_bool[i] = ae_isfinite(bndu->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +This function sets linear constraints for QP optimizer. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - structure previously allocated with MinQPCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately - + there always exists some minor violation (about 10^-10...10^-13) + due to numerical errors. + + -- ALGLIB -- + Copyright 19.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlc(minqpstate* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + double v; + + + n = state->n; + + /* + * First, check for errors in the inputs + */ + ae_assert(k>=0, "MinQPSetLC: K<0", _state); + ae_assert(c->cols>=n+1||k==0, "MinQPSetLC: Cols(C)rows>=k, "MinQPSetLC: Rows(C)cnt>=k, "MinQPSetLC: Length(CT)nec = 0; + state->nic = 0; + return; + } + + /* + * Equality constraints are stored first, in the upper + * NEC rows of State.CLEIC matrix. Inequality constraints + * are stored in the next NIC rows. + * + * NOTE: we convert inequality constraints to the form + * A*x<=b before copying them. + */ + rmatrixsetlengthatleast(&state->cleic, k, n+1, _state); + state->nec = 0; + state->nic = 0; + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]==0 ) + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + state->nec = state->nec+1; + } + } + for(i=0; i<=k-1; i++) + { + if( ct->ptr.p_int[i]!=0 ) + { + if( ct->ptr.p_int[i]>0 ) + { + ae_v_moveneg(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + else + { + ae_v_move(&state->cleic.ptr.pp_double[state->nec+state->nic][0], 1, &c->ptr.pp_double[i][0], 1, ae_v_len(0,n)); + } + state->nic = state->nic+1; + } + } + + /* + * Normalize rows of State.CLEIC: each row must have unit norm. + * Norm is calculated using first N elements (i.e. right part is + * not counted when we calculate norm). + */ + for(i=0; i<=k-1; i++) + { + v = 0; + for(j=0; j<=n-1; j++) + { + v = v+ae_sqr(state->cleic.ptr.pp_double[i][j], _state); + } + if( ae_fp_eq(v,0) ) + { + continue; + } + v = 1/ae_sqrt(v, _state); + ae_v_muld(&state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n), v); + } +} + + +/************************************************************************* +This function solves quadratic programming problem. +You should call it after setting solver options with MinQPSet...() calls. + +INPUT PARAMETERS: + State - algorithm state + +You should use MinQPResults() function to access results after calls +to this function. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey. + Special thanks to Elvira Illarionova for important suggestions on + the linearly constrained QP algorithm. +*************************************************************************/ +void minqpoptimize(minqpstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t nbc; + double v0; + double v1; + double v; + double d2; + double d1; + double d0; + double noisetolerance; + double fprev; + double fcand; + double fcur; + ae_int_t nextaction; + ae_int_t actstatus; + double noiselevel; + ae_int_t badnewtonits; + double maxscaledgrad; + + + noisetolerance = 10; + n = state->n; + state->repterminationtype = -5; + state->repinneriterationscount = 0; + state->repouteriterationscount = 0; + state->repncholesky = 0; + state->repnmv = 0; + state->debugphase1flops = 0; + state->debugphase2flops = 0; + state->debugphase3flops = 0; + rvectorsetlengthatleast(&state->rctmpg, n, _state); + + /* + * check correctness of constraints + */ + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i]&&state->havebndu.ptr.p_bool[i] ) + { + if( ae_fp_greater(state->bndl.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->repterminationtype = -3; + return; + } + } + } + + /* + * count number of bound and linear constraints + */ + nbc = 0; + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i] ) + { + nbc = nbc+1; + } + if( state->havebndu.ptr.p_bool[i] ) + { + nbc = nbc+1; + } + } + + /* + * Initial point: + * * if we have starting point in StartX, we just have to bound it + * * if we do not have StartX, deduce initial point from boundary constraints + */ + if( state->havex ) + { + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->startx.ptr.p_double[i]; + if( state->havebndl.ptr.p_bool[i]&&ae_fp_less(state->xs.ptr.p_double[i],state->bndl.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + } + if( state->havebndu.ptr.p_bool[i]&&ae_fp_greater(state->xs.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->bndu.ptr.p_double[i]; + } + } + } + else + { + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i]&&state->havebndu.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = 0.5*(state->bndl.ptr.p_double[i]+state->bndu.ptr.p_double[i]); + continue; + } + if( state->havebndl.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + continue; + } + if( state->havebndu.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = state->bndu.ptr.p_double[i]; + continue; + } + state->xs.ptr.p_double[i] = 0; + } + } + + /* + * Cholesky solver. + */ + if( state->algokind==1 ) + { + + /* + * Check matrix type. + * Cholesky solver supports only dense matrices. + */ + if( state->akind!=0 ) + { + state->repterminationtype = -5; + return; + } + + /* + * Our formulation of quadratic problem includes origin point, + * i.e. we have F(x-x_origin) which is minimized subject to + * constraints on x, instead of having simply F(x). + * + * Here we make transition from non-zero origin to zero one. + * In order to make such transition we have to: + * 1. subtract x_origin from x_start + * 2. modify constraints + * 3. solve problem + * 4. add x_origin to solution + * + * There is alternate solution - to modify quadratic function + * by expansion of multipliers containing (x-x_origin), but + * we prefer to modify constraints, because it is a) more precise + * and b) easier to to. + * + * Parts (1)-(2) are done here. After this block is over, + * we have: + * * XS, which stores shifted XStart (if we don't have XStart, + * value of XS will be ignored later) + * * WorkBndL, WorkBndU, which store modified boundary constraints. + */ + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i] ) + { + state->workbndl.ptr.p_double[i] = state->bndl.ptr.p_double[i]-state->xorigin.ptr.p_double[i]; + } + else + { + state->workbndl.ptr.p_double[i] = _state->v_neginf; + } + if( state->havebndu.ptr.p_bool[i] ) + { + state->workbndu.ptr.p_double[i] = state->bndu.ptr.p_double[i]-state->xorigin.ptr.p_double[i]; + } + else + { + state->workbndu.ptr.p_double[i] = _state->v_posinf; + } + } + rmatrixsetlengthatleast(&state->workcleic, state->nec+state->nic, n+1, _state); + for(i=0; i<=state->nec+state->nic-1; i++) + { + v = ae_v_dotproduct(&state->cleic.ptr.pp_double[i][0], 1, &state->xorigin.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->workcleic.ptr.pp_double[i][0], 1, &state->cleic.ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + state->workcleic.ptr.pp_double[i][n] = state->cleic.ptr.pp_double[i][n]-v; + } + + /* + * Starting point XS + */ + if( state->havex ) + { + + /* + * We have starting point in StartX, so we just have to shift and bound it + */ + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->startx.ptr.p_double[i]-state->xorigin.ptr.p_double[i]; + if( state->havebndl.ptr.p_bool[i] ) + { + if( ae_fp_less(state->xs.ptr.p_double[i],state->workbndl.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->workbndl.ptr.p_double[i]; + } + } + if( state->havebndu.ptr.p_bool[i] ) + { + if( ae_fp_greater(state->xs.ptr.p_double[i],state->workbndu.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->workbndu.ptr.p_double[i]; + } + } + } + } + else + { + + /* + * We don't have starting point, so we deduce it from + * constraints (if they are present). + * + * NOTE: XS contains some meaningless values from previous block + * which are ignored by code below. + */ + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i]&&state->havebndu.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = 0.5*(state->workbndl.ptr.p_double[i]+state->workbndu.ptr.p_double[i]); + if( ae_fp_less(state->xs.ptr.p_double[i],state->workbndl.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->workbndl.ptr.p_double[i]; + } + if( ae_fp_greater(state->xs.ptr.p_double[i],state->workbndu.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->workbndu.ptr.p_double[i]; + } + continue; + } + if( state->havebndl.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = state->workbndl.ptr.p_double[i]; + continue; + } + if( state->havebndu.ptr.p_bool[i] ) + { + state->xs.ptr.p_double[i] = state->workbndu.ptr.p_double[i]; + continue; + } + state->xs.ptr.p_double[i] = 0; + } + } + + /* + * Handle special case - no constraints + */ + if( nbc==0&&state->nec+state->nic==0 ) + { + + /* + * "Simple" unconstrained Cholesky + */ + bvectorsetlengthatleast(&state->tmpb, n, _state); + for(i=0; i<=n-1; i++) + { + state->tmpb.ptr.p_bool[i] = ae_false; + } + state->repncholesky = state->repncholesky+1; + cqmsetb(&state->a, &state->b, _state); + cqmsetactiveset(&state->a, &state->xs, &state->tmpb, _state); + if( !cqmconstrainedoptimum(&state->a, &state->xn, _state) ) + { + state->repterminationtype = -5; + return; + } + ae_v_move(&state->xs.ptr.p_double[0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->xs.ptr.p_double[0], 1, &state->xorigin.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repinneriterationscount = 1; + state->repouteriterationscount = 1; + state->repterminationtype = 4; + return; + } + + /* + * Prepare "active set" structure + */ + sassetbc(&state->sas, &state->workbndl, &state->workbndu, _state); + sassetlcx(&state->sas, &state->workcleic, state->nec, state->nic, _state); + sassetscale(&state->sas, &state->s, _state); + if( !sasstartoptimization(&state->sas, &state->xs, _state) ) + { + state->repterminationtype = -3; + return; + } + + /* + * Main cycle of CQP algorithm + */ + state->repterminationtype = 4; + badnewtonits = 0; + maxscaledgrad = 0.0; + for(;;) + { + + /* + * Update iterations count + */ + inc(&state->repouteriterationscount, _state); + inc(&state->repinneriterationscount, _state); + + /* + * Phase 1. + * + * Determine active set. + * Update MaxScaledGrad. + */ + cqmadx(&state->a, &state->sas.xc, &state->rctmpg, _state); + ae_v_add(&state->rctmpg.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasreactivateconstraints(&state->sas, &state->rctmpg, _state); + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->rctmpg.ptr.p_double[i]*state->s.ptr.p_double[i], _state); + } + maxscaledgrad = ae_maxreal(maxscaledgrad, ae_sqrt(v, _state), _state); + + /* + * Phase 2: perform penalized steepest descent step. + * + * NextAction control variable is set on exit from this loop: + * * NextAction>0 in case we have to proceed to Phase 3 (Newton step) + * * NextAction<0 in case we have to proceed to Phase 1 (recalculate active set) + * * NextAction=0 in case we found solution (step along projected gradient is small enough) + */ + for(;;) + { + + /* + * Calculate constrained descent direction, store to PG. + * Successful termination if PG is zero. + */ + cqmadx(&state->a, &state->sas.xc, &state->gc, _state); + ae_v_add(&state->gc.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasconstraineddescent(&state->sas, &state->gc, &state->pg, _state); + state->debugphase2flops = state->debugphase2flops+4*(state->nec+state->nic)*n; + v0 = ae_v_dotproduct(&state->pg.ptr.p_double[0], 1, &state->pg.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_eq(v0,0) ) + { + + /* + * Constrained derivative is zero. + * Solution found. + */ + nextaction = 0; + break; + } + + /* + * Build quadratic model of F along descent direction: + * F(xc+alpha*pg) = D2*alpha^2 + D1*alpha + D0 + * Store noise level in the XC (noise level is used to classify + * step as singificant or insignificant). + * + * In case function curvature is negative or product of descent + * direction and gradient is non-negative, iterations are terminated. + * + * NOTE: D0 is not actually used, but we prefer to maintain it. + */ + fprev = minqp_minqpmodelvalue(&state->a, &state->b, &state->sas.xc, n, &state->tmp0, _state); + fprev = fprev+minqp_penaltyfactor*maxscaledgrad*sasactivelcpenalty1(&state->sas, &state->sas.xc, _state); + cqmevalx(&state->a, &state->sas.xc, &v, &noiselevel, _state); + v0 = cqmxtadx2(&state->a, &state->pg, _state); + state->debugphase2flops = state->debugphase2flops+3*2*n*n; + d2 = v0; + v1 = ae_v_dotproduct(&state->pg.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + d1 = v1; + d0 = fprev; + if( ae_fp_less_eq(d2,0) ) + { + + /* + * Second derivative is non-positive, function is non-convex. + */ + state->repterminationtype = -5; + nextaction = 0; + break; + } + if( ae_fp_greater_eq(d1,0) ) + { + + /* + * Second derivative is positive, first derivative is non-negative. + * Solution found. + */ + nextaction = 0; + break; + } + + /* + * Modify quadratic model - add penalty for violation of the active + * constraints. + * + * Boundary constraints are always satisfied exactly, so we do not + * add penalty term for them. General equality constraint of the + * form a'*(xc+alpha*d)=b adds penalty term: + * P(alpha) = (a'*(xc+alpha*d)-b)^2 + * = (alpha*(a'*d) + (a'*xc-b))^2 + * = alpha^2*(a'*d)^2 + alpha*2*(a'*d)*(a'*xc-b) + (a'*xc-b)^2 + * Each penalty term is multiplied by 100*Anorm before adding it to + * the 1-dimensional quadratic model. + * + * Penalization of the quadratic model improves behavior of the + * algorithm in the presense of the multiple degenerate constraints. + * In particular, it prevents algorithm from making large steps in + * directions which violate equality constraints. + */ + for(i=0; i<=state->nec+state->nic-1; i++) + { + if( state->sas.activeset.ptr.p_int[n+i]>0 ) + { + v0 = ae_v_dotproduct(&state->workcleic.ptr.pp_double[i][0], 1, &state->pg.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v1 = ae_v_dotproduct(&state->workcleic.ptr.pp_double[i][0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v1 = v1-state->workcleic.ptr.pp_double[i][n]; + v = 100*state->anorm; + d2 = d2+v*ae_sqr(v0, _state); + d1 = d1+v*2*v0*v1; + d0 = d0+v*ae_sqr(v1, _state); + } + } + state->debugphase2flops = state->debugphase2flops+2*2*(state->nec+state->nic)*n; + + /* + * Try unbounded step. + * In case function change is dominated by noise or function actually increased + * instead of decreasing, we terminate iterations. + */ + v = -d1/(2*d2); + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->xn.ptr.p_double[0], 1, &state->pg.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + fcand = minqp_minqpmodelvalue(&state->a, &state->b, &state->xn, n, &state->tmp0, _state); + fcand = fcand+minqp_penaltyfactor*maxscaledgrad*sasactivelcpenalty1(&state->sas, &state->xn, _state); + state->debugphase2flops = state->debugphase2flops+2*n*n; + if( ae_fp_greater_eq(fcand,fprev-noiselevel*noisetolerance) ) + { + nextaction = 0; + break; + } + + /* + * Save active set + * Perform bounded step with (possible) activation + */ + actstatus = minqp_minqpboundedstepandactivation(state, &state->xn, &state->tmp0, _state); + fcur = minqp_minqpmodelvalue(&state->a, &state->b, &state->sas.xc, n, &state->tmp0, _state); + state->debugphase2flops = state->debugphase2flops+2*n*n; + + /* + * Depending on results, decide what to do: + * 1. In case step was performed without activation of constraints, + * we proceed to Newton method + * 2. In case there was activated at least one constraint with ActiveSet[I]<0, + * we proceed to Phase 1 and re-evaluate active set. + * 3. Otherwise (activation of the constraints with ActiveSet[I]=0) + * we try Phase 2 one more time. + */ + if( actstatus<0 ) + { + + /* + * Step without activation, proceed to Newton + */ + nextaction = 1; + break; + } + if( actstatus==0 ) + { + + /* + * No new constraints added during last activation - only + * ones which were at the boundary (ActiveSet[I]=0), but + * inactive due to numerical noise. + * + * Now, these constraints are added to the active set, and + * we try to perform steepest descent (Phase 2) one more time. + */ + continue; + } + else + { + + /* + * Last step activated at least one significantly new + * constraint (ActiveSet[I]<0), we have to re-evaluate + * active set (Phase 1). + */ + nextaction = -1; + break; + } + } + if( nextaction<0 ) + { + continue; + } + if( nextaction==0 ) + { + break; + } + + /* + * Phase 3: fast equality-constrained solver + * + * NOTE: this solver uses Augmented Lagrangian algorithm to solve + * equality-constrained subproblems. This algorithm may + * perform steps which increase function values instead of + * decreasing it (in hard cases, like overconstrained problems). + * + * Such non-monononic steps may create a loop, when Augmented + * Lagrangian algorithm performs uphill step, and steepest + * descent algorithm (Phase 2) performs downhill step in the + * opposite direction. + * + * In order to prevent iterations to continue forever we + * count iterations when AL algorithm increased function + * value instead of decreasing it. When number of such "bad" + * iterations will increase beyong MaxBadNewtonIts, we will + * terminate algorithm. + */ + fprev = minqp_minqpmodelvalue(&state->a, &state->b, &state->sas.xc, n, &state->tmp0, _state); + for(;;) + { + + /* + * Calculate optimum subject to presently active constraints + */ + state->repncholesky = state->repncholesky+1; + state->debugphase3flops = state->debugphase3flops+ae_pow(n, 3, _state)/3; + if( !minqp_minqpconstrainedoptimum(state, &state->a, state->anorm, &state->b, &state->xn, &state->tmp0, &state->tmpb, &state->tmp1, _state) ) + { + state->repterminationtype = -5; + sasstopoptimization(&state->sas, _state); + return; + } + + /* + * Add constraints. + * If no constraints was added, accept candidate point XN and move to next phase. + */ + if( minqp_minqpboundedstepandactivation(state, &state->xn, &state->tmp0, _state)<0 ) + { + break; + } + } + fcur = minqp_minqpmodelvalue(&state->a, &state->b, &state->sas.xc, n, &state->tmp0, _state); + if( ae_fp_greater_eq(fcur,fprev) ) + { + badnewtonits = badnewtonits+1; + } + if( badnewtonits>=minqp_maxbadnewtonits ) + { + + /* + * Algorithm found solution, but keeps iterating because Newton + * algorithm performs uphill steps (noise in the Augmented Lagrangian + * algorithm). We terminate algorithm; it is considered normal + * termination. + */ + break; + } + } + sasstopoptimization(&state->sas, _state); + + /* + * Post-process: add XOrigin to XC + */ + for(i=0; i<=n-1; i++) + { + if( state->havebndl.ptr.p_bool[i]&&ae_fp_eq(state->sas.xc.ptr.p_double[i],state->workbndl.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->bndl.ptr.p_double[i]; + continue; + } + if( state->havebndu.ptr.p_bool[i]&&ae_fp_eq(state->sas.xc.ptr.p_double[i],state->workbndu.ptr.p_double[i]) ) + { + state->xs.ptr.p_double[i] = state->bndu.ptr.p_double[i]; + continue; + } + state->xs.ptr.p_double[i] = boundval(state->sas.xc.ptr.p_double[i]+state->xorigin.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + return; + } + + /* + * BLEIC solver + */ + if( state->algokind==2 ) + { + ae_assert(state->akind==0||state->akind==1, "MinQPOptimize: unexpected AKind", _state); + ivectorsetlengthatleast(&state->tmpi, state->nec+state->nic, _state); + rvectorsetlengthatleast(&state->tmp0, n, _state); + rvectorsetlengthatleast(&state->tmp1, n, _state); + for(i=0; i<=state->nec-1; i++) + { + state->tmpi.ptr.p_int[i] = 0; + } + for(i=0; i<=state->nic-1; i++) + { + state->tmpi.ptr.p_int[state->nec+i] = -1; + } + minbleicsetlc(&state->solver, &state->cleic, &state->tmpi, state->nec+state->nic, _state); + minbleicsetbc(&state->solver, &state->bndl, &state->bndu, _state); + minbleicsetdrep(&state->solver, ae_true, _state); + minbleicsetcond(&state->solver, ae_minrealnumber, 0.0, 0.0, state->bleicmaxits, _state); + minbleicsetscale(&state->solver, &state->s, _state); + minbleicsetprecscale(&state->solver, _state); + minbleicrestartfrom(&state->solver, &state->xs, _state); + state->repterminationtype = 0; + while(minbleiciteration(&state->solver, _state)) + { + + /* + * Line search started + */ + if( state->solver.lsstart ) + { + + /* + * Iteration counters: + * * inner iterations count is increased on every line search + * * outer iterations count is increased only at steepest descent line search + */ + inc(&state->repinneriterationscount, _state); + if( !state->solver.lbfgssearch ) + { + inc(&state->repouteriterationscount, _state); + } + + /* + * Build quadratic model of F along descent direction: + * F(x+alpha*d) = D2*alpha^2 + D1*alpha + D0 + */ + d0 = state->solver.f; + d1 = ae_v_dotproduct(&state->solver.d.ptr.p_double[0], 1, &state->solver.g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + d2 = 0; + if( state->akind==0 ) + { + d2 = cqmxtadx2(&state->a, &state->solver.d, _state); + } + if( state->akind==1 ) + { + sparsesmv(&state->sparsea, state->sparseaupper, &state->solver.d, &state->tmp0, _state); + d2 = 0.0; + for(i=0; i<=n-1; i++) + { + d2 = d2+state->solver.d.ptr.p_double[i]*state->tmp0.ptr.p_double[i]; + } + d2 = 0.5*d2; + } + + /* + * Suggest new step + */ + if( ae_fp_less(d1,0)&&ae_fp_greater(d2,0) ) + { + state->solver.stp = safeminposrv(-d1, 2*d2, state->solver.curstpmax, _state); + } + + /* + * This line search may be started from steepest descent + * stage (stage 2) or from L-BFGS stage (stage 3) of the + * BLEIC algorithm. Depending on stage type, different + * checks are performed. + * + * Say, L-BFGS stage is an equality-constrained refinement + * stage of BLEIC. This stage refines current iterate + * under "frozen" equality constraints. We can terminate + * iterations at this stage only when we encounter + * unconstrained direction of negative curvature. In all + * other cases (say, when constrained gradient is zero) + * we should not terminate algorithm because everything may + * change after de-activating presently active constraints. + * + * At steepest descent stage of BLEIC we can terminate algorithm + * because it found minimum (steepest descent step is zero + * or too short). We also perform check for direction of + * negative curvature. + */ + if( (ae_fp_less(d2,0)||(ae_fp_eq(d2,0)&&ae_fp_less(d1,0)))&&!state->solver.boundedstep ) + { + + /* + * Function is unbounded from below: + * * function will decrease along D, i.e. either: + * * D2<0 + * * D2=0 and D1<0 + * * step is unconstrained + * + * If these conditions are true, we abnormally terminate QP + * algorithm with return code -4 (we can do so at any stage + * of BLEIC - whether it is L-BFGS or steepest descent one). + */ + state->repterminationtype = -4; + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->solver.x.ptr.p_double[i]; + } + break; + } + if( !state->solver.lbfgssearch&&ae_fp_greater_eq(d2,0) ) + { + + /* + * Tests for "normal" convergence. + * + * These tests are performed only at "steepest descent" stage + * of the BLEIC algorithm, and only when function is non-concave + * (D2>=0) along direction D. + * + * NOTE: we do not test iteration count (MaxIts) here, because + * this stopping condition is tested by BLEIC itself. + */ + if( ae_fp_greater_eq(d1,0) ) + { + + /* + * "Emergency" stopping condition: D is non-descent direction. + * Sometimes it is possible because of numerical noise in the + * target function. + */ + state->repterminationtype = 4; + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->solver.x.ptr.p_double[i]; + } + break; + } + if( ae_fp_greater(d2,0) ) + { + + /* + * Stopping condition #4 - gradient norm is small: + * + * 1. rescale State.Solver.D and State.Solver.G according to + * current scaling, store results to Tmp0 and Tmp1. + * 2. Normalize Tmp0 (scaled direction vector). + * 3. compute directional derivative (in scaled variables), + * which is equal to DOTPRODUCT(Tmp0,Tmp1). + */ + v = 0; + for(i=0; i<=n-1; i++) + { + state->tmp0.ptr.p_double[i] = state->solver.d.ptr.p_double[i]/state->s.ptr.p_double[i]; + state->tmp1.ptr.p_double[i] = state->solver.g.ptr.p_double[i]*state->s.ptr.p_double[i]; + v = v+ae_sqr(state->tmp0.ptr.p_double[i], _state); + } + ae_assert(ae_fp_greater(v,0), "MinQPOptimize: inernal errror (scaled direction is zero)", _state); + v = 1/ae_sqrt(v, _state); + ae_v_muld(&state->tmp0.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + v = ae_v_dotproduct(&state->tmp0.ptr.p_double[0], 1, &state->tmp1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_less_eq(ae_fabs(v, _state),state->bleicepsg) ) + { + state->repterminationtype = 4; + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->solver.x.ptr.p_double[i]; + } + break; + } + + /* + * Stopping condition #1 - relative function improvement is small: + * + * 1. calculate steepest descent step: V = -D1/(2*D2) + * 2. calculate function change: V1= D2*V^2 + D1*V + * 3. stop if function change is small enough + */ + v = -d1/(2*d2); + v1 = d2*v*v+d1*v; + if( ae_fp_less_eq(ae_fabs(v1, _state),state->bleicepsf*ae_maxreal(d0, 1.0, _state)) ) + { + state->repterminationtype = 1; + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->solver.x.ptr.p_double[i]; + } + break; + } + + /* + * Stopping condition #2 - scaled step is small: + * + * 1. calculate step multiplier V0 (step itself is D*V0) + * 2. calculate scaled step length V + * 3. stop if step is small enough + */ + v0 = -d1/(2*d2); + v = 0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(v0*state->solver.d.ptr.p_double[i]/state->s.ptr.p_double[i], _state); + } + if( ae_fp_less_eq(ae_sqrt(v, _state),state->bleicepsx) ) + { + state->repterminationtype = 2; + for(i=0; i<=n-1; i++) + { + state->xs.ptr.p_double[i] = state->solver.x.ptr.p_double[i]; + } + break; + } + } + } + } + + /* + * Gradient evaluation + */ + if( state->solver.needfg ) + { + for(i=0; i<=n-1; i++) + { + state->tmp0.ptr.p_double[i] = state->solver.x.ptr.p_double[i]-state->xorigin.ptr.p_double[i]; + } + if( state->akind==0 ) + { + cqmadx(&state->a, &state->tmp0, &state->tmp1, _state); + } + if( state->akind==1 ) + { + sparsesmv(&state->sparsea, state->sparseaupper, &state->tmp0, &state->tmp1, _state); + } + v0 = ae_v_dotproduct(&state->tmp0.ptr.p_double[0], 1, &state->tmp1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v1 = ae_v_dotproduct(&state->tmp0.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->solver.f = 0.5*v0+v1; + ae_v_move(&state->solver.g.ptr.p_double[0], 1, &state->tmp1.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->solver.g.ptr.p_double[0], 1, &state->b.ptr.p_double[0], 1, ae_v_len(0,n-1)); + } + } + if( state->repterminationtype==0 ) + { + + /* + * BLEIC optimizer was terminated by one of its inner stopping + * conditions. Usually it is iteration counter (if such + * stopping condition was specified by user). + */ + minbleicresults(&state->solver, &state->xs, &state->solverrep, _state); + state->repterminationtype = state->solverrep.terminationtype; + } + else + { + + /* + * BLEIC optimizer was terminated in "emergency" mode by QP + * solver. + * + * NOTE: such termination is "emergency" only when viewed from + * BLEIC's position. QP solver sees such termination as + * routine one, triggered by QP's stopping criteria. + */ + minbleicemergencytermination(&state->solver, _state); + } + return; + } +} + + +/************************************************************************* +QP solver results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution. + This array is allocated and initialized only when + Rep.TerminationType parameter is positive (success). + Rep - optimization report. You should check Rep.TerminationType, + which contains completion code, and you may check another + fields which contain another information about algorithm + functioning. + + Failure codes returned by algorithm are: + * -5 inappropriate solver was used: + * Cholesky solver for (semi)indefinite problems + * Cholesky solver for problems with sparse matrix + * -4 BLEIC-QP algorithm found unconstrained direction + of negative curvature (function is unbounded from + below even under constraints), no meaningful + minimum can be found. + * -3 inconsistent constraints (or maybe feasible point + is too hard to find). If you are sure that + constraints are feasible, try to restart optimizer + with better initial approximation. + + Completion codes specific for Cholesky algorithm: + * 4 successful completion + + Completion codes specific for BLEIC-based algorithm: + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresults(minqpstate* state, + /* Real */ ae_vector* x, + minqpreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _minqpreport_clear(rep); + + minqpresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +QP results + +Buffered implementation of MinQPResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresultsbuf(minqpstate* state, + /* Real */ ae_vector* x, + minqpreport* rep, + ae_state *_state) +{ + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->xs.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->inneriterationscount = state->repinneriterationscount; + rep->outeriterationscount = state->repouteriterationscount; + rep->nmv = state->repnmv; + rep->ncholesky = state->repncholesky; + rep->terminationtype = state->repterminationtype; +} + + +/************************************************************************* +Fast version of MinQPSetLinearTerm(), which doesn't check its arguments. +For internal use only. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlineartermfast(minqpstate* state, + /* Real */ ae_vector* b, + ae_state *_state) +{ + + + ae_v_move(&state->b.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); +} + + +/************************************************************************* +Fast version of MinQPSetQuadraticTerm(), which doesn't check its arguments. + +It accepts additional parameter - shift S, which allows to "shift" matrix +A by adding s*I to A. S must be positive (although it is not checked). + +For internal use only. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadratictermfast(minqpstate* state, + /* Real */ ae_matrix* a, + ae_bool isupper, + double s, + ae_state *_state) +{ + ae_int_t i; + ae_int_t j; + ae_int_t n; + + + n = state->n; + state->akind = 0; + cqmseta(&state->a, a, isupper, 1.0, _state); + if( ae_fp_greater(s,0) ) + { + rvectorsetlengthatleast(&state->tmp0, n, _state); + for(i=0; i<=n-1; i++) + { + state->tmp0.ptr.p_double[i] = a->ptr.pp_double[i][i]+s; + } + cqmrewritedensediagonal(&state->a, &state->tmp0, _state); + } + + /* + * Estimate norm of A + * (it will be used later in the quadratic penalty function) + */ + state->anorm = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + for(j=i; j<=n-1; j++) + { + state->anorm = ae_maxreal(state->anorm, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + else + { + for(j=0; j<=i; j++) + { + state->anorm = ae_maxreal(state->anorm, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + } + state->anorm = state->anorm*n; +} + + +/************************************************************************* +Internal function which allows to rewrite diagonal of quadratic term. +For internal use only. + +This function can be used only when you have dense A and already made +MinQPSetQuadraticTerm(Fast) call. + + -- ALGLIB -- + Copyright 16.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqprewritediagonal(minqpstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + + + cqmrewritedensediagonal(&state->a, s, _state); +} + + +/************************************************************************* +Fast version of MinQPSetStartingPoint(), which doesn't check its arguments. +For internal use only. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetstartingpointfast(minqpstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_int_t n; + + + n = state->n; + ae_v_move(&state->startx.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->havex = ae_true; +} + + +/************************************************************************* +Fast version of MinQPSetOrigin(), which doesn't check its arguments. +For internal use only. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetoriginfast(minqpstate* state, + /* Real */ ae_vector* xorigin, + ae_state *_state) +{ + ae_int_t n; + + + n = state->n; + ae_v_move(&state->xorigin.ptr.p_double[0], 1, &xorigin->ptr.p_double[0], 1, ae_v_len(0,n-1)); +} + + +/************************************************************************* +Having feasible current point XC and possibly infeasible candidate point +XN, this function performs longest step from XC to XN which retains +feasibility. In case XN is found to be infeasible, at least one constraint +is activated. + +For example, if we have: + XC=0.5 + XN=1.2 + x>=0, x<=1 +then this function will move us to X=1.0 and activate constraint "x<=1". + +INPUT PARAMETERS: + State - MinQP state. + XC - current point, must be feasible with respect to + all constraints + XN - candidate point, can be infeasible with respect to some + constraints. Must be located in the subspace of current + active set, i.e. it is feasible with respect to already + active constraints. + Buf - temporary buffer, automatically resized if needed + +OUTPUT PARAMETERS: + State - this function changes following fields of State: + * State.ActiveSet + * State.ActiveC - active linear constraints + XC - new position + +RESULT: + >0, in case at least one inactive non-candidate constraint was activated + =0, in case only "candidate" constraints were activated + <0, in case no constraints were activated by the step + + + -- ALGLIB -- + Copyright 29.02.2012 by Bochkanov Sergey +*************************************************************************/ +static ae_int_t minqp_minqpboundedstepandactivation(minqpstate* state, + /* Real */ ae_vector* xn, + /* Real */ ae_vector* buf, + ae_state *_state) +{ + ae_int_t n; + double stpmax; + ae_int_t cidx; + double cval; + ae_bool needact; + double v; + ae_int_t result; + + + n = state->n; + rvectorsetlengthatleast(buf, n, _state); + ae_v_move(&buf->ptr.p_double[0], 1, &xn->ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_sub(&buf->ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + sasexploredirection(&state->sas, buf, &stpmax, &cidx, &cval, _state); + needact = ae_fp_less_eq(stpmax,1); + v = ae_minreal(stpmax, 1.0, _state); + ae_v_muld(&buf->ptr.p_double[0], 1, ae_v_len(0,n-1), v); + ae_v_add(&buf->ptr.p_double[0], 1, &state->sas.xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + result = sasmoveto(&state->sas, buf, needact, cidx, cval, _state); + return result; +} + + +/************************************************************************* +Model value: f = 0.5*x'*A*x + b'*x + +INPUT PARAMETERS: + A - convex quadratic model; only main quadratic term is used, + other parts of the model (D/Q/linear term) are ignored. + This function does not modify model state. + B - right part + XC - evaluation point + Tmp - temporary buffer, automatically resized if needed + + -- ALGLIB -- + Copyright 20.06.2012 by Bochkanov Sergey +*************************************************************************/ +static double minqp_minqpmodelvalue(convexquadraticmodel* a, + /* Real */ ae_vector* b, + /* Real */ ae_vector* xc, + ae_int_t n, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + double v0; + double v1; + double result; + + + rvectorsetlengthatleast(tmp, n, _state); + cqmadx(a, xc, tmp, _state); + v0 = ae_v_dotproduct(&xc->ptr.p_double[0], 1, &tmp->ptr.p_double[0], 1, ae_v_len(0,n-1)); + v1 = ae_v_dotproduct(&xc->ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + result = 0.5*v0+v1; + return result; +} + + +/************************************************************************* +Optimum of A subject to: +a) active boundary constraints (given by ActiveSet[] and corresponding + elements of XC) +b) active linear constraints (given by C, R, LagrangeC) + +INPUT PARAMETERS: + A - main quadratic term of the model; + although structure may store linear and rank-K terms, + these terms are ignored and rewritten by this function. + ANorm - estimate of ||A|| (2-norm is used) + B - array[N], linear term of the model + XN - possibly preallocated buffer + Tmp - temporary buffer (automatically resized) + Tmp1 - temporary buffer (automatically resized) + +OUTPUT PARAMETERS: + A - modified quadratic model (this function changes rank-K + term and linear term of the model) + LagrangeC- current estimate of the Lagrange coefficients + XN - solution + +RESULT: + True on success, False on failure (non-SPD model) + + -- ALGLIB -- + Copyright 20.06.2012 by Bochkanov Sergey +*************************************************************************/ +static ae_bool minqp_minqpconstrainedoptimum(minqpstate* state, + convexquadraticmodel* a, + double anorm, + /* Real */ ae_vector* b, + /* Real */ ae_vector* xn, + /* Real */ ae_vector* tmp, + /* Boolean */ ae_vector* tmpb, + /* Real */ ae_vector* lagrangec, + ae_state *_state) +{ + ae_int_t itidx; + ae_int_t i; + double v; + double feaserrold; + double feaserrnew; + double theta; + ae_int_t n; + ae_bool result; + + + n = state->n; + + /* + * Rebuild basis accroding to current active set. + * We call SASRebuildBasis() to make sure that fields of SAS + * store up to date values. + */ + sasrebuildbasis(&state->sas, _state); + + /* + * Allocate temporaries. + */ + rvectorsetlengthatleast(tmp, ae_maxint(n, state->sas.basissize, _state), _state); + bvectorsetlengthatleast(tmpb, n, _state); + rvectorsetlengthatleast(lagrangec, state->sas.basissize, _state); + + /* + * Prepare model + */ + for(i=0; i<=state->sas.basissize-1; i++) + { + tmp->ptr.p_double[i] = state->sas.pbasis.ptr.pp_double[i][n]; + } + theta = 100.0*anorm; + for(i=0; i<=n-1; i++) + { + if( state->sas.activeset.ptr.p_int[i]>0 ) + { + tmpb->ptr.p_bool[i] = ae_true; + } + else + { + tmpb->ptr.p_bool[i] = ae_false; + } + } + cqmsetactiveset(a, &state->sas.xc, tmpb, _state); + cqmsetq(a, &state->sas.pbasis, tmp, state->sas.basissize, theta, _state); + + /* + * Iterate until optimal values of Lagrange multipliers are found + */ + for(i=0; i<=state->sas.basissize-1; i++) + { + lagrangec->ptr.p_double[i] = 0; + } + feaserrnew = ae_maxrealnumber; + result = ae_true; + for(itidx=1; itidx<=minqp_maxlagrangeits; itidx++) + { + + /* + * Generate right part B using linear term and current + * estimate of the Lagrange multipliers. + */ + ae_v_move(&tmp->ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=state->sas.basissize-1; i++) + { + v = lagrangec->ptr.p_double[i]; + ae_v_subd(&tmp->ptr.p_double[0], 1, &state->sas.pbasis.ptr.pp_double[i][0], 1, ae_v_len(0,n-1), v); + } + cqmsetb(a, tmp, _state); + + /* + * Solve + */ + result = cqmconstrainedoptimum(a, xn, _state); + if( !result ) + { + return result; + } + + /* + * Compare feasibility errors. + * Terminate if error decreased too slowly. + */ + feaserrold = feaserrnew; + feaserrnew = 0; + for(i=0; i<=state->sas.basissize-1; i++) + { + v = ae_v_dotproduct(&state->sas.pbasis.ptr.pp_double[i][0], 1, &xn->ptr.p_double[0], 1, ae_v_len(0,n-1)); + feaserrnew = feaserrnew+ae_sqr(v-state->sas.pbasis.ptr.pp_double[i][n], _state); + } + feaserrnew = ae_sqrt(feaserrnew, _state); + if( ae_fp_greater_eq(feaserrnew,0.2*feaserrold) ) + { + break; + } + + /* + * Update Lagrange multipliers + */ + for(i=0; i<=state->sas.basissize-1; i++) + { + v = ae_v_dotproduct(&state->sas.pbasis.ptr.pp_double[i][0], 1, &xn->ptr.p_double[0], 1, ae_v_len(0,n-1)); + lagrangec->ptr.p_double[i] = lagrangec->ptr.p_double[i]-theta*(v-state->sas.pbasis.ptr.pp_double[i][n]); + } + } + return result; +} + + +ae_bool _minqpstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minqpstate *p = (minqpstate*)_p; + ae_touch_ptr((void*)p); + if( !_convexquadraticmodel_init(&p->a, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init(&p->sparsea, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->havebndl, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->havebndu, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xorigin, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->startx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->cleic, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_sactiveset_init(&p->sas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->pg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->workbndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->workbndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->workcleic, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xs, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpb, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rctmpg, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpi, 0, DT_INT, _state, make_automatic) ) + return ae_false; + if( !_normestimatorstate_init(&p->estimator, _state, make_automatic) ) + return ae_false; + if( !_minbleicstate_init(&p->solver, _state, make_automatic) ) + return ae_false; + if( !_minbleicreport_init(&p->solverrep, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _minqpstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minqpstate *dst = (minqpstate*)_dst; + minqpstate *src = (minqpstate*)_src; + dst->n = src->n; + dst->algokind = src->algokind; + dst->akind = src->akind; + if( !_convexquadraticmodel_init_copy(&dst->a, &src->a, _state, make_automatic) ) + return ae_false; + if( !_sparsematrix_init_copy(&dst->sparsea, &src->sparsea, _state, make_automatic) ) + return ae_false; + dst->sparseaupper = src->sparseaupper; + dst->anorm = src->anorm; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->havebndl, &src->havebndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->havebndu, &src->havebndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xorigin, &src->xorigin, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->startx, &src->startx, _state, make_automatic) ) + return ae_false; + dst->havex = src->havex; + if( !ae_matrix_init_copy(&dst->cleic, &src->cleic, _state, make_automatic) ) + return ae_false; + dst->nec = src->nec; + dst->nic = src->nic; + dst->bleicepsg = src->bleicepsg; + dst->bleicepsf = src->bleicepsf; + dst->bleicepsx = src->bleicepsx; + dst->bleicmaxits = src->bleicmaxits; + if( !_sactiveset_init_copy(&dst->sas, &src->sas, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gc, &src->gc, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xn, &src->xn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->pg, &src->pg, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->workbndl, &src->workbndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->workbndu, &src->workbndu, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->workcleic, &src->workcleic, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xs, &src->xs, _state, make_automatic) ) + return ae_false; + dst->repinneriterationscount = src->repinneriterationscount; + dst->repouteriterationscount = src->repouteriterationscount; + dst->repncholesky = src->repncholesky; + dst->repnmv = src->repnmv; + dst->repterminationtype = src->repterminationtype; + dst->debugphase1flops = src->debugphase1flops; + dst->debugphase2flops = src->debugphase2flops; + dst->debugphase3flops = src->debugphase3flops; + if( !ae_vector_init_copy(&dst->tmp0, &src->tmp0, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp1, &src->tmp1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpb, &src->tmpb, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rctmpg, &src->rctmpg, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpi, &src->tmpi, _state, make_automatic) ) + return ae_false; + if( !_normestimatorstate_init_copy(&dst->estimator, &src->estimator, _state, make_automatic) ) + return ae_false; + if( !_minbleicstate_init_copy(&dst->solver, &src->solver, _state, make_automatic) ) + return ae_false; + if( !_minbleicreport_init_copy(&dst->solverrep, &src->solverrep, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _minqpstate_clear(void* _p) +{ + minqpstate *p = (minqpstate*)_p; + ae_touch_ptr((void*)p); + _convexquadraticmodel_clear(&p->a); + _sparsematrix_clear(&p->sparsea); + ae_vector_clear(&p->b); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_vector_clear(&p->s); + ae_vector_clear(&p->havebndl); + ae_vector_clear(&p->havebndu); + ae_vector_clear(&p->xorigin); + ae_vector_clear(&p->startx); + ae_matrix_clear(&p->cleic); + _sactiveset_clear(&p->sas); + ae_vector_clear(&p->gc); + ae_vector_clear(&p->xn); + ae_vector_clear(&p->pg); + ae_vector_clear(&p->workbndl); + ae_vector_clear(&p->workbndu); + ae_matrix_clear(&p->workcleic); + ae_vector_clear(&p->xs); + ae_vector_clear(&p->tmp0); + ae_vector_clear(&p->tmp1); + ae_vector_clear(&p->tmpb); + ae_vector_clear(&p->rctmpg); + ae_vector_clear(&p->tmpi); + _normestimatorstate_clear(&p->estimator); + _minbleicstate_clear(&p->solver); + _minbleicreport_clear(&p->solverrep); +} + + +void _minqpstate_destroy(void* _p) +{ + minqpstate *p = (minqpstate*)_p; + ae_touch_ptr((void*)p); + _convexquadraticmodel_destroy(&p->a); + _sparsematrix_destroy(&p->sparsea); + ae_vector_destroy(&p->b); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->havebndl); + ae_vector_destroy(&p->havebndu); + ae_vector_destroy(&p->xorigin); + ae_vector_destroy(&p->startx); + ae_matrix_destroy(&p->cleic); + _sactiveset_destroy(&p->sas); + ae_vector_destroy(&p->gc); + ae_vector_destroy(&p->xn); + ae_vector_destroy(&p->pg); + ae_vector_destroy(&p->workbndl); + ae_vector_destroy(&p->workbndu); + ae_matrix_destroy(&p->workcleic); + ae_vector_destroy(&p->xs); + ae_vector_destroy(&p->tmp0); + ae_vector_destroy(&p->tmp1); + ae_vector_destroy(&p->tmpb); + ae_vector_destroy(&p->rctmpg); + ae_vector_destroy(&p->tmpi); + _normestimatorstate_destroy(&p->estimator); + _minbleicstate_destroy(&p->solver); + _minbleicreport_destroy(&p->solverrep); +} + + +ae_bool _minqpreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minqpreport *p = (minqpreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _minqpreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minqpreport *dst = (minqpreport*)_dst; + minqpreport *src = (minqpreport*)_src; + dst->inneriterationscount = src->inneriterationscount; + dst->outeriterationscount = src->outeriterationscount; + dst->nmv = src->nmv; + dst->ncholesky = src->ncholesky; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _minqpreport_clear(void* _p) +{ + minqpreport *p = (minqpreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _minqpreport_destroy(void* _p) +{ + minqpreport *p = (minqpreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] and Jacobian of f[]. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function vector f[] at given point X +* function vector f[] and Jacobian of f[] (simultaneously) at given point + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() and jac() callbacks. +First one is used to calculate f[] at given point, second one calculates +f[] and Jacobian df[i]/dx[j]. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not provide Jacobian), but it +will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateVJ() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + ae_assert(n>=1, "MinLMCreateVJ: N<1!", _state); + ae_assert(m>=1, "MinLMCreateVJ: M<1!", _state); + ae_assert(x->cnt>=n, "MinLMCreateVJ: Length(X)teststep = 0; + state->n = n; + state->m = m; + state->algomode = 1; + state->hasf = ae_false; + state->hasfi = ae_true; + state->hasg = ae_false; + + /* + * second stage of initialization + */ + minlm_lmprepare(n, m, ae_false, state, _state); + minlmsetacctype(state, 0, _state); + minlmsetcond(state, 0, 0, 0, 0, _state); + minlmsetxrep(state, ae_false, _state); + minlmsetstpmax(state, 0, _state); + minlmrestartfrom(state, x, _state); +} + + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] only. Finite differences are used to +calculate Jacobian. + + +REQUIREMENTS: +This algorithm will request following information during its operation: +* function vector f[] at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() callback. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not accept function vector), but +it will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateV() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +See also MinLMIteration, MinLMResults. + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatev(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + double diffstep, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + ae_assert(ae_isfinite(diffstep, _state), "MinLMCreateV: DiffStep is not finite!", _state); + ae_assert(ae_fp_greater(diffstep,0), "MinLMCreateV: DiffStep<=0!", _state); + ae_assert(n>=1, "MinLMCreateV: N<1!", _state); + ae_assert(m>=1, "MinLMCreateV: M<1!", _state); + ae_assert(x->cnt>=n, "MinLMCreateV: Length(X)teststep = 0; + state->n = n; + state->m = m; + state->algomode = 0; + state->hasf = ae_false; + state->hasfi = ae_true; + state->hasg = ae_false; + state->diffstep = diffstep; + + /* + * Second stage of initialization + */ + minlm_lmprepare(n, m, ae_false, state, _state); + minlmsetacctype(state, 1, _state); + minlmsetcond(state, 0, 0, 0, 0, _state); + minlmsetxrep(state, ae_false, _state); + minlmsetstpmax(state, 0, _state); + minlmrestartfrom(state, x, _state); +} + + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of general form (not "sum-of- +-squares") function + F = F(x[0], ..., x[n-1]) +using its gradient and Hessian. Levenberg-Marquardt modification with +L-BFGS pre-optimization and internal pre-conditioned L-BFGS optimization +after each Levenberg-Marquardt step is used. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function value F at given point X +* F and gradient G (simultaneously) at given point X +* F, G and Hessian H (simultaneously) at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts func(), grad() and hess() +function pointers. First pointer is used to calculate F at given point, +second one calculates F(x) and grad F(x), third one calculates F(x), +grad F(x), hess F(x). + +You can try to initialize MinLMState structure with FGH-function and then +use incorrect version of MinLMOptimize() (for example, version which does +not provide Hessian matrix), but it will lead to exception being thrown +after first attempt to calculate Hessian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateFGH() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + pointers (delegates, etc.) to callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgh(ae_int_t n, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + ae_assert(n>=1, "MinLMCreateFGH: N<1!", _state); + ae_assert(x->cnt>=n, "MinLMCreateFGH: Length(X)teststep = 0; + state->n = n; + state->m = 0; + state->algomode = 2; + state->hasf = ae_true; + state->hasfi = ae_false; + state->hasg = ae_true; + + /* + * init2 + */ + minlm_lmprepare(n, 0, ae_true, state, _state); + minlmsetacctype(state, 2, _state); + minlmsetcond(state, 0, 0, 0, 0, _state); + minlmsetxrep(state, ae_false, _state); + minlmsetstpmax(state, 0, _state); + minlmrestartfrom(state, x, _state); +} + + +/************************************************************************* +This function sets stopping conditions for Levenberg-Marquardt optimization +algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLMSetScale() + 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). + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetcond(minlmstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinLMSetCond: EpsG is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinLMSetCond: negative EpsG!", _state); + ae_assert(ae_isfinite(epsf, _state), "MinLMSetCond: EpsF is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinLMSetCond: negative EpsF!", _state); + ae_assert(ae_isfinite(epsx, _state), "MinLMSetCond: EpsX is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinLMSetCond: negative EpsX!", _state); + ae_assert(maxits>=0, "MinLMSetCond: negative MaxIts!", _state); + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->epsg = epsg; + state->epsf = epsf; + state->epsx = epsx; + state->maxits = maxits; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLMOptimize(). Both Levenberg-Marquardt and internal L-BFGS +iterations are reported. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetxrep(minlmstate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetstpmax(minlmstate* state, double stpmax, ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "MinLMSetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "MinLMSetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* +This function sets scaling coefficients for LM 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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetscale(minlmstate* state, + /* Real */ ae_vector* s, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(s->cnt>=state->n, "MinLMSetScale: Length(S)n-1; i++) + { + ae_assert(ae_isfinite(s->ptr.p_double[i], _state), "MinLMSetScale: S contains infinite or NAN elements", _state); + ae_assert(ae_fp_neq(s->ptr.p_double[i],0), "MinLMSetScale: S contains zero elements", _state); + state->s.ptr.p_double[i] = ae_fabs(s->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +This function sets boundary constraints for LM 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[N]. + 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[N]. + 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: this solver has following useful properties: +* bound constraints are always satisfied exactly +* function is evaluated only INSIDE area specified by bound constraints + or at its boundary + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetbc(minlmstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state) +{ + ae_int_t i; + ae_int_t n; + + + n = state->n; + ae_assert(bndl->cnt>=n, "MinLMSetBC: Length(BndL)cnt>=n, "MinLMSetBC: Length(BndU)ptr.p_double[i], _state)||ae_isneginf(bndl->ptr.p_double[i], _state), "MinLMSetBC: BndL contains NAN or +INF", _state); + ae_assert(ae_isfinite(bndu->ptr.p_double[i], _state)||ae_isposinf(bndu->ptr.p_double[i], _state), "MinLMSetBC: BndU contains NAN or -INF", _state); + state->bndl.ptr.p_double[i] = bndl->ptr.p_double[i]; + state->havebndl.ptr.p_bool[i] = ae_isfinite(bndl->ptr.p_double[i], _state); + state->bndu.ptr.p_double[i] = bndu->ptr.p_double[i]; + state->havebndu.ptr.p_bool[i] = ae_isfinite(bndu->ptr.p_double[i], _state); + } +} + + +/************************************************************************* +This function is used to change acceleration settings + +You can choose between three acceleration strategies: +* AccType=0, no acceleration. +* AccType=1, secant updates are used to update quadratic model after each + iteration. After fixed number of iterations (or after model breakdown) + we recalculate quadratic model using analytic Jacobian or finite + differences. Number of secant-based iterations depends on optimization + settings: about 3 iterations - when we have analytic Jacobian, up to 2*N + iterations - when we use finite differences to calculate Jacobian. + +AccType=1 is recommended when Jacobian calculation cost is prohibitive +high (several Mx1 function vector calculations followed by several NxN +Cholesky factorizations are faster than calculation of one M*N Jacobian). +It should also be used when we have no Jacobian, because finite difference +approximation takes too much time to compute. + +Table below list optimization protocols (XYZ protocol corresponds to +MinLMCreateXYZ) and acceleration types they support (and use by default). + +ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS: + +protocol 0 1 comment +V + + +VJ + + +FGH + + +DAFAULT VALUES: + +protocol 0 1 comment +V x without acceleration it is so slooooooooow +VJ x +FGH x + +NOTE: this function should be called before optimization. Attempt to call +it during algorithm iterations may result in unexpected behavior. + +NOTE: attempt to call this function with unsupported protocol/acceleration +combination will result in exception being thrown. + + -- ALGLIB -- + Copyright 14.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetacctype(minlmstate* state, + ae_int_t acctype, + ae_state *_state) +{ + + + ae_assert((acctype==0||acctype==1)||acctype==2, "MinLMSetAccType: incorrect AccType!", _state); + if( acctype==2 ) + { + acctype = 0; + } + if( acctype==0 ) + { + state->maxmodelage = 0; + state->makeadditers = ae_false; + return; + } + if( acctype==1 ) + { + ae_assert(state->hasfi, "MinLMSetAccType: AccType=1 is incompatible with current protocol!", _state); + if( state->algomode==0 ) + { + state->maxmodelage = 2*state->n; + } + else + { + state->maxmodelage = minlm_smallmodelage; + } + state->makeadditers = ae_false; + return; + } +} + + +/************************************************************************* +NOTES: + +1. Depending on function used to create state structure, this algorithm + may accept Jacobian and/or Hessian and/or gradient. 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 + MinLMCreateFGH() (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. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool minlmiteration(minlmstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t m; + ae_bool bflag; + ae_int_t iflag; + double v; + double s; + double t; + ae_int_t i; + ae_int_t k; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + iflag = state->rstate.ia.ptr.p_int[2]; + i = state->rstate.ia.ptr.p_int[3]; + k = state->rstate.ia.ptr.p_int[4]; + bflag = state->rstate.ba.ptr.p_bool[0]; + v = state->rstate.ra.ptr.p_double[0]; + s = state->rstate.ra.ptr.p_double[1]; + t = state->rstate.ra.ptr.p_double[2]; + } + else + { + n = -983; + m = -989; + iflag = -834; + i = 900; + k = -287; + bflag = ae_false; + v = 214; + s = -338; + t = -686; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + if( state->rstate.stage==14 ) + { + goto lbl_14; + } + if( state->rstate.stage==15 ) + { + goto lbl_15; + } + if( state->rstate.stage==16 ) + { + goto lbl_16; + } + if( state->rstate.stage==17 ) + { + goto lbl_17; + } + if( state->rstate.stage==18 ) + { + goto lbl_18; + } + + /* + * Routine body + */ + + /* + * prepare + */ + n = state->n; + m = state->m; + state->repiterationscount = 0; + state->repterminationtype = 0; + state->repfuncidx = -1; + state->repvaridx = -1; + state->repnfunc = 0; + state->repnjac = 0; + state->repngrad = 0; + state->repnhess = 0; + state->repncholesky = 0; + + /* + * check consistency of constraints, + * enforce feasibility of the solution + * set constraints + */ + if( !enforceboundaryconstraints(&state->xbase, &state->bndl, &state->havebndl, &state->bndu, &state->havebndu, n, 0, _state) ) + { + state->repterminationtype = -3; + result = ae_false; + return result; + } + minqpsetbc(&state->qpstate, &state->bndl, &state->bndu, _state); + + /* + * Check, that transferred derivative value is right + */ + minlm_clearrequestfields(state, _state); + if( !(state->algomode==1&&ae_fp_greater(state->teststep,0)) ) + { + goto lbl_19; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->needfij = ae_true; + i = 0; +lbl_21: + if( i>n-1 ) + { + goto lbl_23; + } + ae_assert((state->havebndl.ptr.p_bool[i]&&ae_fp_less_eq(state->bndl.ptr.p_double[i],state->x.ptr.p_double[i]))||!state->havebndl.ptr.p_bool[i], "MinLM: internal error(State.X is out of bounds)", _state); + ae_assert((state->havebndu.ptr.p_bool[i]&&ae_fp_less_eq(state->x.ptr.p_double[i],state->bndu.ptr.p_double[i]))||!state->havebndu.ptr.p_bool[i], "MinLMIteration: internal error(State.X is out of bounds)", _state); + v = state->x.ptr.p_double[i]; + state->x.ptr.p_double[i] = v-state->teststep*state->s.ptr.p_double[i]; + if( state->havebndl.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_maxreal(state->x.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + state->xm1 = state->x.ptr.p_double[i]; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + ae_v_move(&state->fm1.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_move(&state->gm1.ptr.p_double[0], 1, &state->j.ptr.pp_double[0][i], state->j.stride, ae_v_len(0,m-1)); + state->x.ptr.p_double[i] = v+state->teststep*state->s.ptr.p_double[i]; + if( state->havebndu.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_minreal(state->x.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + state->xp1 = state->x.ptr.p_double[i]; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + ae_v_move(&state->fp1.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_move(&state->gp1.ptr.p_double[0], 1, &state->j.ptr.pp_double[0][i], state->j.stride, ae_v_len(0,m-1)); + state->x.ptr.p_double[i] = (state->xm1+state->xp1)/2; + if( state->havebndl.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_maxreal(state->x.ptr.p_double[i], state->bndl.ptr.p_double[i], _state); + } + if( state->havebndu.ptr.p_bool[i] ) + { + state->x.ptr.p_double[i] = ae_minreal(state->x.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + ae_v_move(&state->fc1.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_move(&state->gc1.ptr.p_double[0], 1, &state->j.ptr.pp_double[0][i], state->j.stride, ae_v_len(0,m-1)); + state->x.ptr.p_double[i] = v; + for(k=0; k<=m-1; k++) + { + if( !derivativecheck(state->fm1.ptr.p_double[k], state->gm1.ptr.p_double[k], state->fp1.ptr.p_double[k], state->gp1.ptr.p_double[k], state->fc1.ptr.p_double[k], state->gc1.ptr.p_double[k], state->xp1-state->xm1, _state) ) + { + state->repfuncidx = k; + state->repvaridx = i; + state->repterminationtype = -7; + result = ae_false; + return result; + } + } + i = i+1; + goto lbl_21; +lbl_23: + state->needfij = ae_false; +lbl_19: + + /* + * Initial report of current point + * + * Note 1: we rewrite State.X twice because + * user may accidentally change it after first call. + * + * Note 2: we set NeedF or NeedFI depending on what + * information about function we have. + */ + if( !state->xrep ) + { + goto lbl_24; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + if( !state->hasf ) + { + goto lbl_26; + } + state->needf = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needf = ae_false; + goto lbl_27; +lbl_26: + ae_assert(state->hasfi, "MinLM: internal error 2!", _state); + state->needfi = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->needfi = ae_false; + v = ae_v_dotproduct(&state->fi.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->f = v; +lbl_27: + state->repnfunc = state->repnfunc+1; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->xupdated = ae_false; +lbl_24: + + /* + * Prepare control variables + */ + state->nu = 1; + state->lambdav = -ae_maxrealnumber; + state->modelage = state->maxmodelage+1; + state->deltaxready = ae_false; + state->deltafready = ae_false; + + /* + * Main cycle. + * + * We move through it until either: + * * one of the stopping conditions is met + * * we decide that stopping conditions are too stringent + * and break from cycle + * + */ +lbl_28: + if( ae_false ) + { + goto lbl_29; + } + + /* + * First, we have to prepare quadratic model for our function. + * We use BFlag to ensure that model is prepared; + * if it is false at the end of this block, something went wrong. + * + * We may either calculate brand new model or update old one. + * + * Before this block we have: + * * State.XBase - current position. + * * State.DeltaX - if DeltaXReady is True + * * State.DeltaF - if DeltaFReady is True + * + * After this block is over, we will have: + * * State.XBase - base point (unchanged) + * * State.FBase - F(XBase) + * * State.GBase - linear term + * * State.QuadraticModel - quadratic term + * * State.LambdaV - current estimate for lambda + * + * We also clear DeltaXReady/DeltaFReady flags + * after initialization is done. + */ + bflag = ae_false; + if( !(state->algomode==0||state->algomode==1) ) + { + goto lbl_30; + } + + /* + * Calculate f[] and Jacobian + */ + if( !(state->modelage>state->maxmodelage||!(state->deltaxready&&state->deltafready)) ) + { + goto lbl_32; + } + + /* + * Refresh model (using either finite differences or analytic Jacobian) + */ + if( state->algomode!=0 ) + { + goto lbl_34; + } + + /* + * Optimization using F values only. + * Use finite differences to estimate Jacobian. + */ + ae_assert(state->hasfi, "MinLMIteration: internal error when estimating Jacobian (no f[])", _state); + k = 0; +lbl_36: + if( k>n-1 ) + { + goto lbl_38; + } + + /* + * We guard X[k] from leaving [BndL,BndU]. + * In case BndL=BndU, we assume that derivative in this direction is zero. + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->x.ptr.p_double[k] = state->x.ptr.p_double[k]-state->s.ptr.p_double[k]*state->diffstep; + if( state->havebndl.ptr.p_bool[k] ) + { + state->x.ptr.p_double[k] = ae_maxreal(state->x.ptr.p_double[k], state->bndl.ptr.p_double[k], _state); + } + if( state->havebndu.ptr.p_bool[k] ) + { + state->x.ptr.p_double[k] = ae_minreal(state->x.ptr.p_double[k], state->bndu.ptr.p_double[k], _state); + } + state->xm1 = state->x.ptr.p_double[k]; + minlm_clearrequestfields(state, _state); + state->needfi = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->repnfunc = state->repnfunc+1; + ae_v_move(&state->fm1.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->x.ptr.p_double[k] = state->x.ptr.p_double[k]+state->s.ptr.p_double[k]*state->diffstep; + if( state->havebndl.ptr.p_bool[k] ) + { + state->x.ptr.p_double[k] = ae_maxreal(state->x.ptr.p_double[k], state->bndl.ptr.p_double[k], _state); + } + if( state->havebndu.ptr.p_bool[k] ) + { + state->x.ptr.p_double[k] = ae_minreal(state->x.ptr.p_double[k], state->bndu.ptr.p_double[k], _state); + } + state->xp1 = state->x.ptr.p_double[k]; + minlm_clearrequestfields(state, _state); + state->needfi = ae_true; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->repnfunc = state->repnfunc+1; + ae_v_move(&state->fp1.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + v = state->xp1-state->xm1; + if( ae_fp_neq(v,0) ) + { + v = 1/v; + ae_v_moved(&state->j.ptr.pp_double[0][k], state->j.stride, &state->fp1.ptr.p_double[0], 1, ae_v_len(0,m-1), v); + ae_v_subd(&state->j.ptr.pp_double[0][k], state->j.stride, &state->fm1.ptr.p_double[0], 1, ae_v_len(0,m-1), v); + } + else + { + for(i=0; i<=m-1; i++) + { + state->j.ptr.pp_double[i][k] = 0; + } + } + k = k+1; + goto lbl_36; +lbl_38: + + /* + * Calculate F(XBase) + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->needfi = ae_true; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->needfi = ae_false; + state->repnfunc = state->repnfunc+1; + state->repnjac = state->repnjac+1; + + /* + * New model + */ + state->modelage = 0; + goto lbl_35; +lbl_34: + + /* + * Obtain f[] and Jacobian + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->needfij = ae_true; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->needfij = ae_false; + state->repnfunc = state->repnfunc+1; + state->repnjac = state->repnjac+1; + + /* + * New model + */ + state->modelage = 0; +lbl_35: + goto lbl_33; +lbl_32: + + /* + * State.J contains Jacobian or its current approximation; + * refresh it using secant updates: + * + * f(x0+dx) = f(x0) + J*dx, + * J_new = J_old + u*h' + * h = x_new-x_old + * u = (f_new - f_old - J_old*h)/(h'h) + * + * We can explicitly generate h and u, but it is + * preferential to do in-place calculations. Only + * I-th row of J_old is needed to calculate u[I], + * so we can update J row by row in one pass. + * + * NOTE: we expect that State.XBase contains new point, + * State.FBase contains old point, State.DeltaX and + * State.DeltaY contain updates from last step. + */ + ae_assert(state->deltaxready&&state->deltafready, "MinLMIteration: uninitialized DeltaX/DeltaF", _state); + t = ae_v_dotproduct(&state->deltax.ptr.p_double[0], 1, &state->deltax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_assert(ae_fp_neq(t,0), "MinLM: internal error (T=0)", _state); + for(i=0; i<=m-1; i++) + { + v = ae_v_dotproduct(&state->j.ptr.pp_double[i][0], 1, &state->deltax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = (state->deltaf.ptr.p_double[i]-v)/t; + ae_v_addd(&state->j.ptr.pp_double[i][0], 1, &state->deltax.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + ae_v_move(&state->fi.ptr.p_double[0], 1, &state->fibase.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_add(&state->fi.ptr.p_double[0], 1, &state->deltaf.ptr.p_double[0], 1, ae_v_len(0,m-1)); + + /* + * Increase model age + */ + state->modelage = state->modelage+1; +lbl_33: + + /* + * Generate quadratic model: + * f(xbase+dx) = + * = (f0 + J*dx)'(f0 + J*dx) + * = f0^2 + dx'J'f0 + f0*J*dx + dx'J'J*dx + * = f0^2 + 2*f0*J*dx + dx'J'J*dx + * + * Note that we calculate 2*(J'J) instead of J'J because + * our quadratic model is based on Tailor decomposition, + * i.e. it has 0.5 before quadratic term. + */ + rmatrixgemm(n, n, m, 2.0, &state->j, 0, 0, 1, &state->j, 0, 0, 0, 0.0, &state->quadraticmodel, 0, 0, _state); + rmatrixmv(n, m, &state->j, 0, 0, 1, &state->fi, 0, &state->gbase, 0, _state); + ae_v_muld(&state->gbase.ptr.p_double[0], 1, ae_v_len(0,n-1), 2); + v = ae_v_dotproduct(&state->fi.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->fbase = v; + ae_v_move(&state->fibase.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + + /* + * set control variables + */ + bflag = ae_true; +lbl_30: + if( state->algomode!=2 ) + { + goto lbl_39; + } + ae_assert(!state->hasfi, "MinLMIteration: internal error (HasFI is True in Hessian-based mode)", _state); + + /* + * Obtain F, G, H + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->needfgh = ae_true; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->needfgh = ae_false; + state->repnfunc = state->repnfunc+1; + state->repngrad = state->repngrad+1; + state->repnhess = state->repnhess+1; + rmatrixcopy(n, n, &state->h, 0, 0, &state->quadraticmodel, 0, 0, _state); + ae_v_move(&state->gbase.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fbase = state->f; + + /* + * set control variables + */ + bflag = ae_true; + state->modelage = 0; +lbl_39: + ae_assert(bflag, "MinLM: internal integrity check failed!", _state); + state->deltaxready = ae_false; + state->deltafready = ae_false; + + /* + * If Lambda is not initialized, initialize it using quadratic model + */ + if( ae_fp_less(state->lambdav,0) ) + { + state->lambdav = 0; + for(i=0; i<=n-1; i++) + { + state->lambdav = ae_maxreal(state->lambdav, ae_fabs(state->quadraticmodel.ptr.pp_double[i][i], _state)*ae_sqr(state->s.ptr.p_double[i], _state), _state); + } + state->lambdav = 0.001*state->lambdav; + if( ae_fp_eq(state->lambdav,0) ) + { + state->lambdav = 1; + } + } + + /* + * Test stopping conditions for function gradient + */ + if( ae_fp_greater(minlm_boundedscaledantigradnorm(state, &state->xbase, &state->gbase, _state),state->epsg) ) + { + goto lbl_41; + } + if( state->modelage!=0 ) + { + goto lbl_43; + } + + /* + * Model is fresh, we can rely on it and terminate algorithm + */ + state->repterminationtype = 4; + if( !state->xrep ) + { + goto lbl_45; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + state->xupdated = ae_false; +lbl_45: + result = ae_false; + return result; + goto lbl_44; +lbl_43: + + /* + * Model is not fresh, we should refresh it and test + * conditions once more + */ + state->modelage = state->maxmodelage+1; + goto lbl_28; +lbl_44: +lbl_41: + + /* + * Find value of Levenberg-Marquardt damping parameter which: + * * leads to positive definite damped model + * * within bounds specified by StpMax + * * generates step which decreases function value + * + * After this block IFlag is set to: + * * -3, if constraints are infeasible + * * -2, if model update is needed (either Lambda growth is too large + * or step is too short, but we can't rely on model and stop iterations) + * * -1, if model is fresh, Lambda have grown too large, termination is needed + * * 0, if everything is OK, continue iterations + * + * State.Nu can have any value on enter, but after exit it is set to 1.0 + */ + iflag = -99; +lbl_47: + if( ae_false ) + { + goto lbl_48; + } + + /* + * Do we need model update? + */ + if( state->modelage>0&&ae_fp_greater_eq(state->nu,minlm_suspiciousnu) ) + { + iflag = -2; + goto lbl_48; + } + + /* + * Setup quadratic solver and solve quadratic programming problem. + * After problem is solved we'll try to bound step by StpMax + * (Lambda will be increased if step size is too large). + * + * We use BFlag variable to indicate that we have to increase Lambda. + * If it is False, we will try to increase Lambda and move to new iteration. + */ + bflag = ae_true; + minqpsetstartingpointfast(&state->qpstate, &state->xbase, _state); + minqpsetoriginfast(&state->qpstate, &state->xbase, _state); + minqpsetlineartermfast(&state->qpstate, &state->gbase, _state); + minqpsetquadratictermfast(&state->qpstate, &state->quadraticmodel, ae_true, 0.0, _state); + for(i=0; i<=n-1; i++) + { + state->tmp0.ptr.p_double[i] = state->quadraticmodel.ptr.pp_double[i][i]+state->lambdav/ae_sqr(state->s.ptr.p_double[i], _state); + } + minqprewritediagonal(&state->qpstate, &state->tmp0, _state); + minqpoptimize(&state->qpstate, _state); + minqpresultsbuf(&state->qpstate, &state->xdir, &state->qprep, _state); + if( state->qprep.terminationtype>0 ) + { + + /* + * successful solution of QP problem + */ + ae_v_sub(&state->xdir.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = ae_v_dotproduct(&state->xdir.ptr.p_double[0], 1, &state->xdir.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_isfinite(v, _state) ) + { + v = ae_sqrt(v, _state); + if( ae_fp_greater(state->stpmax,0)&&ae_fp_greater(v,state->stpmax) ) + { + bflag = ae_false; + } + } + else + { + bflag = ae_false; + } + } + else + { + + /* + * Either problem is non-convex (increase LambdaV) or constraints are inconsistent + */ + ae_assert(state->qprep.terminationtype==-3||state->qprep.terminationtype==-5, "MinLM: unexpected completion code from QP solver", _state); + if( state->qprep.terminationtype==-3 ) + { + iflag = -3; + goto lbl_48; + } + bflag = ae_false; + } + if( !bflag ) + { + + /* + * Solution failed: + * try to increase lambda to make matrix positive definite and continue. + */ + if( !minlm_increaselambda(&state->lambdav, &state->nu, _state) ) + { + iflag = -1; + goto lbl_48; + } + goto lbl_47; + } + + /* + * Step in State.XDir and it is bounded by StpMax. + * + * We should check stopping conditions on step size here. + * DeltaX, which is used for secant updates, is initialized here. + * + * This code is a bit tricky because sometimes XDir<>0, but + * it is so small that XDir+XBase==XBase (in finite precision + * arithmetics). So we set DeltaX to XBase, then + * add XDir, and then subtract XBase to get exact value of + * DeltaX. + * + * Step length is estimated using DeltaX. + * + * NOTE: stopping conditions are tested + * for fresh models only (ModelAge=0) + */ + ae_v_move(&state->deltax.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->deltax.ptr.p_double[0], 1, &state->xdir.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_sub(&state->deltax.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->deltaxready = ae_true; + v = 0.0; + for(i=0; i<=n-1; i++) + { + v = v+ae_sqr(state->deltax.ptr.p_double[i]/state->s.ptr.p_double[i], _state); + } + v = ae_sqrt(v, _state); + if( ae_fp_greater(v,state->epsx) ) + { + goto lbl_49; + } + if( state->modelage!=0 ) + { + goto lbl_51; + } + + /* + * Step is too short, model is fresh and we can rely on it. + * Terminating. + */ + state->repterminationtype = 2; + if( !state->xrep ) + { + goto lbl_53; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + state->xupdated = ae_false; +lbl_53: + result = ae_false; + return result; + goto lbl_52; +lbl_51: + + /* + * Step is suspiciously short, but model is not fresh + * and we can't rely on it. + */ + iflag = -2; + goto lbl_48; +lbl_52: +lbl_49: + + /* + * Let's evaluate new step: + * a) if we have Fi vector, we evaluate it using rcomm, and + * then we manually calculate State.F as sum of squares of Fi[] + * b) if we have F value, we just evaluate it through rcomm interface + * + * We prefer (a) because we may need Fi vector for additional + * iterations + */ + ae_assert(state->hasfi||state->hasf, "MinLM: internal error 2!", _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_add(&state->x.ptr.p_double[0], 1, &state->xdir.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + if( !state->hasfi ) + { + goto lbl_55; + } + state->needfi = ae_true; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->needfi = ae_false; + v = ae_v_dotproduct(&state->fi.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->f = v; + ae_v_move(&state->deltaf.ptr.p_double[0], 1, &state->fi.ptr.p_double[0], 1, ae_v_len(0,m-1)); + ae_v_sub(&state->deltaf.ptr.p_double[0], 1, &state->fibase.ptr.p_double[0], 1, ae_v_len(0,m-1)); + state->deltafready = ae_true; + goto lbl_56; +lbl_55: + state->needf = ae_true; + state->rstate.stage = 14; + goto lbl_rcomm; +lbl_14: + state->needf = ae_false; +lbl_56: + state->repnfunc = state->repnfunc+1; + if( ae_fp_greater_eq(state->f,state->fbase) ) + { + + /* + * Increase lambda and continue + */ + if( !minlm_increaselambda(&state->lambdav, &state->nu, _state) ) + { + iflag = -1; + goto lbl_48; + } + goto lbl_47; + } + + /* + * We've found our step! + */ + iflag = 0; + goto lbl_48; + goto lbl_47; +lbl_48: + state->nu = 1; + ae_assert(iflag>=-3&&iflag<=0, "MinLM: internal integrity check failed!", _state); + if( iflag==-3 ) + { + state->repterminationtype = -3; + result = ae_false; + return result; + } + if( iflag==-2 ) + { + state->modelage = state->maxmodelage+1; + goto lbl_28; + } + if( iflag==-1 ) + { + goto lbl_29; + } + + /* + * Levenberg-Marquardt step is ready. + * Compare predicted vs. actual decrease and decide what to do with lambda. + * + * NOTE: we expect that State.DeltaX contains direction of step, + * State.F contains function value at new point. + */ + ae_assert(state->deltaxready, "MinLM: deltaX is not ready", _state); + t = 0; + for(i=0; i<=n-1; i++) + { + v = ae_v_dotproduct(&state->quadraticmodel.ptr.pp_double[i][0], 1, &state->deltax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + t = t+state->deltax.ptr.p_double[i]*state->gbase.ptr.p_double[i]+0.5*state->deltax.ptr.p_double[i]*v; + } + state->predicteddecrease = -t; + state->actualdecrease = -(state->f-state->fbase); + if( ae_fp_less_eq(state->predicteddecrease,0) ) + { + goto lbl_29; + } + v = state->actualdecrease/state->predicteddecrease; + if( ae_fp_greater_eq(v,0.1) ) + { + goto lbl_57; + } + if( minlm_increaselambda(&state->lambdav, &state->nu, _state) ) + { + goto lbl_59; + } + + /* + * Lambda is too large, we have to break iterations. + */ + state->repterminationtype = 7; + if( !state->xrep ) + { + goto lbl_61; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 15; + goto lbl_rcomm; +lbl_15: + state->xupdated = ae_false; +lbl_61: + result = ae_false; + return result; +lbl_59: +lbl_57: + if( ae_fp_greater(v,0.5) ) + { + minlm_decreaselambda(&state->lambdav, &state->nu, _state); + } + + /* + * Accept step, report it and + * test stopping conditions on iterations count and function decrease. + * + * NOTE: we expect that State.DeltaX contains direction of step, + * State.F contains function value at new point. + * + * NOTE2: we should update XBase ONLY. In the beginning of the next + * iteration we expect that State.FIBase is NOT updated and + * contains old value of a function vector. + */ + ae_v_add(&state->xbase.ptr.p_double[0], 1, &state->deltax.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( !state->xrep ) + { + goto lbl_63; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 16; + goto lbl_rcomm; +lbl_16: + state->xupdated = ae_false; +lbl_63: + state->repiterationscount = state->repiterationscount+1; + if( state->repiterationscount>=state->maxits&&state->maxits>0 ) + { + state->repterminationtype = 5; + } + if( state->modelage==0 ) + { + if( ae_fp_less_eq(ae_fabs(state->f-state->fbase, _state),state->epsf*ae_maxreal(1, ae_maxreal(ae_fabs(state->f, _state), ae_fabs(state->fbase, _state), _state), _state)) ) + { + state->repterminationtype = 1; + } + } + if( state->repterminationtype<=0 ) + { + goto lbl_65; + } + if( !state->xrep ) + { + goto lbl_67; + } + + /* + * Report: XBase contains new point, F contains function value at new point + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 17; + goto lbl_rcomm; +lbl_17: + state->xupdated = ae_false; +lbl_67: + result = ae_false; + return result; +lbl_65: + state->modelage = state->modelage+1; + goto lbl_28; +lbl_29: + + /* + * Lambda is too large, we have to break iterations. + */ + state->repterminationtype = 7; + if( !state->xrep ) + { + goto lbl_69; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + minlm_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 18; + goto lbl_rcomm; +lbl_18: + state->xupdated = ae_false; +lbl_69: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = iflag; + state->rstate.ia.ptr.p_int[3] = i; + state->rstate.ia.ptr.p_int[4] = k; + state->rstate.ba.ptr.p_bool[0] = bflag; + state->rstate.ra.ptr.p_double[0] = v; + state->rstate.ra.ptr.p_double[1] = s; + state->rstate.ra.ptr.p_double[2] = t; + return result; +} + + +/************************************************************************* +Levenberg-Marquardt algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report; + see comments for this structure for more info. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresults(minlmstate* state, + /* Real */ ae_vector* x, + minlmreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _minlmreport_clear(rep); + + minlmresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +Levenberg-Marquardt algorithm results + +Buffered implementation of MinLMResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresultsbuf(minlmstate* state, + /* Real */ ae_vector* x, + minlmreport* rep, + ae_state *_state) +{ + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->terminationtype = state->repterminationtype; + rep->funcidx = state->repfuncidx; + rep->varidx = state->repvaridx; + rep->nfunc = state->repnfunc; + rep->njac = state->repnjac; + rep->ngrad = state->repngrad; + rep->nhess = state->repnhess; + rep->ncholesky = state->repncholesky; +} + + +/************************************************************************* +This subroutine restarts LM algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinLMCreateXXX call. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmrestartfrom(minlmstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + + + ae_assert(x->cnt>=state->n, "MinLMRestartFrom: Length(X)n, _state), "MinLMRestartFrom: X contains infinite or NaN values!", _state); + ae_v_move(&state->xbase.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_vector_set_length(&state->rstate.ia, 4+1, _state); + ae_vector_set_length(&state->rstate.ba, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; + minlm_clearrequestfields(state, _state); +} + + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevgj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + minlmcreatevj(n, m, x, state, _state); +} + + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + minlmcreatefj(n, m, x, state, _state); +} + + +/************************************************************************* +This function is considered obsolete since ALGLIB 3.1.0 and is present for +backward compatibility only. We recommend to use MinLMCreateVJ, which +provides similar, but more consistent and feature-rich interface. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state) +{ + + _minlmstate_clear(state); + + ae_assert(n>=1, "MinLMCreateFJ: N<1!", _state); + ae_assert(m>=1, "MinLMCreateFJ: M<1!", _state); + ae_assert(x->cnt>=n, "MinLMCreateFJ: Length(X)teststep = 0; + state->n = n; + state->m = m; + state->algomode = 1; + state->hasf = ae_true; + state->hasfi = ae_false; + state->hasg = ae_false; + + /* + * init 2 + */ + minlm_lmprepare(n, m, ae_true, state, _state); + minlmsetacctype(state, 0, _state); + minlmsetcond(state, 0, 0, 0, 0, _state); + minlmsetxrep(state, ae_false, _state); + minlmsetstpmax(state, 0, _state); + minlmrestartfrom(state, x, _state); +} + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLMOptimize() is called +* prior to actual optimization, for each function Fi and each component + of parameters being optimized X[j] algorithm performs following steps: + * two trial steps are made to X[j]-TestStep*S[j] and X[j]+TestStep*S[j], + where X[j] is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * Fi(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative, + Rep.FuncIdx is set to index of the function. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) Jacobian evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided + by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLMSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minlmsetgradientcheck(minlmstate* state, + double teststep, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(teststep, _state), "MinLMSetGradientCheck: TestStep contains NaN or Infinite", _state); + ae_assert(ae_fp_greater_eq(teststep,0), "MinLMSetGradientCheck: invalid argument TestStep(TestStep<0)", _state); + state->teststep = teststep; +} + + +/************************************************************************* +Prepare internal structures (except for RComm). + +Note: M must be zero for FGH mode, non-zero for V/VJ/FJ/FGJ mode. +*************************************************************************/ +static void minlm_lmprepare(ae_int_t n, + ae_int_t m, + ae_bool havegrad, + minlmstate* state, + ae_state *_state) +{ + ae_int_t i; + + + if( n<=0||m<0 ) + { + return; + } + if( havegrad ) + { + ae_vector_set_length(&state->g, n, _state); + } + if( m!=0 ) + { + ae_matrix_set_length(&state->j, m, n, _state); + ae_vector_set_length(&state->fi, m, _state); + ae_vector_set_length(&state->fibase, m, _state); + ae_vector_set_length(&state->deltaf, m, _state); + ae_vector_set_length(&state->fm1, m, _state); + ae_vector_set_length(&state->fp1, m, _state); + ae_vector_set_length(&state->fc1, m, _state); + ae_vector_set_length(&state->gm1, m, _state); + ae_vector_set_length(&state->gp1, m, _state); + ae_vector_set_length(&state->gc1, m, _state); + } + else + { + ae_matrix_set_length(&state->h, n, n, _state); + } + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->deltax, n, _state); + ae_matrix_set_length(&state->quadraticmodel, n, n, _state); + ae_vector_set_length(&state->xbase, n, _state); + ae_vector_set_length(&state->gbase, n, _state); + ae_vector_set_length(&state->xdir, n, _state); + ae_vector_set_length(&state->tmp0, n, _state); + + /* + * prepare internal L-BFGS + */ + for(i=0; i<=n-1; i++) + { + state->x.ptr.p_double[i] = 0; + } + minlbfgscreate(n, ae_minint(minlm_additers, n, _state), &state->x, &state->internalstate, _state); + minlbfgssetcond(&state->internalstate, 0.0, 0.0, 0.0, ae_minint(minlm_additers, n, _state), _state); + + /* + * Prepare internal QP solver + */ + minqpcreate(n, &state->qpstate, _state); + minqpsetalgocholesky(&state->qpstate, _state); + + /* + * Prepare boundary constraints + */ + ae_vector_set_length(&state->bndl, n, _state); + ae_vector_set_length(&state->bndu, n, _state); + ae_vector_set_length(&state->havebndl, n, _state); + ae_vector_set_length(&state->havebndu, n, _state); + for(i=0; i<=n-1; i++) + { + state->bndl.ptr.p_double[i] = _state->v_neginf; + state->havebndl.ptr.p_bool[i] = ae_false; + state->bndu.ptr.p_double[i] = _state->v_posinf; + state->havebndu.ptr.p_bool[i] = ae_false; + } + + /* + * Prepare scaling matrix + */ + ae_vector_set_length(&state->s, n, _state); + for(i=0; i<=n-1; i++) + { + state->s.ptr.p_double[i] = 1.0; + } +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void minlm_clearrequestfields(minlmstate* state, ae_state *_state) +{ + + + state->needf = ae_false; + state->needfg = ae_false; + state->needfgh = ae_false; + state->needfij = ae_false; + state->needfi = ae_false; + state->xupdated = ae_false; +} + + +/************************************************************************* +Increases lambda, returns False when there is a danger of overflow +*************************************************************************/ +static ae_bool minlm_increaselambda(double* lambdav, + double* nu, + ae_state *_state) +{ + double lnlambda; + double lnnu; + double lnlambdaup; + double lnmax; + ae_bool result; + + + result = ae_false; + lnlambda = ae_log(*lambdav, _state); + lnlambdaup = ae_log(minlm_lambdaup, _state); + lnnu = ae_log(*nu, _state); + lnmax = ae_log(ae_maxrealnumber, _state); + if( ae_fp_greater(lnlambda+lnlambdaup+lnnu,0.25*lnmax) ) + { + return result; + } + if( ae_fp_greater(lnnu+ae_log(2, _state),lnmax) ) + { + return result; + } + *lambdav = *lambdav*minlm_lambdaup*(*nu); + *nu = *nu*2; + result = ae_true; + return result; +} + + +/************************************************************************* +Decreases lambda, but leaves it unchanged when there is danger of underflow. +*************************************************************************/ +static void minlm_decreaselambda(double* lambdav, + double* nu, + ae_state *_state) +{ + + + *nu = 1; + if( ae_fp_less(ae_log(*lambdav, _state)+ae_log(minlm_lambdadown, _state),ae_log(ae_minrealnumber, _state)) ) + { + *lambdav = ae_minrealnumber; + } + else + { + *lambdav = *lambdav*minlm_lambdadown; + } +} + + +/************************************************************************* +Returns norm of bounded scaled anti-gradient. + +Bounded antigradient is a vector obtained from anti-gradient by zeroing +components which point outwards: + result = norm(v) + v[i]=0 if ((-g[i]<0)and(x[i]=bndl[i])) or + ((-g[i]>0)and(x[i]=bndu[i])) + v[i]=-g[i]*s[i] otherwise, where s[i] is a scale for I-th variable + +This function may be used to check a stopping criterion. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +static double minlm_boundedscaledantigradnorm(minlmstate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* g, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double v; + double result; + + + result = 0; + n = state->n; + for(i=0; i<=n-1; i++) + { + v = -g->ptr.p_double[i]*state->s.ptr.p_double[i]; + if( state->havebndl.ptr.p_bool[i] ) + { + if( ae_fp_less_eq(x->ptr.p_double[i],state->bndl.ptr.p_double[i])&&ae_fp_less(-g->ptr.p_double[i],0) ) + { + v = 0; + } + } + if( state->havebndu.ptr.p_bool[i] ) + { + if( ae_fp_greater_eq(x->ptr.p_double[i],state->bndu.ptr.p_double[i])&&ae_fp_greater(-g->ptr.p_double[i],0) ) + { + v = 0; + } + } + result = result+ae_sqr(v, _state); + } + result = ae_sqrt(result, _state); + return result; +} + + +ae_bool _minlmstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minlmstate *p = (minlmstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fi, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->j, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->h, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xbase, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fibase, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gbase, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->quadraticmodel, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->havebndl, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->havebndu, 0, DT_BOOL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->s, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xdir, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->deltax, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->deltaf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->choleskybuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmp0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fm1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fp1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fc1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gm1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gp1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gc1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsstate_init(&p->internalstate, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsreport_init(&p->internalrep, _state, make_automatic) ) + return ae_false; + if( !_minqpstate_init(&p->qpstate, _state, make_automatic) ) + return ae_false; + if( !_minqpreport_init(&p->qprep, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _minlmstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minlmstate *dst = (minlmstate*)_dst; + minlmstate *src = (minlmstate*)_src; + dst->n = src->n; + dst->m = src->m; + dst->diffstep = src->diffstep; + dst->epsg = src->epsg; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->stpmax = src->stpmax; + dst->maxmodelage = src->maxmodelage; + dst->makeadditers = src->makeadditers; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->fi, &src->fi, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->j, &src->j, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->h, &src->h, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + dst->needf = src->needf; + dst->needfg = src->needfg; + dst->needfgh = src->needfgh; + dst->needfij = src->needfij; + dst->needfi = src->needfi; + dst->xupdated = src->xupdated; + dst->algomode = src->algomode; + dst->hasf = src->hasf; + dst->hasfi = src->hasfi; + dst->hasg = src->hasg; + if( !ae_vector_init_copy(&dst->xbase, &src->xbase, _state, make_automatic) ) + return ae_false; + dst->fbase = src->fbase; + if( !ae_vector_init_copy(&dst->fibase, &src->fibase, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gbase, &src->gbase, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->quadraticmodel, &src->quadraticmodel, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->havebndl, &src->havebndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->havebndu, &src->havebndu, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->s, &src->s, _state, make_automatic) ) + return ae_false; + dst->lambdav = src->lambdav; + dst->nu = src->nu; + dst->modelage = src->modelage; + if( !ae_vector_init_copy(&dst->xdir, &src->xdir, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->deltax, &src->deltax, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->deltaf, &src->deltaf, _state, make_automatic) ) + return ae_false; + dst->deltaxready = src->deltaxready; + dst->deltafready = src->deltafready; + dst->teststep = src->teststep; + dst->repiterationscount = src->repiterationscount; + dst->repterminationtype = src->repterminationtype; + dst->repfuncidx = src->repfuncidx; + dst->repvaridx = src->repvaridx; + dst->repnfunc = src->repnfunc; + dst->repnjac = src->repnjac; + dst->repngrad = src->repngrad; + dst->repnhess = src->repnhess; + dst->repncholesky = src->repncholesky; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->choleskybuf, &src->choleskybuf, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmp0, &src->tmp0, _state, make_automatic) ) + return ae_false; + dst->actualdecrease = src->actualdecrease; + dst->predicteddecrease = src->predicteddecrease; + dst->xm1 = src->xm1; + dst->xp1 = src->xp1; + if( !ae_vector_init_copy(&dst->fm1, &src->fm1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->fp1, &src->fp1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->fc1, &src->fc1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gm1, &src->gm1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gp1, &src->gp1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gc1, &src->gc1, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsstate_init_copy(&dst->internalstate, &src->internalstate, _state, make_automatic) ) + return ae_false; + if( !_minlbfgsreport_init_copy(&dst->internalrep, &src->internalrep, _state, make_automatic) ) + return ae_false; + if( !_minqpstate_init_copy(&dst->qpstate, &src->qpstate, _state, make_automatic) ) + return ae_false; + if( !_minqpreport_init_copy(&dst->qprep, &src->qprep, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _minlmstate_clear(void* _p) +{ + minlmstate *p = (minlmstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->fi); + ae_matrix_clear(&p->j); + ae_matrix_clear(&p->h); + ae_vector_clear(&p->g); + ae_vector_clear(&p->xbase); + ae_vector_clear(&p->fibase); + ae_vector_clear(&p->gbase); + ae_matrix_clear(&p->quadraticmodel); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_vector_clear(&p->havebndl); + ae_vector_clear(&p->havebndu); + ae_vector_clear(&p->s); + ae_vector_clear(&p->xdir); + ae_vector_clear(&p->deltax); + ae_vector_clear(&p->deltaf); + _rcommstate_clear(&p->rstate); + ae_vector_clear(&p->choleskybuf); + ae_vector_clear(&p->tmp0); + ae_vector_clear(&p->fm1); + ae_vector_clear(&p->fp1); + ae_vector_clear(&p->fc1); + ae_vector_clear(&p->gm1); + ae_vector_clear(&p->gp1); + ae_vector_clear(&p->gc1); + _minlbfgsstate_clear(&p->internalstate); + _minlbfgsreport_clear(&p->internalrep); + _minqpstate_clear(&p->qpstate); + _minqpreport_clear(&p->qprep); +} + + +void _minlmstate_destroy(void* _p) +{ + minlmstate *p = (minlmstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->fi); + ae_matrix_destroy(&p->j); + ae_matrix_destroy(&p->h); + ae_vector_destroy(&p->g); + ae_vector_destroy(&p->xbase); + ae_vector_destroy(&p->fibase); + ae_vector_destroy(&p->gbase); + ae_matrix_destroy(&p->quadraticmodel); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_vector_destroy(&p->havebndl); + ae_vector_destroy(&p->havebndu); + ae_vector_destroy(&p->s); + ae_vector_destroy(&p->xdir); + ae_vector_destroy(&p->deltax); + ae_vector_destroy(&p->deltaf); + _rcommstate_destroy(&p->rstate); + ae_vector_destroy(&p->choleskybuf); + ae_vector_destroy(&p->tmp0); + ae_vector_destroy(&p->fm1); + ae_vector_destroy(&p->fp1); + ae_vector_destroy(&p->fc1); + ae_vector_destroy(&p->gm1); + ae_vector_destroy(&p->gp1); + ae_vector_destroy(&p->gc1); + _minlbfgsstate_destroy(&p->internalstate); + _minlbfgsreport_destroy(&p->internalrep); + _minqpstate_destroy(&p->qpstate); + _minqpreport_destroy(&p->qprep); +} + + +ae_bool _minlmreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minlmreport *p = (minlmreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _minlmreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minlmreport *dst = (minlmreport*)_dst; + minlmreport *src = (minlmreport*)_src; + dst->iterationscount = src->iterationscount; + dst->terminationtype = src->terminationtype; + dst->funcidx = src->funcidx; + dst->varidx = src->varidx; + dst->nfunc = src->nfunc; + dst->njac = src->njac; + dst->ngrad = src->ngrad; + dst->nhess = src->nhess; + dst->ncholesky = src->ncholesky; + return ae_true; +} + + +void _minlmreport_clear(void* _p) +{ + minlmreport *p = (minlmreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _minlmreport_destroy(void* _p) +{ + minlmreport *p = (minlmreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +Obsolete function, use MinLBFGSSetPrecDefault() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetdefaultpreconditioner(minlbfgsstate* state, + ae_state *_state) +{ + + + minlbfgssetprecdefault(state, _state); +} + + +/************************************************************************* +Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcholeskypreconditioner(minlbfgsstate* state, + /* Real */ ae_matrix* p, + ae_bool isupper, + ae_state *_state) +{ + + + minlbfgssetpreccholesky(state, p, isupper, _state); +} + + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierwidth(minbleicstate* state, + double mu, + ae_state *_state) +{ + + +} + + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierdecay(minbleicstate* state, + double mudecay, + ae_state *_state) +{ + + +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void minasacreate(ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + minasastate* state, + ae_state *_state) +{ + ae_int_t i; + + _minasastate_clear(state); + + ae_assert(n>=1, "MinASA: N too small!", _state); + ae_assert(x->cnt>=n, "MinCGCreate: Length(X)cnt>=n, "MinCGCreate: Length(BndL)cnt>=n, "MinCGCreate: Length(BndU)ptr.p_double[i],bndu->ptr.p_double[i]), "MinASA: inconsistent bounds!", _state); + ae_assert(ae_fp_less_eq(bndl->ptr.p_double[i],x->ptr.p_double[i]), "MinASA: infeasible X!", _state); + ae_assert(ae_fp_less_eq(x->ptr.p_double[i],bndu->ptr.p_double[i]), "MinASA: infeasible X!", _state); + } + + /* + * Initialize + */ + state->n = n; + minasasetcond(state, 0, 0, 0, 0, _state); + minasasetxrep(state, ae_false, _state); + minasasetstpmax(state, 0, _state); + minasasetalgorithm(state, -1, _state); + ae_vector_set_length(&state->bndl, n, _state); + ae_vector_set_length(&state->bndu, n, _state); + ae_vector_set_length(&state->ak, n, _state); + ae_vector_set_length(&state->xk, n, _state); + ae_vector_set_length(&state->dk, n, _state); + ae_vector_set_length(&state->an, n, _state); + ae_vector_set_length(&state->xn, n, _state); + ae_vector_set_length(&state->dn, n, _state); + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->d, n, _state); + ae_vector_set_length(&state->g, n, _state); + ae_vector_set_length(&state->gc, n, _state); + ae_vector_set_length(&state->work, n, _state); + ae_vector_set_length(&state->yk, n, _state); + minasarestartfrom(state, x, bndl, bndu, _state); +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetcond(minasastate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsg, _state), "MinASASetCond: EpsG is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsg,0), "MinASASetCond: negative EpsG!", _state); + ae_assert(ae_isfinite(epsf, _state), "MinASASetCond: EpsF is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "MinASASetCond: negative EpsF!", _state); + ae_assert(ae_isfinite(epsx, _state), "MinASASetCond: EpsX is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsx,0), "MinASASetCond: negative EpsX!", _state); + ae_assert(maxits>=0, "MinASASetCond: negative MaxIts!", _state); + if( ((ae_fp_eq(epsg,0)&&ae_fp_eq(epsf,0))&&ae_fp_eq(epsx,0))&&maxits==0 ) + { + epsx = 1.0E-6; + } + state->epsg = epsg; + state->epsf = epsf; + state->epsx = epsx; + state->maxits = maxits; +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetxrep(minasastate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetalgorithm(minasastate* state, + ae_int_t algotype, + ae_state *_state) +{ + + + ae_assert(algotype>=-1&&algotype<=1, "MinASASetAlgorithm: incorrect AlgoType!", _state); + if( algotype==-1 ) + { + algotype = 1; + } + state->cgtype = algotype; +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetstpmax(minasastate* state, double stpmax, ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "MinASASetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "MinASASetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool minasaiteration(minasastate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double betak; + double v; + double vv; + ae_int_t mcinfo; + ae_bool b; + ae_bool stepfound; + ae_int_t diffcnt; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + i = state->rstate.ia.ptr.p_int[1]; + mcinfo = state->rstate.ia.ptr.p_int[2]; + diffcnt = state->rstate.ia.ptr.p_int[3]; + b = state->rstate.ba.ptr.p_bool[0]; + stepfound = state->rstate.ba.ptr.p_bool[1]; + betak = state->rstate.ra.ptr.p_double[0]; + v = state->rstate.ra.ptr.p_double[1]; + vv = state->rstate.ra.ptr.p_double[2]; + } + else + { + n = -983; + i = -989; + mcinfo = -834; + diffcnt = 900; + b = ae_true; + stepfound = ae_false; + betak = 214; + v = -338; + vv = -686; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + if( state->rstate.stage==8 ) + { + goto lbl_8; + } + if( state->rstate.stage==9 ) + { + goto lbl_9; + } + if( state->rstate.stage==10 ) + { + goto lbl_10; + } + if( state->rstate.stage==11 ) + { + goto lbl_11; + } + if( state->rstate.stage==12 ) + { + goto lbl_12; + } + if( state->rstate.stage==13 ) + { + goto lbl_13; + } + if( state->rstate.stage==14 ) + { + goto lbl_14; + } + + /* + * Routine body + */ + + /* + * Prepare + */ + n = state->n; + state->repterminationtype = 0; + state->repiterationscount = 0; + state->repnfev = 0; + state->debugrestartscount = 0; + state->cgtype = 1; + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + if( ae_fp_eq(state->xk.ptr.p_double[i],state->bndl.ptr.p_double[i])||ae_fp_eq(state->xk.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->ak.ptr.p_double[i] = 0; + } + else + { + state->ak.ptr.p_double[i] = 1; + } + } + state->mu = 0.1; + state->curalgo = 0; + + /* + * Calculate F/G, initialize algorithm + */ + mincomp_clearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needfg = ae_false; + if( !state->xrep ) + { + goto lbl_15; + } + + /* + * progress report + */ + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->xupdated = ae_false; +lbl_15: + if( ae_fp_less_eq(mincomp_asaboundedantigradnorm(state, _state),state->epsg) ) + { + state->repterminationtype = 4; + result = ae_false; + return result; + } + state->repnfev = state->repnfev+1; + + /* + * Main cycle + * + * At the beginning of new iteration: + * * CurAlgo stores current algorithm selector + * * State.XK, State.F and State.G store current X/F/G + * * State.AK stores current set of active constraints + */ +lbl_17: + if( ae_false ) + { + goto lbl_18; + } + + /* + * GPA algorithm + */ + if( state->curalgo!=0 ) + { + goto lbl_19; + } + state->k = 0; + state->acount = 0; +lbl_21: + if( ae_false ) + { + goto lbl_22; + } + + /* + * Determine Dk = proj(xk - gk)-xk + */ + for(i=0; i<=n-1; i++) + { + state->d.ptr.p_double[i] = boundval(state->xk.ptr.p_double[i]-state->g.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state)-state->xk.ptr.p_double[i]; + } + + /* + * Armijo line search. + * * exact search with alpha=1 is tried first, + * 'exact' means that we evaluate f() EXACTLY at + * bound(x-g,bndl,bndu), without intermediate floating + * point operations. + * * alpha<1 are tried if explicit search wasn't successful + * Result is placed into XN. + * + * Two types of search are needed because we can't + * just use second type with alpha=1 because in finite + * precision arithmetics (x1-x0)+x0 may differ from x1. + * So while x1 is correctly bounded (it lie EXACTLY on + * boundary, if it is active), (x1-x0)+x0 may be + * not bounded. + */ + v = ae_v_dotproduct(&state->d.ptr.p_double[0], 1, &state->g.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->dginit = v; + state->finit = state->f; + if( !(ae_fp_less_eq(mincomp_asad1norm(state, _state),state->stpmax)||ae_fp_eq(state->stpmax,0)) ) + { + goto lbl_23; + } + + /* + * Try alpha=1 step first + */ + for(i=0; i<=n-1; i++) + { + state->x.ptr.p_double[i] = boundval(state->xk.ptr.p_double[i]-state->g.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + mincomp_clearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->needfg = ae_false; + state->repnfev = state->repnfev+1; + stepfound = ae_fp_less_eq(state->f,state->finit+mincomp_gpaftol*state->dginit); + goto lbl_24; +lbl_23: + stepfound = ae_false; +lbl_24: + if( !stepfound ) + { + goto lbl_25; + } + + /* + * we are at the boundary(ies) + */ + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->stp = 1; + goto lbl_26; +lbl_25: + + /* + * alpha=1 is too large, try smaller values + */ + state->stp = 1; + linminnormalized(&state->d, &state->stp, n, _state); + state->dginit = state->dginit/state->stp; + state->stp = mincomp_gpadecay*state->stp; + if( ae_fp_greater(state->stpmax,0) ) + { + state->stp = ae_minreal(state->stp, state->stpmax, _state); + } +lbl_27: + if( ae_false ) + { + goto lbl_28; + } + v = state->stp; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->d.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + mincomp_clearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needfg = ae_false; + state->repnfev = state->repnfev+1; + if( ae_fp_less_eq(state->stp,mincomp_stpmin) ) + { + goto lbl_28; + } + if( ae_fp_less_eq(state->f,state->finit+state->stp*mincomp_gpaftol*state->dginit) ) + { + goto lbl_28; + } + state->stp = state->stp*mincomp_gpadecay; + goto lbl_27; +lbl_28: + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); +lbl_26: + state->repiterationscount = state->repiterationscount+1; + if( !state->xrep ) + { + goto lbl_29; + } + + /* + * progress report + */ + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->xupdated = ae_false; +lbl_29: + + /* + * Calculate new set of active constraints. + * Reset counter if active set was changed. + * Prepare for the new iteration + */ + for(i=0; i<=n-1; i++) + { + if( ae_fp_eq(state->xn.ptr.p_double[i],state->bndl.ptr.p_double[i])||ae_fp_eq(state->xn.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->an.ptr.p_double[i] = 0; + } + else + { + state->an.ptr.p_double[i] = 1; + } + } + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(state->ak.ptr.p_double[i],state->an.ptr.p_double[i]) ) + { + state->acount = -1; + break; + } + } + state->acount = state->acount+1; + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->ak.ptr.p_double[0], 1, &state->an.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * Stopping conditions + */ + if( !(state->repiterationscount>=state->maxits&&state->maxits>0) ) + { + goto lbl_31; + } + + /* + * Too many iterations + */ + state->repterminationtype = 5; + if( !state->xrep ) + { + goto lbl_33; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->xupdated = ae_false; +lbl_33: + result = ae_false; + return result; +lbl_31: + if( ae_fp_greater(mincomp_asaboundedantigradnorm(state, _state),state->epsg) ) + { + goto lbl_35; + } + + /* + * Gradient is small enough + */ + state->repterminationtype = 4; + if( !state->xrep ) + { + goto lbl_37; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->xupdated = ae_false; +lbl_37: + result = ae_false; + return result; +lbl_35: + v = ae_v_dotproduct(&state->d.ptr.p_double[0], 1, &state->d.ptr.p_double[0], 1, ae_v_len(0,n-1)); + if( ae_fp_greater(ae_sqrt(v, _state)*state->stp,state->epsx) ) + { + goto lbl_39; + } + + /* + * Step size is too small, no further improvement is + * possible + */ + state->repterminationtype = 2; + if( !state->xrep ) + { + goto lbl_41; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->xupdated = ae_false; +lbl_41: + result = ae_false; + return result; +lbl_39: + if( ae_fp_greater(state->finit-state->f,state->epsf*ae_maxreal(ae_fabs(state->finit, _state), ae_maxreal(ae_fabs(state->f, _state), 1.0, _state), _state)) ) + { + goto lbl_43; + } + + /* + * F(k+1)-F(k) is small enough + */ + state->repterminationtype = 1; + if( !state->xrep ) + { + goto lbl_45; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 8; + goto lbl_rcomm; +lbl_8: + state->xupdated = ae_false; +lbl_45: + result = ae_false; + return result; +lbl_43: + + /* + * Decide - should we switch algorithm or not + */ + if( mincomp_asauisempty(state, _state) ) + { + if( ae_fp_greater_eq(mincomp_asaginorm(state, _state),state->mu*mincomp_asad1norm(state, _state)) ) + { + state->curalgo = 1; + goto lbl_22; + } + else + { + state->mu = state->mu*mincomp_asarho; + } + } + else + { + if( state->acount==mincomp_n1 ) + { + if( ae_fp_greater_eq(mincomp_asaginorm(state, _state),state->mu*mincomp_asad1norm(state, _state)) ) + { + state->curalgo = 1; + goto lbl_22; + } + } + } + + /* + * Next iteration + */ + state->k = state->k+1; + goto lbl_21; +lbl_22: +lbl_19: + + /* + * CG algorithm + */ + if( state->curalgo!=1 ) + { + goto lbl_47; + } + + /* + * first, check that there are non-active constraints. + * move to GPA algorithm, if all constraints are active + */ + b = ae_true; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(state->ak.ptr.p_double[i],0) ) + { + b = ae_false; + break; + } + } + if( b ) + { + state->curalgo = 0; + goto lbl_17; + } + + /* + * CG iterations + */ + state->fold = state->f; + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + state->dk.ptr.p_double[i] = -state->g.ptr.p_double[i]*state->ak.ptr.p_double[i]; + state->gc.ptr.p_double[i] = state->g.ptr.p_double[i]*state->ak.ptr.p_double[i]; + } +lbl_49: + if( ae_false ) + { + goto lbl_50; + } + + /* + * Store G[k] for later calculation of Y[k] + */ + for(i=0; i<=n-1; i++) + { + state->yk.ptr.p_double[i] = -state->gc.ptr.p_double[i]; + } + + /* + * Make a CG step in direction given by DK[]: + * * calculate step. Step projection into feasible set + * is used. It has several benefits: a) step may be + * found with usual line search, b) multiple constraints + * may be activated with one step, c) activated constraints + * are detected in a natural way - just compare x[i] with + * bounds + * * update active set, set B to True, if there + * were changes in the set. + */ + ae_v_move(&state->d.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_move(&state->xn.ptr.p_double[0], 1, &state->xk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->mcstage = 0; + state->stp = 1; + linminnormalized(&state->d, &state->stp, n, _state); + if( ae_fp_neq(state->laststep,0) ) + { + state->stp = state->laststep; + } + mcsrch(n, &state->xn, &state->f, &state->gc, &state->d, &state->stp, state->stpmax, mincomp_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); +lbl_51: + if( state->mcstage==0 ) + { + goto lbl_52; + } + + /* + * preprocess data: bound State.XN so it belongs to the + * feasible set and store it in the State.X + */ + for(i=0; i<=n-1; i++) + { + state->x.ptr.p_double[i] = boundval(state->xn.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + } + + /* + * RComm + */ + mincomp_clearrequestfields(state, _state); + state->needfg = ae_true; + state->rstate.stage = 9; + goto lbl_rcomm; +lbl_9: + state->needfg = ae_false; + + /* + * postprocess data: zero components of G corresponding to + * the active constraints + */ + for(i=0; i<=n-1; i++) + { + if( ae_fp_eq(state->x.ptr.p_double[i],state->bndl.ptr.p_double[i])||ae_fp_eq(state->x.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->gc.ptr.p_double[i] = 0; + } + else + { + state->gc.ptr.p_double[i] = state->g.ptr.p_double[i]; + } + } + mcsrch(n, &state->xn, &state->f, &state->gc, &state->d, &state->stp, state->stpmax, mincomp_gtol, &mcinfo, &state->nfev, &state->work, &state->lstate, &state->mcstage, _state); + goto lbl_51; +lbl_52: + diffcnt = 0; + for(i=0; i<=n-1; i++) + { + + /* + * XN contains unprojected result, project it, + * save copy to X (will be used for progress reporting) + */ + state->xn.ptr.p_double[i] = boundval(state->xn.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state); + + /* + * update active set + */ + if( ae_fp_eq(state->xn.ptr.p_double[i],state->bndl.ptr.p_double[i])||ae_fp_eq(state->xn.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + state->an.ptr.p_double[i] = 0; + } + else + { + state->an.ptr.p_double[i] = 1; + } + if( ae_fp_neq(state->an.ptr.p_double[i],state->ak.ptr.p_double[i]) ) + { + diffcnt = diffcnt+1; + } + state->ak.ptr.p_double[i] = state->an.ptr.p_double[i]; + } + ae_v_move(&state->xk.ptr.p_double[0], 1, &state->xn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->repnfev = state->repnfev+state->nfev; + state->repiterationscount = state->repiterationscount+1; + if( !state->xrep ) + { + goto lbl_53; + } + + /* + * progress report + */ + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 10; + goto lbl_rcomm; +lbl_10: + state->xupdated = ae_false; +lbl_53: + + /* + * Update info about step length + */ + v = ae_v_dotproduct(&state->d.ptr.p_double[0], 1, &state->d.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->laststep = ae_sqrt(v, _state)*state->stp; + + /* + * Check stopping conditions. + */ + if( ae_fp_greater(mincomp_asaboundedantigradnorm(state, _state),state->epsg) ) + { + goto lbl_55; + } + + /* + * Gradient is small enough + */ + state->repterminationtype = 4; + if( !state->xrep ) + { + goto lbl_57; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 11; + goto lbl_rcomm; +lbl_11: + state->xupdated = ae_false; +lbl_57: + result = ae_false; + return result; +lbl_55: + if( !(state->repiterationscount>=state->maxits&&state->maxits>0) ) + { + goto lbl_59; + } + + /* + * Too many iterations + */ + state->repterminationtype = 5; + if( !state->xrep ) + { + goto lbl_61; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 12; + goto lbl_rcomm; +lbl_12: + state->xupdated = ae_false; +lbl_61: + result = ae_false; + return result; +lbl_59: + if( !(ae_fp_greater_eq(mincomp_asaginorm(state, _state),state->mu*mincomp_asad1norm(state, _state))&&diffcnt==0) ) + { + goto lbl_63; + } + + /* + * These conditions (EpsF/EpsX) are explicitly or implicitly + * related to the current step size and influenced + * by changes in the active constraints. + * + * For these reasons they are checked only when we don't + * want to 'unstick' at the end of the iteration and there + * were no changes in the active set. + * + * NOTE: consition |G|>=Mu*|D1| must be exactly opposite + * to the condition used to switch back to GPA. At least + * one inequality must be strict, otherwise infinite cycle + * may occur when |G|=Mu*|D1| (we DON'T test stopping + * conditions and we DON'T switch to GPA, so we cycle + * indefinitely). + */ + if( ae_fp_greater(state->fold-state->f,state->epsf*ae_maxreal(ae_fabs(state->fold, _state), ae_maxreal(ae_fabs(state->f, _state), 1.0, _state), _state)) ) + { + goto lbl_65; + } + + /* + * F(k+1)-F(k) is small enough + */ + state->repterminationtype = 1; + if( !state->xrep ) + { + goto lbl_67; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 13; + goto lbl_rcomm; +lbl_13: + state->xupdated = ae_false; +lbl_67: + result = ae_false; + return result; +lbl_65: + if( ae_fp_greater(state->laststep,state->epsx) ) + { + goto lbl_69; + } + + /* + * X(k+1)-X(k) is small enough + */ + state->repterminationtype = 2; + if( !state->xrep ) + { + goto lbl_71; + } + mincomp_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 14; + goto lbl_rcomm; +lbl_14: + state->xupdated = ae_false; +lbl_71: + result = ae_false; + return result; +lbl_69: +lbl_63: + + /* + * Check conditions for switching + */ + if( ae_fp_less(mincomp_asaginorm(state, _state),state->mu*mincomp_asad1norm(state, _state)) ) + { + state->curalgo = 0; + goto lbl_50; + } + if( diffcnt>0 ) + { + if( mincomp_asauisempty(state, _state)||diffcnt>=mincomp_n2 ) + { + state->curalgo = 1; + } + else + { + state->curalgo = 0; + } + goto lbl_50; + } + + /* + * Calculate D(k+1) + * + * Line search may result in: + * * maximum feasible step being taken (already processed) + * * point satisfying Wolfe conditions + * * some kind of error (CG is restarted by assigning 0.0 to Beta) + */ + if( mcinfo==1 ) + { + + /* + * Standard Wolfe conditions are satisfied: + * * calculate Y[K] and BetaK + */ + ae_v_add(&state->yk.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + vv = ae_v_dotproduct(&state->yk.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + v = ae_v_dotproduct(&state->gc.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->betady = v/vv; + v = ae_v_dotproduct(&state->gc.ptr.p_double[0], 1, &state->yk.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->betahs = v/vv; + if( state->cgtype==0 ) + { + betak = state->betady; + } + if( state->cgtype==1 ) + { + betak = ae_maxreal(0, ae_minreal(state->betady, state->betahs, _state), _state); + } + } + else + { + + /* + * Something is wrong (may be function is too wild or too flat). + * + * We'll set BetaK=0, which will restart CG algorithm. + * We can stop later (during normal checks) if stopping conditions are met. + */ + betak = 0; + state->debugrestartscount = state->debugrestartscount+1; + } + ae_v_moveneg(&state->dn.ptr.p_double[0], 1, &state->gc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->dn.ptr.p_double[0], 1, &state->dk.ptr.p_double[0], 1, ae_v_len(0,n-1), betak); + ae_v_move(&state->dk.ptr.p_double[0], 1, &state->dn.ptr.p_double[0], 1, ae_v_len(0,n-1)); + + /* + * update other information + */ + state->fold = state->f; + state->k = state->k+1; + goto lbl_49; +lbl_50: +lbl_47: + goto lbl_17; +lbl_18: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = i; + state->rstate.ia.ptr.p_int[2] = mcinfo; + state->rstate.ia.ptr.p_int[3] = diffcnt; + state->rstate.ba.ptr.p_bool[0] = b; + state->rstate.ba.ptr.p_bool[1] = stepfound; + state->rstate.ra.ptr.p_double[0] = betak; + state->rstate.ra.ptr.p_double[1] = v; + state->rstate.ra.ptr.p_double[2] = vv; + return result; +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresults(minasastate* state, + /* Real */ ae_vector* x, + minasareport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _minasareport_clear(rep); + + minasaresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresultsbuf(minasastate* state, + /* Real */ ae_vector* x, + minasareport* rep, + ae_state *_state) +{ + ae_int_t i; + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nfev = state->repnfev; + rep->terminationtype = state->repterminationtype; + rep->activeconstraints = 0; + for(i=0; i<=state->n-1; i++) + { + if( ae_fp_eq(state->ak.ptr.p_double[i],0) ) + { + rep->activeconstraints = rep->activeconstraints+1; + } + } +} + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minasarestartfrom(minasastate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state) +{ + + + ae_assert(x->cnt>=state->n, "MinASARestartFrom: Length(X)n, _state), "MinASARestartFrom: X contains infinite or NaN values!", _state); + ae_assert(bndl->cnt>=state->n, "MinASARestartFrom: Length(BndL)n, _state), "MinASARestartFrom: BndL contains infinite or NaN values!", _state); + ae_assert(bndu->cnt>=state->n, "MinASARestartFrom: Length(BndU)n, _state), "MinASARestartFrom: BndU contains infinite or NaN values!", _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_v_move(&state->bndl.ptr.p_double[0], 1, &bndl->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_v_move(&state->bndu.ptr.p_double[0], 1, &bndu->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->laststep = 0; + ae_vector_set_length(&state->rstate.ia, 3+1, _state); + ae_vector_set_length(&state->rstate.ba, 1+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; + mincomp_clearrequestfields(state, _state); +} + + +/************************************************************************* +Returns norm of bounded anti-gradient. + +Bounded antigradient is a vector obtained from anti-gradient by zeroing +components which point outwards: + result = norm(v) + v[i]=0 if ((-g[i]<0)and(x[i]=bndl[i])) or + ((-g[i]>0)and(x[i]=bndu[i])) + v[i]=-g[i] otherwise + +This function may be used to check a stopping criterion. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +static double mincomp_asaboundedantigradnorm(minasastate* state, + ae_state *_state) +{ + ae_int_t i; + double v; + double result; + + + result = 0; + for(i=0; i<=state->n-1; i++) + { + v = -state->g.ptr.p_double[i]; + if( ae_fp_eq(state->x.ptr.p_double[i],state->bndl.ptr.p_double[i])&&ae_fp_less(-state->g.ptr.p_double[i],0) ) + { + v = 0; + } + if( ae_fp_eq(state->x.ptr.p_double[i],state->bndu.ptr.p_double[i])&&ae_fp_greater(-state->g.ptr.p_double[i],0) ) + { + v = 0; + } + result = result+ae_sqr(v, _state); + } + result = ae_sqrt(result, _state); + return result; +} + + +/************************************************************************* +Returns norm of GI(x). + +GI(x) is a gradient vector whose components associated with active +constraints are zeroed. It differs from bounded anti-gradient because +components of GI(x) are zeroed independently of sign(g[i]), and +anti-gradient's components are zeroed with respect to both constraint and +sign. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +static double mincomp_asaginorm(minasastate* state, ae_state *_state) +{ + ae_int_t i; + double result; + + + result = 0; + for(i=0; i<=state->n-1; i++) + { + if( ae_fp_neq(state->x.ptr.p_double[i],state->bndl.ptr.p_double[i])&&ae_fp_neq(state->x.ptr.p_double[i],state->bndu.ptr.p_double[i]) ) + { + result = result+ae_sqr(state->g.ptr.p_double[i], _state); + } + } + result = ae_sqrt(result, _state); + return result; +} + + +/************************************************************************* +Returns norm(D1(State.X)) + +For a meaning of D1 see 'NEW ACTIVE SET ALGORITHM FOR BOX CONSTRAINED +OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +static double mincomp_asad1norm(minasastate* state, ae_state *_state) +{ + ae_int_t i; + double result; + + + result = 0; + for(i=0; i<=state->n-1; i++) + { + result = result+ae_sqr(boundval(state->x.ptr.p_double[i]-state->g.ptr.p_double[i], state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i], _state)-state->x.ptr.p_double[i], _state); + } + result = ae_sqrt(result, _state); + return result; +} + + +/************************************************************************* +Returns True, if U set is empty. + +* State.X is used as point, +* State.G - as gradient, +* D is calculated within function (because State.D may have different + meaning depending on current optimization algorithm) + +For a meaning of U see 'NEW ACTIVE SET ALGORITHM FOR BOX CONSTRAINED +OPTIMIZATION' by WILLIAM W. HAGER AND HONGCHAO ZHANG. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +static ae_bool mincomp_asauisempty(minasastate* state, ae_state *_state) +{ + ae_int_t i; + double d; + double d2; + double d32; + ae_bool result; + + + d = mincomp_asad1norm(state, _state); + d2 = ae_sqrt(d, _state); + d32 = d*d2; + result = ae_true; + for(i=0; i<=state->n-1; i++) + { + if( ae_fp_greater_eq(ae_fabs(state->g.ptr.p_double[i], _state),d2)&&ae_fp_greater_eq(ae_minreal(state->x.ptr.p_double[i]-state->bndl.ptr.p_double[i], state->bndu.ptr.p_double[i]-state->x.ptr.p_double[i], _state),d32) ) + { + result = ae_false; + return result; + } + } + return result; +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void mincomp_clearrequestfields(minasastate* state, + ae_state *_state) +{ + + + state->needfg = ae_false; + state->xupdated = ae_false; +} + + +ae_bool _minasastate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minasastate *p = (minasastate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->bndl, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->bndu, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ak, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->an, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->dn, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->work, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->yk, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->gc, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->g, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !_linminstate_init(&p->lstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _minasastate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minasastate *dst = (minasastate*)_dst; + minasastate *src = (minasastate*)_src; + dst->n = src->n; + dst->epsg = src->epsg; + dst->epsf = src->epsf; + dst->epsx = src->epsx; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->stpmax = src->stpmax; + dst->cgtype = src->cgtype; + dst->k = src->k; + dst->nfev = src->nfev; + dst->mcstage = src->mcstage; + if( !ae_vector_init_copy(&dst->bndl, &src->bndl, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->bndu, &src->bndu, _state, make_automatic) ) + return ae_false; + dst->curalgo = src->curalgo; + dst->acount = src->acount; + dst->mu = src->mu; + dst->finit = src->finit; + dst->dginit = src->dginit; + if( !ae_vector_init_copy(&dst->ak, &src->ak, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xk, &src->xk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dk, &src->dk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->an, &src->an, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->xn, &src->xn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->dn, &src->dn, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + dst->fold = src->fold; + dst->stp = src->stp; + if( !ae_vector_init_copy(&dst->work, &src->work, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->yk, &src->yk, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->gc, &src->gc, _state, make_automatic) ) + return ae_false; + dst->laststep = src->laststep; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->g, &src->g, _state, make_automatic) ) + return ae_false; + dst->needfg = src->needfg; + dst->xupdated = src->xupdated; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->repiterationscount = src->repiterationscount; + dst->repnfev = src->repnfev; + dst->repterminationtype = src->repterminationtype; + dst->debugrestartscount = src->debugrestartscount; + if( !_linminstate_init_copy(&dst->lstate, &src->lstate, _state, make_automatic) ) + return ae_false; + dst->betahs = src->betahs; + dst->betady = src->betady; + return ae_true; +} + + +void _minasastate_clear(void* _p) +{ + minasastate *p = (minasastate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->bndl); + ae_vector_clear(&p->bndu); + ae_vector_clear(&p->ak); + ae_vector_clear(&p->xk); + ae_vector_clear(&p->dk); + ae_vector_clear(&p->an); + ae_vector_clear(&p->xn); + ae_vector_clear(&p->dn); + ae_vector_clear(&p->d); + ae_vector_clear(&p->work); + ae_vector_clear(&p->yk); + ae_vector_clear(&p->gc); + ae_vector_clear(&p->x); + ae_vector_clear(&p->g); + _rcommstate_clear(&p->rstate); + _linminstate_clear(&p->lstate); +} + + +void _minasastate_destroy(void* _p) +{ + minasastate *p = (minasastate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->bndl); + ae_vector_destroy(&p->bndu); + ae_vector_destroy(&p->ak); + ae_vector_destroy(&p->xk); + ae_vector_destroy(&p->dk); + ae_vector_destroy(&p->an); + ae_vector_destroy(&p->xn); + ae_vector_destroy(&p->dn); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->work); + ae_vector_destroy(&p->yk); + ae_vector_destroy(&p->gc); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->g); + _rcommstate_destroy(&p->rstate); + _linminstate_destroy(&p->lstate); +} + + +ae_bool _minasareport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + minasareport *p = (minasareport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _minasareport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + minasareport *dst = (minasareport*)_dst; + minasareport *src = (minasareport*)_src; + dst->iterationscount = src->iterationscount; + dst->nfev = src->nfev; + dst->terminationtype = src->terminationtype; + dst->activeconstraints = src->activeconstraints; + return ae_true; +} + + +void _minasareport_clear(void* _p) +{ + minasareport *p = (minasareport*)_p; + ae_touch_ptr((void*)p); +} + + +void _minasareport_destroy(void* _p) +{ + minasareport *p = (minasareport*)_p; + ae_touch_ptr((void*)p); +} + + + +} + diff --git a/src/inc/alglib/optimization.h b/src/inc/alglib/optimization.h new file mode 100644 index 0000000..eb62e95 --- /dev/null +++ b/src/inc/alglib/optimization.h @@ -0,0 +1,4379 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _optimization_pkg_h +#define _optimization_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "linalg.h" +#include "alglibmisc.h" +#include "solvers.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + ae_int_t n; + ae_int_t k; + double alpha; + double tau; + double theta; + ae_matrix a; + ae_matrix q; + ae_vector b; + ae_vector r; + ae_vector xc; + ae_vector d; + ae_vector activeset; + ae_matrix tq2dense; + ae_matrix tk2; + ae_vector tq2diag; + ae_vector tq1; + ae_vector tk1; + double tq0; + double tk0; + ae_vector txc; + ae_vector tb; + ae_int_t nfree; + ae_int_t ecakind; + ae_matrix ecadense; + ae_matrix eq; + ae_matrix eccm; + ae_vector ecadiag; + ae_vector eb; + double ec; + ae_vector tmp0; + ae_vector tmp1; + ae_vector tmpg; + ae_matrix tmp2; + ae_bool ismaintermchanged; + ae_bool issecondarytermchanged; + ae_bool islineartermchanged; + ae_bool isactivesetchanged; +} convexquadraticmodel; +typedef struct +{ + ae_int_t ns; + ae_int_t nd; + ae_int_t nr; + ae_matrix densea; + ae_vector b; + ae_vector nnc; + ae_int_t refinementits; + double debugflops; + ae_int_t debugmaxnewton; + ae_vector xn; + ae_matrix tmpz; + ae_matrix tmpca; + ae_vector g; + ae_vector d; + ae_vector dx; + ae_vector diagaa; + ae_vector cb; + ae_vector cx; + ae_vector cborg; + ae_vector columnmap; + ae_vector rowmap; + ae_vector tmpcholesky; + ae_vector r; +} snnlssolver; +typedef struct +{ + ae_int_t n; + ae_int_t algostate; + ae_vector xc; + ae_bool hasxc; + ae_vector s; + ae_vector h; + ae_vector activeset; + ae_bool basisisready; + ae_matrix sbasis; + ae_matrix pbasis; + ae_matrix ibasis; + ae_int_t basissize; + ae_bool constraintschanged; + ae_vector hasbndl; + ae_vector hasbndu; + ae_vector bndl; + ae_vector bndu; + ae_matrix cleic; + ae_int_t nec; + ae_int_t nic; + ae_vector mtx; + ae_vector mtas; + ae_vector cdtmp; + ae_vector corrtmp; + ae_vector unitdiagonal; + snnlssolver solver; + ae_vector scntmp; + ae_vector tmp0; + ae_vector tmpfeas; + ae_matrix tmpm0; + ae_vector rctmps; + ae_vector rctmpg; + ae_vector rctmprightpart; + ae_matrix rctmpdense0; + ae_matrix rctmpdense1; + ae_vector rctmpisequality; + ae_vector rctmpconstraintidx; + ae_vector rctmplambdas; + ae_matrix tmpbasis; +} sactiveset; +typedef struct +{ + ae_int_t n; + double epsg; + double epsf; + double epsx; + ae_int_t maxits; + double stpmax; + double suggestedstep; + ae_bool xrep; + ae_bool drep; + ae_int_t cgtype; + ae_int_t prectype; + ae_vector diagh; + ae_vector diaghl2; + ae_matrix vcorr; + ae_int_t vcnt; + ae_vector s; + double diffstep; + ae_int_t nfev; + ae_int_t mcstage; + ae_int_t k; + ae_vector xk; + ae_vector dk; + ae_vector xn; + ae_vector dn; + ae_vector d; + double fold; + double stp; + double curstpmax; + ae_vector yk; + double lastgoodstep; + double lastscaledstep; + ae_int_t mcinfo; + ae_bool innerresetneeded; + ae_bool terminationneeded; + double trimthreshold; + ae_int_t rstimer; + ae_vector x; + double f; + ae_vector g; + ae_bool needf; + ae_bool needfg; + ae_bool xupdated; + ae_bool algpowerup; + ae_bool lsstart; + ae_bool lsend; + double teststep; + rcommstate rstate; + ae_int_t repiterationscount; + ae_int_t repnfev; + ae_int_t repvaridx; + ae_int_t repterminationtype; + ae_int_t debugrestartscount; + linminstate lstate; + double fbase; + double fm2; + double fm1; + double fp1; + double fp2; + double betahs; + double betady; + ae_vector work0; + ae_vector work1; +} mincgstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nfev; + ae_int_t varidx; + ae_int_t terminationtype; +} mincgreport; +typedef struct +{ + ae_int_t nmain; + ae_int_t nslack; + double epsg; + double epsf; + double epsx; + ae_int_t maxits; + ae_bool xrep; + ae_bool drep; + double stpmax; + double diffstep; + sactiveset sas; + ae_vector s; + ae_int_t prectype; + ae_vector diagh; + ae_vector x; + double f; + ae_vector g; + ae_bool needf; + ae_bool needfg; + ae_bool xupdated; + ae_bool lsstart; + ae_bool lbfgssearch; + ae_bool boundedstep; + double teststep; + rcommstate rstate; + ae_vector gc; + ae_vector xn; + ae_vector gn; + ae_vector xp; + ae_vector gp; + double fc; + double fn; + double fp; + ae_vector d; + ae_matrix cleic; + ae_int_t nec; + ae_int_t nic; + double lastgoodstep; + double lastscaledgoodstep; + double maxscaledgrad; + ae_vector hasbndl; + ae_vector hasbndu; + ae_vector bndl; + ae_vector bndu; + ae_int_t repinneriterationscount; + ae_int_t repouteriterationscount; + ae_int_t repnfev; + ae_int_t repvaridx; + ae_int_t repterminationtype; + double repdebugeqerr; + double repdebugfs; + double repdebugff; + double repdebugdx; + ae_int_t repdebugfeasqpits; + ae_int_t repdebugfeasgpaits; + ae_vector xstart; + snnlssolver solver; + double fbase; + double fm2; + double fm1; + double fp1; + double fp2; + double xm1; + double xp1; + double gm1; + double gp1; + ae_int_t cidx; + double cval; + ae_vector tmpprec; + ae_int_t nfev; + ae_int_t mcstage; + double stp; + double curstpmax; + double activationstep; + ae_vector work; + linminstate lstate; + double trimthreshold; + ae_int_t nonmonotoniccnt; + ae_int_t k; + ae_int_t q; + ae_int_t p; + ae_vector rho; + ae_matrix yk; + ae_matrix sk; + ae_vector theta; +} minbleicstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nfev; + ae_int_t varidx; + ae_int_t terminationtype; + double debugeqerr; + double debugfs; + double debugff; + double debugdx; + ae_int_t debugfeasqpits; + ae_int_t debugfeasgpaits; + ae_int_t inneriterationscount; + ae_int_t outeriterationscount; +} minbleicreport; +typedef struct +{ + ae_int_t n; + ae_int_t m; + double epsg; + double epsf; + double epsx; + ae_int_t maxits; + ae_bool xrep; + double stpmax; + ae_vector s; + double diffstep; + ae_int_t nfev; + ae_int_t mcstage; + ae_int_t k; + ae_int_t q; + ae_int_t p; + ae_vector rho; + ae_matrix yk; + ae_matrix sk; + ae_vector theta; + ae_vector d; + double stp; + ae_vector work; + double fold; + double trimthreshold; + ae_int_t prectype; + double gammak; + ae_matrix denseh; + ae_vector diagh; + double fbase; + double fm2; + double fm1; + double fp1; + double fp2; + ae_vector autobuf; + ae_vector x; + double f; + ae_vector g; + ae_bool needf; + ae_bool needfg; + ae_bool xupdated; + double teststep; + rcommstate rstate; + ae_int_t repiterationscount; + ae_int_t repnfev; + ae_int_t repvaridx; + ae_int_t repterminationtype; + linminstate lstate; +} minlbfgsstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nfev; + ae_int_t varidx; + ae_int_t terminationtype; +} minlbfgsreport; +typedef struct +{ + ae_int_t n; + ae_int_t algokind; + ae_int_t akind; + convexquadraticmodel a; + sparsematrix sparsea; + ae_bool sparseaupper; + double anorm; + ae_vector b; + ae_vector bndl; + ae_vector bndu; + ae_vector s; + ae_vector havebndl; + ae_vector havebndu; + ae_vector xorigin; + ae_vector startx; + ae_bool havex; + ae_matrix cleic; + ae_int_t nec; + ae_int_t nic; + double bleicepsg; + double bleicepsf; + double bleicepsx; + ae_int_t bleicmaxits; + sactiveset sas; + ae_vector gc; + ae_vector xn; + ae_vector pg; + ae_vector workbndl; + ae_vector workbndu; + ae_matrix workcleic; + ae_vector xs; + ae_int_t repinneriterationscount; + ae_int_t repouteriterationscount; + ae_int_t repncholesky; + ae_int_t repnmv; + ae_int_t repterminationtype; + double debugphase1flops; + double debugphase2flops; + double debugphase3flops; + ae_vector tmp0; + ae_vector tmp1; + ae_vector tmpb; + ae_vector rctmpg; + ae_vector tmpi; + normestimatorstate estimator; + minbleicstate solver; + minbleicreport solverrep; +} minqpstate; +typedef struct +{ + ae_int_t inneriterationscount; + ae_int_t outeriterationscount; + ae_int_t nmv; + ae_int_t ncholesky; + ae_int_t terminationtype; +} minqpreport; +typedef struct +{ + ae_int_t n; + ae_int_t m; + double diffstep; + double epsg; + double epsf; + double epsx; + ae_int_t maxits; + ae_bool xrep; + double stpmax; + ae_int_t maxmodelage; + ae_bool makeadditers; + ae_vector x; + double f; + ae_vector fi; + ae_matrix j; + ae_matrix h; + ae_vector g; + ae_bool needf; + ae_bool needfg; + ae_bool needfgh; + ae_bool needfij; + ae_bool needfi; + ae_bool xupdated; + ae_int_t algomode; + ae_bool hasf; + ae_bool hasfi; + ae_bool hasg; + ae_vector xbase; + double fbase; + ae_vector fibase; + ae_vector gbase; + ae_matrix quadraticmodel; + ae_vector bndl; + ae_vector bndu; + ae_vector havebndl; + ae_vector havebndu; + ae_vector s; + double lambdav; + double nu; + ae_int_t modelage; + ae_vector xdir; + ae_vector deltax; + ae_vector deltaf; + ae_bool deltaxready; + ae_bool deltafready; + double teststep; + ae_int_t repiterationscount; + ae_int_t repterminationtype; + ae_int_t repfuncidx; + ae_int_t repvaridx; + ae_int_t repnfunc; + ae_int_t repnjac; + ae_int_t repngrad; + ae_int_t repnhess; + ae_int_t repncholesky; + rcommstate rstate; + ae_vector choleskybuf; + ae_vector tmp0; + double actualdecrease; + double predicteddecrease; + double xm1; + double xp1; + ae_vector fm1; + ae_vector fp1; + ae_vector fc1; + ae_vector gm1; + ae_vector gp1; + ae_vector gc1; + minlbfgsstate internalstate; + minlbfgsreport internalrep; + minqpstate qpstate; + minqpreport qprep; +} minlmstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t terminationtype; + ae_int_t funcidx; + ae_int_t varidx; + ae_int_t nfunc; + ae_int_t njac; + ae_int_t ngrad; + ae_int_t nhess; + ae_int_t ncholesky; +} minlmreport; +typedef struct +{ + ae_int_t n; + double epsg; + double epsf; + double epsx; + ae_int_t maxits; + ae_bool xrep; + double stpmax; + ae_int_t cgtype; + ae_int_t k; + ae_int_t nfev; + ae_int_t mcstage; + ae_vector bndl; + ae_vector bndu; + ae_int_t curalgo; + ae_int_t acount; + double mu; + double finit; + double dginit; + ae_vector ak; + ae_vector xk; + ae_vector dk; + ae_vector an; + ae_vector xn; + ae_vector dn; + ae_vector d; + double fold; + double stp; + ae_vector work; + ae_vector yk; + ae_vector gc; + double laststep; + ae_vector x; + double f; + ae_vector g; + ae_bool needfg; + ae_bool xupdated; + rcommstate rstate; + ae_int_t repiterationscount; + ae_int_t repnfev; + ae_int_t repterminationtype; + ae_int_t debugrestartscount; + linminstate lstate; + double betahs; + double betady; +} minasastate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nfev; + ae_int_t terminationtype; + ae_int_t activeconstraints; +} minasareport; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + + + + + + + + +/************************************************************************* +This object stores state of the nonlinear CG optimizer. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +class _mincgstate_owner +{ +public: + _mincgstate_owner(); + _mincgstate_owner(const _mincgstate_owner &rhs); + _mincgstate_owner& operator=(const _mincgstate_owner &rhs); + virtual ~_mincgstate_owner(); + alglib_impl::mincgstate* c_ptr(); + alglib_impl::mincgstate* c_ptr() const; +protected: + alglib_impl::mincgstate *p_struct; +}; +class mincgstate : public _mincgstate_owner +{ +public: + mincgstate(); + mincgstate(const mincgstate &rhs); + mincgstate& operator=(const mincgstate &rhs); + virtual ~mincgstate(); + ae_bool &needf; + ae_bool &needfg; + ae_bool &xupdated; + double &f; + real_1d_array g; + real_1d_array x; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _mincgreport_owner +{ +public: + _mincgreport_owner(); + _mincgreport_owner(const _mincgreport_owner &rhs); + _mincgreport_owner& operator=(const _mincgreport_owner &rhs); + virtual ~_mincgreport_owner(); + alglib_impl::mincgreport* c_ptr(); + alglib_impl::mincgreport* c_ptr() const; +protected: + alglib_impl::mincgreport *p_struct; +}; +class mincgreport : public _mincgreport_owner +{ +public: + mincgreport(); + mincgreport(const mincgreport &rhs); + mincgreport& operator=(const mincgreport &rhs); + virtual ~mincgreport(); + ae_int_t &iterationscount; + ae_int_t &nfev; + ae_int_t &varidx; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +This object stores nonlinear optimizer state. +You should use functions provided by MinBLEIC subpackage to work with this +object +*************************************************************************/ +class _minbleicstate_owner +{ +public: + _minbleicstate_owner(); + _minbleicstate_owner(const _minbleicstate_owner &rhs); + _minbleicstate_owner& operator=(const _minbleicstate_owner &rhs); + virtual ~_minbleicstate_owner(); + alglib_impl::minbleicstate* c_ptr(); + alglib_impl::minbleicstate* c_ptr() const; +protected: + alglib_impl::minbleicstate *p_struct; +}; +class minbleicstate : public _minbleicstate_owner +{ +public: + minbleicstate(); + minbleicstate(const minbleicstate &rhs); + minbleicstate& operator=(const minbleicstate &rhs); + virtual ~minbleicstate(); + ae_bool &needf; + ae_bool &needfg; + ae_bool &xupdated; + double &f; + real_1d_array g; + real_1d_array x; + +}; + + +/************************************************************************* +This structure stores optimization report: +* IterationsCount number of iterations +* NFEV number of gradient evaluations +* TerminationType termination type (see below) + +TERMINATION CODES + +TerminationType field contains completion code, which can be: + -7 gradient verification failed. + See MinBLEICSetGradientCheck() for more information. + -3 inconsistent constraints. Feasible point is + either nonexistent or too hard to find. Try to + restart optimizer with better initial approximation + 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, + X contains best point found so far. + +ADDITIONAL FIELDS + +There are additional fields which can be used for debugging: +* DebugEqErr error in the equality constraints (2-norm) +* DebugFS f, calculated at projection of initial point + to the feasible set +* DebugFF f, calculated at the final point +* DebugDX |X_start-X_final| +*************************************************************************/ +class _minbleicreport_owner +{ +public: + _minbleicreport_owner(); + _minbleicreport_owner(const _minbleicreport_owner &rhs); + _minbleicreport_owner& operator=(const _minbleicreport_owner &rhs); + virtual ~_minbleicreport_owner(); + alglib_impl::minbleicreport* c_ptr(); + alglib_impl::minbleicreport* c_ptr() const; +protected: + alglib_impl::minbleicreport *p_struct; +}; +class minbleicreport : public _minbleicreport_owner +{ +public: + minbleicreport(); + minbleicreport(const minbleicreport &rhs); + minbleicreport& operator=(const minbleicreport &rhs); + virtual ~minbleicreport(); + ae_int_t &iterationscount; + ae_int_t &nfev; + ae_int_t &varidx; + ae_int_t &terminationtype; + double &debugeqerr; + double &debugfs; + double &debugff; + double &debugdx; + ae_int_t &debugfeasqpits; + ae_int_t &debugfeasgpaits; + ae_int_t &inneriterationscount; + ae_int_t &outeriterationscount; + +}; + +/************************************************************************* + +*************************************************************************/ +class _minlbfgsstate_owner +{ +public: + _minlbfgsstate_owner(); + _minlbfgsstate_owner(const _minlbfgsstate_owner &rhs); + _minlbfgsstate_owner& operator=(const _minlbfgsstate_owner &rhs); + virtual ~_minlbfgsstate_owner(); + alglib_impl::minlbfgsstate* c_ptr(); + alglib_impl::minlbfgsstate* c_ptr() const; +protected: + alglib_impl::minlbfgsstate *p_struct; +}; +class minlbfgsstate : public _minlbfgsstate_owner +{ +public: + minlbfgsstate(); + minlbfgsstate(const minlbfgsstate &rhs); + minlbfgsstate& operator=(const minlbfgsstate &rhs); + virtual ~minlbfgsstate(); + ae_bool &needf; + ae_bool &needfg; + ae_bool &xupdated; + double &f; + real_1d_array g; + real_1d_array x; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _minlbfgsreport_owner +{ +public: + _minlbfgsreport_owner(); + _minlbfgsreport_owner(const _minlbfgsreport_owner &rhs); + _minlbfgsreport_owner& operator=(const _minlbfgsreport_owner &rhs); + virtual ~_minlbfgsreport_owner(); + alglib_impl::minlbfgsreport* c_ptr(); + alglib_impl::minlbfgsreport* c_ptr() const; +protected: + alglib_impl::minlbfgsreport *p_struct; +}; +class minlbfgsreport : public _minlbfgsreport_owner +{ +public: + minlbfgsreport(); + minlbfgsreport(const minlbfgsreport &rhs); + minlbfgsreport& operator=(const minlbfgsreport &rhs); + virtual ~minlbfgsreport(); + ae_int_t &iterationscount; + ae_int_t &nfev; + ae_int_t &varidx; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +This object stores nonlinear optimizer state. +You should use functions provided by MinQP subpackage to work with this +object +*************************************************************************/ +class _minqpstate_owner +{ +public: + _minqpstate_owner(); + _minqpstate_owner(const _minqpstate_owner &rhs); + _minqpstate_owner& operator=(const _minqpstate_owner &rhs); + virtual ~_minqpstate_owner(); + alglib_impl::minqpstate* c_ptr(); + alglib_impl::minqpstate* c_ptr() const; +protected: + alglib_impl::minqpstate *p_struct; +}; +class minqpstate : public _minqpstate_owner +{ +public: + minqpstate(); + minqpstate(const minqpstate &rhs); + minqpstate& operator=(const minqpstate &rhs); + virtual ~minqpstate(); + +}; + + +/************************************************************************* +This structure stores optimization report: +* InnerIterationsCount number of inner iterations +* OuterIterationsCount number of outer iterations +* NCholesky number of Cholesky decomposition +* NMV number of matrix-vector products + (only products calculated as part of iterative + process are counted) +* TerminationType completion code (see below) + +Completion codes: +* -5 inappropriate solver was used: + * Cholesky solver for semidefinite or indefinite problems + * Cholesky solver for problems with non-boundary constraints +* -4 BLEIC-QP algorithm found unconstrained direction + of negative curvature (function is unbounded from + below even under constraints), no meaningful + minimum can be found. +* -3 inconsistent constraints (or, maybe, feasible point is + too hard to find). If you are sure that constraints are feasible, + try to restart optimizer with better initial approximation. +* -1 solver error +* 4 successful completion +* 5 MaxIts steps was taken +* 7 stopping conditions are too stringent, + further improvement is impossible, + X contains best point found so far. +*************************************************************************/ +class _minqpreport_owner +{ +public: + _minqpreport_owner(); + _minqpreport_owner(const _minqpreport_owner &rhs); + _minqpreport_owner& operator=(const _minqpreport_owner &rhs); + virtual ~_minqpreport_owner(); + alglib_impl::minqpreport* c_ptr(); + alglib_impl::minqpreport* c_ptr() const; +protected: + alglib_impl::minqpreport *p_struct; +}; +class minqpreport : public _minqpreport_owner +{ +public: + minqpreport(); + minqpreport(const minqpreport &rhs); + minqpreport& operator=(const minqpreport &rhs); + virtual ~minqpreport(); + ae_int_t &inneriterationscount; + ae_int_t &outeriterationscount; + ae_int_t &nmv; + ae_int_t &ncholesky; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +Levenberg-Marquardt optimizer. + +This structure should be created using one of the MinLMCreate???() +functions. You should not access its fields directly; use ALGLIB functions +to work with it. +*************************************************************************/ +class _minlmstate_owner +{ +public: + _minlmstate_owner(); + _minlmstate_owner(const _minlmstate_owner &rhs); + _minlmstate_owner& operator=(const _minlmstate_owner &rhs); + virtual ~_minlmstate_owner(); + alglib_impl::minlmstate* c_ptr(); + alglib_impl::minlmstate* c_ptr() const; +protected: + alglib_impl::minlmstate *p_struct; +}; +class minlmstate : public _minlmstate_owner +{ +public: + minlmstate(); + minlmstate(const minlmstate &rhs); + minlmstate& operator=(const minlmstate &rhs); + virtual ~minlmstate(); + ae_bool &needf; + ae_bool &needfg; + ae_bool &needfgh; + ae_bool &needfi; + ae_bool &needfij; + ae_bool &xupdated; + double &f; + real_1d_array fi; + real_1d_array g; + real_2d_array h; + real_2d_array j; + real_1d_array x; + +}; + + +/************************************************************************* +Optimization report, filled by MinLMResults() function + +FIELDS: +* TerminationType, completetion code: + * -7 derivative correctness check failed; + see Rep.WrongNum, Rep.WrongI, Rep.WrongJ for + more information. + * 1 relative function improvement is no more than + EpsF. + * 2 relative step is no more than EpsX. + * 4 gradient is no more than EpsG. + * 5 MaxIts steps was taken + * 7 stopping conditions are too stringent, + further improvement is impossible +* IterationsCount, contains iterations count +* NFunc, number of function calculations +* NJac, number of Jacobi matrix calculations +* NGrad, number of gradient calculations +* NHess, number of Hessian calculations +* NCholesky, number of Cholesky decomposition calculations +*************************************************************************/ +class _minlmreport_owner +{ +public: + _minlmreport_owner(); + _minlmreport_owner(const _minlmreport_owner &rhs); + _minlmreport_owner& operator=(const _minlmreport_owner &rhs); + virtual ~_minlmreport_owner(); + alglib_impl::minlmreport* c_ptr(); + alglib_impl::minlmreport* c_ptr() const; +protected: + alglib_impl::minlmreport *p_struct; +}; +class minlmreport : public _minlmreport_owner +{ +public: + minlmreport(); + minlmreport(const minlmreport &rhs); + minlmreport& operator=(const minlmreport &rhs); + virtual ~minlmreport(); + ae_int_t &iterationscount; + ae_int_t &terminationtype; + ae_int_t &funcidx; + ae_int_t &varidx; + ae_int_t &nfunc; + ae_int_t &njac; + ae_int_t &ngrad; + ae_int_t &nhess; + ae_int_t &ncholesky; + +}; + +/************************************************************************* + +*************************************************************************/ +class _minasastate_owner +{ +public: + _minasastate_owner(); + _minasastate_owner(const _minasastate_owner &rhs); + _minasastate_owner& operator=(const _minasastate_owner &rhs); + virtual ~_minasastate_owner(); + alglib_impl::minasastate* c_ptr(); + alglib_impl::minasastate* c_ptr() const; +protected: + alglib_impl::minasastate *p_struct; +}; +class minasastate : public _minasastate_owner +{ +public: + minasastate(); + minasastate(const minasastate &rhs); + minasastate& operator=(const minasastate &rhs); + virtual ~minasastate(); + ae_bool &needfg; + ae_bool &xupdated; + double &f; + real_1d_array g; + real_1d_array x; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _minasareport_owner +{ +public: + _minasareport_owner(); + _minasareport_owner(const _minasareport_owner &rhs); + _minasareport_owner& operator=(const _minasareport_owner &rhs); + virtual ~_minasareport_owner(); + alglib_impl::minasareport* c_ptr(); + alglib_impl::minasareport* c_ptr() const; +protected: + alglib_impl::minasareport *p_struct; +}; +class minasareport : public _minasareport_owner +{ +public: + minasareport(); + minasareport(const minasareport &rhs); + minasareport& operator=(const minasareport &rhs); + virtual ~minasareport(); + ae_int_t &iterationscount; + ae_int_t &nfev; + ae_int_t &terminationtype; + ae_int_t &activeconstraints; + +}; + + + + + + + + + +/************************************************************************* + NONLINEAR CONJUGATE GRADIENT METHOD + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using one of the +nonlinear conjugate gradient methods. + +These CG methods are globally convergent (even on non-convex functions) as +long as grad(f) is Lipschitz continuous in a some neighborhood of the +L = { x : f(x)<=f(x0) }. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinCGCreate() call +2. User tunes solver parameters with MinCGSetCond(), MinCGSetStpMax() and + other functions +3. User calls MinCGOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinCGResults() to get solution +5. Optionally, user may call MinCGRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinCGRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgcreate(const ae_int_t n, const real_1d_array &x, mincgstate &state); +void mincgcreate(const real_1d_array &x, mincgstate &state); + + +/************************************************************************* +The subroutine is finite difference variant of MinCGCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinCGCreate() in order to get more +information about creation of CG optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinCGSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. L-BFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgcreatef(const ae_int_t n, const real_1d_array &x, const double diffstep, mincgstate &state); +void mincgcreatef(const real_1d_array &x, const double diffstep, mincgstate &state); + + +/************************************************************************* +This function sets stopping conditions for CG optimization algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinCGSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcond(const mincgstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +This function sets scaling coefficients for CG 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 + +Scaling is also used by finite difference variant of CG optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the CG too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinCGSetPrec...() functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void mincgsetscale(const mincgstate &state, const real_1d_array &s); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetxrep(const mincgstate &state, const bool needxrep); + + +/************************************************************************* +This function sets CG algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + CGType - algorithm type: + * -1 automatic selection of the best algorithm + * 0 DY (Dai and Yuan) algorithm + * 1 Hybrid DY-HS algorithm + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetcgtype(const mincgstate &state, const ae_int_t cgtype); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetstpmax(const mincgstate &state, const double stpmax); + + +/************************************************************************* +This function allows to suggest initial step length to the CG algorithm. + +Suggested step length is used as starting point for the line search. It +can be useful when you have badly scaled problem, i.e. when ||grad|| +(which is used as initial estimate for the first step) is many orders of +magnitude different from the desired step. + +Line search may fail on such problems without good estimate of initial +step length. Imagine, for example, problem with ||grad||=10^50 and desired +step equal to 0.1 Line search function will use 10^50 as initial step, +then it will decrease step length by 2 (up to 20 attempts) and will get +10^44, which is still too large. + +This function allows us to tell than line search should be started from +some moderate step length, like 1.0, so algorithm will be able to detect +desired step length in a several searches. + +Default behavior (when no step is suggested) is to use preconditioner, if +it is available, to generate initial estimate of step length. + +This function influences only first iteration of algorithm. It should be +called between MinCGCreate/MinCGRestartFrom() call and MinCGOptimize call. +Suggested step is ignored if you have preconditioner. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + Stp - initial estimate of the step length. + Can be zero (no estimate). + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsuggeststep(const mincgstate &state, const double stp); + + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdefault(const mincgstate &state); + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecdiag(const mincgstate &state, const real_1d_array &d); + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinCGSetScale() call +(before or after MinCGSetPrecScale() call). Without knowledge of the scale +of your variables scale-based preconditioner will be just unit matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgsetprecscale(const mincgstate &state); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool mincgiteration(const mincgstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear optimizer + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinCGCreate() for analytical gradient or MinCGCreateF() for + numerical differentiation) you should choose appropriate variant of + MinCGOptimize() - one which accepts function AND gradient or one which + accepts function ONLY. + + Be careful to choose variant of MinCGOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinCGOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinCGOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinCGCreateF() | work FAIL + MinCGCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinCGOptimize() version. Attemps to use such combination + (for example, to create optimizer with MinCGCreateF() and to pass + gradient information to MinCGOptimize()) will lead to exception being + thrown. Either you did not pass gradient when it WAS needed or you + passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey + +*************************************************************************/ +void mincgoptimize(mincgstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void mincgoptimize(mincgstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +Conjugate gradient results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinCGSetGradientCheck() for more information. + * 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, + we return best X found so far + * 8 terminated by user + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresults(const mincgstate &state, real_1d_array &x, mincgreport &rep); + + +/************************************************************************* +Conjugate gradient results + +Buffered implementation of MinCGResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.04.2009 by Bochkanov Sergey +*************************************************************************/ +void mincgresultsbuf(const mincgstate &state, real_1d_array &x, mincgreport &rep); + + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void mincgrestartfrom(const mincgstate &state, const real_1d_array &x); + + +/************************************************************************* + +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinCGOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinCGSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 31.05.2012 by Bochkanov Sergey +*************************************************************************/ +void mincgsetgradientcheck(const mincgstate &state, const double teststep); + +/************************************************************************* + BOUND CONSTRAINED OPTIMIZATION + WITH ADDITIONAL LINEAR EQUALITY AND INEQUALITY CONSTRAINTS + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments subject to any +combination of: +* bound constraints +* linear inequality constraints +* linear equality constraints + +REQUIREMENTS: +* user must provide function value and gradient +* starting point X0 must be feasible or + not too far away from the feasible set +* grad(f) must be Lipschitz continuous on a level set: + L = { x : f(x)<=f(x0) } +* function must be defined everywhere on the feasible set F + +USAGE: + +Constrained optimization if far more complex than the unconstrained one. +Here we give very brief outline of the BLEIC optimizer. We strongly recommend +you to read examples in the ALGLIB Reference Manual and to read ALGLIB User Guide +on optimization, which is available at http://www.alglib.net/optimization/ + +1. User initializes algorithm state with MinBLEICCreate() call + +2. USer adds boundary and/or linear constraints by calling + MinBLEICSetBC() and MinBLEICSetLC() functions. + +3. User sets stopping conditions with MinBLEICSetCond(). + +4. User calls MinBLEICOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. + +5. User calls MinBLEICResults() to get solution + +6. Optionally user may call MinBLEICRestartFrom() to solve another problem + with same N but another starting point. + MinBLEICRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size ofX + X - starting point, array[N]: + * it is better to set X to a feasible point + * but X can be infeasible, in which case algorithm will try + to find feasible point first, using X as initial + approximation. + +OUTPUT PARAMETERS: + State - structure stores algorithm state + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreate(const ae_int_t n, const real_1d_array &x, minbleicstate &state); +void minbleiccreate(const real_1d_array &x, minbleicstate &state); + + +/************************************************************************* +The subroutine is finite difference variant of MinBLEICCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinBLEICCreate() in order to get +more information about creation of BLEIC optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinBLEICSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. CG needs exact gradient values. Imprecise + gradient may slow down convergence, especially on highly nonlinear + problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleiccreatef(const ae_int_t n, const real_1d_array &x, const double diffstep, minbleicstate &state); +void minbleiccreatef(const real_1d_array &x, const double diffstep, minbleicstate &state); + + +/************************************************************************* +This function sets boundary constraints for BLEIC optimizer. + +Boundary constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + If some (all) variables are unbounded, you may specify + very small number or -INF. + BndU - upper bounds, array[N]. + If some (all) variables are unbounded, you may specify + very large number or +INF. + +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: this solver has following useful properties: +* bound constraints are always satisfied exactly +* function is evaluated only INSIDE area specified by bound constraints, + even when numerical differentiation is used (algorithm adjusts nodes + according to boundary constraints) + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbc(const minbleicstate &state, const real_1d_array &bndl, const real_1d_array &bndu); + + +/************************************************************************* +This function sets linear constraints for BLEIC optimizer. + +Linear constraints are inactive by default (after initial creation). +They are preserved after algorithm restart with MinBLEICRestartFrom(). + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately: +* there always exists some minor violation (about Epsilon in magnitude) + due to rounding errors +* numerical differentiation, if used, may lead to function evaluations + outside of the feasible area, because algorithm does NOT change + numerical differentiation formula according to linear constraints. +If you want constraints to be satisfied exactly, try to reformulate your +problem in such manner that all constraints will become boundary ones +(this kind of constraints is always satisfied exactly, both in the final +solution and in all intermediate points). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetlc(const minbleicstate &state, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k); +void minbleicsetlc(const minbleicstate &state, const real_2d_array &c, const integer_1d_array &ct); + + +/************************************************************************* +This function sets stopping conditions for the optimizer. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinBLEICSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection. + +NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver may perform + slightly more than MaxIts iterations. I.e., MaxIts sets non-strict + limit on iterations count. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetcond(const minbleicstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +This function sets scaling coefficients for BLEIC 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the BLEIC too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinBLEICSetPrec...() +functions. + +There is a special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetscale(const minbleicstate &state, const real_1d_array &s); + + +/************************************************************************* +Modification of the preconditioner: preconditioning is turned off. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdefault(const minbleicstate &state); + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE 1: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 2: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecdiag(const minbleicstate &state, const real_1d_array &d); + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinBLEICSetScale() +call (before or after MinBLEICSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetprecscale(const minbleicstate &state); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinBLEICOptimize(). + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetxrep(const minbleicstate &state, const bool needxrep); + + +/************************************************************************* +This function sets maximum step length + +IMPORTANT: this feature is hard to combine with preconditioning. You can't +set upper limit on step length, when you solve optimization problem with +linear (non-boundary) constraints AND preconditioner turned on. + +When non-boundary constraints are present, you have to either a) use +preconditioner, or b) use upper limit on step length. YOU CAN'T USE BOTH! +In this case algorithm will terminate with appropriate error code. + +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 lead 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetstpmax(const minbleicstate &state, const double stpmax); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minbleiciteration(const minbleicstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear optimizer + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinBLEICCreate() for analytical gradient or MinBLEICCreateF() + for numerical differentiation) you should choose appropriate variant of + MinBLEICOptimize() - one which accepts function AND gradient or one + which accepts function ONLY. + + Be careful to choose variant of MinBLEICOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinBLEICOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinBLEICOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinBLEICCreateF() | work FAIL + MinBLEICCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinBLEICOptimize() version. Attemps to use such + combination (for example, to create optimizer with MinBLEICCreateF() + and to pass gradient information to MinCGOptimize()) will lead to + exception being thrown. Either you did not pass gradient when it WAS + needed or you passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey + +*************************************************************************/ +void minbleicoptimize(minbleicstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minbleicoptimize(minbleicstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +BLEIC results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report. You should check Rep.TerminationType + in order to distinguish successful termination from + unsuccessful one: + * -7 gradient verification failed. + See MinBLEICSetGradientCheck() for more information. + * -3 inconsistent constraints. Feasible point is + either nonexistent or too hard to find. Try to + restart optimizer with better initial approximation + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + More information about fields of this structure can be + found in the comments on MinBLEICReport datatype. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresults(const minbleicstate &state, real_1d_array &x, minbleicreport &rep); + + +/************************************************************************* +BLEIC results + +Buffered implementation of MinBLEICResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicresultsbuf(const minbleicstate &state, real_1d_array &x, minbleicreport &rep); + + +/************************************************************************* +This subroutine restarts algorithm from new point. +All optimization parameters (including constraints) are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure previously allocated with MinBLEICCreate call. + X - new starting point. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicrestartfrom(const minbleicstate &state, const real_1d_array &x); + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinBLEICOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinBLEICSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetgradientcheck(const minbleicstate &state, const double teststep); + +/************************************************************************* + LIMITED MEMORY BFGS METHOD FOR LARGE SCALE OPTIMIZATION + +DESCRIPTION: +The subroutine minimizes function F(x) of N arguments by using a quasi- +Newton method (LBFGS scheme) which is optimized to use a minimum amount +of memory. +The subroutine generates the approximation of an inverse Hessian matrix by +using information about the last M steps of the algorithm (instead of N). +It lessens a required amount of memory from a value of order N^2 to a +value of order 2*N*M. + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function value F and its gradient G (simultaneously) at given point X + + +USAGE: +1. User initializes algorithm state with MinLBFGSCreate() call +2. User tunes solver parameters with MinLBFGSSetCond() MinLBFGSSetStpMax() + and other functions +3. User calls MinLBFGSOptimize() function which takes algorithm state and + pointer (delegate, etc.) to callback function which calculates F/G. +4. User calls MinLBFGSResults() to get solution +5. Optionally user may call MinLBFGSRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLBFGSRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - problem dimension. N>0 + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - initial solution approximation, array[0..N-1]. + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with MinLBFGSSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLBFGSSetStpMax() function to bound algorithm's steps. However, + L-BFGS rarely needs such a tuning. + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreate(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlbfgsstate &state); +void minlbfgscreate(const ae_int_t m, const real_1d_array &x, minlbfgsstate &state); + + +/************************************************************************* +The subroutine is finite difference variant of MinLBFGSCreate(). It uses +finite differences in order to differentiate target function. + +Description below contains information which is specific to this function +only. We recommend to read comments on MinLBFGSCreate() in order to get +more information about creation of LBFGS optimizer. + +INPUT PARAMETERS: + N - problem dimension, N>0: + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of corrections in the BFGS scheme of Hessian + approximation update. Recommended value: 3<=M<=7. The smaller + value causes worse convergence, the bigger will not cause a + considerably better convergence, but will cause a fall in the + performance. M<=N. + X - starting point, array[0..N-1]. + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. algorithm uses 4-point central formula for differentiation. +2. differentiation step along I-th axis is equal to DiffStep*S[I] where + S[] is scaling vector which can be set by MinLBFGSSetScale() call. +3. we recommend you to use moderate values of differentiation step. Too + large step will result in too large truncation errors, while too small + step will result in too large numerical errors. 1.0E-6 can be good + value to start with. +4. Numerical differentiation is very inefficient - one gradient + calculation needs 4*N function evaluations. This function will work for + any N - either small (1...10), moderate (10...100) or large (100...). + However, performance penalty will be too severe for any N's except for + small ones. + We should also say that code which relies on numerical differentiation + is less robust and precise. LBFGS needs exact gradient values. + Imprecise gradient may slow down convergence, especially on highly + nonlinear problems. + Thus we recommend to use this function for fast prototyping on small- + dimensional problems only, and to implement analytical gradient as soon + as possible. + + -- ALGLIB -- + Copyright 16.05.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgscreatef(const ae_int_t n, const ae_int_t m, const real_1d_array &x, const double diffstep, minlbfgsstate &state); +void minlbfgscreatef(const ae_int_t m, const real_1d_array &x, const double diffstep, minlbfgsstate &state); + + +/************************************************************************* +This function sets stopping conditions for L-BFGS optimization algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLBFGSSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcond(const minlbfgsstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLBFGSOptimize(). + + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetxrep(const minlbfgsstate &state, const bool needxrep); + + +/************************************************************************* +This function sets maximum step length + +INPUT PARAMETERS: + State - structure which stores algorithm state + StpMax - maximum step length, >=0. Set StpMax to 0.0 (default), 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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetstpmax(const minlbfgsstate &state, const double stpmax); + + +/************************************************************************* +This function sets scaling coefficients for LBFGS 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 + +Scaling is also used by finite difference variant of the optimizer - step +along I-th axis is equal to DiffStep*S[I]. + +In most optimizers (and in the LBFGS too) scaling is NOT a form of +preconditioning. It just affects stopping conditions. You should set +preconditioner by separate call to one of the MinLBFGSSetPrec...() +functions. + +There is special preconditioning mode, however, which uses scaling +coefficients to form diagonal preconditioning matrix. You can turn this +mode on, if you want. But you should understand that scaling is not the +same thing as preconditioning - these are two different, although related +forms of tuning solver. + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetscale(const minlbfgsstate &state, const real_1d_array &s); + + +/************************************************************************* +Modification of the preconditioner: default preconditioner (simple +scaling, same for all elements of X) is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdefault(const minlbfgsstate &state); + + +/************************************************************************* +Modification of the preconditioner: Cholesky factorization of approximate +Hessian is used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + P - triangular preconditioner, Cholesky factorization of + the approximate Hessian. array[0..N-1,0..N-1], + (if larger, only leading N elements are used). + IsUpper - whether upper or lower triangle of P is given + (other triangle is not referenced) + +After call to this function preconditioner is changed to P (P is copied +into the internal buffer). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: P should be nonsingular. Exception will be thrown otherwise. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetpreccholesky(const minlbfgsstate &state, const real_2d_array &p, const bool isupper); + + +/************************************************************************* +Modification of the preconditioner: diagonal of approximate Hessian is +used. + +INPUT PARAMETERS: + State - structure which stores algorithm state + D - diagonal of the approximate Hessian, array[0..N-1], + (if larger, only leading N elements are used). + +NOTE: you can change preconditioner "on the fly", during algorithm +iterations. + +NOTE 2: D[i] should be positive. Exception will be thrown otherwise. + +NOTE 3: you should pass diagonal of approximate Hessian - NOT ITS INVERSE. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecdiag(const minlbfgsstate &state, const real_1d_array &d); + + +/************************************************************************* +Modification of the preconditioner: scale-based diagonal preconditioning. + +This preconditioning mode can be useful when you don't have approximate +diagonal of Hessian, but you know that your variables are badly scaled +(for example, one variable is in [1,10], and another in [1000,100000]), +and most part of the ill-conditioning comes from different scales of vars. + +In this case simple scale-based preconditioner, with H[i] = 1/(s[i]^2), +can greatly improve convergence. + +IMPRTANT: you should set scale of your variables with MinLBFGSSetScale() +call (before or after MinLBFGSSetPrecScale() call). Without knowledge of +the scale of your variables scale-based preconditioner will be just unit +matrix. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetprecscale(const minlbfgsstate &state); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minlbfgsiteration(const minlbfgsstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear optimizer + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + +NOTES: + +1. This function has two different implementations: one which uses exact + (analytical) user-supplied gradient, and one which uses function value + only and numerically differentiates function in order to obtain + gradient. + + Depending on the specific function used to create optimizer object + (either MinLBFGSCreate() for analytical gradient or MinLBFGSCreateF() + for numerical differentiation) you should choose appropriate variant of + MinLBFGSOptimize() - one which accepts function AND gradient or one + which accepts function ONLY. + + Be careful to choose variant of MinLBFGSOptimize() which corresponds to + your optimization scheme! Table below lists different combinations of + callback (function/gradient) passed to MinLBFGSOptimize() and specific + function used to create optimizer. + + + | USER PASSED TO MinLBFGSOptimize() + CREATED WITH | function only | function and gradient + ------------------------------------------------------------ + MinLBFGSCreateF() | work FAIL + MinLBFGSCreate() | FAIL work + + Here "FAIL" denotes inappropriate combinations of optimizer creation + function and MinLBFGSOptimize() version. Attemps to use such + combination (for example, to create optimizer with MinLBFGSCreateF() and + to pass gradient information to MinCGOptimize()) will lead to exception + being thrown. Either you did not pass gradient when it WAS needed or + you passed gradient when it was NOT needed. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey + +*************************************************************************/ +void minlbfgsoptimize(minlbfgsstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minlbfgsoptimize(minlbfgsstate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +L-BFGS algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -7 gradient verification failed. + See MinLBFGSSetGradientCheck() for more information. + * -2 rounding errors prevent further improvement. + X contains best point found. + * -1 incorrect parameters were specified + * 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 + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresults(const minlbfgsstate &state, real_1d_array &x, minlbfgsreport &rep); + + +/************************************************************************* +L-BFGS algorithm results + +Buffered implementation of MinLBFGSResults which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsresultsbuf(const minlbfgsstate &state, real_1d_array &x, minlbfgsreport &rep); + + +/************************************************************************* +This subroutine restarts LBFGS algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used to store algorithm state + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgsrestartfrom(const minlbfgsstate &state, const real_1d_array &x); + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLBFGSOptimize() is called +* prior to actual optimization, for each component of parameters being + optimized X[i] algorithm performs following steps: + * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i], + where X[i] is i-th component of the initial point and S[i] is a scale + of i-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * F(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) gradient evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided by + some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLBFGSSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 24.05.2012 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetgradientcheck(const minlbfgsstate &state, const double teststep); + +/************************************************************************* + CONSTRAINED QUADRATIC PROGRAMMING + +The subroutine creates QP optimizer. After initial creation, it contains +default optimization problem with zero quadratic and linear terms and no +constraints. You should set quadratic/linear terms with calls to functions +provided by MinQP subpackage. + +INPUT PARAMETERS: + N - problem size + +OUTPUT PARAMETERS: + State - optimizer with zero quadratic/linear terms + and no constraints + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpcreate(const ae_int_t n, minqpstate &state); + + +/************************************************************************* +This function sets linear term for QP solver. + +By default, linear term is zero. + +INPUT PARAMETERS: + State - structure which stores algorithm state + B - linear term, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlinearterm(const minqpstate &state, const real_1d_array &b); + + +/************************************************************************* +This function sets dense quadratic term for QP solver. By default, +quadratic term is zero. + +SUPPORT BY ALGLIB QP ALGORITHMS: + +Dense quadratic term can be handled by any of the QP algorithms supported +by ALGLIB QP Solver. + +IMPORTANT: + +This solver minimizes following function: + f(x) = 0.5*x'*A*x + b'*x. +Note that quadratic term has 0.5 before it. So if you want to minimize + f(x) = x^2 + x +you should rewrite your problem as follows: + f(x) = 0.5*(2*x^2) + x +and your matrix A will be equal to [[2.0]], not to [[1.0]] + +INPUT PARAMETERS: + State - structure which stores algorithm state + A - matrix, array[N,N] + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used + * if not given, both lower and upper triangles must be + filled. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadraticterm(const minqpstate &state, const real_2d_array &a, const bool isupper); +void minqpsetquadraticterm(const minqpstate &state, const real_2d_array &a); + + +/************************************************************************* +This function sets sparse quadratic term for QP solver. By default, +quadratic term is zero. + +SUPPORT BY ALGLIB QP ALGORITHMS: + +Sparse quadratic term is supported only by BLEIC-based QP algorithm (one +which is activated by MinQPSetAlgoBLEIC function). Cholesky-based QP algo +won't be able to deal with sparse quadratic term and will terminate +abnormally. + +IF YOU CALLED THIS FUNCTION, YOU MUST SWITCH TO BLEIC-BASED QP ALGORITHM +BEFORE CALLING MINQPOPTIMIZE() FUNCTION. + +IMPORTANT: + +This solver minimizes following function: + f(x) = 0.5*x'*A*x + b'*x. +Note that quadratic term has 0.5 before it. So if you want to minimize + f(x) = x^2 + x +you should rewrite your problem as follows: + f(x) = 0.5*(2*x^2) + x +and your matrix A will be equal to [[2.0]], not to [[1.0]] + +INPUT PARAMETERS: + State - structure which stores algorithm state + A - matrix, array[N,N] + IsUpper - (optional) storage type: + * if True, symmetric matrix A is given by its upper + triangle, and the lower triangle isn’t used + * if False, symmetric matrix A is given by its lower + triangle, and the upper triangle isn’t used + * if not given, both lower and upper triangles must be + filled. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetquadratictermsparse(const minqpstate &state, const sparsematrix &a, const bool isupper); + + +/************************************************************************* +This function sets starting point for QP solver. It is useful to have +good initial approximation to the solution, because it will increase +speed of convergence and identification of active constraints. + +INPUT PARAMETERS: + State - structure which stores algorithm state + X - starting point, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetstartingpoint(const minqpstate &state, const real_1d_array &x); + + +/************************************************************************* +This function sets origin for QP solver. By default, following QP program +is solved: + + min(0.5*x'*A*x+b'*x) + +This function allows to solve different problem: + + min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin)) + +INPUT PARAMETERS: + State - structure which stores algorithm state + XOrigin - origin, array[N]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetorigin(const minqpstate &state, const real_1d_array &xorigin); + + +/************************************************************************* +This function sets scaling coefficients. + +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 + +BLEIC-based QP solver uses scale for two purposes: +* to evaluate stopping conditions +* for preconditioning of the underlying BLEIC solver + +INPUT PARAMETERS: + State - structure stores algorithm state + S - array[N], non-zero scaling coefficients + S[i] may be negative, sign doesn't matter. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetscale(const minqpstate &state, const real_1d_array &s); + + +/************************************************************************* +This function tells solver to use Cholesky-based algorithm. This algorithm +is active by default. + +DESCRIPTION: + +Cholesky-based algorithm can be used only for problems which: +* have dense quadratic term, set by MinQPSetQuadraticTerm(), sparse or + structured problems are not supported. +* are strictly convex, i.e. quadratic term is symmetric positive definite, + indefinite or semidefinite problems are not supported by this algorithm. + +If anything of what listed above is violated, you may use BLEIC-based QP +algorithm which can be activated by MinQPSetAlgoBLEIC(). + +BENEFITS AND DRAWBACKS: + +This algorithm gives best precision amongst all QP solvers provided by +ALGLIB (Newton iterations have much higher precision than any other +optimization algorithm). This solver also gracefully handles problems with +very large amount of constraints. + +Performance of the algorithm is good because internally it uses Level 3 +Dense BLAS for its performance-critical parts. + + +From the other side, algorithm has O(N^3) complexity for unconstrained +problems and up to orders of magnitude slower on constrained problems +(these additional iterations are needed to identify active constraints). +So, its running time depends on number of constraints active at solution. + +Furthermore, this algorithm can not solve problems with sparse matrices or +problems with semidefinite/indefinite matrices of any kind (dense/sparse). + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgocholesky(const minqpstate &state); + + +/************************************************************************* +This function tells solver to use BLEIC-based algorithm and sets stopping +criteria for the algorithm. + +DESCRIPTION: + +BLEIC-based QP algorithm can be used for any kind of QP problems: +* problems with both dense and sparse quadratic terms +* problems with positive definite, semidefinite, indefinite terms + +BLEIC-based algorithm can solve even indefinite problems - as long as they +are bounded from below on the feasible set. Of course, global minimum is +found only for positive definite and semidefinite problems. As for +indefinite ones - only local minimum is found. + +BENEFITS AND DRAWBACKS: + +This algorithm can be used to solve both convex and indefinite QP problems +and it can utilize sparsity of the quadratic term (algorithm calculates +matrix-vector products, which can be performed efficiently in case of +sparse matrix). + +Algorithm has iteration cost, which (assuming fixed amount of non-boundary +linear constraints) linearly depends on problem size. Boundary constraints +does not significantly change iteration cost. + +Thus, it outperforms Cholesky-based QP algorithm (CQP) on high-dimensional +sparse problems with moderate amount of constraints. + + +From the other side, unlike CQP solver, this algorithm does NOT make use +of Level 3 Dense BLAS. Thus, its performance on dense problems is inferior +to that of CQP solver. + +Its precision is also inferior to that of CQP. CQP performs Newton steps +which are know to achieve very good precision. In many cases Newton step +leads us exactly to the solution. BLEIC-QP performs LBFGS steps, which are +good at detecting neighborhood of the solution, buy need many iterations +to find solution with 6 digits of precision. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + EpsX - >=0 + The subroutine finishes its work if exploratory steepest + descent step on k+1-th iteration satisfies following + condition: + * |.| means Euclidian norm + * v - scaled step vector, v[i]=dx[i]/s[i] + * dx - step vector, dx=X(k+1)-X(k) + * s - scaling coefficients set by MinQPSetScale() + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead +to automatic stopping criterion selection (presently it is small step +length, but it may change in the future versions of ALGLIB). + +IT IS VERY IMPORTANT THAT YOU CALL MinQPSetScale() WHEN YOU USE THIS ALGO! + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetalgobleic(const minqpstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +This function sets boundary constraints for QP solver + +Boundary constraints are inactive by default (after initial creation). +After being set, they are preserved until explicitly turned off with +another SetBC() call. + +INPUT PARAMETERS: + State - structure stores algorithm state + BndL - lower bounds, array[N]. + 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[N]. + 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: 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]. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpsetbc(const minqpstate &state, const real_1d_array &bndl, const real_1d_array &bndu); + + +/************************************************************************* +This function sets linear constraints for QP optimizer. + +Linear constraints are inactive by default (after initial creation). + +INPUT PARAMETERS: + State - structure previously allocated with MinQPCreate call. + C - linear constraints, array[K,N+1]. + Each row of C represents one constraint, either equality + or inequality (see below): + * first N elements correspond to coefficients, + * last element corresponds to the right part. + All elements of C (including right part) must be finite. + CT - type of constraints, array[K]: + * if CT[i]>0, then I-th constraint is C[i,*]*x >= C[i,n+1] + * if CT[i]=0, then I-th constraint is C[i,*]*x = C[i,n+1] + * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n+1] + K - number of equality/inequality constraints, K>=0: + * if given, only leading K elements of C/CT are used + * if not given, automatically determined from sizes of C/CT + +NOTE 1: linear (non-bound) constraints are satisfied only approximately - + there always exists some minor violation (about 10^-10...10^-13) + due to numerical errors. + + -- ALGLIB -- + Copyright 19.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minqpsetlc(const minqpstate &state, const real_2d_array &c, const integer_1d_array &ct, const ae_int_t k); +void minqpsetlc(const minqpstate &state, const real_2d_array &c, const integer_1d_array &ct); + + +/************************************************************************* +This function solves quadratic programming problem. +You should call it after setting solver options with MinQPSet...() calls. + +INPUT PARAMETERS: + State - algorithm state + +You should use MinQPResults() function to access results after calls +to this function. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey. + Special thanks to Elvira Illarionova for important suggestions on + the linearly constrained QP algorithm. +*************************************************************************/ +void minqpoptimize(const minqpstate &state); + + +/************************************************************************* +QP solver results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution. + This array is allocated and initialized only when + Rep.TerminationType parameter is positive (success). + Rep - optimization report. You should check Rep.TerminationType, + which contains completion code, and you may check another + fields which contain another information about algorithm + functioning. + + Failure codes returned by algorithm are: + * -5 inappropriate solver was used: + * Cholesky solver for (semi)indefinite problems + * Cholesky solver for problems with sparse matrix + * -4 BLEIC-QP algorithm found unconstrained direction + of negative curvature (function is unbounded from + below even under constraints), no meaningful + minimum can be found. + * -3 inconsistent constraints (or maybe feasible point + is too hard to find). If you are sure that + constraints are feasible, try to restart optimizer + with better initial approximation. + + Completion codes specific for Cholesky algorithm: + * 4 successful completion + + Completion codes specific for BLEIC-based algorithm: + * 1 relative function improvement is no more than EpsF. + * 2 scaled step is no more than EpsX. + * 4 scaled gradient norm is no more than EpsG. + * 5 MaxIts steps was taken + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresults(const minqpstate &state, real_1d_array &x, minqpreport &rep); + + +/************************************************************************* +QP results + +Buffered implementation of MinQPResults() which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 11.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minqpresultsbuf(const minqpstate &state, real_1d_array &x, minqpreport &rep); + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] and Jacobian of f[]. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function vector f[] at given point X +* function vector f[] and Jacobian of f[] (simultaneously) at given point + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() and jac() callbacks. +First one is used to calculate f[] at given point, second one calculates +f[] and Jacobian df[i]/dx[j]. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not provide Jacobian), but it +will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateVJ() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state); +void minlmcreatevj(const ae_int_t m, const real_1d_array &x, minlmstate &state); + + +/************************************************************************* + IMPROVED LEVENBERG-MARQUARDT METHOD FOR + NON-LINEAR LEAST SQUARES OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of function which is represented as +sum of squares: + F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1]) +using value of function vector f[] only. Finite differences are used to +calculate Jacobian. + + +REQUIREMENTS: +This algorithm will request following information during its operation: +* function vector f[] at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts fvec() callback. + +You can try to initialize MinLMState structure with VJ function and then +use incorrect version of MinLMOptimize() (for example, version which +works with general form function and does not accept function vector), but +it will lead to exception being thrown after first attempt to calculate +Jacobian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateV() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N/M but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + M - number of functions f[i] + X - initial solution, array[0..N-1] + DiffStep- differentiation step, >0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +See also MinLMIteration, MinLMResults. + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatev(const ae_int_t n, const ae_int_t m, const real_1d_array &x, const double diffstep, minlmstate &state); +void minlmcreatev(const ae_int_t m, const real_1d_array &x, const double diffstep, minlmstate &state); + + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE METHOD FOR NON-LINEAR OPTIMIZATION + +DESCRIPTION: +This function is used to find minimum of general form (not "sum-of- +-squares") function + F = F(x[0], ..., x[n-1]) +using its gradient and Hessian. Levenberg-Marquardt modification with +L-BFGS pre-optimization and internal pre-conditioned L-BFGS optimization +after each Levenberg-Marquardt step is used. + + +REQUIREMENTS: +This algorithm will request following information during its operation: + +* function value F at given point X +* F and gradient G (simultaneously) at given point X +* F, G and Hessian H (simultaneously) at given point X + +There are several overloaded versions of MinLMOptimize() function which +correspond to different LM-like optimization algorithms provided by this +unit. You should choose version which accepts func(), grad() and hess() +function pointers. First pointer is used to calculate F at given point, +second one calculates F(x) and grad F(x), third one calculates F(x), +grad F(x), hess F(x). + +You can try to initialize MinLMState structure with FGH-function and then +use incorrect version of MinLMOptimize() (for example, version which does +not provide Hessian matrix), but it will lead to exception being thrown +after first attempt to calculate Hessian. + + +USAGE: +1. User initializes algorithm state with MinLMCreateFGH() call +2. User tunes solver parameters with MinLMSetCond(), MinLMSetStpMax() and + other functions +3. User calls MinLMOptimize() function which takes algorithm state and + pointers (delegates, etc.) to callback functions. +4. User calls MinLMResults() to get solution +5. Optionally, user may call MinLMRestartFrom() to solve another problem + with same N but another starting point and/or another function. + MinLMRestartFrom() allows to reuse already initialized structure. + + +INPUT PARAMETERS: + N - dimension, N>1 + * if given, only leading N elements of X are used + * if not given, automatically determined from size of X + X - initial solution, array[0..N-1] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +1. you may tune stopping conditions with MinLMSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use MinLMSetStpMax() function to bound algorithm's steps. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgh(const ae_int_t n, const real_1d_array &x, minlmstate &state); +void minlmcreatefgh(const real_1d_array &x, minlmstate &state); + + +/************************************************************************* +This function sets stopping conditions for Levenberg-Marquardt optimization +algorithm. + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsG - >=0 + The subroutine finishes its work if the condition + |v|=0 + The subroutine finishes its work if on k+1-th iteration + the condition |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1} + is satisfied. + 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 MinLMSetScale() + 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). + +Passing EpsG=0, EpsF=0, EpsX=0 and MaxIts=0 (simultaneously) will lead to +automatic stopping criterion selection (small EpsX). + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetcond(const minlmstate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinLMOptimize(). Both Levenberg-Marquardt and internal L-BFGS +iterations are reported. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetxrep(const minlmstate &state, const bool needxrep); + + +/************************************************************************* +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. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetstpmax(const minlmstate &state, const double stpmax); + + +/************************************************************************* +This function sets scaling coefficients for LM 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. + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetscale(const minlmstate &state, const real_1d_array &s); + + +/************************************************************************* +This function sets boundary constraints for LM 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[N]. + 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[N]. + 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: this solver has following useful properties: +* bound constraints are always satisfied exactly +* function is evaluated only INSIDE area specified by bound constraints + or at its boundary + + -- ALGLIB -- + Copyright 14.01.2011 by Bochkanov Sergey +*************************************************************************/ +void minlmsetbc(const minlmstate &state, const real_1d_array &bndl, const real_1d_array &bndu); + + +/************************************************************************* +This function is used to change acceleration settings + +You can choose between three acceleration strategies: +* AccType=0, no acceleration. +* AccType=1, secant updates are used to update quadratic model after each + iteration. After fixed number of iterations (or after model breakdown) + we recalculate quadratic model using analytic Jacobian or finite + differences. Number of secant-based iterations depends on optimization + settings: about 3 iterations - when we have analytic Jacobian, up to 2*N + iterations - when we use finite differences to calculate Jacobian. + +AccType=1 is recommended when Jacobian calculation cost is prohibitive +high (several Mx1 function vector calculations followed by several NxN +Cholesky factorizations are faster than calculation of one M*N Jacobian). +It should also be used when we have no Jacobian, because finite difference +approximation takes too much time to compute. + +Table below list optimization protocols (XYZ protocol corresponds to +MinLMCreateXYZ) and acceleration types they support (and use by default). + +ACCELERATION TYPES SUPPORTED BY OPTIMIZATION PROTOCOLS: + +protocol 0 1 comment +V + + +VJ + + +FGH + + +DAFAULT VALUES: + +protocol 0 1 comment +V x without acceleration it is so slooooooooow +VJ x +FGH x + +NOTE: this function should be called before optimization. Attempt to call +it during algorithm iterations may result in unexpected behavior. + +NOTE: attempt to call this function with unsupported protocol/acceleration +combination will result in exception being thrown. + + -- ALGLIB -- + Copyright 14.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmsetacctype(const minlmstate &state, const ae_int_t acctype); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minlmiteration(const minlmstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear optimizer + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + hess - callback which calculates function (or merit function) + value func, gradient grad and Hessian hess at given point x + fvec - callback which calculates function vector fi[] + at given point x + jac - callback which calculates function vector fi[] + and Jacobian jac at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + +NOTES: + +1. Depending on function used to create state structure, this algorithm + may accept Jacobian and/or Hessian and/or gradient. 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 + MinLMCreateFGH() (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. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey + +*************************************************************************/ +void minlmoptimize(minlmstate &state, + void (*fvec)(const real_1d_array &x, real_1d_array &fi, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minlmoptimize(minlmstate &state, + void (*fvec)(const real_1d_array &x, real_1d_array &fi, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*hess)(const real_1d_array &x, double &func, real_1d_array &grad, real_2d_array &hess, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); +void minlmoptimize(minlmstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +Levenberg-Marquardt algorithm results + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report; + see comments for this structure for more info. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresults(const minlmstate &state, real_1d_array &x, minlmreport &rep); + + +/************************************************************************* +Levenberg-Marquardt algorithm results + +Buffered implementation of MinLMResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 10.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmresultsbuf(const minlmstate &state, real_1d_array &x, minlmreport &rep); + + +/************************************************************************* +This subroutine restarts LM algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinLMCreateXXX call. + X - new starting point. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minlmrestartfrom(const minlmstate &state, const real_1d_array &x); + + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateVJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatevgj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state); +void minlmcreatevgj(const ae_int_t m, const real_1d_array &x, minlmstate &state); + + +/************************************************************************* +This is obsolete function. + +Since ALGLIB 3.3 it is equivalent to MinLMCreateFJ(). + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefgj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state); +void minlmcreatefgj(const ae_int_t m, const real_1d_array &x, minlmstate &state); + + +/************************************************************************* +This function is considered obsolete since ALGLIB 3.1.0 and is present for +backward compatibility only. We recommend to use MinLMCreateVJ, which +provides similar, but more consistent and feature-rich interface. + + -- ALGLIB -- + Copyright 30.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minlmcreatefj(const ae_int_t n, const ae_int_t m, const real_1d_array &x, minlmstate &state); +void minlmcreatefj(const ae_int_t m, const real_1d_array &x, minlmstate &state); + + +/************************************************************************* +This subroutine turns on verification of the user-supplied analytic +gradient: +* user calls this subroutine before optimization begins +* MinLMOptimize() is called +* prior to actual optimization, for each function Fi and each component + of parameters being optimized X[j] algorithm performs following steps: + * two trial steps are made to X[j]-TestStep*S[j] and X[j]+TestStep*S[j], + where X[j] is j-th parameter and S[j] is a scale of j-th parameter + * if needed, steps are bounded with respect to constraints on X[] + * Fi(X) is evaluated at these trial points + * we perform one more evaluation in the middle point of the interval + * we build cubic model using function values and derivatives at trial + points and we compare its prediction with actual value in the middle + point + * in case difference between prediction and actual value is higher than + some predetermined threshold, algorithm stops with completion code -7; + Rep.VarIdx is set to index of the parameter with incorrect derivative, + Rep.FuncIdx is set to index of the function. +* after verification is over, algorithm proceeds to the actual optimization. + +NOTE 1: verification needs N (parameters count) Jacobian evaluations. It + is very costly and you should use it only for low dimensional + problems, when you want to be sure that you've correctly + calculated analytic derivatives. You should not use it in the + production code (unless you want to check derivatives provided + by some third party). + +NOTE 2: you should carefully choose TestStep. Value which is too large + (so large that function behaviour is significantly non-cubic) will + lead to false alarms. You may use different step for different + parameters by means of setting scale with MinLMSetScale(). + +NOTE 3: this function may lead to false positives. In case it reports that + I-th derivative was calculated incorrectly, you may decrease test + step and try one more time - maybe your function changes too + sharply and your step is too large for such rapidly chanding + function. + +INPUT PARAMETERS: + State - structure used to store algorithm state + TestStep - verification step: + * TestStep=0 turns verification off + * TestStep>0 activates verification + + -- ALGLIB -- + Copyright 15.06.2012 by Bochkanov Sergey +*************************************************************************/ +void minlmsetgradientcheck(const minlmstate &state, const double teststep); + +/************************************************************************* +Obsolete function, use MinLBFGSSetPrecDefault() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetdefaultpreconditioner(const minlbfgsstate &state); + + +/************************************************************************* +Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead. + + -- ALGLIB -- + Copyright 13.10.2010 by Bochkanov Sergey +*************************************************************************/ +void minlbfgssetcholeskypreconditioner(const minlbfgsstate &state, const real_2d_array &p, const bool isupper); + + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierwidth(const minbleicstate &state, const double mu); + + +/************************************************************************* +This is obsolete function which was used by previous version of the BLEIC +optimizer. It does nothing in the current version of BLEIC. + + -- ALGLIB -- + Copyright 28.11.2010 by Bochkanov Sergey +*************************************************************************/ +void minbleicsetbarrierdecay(const minbleicstate &state, const double mudecay); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 25.03.2010 by Bochkanov Sergey +*************************************************************************/ +void minasacreate(const ae_int_t n, const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu, minasastate &state); +void minasacreate(const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu, minasastate &state); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetcond(const minasastate &state, const double epsg, const double epsf, const double epsx, const ae_int_t maxits); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetxrep(const minasastate &state, const bool needxrep); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetalgorithm(const minasastate &state, const ae_int_t algotype); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 02.04.2010 by Bochkanov Sergey +*************************************************************************/ +void minasasetstpmax(const minasastate &state, const double stpmax); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool minasaiteration(const minasastate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear optimizer + +These functions accept following parameters: + state - algorithm state + grad - callback which calculates function (or merit function) + value func and gradient grad at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey + +*************************************************************************/ +void minasaoptimize(minasastate &state, + void (*grad)(const real_1d_array &x, double &func, real_1d_array &grad, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresults(const minasastate &state, real_1d_array &x, minasareport &rep); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +void minasaresultsbuf(const minasastate &state, real_1d_array &x, minasareport &rep); + + +/************************************************************************* +Obsolete optimization algorithm. +Was replaced by MinBLEIC subpackage. + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void minasarestartfrom(const minasastate &state, const real_1d_array &x, const real_1d_array &bndl, const real_1d_array &bndu); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void trimprepare(double f, double* threshold, ae_state *_state); +void trimfunction(double* f, + /* Real */ ae_vector* g, + ae_int_t n, + double threshold, + ae_state *_state); +ae_bool enforceboundaryconstraints(/* Real */ ae_vector* x, + /* Real */ ae_vector* bl, + /* Boolean */ ae_vector* havebl, + /* Real */ ae_vector* bu, + /* Boolean */ ae_vector* havebu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state); +void projectgradientintobc(/* Real */ ae_vector* x, + /* Real */ ae_vector* g, + /* Real */ ae_vector* bl, + /* Boolean */ ae_vector* havebl, + /* Real */ ae_vector* bu, + /* Boolean */ ae_vector* havebu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state); +void calculatestepbound(/* Real */ ae_vector* x, + /* Real */ ae_vector* d, + double alpha, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_int_t* variabletofreeze, + double* valuetofreeze, + double* maxsteplen, + ae_state *_state); +ae_int_t postprocessboundedstep(/* Real */ ae_vector* x, + /* Real */ ae_vector* xprev, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_int_t variabletofreeze, + double valuetofreeze, + double steptaken, + double maxsteplen, + ae_state *_state); +void filterdirection(/* Real */ ae_vector* d, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + /* Real */ ae_vector* s, + ae_int_t nmain, + ae_int_t nslack, + double droptol, + ae_state *_state); +ae_int_t numberofchangedconstraints(/* Real */ ae_vector* x, + /* Real */ ae_vector* xprev, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + ae_state *_state); +ae_bool findfeasiblepoint(/* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Boolean */ ae_vector* havebndl, + /* Real */ ae_vector* bndu, + /* Boolean */ ae_vector* havebndu, + ae_int_t nmain, + ae_int_t nslack, + /* Real */ ae_matrix* ce, + ae_int_t k, + double epsi, + ae_int_t* qpits, + ae_int_t* gpaits, + ae_state *_state); +ae_bool derivativecheck(double f0, + double df0, + double f1, + double df1, + double f, + double df, + double width, + ae_state *_state); +void cqminit(ae_int_t n, convexquadraticmodel* s, ae_state *_state); +void cqmseta(convexquadraticmodel* s, + /* Real */ ae_matrix* a, + ae_bool isupper, + double alpha, + ae_state *_state); +void cqmrewritedensediagonal(convexquadraticmodel* s, + /* Real */ ae_vector* z, + ae_state *_state); +void cqmsetd(convexquadraticmodel* s, + /* Real */ ae_vector* d, + double tau, + ae_state *_state); +void cqmdropa(convexquadraticmodel* s, ae_state *_state); +void cqmsetb(convexquadraticmodel* s, + /* Real */ ae_vector* b, + ae_state *_state); +void cqmsetq(convexquadraticmodel* s, + /* Real */ ae_matrix* q, + /* Real */ ae_vector* r, + ae_int_t k, + double theta, + ae_state *_state); +void cqmsetactiveset(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Boolean */ ae_vector* activeset, + ae_state *_state); +double cqmeval(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +void cqmevalx(convexquadraticmodel* s, + /* Real */ ae_vector* x, + double* r, + double* noise, + ae_state *_state); +void cqmgradunconstrained(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* g, + ae_state *_state); +double cqmxtadx2(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +void cqmadx(convexquadraticmodel* s, + /* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_state *_state); +ae_bool cqmconstrainedoptimum(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +void cqmscalevector(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +double cqmdebugconstrainedevalt(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +double cqmdebugconstrainedevale(convexquadraticmodel* s, + /* Real */ ae_vector* x, + ae_state *_state); +ae_bool _convexquadraticmodel_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _convexquadraticmodel_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _convexquadraticmodel_clear(void* _p); +void _convexquadraticmodel_destroy(void* _p); +void snnlsinit(ae_int_t nsmax, + ae_int_t ndmax, + ae_int_t nrmax, + snnlssolver* s, + ae_state *_state); +void snnlssetproblem(snnlssolver* s, + /* Real */ ae_matrix* a, + /* Real */ ae_vector* b, + ae_int_t ns, + ae_int_t nd, + ae_int_t nr, + ae_state *_state); +void snnlsdropnnc(snnlssolver* s, ae_int_t idx, ae_state *_state); +void snnlssolve(snnlssolver* s, + /* Real */ ae_vector* x, + ae_state *_state); +ae_bool _snnlssolver_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _snnlssolver_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _snnlssolver_clear(void* _p); +void _snnlssolver_destroy(void* _p); +void sasinit(ae_int_t n, sactiveset* s, ae_state *_state); +void sassetscale(sactiveset* state, + /* Real */ ae_vector* s, + ae_state *_state); +void sassetprecdiag(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state); +void sassetbc(sactiveset* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +void sassetlc(sactiveset* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state); +void sassetlcx(sactiveset* state, + /* Real */ ae_matrix* cleic, + ae_int_t nec, + ae_int_t nic, + ae_state *_state); +ae_bool sasstartoptimization(sactiveset* state, + /* Real */ ae_vector* x, + ae_state *_state); +void sasexploredirection(sactiveset* state, + /* Real */ ae_vector* d, + double* stpmax, + ae_int_t* cidx, + double* vval, + ae_state *_state); +ae_int_t sasmoveto(sactiveset* state, + /* Real */ ae_vector* xn, + ae_bool needact, + ae_int_t cidx, + double cval, + ae_state *_state); +void sasimmediateactivation(sactiveset* state, + ae_int_t cidx, + double cval, + ae_state *_state); +void sasconstraineddescent(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* d, + ae_state *_state); +void sasconstraineddescentprec(sactiveset* state, + /* Real */ ae_vector* g, + /* Real */ ae_vector* d, + ae_state *_state); +void sasconstraineddirection(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state); +void sasconstraineddirectionprec(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state); +void sascorrection(sactiveset* state, + /* Real */ ae_vector* x, + double* penalty, + ae_state *_state); +double sasactivelcpenalty1(sactiveset* state, + /* Real */ ae_vector* x, + ae_state *_state); +double sasscaledconstrainednorm(sactiveset* state, + /* Real */ ae_vector* d, + ae_state *_state); +void sasstopoptimization(sactiveset* state, ae_state *_state); +void sasreactivateconstraints(sactiveset* state, + /* Real */ ae_vector* gc, + ae_state *_state); +void sasreactivateconstraintsprec(sactiveset* state, + /* Real */ ae_vector* gc, + ae_state *_state); +void sasrebuildbasis(sactiveset* state, ae_state *_state); +ae_bool _sactiveset_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _sactiveset_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _sactiveset_clear(void* _p); +void _sactiveset_destroy(void* _p); +void mincgcreate(ae_int_t n, + /* Real */ ae_vector* x, + mincgstate* state, + ae_state *_state); +void mincgcreatef(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + mincgstate* state, + ae_state *_state); +void mincgsetcond(mincgstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void mincgsetscale(mincgstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void mincgsetxrep(mincgstate* state, ae_bool needxrep, ae_state *_state); +void mincgsetdrep(mincgstate* state, ae_bool needdrep, ae_state *_state); +void mincgsetcgtype(mincgstate* state, ae_int_t cgtype, ae_state *_state); +void mincgsetstpmax(mincgstate* state, double stpmax, ae_state *_state); +void mincgsuggeststep(mincgstate* state, double stp, ae_state *_state); +void mincgsetprecdefault(mincgstate* state, ae_state *_state); +void mincgsetprecdiag(mincgstate* state, + /* Real */ ae_vector* d, + ae_state *_state); +void mincgsetprecscale(mincgstate* state, ae_state *_state); +ae_bool mincgiteration(mincgstate* state, ae_state *_state); +void mincgresults(mincgstate* state, + /* Real */ ae_vector* x, + mincgreport* rep, + ae_state *_state); +void mincgresultsbuf(mincgstate* state, + /* Real */ ae_vector* x, + mincgreport* rep, + ae_state *_state); +void mincgrestartfrom(mincgstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void mincgsetprecdiagfast(mincgstate* state, + /* Real */ ae_vector* d, + ae_state *_state); +void mincgsetpreclowrankfast(mincgstate* state, + /* Real */ ae_vector* d1, + /* Real */ ae_vector* c, + /* Real */ ae_matrix* v, + ae_int_t vcnt, + ae_state *_state); +void mincgsetprecvarpart(mincgstate* state, + /* Real */ ae_vector* d2, + ae_state *_state); +void mincgsetgradientcheck(mincgstate* state, + double teststep, + ae_state *_state); +ae_bool _mincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mincgstate_clear(void* _p); +void _mincgstate_destroy(void* _p); +ae_bool _mincgreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _mincgreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _mincgreport_clear(void* _p); +void _mincgreport_destroy(void* _p); +void minbleiccreate(ae_int_t n, + /* Real */ ae_vector* x, + minbleicstate* state, + ae_state *_state); +void minbleiccreatef(ae_int_t n, + /* Real */ ae_vector* x, + double diffstep, + minbleicstate* state, + ae_state *_state); +void minbleicsetbc(minbleicstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +void minbleicsetlc(minbleicstate* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state); +void minbleicsetcond(minbleicstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void minbleicsetscale(minbleicstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void minbleicsetprecdefault(minbleicstate* state, ae_state *_state); +void minbleicsetprecdiag(minbleicstate* state, + /* Real */ ae_vector* d, + ae_state *_state); +void minbleicsetprecscale(minbleicstate* state, ae_state *_state); +void minbleicsetxrep(minbleicstate* state, + ae_bool needxrep, + ae_state *_state); +void minbleicsetdrep(minbleicstate* state, + ae_bool needdrep, + ae_state *_state); +void minbleicsetstpmax(minbleicstate* state, + double stpmax, + ae_state *_state); +ae_bool minbleiciteration(minbleicstate* state, ae_state *_state); +void minbleicresults(minbleicstate* state, + /* Real */ ae_vector* x, + minbleicreport* rep, + ae_state *_state); +void minbleicresultsbuf(minbleicstate* state, + /* Real */ ae_vector* x, + minbleicreport* rep, + ae_state *_state); +void minbleicrestartfrom(minbleicstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void minbleicemergencytermination(minbleicstate* state, ae_state *_state); +void minbleicsetgradientcheck(minbleicstate* state, + double teststep, + ae_state *_state); +ae_bool _minbleicstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minbleicstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minbleicstate_clear(void* _p); +void _minbleicstate_destroy(void* _p); +ae_bool _minbleicreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minbleicreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minbleicreport_clear(void* _p); +void _minbleicreport_destroy(void* _p); +void minlbfgscreate(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlbfgsstate* state, + ae_state *_state); +void minlbfgscreatef(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + double diffstep, + minlbfgsstate* state, + ae_state *_state); +void minlbfgssetcond(minlbfgsstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void minlbfgssetxrep(minlbfgsstate* state, + ae_bool needxrep, + ae_state *_state); +void minlbfgssetstpmax(minlbfgsstate* state, + double stpmax, + ae_state *_state); +void minlbfgssetscale(minlbfgsstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void minlbfgscreatex(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + ae_int_t flags, + double diffstep, + minlbfgsstate* state, + ae_state *_state); +void minlbfgssetprecdefault(minlbfgsstate* state, ae_state *_state); +void minlbfgssetpreccholesky(minlbfgsstate* state, + /* Real */ ae_matrix* p, + ae_bool isupper, + ae_state *_state); +void minlbfgssetprecdiag(minlbfgsstate* state, + /* Real */ ae_vector* d, + ae_state *_state); +void minlbfgssetprecscale(minlbfgsstate* state, ae_state *_state); +ae_bool minlbfgsiteration(minlbfgsstate* state, ae_state *_state); +void minlbfgsresults(minlbfgsstate* state, + /* Real */ ae_vector* x, + minlbfgsreport* rep, + ae_state *_state); +void minlbfgsresultsbuf(minlbfgsstate* state, + /* Real */ ae_vector* x, + minlbfgsreport* rep, + ae_state *_state); +void minlbfgsrestartfrom(minlbfgsstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void minlbfgssetgradientcheck(minlbfgsstate* state, + double teststep, + ae_state *_state); +ae_bool _minlbfgsstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minlbfgsstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minlbfgsstate_clear(void* _p); +void _minlbfgsstate_destroy(void* _p); +ae_bool _minlbfgsreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minlbfgsreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minlbfgsreport_clear(void* _p); +void _minlbfgsreport_destroy(void* _p); +void minqpcreate(ae_int_t n, minqpstate* state, ae_state *_state); +void minqpsetlinearterm(minqpstate* state, + /* Real */ ae_vector* b, + ae_state *_state); +void minqpsetquadraticterm(minqpstate* state, + /* Real */ ae_matrix* a, + ae_bool isupper, + ae_state *_state); +void minqpsetquadratictermsparse(minqpstate* state, + sparsematrix* a, + ae_bool isupper, + ae_state *_state); +void minqpsetstartingpoint(minqpstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void minqpsetorigin(minqpstate* state, + /* Real */ ae_vector* xorigin, + ae_state *_state); +void minqpsetscale(minqpstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void minqpsetalgocholesky(minqpstate* state, ae_state *_state); +void minqpsetalgobleic(minqpstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void minqpsetbc(minqpstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +void minqpsetlc(minqpstate* state, + /* Real */ ae_matrix* c, + /* Integer */ ae_vector* ct, + ae_int_t k, + ae_state *_state); +void minqpoptimize(minqpstate* state, ae_state *_state); +void minqpresults(minqpstate* state, + /* Real */ ae_vector* x, + minqpreport* rep, + ae_state *_state); +void minqpresultsbuf(minqpstate* state, + /* Real */ ae_vector* x, + minqpreport* rep, + ae_state *_state); +void minqpsetlineartermfast(minqpstate* state, + /* Real */ ae_vector* b, + ae_state *_state); +void minqpsetquadratictermfast(minqpstate* state, + /* Real */ ae_matrix* a, + ae_bool isupper, + double s, + ae_state *_state); +void minqprewritediagonal(minqpstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void minqpsetstartingpointfast(minqpstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void minqpsetoriginfast(minqpstate* state, + /* Real */ ae_vector* xorigin, + ae_state *_state); +ae_bool _minqpstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minqpstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minqpstate_clear(void* _p); +void _minqpstate_destroy(void* _p); +ae_bool _minqpreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minqpreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minqpreport_clear(void* _p); +void _minqpreport_destroy(void* _p); +void minlmcreatevj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state); +void minlmcreatev(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + double diffstep, + minlmstate* state, + ae_state *_state); +void minlmcreatefgh(ae_int_t n, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state); +void minlmsetcond(minlmstate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void minlmsetxrep(minlmstate* state, ae_bool needxrep, ae_state *_state); +void minlmsetstpmax(minlmstate* state, double stpmax, ae_state *_state); +void minlmsetscale(minlmstate* state, + /* Real */ ae_vector* s, + ae_state *_state); +void minlmsetbc(minlmstate* state, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +void minlmsetacctype(minlmstate* state, + ae_int_t acctype, + ae_state *_state); +ae_bool minlmiteration(minlmstate* state, ae_state *_state); +void minlmresults(minlmstate* state, + /* Real */ ae_vector* x, + minlmreport* rep, + ae_state *_state); +void minlmresultsbuf(minlmstate* state, + /* Real */ ae_vector* x, + minlmreport* rep, + ae_state *_state); +void minlmrestartfrom(minlmstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void minlmcreatevgj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state); +void minlmcreatefgj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state); +void minlmcreatefj(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + minlmstate* state, + ae_state *_state); +void minlmsetgradientcheck(minlmstate* state, + double teststep, + ae_state *_state); +ae_bool _minlmstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minlmstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minlmstate_clear(void* _p); +void _minlmstate_destroy(void* _p); +ae_bool _minlmreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minlmreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minlmreport_clear(void* _p); +void _minlmreport_destroy(void* _p); +void minlbfgssetdefaultpreconditioner(minlbfgsstate* state, + ae_state *_state); +void minlbfgssetcholeskypreconditioner(minlbfgsstate* state, + /* Real */ ae_matrix* p, + ae_bool isupper, + ae_state *_state); +void minbleicsetbarrierwidth(minbleicstate* state, + double mu, + ae_state *_state); +void minbleicsetbarrierdecay(minbleicstate* state, + double mudecay, + ae_state *_state); +void minasacreate(ae_int_t n, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + minasastate* state, + ae_state *_state); +void minasasetcond(minasastate* state, + double epsg, + double epsf, + double epsx, + ae_int_t maxits, + ae_state *_state); +void minasasetxrep(minasastate* state, ae_bool needxrep, ae_state *_state); +void minasasetalgorithm(minasastate* state, + ae_int_t algotype, + ae_state *_state); +void minasasetstpmax(minasastate* state, double stpmax, ae_state *_state); +ae_bool minasaiteration(minasastate* state, ae_state *_state); +void minasaresults(minasastate* state, + /* Real */ ae_vector* x, + minasareport* rep, + ae_state *_state); +void minasaresultsbuf(minasastate* state, + /* Real */ ae_vector* x, + minasareport* rep, + ae_state *_state); +void minasarestartfrom(minasastate* state, + /* Real */ ae_vector* x, + /* Real */ ae_vector* bndl, + /* Real */ ae_vector* bndu, + ae_state *_state); +ae_bool _minasastate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minasastate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minasastate_clear(void* _p); +void _minasastate_destroy(void* _p); +ae_bool _minasareport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _minasareport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _minasareport_clear(void* _p); +void _minasareport_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/solvers.cpp b/src/inc/alglib/solvers.cpp new file mode 100644 index 0000000..f1632cd --- /dev/null +++ b/src/inc/alglib/solvers.cpp @@ -0,0 +1,8709 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "solvers.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* + +*************************************************************************/ +_densesolverreport_owner::_densesolverreport_owner() +{ + p_struct = (alglib_impl::densesolverreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::densesolverreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_densesolverreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_densesolverreport_owner::_densesolverreport_owner(const _densesolverreport_owner &rhs) +{ + p_struct = (alglib_impl::densesolverreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::densesolverreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_densesolverreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_densesolverreport_owner& _densesolverreport_owner::operator=(const _densesolverreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_densesolverreport_clear(p_struct); + if( !alglib_impl::_densesolverreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_densesolverreport_owner::~_densesolverreport_owner() +{ + alglib_impl::_densesolverreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::densesolverreport* _densesolverreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::densesolverreport* _densesolverreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +densesolverreport::densesolverreport() : _densesolverreport_owner() ,r1(p_struct->r1),rinf(p_struct->rinf) +{ +} + +densesolverreport::densesolverreport(const densesolverreport &rhs):_densesolverreport_owner(rhs) ,r1(p_struct->r1),rinf(p_struct->rinf) +{ +} + +densesolverreport& densesolverreport::operator=(const densesolverreport &rhs) +{ + if( this==&rhs ) + return *this; + _densesolverreport_owner::operator=(rhs); + return *this; +} + +densesolverreport::~densesolverreport() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_densesolverlsreport_owner::_densesolverlsreport_owner() +{ + p_struct = (alglib_impl::densesolverlsreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::densesolverlsreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_densesolverlsreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_densesolverlsreport_owner::_densesolverlsreport_owner(const _densesolverlsreport_owner &rhs) +{ + p_struct = (alglib_impl::densesolverlsreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::densesolverlsreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_densesolverlsreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_densesolverlsreport_owner& _densesolverlsreport_owner::operator=(const _densesolverlsreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_densesolverlsreport_clear(p_struct); + if( !alglib_impl::_densesolverlsreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_densesolverlsreport_owner::~_densesolverlsreport_owner() +{ + alglib_impl::_densesolverlsreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::densesolverlsreport* _densesolverlsreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::densesolverlsreport* _densesolverlsreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +densesolverlsreport::densesolverlsreport() : _densesolverlsreport_owner() ,r2(p_struct->r2),cx(&p_struct->cx),n(p_struct->n),k(p_struct->k) +{ +} + +densesolverlsreport::densesolverlsreport(const densesolverlsreport &rhs):_densesolverlsreport_owner(rhs) ,r2(p_struct->r2),cx(&p_struct->cx),n(p_struct->n),k(p_struct->k) +{ +} + +densesolverlsreport& densesolverlsreport::operator=(const densesolverlsreport &rhs) +{ + if( this==&rhs ) + return *this; + _densesolverlsreport_owner::operator=(rhs); + return *this; +} + +densesolverlsreport::~densesolverlsreport() +{ +} + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where A is NxN non-denegerate +real matrix, x and b are vectors. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - return code: + * -3 A is singular, or VERY close to singular. + X is filled by zeros in such cases. + * -1 N<=0 was passed + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + X - array[0..N-1], it contains: + * solution of A*x=b if A is non-singular (well-conditioned + or ill-conditioned, but not very close to singular) + * zeros, if A is singular or VERY close to singular + (in this case Info=-3). + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolve(const real_2d_array &a, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixsolve(const_cast(a.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +Similar to RMatrixSolve() but solves task with multiple right parts (where +b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* optional iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvem(const real_2d_array &a, const ae_int_t n, const real_2d_array &b, const ae_int_t m, const bool rfs, ae_int_t &info, densesolverreport &rep, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixsolvem(const_cast(a.c_ptr()), n, const_cast(b.c_ptr()), m, rfs, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*X=B, where A is NxN non-denegerate +real matrix given by its LU decomposition, X and B are NxM real matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolve(const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlusolve(const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +Similar to RMatrixLUSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolvem(const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixlusolvem(const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where BOTH ORIGINAL A AND ITS +LU DECOMPOSITION ARE KNOWN. You can use it if for some reasons you have +both A and its LU decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolve(const real_2d_array &a, const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixmixedsolve(const_cast(a.c_ptr()), const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +Similar to RMatrixMixedSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolvem(const real_2d_array &a, const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixmixedsolvem(const_cast(a.c_ptr()), const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolvem(const complex_2d_array &a, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, const bool rfs, ae_int_t &info, densesolverreport &rep, complex_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixsolvem(const_cast(a.c_ptr()), n, const_cast(b.c_ptr()), m, rfs, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolve(const complex_2d_array &a, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixsolve(const_cast(a.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolvem(const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlusolvem(const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolve(const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixlusolve(const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolvem(const complex_2d_array &a, const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixmixedsolvem(const_cast(a.c_ptr()), const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolve(const complex_2d_array &a, const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::cmatrixmixedsolve(const_cast(a.c_ptr()), const_cast(lua.c_ptr()), const_cast(p.c_ptr()), n, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for symmetric positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolvem(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixsolvem(const_cast(a.c_ptr()), n, isupper, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for SPD matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolve(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixsolve(const_cast(a.c_ptr()), n, isupper, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolvem(const real_2d_array &cha, const ae_int_t n, const bool isupper, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixcholeskysolvem(const_cast(cha.c_ptr()), n, isupper, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolve(const real_2d_array &cha, const ae_int_t n, const bool isupper, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spdmatrixcholeskysolve(const_cast(cha.c_ptr()), n, isupper, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolvem(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixsolvem(const_cast(a.c_ptr()), n, isupper, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolve(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixsolve(const_cast(a.c_ptr()), n, isupper, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + HPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolvem(const complex_2d_array &cha, const ae_int_t n, const bool isupper, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixcholeskysolvem(const_cast(cha.c_ptr()), n, isupper, const_cast(b.c_ptr()), m, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolve(const complex_2d_array &cha, const ae_int_t n, const bool isupper, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hpdmatrixcholeskysolve(const_cast(cha.c_ptr()), n, isupper, const_cast(b.c_ptr()), &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dense solver. + +This subroutine finds solution of the linear system A*X=B with non-square, +possibly degenerate A. System is solved in the least squares sense, and +general least squares solution X = X0 + CX*y which minimizes |A*X-B| is +returned. If A is non-degenerate, solution in the usual sense is returned. + +Algorithm features: +* automatic detection (and correct handling!) of degenerate cases +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..NRows-1,0..NCols-1], system matrix + NRows - vertical size of A + NCols - horizontal size of A + B - array[0..NCols-1], right part + Threshold- a number in [0,1]. Singular values beyond Threshold are + considered zero. Set it to 0.0, if you don't understand + what it means, so the solver will choose good value on its + own. + +OUTPUT PARAMETERS + Info - return code: + * -4 SVD subroutine failed + * -1 if NRows<=0 or NCols<=0 or Threshold<0 was passed + * 1 if task is solved + Rep - solver report, see below for more info + X - array[0..N-1,0..M-1], it contains: + * solution of A*X=B (even for singular A) + * zeros, if SVD subroutine failed + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R2 reciprocal of condition number: 1/cond(A), 2-norm. +* N = NCols +* K dim(Null(A)) +* CX array[0..N-1,0..K-1], kernel of A. + Columns of CX store such vectors that A*CX[i]=0. + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvels(const real_2d_array &a, const ae_int_t nrows, const ae_int_t ncols, const real_1d_array &b, const double threshold, ae_int_t &info, densesolverlsreport &rep, real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rmatrixsolvels(const_cast(a.c_ptr()), nrows, ncols, const_cast(b.c_ptr()), threshold, &info, const_cast(rep.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This object stores state of the LinLSQR method. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +_linlsqrstate_owner::_linlsqrstate_owner() +{ + p_struct = (alglib_impl::linlsqrstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::linlsqrstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linlsqrstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linlsqrstate_owner::_linlsqrstate_owner(const _linlsqrstate_owner &rhs) +{ + p_struct = (alglib_impl::linlsqrstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::linlsqrstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linlsqrstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linlsqrstate_owner& _linlsqrstate_owner::operator=(const _linlsqrstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_linlsqrstate_clear(p_struct); + if( !alglib_impl::_linlsqrstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_linlsqrstate_owner::~_linlsqrstate_owner() +{ + alglib_impl::_linlsqrstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::linlsqrstate* _linlsqrstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::linlsqrstate* _linlsqrstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +linlsqrstate::linlsqrstate() : _linlsqrstate_owner() +{ +} + +linlsqrstate::linlsqrstate(const linlsqrstate &rhs):_linlsqrstate_owner(rhs) +{ +} + +linlsqrstate& linlsqrstate::operator=(const linlsqrstate &rhs) +{ + if( this==&rhs ) + return *this; + _linlsqrstate_owner::operator=(rhs); + return *this; +} + +linlsqrstate::~linlsqrstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_linlsqrreport_owner::_linlsqrreport_owner() +{ + p_struct = (alglib_impl::linlsqrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::linlsqrreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linlsqrreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linlsqrreport_owner::_linlsqrreport_owner(const _linlsqrreport_owner &rhs) +{ + p_struct = (alglib_impl::linlsqrreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::linlsqrreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_linlsqrreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_linlsqrreport_owner& _linlsqrreport_owner::operator=(const _linlsqrreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_linlsqrreport_clear(p_struct); + if( !alglib_impl::_linlsqrreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_linlsqrreport_owner::~_linlsqrreport_owner() +{ + alglib_impl::_linlsqrreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::linlsqrreport* _linlsqrreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::linlsqrreport* _linlsqrreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +linlsqrreport::linlsqrreport() : _linlsqrreport_owner() ,iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype) +{ +} + +linlsqrreport::linlsqrreport(const linlsqrreport &rhs):_linlsqrreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype) +{ +} + +linlsqrreport& linlsqrreport::operator=(const linlsqrreport &rhs) +{ + if( this==&rhs ) + return *this; + _linlsqrreport_owner::operator=(rhs); + return *this; +} + +linlsqrreport::~linlsqrreport() +{ +} + +/************************************************************************* +This function initializes linear LSQR Solver. This solver is used to solve +non-symmetric (and, possibly, non-square) problems. Least squares solution +is returned for non-compatible systems. + +USAGE: +1. User initializes algorithm state with LinLSQRCreate() call +2. User tunes solver parameters with LinLSQRSetCond() and other functions +3. User calls LinLSQRSolveSparse() function which takes algorithm state + and SparseMatrix object. +4. User calls LinLSQRResults() to get solution +5. Optionally, user may call LinLSQRSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinLSQRState structure. + +INPUT PARAMETERS: + M - number of rows in A + N - number of variables, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrcreate(const ae_int_t m, const ae_int_t n, linlsqrstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrcreate(m, n, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function changes preconditioning settings of LinLSQQSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecunit(const linlsqrstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsetprecunit(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecdiag(const linlsqrstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsetprecdiag(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets optional Tikhonov regularization coefficient. +It is zero by default. + +INPUT PARAMETERS: + LambdaI - regularization factor, LambdaI>=0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetlambdai(const linlsqrstate &state, const double lambdai) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsetlambdai(const_cast(state.c_ptr()), lambdai, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse M*N matrix in the CRS format (you MUST contvert it + to CRS format by calling SparseConvertToCRS() function + BEFORE you pass it to this function). + B - right part, array[M] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinLSQRSetPrecUnit(). However, preconditioning cost is low + and preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsolvesparse(const linlsqrstate &state, const sparsematrix &a, const real_1d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsolvesparse(const_cast(state.c_ptr()), const_cast(a.c_ptr()), const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsA - algorithm will be stopped if ||A^T*Rk||/(||A||*||Rk||)<=EpsA. + EpsB - algorithm will be stopped if ||Rk||<=EpsB*||B|| + MaxIts - algorithm will be stopped if number of iterations + more than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: if EpsA,EpsB,EpsC and MaxIts are zero then these variables will +be setted as default values. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetcond(const linlsqrstate &state, const double epsa, const double epsb, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsetcond(const_cast(state.c_ptr()), epsa, epsb, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +LSQR solver: results. + +This function must be called after LinLSQRSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * 1 ||Rk||<=EpsB*||B|| + * 4 ||A^T*Rk||/(||A||*||Rk||)<=EpsA + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + X contains best point found so far. + (sometimes returned on singular systems) + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrresults(const linlsqrstate &state, real_1d_array &x, linlsqrreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetxrep(const linlsqrstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::linlsqrsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This object stores state of the linear CG method. + +You should use ALGLIB functions to work with this object. +Never try to access its fields directly! +*************************************************************************/ +_lincgstate_owner::_lincgstate_owner() +{ + p_struct = (alglib_impl::lincgstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::lincgstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lincgstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lincgstate_owner::_lincgstate_owner(const _lincgstate_owner &rhs) +{ + p_struct = (alglib_impl::lincgstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::lincgstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lincgstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lincgstate_owner& _lincgstate_owner::operator=(const _lincgstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_lincgstate_clear(p_struct); + if( !alglib_impl::_lincgstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_lincgstate_owner::~_lincgstate_owner() +{ + alglib_impl::_lincgstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::lincgstate* _lincgstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::lincgstate* _lincgstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +lincgstate::lincgstate() : _lincgstate_owner() +{ +} + +lincgstate::lincgstate(const lincgstate &rhs):_lincgstate_owner(rhs) +{ +} + +lincgstate& lincgstate::operator=(const lincgstate &rhs) +{ + if( this==&rhs ) + return *this; + _lincgstate_owner::operator=(rhs); + return *this; +} + +lincgstate::~lincgstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_lincgreport_owner::_lincgreport_owner() +{ + p_struct = (alglib_impl::lincgreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lincgreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lincgreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lincgreport_owner::_lincgreport_owner(const _lincgreport_owner &rhs) +{ + p_struct = (alglib_impl::lincgreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::lincgreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_lincgreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_lincgreport_owner& _lincgreport_owner::operator=(const _lincgreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_lincgreport_clear(p_struct); + if( !alglib_impl::_lincgreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_lincgreport_owner::~_lincgreport_owner() +{ + alglib_impl::_lincgreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::lincgreport* _lincgreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::lincgreport* _lincgreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +lincgreport::lincgreport() : _lincgreport_owner() ,iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype),r2(p_struct->r2) +{ +} + +lincgreport::lincgreport(const lincgreport &rhs):_lincgreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nmv(p_struct->nmv),terminationtype(p_struct->terminationtype),r2(p_struct->r2) +{ +} + +lincgreport& lincgreport::operator=(const lincgreport &rhs) +{ + if( this==&rhs ) + return *this; + _lincgreport_owner::operator=(rhs); + return *this; +} + +lincgreport::~lincgreport() +{ +} + +/************************************************************************* +This function initializes linear CG Solver. This solver is used to solve +symmetric positive definite problems. If you want to solve nonsymmetric +(or non-positive definite) problem you may use LinLSQR solver provided by +ALGLIB. + +USAGE: +1. User initializes algorithm state with LinCGCreate() call +2. User tunes solver parameters with LinCGSetCond() and other functions +3. Optionally, user sets starting point with LinCGSetStartingPoint() +4. User calls LinCGSolveSparse() function which takes algorithm state and + SparseMatrix object. +5. User calls LinCGResults() to get solution +6. Optionally, user may call LinCGSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinCGState structure. + +INPUT PARAMETERS: + N - problem dimension, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgcreate(const ae_int_t n, lincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgcreate(n, const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets starting point. +By default, zero starting point is used. + +INPUT PARAMETERS: + X - starting point, array[N] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetstartingpoint(const lincgstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetstartingpoint(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecunit(const lincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetprecunit(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecdiag(const lincgstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetprecdiag(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsF - algorithm will be stopped if norm of residual is less than + EpsF*||b||. + MaxIts - algorithm will be stopped if number of iterations is more + than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +If both EpsF and MaxIts are zero then small EpsF will be set to small +value. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetcond(const lincgstate &state, const double epsf, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetcond(const_cast(state.c_ptr()), epsf, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse matrix in the CRS format (you MUST contvert it to + CRS format by calling SparseConvertToCRS() function). + IsUpper - whether upper or lower triangle of A is used: + * IsUpper=True => only upper triangle is used and lower + triangle is not referenced at all + * IsUpper=False => only lower triangle is used and upper + triangle is not referenced at all + B - right part, array[N] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinCGSetPrecUnit(). However, preconditioning cost is low and + preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsolvesparse(const lincgstate &state, const sparsematrix &a, const bool isupper, const real_1d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsolvesparse(const_cast(state.c_ptr()), const_cast(a.c_ptr()), isupper, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +CG-solver: results. + +This function must be called after LinCGSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -5 input matrix is either not positive definite, + too large or too small + * -4 overflow/underflow during solution + (ill conditioned problem) + * 1 ||residual||<=EpsF*||b|| + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + best point found is returned + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgresults(const lincgstate &state, real_1d_array &x, lincgreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets restart frequency. By default, algorithm is restarted +after N subsequent iterations. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrestartfreq(const lincgstate &state, const ae_int_t srf) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetrestartfreq(const_cast(state.c_ptr()), srf, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets frequency of residual recalculations. + +Algorithm updates residual r_k using iterative formula, but recalculates +it from scratch after each 10 iterations. It is done to avoid accumulation +of numerical errors and to stop algorithm when r_k starts to grow. + +Such low update frequence (1/10) gives very little overhead, but makes +algorithm a bit more robust against numerical errors. However, you may +change it + +INPUT PARAMETERS: + Freq - desired update frequency, Freq>=0. + Zero value means that no updates will be done. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrupdatefreq(const lincgstate &state, const ae_int_t freq) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetrupdatefreq(const_cast(state.c_ptr()), freq, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetxrep(const lincgstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::lincgsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +_nleqstate_owner::_nleqstate_owner() +{ + p_struct = (alglib_impl::nleqstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::nleqstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_nleqstate_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_nleqstate_owner::_nleqstate_owner(const _nleqstate_owner &rhs) +{ + p_struct = (alglib_impl::nleqstate*)alglib_impl::ae_malloc(sizeof(alglib_impl::nleqstate), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_nleqstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_nleqstate_owner& _nleqstate_owner::operator=(const _nleqstate_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_nleqstate_clear(p_struct); + if( !alglib_impl::_nleqstate_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_nleqstate_owner::~_nleqstate_owner() +{ + alglib_impl::_nleqstate_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::nleqstate* _nleqstate_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::nleqstate* _nleqstate_owner::c_ptr() const +{ + return const_cast(p_struct); +} +nleqstate::nleqstate() : _nleqstate_owner() ,needf(p_struct->needf),needfij(p_struct->needfij),xupdated(p_struct->xupdated),f(p_struct->f),fi(&p_struct->fi),j(&p_struct->j),x(&p_struct->x) +{ +} + +nleqstate::nleqstate(const nleqstate &rhs):_nleqstate_owner(rhs) ,needf(p_struct->needf),needfij(p_struct->needfij),xupdated(p_struct->xupdated),f(p_struct->f),fi(&p_struct->fi),j(&p_struct->j),x(&p_struct->x) +{ +} + +nleqstate& nleqstate::operator=(const nleqstate &rhs) +{ + if( this==&rhs ) + return *this; + _nleqstate_owner::operator=(rhs); + return *this; +} + +nleqstate::~nleqstate() +{ +} + + +/************************************************************************* + +*************************************************************************/ +_nleqreport_owner::_nleqreport_owner() +{ + p_struct = (alglib_impl::nleqreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::nleqreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_nleqreport_init(p_struct, NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_nleqreport_owner::_nleqreport_owner(const _nleqreport_owner &rhs) +{ + p_struct = (alglib_impl::nleqreport*)alglib_impl::ae_malloc(sizeof(alglib_impl::nleqreport), NULL); + if( p_struct==NULL ) + throw ap_error("ALGLIB: malloc error"); + if( !alglib_impl::_nleqreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); +} + +_nleqreport_owner& _nleqreport_owner::operator=(const _nleqreport_owner &rhs) +{ + if( this==&rhs ) + return *this; + alglib_impl::_nleqreport_clear(p_struct); + if( !alglib_impl::_nleqreport_init_copy(p_struct, const_cast(rhs.p_struct), NULL, ae_false) ) + throw ap_error("ALGLIB: malloc error"); + return *this; +} + +_nleqreport_owner::~_nleqreport_owner() +{ + alglib_impl::_nleqreport_clear(p_struct); + ae_free(p_struct); +} + +alglib_impl::nleqreport* _nleqreport_owner::c_ptr() +{ + return p_struct; +} + +alglib_impl::nleqreport* _nleqreport_owner::c_ptr() const +{ + return const_cast(p_struct); +} +nleqreport::nleqreport() : _nleqreport_owner() ,iterationscount(p_struct->iterationscount),nfunc(p_struct->nfunc),njac(p_struct->njac),terminationtype(p_struct->terminationtype) +{ +} + +nleqreport::nleqreport(const nleqreport &rhs):_nleqreport_owner(rhs) ,iterationscount(p_struct->iterationscount),nfunc(p_struct->nfunc),njac(p_struct->njac),terminationtype(p_struct->terminationtype) +{ +} + +nleqreport& nleqreport::operator=(const nleqreport &rhs) +{ + if( this==&rhs ) + return *this; + _nleqreport_owner::operator=(rhs); + return *this; +} + +nleqreport::~nleqreport() +{ +} + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER + +DESCRIPTION: +This algorithm solves system of nonlinear equations + F[0](x[0], ..., x[n-1]) = 0 + F[1](x[0], ..., x[n-1]) = 0 + ... + F[M-1](x[0], ..., x[n-1]) = 0 +with M/N do not necessarily coincide. Algorithm converges quadratically +under following conditions: + * the solution set XS is nonempty + * for some xs in XS there exist such neighbourhood N(xs) that: + * vector function F(x) and its Jacobian J(x) are continuously + differentiable on N + * ||F(x)|| provides local error bound on N, i.e. there exists such + c1, that ||F(x)||>c1*distance(x,XS) +Note that these conditions are much more weaker than usual non-singularity +conditions. For example, algorithm will converge for any affine function +F (whether its Jacobian singular or not). + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function vector F[] and Jacobian matrix at given point X +* value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X + + +USAGE: +1. User initializes algorithm state with NLEQCreateLM() call +2. User tunes solver parameters with NLEQSetCond(), NLEQSetStpMax() and + other functions +3. User calls NLEQSolve() function which takes algorithm state and + pointers (delegates, etc.) to callback functions which calculate merit + function value and Jacobian. +4. User calls NLEQResults() to get solution +5. Optionally, user may call NLEQRestartFrom() to solve another problem + with same parameters (N/M) but another starting point and/or another + function vector. NLEQRestartFrom() allows to reuse already initialized + structure. + + +INPUT PARAMETERS: + N - space dimension, N>1: + * if provided, only leading N elements of X are used + * if not provided, determined automatically from size of X + M - system size + X - starting point + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with NLEQSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use NLEQSetStpMax() function to bound algorithm's steps. +3. this algorithm is a slightly modified implementation of the method + described in 'Levenberg-Marquardt method for constrained nonlinear + equations with strong local convergence properties' by Christian Kanzow + Nobuo Yamashita and Masao Fukushima and further developed in 'On the + convergence of a New Levenberg-Marquardt Method' by Jin-yan Fan and + Ya-Xiang Yuan. + + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqcreatelm(const ae_int_t n, const ae_int_t m, const real_1d_array &x, nleqstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqcreatelm(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER + +DESCRIPTION: +This algorithm solves system of nonlinear equations + F[0](x[0], ..., x[n-1]) = 0 + F[1](x[0], ..., x[n-1]) = 0 + ... + F[M-1](x[0], ..., x[n-1]) = 0 +with M/N do not necessarily coincide. Algorithm converges quadratically +under following conditions: + * the solution set XS is nonempty + * for some xs in XS there exist such neighbourhood N(xs) that: + * vector function F(x) and its Jacobian J(x) are continuously + differentiable on N + * ||F(x)|| provides local error bound on N, i.e. there exists such + c1, that ||F(x)||>c1*distance(x,XS) +Note that these conditions are much more weaker than usual non-singularity +conditions. For example, algorithm will converge for any affine function +F (whether its Jacobian singular or not). + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function vector F[] and Jacobian matrix at given point X +* value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X + + +USAGE: +1. User initializes algorithm state with NLEQCreateLM() call +2. User tunes solver parameters with NLEQSetCond(), NLEQSetStpMax() and + other functions +3. User calls NLEQSolve() function which takes algorithm state and + pointers (delegates, etc.) to callback functions which calculate merit + function value and Jacobian. +4. User calls NLEQResults() to get solution +5. Optionally, user may call NLEQRestartFrom() to solve another problem + with same parameters (N/M) but another starting point and/or another + function vector. NLEQRestartFrom() allows to reuse already initialized + structure. + + +INPUT PARAMETERS: + N - space dimension, N>1: + * if provided, only leading N elements of X are used + * if not provided, determined automatically from size of X + M - system size + X - starting point + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with NLEQSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use NLEQSetStpMax() function to bound algorithm's steps. +3. this algorithm is a slightly modified implementation of the method + described in 'Levenberg-Marquardt method for constrained nonlinear + equations with strong local convergence properties' by Christian Kanzow + Nobuo Yamashita and Masao Fukushima and further developed in 'On the + convergence of a New Levenberg-Marquardt Method' by Jin-yan Fan and + Ya-Xiang Yuan. + + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqcreatelm(const ae_int_t m, const real_1d_array &x, nleqstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqcreatelm(n, m, const_cast(x.c_ptr()), const_cast(state.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets stopping conditions for the nonlinear solver + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsF - >=0 + The subroutine finishes its work if on k+1-th iteration + the condition ||F||<=EpsF is satisfied + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsF=0 and MaxIts=0 simultaneously will lead to automatic +stopping criterion selection (small EpsF). + +NOTES: + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetcond(const nleqstate &state, const double epsf, const ae_int_t maxits) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqsetcond(const_cast(state.c_ptr()), epsf, maxits, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to NLEQSolve(). + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetxrep(const nleqstate &state, const bool needxrep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqsetxrep(const_cast(state.c_ptr()), needxrep, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function sets 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 target function contains exp() or other fast +growing functions, and algorithm makes too large steps which lead 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. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetstpmax(const nleqstate &state, const double stpmax) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqsetstpmax(const_cast(state.c_ptr()), stpmax, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool nleqiteration(const nleqstate &state) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + ae_bool result = alglib_impl::nleqiteration(const_cast(state.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void nleqsolve(nleqstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr), + void *ptr) +{ + alglib_impl::ae_state _alglib_env_state; + if( func==NULL ) + throw ap_error("ALGLIB: error in 'nleqsolve()' (func is NULL)"); + if( jac==NULL ) + throw ap_error("ALGLIB: error in 'nleqsolve()' (jac is NULL)"); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + while( alglib_impl::nleqiteration(state.c_ptr(), &_alglib_env_state) ) + { + if( state.needf ) + { + func(state.x, state.f, ptr); + continue; + } + if( state.needfij ) + { + jac(state.x, state.fi, state.j, ptr); + continue; + } + if( state.xupdated ) + { + if( rep!=NULL ) + rep(state.x, state.f, ptr); + continue; + } + throw ap_error("ALGLIB: error in 'nleqsolve' (some derivatives were not provided?)"); + } + alglib_impl::ae_state_clear(&_alglib_env_state); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + + +/************************************************************************* +NLEQ solver results + +INPUT PARAMETERS: + State - algorithm state. + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -4 ERROR: algorithm has converged to the + stationary point Xf which is local minimum of + f=F[0]^2+...+F[m-1]^2, but is not solution of + nonlinear system. + * 1 sqrt(f)<=EpsF. + * 5 MaxIts steps was taken + * 7 stopping conditions are too stringent, + further improvement is impossible + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + * ActiveConstraints contains number of active constraints + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresults(const nleqstate &state, real_1d_array &x, nleqreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqresults(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +NLEQ solver results + +Buffered implementation of NLEQResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresultsbuf(const nleqstate &state, real_1d_array &x, nleqreport &rep) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqresultsbuf(const_cast(state.c_ptr()), const_cast(x.c_ptr()), const_cast(rep.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinCGCreate call. + X - new starting point. + BndL - new lower bounds + BndU - new upper bounds + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqrestartfrom(const nleqstate &state, const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::nleqrestartfrom(const_cast(state.c_ptr()), const_cast(x.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static void densesolver_rmatrixlusolveinternal(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_bool havea, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +static void densesolver_spdmatrixcholeskysolveinternal(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* a, + ae_bool havea, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +static void densesolver_cmatrixlusolveinternal(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_bool havea, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +static void densesolver_hpdmatrixcholeskysolveinternal(/* Complex */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* a, + ae_bool havea, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +static ae_int_t densesolver_densesolverrfsmax(ae_int_t n, + double r1, + double rinf, + ae_state *_state); +static ae_int_t densesolver_densesolverrfsmaxv2(ae_int_t n, + double r2, + ae_state *_state); +static void densesolver_rbasiclusolve(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void densesolver_spdbasiccholeskysolve(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state); +static void densesolver_cbasiclusolve(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Complex */ ae_vector* xb, + /* Complex */ ae_vector* tmp, + ae_state *_state); +static void densesolver_hpdbasiccholeskysolve(/* Complex */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* xb, + /* Complex */ ae_vector* tmp, + ae_state *_state); + + +static double linlsqr_atol = 1.0E-6; +static double linlsqr_btol = 1.0E-6; +static void linlsqr_clearrfields(linlsqrstate* state, ae_state *_state); + + +static double lincg_defaultprecision = 1.0E-6; +static void lincg_clearrfields(lincgstate* state, ae_state *_state); +static void lincg_updateitersdata(lincgstate* state, ae_state *_state); + + +static void nleq_clearrequestfields(nleqstate* state, ae_state *_state); +static ae_bool nleq_increaselambda(double* lambdav, + double* nu, + double lambdaup, + ae_state *_state); +static void nleq_decreaselambda(double* lambdav, + double* nu, + double lambdadown, + ae_state *_state); + + + + + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where A is NxN non-denegerate +real matrix, x and b are vectors. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - return code: + * -3 A is singular, or VERY close to singular. + X is filled by zeros in such cases. + * -1 N<=0 was passed + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + X - array[0..N-1], it contains: + * solution of A*x=b if A is non-singular (well-conditioned + or ill-conditioned, but not very close to singular) + * zeros, if A is singular or VERY close to singular + (in this case Info=-3). + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolve(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_move(&bm.ptr.pp_double[0][0], bm.stride, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + rmatrixsolvem(a, n, &bm, 1, ae_true, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_move(&x->ptr.p_double[0], 1, &xm.ptr.pp_double[0][0], xm.stride, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +Similar to RMatrixSolve() but solves task with multiple right parts (where +b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* optional iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvem(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_bool rfs, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix da; + ae_matrix emptya; + ae_vector p; + double scalea; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&da, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&emptya, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&da, n, n, _state); + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + for(i=0; i<=n-1; i++) + { + ae_v_move(&da.ptr.pp_double[i][0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1)); + } + rmatrixlu(&da, n, n, &p, _state); + if( rfs ) + { + densesolver_rmatrixlusolveinternal(&da, &p, scalea, n, a, ae_true, b, m, info, rep, x, _state); + } + else + { + densesolver_rmatrixlusolveinternal(&da, &p, scalea, n, &emptya, ae_false, b, m, info, rep, x, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*X=B, where A is NxN non-denegerate +real matrix given by its LU decomposition, X and B are NxM real matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolve(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_move(&bm.ptr.pp_double[0][0], bm.stride, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + rmatrixlusolvem(lua, p, n, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_move(&x->ptr.p_double[0], 1, &xm.ptr.pp_double[0][0], xm.stride, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +Similar to RMatrixLUSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolvem(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix emptya; + ae_int_t i; + ae_int_t j; + double scalea; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&emptya, 0, 0, DT_REAL, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + + /* + * 1. scale matrix, max(|U[i,j]|) + * we assume that LU is in its normal form, i.e. |L[i,j]|<=1 + * 2. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_fabs(lua->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + densesolver_rmatrixlusolveinternal(lua, p, scalea, n, &emptya, ae_false, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where BOTH ORIGINAL A AND ITS +LU DECOMPOSITION ARE KNOWN. You can use it if for some reasons you have +both A and its LU decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolve(/* Real */ ae_matrix* a, + /* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_move(&bm.ptr.pp_double[0][0], bm.stride, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + rmatrixmixedsolvem(a, lua, p, n, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_move(&x->ptr.p_double[0], 1, &xm.ptr.pp_double[0][0], xm.stride, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +Similar to RMatrixMixedSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolvem(/* Real */ ae_matrix* a, + /* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + double scalea; + ae_int_t i; + ae_int_t j; + + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + return; + } + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + densesolver_rmatrixlusolveinternal(lua, p, scalea, n, a, ae_true, b, m, info, rep, x, _state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolvem(/* Complex */ ae_matrix* a, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_bool rfs, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix da; + ae_matrix emptya; + ae_vector p; + double scalea; + ae_int_t i; + ae_int_t j; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&da, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&emptya, 0, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&p, 0, DT_INT, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&da, n, n, _state); + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + for(i=0; i<=n-1; i++) + { + ae_v_cmove(&da.ptr.pp_complex[i][0], 1, &a->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,n-1)); + } + cmatrixlu(&da, n, n, &p, _state); + if( rfs ) + { + densesolver_cmatrixlusolveinternal(&da, &p, scalea, n, a, ae_true, b, m, info, rep, x, _state); + } + else + { + densesolver_cmatrixlusolveinternal(&da, &p, scalea, n, &emptya, ae_false, b, m, info, rep, x, _state); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolve(/* Complex */ ae_matrix* a, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_cmove(&bm.ptr.pp_complex[0][0], bm.stride, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + cmatrixsolvem(a, n, &bm, 1, ae_true, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_cmove(&x->ptr.p_complex[0], 1, &xm.ptr.pp_complex[0][0], xm.stride, "N", ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolvem(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix emptya; + ae_int_t i; + ae_int_t j; + double scalea; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&emptya, 0, 0, DT_COMPLEX, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + + /* + * 1. scale matrix, max(|U[i,j]|) + * we assume that LU is in its normal form, i.e. |L[i,j]|<=1 + * 2. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=i; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_c_abs(lua->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + densesolver_cmatrixlusolveinternal(lua, p, scalea, n, &emptya, ae_false, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolve(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_cmove(&bm.ptr.pp_complex[0][0], bm.stride, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + cmatrixlusolvem(lua, p, n, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_cmove(&x->ptr.p_complex[0], 1, &xm.ptr.pp_complex[0][0], xm.stride, "N", ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolvem(/* Complex */ ae_matrix* a, + /* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + double scalea; + ae_int_t i; + ae_int_t j; + + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + return; + } + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + scalea = 0; + for(i=0; i<=n-1; i++) + { + for(j=0; j<=n-1; j++) + { + scalea = ae_maxreal(scalea, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(scalea,0) ) + { + scalea = 1; + } + scalea = 1/scalea; + densesolver_cmatrixlusolveinternal(lua, p, scalea, n, a, ae_true, b, m, info, rep, x, _state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolve(/* Complex */ ae_matrix* a, + /* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_cmove(&bm.ptr.pp_complex[0][0], bm.stride, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + cmatrixmixedsolvem(a, lua, p, n, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_cmove(&x->ptr.p_complex[0], 1, &xm.ptr.pp_complex[0][0], xm.stride, "N", ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for symmetric positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolvem(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix da; + double sqrtscalea; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&da, 0, 0, DT_REAL, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&da, n, n, _state); + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + sqrtscalea = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + sqrtscalea = ae_maxreal(sqrtscalea, ae_fabs(a->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(sqrtscalea,0) ) + { + sqrtscalea = 1; + } + sqrtscalea = 1/sqrtscalea; + sqrtscalea = ae_sqrt(sqrtscalea, _state); + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + ae_v_move(&da.ptr.pp_double[i][j1], 1, &a->ptr.pp_double[i][j1], 1, ae_v_len(j1,j2)); + } + if( !spdmatrixcholesky(&da, n, isupper, _state) ) + { + ae_matrix_set_length(x, n, m, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_double[i][j] = 0; + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + densesolver_spdmatrixcholeskysolveinternal(&da, sqrtscalea, n, isupper, a, ae_true, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for SPD matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolve(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_move(&bm.ptr.pp_double[0][0], bm.stride, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + spdmatrixsolvem(a, n, isupper, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_move(&x->ptr.p_double[0], 1, &xm.ptr.pp_double[0][0], xm.stride, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolvem(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix emptya; + double sqrtscalea; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&emptya, 0, 0, DT_REAL, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + + /* + * 1. scale matrix, max(|U[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + sqrtscalea = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + sqrtscalea = ae_maxreal(sqrtscalea, ae_fabs(cha->ptr.pp_double[i][j], _state), _state); + } + } + if( ae_fp_eq(sqrtscalea,0) ) + { + sqrtscalea = 1; + } + sqrtscalea = 1/sqrtscalea; + densesolver_spdmatrixcholeskysolveinternal(cha, sqrtscalea, n, isupper, &emptya, ae_false, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolve(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_REAL, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_move(&bm.ptr.pp_double[0][0], bm.stride, &b->ptr.p_double[0], 1, ae_v_len(0,n-1)); + spdmatrixcholeskysolvem(cha, n, isupper, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_move(&x->ptr.p_double[0], 1, &xm.ptr.pp_double[0][0], xm.stride, ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolvem(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix da; + double sqrtscalea; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&da, 0, 0, DT_COMPLEX, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&da, n, n, _state); + + /* + * 1. scale matrix, max(|A[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + sqrtscalea = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + sqrtscalea = ae_maxreal(sqrtscalea, ae_c_abs(a->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(sqrtscalea,0) ) + { + sqrtscalea = 1; + } + sqrtscalea = 1/sqrtscalea; + sqrtscalea = ae_sqrt(sqrtscalea, _state); + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + ae_v_cmove(&da.ptr.pp_complex[i][j1], 1, &a->ptr.pp_complex[i][j1], 1, "N", ae_v_len(j1,j2)); + } + if( !hpdmatrixcholesky(&da, n, isupper, _state) ) + { + ae_matrix_set_length(x, n, m, _state); + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + densesolver_hpdmatrixcholeskysolveinternal(&da, sqrtscalea, n, isupper, a, ae_true, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolve(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_cmove(&bm.ptr.pp_complex[0][0], bm.stride, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + hpdmatrixsolvem(a, n, isupper, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_cmove(&x->ptr.p_complex[0], 1, &xm.ptr.pp_complex[0][0], xm.stride, "N", ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + HPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolvem(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix emptya; + double sqrtscalea; + ae_int_t i; + ae_int_t j; + ae_int_t j1; + ae_int_t j2; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_matrix_init(&emptya, 0, 0, DT_COMPLEX, _state, ae_true); + + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + + /* + * 1. scale matrix, max(|U[i,j]|) + * 2. factorize scaled matrix + * 3. solve + */ + sqrtscalea = 0; + for(i=0; i<=n-1; i++) + { + if( isupper ) + { + j1 = i; + j2 = n-1; + } + else + { + j1 = 0; + j2 = i; + } + for(j=j1; j<=j2; j++) + { + sqrtscalea = ae_maxreal(sqrtscalea, ae_c_abs(cha->ptr.pp_complex[i][j], _state), _state); + } + } + if( ae_fp_eq(sqrtscalea,0) ) + { + sqrtscalea = 1; + } + sqrtscalea = 1/sqrtscalea; + densesolver_hpdmatrixcholeskysolveinternal(cha, sqrtscalea, n, isupper, &emptya, ae_false, b, m, info, rep, x, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolve(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix bm; + ae_matrix xm; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_vector_clear(x); + ae_matrix_init(&bm, 0, 0, DT_COMPLEX, _state, ae_true); + ae_matrix_init(&xm, 0, 0, DT_COMPLEX, _state, ae_true); + + if( n<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(&bm, n, 1, _state); + ae_v_cmove(&bm.ptr.pp_complex[0][0], bm.stride, &b->ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + hpdmatrixcholeskysolvem(cha, n, isupper, &bm, 1, info, rep, &xm, _state); + ae_vector_set_length(x, n, _state); + ae_v_cmove(&x->ptr.p_complex[0], 1, &xm.ptr.pp_complex[0][0], xm.stride, "N", ae_v_len(0,n-1)); + ae_frame_leave(_state); +} + + +/************************************************************************* +Dense solver. + +This subroutine finds solution of the linear system A*X=B with non-square, +possibly degenerate A. System is solved in the least squares sense, and +general least squares solution X = X0 + CX*y which minimizes |A*X-B| is +returned. If A is non-degenerate, solution in the usual sense is returned. + +Algorithm features: +* automatic detection (and correct handling!) of degenerate cases +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..NRows-1,0..NCols-1], system matrix + NRows - vertical size of A + NCols - horizontal size of A + B - array[0..NCols-1], right part + Threshold- a number in [0,1]. Singular values beyond Threshold are + considered zero. Set it to 0.0, if you don't understand + what it means, so the solver will choose good value on its + own. + +OUTPUT PARAMETERS + Info - return code: + * -4 SVD subroutine failed + * -1 if NRows<=0 or NCols<=0 or Threshold<0 was passed + * 1 if task is solved + Rep - solver report, see below for more info + X - array[0..N-1,0..M-1], it contains: + * solution of A*X=B (even for singular A) + * zeros, if SVD subroutine failed + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R2 reciprocal of condition number: 1/cond(A), 2-norm. +* N = NCols +* K dim(Null(A)) +* CX array[0..N-1,0..K-1], kernel of A. + Columns of CX store such vectors that A*CX[i]=0. + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvels(/* Real */ ae_matrix* a, + ae_int_t nrows, + ae_int_t ncols, + /* Real */ ae_vector* b, + double threshold, + ae_int_t* info, + densesolverlsreport* rep, + /* Real */ ae_vector* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector sv; + ae_matrix u; + ae_matrix vt; + ae_vector rp; + ae_vector utb; + ae_vector sutb; + ae_vector tmp; + ae_vector ta; + ae_vector tx; + ae_vector buf; + ae_vector w; + ae_int_t i; + ae_int_t j; + ae_int_t nsv; + ae_int_t kernelidx; + double v; + double verr; + ae_bool svdfailed; + ae_bool zeroa; + ae_int_t rfs; + ae_int_t nrfs; + ae_bool terminatenexttime; + ae_bool smallerr; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverlsreport_clear(rep); + ae_vector_clear(x); + ae_vector_init(&sv, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&u, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&vt, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&rp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&utb, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sutb, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tmp, 0, DT_REAL, _state, ae_true); + ae_vector_init(&ta, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&buf, 0, DT_REAL, _state, ae_true); + ae_vector_init(&w, 0, DT_REAL, _state, ae_true); + + if( (nrows<=0||ncols<=0)||ae_fp_less(threshold,0) ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + if( ae_fp_eq(threshold,0) ) + { + threshold = 1000*ae_machineepsilon; + } + + /* + * Factorize A first + */ + svdfailed = !rmatrixsvd(a, nrows, ncols, 1, 2, 2, &sv, &u, &vt, _state); + zeroa = ae_fp_eq(sv.ptr.p_double[0],0); + if( svdfailed||zeroa ) + { + if( svdfailed ) + { + *info = -4; + } + else + { + *info = 1; + } + ae_vector_set_length(x, ncols, _state); + for(i=0; i<=ncols-1; i++) + { + x->ptr.p_double[i] = 0; + } + rep->n = ncols; + rep->k = ncols; + ae_matrix_set_length(&rep->cx, ncols, ncols, _state); + for(i=0; i<=ncols-1; i++) + { + for(j=0; j<=ncols-1; j++) + { + if( i==j ) + { + rep->cx.ptr.pp_double[i][j] = 1; + } + else + { + rep->cx.ptr.pp_double[i][j] = 0; + } + } + } + rep->r2 = 0; + ae_frame_leave(_state); + return; + } + nsv = ae_minint(ncols, nrows, _state); + if( nsv==ncols ) + { + rep->r2 = sv.ptr.p_double[nsv-1]/sv.ptr.p_double[0]; + } + else + { + rep->r2 = 0; + } + rep->n = ncols; + *info = 1; + + /* + * Iterative refinement of xc combined with solution: + * 1. xc = 0 + * 2. calculate r = bc-A*xc using extra-precise dot product + * 3. solve A*y = r + * 4. update x:=x+r + * 5. goto 2 + * + * This cycle is executed until one of two things happens: + * 1. maximum number of iterations reached + * 2. last iteration decreased error to the lower limit + */ + ae_vector_set_length(&utb, nsv, _state); + ae_vector_set_length(&sutb, nsv, _state); + ae_vector_set_length(x, ncols, _state); + ae_vector_set_length(&tmp, ncols, _state); + ae_vector_set_length(&ta, ncols+1, _state); + ae_vector_set_length(&tx, ncols+1, _state); + ae_vector_set_length(&buf, ncols+1, _state); + for(i=0; i<=ncols-1; i++) + { + x->ptr.p_double[i] = 0; + } + kernelidx = nsv; + for(i=0; i<=nsv-1; i++) + { + if( ae_fp_less_eq(sv.ptr.p_double[i],threshold*sv.ptr.p_double[0]) ) + { + kernelidx = i; + break; + } + } + rep->k = ncols-kernelidx; + nrfs = densesolver_densesolverrfsmaxv2(ncols, rep->r2, _state); + terminatenexttime = ae_false; + ae_vector_set_length(&rp, nrows, _state); + for(rfs=0; rfs<=nrfs; rfs++) + { + if( terminatenexttime ) + { + break; + } + + /* + * calculate right part + */ + if( rfs==0 ) + { + ae_v_move(&rp.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,nrows-1)); + } + else + { + smallerr = ae_true; + for(i=0; i<=nrows-1; i++) + { + ae_v_move(&ta.ptr.p_double[0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,ncols-1)); + ta.ptr.p_double[ncols] = -1; + ae_v_move(&tx.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,ncols-1)); + tx.ptr.p_double[ncols] = b->ptr.p_double[i]; + xdot(&ta, &tx, ncols+1, &buf, &v, &verr, _state); + rp.ptr.p_double[i] = -v; + smallerr = smallerr&&ae_fp_less(ae_fabs(v, _state),4*verr); + } + if( smallerr ) + { + terminatenexttime = ae_true; + } + } + + /* + * solve A*dx = rp + */ + for(i=0; i<=ncols-1; i++) + { + tmp.ptr.p_double[i] = 0; + } + for(i=0; i<=nsv-1; i++) + { + utb.ptr.p_double[i] = 0; + } + for(i=0; i<=nrows-1; i++) + { + v = rp.ptr.p_double[i]; + ae_v_addd(&utb.ptr.p_double[0], 1, &u.ptr.pp_double[i][0], 1, ae_v_len(0,nsv-1), v); + } + for(i=0; i<=nsv-1; i++) + { + if( iptr.p_double[0], 1, &tmp.ptr.p_double[0], 1, ae_v_len(0,ncols-1)); + } + + /* + * fill CX + */ + if( rep->k>0 ) + { + ae_matrix_set_length(&rep->cx, ncols, rep->k, _state); + for(i=0; i<=rep->k-1; i++) + { + ae_v_move(&rep->cx.ptr.pp_double[0][i], rep->cx.stride, &vt.ptr.pp_double[kernelidx+i][0], 1, ae_v_len(0,ncols-1)); + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal LU solver + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_rmatrixlusolveinternal(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Real */ ae_matrix* a, + ae_bool havea, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t rfs; + ae_int_t nrfs; + ae_vector xc; + ae_vector y; + ae_vector bc; + ae_vector xa; + ae_vector xb; + ae_vector tx; + double v; + double verr; + double mxb; + double scaleright; + ae_bool smallerr; + ae_bool terminatenexttime; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xa, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xb, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tx, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_greater(scalea,0), "Assertion failed", _state); + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + if( p->ptr.p_int[i]>n-1||p->ptr.p_int[i]r1 = rmatrixlurcond1(lua, n, _state); + rep->rinf = rmatrixlurcondinf(lua, n, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_double[i][j] = 0; + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * solve + */ + for(k=0; k<=m-1; k++) + { + + /* + * copy B to contiguous storage + */ + ae_v_move(&bc.ptr.p_double[0], 1, &b->ptr.pp_double[0][k], b->stride, ae_v_len(0,n-1)); + + /* + * Scale right part: + * * MX stores max(|Bi|) + * * ScaleRight stores actual scaling applied to B when solving systems + * it is chosen to make |scaleRight*b| close to 1. + */ + mxb = 0; + for(i=0; i<=n-1; i++) + { + mxb = ae_maxreal(mxb, ae_fabs(bc.ptr.p_double[i], _state), _state); + } + if( ae_fp_eq(mxb,0) ) + { + mxb = 1; + } + scaleright = 1/mxb; + + /* + * First, non-iterative part of solution process. + * We use separate code for this task because + * XDot is quite slow and we want to save time. + */ + ae_v_moved(&xc.ptr.p_double[0], 1, &bc.ptr.p_double[0], 1, ae_v_len(0,n-1), scaleright); + densesolver_rbasiclusolve(lua, p, scalea, n, &xc, &tx, _state); + + /* + * Iterative refinement of xc: + * * calculate r = bc-A*xc using extra-precise dot product + * * solve A*y = r + * * update x:=x+r + * + * This cycle is executed until one of two things happens: + * 1. maximum number of iterations reached + * 2. last iteration decreased error to the lower limit + */ + if( havea ) + { + nrfs = densesolver_densesolverrfsmax(n, rep->r1, rep->rinf, _state); + terminatenexttime = ae_false; + for(rfs=0; rfs<=nrfs-1; rfs++) + { + if( terminatenexttime ) + { + break; + } + + /* + * generate right part + */ + smallerr = ae_true; + ae_v_move(&xb.ptr.p_double[0], 1, &xc.ptr.p_double[0], 1, ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + ae_v_moved(&xa.ptr.p_double[0], 1, &a->ptr.pp_double[i][0], 1, ae_v_len(0,n-1), scalea); + xa.ptr.p_double[n] = -1; + xb.ptr.p_double[n] = scaleright*bc.ptr.p_double[i]; + xdot(&xa, &xb, n+1, &tx, &v, &verr, _state); + y.ptr.p_double[i] = -v; + smallerr = smallerr&&ae_fp_less(ae_fabs(v, _state),4*verr); + } + if( smallerr ) + { + terminatenexttime = ae_true; + } + + /* + * solve and update + */ + densesolver_rbasiclusolve(lua, p, scalea, n, &y, &tx, _state); + ae_v_add(&xc.ptr.p_double[0], 1, &y.ptr.p_double[0], 1, ae_v_len(0,n-1)); + } + } + + /* + * Store xc. + * Post-scale result. + */ + v = scalea*mxb; + ae_v_moved(&x->ptr.pp_double[0][k], x->stride, &xc.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal Cholesky solver + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_spdmatrixcholeskysolveinternal(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* a, + ae_bool havea, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_vector xc; + ae_vector y; + ae_vector bc; + ae_vector xa; + ae_vector xb; + ae_vector tx; + double v; + double mxb; + double scaleright; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_vector_init(&xc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y, 0, DT_REAL, _state, ae_true); + ae_vector_init(&bc, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xa, 0, DT_REAL, _state, ae_true); + ae_vector_init(&xb, 0, DT_REAL, _state, ae_true); + ae_vector_init(&tx, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_greater(sqrtscalea,0), "Assertion failed", _state); + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(x, n, m, _state); + ae_vector_set_length(&y, n, _state); + ae_vector_set_length(&xc, n, _state); + ae_vector_set_length(&bc, n, _state); + ae_vector_set_length(&tx, n+1, _state); + ae_vector_set_length(&xa, n+1, _state); + ae_vector_set_length(&xb, n+1, _state); + + /* + * estimate condition number, test for near singularity + */ + rep->r1 = spdmatrixcholeskyrcond(cha, n, isupper, _state); + rep->rinf = rep->r1; + if( ae_fp_less(rep->r1,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_double[i][j] = 0; + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * solve + */ + for(k=0; k<=m-1; k++) + { + + /* + * copy B to contiguous storage + */ + ae_v_move(&bc.ptr.p_double[0], 1, &b->ptr.pp_double[0][k], b->stride, ae_v_len(0,n-1)); + + /* + * Scale right part: + * * MX stores max(|Bi|) + * * ScaleRight stores actual scaling applied to B when solving systems + * it is chosen to make |scaleRight*b| close to 1. + */ + mxb = 0; + for(i=0; i<=n-1; i++) + { + mxb = ae_maxreal(mxb, ae_fabs(bc.ptr.p_double[i], _state), _state); + } + if( ae_fp_eq(mxb,0) ) + { + mxb = 1; + } + scaleright = 1/mxb; + + /* + * First, non-iterative part of solution process. + * We use separate code for this task because + * XDot is quite slow and we want to save time. + */ + ae_v_moved(&xc.ptr.p_double[0], 1, &bc.ptr.p_double[0], 1, ae_v_len(0,n-1), scaleright); + densesolver_spdbasiccholeskysolve(cha, sqrtscalea, n, isupper, &xc, &tx, _state); + + /* + * Store xc. + * Post-scale result. + */ + v = ae_sqr(sqrtscalea, _state)*mxb; + ae_v_moved(&x->ptr.pp_double[0][k], x->stride, &xc.ptr.p_double[0], 1, ae_v_len(0,n-1), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal LU solver + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_cmatrixlusolveinternal(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Complex */ ae_matrix* a, + ae_bool havea, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t rfs; + ae_int_t nrfs; + ae_vector xc; + ae_vector y; + ae_vector bc; + ae_vector xa; + ae_vector xb; + ae_vector tx; + ae_vector tmpbuf; + ae_complex v; + double verr; + double mxb; + double scaleright; + ae_bool smallerr; + ae_bool terminatenexttime; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_vector_init(&xc, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&y, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&bc, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&xa, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&xb, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&tx, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&tmpbuf, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_greater(scalea,0), "Assertion failed", _state); + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + for(i=0; i<=n-1; i++) + { + if( p->ptr.p_int[i]>n-1||p->ptr.p_int[i]r1 = cmatrixlurcond1(lua, n, _state); + rep->rinf = cmatrixlurcondinf(lua, n, _state); + if( ae_fp_less(rep->r1,rcondthreshold(_state))||ae_fp_less(rep->rinf,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * solve + */ + for(k=0; k<=m-1; k++) + { + + /* + * copy B to contiguous storage + */ + ae_v_cmove(&bc.ptr.p_complex[0], 1, &b->ptr.pp_complex[0][k], b->stride, "N", ae_v_len(0,n-1)); + + /* + * Scale right part: + * * MX stores max(|Bi|) + * * ScaleRight stores actual scaling applied to B when solving systems + * it is chosen to make |scaleRight*b| close to 1. + */ + mxb = 0; + for(i=0; i<=n-1; i++) + { + mxb = ae_maxreal(mxb, ae_c_abs(bc.ptr.p_complex[i], _state), _state); + } + if( ae_fp_eq(mxb,0) ) + { + mxb = 1; + } + scaleright = 1/mxb; + + /* + * First, non-iterative part of solution process. + * We use separate code for this task because + * XDot is quite slow and we want to save time. + */ + ae_v_cmoved(&xc.ptr.p_complex[0], 1, &bc.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1), scaleright); + densesolver_cbasiclusolve(lua, p, scalea, n, &xc, &tx, _state); + + /* + * Iterative refinement of xc: + * * calculate r = bc-A*xc using extra-precise dot product + * * solve A*y = r + * * update x:=x+r + * + * This cycle is executed until one of two things happens: + * 1. maximum number of iterations reached + * 2. last iteration decreased error to the lower limit + */ + if( havea ) + { + nrfs = densesolver_densesolverrfsmax(n, rep->r1, rep->rinf, _state); + terminatenexttime = ae_false; + for(rfs=0; rfs<=nrfs-1; rfs++) + { + if( terminatenexttime ) + { + break; + } + + /* + * generate right part + */ + smallerr = ae_true; + ae_v_cmove(&xb.ptr.p_complex[0], 1, &xc.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + for(i=0; i<=n-1; i++) + { + ae_v_cmoved(&xa.ptr.p_complex[0], 1, &a->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,n-1), scalea); + xa.ptr.p_complex[n] = ae_complex_from_d(-1); + xb.ptr.p_complex[n] = ae_c_mul_d(bc.ptr.p_complex[i],scaleright); + xcdot(&xa, &xb, n+1, &tmpbuf, &v, &verr, _state); + y.ptr.p_complex[i] = ae_c_neg(v); + smallerr = smallerr&&ae_fp_less(ae_c_abs(v, _state),4*verr); + } + if( smallerr ) + { + terminatenexttime = ae_true; + } + + /* + * solve and update + */ + densesolver_cbasiclusolve(lua, p, scalea, n, &y, &tx, _state); + ae_v_cadd(&xc.ptr.p_complex[0], 1, &y.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1)); + } + } + + /* + * Store xc. + * Post-scale result. + */ + v = ae_complex_from_d(scalea*mxb); + ae_v_cmovec(&x->ptr.pp_complex[0][k], x->stride, &xc.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal Cholesky solver + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_hpdmatrixcholeskysolveinternal(/* Complex */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* a, + ae_bool havea, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_vector xc; + ae_vector y; + ae_vector bc; + ae_vector xa; + ae_vector xb; + ae_vector tx; + double v; + double mxb; + double scaleright; + + ae_frame_make(_state, &_frame_block); + *info = 0; + _densesolverreport_clear(rep); + ae_matrix_clear(x); + ae_vector_init(&xc, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&y, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&bc, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&xa, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&xb, 0, DT_COMPLEX, _state, ae_true); + ae_vector_init(&tx, 0, DT_COMPLEX, _state, ae_true); + + ae_assert(ae_fp_greater(sqrtscalea,0), "Assertion failed", _state); + + /* + * prepare: check inputs, allocate space... + */ + if( n<=0||m<=0 ) + { + *info = -1; + ae_frame_leave(_state); + return; + } + ae_matrix_set_length(x, n, m, _state); + ae_vector_set_length(&y, n, _state); + ae_vector_set_length(&xc, n, _state); + ae_vector_set_length(&bc, n, _state); + ae_vector_set_length(&tx, n+1, _state); + ae_vector_set_length(&xa, n+1, _state); + ae_vector_set_length(&xb, n+1, _state); + + /* + * estimate condition number, test for near singularity + */ + rep->r1 = hpdmatrixcholeskyrcond(cha, n, isupper, _state); + rep->rinf = rep->r1; + if( ae_fp_less(rep->r1,rcondthreshold(_state)) ) + { + for(i=0; i<=n-1; i++) + { + for(j=0; j<=m-1; j++) + { + x->ptr.pp_complex[i][j] = ae_complex_from_d(0); + } + } + rep->r1 = 0; + rep->rinf = 0; + *info = -3; + ae_frame_leave(_state); + return; + } + *info = 1; + + /* + * solve + */ + for(k=0; k<=m-1; k++) + { + + /* + * copy B to contiguous storage + */ + ae_v_cmove(&bc.ptr.p_complex[0], 1, &b->ptr.pp_complex[0][k], b->stride, "N", ae_v_len(0,n-1)); + + /* + * Scale right part: + * * MX stores max(|Bi|) + * * ScaleRight stores actual scaling applied to B when solving systems + * it is chosen to make |scaleRight*b| close to 1. + */ + mxb = 0; + for(i=0; i<=n-1; i++) + { + mxb = ae_maxreal(mxb, ae_c_abs(bc.ptr.p_complex[i], _state), _state); + } + if( ae_fp_eq(mxb,0) ) + { + mxb = 1; + } + scaleright = 1/mxb; + + /* + * First, non-iterative part of solution process. + * We use separate code for this task because + * XDot is quite slow and we want to save time. + */ + ae_v_cmoved(&xc.ptr.p_complex[0], 1, &bc.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1), scaleright); + densesolver_hpdbasiccholeskysolve(cha, sqrtscalea, n, isupper, &xc, &tx, _state); + + /* + * Store xc. + * Post-scale result. + */ + v = ae_sqr(sqrtscalea, _state)*mxb; + ae_v_cmoved(&x->ptr.pp_complex[0][k], x->stride, &xc.ptr.p_complex[0], 1, "N", ae_v_len(0,n-1), v); + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Internal subroutine. +Returns maximum count of RFS iterations as function of: +1. machine epsilon +2. task size. +3. condition number + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static ae_int_t densesolver_densesolverrfsmax(ae_int_t n, + double r1, + double rinf, + ae_state *_state) +{ + ae_int_t result; + + + result = 5; + return result; +} + + +/************************************************************************* +Internal subroutine. +Returns maximum count of RFS iterations as function of: +1. machine epsilon +2. task size. +3. norm-2 condition number + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static ae_int_t densesolver_densesolverrfsmaxv2(ae_int_t n, + double r2, + ae_state *_state) +{ + ae_int_t result; + + + result = densesolver_densesolverrfsmax(n, 0, 0, _state); + return result; +} + + +/************************************************************************* +Basic LU solver for ScaleA*PLU*x = y. + +This subroutine assumes that: +* L is well-scaled, and it is U which needs scaling by ScaleA. +* A=PLU is well-conditioned, so no zero divisions or overflow may occur + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_rbasiclusolve(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + double v; + + + for(i=0; i<=n-1; i++) + { + if( p->ptr.p_int[i]!=i ) + { + v = xb->ptr.p_double[i]; + xb->ptr.p_double[i] = xb->ptr.p_double[p->ptr.p_int[i]]; + xb->ptr.p_double[p->ptr.p_int[i]] = v; + } + } + for(i=1; i<=n-1; i++) + { + v = ae_v_dotproduct(&lua->ptr.pp_double[i][0], 1, &xb->ptr.p_double[0], 1, ae_v_len(0,i-1)); + xb->ptr.p_double[i] = xb->ptr.p_double[i]-v; + } + xb->ptr.p_double[n-1] = xb->ptr.p_double[n-1]/(scalea*lua->ptr.pp_double[n-1][n-1]); + for(i=n-2; i>=0; i--) + { + ae_v_moved(&tmp->ptr.p_double[i+1], 1, &lua->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), scalea); + v = ae_v_dotproduct(&tmp->ptr.p_double[i+1], 1, &xb->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1)); + xb->ptr.p_double[i] = (xb->ptr.p_double[i]-v)/(scalea*lua->ptr.pp_double[i][i]); + } +} + + +/************************************************************************* +Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y. + +This subroutine assumes that: +* A*ScaleA is well scaled +* A is well-conditioned, so no zero divisions or overflow may occur + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_spdbasiccholeskysolve(/* Real */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* xb, + /* Real */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + double v; + + + + /* + * A = L*L' or A=U'*U + */ + if( isupper ) + { + + /* + * Solve U'*y=b first. + */ + for(i=0; i<=n-1; i++) + { + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + if( iptr.p_double[i]; + ae_v_moved(&tmp->ptr.p_double[i+1], 1, &cha->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sqrtscalea); + ae_v_subd(&xb->ptr.p_double[i+1], 1, &tmp->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1), v); + } + } + + /* + * Solve U*x=y then. + */ + for(i=n-1; i>=0; i--) + { + if( iptr.p_double[i+1], 1, &cha->ptr.pp_double[i][i+1], 1, ae_v_len(i+1,n-1), sqrtscalea); + v = ae_v_dotproduct(&tmp->ptr.p_double[i+1], 1, &xb->ptr.p_double[i+1], 1, ae_v_len(i+1,n-1)); + xb->ptr.p_double[i] = xb->ptr.p_double[i]-v; + } + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + } + } + else + { + + /* + * Solve L*y=b first + */ + for(i=0; i<=n-1; i++) + { + if( i>0 ) + { + ae_v_moved(&tmp->ptr.p_double[0], 1, &cha->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sqrtscalea); + v = ae_v_dotproduct(&tmp->ptr.p_double[0], 1, &xb->ptr.p_double[0], 1, ae_v_len(0,i-1)); + xb->ptr.p_double[i] = xb->ptr.p_double[i]-v; + } + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + } + + /* + * Solve L'*x=y then. + */ + for(i=n-1; i>=0; i--) + { + xb->ptr.p_double[i] = xb->ptr.p_double[i]/(sqrtscalea*cha->ptr.pp_double[i][i]); + if( i>0 ) + { + v = xb->ptr.p_double[i]; + ae_v_moved(&tmp->ptr.p_double[0], 1, &cha->ptr.pp_double[i][0], 1, ae_v_len(0,i-1), sqrtscalea); + ae_v_subd(&xb->ptr.p_double[0], 1, &tmp->ptr.p_double[0], 1, ae_v_len(0,i-1), v); + } + } + } +} + + +/************************************************************************* +Basic LU solver for ScaleA*PLU*x = y. + +This subroutine assumes that: +* L is well-scaled, and it is U which needs scaling by ScaleA. +* A=PLU is well-conditioned, so no zero divisions or overflow may occur + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_cbasiclusolve(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + double scalea, + ae_int_t n, + /* Complex */ ae_vector* xb, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_complex v; + + + for(i=0; i<=n-1; i++) + { + if( p->ptr.p_int[i]!=i ) + { + v = xb->ptr.p_complex[i]; + xb->ptr.p_complex[i] = xb->ptr.p_complex[p->ptr.p_int[i]]; + xb->ptr.p_complex[p->ptr.p_int[i]] = v; + } + } + for(i=1; i<=n-1; i++) + { + v = ae_v_cdotproduct(&lua->ptr.pp_complex[i][0], 1, "N", &xb->ptr.p_complex[0], 1, "N", ae_v_len(0,i-1)); + xb->ptr.p_complex[i] = ae_c_sub(xb->ptr.p_complex[i],v); + } + xb->ptr.p_complex[n-1] = ae_c_div(xb->ptr.p_complex[n-1],ae_c_mul_d(lua->ptr.pp_complex[n-1][n-1],scalea)); + for(i=n-2; i>=0; i--) + { + ae_v_cmoved(&tmp->ptr.p_complex[i+1], 1, &lua->ptr.pp_complex[i][i+1], 1, "N", ae_v_len(i+1,n-1), scalea); + v = ae_v_cdotproduct(&tmp->ptr.p_complex[i+1], 1, "N", &xb->ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1)); + xb->ptr.p_complex[i] = ae_c_div(ae_c_sub(xb->ptr.p_complex[i],v),ae_c_mul_d(lua->ptr.pp_complex[i][i],scalea)); + } +} + + +/************************************************************************* +Basic Cholesky solver for ScaleA*Cholesky(A)'*x = y. + +This subroutine assumes that: +* A*ScaleA is well scaled +* A is well-conditioned, so no zero divisions or overflow may occur + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +static void densesolver_hpdbasiccholeskysolve(/* Complex */ ae_matrix* cha, + double sqrtscalea, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* xb, + /* Complex */ ae_vector* tmp, + ae_state *_state) +{ + ae_int_t i; + ae_complex v; + + + + /* + * A = L*L' or A=U'*U + */ + if( isupper ) + { + + /* + * Solve U'*y=b first. + */ + for(i=0; i<=n-1; i++) + { + xb->ptr.p_complex[i] = ae_c_div(xb->ptr.p_complex[i],ae_c_mul_d(ae_c_conj(cha->ptr.pp_complex[i][i], _state),sqrtscalea)); + if( iptr.p_complex[i]; + ae_v_cmoved(&tmp->ptr.p_complex[i+1], 1, &cha->ptr.pp_complex[i][i+1], 1, "Conj", ae_v_len(i+1,n-1), sqrtscalea); + ae_v_csubc(&xb->ptr.p_complex[i+1], 1, &tmp->ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1), v); + } + } + + /* + * Solve U*x=y then. + */ + for(i=n-1; i>=0; i--) + { + if( iptr.p_complex[i+1], 1, &cha->ptr.pp_complex[i][i+1], 1, "N", ae_v_len(i+1,n-1), sqrtscalea); + v = ae_v_cdotproduct(&tmp->ptr.p_complex[i+1], 1, "N", &xb->ptr.p_complex[i+1], 1, "N", ae_v_len(i+1,n-1)); + xb->ptr.p_complex[i] = ae_c_sub(xb->ptr.p_complex[i],v); + } + xb->ptr.p_complex[i] = ae_c_div(xb->ptr.p_complex[i],ae_c_mul_d(cha->ptr.pp_complex[i][i],sqrtscalea)); + } + } + else + { + + /* + * Solve L*y=b first + */ + for(i=0; i<=n-1; i++) + { + if( i>0 ) + { + ae_v_cmoved(&tmp->ptr.p_complex[0], 1, &cha->ptr.pp_complex[i][0], 1, "N", ae_v_len(0,i-1), sqrtscalea); + v = ae_v_cdotproduct(&tmp->ptr.p_complex[0], 1, "N", &xb->ptr.p_complex[0], 1, "N", ae_v_len(0,i-1)); + xb->ptr.p_complex[i] = ae_c_sub(xb->ptr.p_complex[i],v); + } + xb->ptr.p_complex[i] = ae_c_div(xb->ptr.p_complex[i],ae_c_mul_d(cha->ptr.pp_complex[i][i],sqrtscalea)); + } + + /* + * Solve L'*x=y then. + */ + for(i=n-1; i>=0; i--) + { + xb->ptr.p_complex[i] = ae_c_div(xb->ptr.p_complex[i],ae_c_mul_d(ae_c_conj(cha->ptr.pp_complex[i][i], _state),sqrtscalea)); + if( i>0 ) + { + v = xb->ptr.p_complex[i]; + ae_v_cmoved(&tmp->ptr.p_complex[0], 1, &cha->ptr.pp_complex[i][0], 1, "Conj", ae_v_len(0,i-1), sqrtscalea); + ae_v_csubc(&xb->ptr.p_complex[0], 1, &tmp->ptr.p_complex[0], 1, "N", ae_v_len(0,i-1), v); + } + } + } +} + + +ae_bool _densesolverreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + densesolverreport *p = (densesolverreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _densesolverreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + densesolverreport *dst = (densesolverreport*)_dst; + densesolverreport *src = (densesolverreport*)_src; + dst->r1 = src->r1; + dst->rinf = src->rinf; + return ae_true; +} + + +void _densesolverreport_clear(void* _p) +{ + densesolverreport *p = (densesolverreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _densesolverreport_destroy(void* _p) +{ + densesolverreport *p = (densesolverreport*)_p; + ae_touch_ptr((void*)p); +} + + +ae_bool _densesolverlsreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + densesolverlsreport *p = (densesolverlsreport*)_p; + ae_touch_ptr((void*)p); + if( !ae_matrix_init(&p->cx, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _densesolverlsreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + densesolverlsreport *dst = (densesolverlsreport*)_dst; + densesolverlsreport *src = (densesolverlsreport*)_src; + dst->r2 = src->r2; + if( !ae_matrix_init_copy(&dst->cx, &src->cx, _state, make_automatic) ) + return ae_false; + dst->n = src->n; + dst->k = src->k; + return ae_true; +} + + +void _densesolverlsreport_clear(void* _p) +{ + densesolverlsreport *p = (densesolverlsreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_clear(&p->cx); +} + + +void _densesolverlsreport_destroy(void* _p) +{ + densesolverlsreport *p = (densesolverlsreport*)_p; + ae_touch_ptr((void*)p); + ae_matrix_destroy(&p->cx); +} + + + + +/************************************************************************* +This function initializes linear LSQR Solver. This solver is used to solve +non-symmetric (and, possibly, non-square) problems. Least squares solution +is returned for non-compatible systems. + +USAGE: +1. User initializes algorithm state with LinLSQRCreate() call +2. User tunes solver parameters with LinLSQRSetCond() and other functions +3. User calls LinLSQRSolveSparse() function which takes algorithm state + and SparseMatrix object. +4. User calls LinLSQRResults() to get solution +5. Optionally, user may call LinLSQRSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinLSQRState structure. + +INPUT PARAMETERS: + M - number of rows in A + N - number of variables, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrcreate(ae_int_t m, + ae_int_t n, + linlsqrstate* state, + ae_state *_state) +{ + ae_int_t i; + + _linlsqrstate_clear(state); + + ae_assert(m>0, "LinLSQRCreate: M<=0", _state); + ae_assert(n>0, "LinLSQRCreate: N<=0", _state); + state->m = m; + state->n = n; + state->prectype = 0; + state->epsa = linlsqr_atol; + state->epsb = linlsqr_btol; + state->epsc = 1/ae_sqrt(ae_machineepsilon, _state); + state->maxits = 0; + state->lambdai = 0; + state->xrep = ae_false; + state->running = ae_false; + + /* + * * allocate arrays + * * set RX to NAN (just for the case user calls Results() without + * calling SolveSparse() + * * set B to zero + */ + normestimatorcreate(m, n, 2, 2, &state->nes, _state); + ae_vector_set_length(&state->rx, state->n, _state); + ae_vector_set_length(&state->ui, state->m+state->n, _state); + ae_vector_set_length(&state->uip1, state->m+state->n, _state); + ae_vector_set_length(&state->vip1, state->n, _state); + ae_vector_set_length(&state->vi, state->n, _state); + ae_vector_set_length(&state->omegai, state->n, _state); + ae_vector_set_length(&state->omegaip1, state->n, _state); + ae_vector_set_length(&state->d, state->n, _state); + ae_vector_set_length(&state->x, state->m+state->n, _state); + ae_vector_set_length(&state->mv, state->m+state->n, _state); + ae_vector_set_length(&state->mtv, state->n, _state); + ae_vector_set_length(&state->b, state->m, _state); + for(i=0; i<=n-1; i++) + { + state->rx.ptr.p_double[i] = _state->v_nan; + } + for(i=0; i<=m-1; i++) + { + state->b.ptr.p_double[i] = 0; + } + ae_vector_set_length(&state->rstate.ia, 1+1, _state); + ae_vector_set_length(&state->rstate.ra, 0+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +This function sets right part. By default, right part is zero. + +INPUT PARAMETERS: + B - right part, array[N]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetb(linlsqrstate* state, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(!state->running, "LinLSQRSetB: you can not change B when LinLSQRIteration is running", _state); + ae_assert(state->m<=b->cnt, "LinLSQRSetB: Length(B)m, _state), "LinLSQRSetB: B contains infinite or NaN values", _state); + state->bnorm2 = 0; + for(i=0; i<=state->m-1; i++) + { + state->b.ptr.p_double[i] = b->ptr.p_double[i]; + state->bnorm2 = state->bnorm2+b->ptr.p_double[i]*b->ptr.p_double[i]; + } +} + + +/************************************************************************* +This function changes preconditioning settings of LinLSQQSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecunit(linlsqrstate* state, ae_state *_state) +{ + + + ae_assert(!state->running, "LinLSQRSetPrecUnit: you can not change preconditioner, because function LinLSQRIteration is running!", _state); + state->prectype = -1; +} + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecdiag(linlsqrstate* state, ae_state *_state) +{ + + + ae_assert(!state->running, "LinLSQRSetPrecDiag: you can not change preconditioner, because function LinCGIteration is running!", _state); + state->prectype = 0; +} + + +/************************************************************************* +This function sets optional Tikhonov regularization coefficient. +It is zero by default. + +INPUT PARAMETERS: + LambdaI - regularization factor, LambdaI>=0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetlambdai(linlsqrstate* state, + double lambdai, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinLSQRSetLambdaI: you can not set LambdaI, because function LinLSQRIteration is running", _state); + ae_assert(ae_isfinite(lambdai, _state)&&ae_fp_greater_eq(lambdai,0), "LinLSQRSetLambdaI: LambdaI is infinite or NaN", _state); + state->lambdai = lambdai; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +ae_bool linlsqriteration(linlsqrstate* state, ae_state *_state) +{ + ae_int_t summn; + double bnorm; + ae_int_t i; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + summn = state->rstate.ia.ptr.p_int[0]; + i = state->rstate.ia.ptr.p_int[1]; + bnorm = state->rstate.ra.ptr.p_double[0]; + } + else + { + summn = -983; + i = -989; + bnorm = -834; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + + /* + * Routine body + */ + ae_assert(state->b.cnt>0, "LinLSQRIteration: using non-allocated array B", _state); + bnorm = ae_sqrt(state->bnorm2, _state); + state->running = ae_true; + state->repnmv = 0; + linlsqr_clearrfields(state, _state); + state->repiterationscount = 0; + summn = state->m+state->n; + state->r2 = state->bnorm2; + + /* + *estimate for ANorm + */ + normestimatorrestart(&state->nes, _state); +lbl_7: + if( !normestimatoriteration(&state->nes, _state) ) + { + goto lbl_8; + } + if( !state->nes.needmv ) + { + goto lbl_9; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->nes.x.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + linlsqr_clearrfields(state, _state); + state->needmv = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needmv = ae_false; + ae_v_move(&state->nes.mv.ptr.p_double[0], 1, &state->mv.ptr.p_double[0], 1, ae_v_len(0,state->m-1)); + goto lbl_7; +lbl_9: + if( !state->nes.needmtv ) + { + goto lbl_11; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->nes.x.ptr.p_double[0], 1, ae_v_len(0,state->m-1)); + + /* + *matrix-vector multiplication + */ + state->repnmv = state->repnmv+1; + linlsqr_clearrfields(state, _state); + state->needmtv = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->needmtv = ae_false; + ae_v_move(&state->nes.mtv.ptr.p_double[0], 1, &state->mtv.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + goto lbl_7; +lbl_11: + goto lbl_7; +lbl_8: + normestimatorresults(&state->nes, &state->anorm, _state); + + /* + *initialize .RX by zeros + */ + for(i=0; i<=state->n-1; i++) + { + state->rx.ptr.p_double[i] = 0; + } + + /* + *output first report + */ + if( !state->xrep ) + { + goto lbl_13; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + linlsqr_clearrfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->xupdated = ae_false; +lbl_13: + + /* + * LSQR, Step 0. + * + * Algorithm outline corresponds to one which was described at p.50 of + * "LSQR - an algorithm for sparse linear equations and sparse least + * squares" by C.Paige and M.Saunders with one small addition - we + * explicitly extend system matrix by additional N lines in order + * to handle non-zero lambda, i.e. original A is replaced by + * [ A ] + * A_mod = [ ] + * [ lambda*I ]. + * + * Step 0: + * x[0] = 0 + * beta[1]*u[1] = b + * alpha[1]*v[1] = A_mod'*u[1] + * w[1] = v[1] + * phiBar[1] = beta[1] + * rhoBar[1] = alpha[1] + * d[0] = 0 + * + * NOTE: + * There are three criteria for stopping: + * (S0) maximum number of iterations + * (S1) ||Rk||<=EpsB*||B||; + * (S2) ||A^T*Rk||/(||A||*||Rk||)<=EpsA. + * It is very important that S2 always checked AFTER S1. It is necessary + * to avoid division by zero when Rk=0. + */ + state->betai = bnorm; + if( ae_fp_eq(state->betai,0) ) + { + + /* + * Zero right part + */ + state->running = ae_false; + state->repterminationtype = 1; + result = ae_false; + return result; + } + for(i=0; i<=summn-1; i++) + { + if( im ) + { + state->ui.ptr.p_double[i] = state->b.ptr.p_double[i]/state->betai; + } + else + { + state->ui.ptr.p_double[i] = 0; + } + state->x.ptr.p_double[i] = state->ui.ptr.p_double[i]; + } + state->repnmv = state->repnmv+1; + linlsqr_clearrfields(state, _state); + state->needmtv = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needmtv = ae_false; + for(i=0; i<=state->n-1; i++) + { + state->mtv.ptr.p_double[i] = state->mtv.ptr.p_double[i]+state->lambdai*state->ui.ptr.p_double[state->m+i]; + } + state->alphai = 0; + for(i=0; i<=state->n-1; i++) + { + state->alphai = state->alphai+state->mtv.ptr.p_double[i]*state->mtv.ptr.p_double[i]; + } + state->alphai = ae_sqrt(state->alphai, _state); + if( ae_fp_eq(state->alphai,0) ) + { + + /* + * Orthogonality stopping criterion is met + */ + state->running = ae_false; + state->repterminationtype = 4; + result = ae_false; + return result; + } + for(i=0; i<=state->n-1; i++) + { + state->vi.ptr.p_double[i] = state->mtv.ptr.p_double[i]/state->alphai; + state->omegai.ptr.p_double[i] = state->vi.ptr.p_double[i]; + } + state->phibari = state->betai; + state->rhobari = state->alphai; + for(i=0; i<=state->n-1; i++) + { + state->d.ptr.p_double[i] = 0; + } + state->dnorm = 0; + + /* + * Steps I=1, 2, ... + */ +lbl_15: + if( ae_false ) + { + goto lbl_16; + } + + /* + * At I-th step State.RepIterationsCount=I. + */ + state->repiterationscount = state->repiterationscount+1; + + /* + * Bidiagonalization part: + * beta[i+1]*u[i+1] = A_mod*v[i]-alpha[i]*u[i] + * alpha[i+1]*v[i+1] = A_mod'*u[i+1] - beta[i+1]*v[i] + * + * NOTE: beta[i+1]=0 or alpha[i+1]=0 will lead to successful termination + * in the end of the current iteration. In this case u/v are zero. + * NOTE2: algorithm won't fail on zero alpha or beta (there will be no + * division by zero because it will be stopped BEFORE division + * occurs). However, near-zero alpha and beta won't stop algorithm + * and, although no division by zero will happen, orthogonality + * in U and V will be lost. + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->vi.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + linlsqr_clearrfields(state, _state); + state->needmv = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->needmv = ae_false; + for(i=0; i<=state->n-1; i++) + { + state->mv.ptr.p_double[state->m+i] = state->lambdai*state->vi.ptr.p_double[i]; + } + state->betaip1 = 0; + for(i=0; i<=summn-1; i++) + { + state->uip1.ptr.p_double[i] = state->mv.ptr.p_double[i]-state->alphai*state->ui.ptr.p_double[i]; + state->betaip1 = state->betaip1+state->uip1.ptr.p_double[i]*state->uip1.ptr.p_double[i]; + } + if( ae_fp_neq(state->betaip1,0) ) + { + state->betaip1 = ae_sqrt(state->betaip1, _state); + for(i=0; i<=summn-1; i++) + { + state->uip1.ptr.p_double[i] = state->uip1.ptr.p_double[i]/state->betaip1; + } + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->uip1.ptr.p_double[0], 1, ae_v_len(0,state->m-1)); + state->repnmv = state->repnmv+1; + linlsqr_clearrfields(state, _state); + state->needmtv = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->needmtv = ae_false; + for(i=0; i<=state->n-1; i++) + { + state->mtv.ptr.p_double[i] = state->mtv.ptr.p_double[i]+state->lambdai*state->uip1.ptr.p_double[state->m+i]; + } + state->alphaip1 = 0; + for(i=0; i<=state->n-1; i++) + { + state->vip1.ptr.p_double[i] = state->mtv.ptr.p_double[i]-state->betaip1*state->vi.ptr.p_double[i]; + state->alphaip1 = state->alphaip1+state->vip1.ptr.p_double[i]*state->vip1.ptr.p_double[i]; + } + if( ae_fp_neq(state->alphaip1,0) ) + { + state->alphaip1 = ae_sqrt(state->alphaip1, _state); + for(i=0; i<=state->n-1; i++) + { + state->vip1.ptr.p_double[i] = state->vip1.ptr.p_double[i]/state->alphaip1; + } + } + + /* + * Build next orthogonal transformation + */ + state->rhoi = safepythag2(state->rhobari, state->betaip1, _state); + state->ci = state->rhobari/state->rhoi; + state->si = state->betaip1/state->rhoi; + state->theta = state->si*state->alphaip1; + state->rhobarip1 = -state->ci*state->alphaip1; + state->phii = state->ci*state->phibari; + state->phibarip1 = state->si*state->phibari; + + /* + * Update .RNorm + * + * This tricky formula is necessary because simply writing + * State.R2:=State.PhiBarIP1*State.PhiBarIP1 does NOT guarantees + * monotonic decrease of R2. Roundoff error combined with 80-bit + * precision used internally by Intel chips allows R2 to increase + * slightly in some rare, but possible cases. This property is + * undesirable, so we prefer to guard against R increase. + */ + state->r2 = ae_minreal(state->r2, state->phibarip1*state->phibarip1, _state); + + /* + * Update d and DNorm, check condition-related stopping criteria + */ + for(i=0; i<=state->n-1; i++) + { + state->d.ptr.p_double[i] = 1/state->rhoi*(state->vi.ptr.p_double[i]-state->theta*state->d.ptr.p_double[i]); + state->dnorm = state->dnorm+state->d.ptr.p_double[i]*state->d.ptr.p_double[i]; + } + if( ae_fp_greater_eq(ae_sqrt(state->dnorm, _state)*state->anorm,state->epsc) ) + { + state->running = ae_false; + state->repterminationtype = 7; + result = ae_false; + return result; + } + + /* + * Update x, output report + */ + for(i=0; i<=state->n-1; i++) + { + state->rx.ptr.p_double[i] = state->rx.ptr.p_double[i]+state->phii/state->rhoi*state->omegai.ptr.p_double[i]; + } + if( !state->xrep ) + { + goto lbl_17; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + linlsqr_clearrfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->xupdated = ae_false; +lbl_17: + + /* + * Check stopping criteria + * 1. achieved required number of iterations; + * 2. ||Rk||<=EpsB*||B||; + * 3. ||A^T*Rk||/(||A||*||Rk||)<=EpsA; + */ + if( state->maxits>0&&state->repiterationscount>=state->maxits ) + { + + /* + * Achieved required number of iterations + */ + state->running = ae_false; + state->repterminationtype = 5; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->phibarip1,state->epsb*bnorm) ) + { + + /* + * ||Rk||<=EpsB*||B||, here ||Rk||=PhiBar + */ + state->running = ae_false; + state->repterminationtype = 1; + result = ae_false; + return result; + } + if( ae_fp_less_eq(state->alphaip1*ae_fabs(state->ci, _state)/state->anorm,state->epsa) ) + { + + /* + * ||A^T*Rk||/(||A||*||Rk||)<=EpsA, here ||A^T*Rk||=PhiBar*Alpha[i+1]*|.C| + */ + state->running = ae_false; + state->repterminationtype = 4; + result = ae_false; + return result; + } + + /* + * Update omega + */ + for(i=0; i<=state->n-1; i++) + { + state->omegaip1.ptr.p_double[i] = state->vip1.ptr.p_double[i]-state->theta/state->rhoi*state->omegai.ptr.p_double[i]; + } + + /* + * Prepare for the next iteration - rename variables: + * u[i] := u[i+1] + * v[i] := v[i+1] + * rho[i] := rho[i+1] + * ... + */ + ae_v_move(&state->ui.ptr.p_double[0], 1, &state->uip1.ptr.p_double[0], 1, ae_v_len(0,summn-1)); + ae_v_move(&state->vi.ptr.p_double[0], 1, &state->vip1.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_v_move(&state->omegai.ptr.p_double[0], 1, &state->omegaip1.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->alphai = state->alphaip1; + state->betai = state->betaip1; + state->phibari = state->phibarip1; + state->rhobari = state->rhobarip1; + goto lbl_15; +lbl_16: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = summn; + state->rstate.ia.ptr.p_int[1] = i; + state->rstate.ra.ptr.p_double[0] = bnorm; + return result; +} + + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse M*N matrix in the CRS format (you MUST contvert it + to CRS format by calling SparseConvertToCRS() function + BEFORE you pass it to this function). + B - right part, array[M] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinLSQRSetPrecUnit(). However, preconditioning cost is low + and preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsolvesparse(linlsqrstate* state, + sparsematrix* a, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + ae_int_t j; + ae_int_t t0; + ae_int_t t1; + double v; + + + n = state->n; + ae_assert(!state->running, "LinLSQRSolveSparse: you can not call this function when LinLSQRIteration is running", _state); + ae_assert(b->cnt>=state->m, "LinLSQRSolveSparse: Length(B)m, _state), "LinLSQRSolveSparse: B contains infinite or NaN values", _state); + + /* + * Allocate temporaries + */ + rvectorsetlengthatleast(&state->tmpd, n, _state); + rvectorsetlengthatleast(&state->tmpx, n, _state); + + /* + * Compute diagonal scaling matrix D + */ + if( state->prectype==0 ) + { + + /* + * Default preconditioner - inverse of column norms + */ + for(i=0; i<=n-1; i++) + { + state->tmpd.ptr.p_double[i] = 0; + } + t0 = 0; + t1 = 0; + while(sparseenumerate(a, &t0, &t1, &i, &j, &v, _state)) + { + state->tmpd.ptr.p_double[j] = state->tmpd.ptr.p_double[j]+ae_sqr(v, _state); + } + for(i=0; i<=n-1; i++) + { + if( ae_fp_greater(state->tmpd.ptr.p_double[i],0) ) + { + state->tmpd.ptr.p_double[i] = 1/ae_sqrt(state->tmpd.ptr.p_double[i], _state); + } + else + { + state->tmpd.ptr.p_double[i] = 1; + } + } + } + else + { + + /* + * No diagonal scaling + */ + for(i=0; i<=n-1; i++) + { + state->tmpd.ptr.p_double[i] = 1; + } + } + + /* + * Solve. + * + * Instead of solving A*x=b we solve preconditioned system (A*D)*(inv(D)*x)=b. + * Transformed A is not calculated explicitly, we just modify multiplication + * by A or A'. After solution we modify State.RX so it will store untransformed + * variables + */ + linlsqrsetb(state, b, _state); + linlsqrrestart(state, _state); + while(linlsqriteration(state, _state)) + { + if( state->needmv ) + { + for(i=0; i<=n-1; i++) + { + state->tmpx.ptr.p_double[i] = state->tmpd.ptr.p_double[i]*state->x.ptr.p_double[i]; + } + sparsemv(a, &state->tmpx, &state->mv, _state); + } + if( state->needmtv ) + { + sparsemtv(a, &state->x, &state->mtv, _state); + for(i=0; i<=n-1; i++) + { + state->mtv.ptr.p_double[i] = state->tmpd.ptr.p_double[i]*state->mtv.ptr.p_double[i]; + } + } + } + for(i=0; i<=n-1; i++) + { + state->rx.ptr.p_double[i] = state->tmpd.ptr.p_double[i]*state->rx.ptr.p_double[i]; + } +} + + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsA - algorithm will be stopped if ||A^T*Rk||/(||A||*||Rk||)<=EpsA. + EpsB - algorithm will be stopped if ||Rk||<=EpsB*||B|| + MaxIts - algorithm will be stopped if number of iterations + more than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: if EpsA,EpsB,EpsC and MaxIts are zero then these variables will +be setted as default values. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetcond(linlsqrstate* state, + double epsa, + double epsb, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinLSQRSetCond: you can not call this function when LinLSQRIteration is running", _state); + ae_assert(ae_isfinite(epsa, _state)&&ae_fp_greater_eq(epsa,0), "LinLSQRSetCond: EpsA is negative, INF or NAN", _state); + ae_assert(ae_isfinite(epsb, _state)&&ae_fp_greater_eq(epsb,0), "LinLSQRSetCond: EpsB is negative, INF or NAN", _state); + ae_assert(maxits>=0, "LinLSQRSetCond: MaxIts is negative", _state); + if( (ae_fp_eq(epsa,0)&&ae_fp_eq(epsb,0))&&maxits==0 ) + { + state->epsa = linlsqr_atol; + state->epsb = linlsqr_btol; + state->maxits = state->n; + } + else + { + state->epsa = epsa; + state->epsb = epsb; + state->maxits = maxits; + } +} + + +/************************************************************************* +LSQR solver: results. + +This function must be called after LinLSQRSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * 1 ||Rk||<=EpsB*||B|| + * 4 ||A^T*Rk||/(||A||*||Rk||)<=EpsA + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + X contains best point found so far. + (sometimes returned on singular systems) + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrresults(linlsqrstate* state, + /* Real */ ae_vector* x, + linlsqrreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _linlsqrreport_clear(rep); + + ae_assert(!state->running, "LinLSQRResult: you can not call this function when LinLSQRIteration is running", _state); + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nmv = state->repnmv; + rep->terminationtype = state->repterminationtype; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetxrep(linlsqrstate* state, + ae_bool needxrep, + ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +This function restarts LinLSQRIteration + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrrestart(linlsqrstate* state, ae_state *_state) +{ + + + ae_vector_set_length(&state->rstate.ia, 1+1, _state); + ae_vector_set_length(&state->rstate.ra, 0+1, _state); + state->rstate.stage = -1; + linlsqr_clearrfields(state, _state); +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void linlsqr_clearrfields(linlsqrstate* state, ae_state *_state) +{ + + + state->xupdated = ae_false; + state->needmv = ae_false; + state->needmtv = ae_false; + state->needmv2 = ae_false; + state->needvmv = ae_false; + state->needprec = ae_false; +} + + +ae_bool _linlsqrstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + linlsqrstate *p = (linlsqrstate*)_p; + ae_touch_ptr((void*)p); + if( !_normestimatorstate_init(&p->nes, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->ui, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->uip1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->vi, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->vip1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->omegai, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->omegaip1, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->d, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mtv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpd, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _linlsqrstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + linlsqrstate *dst = (linlsqrstate*)_dst; + linlsqrstate *src = (linlsqrstate*)_src; + if( !_normestimatorstate_init_copy(&dst->nes, &src->nes, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rx, &src->rx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + dst->n = src->n; + dst->m = src->m; + dst->prectype = src->prectype; + if( !ae_vector_init_copy(&dst->ui, &src->ui, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->uip1, &src->uip1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->vi, &src->vi, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->vip1, &src->vip1, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->omegai, &src->omegai, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->omegaip1, &src->omegaip1, _state, make_automatic) ) + return ae_false; + dst->alphai = src->alphai; + dst->alphaip1 = src->alphaip1; + dst->betai = src->betai; + dst->betaip1 = src->betaip1; + dst->phibari = src->phibari; + dst->phibarip1 = src->phibarip1; + dst->phii = src->phii; + dst->rhobari = src->rhobari; + dst->rhobarip1 = src->rhobarip1; + dst->rhoi = src->rhoi; + dst->ci = src->ci; + dst->si = src->si; + dst->theta = src->theta; + dst->lambdai = src->lambdai; + if( !ae_vector_init_copy(&dst->d, &src->d, _state, make_automatic) ) + return ae_false; + dst->anorm = src->anorm; + dst->bnorm2 = src->bnorm2; + dst->dnorm = src->dnorm; + dst->r2 = src->r2; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mv, &src->mv, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mtv, &src->mtv, _state, make_automatic) ) + return ae_false; + dst->epsa = src->epsa; + dst->epsb = src->epsb; + dst->epsc = src->epsc; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->xupdated = src->xupdated; + dst->needmv = src->needmv; + dst->needmtv = src->needmtv; + dst->needmv2 = src->needmv2; + dst->needvmv = src->needvmv; + dst->needprec = src->needprec; + dst->repiterationscount = src->repiterationscount; + dst->repnmv = src->repnmv; + dst->repterminationtype = src->repterminationtype; + dst->running = src->running; + if( !ae_vector_init_copy(&dst->tmpd, &src->tmpd, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->tmpx, &src->tmpx, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _linlsqrstate_clear(void* _p) +{ + linlsqrstate *p = (linlsqrstate*)_p; + ae_touch_ptr((void*)p); + _normestimatorstate_clear(&p->nes); + ae_vector_clear(&p->rx); + ae_vector_clear(&p->b); + ae_vector_clear(&p->ui); + ae_vector_clear(&p->uip1); + ae_vector_clear(&p->vi); + ae_vector_clear(&p->vip1); + ae_vector_clear(&p->omegai); + ae_vector_clear(&p->omegaip1); + ae_vector_clear(&p->d); + ae_vector_clear(&p->x); + ae_vector_clear(&p->mv); + ae_vector_clear(&p->mtv); + ae_vector_clear(&p->tmpd); + ae_vector_clear(&p->tmpx); + _rcommstate_clear(&p->rstate); +} + + +void _linlsqrstate_destroy(void* _p) +{ + linlsqrstate *p = (linlsqrstate*)_p; + ae_touch_ptr((void*)p); + _normestimatorstate_destroy(&p->nes); + ae_vector_destroy(&p->rx); + ae_vector_destroy(&p->b); + ae_vector_destroy(&p->ui); + ae_vector_destroy(&p->uip1); + ae_vector_destroy(&p->vi); + ae_vector_destroy(&p->vip1); + ae_vector_destroy(&p->omegai); + ae_vector_destroy(&p->omegaip1); + ae_vector_destroy(&p->d); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->mv); + ae_vector_destroy(&p->mtv); + ae_vector_destroy(&p->tmpd); + ae_vector_destroy(&p->tmpx); + _rcommstate_destroy(&p->rstate); +} + + +ae_bool _linlsqrreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + linlsqrreport *p = (linlsqrreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _linlsqrreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + linlsqrreport *dst = (linlsqrreport*)_dst; + linlsqrreport *src = (linlsqrreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nmv = src->nmv; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _linlsqrreport_clear(void* _p) +{ + linlsqrreport *p = (linlsqrreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _linlsqrreport_destroy(void* _p) +{ + linlsqrreport *p = (linlsqrreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* +This function initializes linear CG Solver. This solver is used to solve +symmetric positive definite problems. If you want to solve nonsymmetric +(or non-positive definite) problem you may use LinLSQR solver provided by +ALGLIB. + +USAGE: +1. User initializes algorithm state with LinCGCreate() call +2. User tunes solver parameters with LinCGSetCond() and other functions +3. Optionally, user sets starting point with LinCGSetStartingPoint() +4. User calls LinCGSolveSparse() function which takes algorithm state and + SparseMatrix object. +5. User calls LinCGResults() to get solution +6. Optionally, user may call LinCGSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinCGState structure. + +INPUT PARAMETERS: + N - problem dimension, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgcreate(ae_int_t n, lincgstate* state, ae_state *_state) +{ + ae_int_t i; + + _lincgstate_clear(state); + + ae_assert(n>0, "LinCGCreate: N<=0", _state); + state->n = n; + state->prectype = 0; + state->itsbeforerestart = n; + state->itsbeforerupdate = 10; + state->epsf = lincg_defaultprecision; + state->maxits = 0; + state->xrep = ae_false; + state->running = ae_false; + + /* + * * allocate arrays + * * set RX to NAN (just for the case user calls Results() without + * calling SolveSparse() + * * set starting point to zero + * * we do NOT initialize B here because we assume that user should + * initializate it using LinCGSetB() function. In case he forgets + * to do so, exception will be thrown in the LinCGIteration(). + */ + ae_vector_set_length(&state->rx, state->n, _state); + ae_vector_set_length(&state->startx, state->n, _state); + ae_vector_set_length(&state->b, state->n, _state); + for(i=0; i<=state->n-1; i++) + { + state->rx.ptr.p_double[i] = _state->v_nan; + state->startx.ptr.p_double[i] = 0.0; + state->b.ptr.p_double[i] = 0; + } + ae_vector_set_length(&state->cx, state->n, _state); + ae_vector_set_length(&state->p, state->n, _state); + ae_vector_set_length(&state->r, state->n, _state); + ae_vector_set_length(&state->cr, state->n, _state); + ae_vector_set_length(&state->z, state->n, _state); + ae_vector_set_length(&state->cz, state->n, _state); + ae_vector_set_length(&state->x, state->n, _state); + ae_vector_set_length(&state->mv, state->n, _state); + ae_vector_set_length(&state->pv, state->n, _state); + lincg_updateitersdata(state, _state); + ae_vector_set_length(&state->rstate.ia, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; +} + + +/************************************************************************* +This function sets starting point. +By default, zero starting point is used. + +INPUT PARAMETERS: + X - starting point, array[N] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetstartingpoint(lincgstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetStartingPoint: you can not change starting point because LinCGIteration() function is running", _state); + ae_assert(state->n<=x->cnt, "LinCGSetStartingPoint: Length(X)n, _state), "LinCGSetStartingPoint: X contains infinite or NaN values!", _state); + ae_v_move(&state->startx.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); +} + + +/************************************************************************* +This function sets right part. By default, right part is zero. + +INPUT PARAMETERS: + B - right part, array[N]. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetb(lincgstate* state, + /* Real */ ae_vector* b, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetB: you can not set B, because function LinCGIteration is running!", _state); + ae_assert(b->cnt>=state->n, "LinCGSetB: Length(B)n, _state), "LinCGSetB: B contains infinite or NaN values!", _state); + ae_v_move(&state->b.ptr.p_double[0], 1, &b->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); +} + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecunit(lincgstate* state, ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetPrecUnit: you can not change preconditioner, because function LinCGIteration is running!", _state); + state->prectype = -1; +} + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecdiag(lincgstate* state, ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetPrecDiag: you can not change preconditioner, because function LinCGIteration is running!", _state); + state->prectype = 0; +} + + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsF - algorithm will be stopped if norm of residual is less than + EpsF*||b||. + MaxIts - algorithm will be stopped if number of iterations is more + than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +If both EpsF and MaxIts are zero then small EpsF will be set to small +value. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetcond(lincgstate* state, + double epsf, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetCond: you can not change stopping criteria when LinCGIteration() is running", _state); + ae_assert(ae_isfinite(epsf, _state)&&ae_fp_greater_eq(epsf,0), "LinCGSetCond: EpsF is negative or contains infinite or NaN values", _state); + ae_assert(maxits>=0, "LinCGSetCond: MaxIts is negative", _state); + if( ae_fp_eq(epsf,0)&&maxits==0 ) + { + state->epsf = lincg_defaultprecision; + state->maxits = maxits; + } + else + { + state->epsf = epsf; + state->maxits = maxits; + } +} + + +/************************************************************************* +Reverse communication version of linear CG. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +ae_bool lincgiteration(lincgstate* state, ae_state *_state) +{ + ae_int_t i; + double uvar; + double bnorm; + double v; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + i = state->rstate.ia.ptr.p_int[0]; + uvar = state->rstate.ra.ptr.p_double[0]; + bnorm = state->rstate.ra.ptr.p_double[1]; + v = state->rstate.ra.ptr.p_double[2]; + } + else + { + i = -983; + uvar = -989; + bnorm = -834; + v = 900; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + if( state->rstate.stage==5 ) + { + goto lbl_5; + } + if( state->rstate.stage==6 ) + { + goto lbl_6; + } + if( state->rstate.stage==7 ) + { + goto lbl_7; + } + + /* + * Routine body + */ + ae_assert(state->b.cnt>0, "LinCGIteration: B is not initialized (you must initialize B by LinCGSetB() call", _state); + state->running = ae_true; + state->repnmv = 0; + lincg_clearrfields(state, _state); + lincg_updateitersdata(state, _state); + + /* + * Start 0-th iteration + */ + ae_v_move(&state->rx.ptr.p_double[0], 1, &state->startx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + lincg_clearrfields(state, _state); + state->needvmv = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needvmv = ae_false; + bnorm = 0; + state->r2 = 0; + state->meritfunction = 0; + for(i=0; i<=state->n-1; i++) + { + state->r.ptr.p_double[i] = state->b.ptr.p_double[i]-state->mv.ptr.p_double[i]; + state->r2 = state->r2+state->r.ptr.p_double[i]*state->r.ptr.p_double[i]; + state->meritfunction = state->meritfunction+state->mv.ptr.p_double[i]*state->rx.ptr.p_double[i]-2*state->b.ptr.p_double[i]*state->rx.ptr.p_double[i]; + bnorm = bnorm+state->b.ptr.p_double[i]*state->b.ptr.p_double[i]; + } + bnorm = ae_sqrt(bnorm, _state); + + /* + * Output first report + */ + if( !state->xrep ) + { + goto lbl_8; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + lincg_clearrfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->xupdated = ae_false; +lbl_8: + + /* + * Is x0 a solution? + */ + if( !ae_isfinite(state->r2, _state)||ae_fp_less_eq(ae_sqrt(state->r2, _state),state->epsf*bnorm) ) + { + state->running = ae_false; + if( ae_isfinite(state->r2, _state) ) + { + state->repterminationtype = 1; + } + else + { + state->repterminationtype = -4; + } + result = ae_false; + return result; + } + + /* + * Calculate Z and P + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->r.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + lincg_clearrfields(state, _state); + state->needprec = ae_true; + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->needprec = ae_false; + for(i=0; i<=state->n-1; i++) + { + state->z.ptr.p_double[i] = state->pv.ptr.p_double[i]; + state->p.ptr.p_double[i] = state->z.ptr.p_double[i]; + } + + /* + * Other iterations(1..N) + */ + state->repiterationscount = 0; +lbl_10: + if( ae_false ) + { + goto lbl_11; + } + state->repiterationscount = state->repiterationscount+1; + + /* + * Calculate Alpha + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->p.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + lincg_clearrfields(state, _state); + state->needvmv = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needvmv = ae_false; + if( !ae_isfinite(state->vmv, _state)||ae_fp_less_eq(state->vmv,0) ) + { + + /* + * a) Overflow when calculating VMV + * b) non-positive VMV (non-SPD matrix) + */ + state->running = ae_false; + if( ae_isfinite(state->vmv, _state) ) + { + state->repterminationtype = -5; + } + else + { + state->repterminationtype = -4; + } + result = ae_false; + return result; + } + state->alpha = 0; + for(i=0; i<=state->n-1; i++) + { + state->alpha = state->alpha+state->r.ptr.p_double[i]*state->z.ptr.p_double[i]; + } + state->alpha = state->alpha/state->vmv; + if( !ae_isfinite(state->alpha, _state) ) + { + + /* + * Overflow when calculating Alpha + */ + state->running = ae_false; + state->repterminationtype = -4; + result = ae_false; + return result; + } + + /* + * Next step toward solution + */ + for(i=0; i<=state->n-1; i++) + { + state->cx.ptr.p_double[i] = state->rx.ptr.p_double[i]+state->alpha*state->p.ptr.p_double[i]; + } + + /* + * Calculate R: + * * use recurrent relation to update R + * * at every ItsBeforeRUpdate-th iteration recalculate it from scratch, using matrix-vector product + * in case R grows instead of decreasing, algorithm is terminated with positive completion code + */ + if( !(state->itsbeforerupdate==0||state->repiterationscount%state->itsbeforerupdate!=0) ) + { + goto lbl_12; + } + + /* + * Calculate R using recurrent formula + */ + for(i=0; i<=state->n-1; i++) + { + state->cr.ptr.p_double[i] = state->r.ptr.p_double[i]-state->alpha*state->mv.ptr.p_double[i]; + state->x.ptr.p_double[i] = state->cr.ptr.p_double[i]; + } + goto lbl_13; +lbl_12: + + /* + * Calculate R using matrix-vector multiplication + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->cx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->repnmv = state->repnmv+1; + lincg_clearrfields(state, _state); + state->needmv = ae_true; + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->needmv = ae_false; + for(i=0; i<=state->n-1; i++) + { + state->cr.ptr.p_double[i] = state->b.ptr.p_double[i]-state->mv.ptr.p_double[i]; + state->x.ptr.p_double[i] = state->cr.ptr.p_double[i]; + } + + /* + * Calculating merit function + * Check emergency stopping criterion + */ + v = 0; + for(i=0; i<=state->n-1; i++) + { + v = v+state->mv.ptr.p_double[i]*state->cx.ptr.p_double[i]-2*state->b.ptr.p_double[i]*state->cx.ptr.p_double[i]; + } + if( ae_fp_less(v,state->meritfunction) ) + { + goto lbl_14; + } + for(i=0; i<=state->n-1; i++) + { + if( !ae_isfinite(state->rx.ptr.p_double[i], _state) ) + { + state->running = ae_false; + state->repterminationtype = -4; + result = ae_false; + return result; + } + } + + /* + *output last report + */ + if( !state->xrep ) + { + goto lbl_16; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + lincg_clearrfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 5; + goto lbl_rcomm; +lbl_5: + state->xupdated = ae_false; +lbl_16: + state->running = ae_false; + state->repterminationtype = 7; + result = ae_false; + return result; +lbl_14: + state->meritfunction = v; +lbl_13: + ae_v_move(&state->rx.ptr.p_double[0], 1, &state->cx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + + /* + * calculating RNorm + * + * NOTE: monotonic decrease of R2 is not guaranteed by algorithm. + */ + state->r2 = 0; + for(i=0; i<=state->n-1; i++) + { + state->r2 = state->r2+state->cr.ptr.p_double[i]*state->cr.ptr.p_double[i]; + } + + /* + *output report + */ + if( !state->xrep ) + { + goto lbl_18; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + lincg_clearrfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 6; + goto lbl_rcomm; +lbl_6: + state->xupdated = ae_false; +lbl_18: + + /* + *stopping criterion + *achieved the required precision + */ + if( !ae_isfinite(state->r2, _state)||ae_fp_less_eq(ae_sqrt(state->r2, _state),state->epsf*bnorm) ) + { + state->running = ae_false; + if( ae_isfinite(state->r2, _state) ) + { + state->repterminationtype = 1; + } + else + { + state->repterminationtype = -4; + } + result = ae_false; + return result; + } + if( state->repiterationscount>=state->maxits&&state->maxits>0 ) + { + for(i=0; i<=state->n-1; i++) + { + if( !ae_isfinite(state->rx.ptr.p_double[i], _state) ) + { + state->running = ae_false; + state->repterminationtype = -4; + result = ae_false; + return result; + } + } + + /* + *if X is finite number + */ + state->running = ae_false; + state->repterminationtype = 5; + result = ae_false; + return result; + } + ae_v_move(&state->x.ptr.p_double[0], 1, &state->cr.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + + /* + *prepere of parameters for next iteration + */ + state->repnmv = state->repnmv+1; + lincg_clearrfields(state, _state); + state->needprec = ae_true; + state->rstate.stage = 7; + goto lbl_rcomm; +lbl_7: + state->needprec = ae_false; + ae_v_move(&state->cz.ptr.p_double[0], 1, &state->pv.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + if( state->repiterationscount%state->itsbeforerestart!=0 ) + { + state->beta = 0; + uvar = 0; + for(i=0; i<=state->n-1; i++) + { + state->beta = state->beta+state->cz.ptr.p_double[i]*state->cr.ptr.p_double[i]; + uvar = uvar+state->z.ptr.p_double[i]*state->r.ptr.p_double[i]; + } + + /* + *check that UVar is't INF or is't zero + */ + if( !ae_isfinite(uvar, _state)||ae_fp_eq(uvar,0) ) + { + state->running = ae_false; + state->repterminationtype = -4; + result = ae_false; + return result; + } + + /* + *calculate .BETA + */ + state->beta = state->beta/uvar; + + /* + *check that .BETA neither INF nor NaN + */ + if( !ae_isfinite(state->beta, _state) ) + { + state->running = ae_false; + state->repterminationtype = -1; + result = ae_false; + return result; + } + for(i=0; i<=state->n-1; i++) + { + state->p.ptr.p_double[i] = state->cz.ptr.p_double[i]+state->beta*state->p.ptr.p_double[i]; + } + } + else + { + ae_v_move(&state->p.ptr.p_double[0], 1, &state->cz.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + } + + /* + *prepere data for next iteration + */ + for(i=0; i<=state->n-1; i++) + { + + /* + *write (k+1)th iteration to (k )th iteration + */ + state->r.ptr.p_double[i] = state->cr.ptr.p_double[i]; + state->z.ptr.p_double[i] = state->cz.ptr.p_double[i]; + } + goto lbl_10; +lbl_11: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = i; + state->rstate.ra.ptr.p_double[0] = uvar; + state->rstate.ra.ptr.p_double[1] = bnorm; + state->rstate.ra.ptr.p_double[2] = v; + return result; +} + + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse matrix in the CRS format (you MUST contvert it to + CRS format by calling SparseConvertToCRS() function). + IsUpper - whether upper or lower triangle of A is used: + * IsUpper=True => only upper triangle is used and lower + triangle is not referenced at all + * IsUpper=False => only lower triangle is used and upper + triangle is not referenced at all + B - right part, array[N] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinCGSetPrecUnit(). However, preconditioning cost is low and + preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsolvesparse(lincgstate* state, + sparsematrix* a, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t n; + ae_int_t i; + double v; + double vmv; + + + n = state->n; + ae_assert(b->cnt>=state->n, "LinCGSetB: Length(B)n, _state), "LinCGSetB: B contains infinite or NaN values!", _state); + + /* + * Allocate temporaries + */ + rvectorsetlengthatleast(&state->tmpd, n, _state); + + /* + * Compute diagonal scaling matrix D + */ + if( state->prectype==0 ) + { + + /* + * Default preconditioner - inverse of matrix diagonal + */ + for(i=0; i<=n-1; i++) + { + v = sparsegetdiagonal(a, i, _state); + if( ae_fp_greater(v,0) ) + { + state->tmpd.ptr.p_double[i] = 1/ae_sqrt(v, _state); + } + else + { + state->tmpd.ptr.p_double[i] = 1; + } + } + } + else + { + + /* + * No diagonal scaling + */ + for(i=0; i<=n-1; i++) + { + state->tmpd.ptr.p_double[i] = 1; + } + } + + /* + * Solve + */ + lincgrestart(state, _state); + lincgsetb(state, b, _state); + while(lincgiteration(state, _state)) + { + + /* + * Process different requests from optimizer + */ + if( state->needmv ) + { + sparsesmv(a, isupper, &state->x, &state->mv, _state); + } + if( state->needvmv ) + { + sparsesmv(a, isupper, &state->x, &state->mv, _state); + vmv = ae_v_dotproduct(&state->x.ptr.p_double[0], 1, &state->mv.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + state->vmv = vmv; + } + if( state->needprec ) + { + for(i=0; i<=n-1; i++) + { + state->pv.ptr.p_double[i] = state->x.ptr.p_double[i]*ae_sqr(state->tmpd.ptr.p_double[i], _state); + } + } + } +} + + +/************************************************************************* +CG-solver: results. + +This function must be called after LinCGSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -5 input matrix is either not positive definite, + too large or too small + * -4 overflow/underflow during solution + (ill conditioned problem) + * 1 ||residual||<=EpsF*||b|| + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + best point found is returned + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgresults(lincgstate* state, + /* Real */ ae_vector* x, + lincgreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _lincgreport_clear(rep); + + ae_assert(!state->running, "LinCGResult: you can not get result, because function LinCGIteration has been launched!", _state); + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->rx.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nmv = state->repnmv; + rep->terminationtype = state->repterminationtype; + rep->r2 = state->r2; +} + + +/************************************************************************* +This function sets restart frequency. By default, algorithm is restarted +after N subsequent iterations. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrestartfreq(lincgstate* state, + ae_int_t srf, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetRestartFreq: you can not change restart frequency when LinCGIteration() is running", _state); + ae_assert(srf>0, "LinCGSetRestartFreq: non-positive SRF", _state); + state->itsbeforerestart = srf; +} + + +/************************************************************************* +This function sets frequency of residual recalculations. + +Algorithm updates residual r_k using iterative formula, but recalculates +it from scratch after each 10 iterations. It is done to avoid accumulation +of numerical errors and to stop algorithm when r_k starts to grow. + +Such low update frequence (1/10) gives very little overhead, but makes +algorithm a bit more robust against numerical errors. However, you may +change it + +INPUT PARAMETERS: + Freq - desired update frequency, Freq>=0. + Zero value means that no updates will be done. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrupdatefreq(lincgstate* state, + ae_int_t freq, + ae_state *_state) +{ + + + ae_assert(!state->running, "LinCGSetRUpdateFreq: you can not change update frequency when LinCGIteration() is running", _state); + ae_assert(freq>=0, "LinCGSetRUpdateFreq: non-positive Freq", _state); + state->itsbeforerupdate = freq; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetxrep(lincgstate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +Procedure for restart function LinCGIteration + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgrestart(lincgstate* state, ae_state *_state) +{ + + + ae_vector_set_length(&state->rstate.ia, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 2+1, _state); + state->rstate.stage = -1; + lincg_clearrfields(state, _state); +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void lincg_clearrfields(lincgstate* state, ae_state *_state) +{ + + + state->xupdated = ae_false; + state->needmv = ae_false; + state->needmtv = ae_false; + state->needmv2 = ae_false; + state->needvmv = ae_false; + state->needprec = ae_false; +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void lincg_updateitersdata(lincgstate* state, ae_state *_state) +{ + + + state->repiterationscount = 0; + state->repnmv = 0; + state->repterminationtype = 0; +} + + +ae_bool _lincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + lincgstate *p = (lincgstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->rx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->b, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cr, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cz, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->p, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->r, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->z, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->mv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->pv, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->startx, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->tmpd, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _lincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + lincgstate *dst = (lincgstate*)_dst; + lincgstate *src = (lincgstate*)_src; + if( !ae_vector_init_copy(&dst->rx, &src->rx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->b, &src->b, _state, make_automatic) ) + return ae_false; + dst->n = src->n; + dst->prectype = src->prectype; + if( !ae_vector_init_copy(&dst->cx, &src->cx, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cr, &src->cr, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cz, &src->cz, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->p, &src->p, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->r, &src->r, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->z, &src->z, _state, make_automatic) ) + return ae_false; + dst->alpha = src->alpha; + dst->beta = src->beta; + dst->r2 = src->r2; + dst->meritfunction = src->meritfunction; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->mv, &src->mv, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->pv, &src->pv, _state, make_automatic) ) + return ae_false; + dst->vmv = src->vmv; + if( !ae_vector_init_copy(&dst->startx, &src->startx, _state, make_automatic) ) + return ae_false; + dst->epsf = src->epsf; + dst->maxits = src->maxits; + dst->itsbeforerestart = src->itsbeforerestart; + dst->itsbeforerupdate = src->itsbeforerupdate; + dst->xrep = src->xrep; + dst->xupdated = src->xupdated; + dst->needmv = src->needmv; + dst->needmtv = src->needmtv; + dst->needmv2 = src->needmv2; + dst->needvmv = src->needvmv; + dst->needprec = src->needprec; + dst->repiterationscount = src->repiterationscount; + dst->repnmv = src->repnmv; + dst->repterminationtype = src->repterminationtype; + dst->running = src->running; + if( !ae_vector_init_copy(&dst->tmpd, &src->tmpd, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _lincgstate_clear(void* _p) +{ + lincgstate *p = (lincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->rx); + ae_vector_clear(&p->b); + ae_vector_clear(&p->cx); + ae_vector_clear(&p->cr); + ae_vector_clear(&p->cz); + ae_vector_clear(&p->p); + ae_vector_clear(&p->r); + ae_vector_clear(&p->z); + ae_vector_clear(&p->x); + ae_vector_clear(&p->mv); + ae_vector_clear(&p->pv); + ae_vector_clear(&p->startx); + ae_vector_clear(&p->tmpd); + _rcommstate_clear(&p->rstate); +} + + +void _lincgstate_destroy(void* _p) +{ + lincgstate *p = (lincgstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->rx); + ae_vector_destroy(&p->b); + ae_vector_destroy(&p->cx); + ae_vector_destroy(&p->cr); + ae_vector_destroy(&p->cz); + ae_vector_destroy(&p->p); + ae_vector_destroy(&p->r); + ae_vector_destroy(&p->z); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->mv); + ae_vector_destroy(&p->pv); + ae_vector_destroy(&p->startx); + ae_vector_destroy(&p->tmpd); + _rcommstate_destroy(&p->rstate); +} + + +ae_bool _lincgreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + lincgreport *p = (lincgreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _lincgreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + lincgreport *dst = (lincgreport*)_dst; + lincgreport *src = (lincgreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nmv = src->nmv; + dst->terminationtype = src->terminationtype; + dst->r2 = src->r2; + return ae_true; +} + + +void _lincgreport_clear(void* _p) +{ + lincgreport *p = (lincgreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _lincgreport_destroy(void* _p) +{ + lincgreport *p = (lincgreport*)_p; + ae_touch_ptr((void*)p); +} + + + + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER + +DESCRIPTION: +This algorithm solves system of nonlinear equations + F[0](x[0], ..., x[n-1]) = 0 + F[1](x[0], ..., x[n-1]) = 0 + ... + F[M-1](x[0], ..., x[n-1]) = 0 +with M/N do not necessarily coincide. Algorithm converges quadratically +under following conditions: + * the solution set XS is nonempty + * for some xs in XS there exist such neighbourhood N(xs) that: + * vector function F(x) and its Jacobian J(x) are continuously + differentiable on N + * ||F(x)|| provides local error bound on N, i.e. there exists such + c1, that ||F(x)||>c1*distance(x,XS) +Note that these conditions are much more weaker than usual non-singularity +conditions. For example, algorithm will converge for any affine function +F (whether its Jacobian singular or not). + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function vector F[] and Jacobian matrix at given point X +* value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X + + +USAGE: +1. User initializes algorithm state with NLEQCreateLM() call +2. User tunes solver parameters with NLEQSetCond(), NLEQSetStpMax() and + other functions +3. User calls NLEQSolve() function which takes algorithm state and + pointers (delegates, etc.) to callback functions which calculate merit + function value and Jacobian. +4. User calls NLEQResults() to get solution +5. Optionally, user may call NLEQRestartFrom() to solve another problem + with same parameters (N/M) but another starting point and/or another + function vector. NLEQRestartFrom() allows to reuse already initialized + structure. + + +INPUT PARAMETERS: + N - space dimension, N>1: + * if provided, only leading N elements of X are used + * if not provided, determined automatically from size of X + M - system size + X - starting point + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with NLEQSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use NLEQSetStpMax() function to bound algorithm's steps. +3. this algorithm is a slightly modified implementation of the method + described in 'Levenberg-Marquardt method for constrained nonlinear + equations with strong local convergence properties' by Christian Kanzow + Nobuo Yamashita and Masao Fukushima and further developed in 'On the + convergence of a New Levenberg-Marquardt Method' by Jin-yan Fan and + Ya-Xiang Yuan. + + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqcreatelm(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + nleqstate* state, + ae_state *_state) +{ + + _nleqstate_clear(state); + + ae_assert(n>=1, "NLEQCreateLM: N<1!", _state); + ae_assert(m>=1, "NLEQCreateLM: M<1!", _state); + ae_assert(x->cnt>=n, "NLEQCreateLM: Length(X)n = n; + state->m = m; + nleqsetcond(state, 0, 0, _state); + nleqsetxrep(state, ae_false, _state); + nleqsetstpmax(state, 0, _state); + ae_vector_set_length(&state->x, n, _state); + ae_vector_set_length(&state->xbase, n, _state); + ae_matrix_set_length(&state->j, m, n, _state); + ae_vector_set_length(&state->fi, m, _state); + ae_vector_set_length(&state->rightpart, n, _state); + ae_vector_set_length(&state->candstep, n, _state); + nleqrestartfrom(state, x, _state); +} + + +/************************************************************************* +This function sets stopping conditions for the nonlinear solver + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsF - >=0 + The subroutine finishes its work if on k+1-th iteration + the condition ||F||<=EpsF is satisfied + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsF=0 and MaxIts=0 simultaneously will lead to automatic +stopping criterion selection (small EpsF). + +NOTES: + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetcond(nleqstate* state, + double epsf, + ae_int_t maxits, + ae_state *_state) +{ + + + ae_assert(ae_isfinite(epsf, _state), "NLEQSetCond: EpsF is not finite number!", _state); + ae_assert(ae_fp_greater_eq(epsf,0), "NLEQSetCond: negative EpsF!", _state); + ae_assert(maxits>=0, "NLEQSetCond: negative MaxIts!", _state); + if( ae_fp_eq(epsf,0)&&maxits==0 ) + { + epsf = 1.0E-6; + } + state->epsf = epsf; + state->maxits = maxits; +} + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to NLEQSolve(). + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetxrep(nleqstate* state, ae_bool needxrep, ae_state *_state) +{ + + + state->xrep = needxrep; +} + + +/************************************************************************* +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 target function contains exp() or other fast +growing functions, and algorithm makes too large steps which lead 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. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetstpmax(nleqstate* state, double stpmax, ae_state *_state) +{ + + + ae_assert(ae_isfinite(stpmax, _state), "NLEQSetStpMax: StpMax is not finite!", _state); + ae_assert(ae_fp_greater_eq(stpmax,0), "NLEQSetStpMax: StpMax<0!", _state); + state->stpmax = stpmax; +} + + +/************************************************************************* + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey +*************************************************************************/ +ae_bool nleqiteration(nleqstate* state, ae_state *_state) +{ + ae_int_t n; + ae_int_t m; + ae_int_t i; + double lambdaup; + double lambdadown; + double lambdav; + double rho; + double mu; + double stepnorm; + ae_bool b; + ae_bool result; + + + + /* + * Reverse communication preparations + * I know it looks ugly, but it works the same way + * anywhere from C++ to Python. + * + * This code initializes locals by: + * * random values determined during code + * generation - on first subroutine call + * * values from previous call - on subsequent calls + */ + if( state->rstate.stage>=0 ) + { + n = state->rstate.ia.ptr.p_int[0]; + m = state->rstate.ia.ptr.p_int[1]; + i = state->rstate.ia.ptr.p_int[2]; + b = state->rstate.ba.ptr.p_bool[0]; + lambdaup = state->rstate.ra.ptr.p_double[0]; + lambdadown = state->rstate.ra.ptr.p_double[1]; + lambdav = state->rstate.ra.ptr.p_double[2]; + rho = state->rstate.ra.ptr.p_double[3]; + mu = state->rstate.ra.ptr.p_double[4]; + stepnorm = state->rstate.ra.ptr.p_double[5]; + } + else + { + n = -983; + m = -989; + i = -834; + b = ae_false; + lambdaup = -287; + lambdadown = 364; + lambdav = 214; + rho = -338; + mu = -686; + stepnorm = 912; + } + if( state->rstate.stage==0 ) + { + goto lbl_0; + } + if( state->rstate.stage==1 ) + { + goto lbl_1; + } + if( state->rstate.stage==2 ) + { + goto lbl_2; + } + if( state->rstate.stage==3 ) + { + goto lbl_3; + } + if( state->rstate.stage==4 ) + { + goto lbl_4; + } + + /* + * Routine body + */ + + /* + * Prepare + */ + n = state->n; + m = state->m; + state->repterminationtype = 0; + state->repiterationscount = 0; + state->repnfunc = 0; + state->repnjac = 0; + + /* + * Calculate F/G, initialize algorithm + */ + nleq_clearrequestfields(state, _state); + state->needf = ae_true; + state->rstate.stage = 0; + goto lbl_rcomm; +lbl_0: + state->needf = ae_false; + state->repnfunc = state->repnfunc+1; + ae_v_move(&state->xbase.ptr.p_double[0], 1, &state->x.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->fbase = state->f; + state->fprev = ae_maxrealnumber; + if( !state->xrep ) + { + goto lbl_5; + } + + /* + * progress report + */ + nleq_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->rstate.stage = 1; + goto lbl_rcomm; +lbl_1: + state->xupdated = ae_false; +lbl_5: + if( ae_fp_less_eq(state->f,ae_sqr(state->epsf, _state)) ) + { + state->repterminationtype = 1; + result = ae_false; + return result; + } + + /* + * Main cycle + */ + lambdaup = 10; + lambdadown = 0.3; + lambdav = 0.001; + rho = 1; +lbl_7: + if( ae_false ) + { + goto lbl_8; + } + + /* + * Get Jacobian; + * before we get to this point we already have State.XBase filled + * with current point and State.FBase filled with function value + * at XBase + */ + nleq_clearrequestfields(state, _state); + state->needfij = ae_true; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 2; + goto lbl_rcomm; +lbl_2: + state->needfij = ae_false; + state->repnfunc = state->repnfunc+1; + state->repnjac = state->repnjac+1; + rmatrixmv(n, m, &state->j, 0, 0, 1, &state->fi, 0, &state->rightpart, 0, _state); + ae_v_muld(&state->rightpart.ptr.p_double[0], 1, ae_v_len(0,n-1), -1); + + /* + * Inner cycle: find good lambda + */ +lbl_9: + if( ae_false ) + { + goto lbl_10; + } + + /* + * Solve (J^T*J + (Lambda+Mu)*I)*y = J^T*F + * to get step d=-y where: + * * Mu=||F|| - is damping parameter for nonlinear system + * * Lambda - is additional Levenberg-Marquardt parameter + * for better convergence when far away from minimum + */ + for(i=0; i<=n-1; i++) + { + state->candstep.ptr.p_double[i] = 0; + } + fblssolvecgx(&state->j, m, n, lambdav, &state->rightpart, &state->candstep, &state->cgbuf, _state); + + /* + * Normalize step (it must be no more than StpMax) + */ + stepnorm = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(state->candstep.ptr.p_double[i],0) ) + { + stepnorm = 1; + break; + } + } + linminnormalized(&state->candstep, &stepnorm, n, _state); + if( ae_fp_neq(state->stpmax,0) ) + { + stepnorm = ae_minreal(stepnorm, state->stpmax, _state); + } + + /* + * Test new step - is it good enough? + * * if not, Lambda is increased and we try again. + * * if step is good, we decrease Lambda and move on. + * + * We can break this cycle on two occasions: + * * step is so small that x+step==x (in floating point arithmetics) + * * lambda is so large + */ + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + ae_v_addd(&state->x.ptr.p_double[0], 1, &state->candstep.ptr.p_double[0], 1, ae_v_len(0,n-1), stepnorm); + b = ae_true; + for(i=0; i<=n-1; i++) + { + if( ae_fp_neq(state->x.ptr.p_double[i],state->xbase.ptr.p_double[i]) ) + { + b = ae_false; + break; + } + } + if( b ) + { + + /* + * Step is too small, force zero step and break + */ + stepnorm = 0; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + goto lbl_10; + } + nleq_clearrequestfields(state, _state); + state->needf = ae_true; + state->rstate.stage = 3; + goto lbl_rcomm; +lbl_3: + state->needf = ae_false; + state->repnfunc = state->repnfunc+1; + if( ae_fp_less(state->f,state->fbase) ) + { + + /* + * function value decreased, move on + */ + nleq_decreaselambda(&lambdav, &rho, lambdadown, _state); + goto lbl_10; + } + if( !nleq_increaselambda(&lambdav, &rho, lambdaup, _state) ) + { + + /* + * Lambda is too large (near overflow), force zero step and break + */ + stepnorm = 0; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->f = state->fbase; + goto lbl_10; + } + goto lbl_9; +lbl_10: + + /* + * Accept step: + * * new position + * * new function value + */ + state->fbase = state->f; + ae_v_addd(&state->xbase.ptr.p_double[0], 1, &state->candstep.ptr.p_double[0], 1, ae_v_len(0,n-1), stepnorm); + state->repiterationscount = state->repiterationscount+1; + + /* + * Report new iteration + */ + if( !state->xrep ) + { + goto lbl_11; + } + nleq_clearrequestfields(state, _state); + state->xupdated = ae_true; + state->f = state->fbase; + ae_v_move(&state->x.ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,n-1)); + state->rstate.stage = 4; + goto lbl_rcomm; +lbl_4: + state->xupdated = ae_false; +lbl_11: + + /* + * Test stopping conditions on F, step (zero/non-zero) and MaxIts; + * If one of the conditions is met, RepTerminationType is changed. + */ + if( ae_fp_less_eq(ae_sqrt(state->f, _state),state->epsf) ) + { + state->repterminationtype = 1; + } + if( ae_fp_eq(stepnorm,0)&&state->repterminationtype==0 ) + { + state->repterminationtype = -4; + } + if( state->repiterationscount>=state->maxits&&state->maxits>0 ) + { + state->repterminationtype = 5; + } + if( state->repterminationtype!=0 ) + { + goto lbl_8; + } + + /* + * Now, iteration is finally over + */ + goto lbl_7; +lbl_8: + result = ae_false; + return result; + + /* + * Saving state + */ +lbl_rcomm: + result = ae_true; + state->rstate.ia.ptr.p_int[0] = n; + state->rstate.ia.ptr.p_int[1] = m; + state->rstate.ia.ptr.p_int[2] = i; + state->rstate.ba.ptr.p_bool[0] = b; + state->rstate.ra.ptr.p_double[0] = lambdaup; + state->rstate.ra.ptr.p_double[1] = lambdadown; + state->rstate.ra.ptr.p_double[2] = lambdav; + state->rstate.ra.ptr.p_double[3] = rho; + state->rstate.ra.ptr.p_double[4] = mu; + state->rstate.ra.ptr.p_double[5] = stepnorm; + return result; +} + + +/************************************************************************* +NLEQ solver results + +INPUT PARAMETERS: + State - algorithm state. + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -4 ERROR: algorithm has converged to the + stationary point Xf which is local minimum of + f=F[0]^2+...+F[m-1]^2, but is not solution of + nonlinear system. + * 1 sqrt(f)<=EpsF. + * 5 MaxIts steps was taken + * 7 stopping conditions are too stringent, + further improvement is impossible + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + * ActiveConstraints contains number of active constraints + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresults(nleqstate* state, + /* Real */ ae_vector* x, + nleqreport* rep, + ae_state *_state) +{ + + ae_vector_clear(x); + _nleqreport_clear(rep); + + nleqresultsbuf(state, x, rep, _state); +} + + +/************************************************************************* +NLEQ solver results + +Buffered implementation of NLEQResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresultsbuf(nleqstate* state, + /* Real */ ae_vector* x, + nleqreport* rep, + ae_state *_state) +{ + + + if( x->cntn ) + { + ae_vector_set_length(x, state->n, _state); + } + ae_v_move(&x->ptr.p_double[0], 1, &state->xbase.ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + rep->iterationscount = state->repiterationscount; + rep->nfunc = state->repnfunc; + rep->njac = state->repnjac; + rep->terminationtype = state->repterminationtype; +} + + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinCGCreate call. + X - new starting point. + BndL - new lower bounds + BndU - new upper bounds + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqrestartfrom(nleqstate* state, + /* Real */ ae_vector* x, + ae_state *_state) +{ + + + ae_assert(x->cnt>=state->n, "NLEQRestartFrom: Length(X)n, _state), "NLEQRestartFrom: X contains infinite or NaN values!", _state); + ae_v_move(&state->x.ptr.p_double[0], 1, &x->ptr.p_double[0], 1, ae_v_len(0,state->n-1)); + ae_vector_set_length(&state->rstate.ia, 2+1, _state); + ae_vector_set_length(&state->rstate.ba, 0+1, _state); + ae_vector_set_length(&state->rstate.ra, 5+1, _state); + state->rstate.stage = -1; + nleq_clearrequestfields(state, _state); +} + + +/************************************************************************* +Clears request fileds (to be sure that we don't forgot to clear something) +*************************************************************************/ +static void nleq_clearrequestfields(nleqstate* state, ae_state *_state) +{ + + + state->needf = ae_false; + state->needfij = ae_false; + state->xupdated = ae_false; +} + + +/************************************************************************* +Increases lambda, returns False when there is a danger of overflow +*************************************************************************/ +static ae_bool nleq_increaselambda(double* lambdav, + double* nu, + double lambdaup, + ae_state *_state) +{ + double lnlambda; + double lnnu; + double lnlambdaup; + double lnmax; + ae_bool result; + + + result = ae_false; + lnlambda = ae_log(*lambdav, _state); + lnlambdaup = ae_log(lambdaup, _state); + lnnu = ae_log(*nu, _state); + lnmax = 0.5*ae_log(ae_maxrealnumber, _state); + if( ae_fp_greater(lnlambda+lnlambdaup+lnnu,lnmax) ) + { + return result; + } + if( ae_fp_greater(lnnu+ae_log(2, _state),lnmax) ) + { + return result; + } + *lambdav = *lambdav*lambdaup*(*nu); + *nu = *nu*2; + result = ae_true; + return result; +} + + +/************************************************************************* +Decreases lambda, but leaves it unchanged when there is danger of underflow. +*************************************************************************/ +static void nleq_decreaselambda(double* lambdav, + double* nu, + double lambdadown, + ae_state *_state) +{ + + + *nu = 1; + if( ae_fp_less(ae_log(*lambdav, _state)+ae_log(lambdadown, _state),ae_log(ae_minrealnumber, _state)) ) + { + *lambdav = ae_minrealnumber; + } + else + { + *lambdav = *lambdav*lambdadown; + } +} + + +ae_bool _nleqstate_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + nleqstate *p = (nleqstate*)_p; + ae_touch_ptr((void*)p); + if( !ae_vector_init(&p->x, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->fi, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init(&p->j, 0, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !_rcommstate_init(&p->rstate, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->xbase, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->candstep, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->rightpart, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init(&p->cgbuf, 0, DT_REAL, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +ae_bool _nleqstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + nleqstate *dst = (nleqstate*)_dst; + nleqstate *src = (nleqstate*)_src; + dst->n = src->n; + dst->m = src->m; + dst->epsf = src->epsf; + dst->maxits = src->maxits; + dst->xrep = src->xrep; + dst->stpmax = src->stpmax; + if( !ae_vector_init_copy(&dst->x, &src->x, _state, make_automatic) ) + return ae_false; + dst->f = src->f; + if( !ae_vector_init_copy(&dst->fi, &src->fi, _state, make_automatic) ) + return ae_false; + if( !ae_matrix_init_copy(&dst->j, &src->j, _state, make_automatic) ) + return ae_false; + dst->needf = src->needf; + dst->needfij = src->needfij; + dst->xupdated = src->xupdated; + if( !_rcommstate_init_copy(&dst->rstate, &src->rstate, _state, make_automatic) ) + return ae_false; + dst->repiterationscount = src->repiterationscount; + dst->repnfunc = src->repnfunc; + dst->repnjac = src->repnjac; + dst->repterminationtype = src->repterminationtype; + if( !ae_vector_init_copy(&dst->xbase, &src->xbase, _state, make_automatic) ) + return ae_false; + dst->fbase = src->fbase; + dst->fprev = src->fprev; + if( !ae_vector_init_copy(&dst->candstep, &src->candstep, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->rightpart, &src->rightpart, _state, make_automatic) ) + return ae_false; + if( !ae_vector_init_copy(&dst->cgbuf, &src->cgbuf, _state, make_automatic) ) + return ae_false; + return ae_true; +} + + +void _nleqstate_clear(void* _p) +{ + nleqstate *p = (nleqstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_clear(&p->x); + ae_vector_clear(&p->fi); + ae_matrix_clear(&p->j); + _rcommstate_clear(&p->rstate); + ae_vector_clear(&p->xbase); + ae_vector_clear(&p->candstep); + ae_vector_clear(&p->rightpart); + ae_vector_clear(&p->cgbuf); +} + + +void _nleqstate_destroy(void* _p) +{ + nleqstate *p = (nleqstate*)_p; + ae_touch_ptr((void*)p); + ae_vector_destroy(&p->x); + ae_vector_destroy(&p->fi); + ae_matrix_destroy(&p->j); + _rcommstate_destroy(&p->rstate); + ae_vector_destroy(&p->xbase); + ae_vector_destroy(&p->candstep); + ae_vector_destroy(&p->rightpart); + ae_vector_destroy(&p->cgbuf); +} + + +ae_bool _nleqreport_init(void* _p, ae_state *_state, ae_bool make_automatic) +{ + nleqreport *p = (nleqreport*)_p; + ae_touch_ptr((void*)p); + return ae_true; +} + + +ae_bool _nleqreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic) +{ + nleqreport *dst = (nleqreport*)_dst; + nleqreport *src = (nleqreport*)_src; + dst->iterationscount = src->iterationscount; + dst->nfunc = src->nfunc; + dst->njac = src->njac; + dst->terminationtype = src->terminationtype; + return ae_true; +} + + +void _nleqreport_clear(void* _p) +{ + nleqreport *p = (nleqreport*)_p; + ae_touch_ptr((void*)p); +} + + +void _nleqreport_destroy(void* _p) +{ + nleqreport *p = (nleqreport*)_p; + ae_touch_ptr((void*)p); +} + + + +} + diff --git a/src/inc/alglib/solvers.h b/src/inc/alglib/solvers.h new file mode 100644 index 0000000..3c94873 --- /dev/null +++ b/src/inc/alglib/solvers.h @@ -0,0 +1,2016 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _solvers_pkg_h +#define _solvers_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "linalg.h" +#include "alglibmisc.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +typedef struct +{ + double r1; + double rinf; +} densesolverreport; +typedef struct +{ + double r2; + ae_matrix cx; + ae_int_t n; + ae_int_t k; +} densesolverlsreport; +typedef struct +{ + normestimatorstate nes; + ae_vector rx; + ae_vector b; + ae_int_t n; + ae_int_t m; + ae_int_t prectype; + ae_vector ui; + ae_vector uip1; + ae_vector vi; + ae_vector vip1; + ae_vector omegai; + ae_vector omegaip1; + double alphai; + double alphaip1; + double betai; + double betaip1; + double phibari; + double phibarip1; + double phii; + double rhobari; + double rhobarip1; + double rhoi; + double ci; + double si; + double theta; + double lambdai; + ae_vector d; + double anorm; + double bnorm2; + double dnorm; + double r2; + ae_vector x; + ae_vector mv; + ae_vector mtv; + double epsa; + double epsb; + double epsc; + ae_int_t maxits; + ae_bool xrep; + ae_bool xupdated; + ae_bool needmv; + ae_bool needmtv; + ae_bool needmv2; + ae_bool needvmv; + ae_bool needprec; + ae_int_t repiterationscount; + ae_int_t repnmv; + ae_int_t repterminationtype; + ae_bool running; + ae_vector tmpd; + ae_vector tmpx; + rcommstate rstate; +} linlsqrstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nmv; + ae_int_t terminationtype; +} linlsqrreport; +typedef struct +{ + ae_vector rx; + ae_vector b; + ae_int_t n; + ae_int_t prectype; + ae_vector cx; + ae_vector cr; + ae_vector cz; + ae_vector p; + ae_vector r; + ae_vector z; + double alpha; + double beta; + double r2; + double meritfunction; + ae_vector x; + ae_vector mv; + ae_vector pv; + double vmv; + ae_vector startx; + double epsf; + ae_int_t maxits; + ae_int_t itsbeforerestart; + ae_int_t itsbeforerupdate; + ae_bool xrep; + ae_bool xupdated; + ae_bool needmv; + ae_bool needmtv; + ae_bool needmv2; + ae_bool needvmv; + ae_bool needprec; + ae_int_t repiterationscount; + ae_int_t repnmv; + ae_int_t repterminationtype; + ae_bool running; + ae_vector tmpd; + rcommstate rstate; +} lincgstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nmv; + ae_int_t terminationtype; + double r2; +} lincgreport; +typedef struct +{ + ae_int_t n; + ae_int_t m; + double epsf; + ae_int_t maxits; + ae_bool xrep; + double stpmax; + ae_vector x; + double f; + ae_vector fi; + ae_matrix j; + ae_bool needf; + ae_bool needfij; + ae_bool xupdated; + rcommstate rstate; + ae_int_t repiterationscount; + ae_int_t repnfunc; + ae_int_t repnjac; + ae_int_t repterminationtype; + ae_vector xbase; + double fbase; + double fprev; + ae_vector candstep; + ae_vector rightpart; + ae_vector cgbuf; +} nleqstate; +typedef struct +{ + ae_int_t iterationscount; + ae_int_t nfunc; + ae_int_t njac; + ae_int_t terminationtype; +} nleqreport; + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + +/************************************************************************* + +*************************************************************************/ +class _densesolverreport_owner +{ +public: + _densesolverreport_owner(); + _densesolverreport_owner(const _densesolverreport_owner &rhs); + _densesolverreport_owner& operator=(const _densesolverreport_owner &rhs); + virtual ~_densesolverreport_owner(); + alglib_impl::densesolverreport* c_ptr(); + alglib_impl::densesolverreport* c_ptr() const; +protected: + alglib_impl::densesolverreport *p_struct; +}; +class densesolverreport : public _densesolverreport_owner +{ +public: + densesolverreport(); + densesolverreport(const densesolverreport &rhs); + densesolverreport& operator=(const densesolverreport &rhs); + virtual ~densesolverreport(); + double &r1; + double &rinf; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _densesolverlsreport_owner +{ +public: + _densesolverlsreport_owner(); + _densesolverlsreport_owner(const _densesolverlsreport_owner &rhs); + _densesolverlsreport_owner& operator=(const _densesolverlsreport_owner &rhs); + virtual ~_densesolverlsreport_owner(); + alglib_impl::densesolverlsreport* c_ptr(); + alglib_impl::densesolverlsreport* c_ptr() const; +protected: + alglib_impl::densesolverlsreport *p_struct; +}; +class densesolverlsreport : public _densesolverlsreport_owner +{ +public: + densesolverlsreport(); + densesolverlsreport(const densesolverlsreport &rhs); + densesolverlsreport& operator=(const densesolverlsreport &rhs); + virtual ~densesolverlsreport(); + double &r2; + real_2d_array cx; + ae_int_t &n; + ae_int_t &k; + +}; + +/************************************************************************* +This object stores state of the LinLSQR method. + +You should use ALGLIB functions to work with this object. +*************************************************************************/ +class _linlsqrstate_owner +{ +public: + _linlsqrstate_owner(); + _linlsqrstate_owner(const _linlsqrstate_owner &rhs); + _linlsqrstate_owner& operator=(const _linlsqrstate_owner &rhs); + virtual ~_linlsqrstate_owner(); + alglib_impl::linlsqrstate* c_ptr(); + alglib_impl::linlsqrstate* c_ptr() const; +protected: + alglib_impl::linlsqrstate *p_struct; +}; +class linlsqrstate : public _linlsqrstate_owner +{ +public: + linlsqrstate(); + linlsqrstate(const linlsqrstate &rhs); + linlsqrstate& operator=(const linlsqrstate &rhs); + virtual ~linlsqrstate(); + +}; + + +/************************************************************************* + +*************************************************************************/ +class _linlsqrreport_owner +{ +public: + _linlsqrreport_owner(); + _linlsqrreport_owner(const _linlsqrreport_owner &rhs); + _linlsqrreport_owner& operator=(const _linlsqrreport_owner &rhs); + virtual ~_linlsqrreport_owner(); + alglib_impl::linlsqrreport* c_ptr(); + alglib_impl::linlsqrreport* c_ptr() const; +protected: + alglib_impl::linlsqrreport *p_struct; +}; +class linlsqrreport : public _linlsqrreport_owner +{ +public: + linlsqrreport(); + linlsqrreport(const linlsqrreport &rhs); + linlsqrreport& operator=(const linlsqrreport &rhs); + virtual ~linlsqrreport(); + ae_int_t &iterationscount; + ae_int_t &nmv; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +This object stores state of the linear CG method. + +You should use ALGLIB functions to work with this object. +Never try to access its fields directly! +*************************************************************************/ +class _lincgstate_owner +{ +public: + _lincgstate_owner(); + _lincgstate_owner(const _lincgstate_owner &rhs); + _lincgstate_owner& operator=(const _lincgstate_owner &rhs); + virtual ~_lincgstate_owner(); + alglib_impl::lincgstate* c_ptr(); + alglib_impl::lincgstate* c_ptr() const; +protected: + alglib_impl::lincgstate *p_struct; +}; +class lincgstate : public _lincgstate_owner +{ +public: + lincgstate(); + lincgstate(const lincgstate &rhs); + lincgstate& operator=(const lincgstate &rhs); + virtual ~lincgstate(); + +}; + + +/************************************************************************* + +*************************************************************************/ +class _lincgreport_owner +{ +public: + _lincgreport_owner(); + _lincgreport_owner(const _lincgreport_owner &rhs); + _lincgreport_owner& operator=(const _lincgreport_owner &rhs); + virtual ~_lincgreport_owner(); + alglib_impl::lincgreport* c_ptr(); + alglib_impl::lincgreport* c_ptr() const; +protected: + alglib_impl::lincgreport *p_struct; +}; +class lincgreport : public _lincgreport_owner +{ +public: + lincgreport(); + lincgreport(const lincgreport &rhs); + lincgreport& operator=(const lincgreport &rhs); + virtual ~lincgreport(); + ae_int_t &iterationscount; + ae_int_t &nmv; + ae_int_t &terminationtype; + double &r2; + +}; + +/************************************************************************* + +*************************************************************************/ +class _nleqstate_owner +{ +public: + _nleqstate_owner(); + _nleqstate_owner(const _nleqstate_owner &rhs); + _nleqstate_owner& operator=(const _nleqstate_owner &rhs); + virtual ~_nleqstate_owner(); + alglib_impl::nleqstate* c_ptr(); + alglib_impl::nleqstate* c_ptr() const; +protected: + alglib_impl::nleqstate *p_struct; +}; +class nleqstate : public _nleqstate_owner +{ +public: + nleqstate(); + nleqstate(const nleqstate &rhs); + nleqstate& operator=(const nleqstate &rhs); + virtual ~nleqstate(); + ae_bool &needf; + ae_bool &needfij; + ae_bool &xupdated; + double &f; + real_1d_array fi; + real_2d_array j; + real_1d_array x; + +}; + + +/************************************************************************* + +*************************************************************************/ +class _nleqreport_owner +{ +public: + _nleqreport_owner(); + _nleqreport_owner(const _nleqreport_owner &rhs); + _nleqreport_owner& operator=(const _nleqreport_owner &rhs); + virtual ~_nleqreport_owner(); + alglib_impl::nleqreport* c_ptr(); + alglib_impl::nleqreport* c_ptr() const; +protected: + alglib_impl::nleqreport *p_struct; +}; +class nleqreport : public _nleqreport_owner +{ +public: + nleqreport(); + nleqreport(const nleqreport &rhs); + nleqreport& operator=(const nleqreport &rhs); + virtual ~nleqreport(); + ae_int_t &iterationscount; + ae_int_t &nfunc; + ae_int_t &njac; + ae_int_t &terminationtype; + +}; + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where A is NxN non-denegerate +real matrix, x and b are vectors. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - return code: + * -3 A is singular, or VERY close to singular. + X is filled by zeros in such cases. + * -1 N<=0 was passed + * 1 task is solved (but matrix A may be ill-conditioned, + check R1/RInf parameters for condition numbers). + Rep - solver report, see below for more info + X - array[0..N-1], it contains: + * solution of A*x=b if A is non-singular (well-conditioned + or ill-conditioned, but not very close to singular) + * zeros, if A is singular or VERY close to singular + (in this case Info=-3). + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R1 reciprocal of condition number: 1/cond(A), 1-norm. +* RInf reciprocal of condition number: 1/cond(A), inf-norm. + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolve(const real_2d_array &a, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x); + + +/************************************************************************* +Dense solver. + +Similar to RMatrixSolve() but solves task with multiple right parts (where +b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* optional iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvem(const real_2d_array &a, const ae_int_t n, const real_2d_array &b, const ae_int_t m, const bool rfs, ae_int_t &info, densesolverreport &rep, real_2d_array &x); + + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*X=B, where A is NxN non-denegerate +real matrix given by its LU decomposition, X and B are NxM real matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolve(const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x); + + +/************************************************************************* +Dense solver. + +Similar to RMatrixLUSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixlusolvem(const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x); + + +/************************************************************************* +Dense solver. + +This subroutine solves a system A*x=b, where BOTH ORIGINAL A AND ITS +LU DECOMPOSITION ARE KNOWN. You can use it if for some reasons you have +both A and its LU decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolve(const real_2d_array &a, const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x); + + +/************************************************************************* +Dense solver. + +Similar to RMatrixMixedSolve() but solves task with multiple right parts +(where b and x are NxM matrices). + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void rmatrixmixedsolvem(const real_2d_array &a, const real_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3+M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + RFS - iterative refinement switch: + * True - refinement is used. + Less performance, more precision. + * False - refinement is not used. + More performance, less precision. + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolvem(const complex_2d_array &a, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, const bool rfs, ae_int_t &info, densesolverreport &rep, complex_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixsolve(const complex_2d_array &a, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, RMatrixLU result + P - array[0..N-1], pivots array, RMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolvem(const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation + +No iterative refinement is provided because exact form of original matrix +is not known to subroutine. Use CMatrixSolve or CMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixlusolve(const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolveM(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(M*N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolvem(const complex_2d_array &a, const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixMixedSolve(), but for complex matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* iterative refinement +* O(N^2) complexity + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + LUA - array[0..N-1,0..N-1], LU decomposition, CMatrixLU result + P - array[0..N-1], pivots array, CMatrixLU result + N - size of A + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolveM + Rep - same as in RMatrixSolveM + X - same as in RMatrixSolveM + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void cmatrixmixedsolve(const complex_2d_array &a, const complex_2d_array &lua, const integer_1d_array &p, const ae_int_t n, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for symmetric positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolvem(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for SPD matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-SPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixsolve(const real_2d_array &a, const ae_int_t n, const bool isupper, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolvem(const real_2d_array &cha, const ae_int_t n, const bool isupper, const real_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, real_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for SPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void spdmatrixcholeskysolve(const real_2d_array &cha, const ae_int_t n, const bool isupper, const real_1d_array &b, ae_int_t &info, densesolverreport &rep, real_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolveM(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3+M*N^2) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve. + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolvem(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixSolve(), but for Hermitian positive definite +matrices. + +Algorithm features: +* automatic detection of degenerate cases +* condition number estimation +* O(N^3) complexity +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + A - array[0..N-1,0..N-1], system matrix + N - size of A + IsUpper - what half of A is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Returns -3 for non-HPD matrices. + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixsolve(const complex_2d_array &a, const ae_int_t n, const bool isupper, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolveM(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(M*N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + HPDMatrixCholesky result + N - size of CHA + IsUpper - what half of CHA is provided + B - array[0..N-1,0..M-1], right part + M - right part size + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolvem(const complex_2d_array &cha, const ae_int_t n, const bool isupper, const complex_2d_array &b, const ae_int_t m, ae_int_t &info, densesolverreport &rep, complex_2d_array &x); + + +/************************************************************************* +Dense solver. Same as RMatrixLUSolve(), but for HPD matrices represented +by their Cholesky decomposition. + +Algorithm features: +* automatic detection of degenerate cases +* O(N^2) complexity +* condition number estimation +* matrix is represented by its upper or lower triangle + +No iterative refinement is provided because such partial representation of +matrix does not allow efficient calculation of extra-precise matrix-vector +products for large matrices. Use RMatrixSolve or RMatrixMixedSolve if you +need iterative refinement. + +INPUT PARAMETERS + CHA - array[0..N-1,0..N-1], Cholesky decomposition, + SPDMatrixCholesky result + N - size of A + IsUpper - what half of CHA is provided + B - array[0..N-1], right part + +OUTPUT PARAMETERS + Info - same as in RMatrixSolve + Rep - same as in RMatrixSolve + X - same as in RMatrixSolve + + -- ALGLIB -- + Copyright 27.01.2010 by Bochkanov Sergey +*************************************************************************/ +void hpdmatrixcholeskysolve(const complex_2d_array &cha, const ae_int_t n, const bool isupper, const complex_1d_array &b, ae_int_t &info, densesolverreport &rep, complex_1d_array &x); + + +/************************************************************************* +Dense solver. + +This subroutine finds solution of the linear system A*X=B with non-square, +possibly degenerate A. System is solved in the least squares sense, and +general least squares solution X = X0 + CX*y which minimizes |A*X-B| is +returned. If A is non-degenerate, solution in the usual sense is returned. + +Algorithm features: +* automatic detection (and correct handling!) of degenerate cases +* iterative refinement +* O(N^3) complexity + +INPUT PARAMETERS + A - array[0..NRows-1,0..NCols-1], system matrix + NRows - vertical size of A + NCols - horizontal size of A + B - array[0..NCols-1], right part + Threshold- a number in [0,1]. Singular values beyond Threshold are + considered zero. Set it to 0.0, if you don't understand + what it means, so the solver will choose good value on its + own. + +OUTPUT PARAMETERS + Info - return code: + * -4 SVD subroutine failed + * -1 if NRows<=0 or NCols<=0 or Threshold<0 was passed + * 1 if task is solved + Rep - solver report, see below for more info + X - array[0..N-1,0..M-1], it contains: + * solution of A*X=B (even for singular A) + * zeros, if SVD subroutine failed + +SOLVER REPORT + +Subroutine sets following fields of the Rep structure: +* R2 reciprocal of condition number: 1/cond(A), 2-norm. +* N = NCols +* K dim(Null(A)) +* CX array[0..N-1,0..K-1], kernel of A. + Columns of CX store such vectors that A*CX[i]=0. + + -- ALGLIB -- + Copyright 24.08.2009 by Bochkanov Sergey +*************************************************************************/ +void rmatrixsolvels(const real_2d_array &a, const ae_int_t nrows, const ae_int_t ncols, const real_1d_array &b, const double threshold, ae_int_t &info, densesolverlsreport &rep, real_1d_array &x); + +/************************************************************************* +This function initializes linear LSQR Solver. This solver is used to solve +non-symmetric (and, possibly, non-square) problems. Least squares solution +is returned for non-compatible systems. + +USAGE: +1. User initializes algorithm state with LinLSQRCreate() call +2. User tunes solver parameters with LinLSQRSetCond() and other functions +3. User calls LinLSQRSolveSparse() function which takes algorithm state + and SparseMatrix object. +4. User calls LinLSQRResults() to get solution +5. Optionally, user may call LinLSQRSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinLSQRState structure. + +INPUT PARAMETERS: + M - number of rows in A + N - number of variables, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrcreate(const ae_int_t m, const ae_int_t n, linlsqrstate &state); + + +/************************************************************************* +This function changes preconditioning settings of LinLSQQSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecunit(const linlsqrstate &state); + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetprecdiag(const linlsqrstate &state); + + +/************************************************************************* +This function sets optional Tikhonov regularization coefficient. +It is zero by default. + +INPUT PARAMETERS: + LambdaI - regularization factor, LambdaI>=0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetlambdai(const linlsqrstate &state, const double lambdai); + + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse M*N matrix in the CRS format (you MUST contvert it + to CRS format by calling SparseConvertToCRS() function + BEFORE you pass it to this function). + B - right part, array[M] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinLSQRSetPrecUnit(). However, preconditioning cost is low + and preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsolvesparse(const linlsqrstate &state, const sparsematrix &a, const real_1d_array &b); + + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsA - algorithm will be stopped if ||A^T*Rk||/(||A||*||Rk||)<=EpsA. + EpsB - algorithm will be stopped if ||Rk||<=EpsB*||B|| + MaxIts - algorithm will be stopped if number of iterations + more than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTE: if EpsA,EpsB,EpsC and MaxIts are zero then these variables will +be setted as default values. + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetcond(const linlsqrstate &state, const double epsa, const double epsb, const ae_int_t maxits); + + +/************************************************************************* +LSQR solver: results. + +This function must be called after LinLSQRSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * 1 ||Rk||<=EpsB*||B|| + * 4 ||A^T*Rk||/(||A||*||Rk||)<=EpsA + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + X contains best point found so far. + (sometimes returned on singular systems) + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrresults(const linlsqrstate &state, real_1d_array &x, linlsqrreport &rep); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 30.11.2011 by Bochkanov Sergey +*************************************************************************/ +void linlsqrsetxrep(const linlsqrstate &state, const bool needxrep); + +/************************************************************************* +This function initializes linear CG Solver. This solver is used to solve +symmetric positive definite problems. If you want to solve nonsymmetric +(or non-positive definite) problem you may use LinLSQR solver provided by +ALGLIB. + +USAGE: +1. User initializes algorithm state with LinCGCreate() call +2. User tunes solver parameters with LinCGSetCond() and other functions +3. Optionally, user sets starting point with LinCGSetStartingPoint() +4. User calls LinCGSolveSparse() function which takes algorithm state and + SparseMatrix object. +5. User calls LinCGResults() to get solution +6. Optionally, user may call LinCGSolveSparse() again to solve another + problem with different matrix and/or right part without reinitializing + LinCGState structure. + +INPUT PARAMETERS: + N - problem dimension, N>0 + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgcreate(const ae_int_t n, lincgstate &state); + + +/************************************************************************* +This function sets starting point. +By default, zero starting point is used. + +INPUT PARAMETERS: + X - starting point, array[N] + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetstartingpoint(const lincgstate &state, const real_1d_array &x); + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. By default, SolveSparse() uses diagonal preconditioner, but if +you want to use solver without preconditioning, you can call this function +which forces solver to use unit matrix for preconditioning. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecunit(const lincgstate &state); + + +/************************************************************************* +This function changes preconditioning settings of LinCGSolveSparse() +function. LinCGSolveSparse() will use diagonal of the system matrix as +preconditioner. This preconditioning mode is active by default. + +INPUT PARAMETERS: + State - structure which stores algorithm state + + -- ALGLIB -- + Copyright 19.11.2012 by Bochkanov Sergey +*************************************************************************/ +void lincgsetprecdiag(const lincgstate &state); + + +/************************************************************************* +This function sets stopping criteria. + +INPUT PARAMETERS: + EpsF - algorithm will be stopped if norm of residual is less than + EpsF*||b||. + MaxIts - algorithm will be stopped if number of iterations is more + than MaxIts. + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + +NOTES: +If both EpsF and MaxIts are zero then small EpsF will be set to small +value. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetcond(const lincgstate &state, const double epsf, const ae_int_t maxits); + + +/************************************************************************* +Procedure for solution of A*x=b with sparse A. + +INPUT PARAMETERS: + State - algorithm state + A - sparse matrix in the CRS format (you MUST contvert it to + CRS format by calling SparseConvertToCRS() function). + IsUpper - whether upper or lower triangle of A is used: + * IsUpper=True => only upper triangle is used and lower + triangle is not referenced at all + * IsUpper=False => only lower triangle is used and upper + triangle is not referenced at all + B - right part, array[N] + +RESULT: + This function returns no result. + You can get solution by calling LinCGResults() + +NOTE: this function uses lightweight preconditioning - multiplication by + inverse of diag(A). If you want, you can turn preconditioning off by + calling LinCGSetPrecUnit(). However, preconditioning cost is low and + preconditioner is very important for solution of badly scaled + problems. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsolvesparse(const lincgstate &state, const sparsematrix &a, const bool isupper, const real_1d_array &b); + + +/************************************************************************* +CG-solver: results. + +This function must be called after LinCGSolve + +INPUT PARAMETERS: + State - algorithm state + +OUTPUT PARAMETERS: + X - array[N], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -5 input matrix is either not positive definite, + too large or too small + * -4 overflow/underflow during solution + (ill conditioned problem) + * 1 ||residual||<=EpsF*||b|| + * 5 MaxIts steps was taken + * 7 rounding errors prevent further progress, + best point found is returned + * Rep.IterationsCount contains iterations count + * NMV countains number of matrix-vector calculations + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgresults(const lincgstate &state, real_1d_array &x, lincgreport &rep); + + +/************************************************************************* +This function sets restart frequency. By default, algorithm is restarted +after N subsequent iterations. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrestartfreq(const lincgstate &state, const ae_int_t srf); + + +/************************************************************************* +This function sets frequency of residual recalculations. + +Algorithm updates residual r_k using iterative formula, but recalculates +it from scratch after each 10 iterations. It is done to avoid accumulation +of numerical errors and to stop algorithm when r_k starts to grow. + +Such low update frequence (1/10) gives very little overhead, but makes +algorithm a bit more robust against numerical errors. However, you may +change it + +INPUT PARAMETERS: + Freq - desired update frequency, Freq>=0. + Zero value means that no updates will be done. + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetrupdatefreq(const lincgstate &state, const ae_int_t freq); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to MinCGOptimize(). + + -- ALGLIB -- + Copyright 14.11.2011 by Bochkanov Sergey +*************************************************************************/ +void lincgsetxrep(const lincgstate &state, const bool needxrep); + +/************************************************************************* + LEVENBERG-MARQUARDT-LIKE NONLINEAR SOLVER + +DESCRIPTION: +This algorithm solves system of nonlinear equations + F[0](x[0], ..., x[n-1]) = 0 + F[1](x[0], ..., x[n-1]) = 0 + ... + F[M-1](x[0], ..., x[n-1]) = 0 +with M/N do not necessarily coincide. Algorithm converges quadratically +under following conditions: + * the solution set XS is nonempty + * for some xs in XS there exist such neighbourhood N(xs) that: + * vector function F(x) and its Jacobian J(x) are continuously + differentiable on N + * ||F(x)|| provides local error bound on N, i.e. there exists such + c1, that ||F(x)||>c1*distance(x,XS) +Note that these conditions are much more weaker than usual non-singularity +conditions. For example, algorithm will converge for any affine function +F (whether its Jacobian singular or not). + + +REQUIREMENTS: +Algorithm will request following information during its operation: +* function vector F[] and Jacobian matrix at given point X +* value of merit function f(x)=F[0]^2(x)+...+F[M-1]^2(x) at given point X + + +USAGE: +1. User initializes algorithm state with NLEQCreateLM() call +2. User tunes solver parameters with NLEQSetCond(), NLEQSetStpMax() and + other functions +3. User calls NLEQSolve() function which takes algorithm state and + pointers (delegates, etc.) to callback functions which calculate merit + function value and Jacobian. +4. User calls NLEQResults() to get solution +5. Optionally, user may call NLEQRestartFrom() to solve another problem + with same parameters (N/M) but another starting point and/or another + function vector. NLEQRestartFrom() allows to reuse already initialized + structure. + + +INPUT PARAMETERS: + N - space dimension, N>1: + * if provided, only leading N elements of X are used + * if not provided, determined automatically from size of X + M - system size + X - starting point + + +OUTPUT PARAMETERS: + State - structure which stores algorithm state + + +NOTES: +1. you may tune stopping conditions with NLEQSetCond() function +2. if target function contains exp() or other fast growing functions, and + optimization algorithm makes too large steps which leads to overflow, + use NLEQSetStpMax() function to bound algorithm's steps. +3. this algorithm is a slightly modified implementation of the method + described in 'Levenberg-Marquardt method for constrained nonlinear + equations with strong local convergence properties' by Christian Kanzow + Nobuo Yamashita and Masao Fukushima and further developed in 'On the + convergence of a New Levenberg-Marquardt Method' by Jin-yan Fan and + Ya-Xiang Yuan. + + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqcreatelm(const ae_int_t n, const ae_int_t m, const real_1d_array &x, nleqstate &state); +void nleqcreatelm(const ae_int_t m, const real_1d_array &x, nleqstate &state); + + +/************************************************************************* +This function sets stopping conditions for the nonlinear solver + +INPUT PARAMETERS: + State - structure which stores algorithm state + EpsF - >=0 + The subroutine finishes its work if on k+1-th iteration + the condition ||F||<=EpsF is satisfied + MaxIts - maximum number of iterations. If MaxIts=0, the number of + iterations is unlimited. + +Passing EpsF=0 and MaxIts=0 simultaneously will lead to automatic +stopping criterion selection (small EpsF). + +NOTES: + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetcond(const nleqstate &state, const double epsf, const ae_int_t maxits); + + +/************************************************************************* +This function turns on/off reporting. + +INPUT PARAMETERS: + State - structure which stores algorithm state + NeedXRep- whether iteration reports are needed or not + +If NeedXRep is True, algorithm will call rep() callback function if it is +provided to NLEQSolve(). + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetxrep(const nleqstate &state, const bool needxrep); + + +/************************************************************************* +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 target function contains exp() or other fast +growing functions, and algorithm makes too large steps which lead 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. + + -- ALGLIB -- + Copyright 20.08.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqsetstpmax(const nleqstate &state, const double stpmax); + + +/************************************************************************* +This function provides reverse communication interface +Reverse communication interface is not documented or recommended to use. +See below for functions which provide better documented API +*************************************************************************/ +bool nleqiteration(const nleqstate &state); + + +/************************************************************************* +This family of functions is used to launcn iterations of nonlinear solver + +These functions accept following parameters: + state - algorithm state + func - callback which calculates function (or merit function) + value func at given point x + jac - callback which calculates function vector fi[] + and Jacobian jac at given point x + rep - optional callback which is called after each iteration + can be NULL + ptr - optional pointer which is passed to func/grad/hess/jac/rep + can be NULL + + + -- ALGLIB -- + Copyright 20.03.2009 by Bochkanov Sergey + +*************************************************************************/ +void nleqsolve(nleqstate &state, + void (*func)(const real_1d_array &x, double &func, void *ptr), + void (*jac)(const real_1d_array &x, real_1d_array &fi, real_2d_array &jac, void *ptr), + void (*rep)(const real_1d_array &x, double func, void *ptr) = NULL, + void *ptr = NULL); + + +/************************************************************************* +NLEQ solver results + +INPUT PARAMETERS: + State - algorithm state. + +OUTPUT PARAMETERS: + X - array[0..N-1], solution + Rep - optimization report: + * Rep.TerminationType completetion code: + * -4 ERROR: algorithm has converged to the + stationary point Xf which is local minimum of + f=F[0]^2+...+F[m-1]^2, but is not solution of + nonlinear system. + * 1 sqrt(f)<=EpsF. + * 5 MaxIts steps was taken + * 7 stopping conditions are too stringent, + further improvement is impossible + * Rep.IterationsCount contains iterations count + * NFEV countains number of function calculations + * ActiveConstraints contains number of active constraints + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresults(const nleqstate &state, real_1d_array &x, nleqreport &rep); + + +/************************************************************************* +NLEQ solver results + +Buffered implementation of NLEQResults(), which uses pre-allocated buffer +to store X[]. If buffer size is too small, it resizes buffer. It is +intended to be used in the inner cycles of performance critical algorithms +where array reallocation penalty is too large to be ignored. + + -- ALGLIB -- + Copyright 20.08.2009 by Bochkanov Sergey +*************************************************************************/ +void nleqresultsbuf(const nleqstate &state, real_1d_array &x, nleqreport &rep); + + +/************************************************************************* +This subroutine restarts CG algorithm from new point. All optimization +parameters are left unchanged. + +This function allows to solve multiple optimization problems (which +must have same number of dimensions) without object reallocation penalty. + +INPUT PARAMETERS: + State - structure used for reverse communication previously + allocated with MinCGCreate call. + X - new starting point. + BndL - new lower bounds + BndU - new upper bounds + + -- ALGLIB -- + Copyright 30.07.2010 by Bochkanov Sergey +*************************************************************************/ +void nleqrestartfrom(const nleqstate &state, const real_1d_array &x); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void rmatrixsolve(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +void rmatrixsolvem(/* Real */ ae_matrix* a, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_bool rfs, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +void rmatrixlusolve(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +void rmatrixlusolvem(/* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +void rmatrixmixedsolve(/* Real */ ae_matrix* a, + /* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +void rmatrixmixedsolvem(/* Real */ ae_matrix* a, + /* Real */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +void cmatrixsolvem(/* Complex */ ae_matrix* a, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_bool rfs, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +void cmatrixsolve(/* Complex */ ae_matrix* a, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state); +void cmatrixlusolvem(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +void cmatrixlusolve(/* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state); +void cmatrixmixedsolvem(/* Complex */ ae_matrix* a, + /* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +void cmatrixmixedsolve(/* Complex */ ae_matrix* a, + /* Complex */ ae_matrix* lua, + /* Integer */ ae_vector* p, + ae_int_t n, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state); +void spdmatrixsolvem(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +void spdmatrixsolve(/* Real */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +void spdmatrixcholeskysolvem(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_matrix* x, + ae_state *_state); +void spdmatrixcholeskysolve(/* Real */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +void hpdmatrixsolvem(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +void hpdmatrixsolve(/* Complex */ ae_matrix* a, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state); +void hpdmatrixcholeskysolvem(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_matrix* b, + ae_int_t m, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_matrix* x, + ae_state *_state); +void hpdmatrixcholeskysolve(/* Complex */ ae_matrix* cha, + ae_int_t n, + ae_bool isupper, + /* Complex */ ae_vector* b, + ae_int_t* info, + densesolverreport* rep, + /* Complex */ ae_vector* x, + ae_state *_state); +void rmatrixsolvels(/* Real */ ae_matrix* a, + ae_int_t nrows, + ae_int_t ncols, + /* Real */ ae_vector* b, + double threshold, + ae_int_t* info, + densesolverlsreport* rep, + /* Real */ ae_vector* x, + ae_state *_state); +ae_bool _densesolverreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _densesolverreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _densesolverreport_clear(void* _p); +void _densesolverreport_destroy(void* _p); +ae_bool _densesolverlsreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _densesolverlsreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _densesolverlsreport_clear(void* _p); +void _densesolverlsreport_destroy(void* _p); +void linlsqrcreate(ae_int_t m, + ae_int_t n, + linlsqrstate* state, + ae_state *_state); +void linlsqrsetb(linlsqrstate* state, + /* Real */ ae_vector* b, + ae_state *_state); +void linlsqrsetprecunit(linlsqrstate* state, ae_state *_state); +void linlsqrsetprecdiag(linlsqrstate* state, ae_state *_state); +void linlsqrsetlambdai(linlsqrstate* state, + double lambdai, + ae_state *_state); +ae_bool linlsqriteration(linlsqrstate* state, ae_state *_state); +void linlsqrsolvesparse(linlsqrstate* state, + sparsematrix* a, + /* Real */ ae_vector* b, + ae_state *_state); +void linlsqrsetcond(linlsqrstate* state, + double epsa, + double epsb, + ae_int_t maxits, + ae_state *_state); +void linlsqrresults(linlsqrstate* state, + /* Real */ ae_vector* x, + linlsqrreport* rep, + ae_state *_state); +void linlsqrsetxrep(linlsqrstate* state, + ae_bool needxrep, + ae_state *_state); +void linlsqrrestart(linlsqrstate* state, ae_state *_state); +ae_bool _linlsqrstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _linlsqrstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _linlsqrstate_clear(void* _p); +void _linlsqrstate_destroy(void* _p); +ae_bool _linlsqrreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _linlsqrreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _linlsqrreport_clear(void* _p); +void _linlsqrreport_destroy(void* _p); +void lincgcreate(ae_int_t n, lincgstate* state, ae_state *_state); +void lincgsetstartingpoint(lincgstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +void lincgsetb(lincgstate* state, + /* Real */ ae_vector* b, + ae_state *_state); +void lincgsetprecunit(lincgstate* state, ae_state *_state); +void lincgsetprecdiag(lincgstate* state, ae_state *_state); +void lincgsetcond(lincgstate* state, + double epsf, + ae_int_t maxits, + ae_state *_state); +ae_bool lincgiteration(lincgstate* state, ae_state *_state); +void lincgsolvesparse(lincgstate* state, + sparsematrix* a, + ae_bool isupper, + /* Real */ ae_vector* b, + ae_state *_state); +void lincgresults(lincgstate* state, + /* Real */ ae_vector* x, + lincgreport* rep, + ae_state *_state); +void lincgsetrestartfreq(lincgstate* state, + ae_int_t srf, + ae_state *_state); +void lincgsetrupdatefreq(lincgstate* state, + ae_int_t freq, + ae_state *_state); +void lincgsetxrep(lincgstate* state, ae_bool needxrep, ae_state *_state); +void lincgrestart(lincgstate* state, ae_state *_state); +ae_bool _lincgstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _lincgstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _lincgstate_clear(void* _p); +void _lincgstate_destroy(void* _p); +ae_bool _lincgreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _lincgreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _lincgreport_clear(void* _p); +void _lincgreport_destroy(void* _p); +void nleqcreatelm(ae_int_t n, + ae_int_t m, + /* Real */ ae_vector* x, + nleqstate* state, + ae_state *_state); +void nleqsetcond(nleqstate* state, + double epsf, + ae_int_t maxits, + ae_state *_state); +void nleqsetxrep(nleqstate* state, ae_bool needxrep, ae_state *_state); +void nleqsetstpmax(nleqstate* state, double stpmax, ae_state *_state); +ae_bool nleqiteration(nleqstate* state, ae_state *_state); +void nleqresults(nleqstate* state, + /* Real */ ae_vector* x, + nleqreport* rep, + ae_state *_state); +void nleqresultsbuf(nleqstate* state, + /* Real */ ae_vector* x, + nleqreport* rep, + ae_state *_state); +void nleqrestartfrom(nleqstate* state, + /* Real */ ae_vector* x, + ae_state *_state); +ae_bool _nleqstate_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _nleqstate_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _nleqstate_clear(void* _p); +void _nleqstate_destroy(void* _p); +ae_bool _nleqreport_init(void* _p, ae_state *_state, ae_bool make_automatic); +ae_bool _nleqreport_init_copy(void* _dst, void* _src, ae_state *_state, ae_bool make_automatic); +void _nleqreport_clear(void* _p); +void _nleqreport_destroy(void* _p); + +} +#endif + diff --git a/src/inc/alglib/specialfunctions.cpp b/src/inc/alglib/specialfunctions.cpp new file mode 100644 index 0000000..bd786b6 --- /dev/null +++ b/src/inc/alglib/specialfunctions.cpp @@ -0,0 +1,9637 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "specialfunctions.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Gamma function + +Input parameters: + X - argument + +Domain: + 0 < X < 171.6 + -170 < X < 0, X is not an integer. + +Relative error: + arithmetic domain # trials peak rms + IEEE -170,-33 20000 2.3e-15 3.3e-16 + IEEE -33, 33 20000 9.4e-16 2.2e-16 + IEEE 33, 171.6 20000 2.3e-15 3.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Original copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double gammafunction(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::gammafunction(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Natural logarithm of gamma function + +Input parameters: + X - argument + +Result: + logarithm of the absolute value of the Gamma(X). + +Output parameters: + SgnGam - sign(Gamma(X)) + +Domain: + 0 < X < 2.55e305 + -2.55e305 < X < 0, X is not an integer. + +ACCURACY: +arithmetic domain # trials peak rms + IEEE 0, 3 28000 5.4e-16 1.1e-16 + IEEE 2.718, 2.556e305 40000 3.5e-16 8.3e-17 +The error criterion was relative when the function magnitude +was greater than one but absolute when it was less than one. + +The following test used the relative error criterion, though +at certain points the relative error could be much higher than +indicated. + IEEE -200, -4 10000 4.8e-16 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double lngamma(const double x, double &sgngam) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::lngamma(x, &sgngam, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Error function + +The integral is + + x + - + 2 | | 2 + erf(x) = -------- | exp( - t ) dt. + sqrt(pi) | | + - + 0 + +For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise +erf(x) = 1 - erfc(x). + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 3.7e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunction(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::errorfunction(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complementary error function + + 1 - erf(x) = + + inf. + - + 2 | | 2 + erfc(x) = -------- | exp( - t ) dt + sqrt(pi) | | + - + x + + +For small x, erfc(x) = 1 - erf(x); otherwise rational +approximations are computed. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,26.6417 30000 5.7e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunctionc(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::errorfunctionc(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Normal distribution function + +Returns the area under the Gaussian probability density +function, integrated from minus infinity to x: + + x + - + 1 | | 2 + ndtr(x) = --------- | exp( - t /2 ) dt + sqrt(2pi) | | + - + -inf. + + = ( 1 + erf(z) ) / 2 + = erfc(z) / 2 + +where z = x/sqrt(2). Computation is via the functions +erf and erfc. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE -13,0 30000 3.4e-14 6.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double normaldistribution(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::normaldistribution(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of the error function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double inverf(const double e) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::inverf(e, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of Normal distribution function + +Returns the argument, x, for which the area under the +Gaussian probability density function (integrated from +minus infinity to x) is equal to y. + + +For small arguments 0 < y < exp(-2), the program computes +z = sqrt( -2.0 * log(y) ); then the approximation is +x = z - log(z)/z - (1/z) P(1/z) / Q(1/z). +There are two rational functions P/Q, one for 0 < y < exp(-32) +and the other for y up to exp(-2). For larger arguments, +w = y - 0.5, and x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0.125, 1 20000 7.2e-16 1.3e-16 + IEEE 3e-308, 0.135 50000 4.6e-16 9.8e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double invnormaldistribution(const double y0) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invnormaldistribution(y0, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Incomplete gamma integral + +The function is defined by + + x + - + 1 | | -t a-1 + igam(a,x) = ----- | e t dt. + - | | + | (a) - + 0 + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 200000 3.6e-14 2.9e-15 + IEEE 0,100 300000 9.9e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegamma(const double a, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::incompletegamma(a, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complemented incomplete gamma integral + +The function is defined by + + + igamc(a,x) = 1 - igam(a,x) + + inf. + - + 1 | | -t a-1 + = ----- | e t dt. + - | | + | (a) - + x + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + +Tested at random a, x. + a x Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,100 200000 1.9e-14 1.7e-15 + IEEE 0.01,0.5 0,100 200000 1.4e-13 1.6e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegammac(const double a, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::incompletegammac(a, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of complemented imcomplete gamma integral + +Given p, the function finds x such that + + igamc( a, x ) = p. + +Starting with the approximate value + + 3 + x = a t + + where + + t = 1 - d - ndtri(p) sqrt(d) + +and + + d = 1/9a, + +the routine performs up to 10 Newton iterations to find the +root of igamc(a,x) - p = 0. + +ACCURACY: + +Tested at random a, p in the intervals indicated. + + a p Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,0.5 100000 1.0e-14 1.7e-15 + IEEE 0.01,0.5 0,0.5 100000 9.0e-14 3.4e-15 + IEEE 0.5,10000 0,0.5 20000 2.3e-13 3.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletegammac(const double a, const double y0) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invincompletegammac(a, y0, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Airy function + +Solution of the differential equation + +y"(x) = xy. + +The function returns the two independent solutions Ai, Bi +and their first derivatives Ai'(x), Bi'(x). + +Evaluation is by power series summation for small x, +by rational minimax approximations for large x. + + + +ACCURACY: +Error criterion is absolute when function <= 1, relative +when function > 1, except * denotes relative error criterion. +For large negative x, the absolute error increases as x^1.5. +For large positive x, the relative error increases as x^1.5. + +Arithmetic domain function # trials peak rms +IEEE -10, 0 Ai 10000 1.6e-15 2.7e-16 +IEEE 0, 10 Ai 10000 2.3e-14* 1.8e-15* +IEEE -10, 0 Ai' 10000 4.6e-15 7.6e-16 +IEEE 0, 10 Ai' 10000 1.8e-14* 1.5e-15* +IEEE -10, 10 Bi 30000 4.2e-15 5.3e-16 +IEEE -10, 10 Bi' 30000 4.9e-15 7.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void airy(const double x, double &ai, double &aip, double &bi, double &bip) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::airy(x, &ai, &aip, &bi, &bip, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of order zero + +Returns Bessel function of order zero of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval the following rational +approximation is used: + + + 2 2 +(w - r ) (w - r ) P (w) / Q (w) + 1 2 3 8 + + 2 +where w = x and the two r's are zeros of the function. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 60000 4.2e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj0(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselj0(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of order one + +Returns Bessel function of order one of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 24 term Chebyshev +expansion is used. In the second, the asymptotic +trigonometric representation is employed using two +rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 2.6e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj1(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselj1(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The ratio of jn(x) to j0(x) is computed by backward +recurrence. First the ratio jn/jn-1 is found by a +continued fraction expansion. Then the recurrence +relating successive orders is applied until j0 or j1 is +reached. + +If n = 0 or 1 the routine for j0 or j1 is called +directly. + +ACCURACY: + + Absolute error: +arithmetic range # trials peak rms + IEEE 0, 30 5000 4.4e-16 7.9e-17 + + +Not suitable for large n or x. Use jv() (fractional order) instead. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseljn(const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besseljn(n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of the second kind, order zero + +Returns Bessel function of the second kind, of order +zero, of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval a rational approximation +R(x) is employed to compute + y0(x) = R(x) + 2 * log(x) * j0(x) / PI. +Thus a call to j0() is required. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + + + +ACCURACY: + + Absolute error, when y0(x) < 1; else relative error: + +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.3e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely0(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::bessely0(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of second kind of order one + +Returns Bessel function of the second kind of order one +of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 25 term Chebyshev +expansion is used, and a call to j1() is required. +In the second, the asymptotic trigonometric representation +is employed using two rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.0e-15 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely1(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::bessely1(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Bessel function of second kind of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The function is evaluated by forward recurrence on +n, starting with values computed by the routines +y0() and y1(). + +If n = 0 or 1 the routine for y0 or y1 is called +directly. + +ACCURACY: + Absolute error, except relative + when y > 1: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 3.4e-15 4.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselyn(const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselyn(n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modified Bessel function of order zero + +Returns modified Bessel function of order zero of the +argument. + +The function is defined as i0(x) = j0( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 5.8e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli0(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besseli0(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modified Bessel function of order one + +Returns modified Bessel function of order one of the +argument. + +The function is defined as i1(x) = -i j1( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.9e-15 2.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli1(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besseli1(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modified Bessel function, second kind, order zero + +Returns modified Bessel function of the second kind +of order zero of the argument. + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + +Tested at 2000 random points between 0 and 8. Peak absolute +error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15. + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk0(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselk0(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modified Bessel function, second kind, order one + +Computes the modified Bessel function of the second kind +of order one of the argument. + +The range is partitioned into the two intervals [0,2] and +(2, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk1(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselk1(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Modified Bessel function, second kind, integer order + +Returns modified Bessel function of the second kind +of order n of the argument. + +The range is partitioned into the two intervals [0,9.55] and +(9.55, infinity). An ascending power series is used in the +low range, and an asymptotic expansion in the high range. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 90000 1.8e-8 3.0e-10 + +Error is high only near the crossover point x = 9.55 +between the two expansions used. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselkn(const ae_int_t nn, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::besselkn(nn, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Beta function + + + - - + | (a) | (b) +beta( a, b ) = -----------. + - + | (a+b) + +For large arguments the logarithm of the function is +evaluated using lgam(), then exponentiated. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 8.1e-14 1.1e-14 + +Cephes Math Library Release 2.0: April, 1987 +Copyright 1984, 1987 by Stephen L. Moshier +*************************************************************************/ +double beta(const double a, const double b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::beta(a, b, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Incomplete beta integral + +Returns incomplete beta integral of the arguments, evaluated +from zero to x. The function is defined as + + x + - - + | (a+b) | | a-1 b-1 + ----------- | t (1-t) dt. + - - | | + | (a) | (b) - + 0 + +The domain of definition is 0 <= x <= 1. In this +implementation a and b are restricted to positive values. +The integral from x to 1 may be obtained by the symmetry +relation + + 1 - incbet( a, b, x ) = incbet( b, a, 1-x ). + +The integral is evaluated by a continued fraction expansion +or, when b*x is small, by a power series. + +ACCURACY: + +Tested at uniformly distributed random points (a,b,x) with a and b +in "domain" and x between 0 and 1. + Relative error +arithmetic domain # trials peak rms + IEEE 0,5 10000 6.9e-15 4.5e-16 + IEEE 0,85 250000 2.2e-13 1.7e-14 + IEEE 0,1000 30000 5.3e-12 6.3e-13 + IEEE 0,10000 250000 9.3e-11 7.1e-12 + IEEE 0,100000 10000 8.7e-10 4.8e-11 +Outputs smaller than the IEEE gradual underflow threshold +were excluded from these statistics. + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletebeta(const double a, const double b, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::incompletebeta(a, b, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of imcomplete beta integral + +Given y, the function finds x such that + + incbet( a, b, x ) = y . + +The routine performs interval halving or Newton iterations to find the +root of incbet(a,b,x) - y = 0. + + +ACCURACY: + + Relative error: + x a,b +arithmetic domain domain # trials peak rms + IEEE 0,1 .5,10000 50000 5.8e-12 1.3e-13 + IEEE 0,1 .25,100 100000 1.8e-13 3.9e-15 + IEEE 0,1 0,5 50000 1.1e-12 5.5e-15 +With a and b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 50000 5.8e-12 1.1e-13 + IEEE 0,1 .5,100 100000 1.7e-14 7.9e-16 +With a = .5, b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 10000 8.3e-11 1.0e-11 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1996, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletebeta(const double a, const double b, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invincompletebeta(a, b, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Binomial distribution + +Returns the sum of the terms 0 through k of the Binomial +probability density: + + k + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=0 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p), with p between 0 and 1. + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 4.3e-15 2.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialdistribution(const ae_int_t k, const ae_int_t n, const double p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::binomialdistribution(k, n, p, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complemented binomial distribution + +Returns the sum of the terms k+1 through n of the Binomial +probability density: + + n + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=k+1 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 6.7e-15 8.2e-16 + For p between 0 and .001: + IEEE 0,100 100000 1.5e-13 2.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialcdistribution(const ae_int_t k, const ae_int_t n, const double p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::binomialcdistribution(k, n, p, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse binomial distribution + +Finds the event probability p such that the sum of the +terms 0 through k of the Binomial probability density +is equal to the given cumulative probability y. + +This is accomplished using the inverse beta integral +function and the relation + +1 - p = incbi( n-k, k+1, y ). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 2.3e-14 6.4e-16 + IEEE 0,10000 100000 6.6e-12 1.2e-13 + For p between 10^-6 and 0.001: + IEEE 0,100 100000 2.0e-12 1.3e-14 + IEEE 0,10000 100000 1.5e-12 3.2e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invbinomialdistribution(const ae_int_t k, const ae_int_t n, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invbinomialdistribution(k, n, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the value of the Chebyshev polynomials of the +first and second kinds. + +Parameters: + r - polynomial kind, either 1 or 2. + n - degree, n>=0 + x - argument, -1 <= x <= 1 + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevcalculate(const ae_int_t r, const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::chebyshevcalculate(r, n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Summation of Chebyshev polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*T0(x) + c[1]*T1(x) + ... + c[N]*TN(x) +or + c[0]*U0(x) + c[1]*U1(x) + ... + c[N]*UN(x) +depending on the R. + +Parameters: + r - polynomial kind, either 1 or 2. + n - degree, n>=0 + x - argument + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevsum(const real_1d_array &c, const ae_int_t r, const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::chebyshevsum(const_cast(c.c_ptr()), r, n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Representation of Tn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void chebyshevcoefficients(const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::chebyshevcoefficients(n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Conversion of a series of Chebyshev polynomials to a power series. + +Represents A[0]*T0(x) + A[1]*T1(x) + ... + A[N]*Tn(x) as +B[0] + B[1]*X + ... + B[N]*X^N. + +Input parameters: + A - Chebyshev series coefficients + N - degree, N>=0 + +Output parameters + B - power series coefficients +*************************************************************************/ +void fromchebyshev(const real_1d_array &a, const ae_int_t n, real_1d_array &b) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fromchebyshev(const_cast(a.c_ptr()), n, const_cast(b.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Chi-square distribution + +Returns the area under the left hand tail (from 0 to x) +of the Chi square probability density function with +v degrees of freedom. + + + x + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + 0 + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igam( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquaredistribution(const double v, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::chisquaredistribution(v, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complemented Chi-square distribution + +Returns the area under the right hand tail (from x to +infinity) of the Chi square probability density function +with v degrees of freedom: + + inf. + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + x + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquarecdistribution(const double v, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::chisquarecdistribution(v, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of complemented Chi-square distribution + +Finds the Chi-square argument x such that the integral +from x to infinity of the Chi-square density is equal +to the given cumulative probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + x/2 = igami( df/2, y ); + +ACCURACY: + +See inverse incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double invchisquaredistribution(const double v, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invchisquaredistribution(v, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Dawson's Integral + +Approximates the integral + + x + - + 2 | | 2 + dawsn(x) = exp( -x ) | exp( t ) dt + | | + - + 0 + +Three different rational approximations are employed, for +the intervals 0 to 3.25; 3.25 to 6.25; and 6.25 up. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,10 10000 6.9e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double dawsonintegral(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::dawsonintegral(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +using the approximation + + P(x) - log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegralk(const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::ellipticintegralk(m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +where m = 1 - m1, using the approximation + + P(x) - log x Q(x). + +The argument m1 is used rather than m so that the logarithmic +singularity at m = 1 will be shifted to the origin; this +preserves maximum accuracy. + +K(0) = pi/2. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Àëãîðèòì âçÿò èç áèáëèîòåêè Cephes +*************************************************************************/ +double ellipticintegralkhighprecision(const double m1) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::ellipticintegralkhighprecision(m1, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Incomplete elliptic integral of the first kind F(phi|m) + +Approximates the integral + + + + phi + - + | | + | dt +F(phi_\m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + + + + +ACCURACY: + +Tested at random points with m in [0, 1] and phi as indicated. + + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 200000 7.4e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegralk(const double phi, const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::incompleteellipticintegralk(phi, m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complete elliptic integral of the second kind + +Approximates the integral + + + pi/2 + - + | | 2 +E(m) = | sqrt( 1 - m sin t ) dt + | | + - + 0 + +using the approximation + + P(x) - x log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 1 10000 2.1e-16 7.3e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegrale(const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::ellipticintegrale(m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Incomplete elliptic integral of the second kind + +Approximates the integral + + + phi + - + | | + | 2 +E(phi_\m) = | sqrt( 1 - m sin t ) dt + | + | | + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + +ACCURACY: + +Tested at random arguments with phi in [-10, 10] and m in +[0, 1]. + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 150000 3.3e-15 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1993, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegrale(const double phi, const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::incompleteellipticintegrale(phi, m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Exponential integral Ei(x) + + x + - t + | | e + Ei(x) = -|- --- dt . + | | t + - + -inf + +Not defined for x <= 0. +See also expn.c. + + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,100 50000 8.6e-16 1.3e-16 + +Cephes Math Library Release 2.8: May, 1999 +Copyright 1999 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralei(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::exponentialintegralei(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Exponential integral En(x) + +Evaluates the exponential integral + + inf. + - + | | -xt + | e + E (x) = | ---- dt. + n | n + | | t + - + 1 + + +Both n and x must be nonnegative. + +The routine employs either a power series, a continued +fraction, or an asymptotic formula depending on the +relative values of n and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 10000 1.7e-15 3.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 2000 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralen(const double x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::exponentialintegralen(x, n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +F distribution + +Returns the area from zero to x under the F density +function (also known as Snedcor's density or the +variance ratio density). This is the density +of x = (u1/df1)/(u2/df2), where u1 and u2 are random +variables having Chi square distributions with df1 +and df2 degrees of freedom, respectively. +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ). + + +The arguments a and b are greater than zero, and x is +nonnegative. + +ACCURACY: + +Tested at random points (a,b,x). + + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 0,100 100000 9.8e-15 1.7e-15 + IEEE 1,5 0,100 100000 6.5e-15 3.5e-16 + IEEE 0,1 1,10000 100000 2.2e-11 3.3e-12 + IEEE 1,5 1,10000 100000 1.1e-11 1.7e-13 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fdistribution(const ae_int_t a, const ae_int_t b, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::fdistribution(a, b, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complemented F distribution + +Returns the area from x to infinity under the F density +function (also known as Snedcor's density or the +variance ratio density). + + + inf. + - + 1 | | a-1 b-1 +1-P(x) = ------ | t (1-t) dt + B(a,b) | | + - + x + + +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ). + + +ACCURACY: + +Tested at random points (a,b,x) in the indicated intervals. + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 1,100 100000 3.7e-14 5.9e-16 + IEEE 1,5 1,100 100000 8.0e-15 1.6e-15 + IEEE 0,1 1,10000 100000 1.8e-11 3.5e-13 + IEEE 1,5 1,10000 100000 2.0e-11 3.0e-12 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fcdistribution(const ae_int_t a, const ae_int_t b, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::fcdistribution(a, b, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse of complemented F distribution + +Finds the F density argument x such that the integral +from x to infinity of the F density is equal to the +given probability p. + +This is accomplished using the inverse beta integral +function and the relations + + z = incbi( df2/2, df1/2, p ) + x = df2 (1-z) / (df1 z). + +Note: the following relations hold for the inverse of +the uncomplemented F distribution: + + z = incbi( df1/2, df2/2, p ) + x = df2 z / (df1 (1-z)). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between .001 and 1: + IEEE 1,100 100000 8.3e-15 4.7e-16 + IEEE 1,10000 100000 2.1e-11 1.4e-13 + For p between 10^-6 and 10^-3: + IEEE 1,100 50000 1.3e-12 8.4e-15 + IEEE 1,10000 50000 3.0e-12 4.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invfdistribution(const ae_int_t a, const ae_int_t b, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invfdistribution(a, b, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Fresnel integral + +Evaluates the Fresnel integrals + + x + - + | | +C(x) = | cos(pi/2 t**2) dt, + | | + - + 0 + + x + - + | | +S(x) = | sin(pi/2 t**2) dt. + | | + - + 0 + + +The integrals are evaluated by a power series for x < 1. +For x >= 1 auxiliary functions f(x) and g(x) are employed +such that + +C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 ) +S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 ) + + + +ACCURACY: + + Relative error. + +Arithmetic function domain # trials peak rms + IEEE S(x) 0, 10 10000 2.0e-15 3.2e-16 + IEEE C(x) 0, 10 10000 1.8e-15 3.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void fresnelintegral(const double x, double &c, double &s) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::fresnelintegral(x, &c, &s, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the value of the Hermite polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial Hn at x +*************************************************************************/ +double hermitecalculate(const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hermitecalculate(n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Summation of Hermite polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*H0(x) + c[1]*H1(x) + ... + c[N]*HN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial at x +*************************************************************************/ +double hermitesum(const real_1d_array &c, const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::hermitesum(const_cast(c.c_ptr()), n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Representation of Hn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void hermitecoefficients(const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hermitecoefficients(n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Jacobian Elliptic Functions + +Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m), +and dn(u|m) of parameter m between 0 and 1, and real +argument u. + +These functions are periodic, with quarter-period on the +real axis equal to the complete elliptic integral +ellpk(1.0-m). + +Relation to incomplete elliptic integral: +If u = ellik(phi,m), then sn(u|m) = sin(phi), +and cn(u|m) = cos(phi). Phi is called the amplitude of u. + +Computation is by means of the arithmetic-geometric mean +algorithm, except when m is within 1e-9 of 0 or 1. In the +latter case with m close to 1, the approximation applies +only for phi < pi/2. + +ACCURACY: + +Tested at random points with u between 0 and 10, m between +0 and 1. + + Absolute error (* = relative error): +arithmetic function # trials peak rms + IEEE phi 10000 9.2e-16* 1.4e-16* + IEEE sn 50000 4.1e-15 4.6e-16 + IEEE cn 40000 3.6e-15 4.4e-16 + IEEE dn 10000 1.3e-12 1.8e-14 + + Peak error observed in consistency check using addition +theorem for sn(u+v) was 4e-16 (absolute). Also tested by +the above relation to the incomplete elliptic integral. +Accuracy deteriorates when u is large. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void jacobianellipticfunctions(const double u, const double m, double &sn, double &cn, double &dn, double &ph) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::jacobianellipticfunctions(u, m, &sn, &cn, &dn, &ph, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the value of the Laguerre polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial Ln at x +*************************************************************************/ +double laguerrecalculate(const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::laguerrecalculate(n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Summation of Laguerre polynomials using Clenshaw’s recurrence formula. + +This routine calculates c[0]*L0(x) + c[1]*L1(x) + ... + c[N]*LN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial at x +*************************************************************************/ +double laguerresum(const real_1d_array &c, const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::laguerresum(const_cast(c.c_ptr()), n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Representation of Ln as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void laguerrecoefficients(const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::laguerrecoefficients(n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the value of the Legendre polynomial Pn. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial Pn at x +*************************************************************************/ +double legendrecalculate(const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::legendrecalculate(n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Summation of Legendre polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*P0(x) + c[1]*P1(x) + ... + c[N]*PN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial at x +*************************************************************************/ +double legendresum(const real_1d_array &c, const ae_int_t n, const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::legendresum(const_cast(c.c_ptr()), n, x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Representation of Pn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void legendrecoefficients(const ae_int_t n, real_1d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::legendrecoefficients(n, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Poisson distribution + +Returns the sum of the first k+1 terms of the Poisson +distribution: + + k j + -- -m m + > e -- + -- j! + j=0 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the relation + +y = pdtr( k, m ) = igamc( k+1, m ). + +The arguments must both be positive. +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissondistribution(const ae_int_t k, const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::poissondistribution(k, m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Complemented Poisson distribution + +Returns the sum of the terms k+1 to infinity of the Poisson +distribution: + + inf. j + -- -m m + > e -- + -- j! + j=k+1 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the formula + +y = pdtrc( k, m ) = igam( k+1, m ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissoncdistribution(const ae_int_t k, const double m) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::poissoncdistribution(k, m, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Inverse Poisson distribution + +Finds the Poisson variable x such that the integral +from 0 to x of the Poisson density is equal to the +given probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + m = igami( k+1, y ). + +ACCURACY: + +See inverse incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invpoissondistribution(const ae_int_t k, const double y) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invpoissondistribution(k, y, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Psi (digamma) function + + d - + psi(x) = -- ln | (x) + dx + +is the logarithmic derivative of the gamma function. +For integer x, + n-1 + - +psi(n) = -EUL + > 1/k. + - + k=1 + +This formula is used for 0 < n <= 10. If x is negative, it +is transformed to a positive argument by the reflection +formula psi(1-x) = psi(x) + pi cot(pi x). +For general positive x, the argument is made greater than 10 +using the recurrence psi(x+1) = psi(x) + 1/x. +Then the following asymptotic expansion is applied: + + inf. B + - 2k +psi(x) = log(x) - 1/2x - > ------- + - 2k + k=1 2k x + +where the B2k are Bernoulli numbers. + +ACCURACY: + Relative error (except absolute when |psi| < 1): +arithmetic domain # trials peak rms + IEEE 0,30 30000 1.3e-15 1.4e-16 + IEEE -30,0 40000 1.5e-15 2.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double psi(const double x) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::psi(x, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Student's t distribution + +Computes the integral from minus infinity to t of the Student +t distribution with integer k > 0 degrees of freedom: + + t + - + | | + - | 2 -(k+1)/2 + | ( (k+1)/2 ) | ( x ) + ---------------------- | ( 1 + --- ) dx + - | ( k ) + sqrt( k pi ) | ( k/2 ) | + | | + - + -inf. + +Relation to incomplete beta integral: + + 1 - stdtr(k,t) = 0.5 * incbet( k/2, 1/2, z ) +where + z = k/(k + t**2). + +For t < -2, this is the method of computation. For higher t, +a direct method is derived from integration by parts. +Since the function is symmetric about t=0, the area under the +right tail of the density is found by calling the function +with -t instead of t. + +ACCURACY: + +Tested at random 1 <= k <= 25. The "domain" refers to t. + Relative error: +arithmetic domain # trials peak rms + IEEE -100,-2 50000 5.9e-15 1.4e-15 + IEEE -2,100 500000 2.7e-15 4.9e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double studenttdistribution(const ae_int_t k, const double t) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::studenttdistribution(k, t, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Functional inverse of Student's t distribution + +Given probability p, finds the argument t such that stdtr(k,t) +is equal to p. + +ACCURACY: + +Tested at random 1 <= k <= 100. The "domain" refers to p: + Relative error: +arithmetic domain # trials peak rms + IEEE .001,.999 25000 5.7e-15 8.0e-16 + IEEE 10^-6,.001 25000 2.0e-12 2.9e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invstudenttdistribution(const ae_int_t k, const double p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::invstudenttdistribution(k, p, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Sine and cosine integrals + +Evaluates the integrals + + x + - + | cos t - 1 + Ci(x) = eul + ln x + | --------- dt, + | t + - + 0 + x + - + | sin t + Si(x) = | ----- dt + | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are approximated by rational functions. +For x > 8 auxiliary functions f(x) and g(x) are employed +such that + +Ci(x) = f(x) sin(x) - g(x) cos(x) +Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x) + + +ACCURACY: + Test interval = [0,50]. +Absolute error, except relative when > 1: +arithmetic function # trials peak rms + IEEE Si 30000 4.4e-16 7.3e-17 + IEEE Ci 30000 6.9e-16 5.1e-17 + +Cephes Math Library Release 2.1: January, 1989 +Copyright 1984, 1987, 1989 by Stephen L. Moshier +*************************************************************************/ +void sinecosineintegrals(const double x, double &si, double &ci) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sinecosineintegrals(x, &si, &ci, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Hyperbolic sine and cosine integrals + +Approximates the integrals + + x + - + | | cosh t - 1 + Chi(x) = eul + ln x + | ----------- dt, + | | t + - + 0 + + x + - + | | sinh t + Shi(x) = | ------ dt + | | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are evaluated by power series for x < 8 +and by Chebyshev expansions for x between 8 and 88. +For large x, both functions approach exp(x)/2x. +Arguments greater than 88 in magnitude return MAXNUM. + + +ACCURACY: + +Test interval 0 to 88. + Relative error: +arithmetic function # trials peak rms + IEEE Shi 30000 6.9e-16 1.6e-16 + Absolute error, except relative when |Chi| > 1: + IEEE Chi 30000 8.4e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void hyperbolicsinecosineintegrals(const double x, double &shi, double &chi) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::hyperbolicsinecosineintegrals(x, &shi, &chi, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static double gammafunc_gammastirf(double x, ae_state *_state); + + + + + + + + +static void bessel_besselmfirstcheb(double c, + double* b0, + double* b1, + double* b2, + ae_state *_state); +static void bessel_besselmnextcheb(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state); +static void bessel_besselm1firstcheb(double c, + double* b0, + double* b1, + double* b2, + ae_state *_state); +static void bessel_besselm1nextcheb(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state); +static void bessel_besselasympt0(double x, + double* pzero, + double* qzero, + ae_state *_state); +static void bessel_besselasympt1(double x, + double* pzero, + double* qzero, + ae_state *_state); + + + + +static double ibetaf_incompletebetafe(double a, + double b, + double x, + double big, + double biginv, + ae_state *_state); +static double ibetaf_incompletebetafe2(double a, + double b, + double x, + double big, + double biginv, + ae_state *_state); +static double ibetaf_incompletebetaps(double a, + double b, + double x, + double maxgam, + ae_state *_state); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +static void trigintegrals_chebiterationshichi(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state); + + + + + +/************************************************************************* +Gamma function + +Input parameters: + X - argument + +Domain: + 0 < X < 171.6 + -170 < X < 0, X is not an integer. + +Relative error: + arithmetic domain # trials peak rms + IEEE -170,-33 20000 2.3e-15 3.3e-16 + IEEE -33, 33 20000 9.4e-16 2.2e-16 + IEEE 33, 171.6 20000 2.3e-15 3.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Original copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double gammafunction(double x, ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SPECFUNCS + double p; + double pp; + double q; + double qq; + double z; + ae_int_t i; + double sgngam; + double result; + + + sgngam = 1; + q = ae_fabs(x, _state); + if( ae_fp_greater(q,33.0) ) + { + if( ae_fp_less(x,0.0) ) + { + p = ae_ifloor(q, _state); + i = ae_round(p, _state); + if( i%2==0 ) + { + sgngam = -1; + } + z = q-p; + if( ae_fp_greater(z,0.5) ) + { + p = p+1; + z = q-p; + } + z = q*ae_sin(ae_pi*z, _state); + z = ae_fabs(z, _state); + z = ae_pi/(z*gammafunc_gammastirf(q, _state)); + } + else + { + z = gammafunc_gammastirf(x, _state); + } + result = sgngam*z; + return result; + } + z = 1; + while(ae_fp_greater_eq(x,3)) + { + x = x-1; + z = z*x; + } + while(ae_fp_less(x,0)) + { + if( ae_fp_greater(x,-0.000000001) ) + { + result = z/((1+0.5772156649015329*x)*x); + return result; + } + z = z/x; + x = x+1; + } + while(ae_fp_less(x,2)) + { + if( ae_fp_less(x,0.000000001) ) + { + result = z/((1+0.5772156649015329*x)*x); + return result; + } + z = z/x; + x = x+1.0; + } + if( ae_fp_eq(x,2) ) + { + result = z; + return result; + } + x = x-2.0; + pp = 1.60119522476751861407E-4; + pp = 1.19135147006586384913E-3+x*pp; + pp = 1.04213797561761569935E-2+x*pp; + pp = 4.76367800457137231464E-2+x*pp; + pp = 2.07448227648435975150E-1+x*pp; + pp = 4.94214826801497100753E-1+x*pp; + pp = 9.99999999999999996796E-1+x*pp; + qq = -2.31581873324120129819E-5; + qq = 5.39605580493303397842E-4+x*qq; + qq = -4.45641913851797240494E-3+x*qq; + qq = 1.18139785222060435552E-2+x*qq; + qq = 3.58236398605498653373E-2+x*qq; + qq = -2.34591795718243348568E-1+x*qq; + qq = 7.14304917030273074085E-2+x*qq; + qq = 1.00000000000000000320+x*qq; + result = z*pp/qq; + return result; +#else + return _ialglib_i_gammafunction(x); +#endif +} + + +/************************************************************************* +Natural logarithm of gamma function + +Input parameters: + X - argument + +Result: + logarithm of the absolute value of the Gamma(X). + +Output parameters: + SgnGam - sign(Gamma(X)) + +Domain: + 0 < X < 2.55e305 + -2.55e305 < X < 0, X is not an integer. + +ACCURACY: +arithmetic domain # trials peak rms + IEEE 0, 3 28000 5.4e-16 1.1e-16 + IEEE 2.718, 2.556e305 40000 3.5e-16 8.3e-17 +The error criterion was relative when the function magnitude +was greater than one but absolute when it was less than one. + +The following test used the relative error criterion, though +at certain points the relative error could be much higher than +indicated. + IEEE -200, -4 10000 4.8e-16 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double lngamma(double x, double* sgngam, ae_state *_state) +{ +#ifndef ALGLIB_INTERCEPTS_SPECFUNCS + double a; + double b; + double c; + double p; + double q; + double u; + double w; + double z; + ae_int_t i; + double logpi; + double ls2pi; + double tmp; + double result; + + *sgngam = 0; + + *sgngam = 1; + logpi = 1.14472988584940017414; + ls2pi = 0.91893853320467274178; + if( ae_fp_less(x,-34.0) ) + { + q = -x; + w = lngamma(q, &tmp, _state); + p = ae_ifloor(q, _state); + i = ae_round(p, _state); + if( i%2==0 ) + { + *sgngam = -1; + } + else + { + *sgngam = 1; + } + z = q-p; + if( ae_fp_greater(z,0.5) ) + { + p = p+1; + z = p-q; + } + z = q*ae_sin(ae_pi*z, _state); + result = logpi-ae_log(z, _state)-w; + return result; + } + if( ae_fp_less(x,13) ) + { + z = 1; + p = 0; + u = x; + while(ae_fp_greater_eq(u,3)) + { + p = p-1; + u = x+p; + z = z*u; + } + while(ae_fp_less(u,2)) + { + z = z/u; + p = p+1; + u = x+p; + } + if( ae_fp_less(z,0) ) + { + *sgngam = -1; + z = -z; + } + else + { + *sgngam = 1; + } + if( ae_fp_eq(u,2) ) + { + result = ae_log(z, _state); + return result; + } + p = p-2; + x = x+p; + b = -1378.25152569120859100; + b = -38801.6315134637840924+x*b; + b = -331612.992738871184744+x*b; + b = -1162370.97492762307383+x*b; + b = -1721737.00820839662146+x*b; + b = -853555.664245765465627+x*b; + c = 1; + c = -351.815701436523470549+x*c; + c = -17064.2106651881159223+x*c; + c = -220528.590553854454839+x*c; + c = -1139334.44367982507207+x*c; + c = -2532523.07177582951285+x*c; + c = -2018891.41433532773231+x*c; + p = x*b/c; + result = ae_log(z, _state)+p; + return result; + } + q = (x-0.5)*ae_log(x, _state)-x+ls2pi; + if( ae_fp_greater(x,100000000) ) + { + result = q; + return result; + } + p = 1/(x*x); + if( ae_fp_greater_eq(x,1000.0) ) + { + q = q+((7.9365079365079365079365*0.0001*p-2.7777777777777777777778*0.001)*p+0.0833333333333333333333)/x; + } + else + { + a = 8.11614167470508450300*0.0001; + a = -5.95061904284301438324*0.0001+p*a; + a = 7.93650340457716943945*0.0001+p*a; + a = -2.77777777730099687205*0.001+p*a; + a = 8.33333333333331927722*0.01+p*a; + q = q+a/x; + } + result = q; + return result; +#else + return _ialglib_i_lngamma(x, sgngam); +#endif +} + + +static double gammafunc_gammastirf(double x, ae_state *_state) +{ + double y; + double w; + double v; + double stir; + double result; + + + w = 1/x; + stir = 7.87311395793093628397E-4; + stir = -2.29549961613378126380E-4+w*stir; + stir = -2.68132617805781232825E-3+w*stir; + stir = 3.47222221605458667310E-3+w*stir; + stir = 8.33333333333482257126E-2+w*stir; + w = 1+w*stir; + y = ae_exp(x, _state); + if( ae_fp_greater(x,143.01608) ) + { + v = ae_pow(x, 0.5*x-0.25, _state); + y = v*(v/y); + } + else + { + y = ae_pow(x, x-0.5, _state)/y; + } + result = 2.50662827463100050242*y*w; + return result; +} + + + + +/************************************************************************* +Error function + +The integral is + + x + - + 2 | | 2 + erf(x) = -------- | exp( - t ) dt. + sqrt(pi) | | + - + 0 + +For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise +erf(x) = 1 - erfc(x). + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 3.7e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunction(double x, ae_state *_state) +{ + double xsq; + double s; + double p; + double q; + double result; + + + s = ae_sign(x, _state); + x = ae_fabs(x, _state); + if( ae_fp_less(x,0.5) ) + { + xsq = x*x; + p = 0.007547728033418631287834; + p = -0.288805137207594084924010+xsq*p; + p = 14.3383842191748205576712+xsq*p; + p = 38.0140318123903008244444+xsq*p; + p = 3017.82788536507577809226+xsq*p; + p = 7404.07142710151470082064+xsq*p; + p = 80437.3630960840172832162+xsq*p; + q = 0.0; + q = 1.00000000000000000000000+xsq*q; + q = 38.0190713951939403753468+xsq*q; + q = 658.070155459240506326937+xsq*q; + q = 6379.60017324428279487120+xsq*q; + q = 34216.5257924628539769006+xsq*q; + q = 80437.3630960840172826266+xsq*q; + result = s*1.1283791670955125738961589031*x*p/q; + return result; + } + if( ae_fp_greater_eq(x,10) ) + { + result = s; + return result; + } + result = s*(1-errorfunctionc(x, _state)); + return result; +} + + +/************************************************************************* +Complementary error function + + 1 - erf(x) = + + inf. + - + 2 | | 2 + erfc(x) = -------- | exp( - t ) dt + sqrt(pi) | | + - + x + + +For small x, erfc(x) = 1 - erf(x); otherwise rational +approximations are computed. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,26.6417 30000 5.7e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunctionc(double x, ae_state *_state) +{ + double p; + double q; + double result; + + + if( ae_fp_less(x,0) ) + { + result = 2-errorfunctionc(-x, _state); + return result; + } + if( ae_fp_less(x,0.5) ) + { + result = 1.0-errorfunction(x, _state); + return result; + } + if( ae_fp_greater_eq(x,10) ) + { + result = 0; + return result; + } + p = 0.0; + p = 0.5641877825507397413087057563+x*p; + p = 9.675807882987265400604202961+x*p; + p = 77.08161730368428609781633646+x*p; + p = 368.5196154710010637133875746+x*p; + p = 1143.262070703886173606073338+x*p; + p = 2320.439590251635247384768711+x*p; + p = 2898.0293292167655611275846+x*p; + p = 1826.3348842295112592168999+x*p; + q = 1.0; + q = 17.14980943627607849376131193+x*q; + q = 137.1255960500622202878443578+x*q; + q = 661.7361207107653469211984771+x*q; + q = 2094.384367789539593790281779+x*q; + q = 4429.612803883682726711528526+x*q; + q = 6089.5424232724435504633068+x*q; + q = 4958.82756472114071495438422+x*q; + q = 1826.3348842295112595576438+x*q; + result = ae_exp(-ae_sqr(x, _state), _state)*p/q; + return result; +} + + +/************************************************************************* +Normal distribution function + +Returns the area under the Gaussian probability density +function, integrated from minus infinity to x: + + x + - + 1 | | 2 + ndtr(x) = --------- | exp( - t /2 ) dt + sqrt(2pi) | | + - + -inf. + + = ( 1 + erf(z) ) / 2 + = erfc(z) / 2 + +where z = x/sqrt(2). Computation is via the functions +erf and erfc. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE -13,0 30000 3.4e-14 6.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double normaldistribution(double x, ae_state *_state) +{ + double result; + + + result = 0.5*(errorfunction(x/1.41421356237309504880, _state)+1); + return result; +} + + +/************************************************************************* +Inverse of the error function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double inverf(double e, ae_state *_state) +{ + double result; + + + result = invnormaldistribution(0.5*(e+1), _state)/ae_sqrt(2, _state); + return result; +} + + +/************************************************************************* +Inverse of Normal distribution function + +Returns the argument, x, for which the area under the +Gaussian probability density function (integrated from +minus infinity to x) is equal to y. + + +For small arguments 0 < y < exp(-2), the program computes +z = sqrt( -2.0 * log(y) ); then the approximation is +x = z - log(z)/z - (1/z) P(1/z) / Q(1/z). +There are two rational functions P/Q, one for 0 < y < exp(-32) +and the other for y up to exp(-2). For larger arguments, +w = y - 0.5, and x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0.125, 1 20000 7.2e-16 1.3e-16 + IEEE 3e-308, 0.135 50000 4.6e-16 9.8e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double invnormaldistribution(double y0, ae_state *_state) +{ + double expm2; + double s2pi; + double x; + double y; + double z; + double y2; + double x0; + double x1; + ae_int_t code; + double p0; + double q0; + double p1; + double q1; + double p2; + double q2; + double result; + + + expm2 = 0.13533528323661269189; + s2pi = 2.50662827463100050242; + if( ae_fp_less_eq(y0,0) ) + { + result = -ae_maxrealnumber; + return result; + } + if( ae_fp_greater_eq(y0,1) ) + { + result = ae_maxrealnumber; + return result; + } + code = 1; + y = y0; + if( ae_fp_greater(y,1.0-expm2) ) + { + y = 1.0-y; + code = 0; + } + if( ae_fp_greater(y,expm2) ) + { + y = y-0.5; + y2 = y*y; + p0 = -59.9633501014107895267; + p0 = 98.0010754185999661536+y2*p0; + p0 = -56.6762857469070293439+y2*p0; + p0 = 13.9312609387279679503+y2*p0; + p0 = -1.23916583867381258016+y2*p0; + q0 = 1; + q0 = 1.95448858338141759834+y2*q0; + q0 = 4.67627912898881538453+y2*q0; + q0 = 86.3602421390890590575+y2*q0; + q0 = -225.462687854119370527+y2*q0; + q0 = 200.260212380060660359+y2*q0; + q0 = -82.0372256168333339912+y2*q0; + q0 = 15.9056225126211695515+y2*q0; + q0 = -1.18331621121330003142+y2*q0; + x = y+y*y2*p0/q0; + x = x*s2pi; + result = x; + return result; + } + x = ae_sqrt(-2.0*ae_log(y, _state), _state); + x0 = x-ae_log(x, _state)/x; + z = 1.0/x; + if( ae_fp_less(x,8.0) ) + { + p1 = 4.05544892305962419923; + p1 = 31.5251094599893866154+z*p1; + p1 = 57.1628192246421288162+z*p1; + p1 = 44.0805073893200834700+z*p1; + p1 = 14.6849561928858024014+z*p1; + p1 = 2.18663306850790267539+z*p1; + p1 = -1.40256079171354495875*0.1+z*p1; + p1 = -3.50424626827848203418*0.01+z*p1; + p1 = -8.57456785154685413611*0.0001+z*p1; + q1 = 1; + q1 = 15.7799883256466749731+z*q1; + q1 = 45.3907635128879210584+z*q1; + q1 = 41.3172038254672030440+z*q1; + q1 = 15.0425385692907503408+z*q1; + q1 = 2.50464946208309415979+z*q1; + q1 = -1.42182922854787788574*0.1+z*q1; + q1 = -3.80806407691578277194*0.01+z*q1; + q1 = -9.33259480895457427372*0.0001+z*q1; + x1 = z*p1/q1; + } + else + { + p2 = 3.23774891776946035970; + p2 = 6.91522889068984211695+z*p2; + p2 = 3.93881025292474443415+z*p2; + p2 = 1.33303460815807542389+z*p2; + p2 = 2.01485389549179081538*0.1+z*p2; + p2 = 1.23716634817820021358*0.01+z*p2; + p2 = 3.01581553508235416007*0.0001+z*p2; + p2 = 2.65806974686737550832*0.000001+z*p2; + p2 = 6.23974539184983293730*0.000000001+z*p2; + q2 = 1; + q2 = 6.02427039364742014255+z*q2; + q2 = 3.67983563856160859403+z*q2; + q2 = 1.37702099489081330271+z*q2; + q2 = 2.16236993594496635890*0.1+z*q2; + q2 = 1.34204006088543189037*0.01+z*q2; + q2 = 3.28014464682127739104*0.0001+z*q2; + q2 = 2.89247864745380683936*0.000001+z*q2; + q2 = 6.79019408009981274425*0.000000001+z*q2; + x1 = z*p2/q2; + } + x = x0-x1; + if( code!=0 ) + { + x = -x; + } + result = x; + return result; +} + + + + +/************************************************************************* +Incomplete gamma integral + +The function is defined by + + x + - + 1 | | -t a-1 + igam(a,x) = ----- | e t dt. + - | | + | (a) - + 0 + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 200000 3.6e-14 2.9e-15 + IEEE 0,100 300000 9.9e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegamma(double a, double x, ae_state *_state) +{ + double igammaepsilon; + double ans; + double ax; + double c; + double r; + double tmp; + double result; + + + igammaepsilon = 0.000000000000001; + if( ae_fp_less_eq(x,0)||ae_fp_less_eq(a,0) ) + { + result = 0; + return result; + } + if( ae_fp_greater(x,1)&&ae_fp_greater(x,a) ) + { + result = 1-incompletegammac(a, x, _state); + return result; + } + ax = a*ae_log(x, _state)-x-lngamma(a, &tmp, _state); + if( ae_fp_less(ax,-709.78271289338399) ) + { + result = 0; + return result; + } + ax = ae_exp(ax, _state); + r = a; + c = 1; + ans = 1; + do + { + r = r+1; + c = c*x/r; + ans = ans+c; + } + while(ae_fp_greater(c/ans,igammaepsilon)); + result = ans*ax/a; + return result; +} + + +/************************************************************************* +Complemented incomplete gamma integral + +The function is defined by + + + igamc(a,x) = 1 - igam(a,x) + + inf. + - + 1 | | -t a-1 + = ----- | e t dt. + - | | + | (a) - + x + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + +Tested at random a, x. + a x Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,100 200000 1.9e-14 1.7e-15 + IEEE 0.01,0.5 0,100 200000 1.4e-13 1.6e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegammac(double a, double x, ae_state *_state) +{ + double igammaepsilon; + double igammabignumber; + double igammabignumberinv; + double ans; + double ax; + double c; + double yc; + double r; + double t; + double y; + double z; + double pk; + double pkm1; + double pkm2; + double qk; + double qkm1; + double qkm2; + double tmp; + double result; + + + igammaepsilon = 0.000000000000001; + igammabignumber = 4503599627370496.0; + igammabignumberinv = 2.22044604925031308085*0.0000000000000001; + if( ae_fp_less_eq(x,0)||ae_fp_less_eq(a,0) ) + { + result = 1; + return result; + } + if( ae_fp_less(x,1)||ae_fp_less(x,a) ) + { + result = 1-incompletegamma(a, x, _state); + return result; + } + ax = a*ae_log(x, _state)-x-lngamma(a, &tmp, _state); + if( ae_fp_less(ax,-709.78271289338399) ) + { + result = 0; + return result; + } + ax = ae_exp(ax, _state); + y = 1-a; + z = x+y+1; + c = 0; + pkm2 = 1; + qkm2 = x; + pkm1 = x+1; + qkm1 = z*x; + ans = pkm1/qkm1; + do + { + c = c+1; + y = y+1; + z = z+2; + yc = y*c; + pk = pkm1*z-pkm2*yc; + qk = qkm1*z-qkm2*yc; + if( ae_fp_neq(qk,0) ) + { + r = pk/qk; + t = ae_fabs((ans-r)/r, _state); + ans = r; + } + else + { + t = 1; + } + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + if( ae_fp_greater(ae_fabs(pk, _state),igammabignumber) ) + { + pkm2 = pkm2*igammabignumberinv; + pkm1 = pkm1*igammabignumberinv; + qkm2 = qkm2*igammabignumberinv; + qkm1 = qkm1*igammabignumberinv; + } + } + while(ae_fp_greater(t,igammaepsilon)); + result = ans*ax; + return result; +} + + +/************************************************************************* +Inverse of complemented imcomplete gamma integral + +Given p, the function finds x such that + + igamc( a, x ) = p. + +Starting with the approximate value + + 3 + x = a t + + where + + t = 1 - d - ndtri(p) sqrt(d) + +and + + d = 1/9a, + +the routine performs up to 10 Newton iterations to find the +root of igamc(a,x) - p = 0. + +ACCURACY: + +Tested at random a, p in the intervals indicated. + + a p Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,0.5 100000 1.0e-14 1.7e-15 + IEEE 0.01,0.5 0,0.5 100000 9.0e-14 3.4e-15 + IEEE 0.5,10000 0,0.5 20000 2.3e-13 3.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletegammac(double a, double y0, ae_state *_state) +{ + double igammaepsilon; + double iinvgammabignumber; + double x0; + double x1; + double x; + double yl; + double yh; + double y; + double d; + double lgm; + double dithresh; + ae_int_t i; + ae_int_t dir; + double tmp; + double result; + + + igammaepsilon = 0.000000000000001; + iinvgammabignumber = 4503599627370496.0; + x0 = iinvgammabignumber; + yl = 0; + x1 = 0; + yh = 1; + dithresh = 5*igammaepsilon; + d = 1/(9*a); + y = 1-d-invnormaldistribution(y0, _state)*ae_sqrt(d, _state); + x = a*y*y*y; + lgm = lngamma(a, &tmp, _state); + i = 0; + while(i<10) + { + if( ae_fp_greater(x,x0)||ae_fp_less(x,x1) ) + { + d = 0.0625; + break; + } + y = incompletegammac(a, x, _state); + if( ae_fp_less(y,yl)||ae_fp_greater(y,yh) ) + { + d = 0.0625; + break; + } + if( ae_fp_less(y,y0) ) + { + x0 = x; + yl = y; + } + else + { + x1 = x; + yh = y; + } + d = (a-1)*ae_log(x, _state)-x-lgm; + if( ae_fp_less(d,-709.78271289338399) ) + { + d = 0.0625; + break; + } + d = -ae_exp(d, _state); + d = (y-y0)/d; + if( ae_fp_less(ae_fabs(d/x, _state),igammaepsilon) ) + { + result = x; + return result; + } + x = x-d; + i = i+1; + } + if( ae_fp_eq(x0,iinvgammabignumber) ) + { + if( ae_fp_less_eq(x,0) ) + { + x = 1; + } + while(ae_fp_eq(x0,iinvgammabignumber)) + { + x = (1+d)*x; + y = incompletegammac(a, x, _state); + if( ae_fp_less(y,y0) ) + { + x0 = x; + yl = y; + break; + } + d = d+d; + } + } + d = 0.5; + dir = 0; + i = 0; + while(i<400) + { + x = x1+d*(x0-x1); + y = incompletegammac(a, x, _state); + lgm = (x0-x1)/(x1+x0); + if( ae_fp_less(ae_fabs(lgm, _state),dithresh) ) + { + break; + } + lgm = (y-y0)/y0; + if( ae_fp_less(ae_fabs(lgm, _state),dithresh) ) + { + break; + } + if( ae_fp_less_eq(x,0.0) ) + { + break; + } + if( ae_fp_greater_eq(y,y0) ) + { + x1 = x; + yh = y; + if( dir<0 ) + { + dir = 0; + d = 0.5; + } + else + { + if( dir>1 ) + { + d = 0.5*d+0.5; + } + else + { + d = (y0-yl)/(yh-yl); + } + } + dir = dir+1; + } + else + { + x0 = x; + yl = y; + if( dir>0 ) + { + dir = 0; + d = 0.5; + } + else + { + if( dir<-1 ) + { + d = 0.5*d; + } + else + { + d = (y0-yl)/(yh-yl); + } + } + dir = dir-1; + } + i = i+1; + } + result = x; + return result; +} + + + + +/************************************************************************* +Airy function + +Solution of the differential equation + +y"(x) = xy. + +The function returns the two independent solutions Ai, Bi +and their first derivatives Ai'(x), Bi'(x). + +Evaluation is by power series summation for small x, +by rational minimax approximations for large x. + + + +ACCURACY: +Error criterion is absolute when function <= 1, relative +when function > 1, except * denotes relative error criterion. +For large negative x, the absolute error increases as x^1.5. +For large positive x, the relative error increases as x^1.5. + +Arithmetic domain function # trials peak rms +IEEE -10, 0 Ai 10000 1.6e-15 2.7e-16 +IEEE 0, 10 Ai 10000 2.3e-14* 1.8e-15* +IEEE -10, 0 Ai' 10000 4.6e-15 7.6e-16 +IEEE 0, 10 Ai' 10000 1.8e-14* 1.5e-15* +IEEE -10, 10 Bi 30000 4.2e-15 5.3e-16 +IEEE -10, 10 Bi' 30000 4.9e-15 7.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void airy(double x, + double* ai, + double* aip, + double* bi, + double* bip, + ae_state *_state) +{ + double z; + double zz; + double t; + double f; + double g; + double uf; + double ug; + double k; + double zeta; + double theta; + ae_int_t domflg; + double c1; + double c2; + double sqrt3; + double sqpii; + double afn; + double afd; + double agn; + double agd; + double apfn; + double apfd; + double apgn; + double apgd; + double an; + double ad; + double apn; + double apd; + double bn16; + double bd16; + double bppn; + double bppd; + + *ai = 0; + *aip = 0; + *bi = 0; + *bip = 0; + + sqpii = 5.64189583547756286948E-1; + c1 = 0.35502805388781723926; + c2 = 0.258819403792806798405; + sqrt3 = 1.732050807568877293527; + domflg = 0; + if( ae_fp_greater(x,25.77) ) + { + *ai = 0; + *aip = 0; + *bi = ae_maxrealnumber; + *bip = ae_maxrealnumber; + return; + } + if( ae_fp_less(x,-2.09) ) + { + domflg = 15; + t = ae_sqrt(-x, _state); + zeta = -2.0*x*t/3.0; + t = ae_sqrt(t, _state); + k = sqpii/t; + z = 1.0/zeta; + zz = z*z; + afn = -1.31696323418331795333E-1; + afn = afn*zz-6.26456544431912369773E-1; + afn = afn*zz-6.93158036036933542233E-1; + afn = afn*zz-2.79779981545119124951E-1; + afn = afn*zz-4.91900132609500318020E-2; + afn = afn*zz-4.06265923594885404393E-3; + afn = afn*zz-1.59276496239262096340E-4; + afn = afn*zz-2.77649108155232920844E-6; + afn = afn*zz-1.67787698489114633780E-8; + afd = 1.00000000000000000000E0; + afd = afd*zz+1.33560420706553243746E1; + afd = afd*zz+3.26825032795224613948E1; + afd = afd*zz+2.67367040941499554804E1; + afd = afd*zz+9.18707402907259625840E0; + afd = afd*zz+1.47529146771666414581E0; + afd = afd*zz+1.15687173795188044134E-1; + afd = afd*zz+4.40291641615211203805E-3; + afd = afd*zz+7.54720348287414296618E-5; + afd = afd*zz+4.51850092970580378464E-7; + uf = 1.0+zz*afn/afd; + agn = 1.97339932091685679179E-2; + agn = agn*zz+3.91103029615688277255E-1; + agn = agn*zz+1.06579897599595591108E0; + agn = agn*zz+9.39169229816650230044E-1; + agn = agn*zz+3.51465656105547619242E-1; + agn = agn*zz+6.33888919628925490927E-2; + agn = agn*zz+5.85804113048388458567E-3; + agn = agn*zz+2.82851600836737019778E-4; + agn = agn*zz+6.98793669997260967291E-6; + agn = agn*zz+8.11789239554389293311E-8; + agn = agn*zz+3.41551784765923618484E-10; + agd = 1.00000000000000000000E0; + agd = agd*zz+9.30892908077441974853E0; + agd = agd*zz+1.98352928718312140417E1; + agd = agd*zz+1.55646628932864612953E1; + agd = agd*zz+5.47686069422975497931E0; + agd = agd*zz+9.54293611618961883998E-1; + agd = agd*zz+8.64580826352392193095E-2; + agd = agd*zz+4.12656523824222607191E-3; + agd = agd*zz+1.01259085116509135510E-4; + agd = agd*zz+1.17166733214413521882E-6; + agd = agd*zz+4.91834570062930015649E-9; + ug = z*agn/agd; + theta = zeta+0.25*ae_pi; + f = ae_sin(theta, _state); + g = ae_cos(theta, _state); + *ai = k*(f*uf-g*ug); + *bi = k*(g*uf+f*ug); + apfn = 1.85365624022535566142E-1; + apfn = apfn*zz+8.86712188052584095637E-1; + apfn = apfn*zz+9.87391981747398547272E-1; + apfn = apfn*zz+4.01241082318003734092E-1; + apfn = apfn*zz+7.10304926289631174579E-2; + apfn = apfn*zz+5.90618657995661810071E-3; + apfn = apfn*zz+2.33051409401776799569E-4; + apfn = apfn*zz+4.08718778289035454598E-6; + apfn = apfn*zz+2.48379932900442457853E-8; + apfd = 1.00000000000000000000E0; + apfd = apfd*zz+1.47345854687502542552E1; + apfd = apfd*zz+3.75423933435489594466E1; + apfd = apfd*zz+3.14657751203046424330E1; + apfd = apfd*zz+1.09969125207298778536E1; + apfd = apfd*zz+1.78885054766999417817E0; + apfd = apfd*zz+1.41733275753662636873E-1; + apfd = apfd*zz+5.44066067017226003627E-3; + apfd = apfd*zz+9.39421290654511171663E-5; + apfd = apfd*zz+5.65978713036027009243E-7; + uf = 1.0+zz*apfn/apfd; + apgn = -3.55615429033082288335E-2; + apgn = apgn*zz-6.37311518129435504426E-1; + apgn = apgn*zz-1.70856738884312371053E0; + apgn = apgn*zz-1.50221872117316635393E0; + apgn = apgn*zz-5.63606665822102676611E-1; + apgn = apgn*zz-1.02101031120216891789E-1; + apgn = apgn*zz-9.48396695961445269093E-3; + apgn = apgn*zz-4.60325307486780994357E-4; + apgn = apgn*zz-1.14300836484517375919E-5; + apgn = apgn*zz-1.33415518685547420648E-7; + apgn = apgn*zz-5.63803833958893494476E-10; + apgd = 1.00000000000000000000E0; + apgd = apgd*zz+9.85865801696130355144E0; + apgd = apgd*zz+2.16401867356585941885E1; + apgd = apgd*zz+1.73130776389749389525E1; + apgd = apgd*zz+6.17872175280828766327E0; + apgd = apgd*zz+1.08848694396321495475E0; + apgd = apgd*zz+9.95005543440888479402E-2; + apgd = apgd*zz+4.78468199683886610842E-3; + apgd = apgd*zz+1.18159633322838625562E-4; + apgd = apgd*zz+1.37480673554219441465E-6; + apgd = apgd*zz+5.79912514929147598821E-9; + ug = z*apgn/apgd; + k = sqpii*t; + *aip = -k*(g*uf+f*ug); + *bip = k*(f*uf-g*ug); + return; + } + if( ae_fp_greater_eq(x,2.09) ) + { + domflg = 5; + t = ae_sqrt(x, _state); + zeta = 2.0*x*t/3.0; + g = ae_exp(zeta, _state); + t = ae_sqrt(t, _state); + k = 2.0*t*g; + z = 1.0/zeta; + an = 3.46538101525629032477E-1; + an = an*z+1.20075952739645805542E1; + an = an*z+7.62796053615234516538E1; + an = an*z+1.68089224934630576269E2; + an = an*z+1.59756391350164413639E2; + an = an*z+7.05360906840444183113E1; + an = an*z+1.40264691163389668864E1; + an = an*z+9.99999999999999995305E-1; + ad = 5.67594532638770212846E-1; + ad = ad*z+1.47562562584847203173E1; + ad = ad*z+8.45138970141474626562E1; + ad = ad*z+1.77318088145400459522E2; + ad = ad*z+1.64234692871529701831E2; + ad = ad*z+7.14778400825575695274E1; + ad = ad*z+1.40959135607834029598E1; + ad = ad*z+1.00000000000000000470E0; + f = an/ad; + *ai = sqpii*f/k; + k = -0.5*sqpii*t/g; + apn = 6.13759184814035759225E-1; + apn = apn*z+1.47454670787755323881E1; + apn = apn*z+8.20584123476060982430E1; + apn = apn*z+1.71184781360976385540E2; + apn = apn*z+1.59317847137141783523E2; + apn = apn*z+6.99778599330103016170E1; + apn = apn*z+1.39470856980481566958E1; + apn = apn*z+1.00000000000000000550E0; + apd = 3.34203677749736953049E-1; + apd = apd*z+1.11810297306158156705E1; + apd = apd*z+7.11727352147859965283E1; + apd = apd*z+1.58778084372838313640E2; + apd = apd*z+1.53206427475809220834E2; + apd = apd*z+6.86752304592780337944E1; + apd = apd*z+1.38498634758259442477E1; + apd = apd*z+9.99999999999999994502E-1; + f = apn/apd; + *aip = f*k; + if( ae_fp_greater(x,8.3203353) ) + { + bn16 = -2.53240795869364152689E-1; + bn16 = bn16*z+5.75285167332467384228E-1; + bn16 = bn16*z-3.29907036873225371650E-1; + bn16 = bn16*z+6.44404068948199951727E-2; + bn16 = bn16*z-3.82519546641336734394E-3; + bd16 = 1.00000000000000000000E0; + bd16 = bd16*z-7.15685095054035237902E0; + bd16 = bd16*z+1.06039580715664694291E1; + bd16 = bd16*z-5.23246636471251500874E0; + bd16 = bd16*z+9.57395864378383833152E-1; + bd16 = bd16*z-5.50828147163549611107E-2; + f = z*bn16/bd16; + k = sqpii*g; + *bi = k*(1.0+f)/t; + bppn = 4.65461162774651610328E-1; + bppn = bppn*z-1.08992173800493920734E0; + bppn = bppn*z+6.38800117371827987759E-1; + bppn = bppn*z-1.26844349553102907034E-1; + bppn = bppn*z+7.62487844342109852105E-3; + bppd = 1.00000000000000000000E0; + bppd = bppd*z-8.70622787633159124240E0; + bppd = bppd*z+1.38993162704553213172E1; + bppd = bppd*z-7.14116144616431159572E0; + bppd = bppd*z+1.34008595960680518666E0; + bppd = bppd*z-7.84273211323341930448E-2; + f = z*bppn/bppd; + *bip = k*t*(1.0+f); + return; + } + } + f = 1.0; + g = x; + t = 1.0; + uf = 1.0; + ug = x; + k = 1.0; + z = x*x*x; + while(ae_fp_greater(t,ae_machineepsilon)) + { + uf = uf*z; + k = k+1.0; + uf = uf/k; + ug = ug*z; + k = k+1.0; + ug = ug/k; + uf = uf/k; + f = f+uf; + k = k+1.0; + ug = ug/k; + g = g+ug; + t = ae_fabs(uf/f, _state); + } + uf = c1*f; + ug = c2*g; + if( domflg%2==0 ) + { + *ai = uf-ug; + } + if( domflg/2%2==0 ) + { + *bi = sqrt3*(uf+ug); + } + k = 4.0; + uf = x*x/2.0; + ug = z/3.0; + f = uf; + g = 1.0+ug; + uf = uf/3.0; + t = 1.0; + while(ae_fp_greater(t,ae_machineepsilon)) + { + uf = uf*z; + ug = ug/k; + k = k+1.0; + ug = ug*z; + uf = uf/k; + f = f+uf; + k = k+1.0; + ug = ug/k; + uf = uf/k; + g = g+ug; + k = k+1.0; + t = ae_fabs(ug/g, _state); + } + uf = c1*f; + ug = c2*g; + if( domflg/4%2==0 ) + { + *aip = uf-ug; + } + if( domflg/8%2==0 ) + { + *bip = sqrt3*(uf+ug); + } +} + + + + +/************************************************************************* +Bessel function of order zero + +Returns Bessel function of order zero of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval the following rational +approximation is used: + + + 2 2 +(w - r ) (w - r ) P (w) / Q (w) + 1 2 3 8 + + 2 +where w = x and the two r's are zeros of the function. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 60000 4.2e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj0(double x, ae_state *_state) +{ + double xsq; + double nn; + double pzero; + double qzero; + double p1; + double q1; + double result; + + + if( ae_fp_less(x,0) ) + { + x = -x; + } + if( ae_fp_greater(x,8.0) ) + { + bessel_besselasympt0(x, &pzero, &qzero, _state); + nn = x-ae_pi/4; + result = ae_sqrt(2/ae_pi/x, _state)*(pzero*ae_cos(nn, _state)-qzero*ae_sin(nn, _state)); + return result; + } + xsq = ae_sqr(x, _state); + p1 = 26857.86856980014981415848441; + p1 = -40504123.71833132706360663322+xsq*p1; + p1 = 25071582855.36881945555156435+xsq*p1; + p1 = -8085222034853.793871199468171+xsq*p1; + p1 = 1434354939140344.111664316553+xsq*p1; + p1 = -136762035308817138.6865416609+xsq*p1; + p1 = 6382059341072356562.289432465+xsq*p1; + p1 = -117915762910761053603.8440800+xsq*p1; + p1 = 493378725179413356181.6813446+xsq*p1; + q1 = 1.0; + q1 = 1363.063652328970604442810507+xsq*q1; + q1 = 1114636.098462985378182402543+xsq*q1; + q1 = 669998767.2982239671814028660+xsq*q1; + q1 = 312304311494.1213172572469442+xsq*q1; + q1 = 112775673967979.8507056031594+xsq*q1; + q1 = 30246356167094626.98627330784+xsq*q1; + q1 = 5428918384092285160.200195092+xsq*q1; + q1 = 493378725179413356211.3278438+xsq*q1; + result = p1/q1; + return result; +} + + +/************************************************************************* +Bessel function of order one + +Returns Bessel function of order one of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 24 term Chebyshev +expansion is used. In the second, the asymptotic +trigonometric representation is employed using two +rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 2.6e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj1(double x, ae_state *_state) +{ + double s; + double xsq; + double nn; + double pzero; + double qzero; + double p1; + double q1; + double result; + + + s = ae_sign(x, _state); + if( ae_fp_less(x,0) ) + { + x = -x; + } + if( ae_fp_greater(x,8.0) ) + { + bessel_besselasympt1(x, &pzero, &qzero, _state); + nn = x-3*ae_pi/4; + result = ae_sqrt(2/ae_pi/x, _state)*(pzero*ae_cos(nn, _state)-qzero*ae_sin(nn, _state)); + if( ae_fp_less(s,0) ) + { + result = -result; + } + return result; + } + xsq = ae_sqr(x, _state); + p1 = 2701.122710892323414856790990; + p1 = -4695753.530642995859767162166+xsq*p1; + p1 = 3413234182.301700539091292655+xsq*p1; + p1 = -1322983480332.126453125473247+xsq*p1; + p1 = 290879526383477.5409737601689+xsq*p1; + p1 = -35888175699101060.50743641413+xsq*p1; + p1 = 2316433580634002297.931815435+xsq*p1; + p1 = -66721065689249162980.20941484+xsq*p1; + p1 = 581199354001606143928.050809+xsq*p1; + q1 = 1.0; + q1 = 1606.931573481487801970916749+xsq*q1; + q1 = 1501793.594998585505921097578+xsq*q1; + q1 = 1013863514.358673989967045588+xsq*q1; + q1 = 524371026216.7649715406728642+xsq*q1; + q1 = 208166122130760.7351240184229+xsq*q1; + q1 = 60920613989175217.46105196863+xsq*q1; + q1 = 11857707121903209998.37113348+xsq*q1; + q1 = 1162398708003212287858.529400+xsq*q1; + result = s*x*p1/q1; + return result; +} + + +/************************************************************************* +Bessel function of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The ratio of jn(x) to j0(x) is computed by backward +recurrence. First the ratio jn/jn-1 is found by a +continued fraction expansion. Then the recurrence +relating successive orders is applied until j0 or j1 is +reached. + +If n = 0 or 1 the routine for j0 or j1 is called +directly. + +ACCURACY: + + Absolute error: +arithmetic range # trials peak rms + IEEE 0, 30 5000 4.4e-16 7.9e-17 + + +Not suitable for large n or x. Use jv() (fractional order) instead. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseljn(ae_int_t n, double x, ae_state *_state) +{ + double pkm2; + double pkm1; + double pk; + double xk; + double r; + double ans; + ae_int_t k; + ae_int_t sg; + double result; + + + if( n<0 ) + { + n = -n; + if( n%2==0 ) + { + sg = 1; + } + else + { + sg = -1; + } + } + else + { + sg = 1; + } + if( ae_fp_less(x,0) ) + { + if( n%2!=0 ) + { + sg = -sg; + } + x = -x; + } + if( n==0 ) + { + result = sg*besselj0(x, _state); + return result; + } + if( n==1 ) + { + result = sg*besselj1(x, _state); + return result; + } + if( n==2 ) + { + if( ae_fp_eq(x,0) ) + { + result = 0; + } + else + { + result = sg*(2.0*besselj1(x, _state)/x-besselj0(x, _state)); + } + return result; + } + if( ae_fp_less(x,ae_machineepsilon) ) + { + result = 0; + return result; + } + k = 53; + pk = 2*(n+k); + ans = pk; + xk = x*x; + do + { + pk = pk-2.0; + ans = pk-xk/ans; + k = k-1; + } + while(k!=0); + ans = x/ans; + pk = 1.0; + pkm1 = 1.0/ans; + k = n-1; + r = 2*k; + do + { + pkm2 = (pkm1*r-pk*x)/x; + pk = pkm1; + pkm1 = pkm2; + r = r-2.0; + k = k-1; + } + while(k!=0); + if( ae_fp_greater(ae_fabs(pk, _state),ae_fabs(pkm1, _state)) ) + { + ans = besselj1(x, _state)/pk; + } + else + { + ans = besselj0(x, _state)/pkm1; + } + result = sg*ans; + return result; +} + + +/************************************************************************* +Bessel function of the second kind, order zero + +Returns Bessel function of the second kind, of order +zero, of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval a rational approximation +R(x) is employed to compute + y0(x) = R(x) + 2 * log(x) * j0(x) / PI. +Thus a call to j0() is required. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + + + +ACCURACY: + + Absolute error, when y0(x) < 1; else relative error: + +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.3e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely0(double x, ae_state *_state) +{ + double nn; + double xsq; + double pzero; + double qzero; + double p4; + double q4; + double result; + + + if( ae_fp_greater(x,8.0) ) + { + bessel_besselasympt0(x, &pzero, &qzero, _state); + nn = x-ae_pi/4; + result = ae_sqrt(2/ae_pi/x, _state)*(pzero*ae_sin(nn, _state)+qzero*ae_cos(nn, _state)); + return result; + } + xsq = ae_sqr(x, _state); + p4 = -41370.35497933148554125235152; + p4 = 59152134.65686889654273830069+xsq*p4; + p4 = -34363712229.79040378171030138+xsq*p4; + p4 = 10255208596863.94284509167421+xsq*p4; + p4 = -1648605817185729.473122082537+xsq*p4; + p4 = 137562431639934407.8571335453+xsq*p4; + p4 = -5247065581112764941.297350814+xsq*p4; + p4 = 65874732757195549259.99402049+xsq*p4; + p4 = -27502866786291095837.01933175+xsq*p4; + q4 = 1.0; + q4 = 1282.452772478993804176329391+xsq*q4; + q4 = 1001702.641288906265666651753+xsq*q4; + q4 = 579512264.0700729537480087915+xsq*q4; + q4 = 261306575504.1081249568482092+xsq*q4; + q4 = 91620380340751.85262489147968+xsq*q4; + q4 = 23928830434997818.57439356652+xsq*q4; + q4 = 4192417043410839973.904769661+xsq*q4; + q4 = 372645883898616588198.9980+xsq*q4; + result = p4/q4+2/ae_pi*besselj0(x, _state)*ae_log(x, _state); + return result; +} + + +/************************************************************************* +Bessel function of second kind of order one + +Returns Bessel function of the second kind of order one +of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 25 term Chebyshev +expansion is used, and a call to j1() is required. +In the second, the asymptotic trigonometric representation +is employed using two rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.0e-15 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely1(double x, ae_state *_state) +{ + double nn; + double xsq; + double pzero; + double qzero; + double p4; + double q4; + double result; + + + if( ae_fp_greater(x,8.0) ) + { + bessel_besselasympt1(x, &pzero, &qzero, _state); + nn = x-3*ae_pi/4; + result = ae_sqrt(2/ae_pi/x, _state)*(pzero*ae_sin(nn, _state)+qzero*ae_cos(nn, _state)); + return result; + } + xsq = ae_sqr(x, _state); + p4 = -2108847.540133123652824139923; + p4 = 3639488548.124002058278999428+xsq*p4; + p4 = -2580681702194.450950541426399+xsq*p4; + p4 = 956993023992168.3481121552788+xsq*p4; + p4 = -196588746272214065.8820322248+xsq*p4; + p4 = 21931073399177975921.11427556+xsq*p4; + p4 = -1212297555414509577913.561535+xsq*p4; + p4 = 26554738314348543268942.48968+xsq*p4; + p4 = -99637534243069222259967.44354+xsq*p4; + q4 = 1.0; + q4 = 1612.361029677000859332072312+xsq*q4; + q4 = 1563282.754899580604737366452+xsq*q4; + q4 = 1128686837.169442121732366891+xsq*q4; + q4 = 646534088126.5275571961681500+xsq*q4; + q4 = 297663212564727.6729292742282+xsq*q4; + q4 = 108225825940881955.2553850180+xsq*q4; + q4 = 29549879358971486742.90758119+xsq*q4; + q4 = 5435310377188854170800.653097+xsq*q4; + q4 = 508206736694124324531442.4152+xsq*q4; + result = x*p4/q4+2/ae_pi*(besselj1(x, _state)*ae_log(x, _state)-1/x); + return result; +} + + +/************************************************************************* +Bessel function of second kind of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The function is evaluated by forward recurrence on +n, starting with values computed by the routines +y0() and y1(). + +If n = 0 or 1 the routine for y0 or y1 is called +directly. + +ACCURACY: + Absolute error, except relative + when y > 1: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 3.4e-15 4.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselyn(ae_int_t n, double x, ae_state *_state) +{ + ae_int_t i; + double a; + double b; + double tmp; + double s; + double result; + + + s = 1; + if( n<0 ) + { + n = -n; + if( n%2!=0 ) + { + s = -1; + } + } + if( n==0 ) + { + result = bessely0(x, _state); + return result; + } + if( n==1 ) + { + result = s*bessely1(x, _state); + return result; + } + a = bessely0(x, _state); + b = bessely1(x, _state); + for(i=1; i<=n-1; i++) + { + tmp = b; + b = 2*i/x*b-a; + a = tmp; + } + result = s*b; + return result; +} + + +/************************************************************************* +Modified Bessel function of order zero + +Returns modified Bessel function of order zero of the +argument. + +The function is defined as i0(x) = j0( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 5.8e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli0(double x, ae_state *_state) +{ + double y; + double v; + double z; + double b0; + double b1; + double b2; + double result; + + + if( ae_fp_less(x,0) ) + { + x = -x; + } + if( ae_fp_less_eq(x,8.0) ) + { + y = x/2.0-2.0; + bessel_besselmfirstcheb(-4.41534164647933937950E-18, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 3.33079451882223809783E-17, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -2.43127984654795469359E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.71539128555513303061E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -1.16853328779934516808E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 7.67618549860493561688E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -4.85644678311192946090E-13, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 2.95505266312963983461E-12, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -1.72682629144155570723E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 9.67580903537323691224E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -5.18979560163526290666E-10, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 2.65982372468238665035E-9, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -1.30002500998624804212E-8, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 6.04699502254191894932E-8, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -2.67079385394061173391E-7, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.11738753912010371815E-6, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -4.41673835845875056359E-6, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.64484480707288970893E-5, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -5.75419501008210370398E-5, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.88502885095841655729E-4, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -5.76375574538582365885E-4, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.63947561694133579842E-3, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -4.32430999505057594430E-3, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.05464603945949983183E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -2.37374148058994688156E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 4.93052842396707084878E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -9.49010970480476444210E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.71620901522208775349E-1, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -3.04682672343198398683E-1, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 6.76795274409476084995E-1, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + result = ae_exp(x, _state)*v; + return result; + } + z = 32.0/x-2.0; + bessel_besselmfirstcheb(-7.23318048787475395456E-18, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -4.83050448594418207126E-18, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 4.46562142029675999901E-17, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.46122286769746109310E-17, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -2.82762398051658348494E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -3.42548561967721913462E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.77256013305652638360E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.81168066935262242075E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -9.55484669882830764870E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -4.15056934728722208663E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.54008621752140982691E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.85277838274214270114E-13, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 7.18012445138366623367E-13, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.79417853150680611778E-12, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.32158118404477131188E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -3.14991652796324136454E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.18891471078464383424E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 4.94060238822496958910E-10, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.39623202570838634515E-9, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 2.26666899049817806459E-8, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 2.04891858946906374183E-7, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 2.89137052083475648297E-6, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 6.88975834691682398426E-5, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.36911647825569408990E-3, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 8.04490411014108831608E-1, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + result = ae_exp(x, _state)*v/ae_sqrt(x, _state); + return result; +} + + +/************************************************************************* +Modified Bessel function of order one + +Returns modified Bessel function of order one of the +argument. + +The function is defined as i1(x) = -i j1( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.9e-15 2.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli1(double x, ae_state *_state) +{ + double y; + double z; + double v; + double b0; + double b1; + double b2; + double result; + + + z = ae_fabs(x, _state); + if( ae_fp_less_eq(z,8.0) ) + { + y = z/2.0-2.0; + bessel_besselm1firstcheb(2.77791411276104639959E-18, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.11142121435816608115E-17, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.55363195773620046921E-16, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.10559694773538630805E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 7.60068429473540693410E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -5.04218550472791168711E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.22379336594557470981E-13, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.98397439776494371520E-12, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.17361862988909016308E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -6.66348972350202774223E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.62559028155211703701E-10, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.88724975172282928790E-9, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 9.38153738649577178388E-9, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -4.44505912879632808065E-8, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.00329475355213526229E-7, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -8.56872026469545474066E-7, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.47025130813767847674E-6, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.32731636560394358279E-5, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 4.78156510755005422638E-5, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.61760815825896745588E-4, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 5.12285956168575772895E-4, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.51357245063125314899E-3, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 4.15642294431288815669E-3, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.05640848946261981558E-2, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.47264490306265168283E-2, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -5.29459812080949914269E-2, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.02643658689847095384E-1, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.76416518357834055153E-1, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.52587186443633654823E-1, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + z = v*z*ae_exp(z, _state); + } + else + { + y = 32.0/z-2.0; + bessel_besselm1firstcheb(7.51729631084210481353E-18, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 4.41434832307170791151E-18, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -4.65030536848935832153E-17, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.20952592199342395980E-17, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.96262899764595013876E-16, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.30820231092092828324E-16, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.88035477551078244854E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.81440307243700780478E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.04202769841288027642E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 4.27244001671195135429E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.10154184277266431302E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -4.08355111109219731823E-13, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -7.19855177624590851209E-13, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.03562854414708950722E-12, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.41258074366137813316E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.25260358301548823856E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.89749581235054123450E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -5.58974346219658380687E-10, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.83538038596423702205E-9, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.63146884688951950684E-8, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.51223623787020892529E-7, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.88256480887769039346E-6, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.10588938762623716291E-4, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -9.76109749136146840777E-3, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 7.78576235018280120474E-1, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + z = v*ae_exp(z, _state)/ae_sqrt(z, _state); + } + if( ae_fp_less(x,0) ) + { + z = -z; + } + result = z; + return result; +} + + +/************************************************************************* +Modified Bessel function, second kind, order zero + +Returns modified Bessel function of the second kind +of order zero of the argument. + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + +Tested at 2000 random points between 0 and 8. Peak absolute +error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15. + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk0(double x, ae_state *_state) +{ + double y; + double z; + double v; + double b0; + double b1; + double b2; + double result; + + + ae_assert(ae_fp_greater(x,0), "Domain error in BesselK0: x<=0", _state); + if( ae_fp_less_eq(x,2) ) + { + y = x*x-2.0; + bessel_besselmfirstcheb(1.37446543561352307156E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 4.25981614279661018399E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.03496952576338420167E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.90451637722020886025E-9, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 2.53479107902614945675E-7, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 2.28621210311945178607E-5, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 1.26461541144692592338E-3, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 3.59799365153615016266E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, 3.44289899924628486886E-1, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(y, -5.35327393233902768720E-1, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + v = v-ae_log(0.5*x, _state)*besseli0(x, _state); + } + else + { + z = 8.0/x-2.0; + bessel_besselmfirstcheb(5.30043377268626276149E-18, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.64758043015242134646E-17, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 5.21039150503902756861E-17, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.67823109680541210385E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 5.51205597852431940784E-16, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.84859337734377901440E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 6.34007647740507060557E-15, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -2.22751332699166985548E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 8.03289077536357521100E-14, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -2.98009692317273043925E-13, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.14034058820847496303E-12, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -4.51459788337394416547E-12, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.85594911495471785253E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -7.95748924447710747776E-11, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 3.57739728140030116597E-10, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.69753450938905987466E-9, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 8.57403401741422608519E-9, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -4.66048989768794782956E-8, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 2.76681363944501510342E-7, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.83175552271911948767E-6, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.39498137188764993662E-5, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -1.28495495816278026384E-4, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 1.56988388573005337491E-3, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, -3.14481013119645005427E-2, &b0, &b1, &b2, _state); + bessel_besselmnextcheb(z, 2.44030308206595545468E0, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + v = v*ae_exp(-x, _state)/ae_sqrt(x, _state); + } + result = v; + return result; +} + + +/************************************************************************* +Modified Bessel function, second kind, order one + +Computes the modified Bessel function of the second kind +of order one of the argument. + +The range is partitioned into the two intervals [0,2] and +(2, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk1(double x, ae_state *_state) +{ + double y; + double z; + double v; + double b0; + double b1; + double b2; + double result; + + + z = 0.5*x; + ae_assert(ae_fp_greater(z,0), "Domain error in K1", _state); + if( ae_fp_less_eq(x,2) ) + { + y = x*x-2.0; + bessel_besselm1firstcheb(-7.02386347938628759343E-18, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.42744985051936593393E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -6.66690169419932900609E-13, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.41148839263352776110E-10, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.21338763073472585583E-8, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.43340614156596823496E-6, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.73028895751305206302E-4, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -6.97572385963986435018E-3, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.22611180822657148235E-1, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.53155960776544875667E-1, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.52530022733894777053E0, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + result = ae_log(z, _state)*besseli1(x, _state)+v/x; + } + else + { + y = 8.0/x-2.0; + bessel_besselm1firstcheb(-5.75674448366501715755E-18, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.79405087314755922667E-17, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -5.68946255844285935196E-17, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.83809354436663880070E-16, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -6.05704724837331885336E-16, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.03870316562433424052E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -7.01983709041831346144E-15, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.47715442448130437068E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -8.97670518232499435011E-14, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 3.34841966607842919884E-13, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.28917396095102890680E-12, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 5.13963967348173025100E-12, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.12996783842756842877E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 9.21831518760500529508E-11, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -4.19035475934189648750E-10, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.01504975519703286596E-9, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.03457624656780970260E-8, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 5.74108412545004946722E-8, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -3.50196060308781257119E-7, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.40648494783721712015E-6, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -1.93619797416608296024E-5, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.95215518471351631108E-4, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, -2.85781685962277938680E-3, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 1.03923736576817238437E-1, &b0, &b1, &b2, _state); + bessel_besselm1nextcheb(y, 2.72062619048444266945E0, &b0, &b1, &b2, _state); + v = 0.5*(b0-b2); + result = ae_exp(-x, _state)*v/ae_sqrt(x, _state); + } + return result; +} + + +/************************************************************************* +Modified Bessel function, second kind, integer order + +Returns modified Bessel function of the second kind +of order n of the argument. + +The range is partitioned into the two intervals [0,9.55] and +(9.55, infinity). An ascending power series is used in the +low range, and an asymptotic expansion in the high range. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 90000 1.8e-8 3.0e-10 + +Error is high only near the crossover point x = 9.55 +between the two expansions used. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselkn(ae_int_t nn, double x, ae_state *_state) +{ + double k; + double kf; + double nk1f; + double nkf; + double zn; + double t; + double s; + double z0; + double z; + double ans; + double fn; + double pn; + double pk; + double zmn; + double tlg; + double tox; + ae_int_t i; + ae_int_t n; + double eul; + double result; + + + eul = 5.772156649015328606065e-1; + if( nn<0 ) + { + n = -nn; + } + else + { + n = nn; + } + ae_assert(n<=31, "Overflow in BesselKN", _state); + ae_assert(ae_fp_greater(x,0), "Domain error in BesselKN", _state); + if( ae_fp_less_eq(x,9.55) ) + { + ans = 0.0; + z0 = 0.25*x*x; + fn = 1.0; + pn = 0.0; + zmn = 1.0; + tox = 2.0/x; + if( n>0 ) + { + pn = -eul; + k = 1.0; + for(i=1; i<=n-1; i++) + { + pn = pn+1.0/k; + k = k+1.0; + fn = fn*k; + } + zmn = tox; + if( n==1 ) + { + ans = 1.0/x; + } + else + { + nk1f = fn/n; + kf = 1.0; + s = nk1f; + z = -z0; + zn = 1.0; + for(i=1; i<=n-1; i++) + { + nk1f = nk1f/(n-i); + kf = kf*i; + zn = zn*z; + t = nk1f*zn/kf; + s = s+t; + ae_assert(ae_fp_greater(ae_maxrealnumber-ae_fabs(t, _state),ae_fabs(s, _state)), "Overflow in BesselKN", _state); + ae_assert(!(ae_fp_greater(tox,1.0)&&ae_fp_less(ae_maxrealnumber/tox,zmn)), "Overflow in BesselKN", _state); + zmn = zmn*tox; + } + s = s*0.5; + t = ae_fabs(s, _state); + ae_assert(!(ae_fp_greater(zmn,1.0)&&ae_fp_less(ae_maxrealnumber/zmn,t)), "Overflow in BesselKN", _state); + ae_assert(!(ae_fp_greater(t,1.0)&&ae_fp_less(ae_maxrealnumber/t,zmn)), "Overflow in BesselKN", _state); + ans = s*zmn; + } + } + tlg = 2.0*ae_log(0.5*x, _state); + pk = -eul; + if( n==0 ) + { + pn = pk; + t = 1.0; + } + else + { + pn = pn+1.0/n; + t = 1.0/fn; + } + s = (pk+pn-tlg)*t; + k = 1.0; + do + { + t = t*(z0/(k*(k+n))); + pk = pk+1.0/k; + pn = pn+1.0/(k+n); + s = s+(pk+pn-tlg)*t; + k = k+1.0; + } + while(ae_fp_greater(ae_fabs(t/s, _state),ae_machineepsilon)); + s = 0.5*s/zmn; + if( n%2!=0 ) + { + s = -s; + } + ans = ans+s; + result = ans; + return result; + } + if( ae_fp_greater(x,ae_log(ae_maxrealnumber, _state)) ) + { + result = 0; + return result; + } + k = n; + pn = 4.0*k*k; + pk = 1.0; + z0 = 8.0*x; + fn = 1.0; + t = 1.0; + s = t; + nkf = ae_maxrealnumber; + i = 0; + do + { + z = pn-pk*pk; + t = t*z/(fn*z0); + nk1f = ae_fabs(t, _state); + if( i>=n&&ae_fp_greater(nk1f,nkf) ) + { + break; + } + nkf = nk1f; + s = s+t; + fn = fn+1.0; + pk = pk+2.0; + i = i+1; + } + while(ae_fp_greater(ae_fabs(t/s, _state),ae_machineepsilon)); + result = ae_exp(-x, _state)*ae_sqrt(ae_pi/(2.0*x), _state)*s; + return result; +} + + +/************************************************************************* +Internal subroutine + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +static void bessel_besselmfirstcheb(double c, + double* b0, + double* b1, + double* b2, + ae_state *_state) +{ + + + *b0 = c; + *b1 = 0.0; + *b2 = 0.0; +} + + +/************************************************************************* +Internal subroutine + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +static void bessel_besselmnextcheb(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state) +{ + + + *b2 = *b1; + *b1 = *b0; + *b0 = x*(*b1)-(*b2)+c; +} + + +/************************************************************************* +Internal subroutine + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +static void bessel_besselm1firstcheb(double c, + double* b0, + double* b1, + double* b2, + ae_state *_state) +{ + + + *b0 = c; + *b1 = 0.0; + *b2 = 0.0; +} + + +/************************************************************************* +Internal subroutine + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +static void bessel_besselm1nextcheb(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state) +{ + + + *b2 = *b1; + *b1 = *b0; + *b0 = x*(*b1)-(*b2)+c; +} + + +static void bessel_besselasympt0(double x, + double* pzero, + double* qzero, + ae_state *_state) +{ + double xsq; + double p2; + double q2; + double p3; + double q3; + + *pzero = 0; + *qzero = 0; + + xsq = 64.0/(x*x); + p2 = 0.0; + p2 = 2485.271928957404011288128951+xsq*p2; + p2 = 153982.6532623911470917825993+xsq*p2; + p2 = 2016135.283049983642487182349+xsq*p2; + p2 = 8413041.456550439208464315611+xsq*p2; + p2 = 12332384.76817638145232406055+xsq*p2; + p2 = 5393485.083869438325262122897+xsq*p2; + q2 = 1.0; + q2 = 2615.700736920839685159081813+xsq*q2; + q2 = 156001.7276940030940592769933+xsq*q2; + q2 = 2025066.801570134013891035236+xsq*q2; + q2 = 8426449.050629797331554404810+xsq*q2; + q2 = 12338310.22786324960844856182+xsq*q2; + q2 = 5393485.083869438325560444960+xsq*q2; + p3 = -0.0; + p3 = -4.887199395841261531199129300+xsq*p3; + p3 = -226.2630641933704113967255053+xsq*p3; + p3 = -2365.956170779108192723612816+xsq*p3; + p3 = -8239.066313485606568803548860+xsq*p3; + p3 = -10381.41698748464093880530341+xsq*p3; + p3 = -3984.617357595222463506790588+xsq*p3; + q3 = 1.0; + q3 = 408.7714673983499223402830260+xsq*q3; + q3 = 15704.89191515395519392882766+xsq*q3; + q3 = 156021.3206679291652539287109+xsq*q3; + q3 = 533291.3634216897168722255057+xsq*q3; + q3 = 666745.4239319826986004038103+xsq*q3; + q3 = 255015.5108860942382983170882+xsq*q3; + *pzero = p2/q2; + *qzero = 8*p3/q3/x; +} + + +static void bessel_besselasympt1(double x, + double* pzero, + double* qzero, + ae_state *_state) +{ + double xsq; + double p2; + double q2; + double p3; + double q3; + + *pzero = 0; + *qzero = 0; + + xsq = 64.0/(x*x); + p2 = -1611.616644324610116477412898; + p2 = -109824.0554345934672737413139+xsq*p2; + p2 = -1523529.351181137383255105722+xsq*p2; + p2 = -6603373.248364939109255245434+xsq*p2; + p2 = -9942246.505077641195658377899+xsq*p2; + p2 = -4435757.816794127857114720794+xsq*p2; + q2 = 1.0; + q2 = -1455.009440190496182453565068+xsq*q2; + q2 = -107263.8599110382011903063867+xsq*q2; + q2 = -1511809.506634160881644546358+xsq*q2; + q2 = -6585339.479723087072826915069+xsq*q2; + q2 = -9934124.389934585658967556309+xsq*q2; + q2 = -4435757.816794127856828016962+xsq*q2; + p3 = 35.26513384663603218592175580; + p3 = 1706.375429020768002061283546+xsq*p3; + p3 = 18494.26287322386679652009819+xsq*p3; + p3 = 66178.83658127083517939992166+xsq*p3; + p3 = 85145.16067533570196555001171+xsq*p3; + p3 = 33220.91340985722351859704442+xsq*p3; + q3 = 1.0; + q3 = 863.8367769604990967475517183+xsq*q3; + q3 = 37890.22974577220264142952256+xsq*q3; + q3 = 400294.4358226697511708610813+xsq*q3; + q3 = 1419460.669603720892855755253+xsq*q3; + q3 = 1819458.042243997298924553839+xsq*q3; + q3 = 708712.8194102874357377502472+xsq*q3; + *pzero = p2/q2; + *qzero = 8*p3/q3/x; +} + + + + +/************************************************************************* +Beta function + + + - - + | (a) | (b) +beta( a, b ) = -----------. + - + | (a+b) + +For large arguments the logarithm of the function is +evaluated using lgam(), then exponentiated. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 8.1e-14 1.1e-14 + +Cephes Math Library Release 2.0: April, 1987 +Copyright 1984, 1987 by Stephen L. Moshier +*************************************************************************/ +double beta(double a, double b, ae_state *_state) +{ + double y; + double sg; + double s; + double result; + + + sg = 1; + ae_assert(ae_fp_greater(a,0)||ae_fp_neq(a,ae_ifloor(a, _state)), "Overflow in Beta", _state); + ae_assert(ae_fp_greater(b,0)||ae_fp_neq(b,ae_ifloor(b, _state)), "Overflow in Beta", _state); + y = a+b; + if( ae_fp_greater(ae_fabs(y, _state),171.624376956302725) ) + { + y = lngamma(y, &s, _state); + sg = sg*s; + y = lngamma(b, &s, _state)-y; + sg = sg*s; + y = lngamma(a, &s, _state)+y; + sg = sg*s; + ae_assert(ae_fp_less_eq(y,ae_log(ae_maxrealnumber, _state)), "Overflow in Beta", _state); + result = sg*ae_exp(y, _state); + return result; + } + y = gammafunction(y, _state); + ae_assert(ae_fp_neq(y,0), "Overflow in Beta", _state); + if( ae_fp_greater(a,b) ) + { + y = gammafunction(a, _state)/y; + y = y*gammafunction(b, _state); + } + else + { + y = gammafunction(b, _state)/y; + y = y*gammafunction(a, _state); + } + result = y; + return result; +} + + + + +/************************************************************************* +Incomplete beta integral + +Returns incomplete beta integral of the arguments, evaluated +from zero to x. The function is defined as + + x + - - + | (a+b) | | a-1 b-1 + ----------- | t (1-t) dt. + - - | | + | (a) | (b) - + 0 + +The domain of definition is 0 <= x <= 1. In this +implementation a and b are restricted to positive values. +The integral from x to 1 may be obtained by the symmetry +relation + + 1 - incbet( a, b, x ) = incbet( b, a, 1-x ). + +The integral is evaluated by a continued fraction expansion +or, when b*x is small, by a power series. + +ACCURACY: + +Tested at uniformly distributed random points (a,b,x) with a and b +in "domain" and x between 0 and 1. + Relative error +arithmetic domain # trials peak rms + IEEE 0,5 10000 6.9e-15 4.5e-16 + IEEE 0,85 250000 2.2e-13 1.7e-14 + IEEE 0,1000 30000 5.3e-12 6.3e-13 + IEEE 0,10000 250000 9.3e-11 7.1e-12 + IEEE 0,100000 10000 8.7e-10 4.8e-11 +Outputs smaller than the IEEE gradual underflow threshold +were excluded from these statistics. + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletebeta(double a, double b, double x, ae_state *_state) +{ + double t; + double xc; + double w; + double y; + ae_int_t flag; + double sg; + double big; + double biginv; + double maxgam; + double minlog; + double maxlog; + double result; + + + big = 4.503599627370496e15; + biginv = 2.22044604925031308085e-16; + maxgam = 171.624376956302725; + minlog = ae_log(ae_minrealnumber, _state); + maxlog = ae_log(ae_maxrealnumber, _state); + ae_assert(ae_fp_greater(a,0)&&ae_fp_greater(b,0), "Domain error in IncompleteBeta", _state); + ae_assert(ae_fp_greater_eq(x,0)&&ae_fp_less_eq(x,1), "Domain error in IncompleteBeta", _state); + if( ae_fp_eq(x,0) ) + { + result = 0; + return result; + } + if( ae_fp_eq(x,1) ) + { + result = 1; + return result; + } + flag = 0; + if( ae_fp_less_eq(b*x,1.0)&&ae_fp_less_eq(x,0.95) ) + { + result = ibetaf_incompletebetaps(a, b, x, maxgam, _state); + return result; + } + w = 1.0-x; + if( ae_fp_greater(x,a/(a+b)) ) + { + flag = 1; + t = a; + a = b; + b = t; + xc = x; + x = w; + } + else + { + xc = w; + } + if( (flag==1&&ae_fp_less_eq(b*x,1.0))&&ae_fp_less_eq(x,0.95) ) + { + t = ibetaf_incompletebetaps(a, b, x, maxgam, _state); + if( ae_fp_less_eq(t,ae_machineepsilon) ) + { + result = 1.0-ae_machineepsilon; + } + else + { + result = 1.0-t; + } + return result; + } + y = x*(a+b-2.0)-(a-1.0); + if( ae_fp_less(y,0.0) ) + { + w = ibetaf_incompletebetafe(a, b, x, big, biginv, _state); + } + else + { + w = ibetaf_incompletebetafe2(a, b, x, big, biginv, _state)/xc; + } + y = a*ae_log(x, _state); + t = b*ae_log(xc, _state); + if( (ae_fp_less(a+b,maxgam)&&ae_fp_less(ae_fabs(y, _state),maxlog))&&ae_fp_less(ae_fabs(t, _state),maxlog) ) + { + t = ae_pow(xc, b, _state); + t = t*ae_pow(x, a, _state); + t = t/a; + t = t*w; + t = t*(gammafunction(a+b, _state)/(gammafunction(a, _state)*gammafunction(b, _state))); + if( flag==1 ) + { + if( ae_fp_less_eq(t,ae_machineepsilon) ) + { + result = 1.0-ae_machineepsilon; + } + else + { + result = 1.0-t; + } + } + else + { + result = t; + } + return result; + } + y = y+t+lngamma(a+b, &sg, _state)-lngamma(a, &sg, _state)-lngamma(b, &sg, _state); + y = y+ae_log(w/a, _state); + if( ae_fp_less(y,minlog) ) + { + t = 0.0; + } + else + { + t = ae_exp(y, _state); + } + if( flag==1 ) + { + if( ae_fp_less_eq(t,ae_machineepsilon) ) + { + t = 1.0-ae_machineepsilon; + } + else + { + t = 1.0-t; + } + } + result = t; + return result; +} + + +/************************************************************************* +Inverse of imcomplete beta integral + +Given y, the function finds x such that + + incbet( a, b, x ) = y . + +The routine performs interval halving or Newton iterations to find the +root of incbet(a,b,x) - y = 0. + + +ACCURACY: + + Relative error: + x a,b +arithmetic domain domain # trials peak rms + IEEE 0,1 .5,10000 50000 5.8e-12 1.3e-13 + IEEE 0,1 .25,100 100000 1.8e-13 3.9e-15 + IEEE 0,1 0,5 50000 1.1e-12 5.5e-15 +With a and b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 50000 5.8e-12 1.1e-13 + IEEE 0,1 .5,100 100000 1.7e-14 7.9e-16 +With a = .5, b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 10000 8.3e-11 1.0e-11 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1996, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletebeta(double a, double b, double y, ae_state *_state) +{ + double aaa; + double bbb; + double y0; + double d; + double yyy; + double x; + double x0; + double x1; + double lgm; + double yp; + double di; + double dithresh; + double yl; + double yh; + double xt; + ae_int_t i; + ae_int_t rflg; + ae_int_t dir; + ae_int_t nflg; + double s; + ae_int_t mainlooppos; + ae_int_t ihalve; + ae_int_t ihalvecycle; + ae_int_t newt; + ae_int_t newtcycle; + ae_int_t breaknewtcycle; + ae_int_t breakihalvecycle; + double result; + + + i = 0; + ae_assert(ae_fp_greater_eq(y,0)&&ae_fp_less_eq(y,1), "Domain error in InvIncompleteBeta", _state); + + /* + * special cases + */ + if( ae_fp_eq(y,0) ) + { + result = 0; + return result; + } + if( ae_fp_eq(y,1.0) ) + { + result = 1; + return result; + } + + /* + * these initializations are not really necessary, + * but without them compiler complains about 'possibly uninitialized variables'. + */ + dithresh = 0; + rflg = 0; + aaa = 0; + bbb = 0; + y0 = 0; + x = 0; + yyy = 0; + lgm = 0; + dir = 0; + di = 0; + + /* + * normal initializations + */ + x0 = 0.0; + yl = 0.0; + x1 = 1.0; + yh = 1.0; + nflg = 0; + mainlooppos = 0; + ihalve = 1; + ihalvecycle = 2; + newt = 3; + newtcycle = 4; + breaknewtcycle = 5; + breakihalvecycle = 6; + + /* + * main loop + */ + for(;;) + { + + /* + * start + */ + if( mainlooppos==0 ) + { + if( ae_fp_less_eq(a,1.0)||ae_fp_less_eq(b,1.0) ) + { + dithresh = 1.0e-6; + rflg = 0; + aaa = a; + bbb = b; + y0 = y; + x = aaa/(aaa+bbb); + yyy = incompletebeta(aaa, bbb, x, _state); + mainlooppos = ihalve; + continue; + } + else + { + dithresh = 1.0e-4; + } + yp = -invnormaldistribution(y, _state); + if( ae_fp_greater(y,0.5) ) + { + rflg = 1; + aaa = b; + bbb = a; + y0 = 1.0-y; + yp = -yp; + } + else + { + rflg = 0; + aaa = a; + bbb = b; + y0 = y; + } + lgm = (yp*yp-3.0)/6.0; + x = 2.0/(1.0/(2.0*aaa-1.0)+1.0/(2.0*bbb-1.0)); + d = yp*ae_sqrt(x+lgm, _state)/x-(1.0/(2.0*bbb-1.0)-1.0/(2.0*aaa-1.0))*(lgm+5.0/6.0-2.0/(3.0*x)); + d = 2.0*d; + if( ae_fp_less(d,ae_log(ae_minrealnumber, _state)) ) + { + x = 0; + break; + } + x = aaa/(aaa+bbb*ae_exp(d, _state)); + yyy = incompletebeta(aaa, bbb, x, _state); + yp = (yyy-y0)/y0; + if( ae_fp_less(ae_fabs(yp, _state),0.2) ) + { + mainlooppos = newt; + continue; + } + mainlooppos = ihalve; + continue; + } + + /* + * ihalve + */ + if( mainlooppos==ihalve ) + { + dir = 0; + di = 0.5; + i = 0; + mainlooppos = ihalvecycle; + continue; + } + + /* + * ihalvecycle + */ + if( mainlooppos==ihalvecycle ) + { + if( i<=99 ) + { + if( i!=0 ) + { + x = x0+di*(x1-x0); + if( ae_fp_eq(x,1.0) ) + { + x = 1.0-ae_machineepsilon; + } + if( ae_fp_eq(x,0.0) ) + { + di = 0.5; + x = x0+di*(x1-x0); + if( ae_fp_eq(x,0.0) ) + { + break; + } + } + yyy = incompletebeta(aaa, bbb, x, _state); + yp = (x1-x0)/(x1+x0); + if( ae_fp_less(ae_fabs(yp, _state),dithresh) ) + { + mainlooppos = newt; + continue; + } + yp = (yyy-y0)/y0; + if( ae_fp_less(ae_fabs(yp, _state),dithresh) ) + { + mainlooppos = newt; + continue; + } + } + if( ae_fp_less(yyy,y0) ) + { + x0 = x; + yl = yyy; + if( dir<0 ) + { + dir = 0; + di = 0.5; + } + else + { + if( dir>3 ) + { + di = 1.0-(1.0-di)*(1.0-di); + } + else + { + if( dir>1 ) + { + di = 0.5*di+0.5; + } + else + { + di = (y0-yyy)/(yh-yl); + } + } + } + dir = dir+1; + if( ae_fp_greater(x0,0.75) ) + { + if( rflg==1 ) + { + rflg = 0; + aaa = a; + bbb = b; + y0 = y; + } + else + { + rflg = 1; + aaa = b; + bbb = a; + y0 = 1.0-y; + } + x = 1.0-x; + yyy = incompletebeta(aaa, bbb, x, _state); + x0 = 0.0; + yl = 0.0; + x1 = 1.0; + yh = 1.0; + mainlooppos = ihalve; + continue; + } + } + else + { + x1 = x; + if( rflg==1&&ae_fp_less(x1,ae_machineepsilon) ) + { + x = 0.0; + break; + } + yh = yyy; + if( dir>0 ) + { + dir = 0; + di = 0.5; + } + else + { + if( dir<-3 ) + { + di = di*di; + } + else + { + if( dir<-1 ) + { + di = 0.5*di; + } + else + { + di = (yyy-y0)/(yh-yl); + } + } + } + dir = dir-1; + } + i = i+1; + mainlooppos = ihalvecycle; + continue; + } + else + { + mainlooppos = breakihalvecycle; + continue; + } + } + + /* + * breakihalvecycle + */ + if( mainlooppos==breakihalvecycle ) + { + if( ae_fp_greater_eq(x0,1.0) ) + { + x = 1.0-ae_machineepsilon; + break; + } + if( ae_fp_less_eq(x,0.0) ) + { + x = 0.0; + break; + } + mainlooppos = newt; + continue; + } + + /* + * newt + */ + if( mainlooppos==newt ) + { + if( nflg!=0 ) + { + break; + } + nflg = 1; + lgm = lngamma(aaa+bbb, &s, _state)-lngamma(aaa, &s, _state)-lngamma(bbb, &s, _state); + i = 0; + mainlooppos = newtcycle; + continue; + } + + /* + * newtcycle + */ + if( mainlooppos==newtcycle ) + { + if( i<=7 ) + { + if( i!=0 ) + { + yyy = incompletebeta(aaa, bbb, x, _state); + } + if( ae_fp_less(yyy,yl) ) + { + x = x0; + yyy = yl; + } + else + { + if( ae_fp_greater(yyy,yh) ) + { + x = x1; + yyy = yh; + } + else + { + if( ae_fp_less(yyy,y0) ) + { + x0 = x; + yl = yyy; + } + else + { + x1 = x; + yh = yyy; + } + } + } + if( ae_fp_eq(x,1.0)||ae_fp_eq(x,0.0) ) + { + mainlooppos = breaknewtcycle; + continue; + } + d = (aaa-1.0)*ae_log(x, _state)+(bbb-1.0)*ae_log(1.0-x, _state)+lgm; + if( ae_fp_less(d,ae_log(ae_minrealnumber, _state)) ) + { + break; + } + if( ae_fp_greater(d,ae_log(ae_maxrealnumber, _state)) ) + { + mainlooppos = breaknewtcycle; + continue; + } + d = ae_exp(d, _state); + d = (yyy-y0)/d; + xt = x-d; + if( ae_fp_less_eq(xt,x0) ) + { + yyy = (x-x0)/(x1-x0); + xt = x0+0.5*yyy*(x-x0); + if( ae_fp_less_eq(xt,0.0) ) + { + mainlooppos = breaknewtcycle; + continue; + } + } + if( ae_fp_greater_eq(xt,x1) ) + { + yyy = (x1-x)/(x1-x0); + xt = x1-0.5*yyy*(x1-x); + if( ae_fp_greater_eq(xt,1.0) ) + { + mainlooppos = breaknewtcycle; + continue; + } + } + x = xt; + if( ae_fp_less(ae_fabs(d/x, _state),128.0*ae_machineepsilon) ) + { + break; + } + i = i+1; + mainlooppos = newtcycle; + continue; + } + else + { + mainlooppos = breaknewtcycle; + continue; + } + } + + /* + * breaknewtcycle + */ + if( mainlooppos==breaknewtcycle ) + { + dithresh = 256.0*ae_machineepsilon; + mainlooppos = ihalve; + continue; + } + } + + /* + * done + */ + if( rflg!=0 ) + { + if( ae_fp_less_eq(x,ae_machineepsilon) ) + { + x = 1.0-ae_machineepsilon; + } + else + { + x = 1.0-x; + } + } + result = x; + return result; +} + + +/************************************************************************* +Continued fraction expansion #1 for incomplete beta integral + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +static double ibetaf_incompletebetafe(double a, + double b, + double x, + double big, + double biginv, + ae_state *_state) +{ + double xk; + double pk; + double pkm1; + double pkm2; + double qk; + double qkm1; + double qkm2; + double k1; + double k2; + double k3; + double k4; + double k5; + double k6; + double k7; + double k8; + double r; + double t; + double ans; + double thresh; + ae_int_t n; + double result; + + + k1 = a; + k2 = a+b; + k3 = a; + k4 = a+1.0; + k5 = 1.0; + k6 = b-1.0; + k7 = k4; + k8 = a+2.0; + pkm2 = 0.0; + qkm2 = 1.0; + pkm1 = 1.0; + qkm1 = 1.0; + ans = 1.0; + r = 1.0; + n = 0; + thresh = 3.0*ae_machineepsilon; + do + { + xk = -x*k1*k2/(k3*k4); + pk = pkm1+pkm2*xk; + qk = qkm1+qkm2*xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + xk = x*k5*k6/(k7*k8); + pk = pkm1+pkm2*xk; + qk = qkm1+qkm2*xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + if( ae_fp_neq(qk,0) ) + { + r = pk/qk; + } + if( ae_fp_neq(r,0) ) + { + t = ae_fabs((ans-r)/r, _state); + ans = r; + } + else + { + t = 1.0; + } + if( ae_fp_less(t,thresh) ) + { + break; + } + k1 = k1+1.0; + k2 = k2+1.0; + k3 = k3+2.0; + k4 = k4+2.0; + k5 = k5+1.0; + k6 = k6-1.0; + k7 = k7+2.0; + k8 = k8+2.0; + if( ae_fp_greater(ae_fabs(qk, _state)+ae_fabs(pk, _state),big) ) + { + pkm2 = pkm2*biginv; + pkm1 = pkm1*biginv; + qkm2 = qkm2*biginv; + qkm1 = qkm1*biginv; + } + if( ae_fp_less(ae_fabs(qk, _state),biginv)||ae_fp_less(ae_fabs(pk, _state),biginv) ) + { + pkm2 = pkm2*big; + pkm1 = pkm1*big; + qkm2 = qkm2*big; + qkm1 = qkm1*big; + } + n = n+1; + } + while(n!=300); + result = ans; + return result; +} + + +/************************************************************************* +Continued fraction expansion #2 +for incomplete beta integral + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +static double ibetaf_incompletebetafe2(double a, + double b, + double x, + double big, + double biginv, + ae_state *_state) +{ + double xk; + double pk; + double pkm1; + double pkm2; + double qk; + double qkm1; + double qkm2; + double k1; + double k2; + double k3; + double k4; + double k5; + double k6; + double k7; + double k8; + double r; + double t; + double ans; + double z; + double thresh; + ae_int_t n; + double result; + + + k1 = a; + k2 = b-1.0; + k3 = a; + k4 = a+1.0; + k5 = 1.0; + k6 = a+b; + k7 = a+1.0; + k8 = a+2.0; + pkm2 = 0.0; + qkm2 = 1.0; + pkm1 = 1.0; + qkm1 = 1.0; + z = x/(1.0-x); + ans = 1.0; + r = 1.0; + n = 0; + thresh = 3.0*ae_machineepsilon; + do + { + xk = -z*k1*k2/(k3*k4); + pk = pkm1+pkm2*xk; + qk = qkm1+qkm2*xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + xk = z*k5*k6/(k7*k8); + pk = pkm1+pkm2*xk; + qk = qkm1+qkm2*xk; + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + if( ae_fp_neq(qk,0) ) + { + r = pk/qk; + } + if( ae_fp_neq(r,0) ) + { + t = ae_fabs((ans-r)/r, _state); + ans = r; + } + else + { + t = 1.0; + } + if( ae_fp_less(t,thresh) ) + { + break; + } + k1 = k1+1.0; + k2 = k2-1.0; + k3 = k3+2.0; + k4 = k4+2.0; + k5 = k5+1.0; + k6 = k6+1.0; + k7 = k7+2.0; + k8 = k8+2.0; + if( ae_fp_greater(ae_fabs(qk, _state)+ae_fabs(pk, _state),big) ) + { + pkm2 = pkm2*biginv; + pkm1 = pkm1*biginv; + qkm2 = qkm2*biginv; + qkm1 = qkm1*biginv; + } + if( ae_fp_less(ae_fabs(qk, _state),biginv)||ae_fp_less(ae_fabs(pk, _state),biginv) ) + { + pkm2 = pkm2*big; + pkm1 = pkm1*big; + qkm2 = qkm2*big; + qkm1 = qkm1*big; + } + n = n+1; + } + while(n!=300); + result = ans; + return result; +} + + +/************************************************************************* +Power series for incomplete beta integral. +Use when b*x is small and x not too close to 1. + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +static double ibetaf_incompletebetaps(double a, + double b, + double x, + double maxgam, + ae_state *_state) +{ + double s; + double t; + double u; + double v; + double n; + double t1; + double z; + double ai; + double sg; + double result; + + + ai = 1.0/a; + u = (1.0-b)*x; + v = u/(a+1.0); + t1 = v; + t = u; + n = 2.0; + s = 0.0; + z = ae_machineepsilon*ai; + while(ae_fp_greater(ae_fabs(v, _state),z)) + { + u = (n-b)*x/n; + t = t*u; + v = t/(a+n); + s = s+v; + n = n+1.0; + } + s = s+t1; + s = s+ai; + u = a*ae_log(x, _state); + if( ae_fp_less(a+b,maxgam)&&ae_fp_less(ae_fabs(u, _state),ae_log(ae_maxrealnumber, _state)) ) + { + t = gammafunction(a+b, _state)/(gammafunction(a, _state)*gammafunction(b, _state)); + s = s*t*ae_pow(x, a, _state); + } + else + { + t = lngamma(a+b, &sg, _state)-lngamma(a, &sg, _state)-lngamma(b, &sg, _state)+u+ae_log(s, _state); + if( ae_fp_less(t,ae_log(ae_minrealnumber, _state)) ) + { + s = 0.0; + } + else + { + s = ae_exp(t, _state); + } + } + result = s; + return result; +} + + + + +/************************************************************************* +Binomial distribution + +Returns the sum of the terms 0 through k of the Binomial +probability density: + + k + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=0 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p), with p between 0 and 1. + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 4.3e-15 2.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialdistribution(ae_int_t k, + ae_int_t n, + double p, + ae_state *_state) +{ + double dk; + double dn; + double result; + + + ae_assert(ae_fp_greater_eq(p,0)&&ae_fp_less_eq(p,1), "Domain error in BinomialDistribution", _state); + ae_assert(k>=-1&&k<=n, "Domain error in BinomialDistribution", _state); + if( k==-1 ) + { + result = 0; + return result; + } + if( k==n ) + { + result = 1; + return result; + } + dn = n-k; + if( k==0 ) + { + dk = ae_pow(1.0-p, dn, _state); + } + else + { + dk = k+1; + dk = incompletebeta(dn, dk, 1.0-p, _state); + } + result = dk; + return result; +} + + +/************************************************************************* +Complemented binomial distribution + +Returns the sum of the terms k+1 through n of the Binomial +probability density: + + n + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=k+1 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 6.7e-15 8.2e-16 + For p between 0 and .001: + IEEE 0,100 100000 1.5e-13 2.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialcdistribution(ae_int_t k, + ae_int_t n, + double p, + ae_state *_state) +{ + double dk; + double dn; + double result; + + + ae_assert(ae_fp_greater_eq(p,0)&&ae_fp_less_eq(p,1), "Domain error in BinomialDistributionC", _state); + ae_assert(k>=-1&&k<=n, "Domain error in BinomialDistributionC", _state); + if( k==-1 ) + { + result = 1; + return result; + } + if( k==n ) + { + result = 0; + return result; + } + dn = n-k; + if( k==0 ) + { + if( ae_fp_less(p,0.01) ) + { + dk = -nuexpm1(dn*nulog1p(-p, _state), _state); + } + else + { + dk = 1.0-ae_pow(1.0-p, dn, _state); + } + } + else + { + dk = k+1; + dk = incompletebeta(dk, dn, p, _state); + } + result = dk; + return result; +} + + +/************************************************************************* +Inverse binomial distribution + +Finds the event probability p such that the sum of the +terms 0 through k of the Binomial probability density +is equal to the given cumulative probability y. + +This is accomplished using the inverse beta integral +function and the relation + +1 - p = incbi( n-k, k+1, y ). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 2.3e-14 6.4e-16 + IEEE 0,10000 100000 6.6e-12 1.2e-13 + For p between 10^-6 and 0.001: + IEEE 0,100 100000 2.0e-12 1.3e-14 + IEEE 0,10000 100000 1.5e-12 3.2e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invbinomialdistribution(ae_int_t k, + ae_int_t n, + double y, + ae_state *_state) +{ + double dk; + double dn; + double p; + double result; + + + ae_assert(k>=0&&k=0 + x - argument, -1 <= x <= 1 + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevcalculate(ae_int_t r, + ae_int_t n, + double x, + ae_state *_state) +{ + ae_int_t i; + double a; + double b; + double result; + + + result = 0; + + /* + * Prepare A and B + */ + if( r==1 ) + { + a = 1; + b = x; + } + else + { + a = 1; + b = 2*x; + } + + /* + * Special cases: N=0 or N=1 + */ + if( n==0 ) + { + result = a; + return result; + } + if( n==1 ) + { + result = b; + return result; + } + + /* + * General case: N>=2 + */ + for(i=2; i<=n; i++) + { + result = 2*x*b-a; + a = b; + b = result; + } + return result; +} + + +/************************************************************************* +Summation of Chebyshev polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*T0(x) + c[1]*T1(x) + ... + c[N]*TN(x) +or + c[0]*U0(x) + c[1]*U1(x) + ... + c[N]*UN(x) +depending on the R. + +Parameters: + r - polynomial kind, either 1 or 2. + n - degree, n>=0 + x - argument + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevsum(/* Real */ ae_vector* c, + ae_int_t r, + ae_int_t n, + double x, + ae_state *_state) +{ + double b1; + double b2; + ae_int_t i; + double result; + + + b1 = 0; + b2 = 0; + for(i=n; i>=1; i--) + { + result = 2*x*b1-b2+c->ptr.p_double[i]; + b2 = b1; + b1 = result; + } + if( r==1 ) + { + result = -b2+x*b1+c->ptr.p_double[0]; + } + else + { + result = -b2+2*x*b1+c->ptr.p_double[0]; + } + return result; +} + + +/************************************************************************* +Representation of Tn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void chebyshevcoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(c); + + ae_vector_set_length(c, n+1, _state); + for(i=0; i<=n; i++) + { + c->ptr.p_double[i] = 0; + } + if( n==0||n==1 ) + { + c->ptr.p_double[n] = 1; + } + else + { + c->ptr.p_double[n] = ae_exp((n-1)*ae_log(2, _state), _state); + for(i=0; i<=n/2-1; i++) + { + c->ptr.p_double[n-2*(i+1)] = -c->ptr.p_double[n-2*i]*(n-2*i)*(n-2*i-1)/4/(i+1)/(n-i-1); + } + } +} + + +/************************************************************************* +Conversion of a series of Chebyshev polynomials to a power series. + +Represents A[0]*T0(x) + A[1]*T1(x) + ... + A[N]*Tn(x) as +B[0] + B[1]*X + ... + B[N]*X^N. + +Input parameters: + A - Chebyshev series coefficients + N - degree, N>=0 + +Output parameters + B - power series coefficients +*************************************************************************/ +void fromchebyshev(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* b, + ae_state *_state) +{ + ae_int_t i; + ae_int_t k; + double e; + double d; + + ae_vector_clear(b); + + ae_vector_set_length(b, n+1, _state); + for(i=0; i<=n; i++) + { + b->ptr.p_double[i] = 0; + } + d = 0; + i = 0; + do + { + k = i; + do + { + e = b->ptr.p_double[k]; + b->ptr.p_double[k] = 0; + if( i<=1&&k==i ) + { + b->ptr.p_double[k] = 1; + } + else + { + if( i!=0 ) + { + b->ptr.p_double[k] = 2*d; + } + if( k>i+1 ) + { + b->ptr.p_double[k] = b->ptr.p_double[k]-b->ptr.p_double[k-2]; + } + } + d = e; + k = k+1; + } + while(k<=n); + d = b->ptr.p_double[i]; + e = 0; + k = i; + while(k<=n) + { + e = e+b->ptr.p_double[k]*a->ptr.p_double[k]; + k = k+2; + } + b->ptr.p_double[i] = e; + i = i+1; + } + while(i<=n); +} + + + + +/************************************************************************* +Chi-square distribution + +Returns the area under the left hand tail (from 0 to x) +of the Chi square probability density function with +v degrees of freedom. + + + x + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + 0 + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igam( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquaredistribution(double v, double x, ae_state *_state) +{ + double result; + + + ae_assert(ae_fp_greater_eq(x,0)&&ae_fp_greater_eq(v,1), "Domain error in ChiSquareDistribution", _state); + result = incompletegamma(v/2.0, x/2.0, _state); + return result; +} + + +/************************************************************************* +Complemented Chi-square distribution + +Returns the area under the right hand tail (from x to +infinity) of the Chi square probability density function +with v degrees of freedom: + + inf. + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + x + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquarecdistribution(double v, double x, ae_state *_state) +{ + double result; + + + ae_assert(ae_fp_greater_eq(x,0)&&ae_fp_greater_eq(v,1), "Domain error in ChiSquareDistributionC", _state); + result = incompletegammac(v/2.0, x/2.0, _state); + return result; +} + + +/************************************************************************* +Inverse of complemented Chi-square distribution + +Finds the Chi-square argument x such that the integral +from x to infinity of the Chi-square density is equal +to the given cumulative probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + x/2 = igami( df/2, y ); + +ACCURACY: + +See inverse incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double invchisquaredistribution(double v, double y, ae_state *_state) +{ + double result; + + + ae_assert((ae_fp_greater_eq(y,0)&&ae_fp_less_eq(y,1))&&ae_fp_greater_eq(v,1), "Domain error in InvChiSquareDistribution", _state); + result = 2*invincompletegammac(0.5*v, y, _state); + return result; +} + + + + +/************************************************************************* +Dawson's Integral + +Approximates the integral + + x + - + 2 | | 2 + dawsn(x) = exp( -x ) | exp( t ) dt + | | + - + 0 + +Three different rational approximations are employed, for +the intervals 0 to 3.25; 3.25 to 6.25; and 6.25 up. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,10 10000 6.9e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double dawsonintegral(double x, ae_state *_state) +{ + double x2; + double y; + ae_int_t sg; + double an; + double ad; + double bn; + double bd; + double cn; + double cd; + double result; + + + sg = 1; + if( ae_fp_less(x,0) ) + { + sg = -1; + x = -x; + } + if( ae_fp_less(x,3.25) ) + { + x2 = x*x; + an = 1.13681498971755972054E-11; + an = an*x2+8.49262267667473811108E-10; + an = an*x2+1.94434204175553054283E-8; + an = an*x2+9.53151741254484363489E-7; + an = an*x2+3.07828309874913200438E-6; + an = an*x2+3.52513368520288738649E-4; + an = an*x2+(-8.50149846724410912031E-4); + an = an*x2+4.22618223005546594270E-2; + an = an*x2+(-9.17480371773452345351E-2); + an = an*x2+9.99999999999999994612E-1; + ad = 2.40372073066762605484E-11; + ad = ad*x2+1.48864681368493396752E-9; + ad = ad*x2+5.21265281010541664570E-8; + ad = ad*x2+1.27258478273186970203E-6; + ad = ad*x2+2.32490249820789513991E-5; + ad = ad*x2+3.25524741826057911661E-4; + ad = ad*x2+3.48805814657162590916E-3; + ad = ad*x2+2.79448531198828973716E-2; + ad = ad*x2+1.58874241960120565368E-1; + ad = ad*x2+5.74918629489320327824E-1; + ad = ad*x2+1.00000000000000000539E0; + y = x*an/ad; + result = sg*y; + return result; + } + x2 = 1.0/(x*x); + if( ae_fp_less(x,6.25) ) + { + bn = 5.08955156417900903354E-1; + bn = bn*x2-2.44754418142697847934E-1; + bn = bn*x2+9.41512335303534411857E-2; + bn = bn*x2-2.18711255142039025206E-2; + bn = bn*x2+3.66207612329569181322E-3; + bn = bn*x2-4.23209114460388756528E-4; + bn = bn*x2+3.59641304793896631888E-5; + bn = bn*x2-2.14640351719968974225E-6; + bn = bn*x2+9.10010780076391431042E-8; + bn = bn*x2-2.40274520828250956942E-9; + bn = bn*x2+3.59233385440928410398E-11; + bd = 1.00000000000000000000E0; + bd = bd*x2-6.31839869873368190192E-1; + bd = bd*x2+2.36706788228248691528E-1; + bd = bd*x2-5.31806367003223277662E-2; + bd = bd*x2+8.48041718586295374409E-3; + bd = bd*x2-9.47996768486665330168E-4; + bd = bd*x2+7.81025592944552338085E-5; + bd = bd*x2-4.55875153252442634831E-6; + bd = bd*x2+1.89100358111421846170E-7; + bd = bd*x2-4.91324691331920606875E-9; + bd = bd*x2+7.18466403235734541950E-11; + y = 1.0/x+x2*bn/(bd*x); + result = sg*0.5*y; + return result; + } + if( ae_fp_greater(x,1.0E9) ) + { + result = sg*0.5/x; + return result; + } + cn = -5.90592860534773254987E-1; + cn = cn*x2+6.29235242724368800674E-1; + cn = cn*x2-1.72858975380388136411E-1; + cn = cn*x2+1.64837047825189632310E-2; + cn = cn*x2-4.86827613020462700845E-4; + cd = 1.00000000000000000000E0; + cd = cd*x2-2.69820057197544900361E0; + cd = cd*x2+1.73270799045947845857E0; + cd = cd*x2-3.93708582281939493482E-1; + cd = cd*x2+3.44278924041233391079E-2; + cd = cd*x2-9.73655226040941223894E-4; + y = 1.0/x+x2*cn/(cd*x); + result = sg*0.5*y; + return result; +} + + + + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +using the approximation + + P(x) - log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegralk(double m, ae_state *_state) +{ + double result; + + + result = ellipticintegralkhighprecision(1.0-m, _state); + return result; +} + + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +where m = 1 - m1, using the approximation + + P(x) - log x Q(x). + +The argument m1 is used rather than m so that the logarithmic +singularity at m = 1 will be shifted to the origin; this +preserves maximum accuracy. + +K(0) = pi/2. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Àëãîðèòì âçÿò èç áèáëèîòåêè Cephes +*************************************************************************/ +double ellipticintegralkhighprecision(double m1, ae_state *_state) +{ + double p; + double q; + double result; + + + if( ae_fp_less_eq(m1,ae_machineepsilon) ) + { + result = 1.3862943611198906188E0-0.5*ae_log(m1, _state); + } + else + { + p = 1.37982864606273237150E-4; + p = p*m1+2.28025724005875567385E-3; + p = p*m1+7.97404013220415179367E-3; + p = p*m1+9.85821379021226008714E-3; + p = p*m1+6.87489687449949877925E-3; + p = p*m1+6.18901033637687613229E-3; + p = p*m1+8.79078273952743772254E-3; + p = p*m1+1.49380448916805252718E-2; + p = p*m1+3.08851465246711995998E-2; + p = p*m1+9.65735902811690126535E-2; + p = p*m1+1.38629436111989062502E0; + q = 2.94078955048598507511E-5; + q = q*m1+9.14184723865917226571E-4; + q = q*m1+5.94058303753167793257E-3; + q = q*m1+1.54850516649762399335E-2; + q = q*m1+2.39089602715924892727E-2; + q = q*m1+3.01204715227604046988E-2; + q = q*m1+3.73774314173823228969E-2; + q = q*m1+4.88280347570998239232E-2; + q = q*m1+7.03124996963957469739E-2; + q = q*m1+1.24999999999870820058E-1; + q = q*m1+4.99999999999999999821E-1; + result = p-q*ae_log(m1, _state); + } + return result; +} + + +/************************************************************************* +Incomplete elliptic integral of the first kind F(phi|m) + +Approximates the integral + + + + phi + - + | | + | dt +F(phi_\m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + + + + +ACCURACY: + +Tested at random points with m in [0, 1] and phi as indicated. + + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 200000 7.4e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegralk(double phi, double m, ae_state *_state) +{ + double a; + double b; + double c; + double e; + double temp; + double pio2; + double t; + double k; + ae_int_t d; + ae_int_t md; + ae_int_t s; + ae_int_t npio2; + double result; + + + pio2 = 1.57079632679489661923; + if( ae_fp_eq(m,0) ) + { + result = phi; + return result; + } + a = 1-m; + if( ae_fp_eq(a,0) ) + { + result = ae_log(ae_tan(0.5*(pio2+phi), _state), _state); + return result; + } + npio2 = ae_ifloor(phi/pio2, _state); + if( npio2%2!=0 ) + { + npio2 = npio2+1; + } + if( npio2!=0 ) + { + k = ellipticintegralk(1-a, _state); + phi = phi-npio2*pio2; + } + else + { + k = 0; + } + if( ae_fp_less(phi,0) ) + { + phi = -phi; + s = -1; + } + else + { + s = 0; + } + b = ae_sqrt(a, _state); + t = ae_tan(phi, _state); + if( ae_fp_greater(ae_fabs(t, _state),10) ) + { + e = 1.0/(b*t); + if( ae_fp_less(ae_fabs(e, _state),10) ) + { + e = ae_atan(e, _state); + if( npio2==0 ) + { + k = ellipticintegralk(1-a, _state); + } + temp = k-incompleteellipticintegralk(e, m, _state); + if( s<0 ) + { + temp = -temp; + } + result = temp+npio2*k; + return result; + } + } + a = 1.0; + c = ae_sqrt(m, _state); + d = 1; + md = 0; + while(ae_fp_greater(ae_fabs(c/a, _state),ae_machineepsilon)) + { + temp = b/a; + phi = phi+ae_atan(t*temp, _state)+md*ae_pi; + md = ae_trunc((phi+pio2)/ae_pi, _state); + t = t*(1.0+temp)/(1.0-temp*t*t); + c = 0.5*(a-b); + temp = ae_sqrt(a*b, _state); + a = 0.5*(a+b); + b = temp; + d = d+d; + } + temp = (ae_atan(t, _state)+md*ae_pi)/(d*a); + if( s<0 ) + { + temp = -temp; + } + result = temp+npio2*k; + return result; +} + + +/************************************************************************* +Complete elliptic integral of the second kind + +Approximates the integral + + + pi/2 + - + | | 2 +E(m) = | sqrt( 1 - m sin t ) dt + | | + - + 0 + +using the approximation + + P(x) - x log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 1 10000 2.1e-16 7.3e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegrale(double m, ae_state *_state) +{ + double p; + double q; + double result; + + + ae_assert(ae_fp_greater_eq(m,0)&&ae_fp_less_eq(m,1), "Domain error in EllipticIntegralE: m<0 or m>1", _state); + m = 1-m; + if( ae_fp_eq(m,0) ) + { + result = 1; + return result; + } + p = 1.53552577301013293365E-4; + p = p*m+2.50888492163602060990E-3; + p = p*m+8.68786816565889628429E-3; + p = p*m+1.07350949056076193403E-2; + p = p*m+7.77395492516787092951E-3; + p = p*m+7.58395289413514708519E-3; + p = p*m+1.15688436810574127319E-2; + p = p*m+2.18317996015557253103E-2; + p = p*m+5.68051945617860553470E-2; + p = p*m+4.43147180560990850618E-1; + p = p*m+1.00000000000000000299E0; + q = 3.27954898576485872656E-5; + q = q*m+1.00962792679356715133E-3; + q = q*m+6.50609489976927491433E-3; + q = q*m+1.68862163993311317300E-2; + q = q*m+2.61769742454493659583E-2; + q = q*m+3.34833904888224918614E-2; + q = q*m+4.27180926518931511717E-2; + q = q*m+5.85936634471101055642E-2; + q = q*m+9.37499997197644278445E-2; + q = q*m+2.49999999999888314361E-1; + result = p-q*m*ae_log(m, _state); + return result; +} + + +/************************************************************************* +Incomplete elliptic integral of the second kind + +Approximates the integral + + + phi + - + | | + | 2 +E(phi_\m) = | sqrt( 1 - m sin t ) dt + | + | | + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + +ACCURACY: + +Tested at random arguments with phi in [-10, 10] and m in +[0, 1]. + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 150000 3.3e-15 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1993, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegrale(double phi, double m, ae_state *_state) +{ + double pio2; + double a; + double b; + double c; + double e; + double temp; + double lphi; + double t; + double ebig; + ae_int_t d; + ae_int_t md; + ae_int_t npio2; + ae_int_t s; + double result; + + + pio2 = 1.57079632679489661923; + if( ae_fp_eq(m,0) ) + { + result = phi; + return result; + } + lphi = phi; + npio2 = ae_ifloor(lphi/pio2, _state); + if( npio2%2!=0 ) + { + npio2 = npio2+1; + } + lphi = lphi-npio2*pio2; + if( ae_fp_less(lphi,0) ) + { + lphi = -lphi; + s = -1; + } + else + { + s = 1; + } + a = 1.0-m; + ebig = ellipticintegrale(m, _state); + if( ae_fp_eq(a,0) ) + { + temp = ae_sin(lphi, _state); + if( s<0 ) + { + temp = -temp; + } + result = temp+npio2*ebig; + return result; + } + t = ae_tan(lphi, _state); + b = ae_sqrt(a, _state); + + /* + * Thanks to Brian Fitzgerald + * for pointing out an instability near odd multiples of pi/2 + */ + if( ae_fp_greater(ae_fabs(t, _state),10) ) + { + + /* + * Transform the amplitude + */ + e = 1.0/(b*t); + + /* + * ... but avoid multiple recursions. + */ + if( ae_fp_less(ae_fabs(e, _state),10) ) + { + e = ae_atan(e, _state); + temp = ebig+m*ae_sin(lphi, _state)*ae_sin(e, _state)-incompleteellipticintegrale(e, m, _state); + if( s<0 ) + { + temp = -temp; + } + result = temp+npio2*ebig; + return result; + } + } + c = ae_sqrt(m, _state); + a = 1.0; + d = 1; + e = 0.0; + md = 0; + while(ae_fp_greater(ae_fabs(c/a, _state),ae_machineepsilon)) + { + temp = b/a; + lphi = lphi+ae_atan(t*temp, _state)+md*ae_pi; + md = ae_trunc((lphi+pio2)/ae_pi, _state); + t = t*(1.0+temp)/(1.0-temp*t*t); + c = 0.5*(a-b); + temp = ae_sqrt(a*b, _state); + a = 0.5*(a+b); + b = temp; + d = d+d; + e = e+c*ae_sin(lphi, _state); + } + temp = ebig/ellipticintegralk(m, _state); + temp = temp*((ae_atan(t, _state)+md*ae_pi)/(d*a)); + temp = temp+e; + if( s<0 ) + { + temp = -temp; + } + result = temp+npio2*ebig; + return result; +} + + + + +/************************************************************************* +Exponential integral Ei(x) + + x + - t + | | e + Ei(x) = -|- --- dt . + | | t + - + -inf + +Not defined for x <= 0. +See also expn.c. + + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,100 50000 8.6e-16 1.3e-16 + +Cephes Math Library Release 2.8: May, 1999 +Copyright 1999 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralei(double x, ae_state *_state) +{ + double eul; + double f; + double f1; + double f2; + double w; + double result; + + + eul = 0.5772156649015328606065; + if( ae_fp_less_eq(x,0) ) + { + result = 0; + return result; + } + if( ae_fp_less(x,2) ) + { + f1 = -5.350447357812542947283; + f1 = f1*x+218.5049168816613393830; + f1 = f1*x-4176.572384826693777058; + f1 = f1*x+55411.76756393557601232; + f1 = f1*x-331338.1331178144034309; + f1 = f1*x+1592627.163384945414220; + f2 = 1.000000000000000000000; + f2 = f2*x-52.50547959112862969197; + f2 = f2*x+1259.616186786790571525; + f2 = f2*x-17565.49581973534652631; + f2 = f2*x+149306.2117002725991967; + f2 = f2*x-729494.9239640527645655; + f2 = f2*x+1592627.163384945429726; + f = f1/f2; + result = eul+ae_log(x, _state)+x*f; + return result; + } + if( ae_fp_less(x,4) ) + { + w = 1/x; + f1 = 1.981808503259689673238E-2; + f1 = f1*w-1.271645625984917501326; + f1 = f1*w-2.088160335681228318920; + f1 = f1*w+2.755544509187936721172; + f1 = f1*w-4.409507048701600257171E-1; + f1 = f1*w+4.665623805935891391017E-2; + f1 = f1*w-1.545042679673485262580E-3; + f1 = f1*w+7.059980605299617478514E-5; + f2 = 1.000000000000000000000; + f2 = f2*w+1.476498670914921440652; + f2 = f2*w+5.629177174822436244827E-1; + f2 = f2*w+1.699017897879307263248E-1; + f2 = f2*w+2.291647179034212017463E-2; + f2 = f2*w+4.450150439728752875043E-3; + f2 = f2*w+1.727439612206521482874E-4; + f2 = f2*w+3.953167195549672482304E-5; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; + } + if( ae_fp_less(x,8) ) + { + w = 1/x; + f1 = -1.373215375871208729803; + f1 = f1*w-7.084559133740838761406E-1; + f1 = f1*w+1.580806855547941010501; + f1 = f1*w-2.601500427425622944234E-1; + f1 = f1*w+2.994674694113713763365E-2; + f1 = f1*w-1.038086040188744005513E-3; + f1 = f1*w+4.371064420753005429514E-5; + f1 = f1*w+2.141783679522602903795E-6; + f2 = 1.000000000000000000000; + f2 = f2*w+8.585231423622028380768E-1; + f2 = f2*w+4.483285822873995129957E-1; + f2 = f2*w+7.687932158124475434091E-2; + f2 = f2*w+2.449868241021887685904E-2; + f2 = f2*w+8.832165941927796567926E-4; + f2 = f2*w+4.590952299511353531215E-4; + f2 = f2*w+(-4.729848351866523044863E-6); + f2 = f2*w+2.665195537390710170105E-6; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; + } + if( ae_fp_less(x,16) ) + { + w = 1/x; + f1 = -2.106934601691916512584; + f1 = f1*w+1.732733869664688041885; + f1 = f1*w-2.423619178935841904839E-1; + f1 = f1*w+2.322724180937565842585E-2; + f1 = f1*w+2.372880440493179832059E-4; + f1 = f1*w-8.343219561192552752335E-5; + f1 = f1*w+1.363408795605250394881E-5; + f1 = f1*w-3.655412321999253963714E-7; + f1 = f1*w+1.464941733975961318456E-8; + f1 = f1*w+6.176407863710360207074E-10; + f2 = 1.000000000000000000000; + f2 = f2*w-2.298062239901678075778E-1; + f2 = f2*w+1.105077041474037862347E-1; + f2 = f2*w-1.566542966630792353556E-2; + f2 = f2*w+2.761106850817352773874E-3; + f2 = f2*w-2.089148012284048449115E-4; + f2 = f2*w+1.708528938807675304186E-5; + f2 = f2*w-4.459311796356686423199E-7; + f2 = f2*w+1.394634930353847498145E-8; + f2 = f2*w+6.150865933977338354138E-10; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; + } + if( ae_fp_less(x,32) ) + { + w = 1/x; + f1 = -2.458119367674020323359E-1; + f1 = f1*w-1.483382253322077687183E-1; + f1 = f1*w+7.248291795735551591813E-2; + f1 = f1*w-1.348315687380940523823E-2; + f1 = f1*w+1.342775069788636972294E-3; + f1 = f1*w-7.942465637159712264564E-5; + f1 = f1*w+2.644179518984235952241E-6; + f1 = f1*w-4.239473659313765177195E-8; + f2 = 1.000000000000000000000; + f2 = f2*w-1.044225908443871106315E-1; + f2 = f2*w-2.676453128101402655055E-1; + f2 = f2*w+9.695000254621984627876E-2; + f2 = f2*w-1.601745692712991078208E-2; + f2 = f2*w+1.496414899205908021882E-3; + f2 = f2*w-8.462452563778485013756E-5; + f2 = f2*w+2.728938403476726394024E-6; + f2 = f2*w-4.239462431819542051337E-8; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; + } + if( ae_fp_less(x,64) ) + { + w = 1/x; + f1 = 1.212561118105456670844E-1; + f1 = f1*w-5.823133179043894485122E-1; + f1 = f1*w+2.348887314557016779211E-1; + f1 = f1*w-3.040034318113248237280E-2; + f1 = f1*w+1.510082146865190661777E-3; + f1 = f1*w-2.523137095499571377122E-5; + f2 = 1.000000000000000000000; + f2 = f2*w-1.002252150365854016662; + f2 = f2*w+2.928709694872224144953E-1; + f2 = f2*w-3.337004338674007801307E-2; + f2 = f2*w+1.560544881127388842819E-3; + f2 = f2*w-2.523137093603234562648E-5; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; + } + w = 1/x; + f1 = -7.657847078286127362028E-1; + f1 = f1*w+6.886192415566705051750E-1; + f1 = f1*w-2.132598113545206124553E-1; + f1 = f1*w+3.346107552384193813594E-2; + f1 = f1*w-3.076541477344756050249E-3; + f1 = f1*w+1.747119316454907477380E-4; + f1 = f1*w-6.103711682274170530369E-6; + f1 = f1*w+1.218032765428652199087E-7; + f1 = f1*w-1.086076102793290233007E-9; + f2 = 1.000000000000000000000; + f2 = f2*w-1.888802868662308731041; + f2 = f2*w+1.066691687211408896850; + f2 = f2*w-2.751915982306380647738E-1; + f2 = f2*w+3.930852688233823569726E-2; + f2 = f2*w-3.414684558602365085394E-3; + f2 = f2*w+1.866844370703555398195E-4; + f2 = f2*w-6.345146083130515357861E-6; + f2 = f2*w+1.239754287483206878024E-7; + f2 = f2*w-1.086076102793126632978E-9; + f = f1/f2; + result = ae_exp(x, _state)*w*(1+w*f); + return result; +} + + +/************************************************************************* +Exponential integral En(x) + +Evaluates the exponential integral + + inf. + - + | | -xt + | e + E (x) = | ---- dt. + n | n + | | t + - + 1 + + +Both n and x must be nonnegative. + +The routine employs either a power series, a continued +fraction, or an asymptotic formula depending on the +relative values of n and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 10000 1.7e-15 3.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 2000 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralen(double x, ae_int_t n, ae_state *_state) +{ + double r; + double t; + double yk; + double xk; + double pk; + double pkm1; + double pkm2; + double qk; + double qkm1; + double qkm2; + double psi; + double z; + ae_int_t i; + ae_int_t k; + double big; + double eul; + double result; + + + eul = 0.57721566490153286060; + big = 1.44115188075855872*ae_pow(10, 17, _state); + if( ((n<0||ae_fp_less(x,0))||ae_fp_greater(x,170))||(ae_fp_eq(x,0)&&n<2) ) + { + result = -1; + return result; + } + if( ae_fp_eq(x,0) ) + { + result = (double)1/(double)(n-1); + return result; + } + if( n==0 ) + { + result = ae_exp(-x, _state)/x; + return result; + } + if( n>5000 ) + { + xk = x+n; + yk = 1/(xk*xk); + t = n; + result = yk*t*(6*x*x-8*t*x+t*t); + result = yk*(result+t*(t-2.0*x)); + result = yk*(result+t); + result = (result+1)*ae_exp(-x, _state)/xk; + return result; + } + if( ae_fp_less_eq(x,1) ) + { + psi = -eul-ae_log(x, _state); + for(i=1; i<=n-1; i++) + { + psi = psi+(double)1/(double)i; + } + z = -x; + xk = 0; + yk = 1; + pk = 1-n; + if( n==1 ) + { + result = 0.0; + } + else + { + result = 1.0/pk; + } + do + { + xk = xk+1; + yk = yk*z/xk; + pk = pk+1; + if( ae_fp_neq(pk,0) ) + { + result = result+yk/pk; + } + if( ae_fp_neq(result,0) ) + { + t = ae_fabs(yk/result, _state); + } + else + { + t = 1; + } + } + while(ae_fp_greater_eq(t,ae_machineepsilon)); + t = 1; + for(i=1; i<=n-1; i++) + { + t = t*z/i; + } + result = psi*t-result; + return result; + } + else + { + k = 1; + pkm2 = 1; + qkm2 = x; + pkm1 = 1.0; + qkm1 = x+n; + result = pkm1/qkm1; + do + { + k = k+1; + if( k%2==1 ) + { + yk = 1; + xk = n+(double)(k-1)/(double)2; + } + else + { + yk = x; + xk = (double)k/(double)2; + } + pk = pkm1*yk+pkm2*xk; + qk = qkm1*yk+qkm2*xk; + if( ae_fp_neq(qk,0) ) + { + r = pk/qk; + t = ae_fabs((result-r)/r, _state); + result = r; + } + else + { + t = 1; + } + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + if( ae_fp_greater(ae_fabs(pk, _state),big) ) + { + pkm2 = pkm2/big; + pkm1 = pkm1/big; + qkm2 = qkm2/big; + qkm1 = qkm1/big; + } + } + while(ae_fp_greater_eq(t,ae_machineepsilon)); + result = result*ae_exp(-x, _state); + } + return result; +} + + + + +/************************************************************************* +F distribution + +Returns the area from zero to x under the F density +function (also known as Snedcor's density or the +variance ratio density). This is the density +of x = (u1/df1)/(u2/df2), where u1 and u2 are random +variables having Chi square distributions with df1 +and df2 degrees of freedom, respectively. +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ). + + +The arguments a and b are greater than zero, and x is +nonnegative. + +ACCURACY: + +Tested at random points (a,b,x). + + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 0,100 100000 9.8e-15 1.7e-15 + IEEE 1,5 0,100 100000 6.5e-15 3.5e-16 + IEEE 0,1 1,10000 100000 2.2e-11 3.3e-12 + IEEE 1,5 1,10000 100000 1.1e-11 1.7e-13 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fdistribution(ae_int_t a, ae_int_t b, double x, ae_state *_state) +{ + double w; + double result; + + + ae_assert((a>=1&&b>=1)&&ae_fp_greater_eq(x,0), "Domain error in FDistribution", _state); + w = a*x; + w = w/(b+w); + result = incompletebeta(0.5*a, 0.5*b, w, _state); + return result; +} + + +/************************************************************************* +Complemented F distribution + +Returns the area from x to infinity under the F density +function (also known as Snedcor's density or the +variance ratio density). + + + inf. + - + 1 | | a-1 b-1 +1-P(x) = ------ | t (1-t) dt + B(a,b) | | + - + x + + +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ). + + +ACCURACY: + +Tested at random points (a,b,x) in the indicated intervals. + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 1,100 100000 3.7e-14 5.9e-16 + IEEE 1,5 1,100 100000 8.0e-15 1.6e-15 + IEEE 0,1 1,10000 100000 1.8e-11 3.5e-13 + IEEE 1,5 1,10000 100000 2.0e-11 3.0e-12 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fcdistribution(ae_int_t a, ae_int_t b, double x, ae_state *_state) +{ + double w; + double result; + + + ae_assert((a>=1&&b>=1)&&ae_fp_greater_eq(x,0), "Domain error in FCDistribution", _state); + w = b/(b+a*x); + result = incompletebeta(0.5*b, 0.5*a, w, _state); + return result; +} + + +/************************************************************************* +Inverse of complemented F distribution + +Finds the F density argument x such that the integral +from x to infinity of the F density is equal to the +given probability p. + +This is accomplished using the inverse beta integral +function and the relations + + z = incbi( df2/2, df1/2, p ) + x = df2 (1-z) / (df1 z). + +Note: the following relations hold for the inverse of +the uncomplemented F distribution: + + z = incbi( df1/2, df2/2, p ) + x = df2 z / (df1 (1-z)). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between .001 and 1: + IEEE 1,100 100000 8.3e-15 4.7e-16 + IEEE 1,10000 100000 2.1e-11 1.4e-13 + For p between 10^-6 and 10^-3: + IEEE 1,100 50000 1.3e-12 8.4e-15 + IEEE 1,10000 50000 3.0e-12 4.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invfdistribution(ae_int_t a, + ae_int_t b, + double y, + ae_state *_state) +{ + double w; + double result; + + + ae_assert(((a>=1&&b>=1)&&ae_fp_greater(y,0))&&ae_fp_less_eq(y,1), "Domain error in InvFDistribution", _state); + + /* + * Compute probability for x = 0.5 + */ + w = incompletebeta(0.5*b, 0.5*a, 0.5, _state); + + /* + * If that is greater than y, then the solution w < .5 + * Otherwise, solve at 1-y to remove cancellation in (b - b*w) + */ + if( ae_fp_greater(w,y)||ae_fp_less(y,0.001) ) + { + w = invincompletebeta(0.5*b, 0.5*a, y, _state); + result = (b-b*w)/(a*w); + } + else + { + w = invincompletebeta(0.5*a, 0.5*b, 1.0-y, _state); + result = b*w/(a*(1.0-w)); + } + return result; +} + + + + +/************************************************************************* +Fresnel integral + +Evaluates the Fresnel integrals + + x + - + | | +C(x) = | cos(pi/2 t**2) dt, + | | + - + 0 + + x + - + | | +S(x) = | sin(pi/2 t**2) dt. + | | + - + 0 + + +The integrals are evaluated by a power series for x < 1. +For x >= 1 auxiliary functions f(x) and g(x) are employed +such that + +C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 ) +S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 ) + + + +ACCURACY: + + Relative error. + +Arithmetic function domain # trials peak rms + IEEE S(x) 0, 10 10000 2.0e-15 3.2e-16 + IEEE C(x) 0, 10 10000 1.8e-15 3.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void fresnelintegral(double x, double* c, double* s, ae_state *_state) +{ + double xxa; + double f; + double g; + double cc; + double ss; + double t; + double u; + double x2; + double sn; + double sd; + double cn; + double cd; + double fn; + double fd; + double gn; + double gd; + double mpi; + double mpio2; + + + mpi = 3.14159265358979323846; + mpio2 = 1.57079632679489661923; + xxa = x; + x = ae_fabs(xxa, _state); + x2 = x*x; + if( ae_fp_less(x2,2.5625) ) + { + t = x2*x2; + sn = -2.99181919401019853726E3; + sn = sn*t+7.08840045257738576863E5; + sn = sn*t-6.29741486205862506537E7; + sn = sn*t+2.54890880573376359104E9; + sn = sn*t-4.42979518059697779103E10; + sn = sn*t+3.18016297876567817986E11; + sd = 1.00000000000000000000E0; + sd = sd*t+2.81376268889994315696E2; + sd = sd*t+4.55847810806532581675E4; + sd = sd*t+5.17343888770096400730E6; + sd = sd*t+4.19320245898111231129E8; + sd = sd*t+2.24411795645340920940E10; + sd = sd*t+6.07366389490084639049E11; + cn = -4.98843114573573548651E-8; + cn = cn*t+9.50428062829859605134E-6; + cn = cn*t-6.45191435683965050962E-4; + cn = cn*t+1.88843319396703850064E-2; + cn = cn*t-2.05525900955013891793E-1; + cn = cn*t+9.99999999999999998822E-1; + cd = 3.99982968972495980367E-12; + cd = cd*t+9.15439215774657478799E-10; + cd = cd*t+1.25001862479598821474E-7; + cd = cd*t+1.22262789024179030997E-5; + cd = cd*t+8.68029542941784300606E-4; + cd = cd*t+4.12142090722199792936E-2; + cd = cd*t+1.00000000000000000118E0; + *s = ae_sign(xxa, _state)*x*x2*sn/sd; + *c = ae_sign(xxa, _state)*x*cn/cd; + return; + } + if( ae_fp_greater(x,36974.0) ) + { + *c = ae_sign(xxa, _state)*0.5; + *s = ae_sign(xxa, _state)*0.5; + return; + } + x2 = x*x; + t = mpi*x2; + u = 1/(t*t); + t = 1/t; + fn = 4.21543555043677546506E-1; + fn = fn*u+1.43407919780758885261E-1; + fn = fn*u+1.15220955073585758835E-2; + fn = fn*u+3.45017939782574027900E-4; + fn = fn*u+4.63613749287867322088E-6; + fn = fn*u+3.05568983790257605827E-8; + fn = fn*u+1.02304514164907233465E-10; + fn = fn*u+1.72010743268161828879E-13; + fn = fn*u+1.34283276233062758925E-16; + fn = fn*u+3.76329711269987889006E-20; + fd = 1.00000000000000000000E0; + fd = fd*u+7.51586398353378947175E-1; + fd = fd*u+1.16888925859191382142E-1; + fd = fd*u+6.44051526508858611005E-3; + fd = fd*u+1.55934409164153020873E-4; + fd = fd*u+1.84627567348930545870E-6; + fd = fd*u+1.12699224763999035261E-8; + fd = fd*u+3.60140029589371370404E-11; + fd = fd*u+5.88754533621578410010E-14; + fd = fd*u+4.52001434074129701496E-17; + fd = fd*u+1.25443237090011264384E-20; + gn = 5.04442073643383265887E-1; + gn = gn*u+1.97102833525523411709E-1; + gn = gn*u+1.87648584092575249293E-2; + gn = gn*u+6.84079380915393090172E-4; + gn = gn*u+1.15138826111884280931E-5; + gn = gn*u+9.82852443688422223854E-8; + gn = gn*u+4.45344415861750144738E-10; + gn = gn*u+1.08268041139020870318E-12; + gn = gn*u+1.37555460633261799868E-15; + gn = gn*u+8.36354435630677421531E-19; + gn = gn*u+1.86958710162783235106E-22; + gd = 1.00000000000000000000E0; + gd = gd*u+1.47495759925128324529E0; + gd = gd*u+3.37748989120019970451E-1; + gd = gd*u+2.53603741420338795122E-2; + gd = gd*u+8.14679107184306179049E-4; + gd = gd*u+1.27545075667729118702E-5; + gd = gd*u+1.04314589657571990585E-7; + gd = gd*u+4.60680728146520428211E-10; + gd = gd*u+1.10273215066240270757E-12; + gd = gd*u+1.38796531259578871258E-15; + gd = gd*u+8.39158816283118707363E-19; + gd = gd*u+1.86958710162783236342E-22; + f = 1-u*fn/fd; + g = t*gn/gd; + t = mpio2*x2; + cc = ae_cos(t, _state); + ss = ae_sin(t, _state); + t = mpi*x; + *c = 0.5+(f*ss-g*cc)/t; + *s = 0.5-(f*cc+g*ss)/t; + *c = *c*ae_sign(xxa, _state); + *s = *s*ae_sign(xxa, _state); +} + + + + +/************************************************************************* +Calculation of the value of the Hermite polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial Hn at x +*************************************************************************/ +double hermitecalculate(ae_int_t n, double x, ae_state *_state) +{ + ae_int_t i; + double a; + double b; + double result; + + + result = 0; + + /* + * Prepare A and B + */ + a = 1; + b = 2*x; + + /* + * Special cases: N=0 or N=1 + */ + if( n==0 ) + { + result = a; + return result; + } + if( n==1 ) + { + result = b; + return result; + } + + /* + * General case: N>=2 + */ + for(i=2; i<=n; i++) + { + result = 2*x*b-2*(i-1)*a; + a = b; + b = result; + } + return result; +} + + +/************************************************************************* +Summation of Hermite polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*H0(x) + c[1]*H1(x) + ... + c[N]*HN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial at x +*************************************************************************/ +double hermitesum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state) +{ + double b1; + double b2; + ae_int_t i; + double result; + + + b1 = 0; + b2 = 0; + result = 0; + for(i=n; i>=0; i--) + { + result = 2*(x*b1-(i+1)*b2)+c->ptr.p_double[i]; + b2 = b1; + b1 = result; + } + return result; +} + + +/************************************************************************* +Representation of Hn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void hermitecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(c); + + ae_vector_set_length(c, n+1, _state); + for(i=0; i<=n; i++) + { + c->ptr.p_double[i] = 0; + } + c->ptr.p_double[n] = ae_exp(n*ae_log(2, _state), _state); + for(i=0; i<=n/2-1; i++) + { + c->ptr.p_double[n-2*(i+1)] = -c->ptr.p_double[n-2*i]*(n-2*i)*(n-2*i-1)/4/(i+1); + } +} + + + + +/************************************************************************* +Jacobian Elliptic Functions + +Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m), +and dn(u|m) of parameter m between 0 and 1, and real +argument u. + +These functions are periodic, with quarter-period on the +real axis equal to the complete elliptic integral +ellpk(1.0-m). + +Relation to incomplete elliptic integral: +If u = ellik(phi,m), then sn(u|m) = sin(phi), +and cn(u|m) = cos(phi). Phi is called the amplitude of u. + +Computation is by means of the arithmetic-geometric mean +algorithm, except when m is within 1e-9 of 0 or 1. In the +latter case with m close to 1, the approximation applies +only for phi < pi/2. + +ACCURACY: + +Tested at random points with u between 0 and 10, m between +0 and 1. + + Absolute error (* = relative error): +arithmetic function # trials peak rms + IEEE phi 10000 9.2e-16* 1.4e-16* + IEEE sn 50000 4.1e-15 4.6e-16 + IEEE cn 40000 3.6e-15 4.4e-16 + IEEE dn 10000 1.3e-12 1.8e-14 + + Peak error observed in consistency check using addition +theorem for sn(u+v) was 4e-16 (absolute). Also tested by +the above relation to the incomplete elliptic integral. +Accuracy deteriorates when u is large. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void jacobianellipticfunctions(double u, + double m, + double* sn, + double* cn, + double* dn, + double* ph, + ae_state *_state) +{ + ae_frame _frame_block; + double ai; + double b; + double phi; + double t; + double twon; + ae_vector a; + ae_vector c; + ae_int_t i; + + ae_frame_make(_state, &_frame_block); + *sn = 0; + *cn = 0; + *dn = 0; + *ph = 0; + ae_vector_init(&a, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_REAL, _state, ae_true); + + ae_assert(ae_fp_greater_eq(m,0)&&ae_fp_less_eq(m,1), "Domain error in JacobianEllipticFunctions: m<0 or m>1", _state); + ae_vector_set_length(&a, 8+1, _state); + ae_vector_set_length(&c, 8+1, _state); + if( ae_fp_less(m,1.0e-9) ) + { + t = ae_sin(u, _state); + b = ae_cos(u, _state); + ai = 0.25*m*(u-t*b); + *sn = t-ai*b; + *cn = b+ai*t; + *ph = u-ai; + *dn = 1.0-0.5*m*t*t; + ae_frame_leave(_state); + return; + } + if( ae_fp_greater_eq(m,0.9999999999) ) + { + ai = 0.25*(1.0-m); + b = ae_cosh(u, _state); + t = ae_tanh(u, _state); + phi = 1.0/b; + twon = b*ae_sinh(u, _state); + *sn = t+ai*(twon-u)/(b*b); + *ph = 2.0*ae_atan(ae_exp(u, _state), _state)-1.57079632679489661923+ai*(twon-u)/b; + ai = ai*t*phi; + *cn = phi-ai*(twon-u); + *dn = phi+ai*(twon+u); + ae_frame_leave(_state); + return; + } + a.ptr.p_double[0] = 1.0; + b = ae_sqrt(1.0-m, _state); + c.ptr.p_double[0] = ae_sqrt(m, _state); + twon = 1.0; + i = 0; + while(ae_fp_greater(ae_fabs(c.ptr.p_double[i]/a.ptr.p_double[i], _state),ae_machineepsilon)) + { + if( i>7 ) + { + ae_assert(ae_false, "Overflow in JacobianEllipticFunctions", _state); + break; + } + ai = a.ptr.p_double[i]; + i = i+1; + c.ptr.p_double[i] = 0.5*(ai-b); + t = ae_sqrt(ai*b, _state); + a.ptr.p_double[i] = 0.5*(ai+b); + b = t; + twon = twon*2.0; + } + phi = twon*a.ptr.p_double[i]*u; + do + { + t = c.ptr.p_double[i]*ae_sin(phi, _state)/a.ptr.p_double[i]; + b = phi; + phi = (ae_asin(t, _state)+phi)/2.0; + i = i-1; + } + while(i!=0); + *sn = ae_sin(phi, _state); + t = ae_cos(phi, _state); + *cn = t; + *dn = t/ae_cos(phi-b, _state); + *ph = phi; + ae_frame_leave(_state); +} + + + + +/************************************************************************* +Calculation of the value of the Laguerre polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial Ln at x +*************************************************************************/ +double laguerrecalculate(ae_int_t n, double x, ae_state *_state) +{ + double a; + double b; + double i; + double result; + + + result = 1; + a = 1; + b = 1-x; + if( n==1 ) + { + result = b; + } + i = 2; + while(ae_fp_less_eq(i,n)) + { + result = ((2*i-1-x)*b-(i-1)*a)/i; + a = b; + b = result; + i = i+1; + } + return result; +} + + +/************************************************************************* +Summation of Laguerre polynomials using Clenshaw’s recurrence formula. + +This routine calculates c[0]*L0(x) + c[1]*L1(x) + ... + c[N]*LN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial at x +*************************************************************************/ +double laguerresum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state) +{ + double b1; + double b2; + ae_int_t i; + double result; + + + b1 = 0; + b2 = 0; + result = 0; + for(i=n; i>=0; i--) + { + result = (2*i+1-x)*b1/(i+1)-(i+1)*b2/(i+2)+c->ptr.p_double[i]; + b2 = b1; + b1 = result; + } + return result; +} + + +/************************************************************************* +Representation of Ln as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void laguerrecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(c); + + ae_vector_set_length(c, n+1, _state); + c->ptr.p_double[0] = 1; + for(i=0; i<=n-1; i++) + { + c->ptr.p_double[i+1] = -c->ptr.p_double[i]*(n-i)/(i+1)/(i+1); + } +} + + + + +/************************************************************************* +Calculation of the value of the Legendre polynomial Pn. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial Pn at x +*************************************************************************/ +double legendrecalculate(ae_int_t n, double x, ae_state *_state) +{ + double a; + double b; + ae_int_t i; + double result; + + + result = 1; + a = 1; + b = x; + if( n==0 ) + { + result = a; + return result; + } + if( n==1 ) + { + result = b; + return result; + } + for(i=2; i<=n; i++) + { + result = ((2*i-1)*x*b-(i-1)*a)/i; + a = b; + b = result; + } + return result; +} + + +/************************************************************************* +Summation of Legendre polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*P0(x) + c[1]*P1(x) + ... + c[N]*PN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial at x +*************************************************************************/ +double legendresum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state) +{ + double b1; + double b2; + ae_int_t i; + double result; + + + b1 = 0; + b2 = 0; + result = 0; + for(i=n; i>=0; i--) + { + result = (2*i+1)*x*b1/(i+1)-(i+1)*b2/(i+2)+c->ptr.p_double[i]; + b2 = b1; + b1 = result; + } + return result; +} + + +/************************************************************************* +Representation of Pn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void legendrecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state) +{ + ae_int_t i; + + ae_vector_clear(c); + + ae_vector_set_length(c, n+1, _state); + for(i=0; i<=n; i++) + { + c->ptr.p_double[i] = 0; + } + c->ptr.p_double[n] = 1; + for(i=1; i<=n; i++) + { + c->ptr.p_double[n] = c->ptr.p_double[n]*(n+i)/2/i; + } + for(i=0; i<=n/2-1; i++) + { + c->ptr.p_double[n-2*(i+1)] = -c->ptr.p_double[n-2*i]*(n-2*i)*(n-2*i-1)/2/(i+1)/(2*(n-i)-1); + } +} + + + + +/************************************************************************* +Poisson distribution + +Returns the sum of the first k+1 terms of the Poisson +distribution: + + k j + -- -m m + > e -- + -- j! + j=0 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the relation + +y = pdtr( k, m ) = igamc( k+1, m ). + +The arguments must both be positive. +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissondistribution(ae_int_t k, double m, ae_state *_state) +{ + double result; + + + ae_assert(k>=0&&ae_fp_greater(m,0), "Domain error in PoissonDistribution", _state); + result = incompletegammac(k+1, m, _state); + return result; +} + + +/************************************************************************* +Complemented Poisson distribution + +Returns the sum of the terms k+1 to infinity of the Poisson +distribution: + + inf. j + -- -m m + > e -- + -- j! + j=k+1 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the formula + +y = pdtrc( k, m ) = igam( k+1, m ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissoncdistribution(ae_int_t k, double m, ae_state *_state) +{ + double result; + + + ae_assert(k>=0&&ae_fp_greater(m,0), "Domain error in PoissonDistributionC", _state); + result = incompletegamma(k+1, m, _state); + return result; +} + + +/************************************************************************* +Inverse Poisson distribution + +Finds the Poisson variable x such that the integral +from 0 to x of the Poisson density is equal to the +given probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + m = igami( k+1, y ). + +ACCURACY: + +See inverse incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invpoissondistribution(ae_int_t k, double y, ae_state *_state) +{ + double result; + + + ae_assert((k>=0&&ae_fp_greater_eq(y,0))&&ae_fp_less(y,1), "Domain error in InvPoissonDistribution", _state); + result = invincompletegammac(k+1, y, _state); + return result; +} + + + + +/************************************************************************* +Psi (digamma) function + + d - + psi(x) = -- ln | (x) + dx + +is the logarithmic derivative of the gamma function. +For integer x, + n-1 + - +psi(n) = -EUL + > 1/k. + - + k=1 + +This formula is used for 0 < n <= 10. If x is negative, it +is transformed to a positive argument by the reflection +formula psi(1-x) = psi(x) + pi cot(pi x). +For general positive x, the argument is made greater than 10 +using the recurrence psi(x+1) = psi(x) + 1/x. +Then the following asymptotic expansion is applied: + + inf. B + - 2k +psi(x) = log(x) - 1/2x - > ------- + - 2k + k=1 2k x + +where the B2k are Bernoulli numbers. + +ACCURACY: + Relative error (except absolute when |psi| < 1): +arithmetic domain # trials peak rms + IEEE 0,30 30000 1.3e-15 1.4e-16 + IEEE -30,0 40000 1.5e-15 2.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double psi(double x, ae_state *_state) +{ + double p; + double q; + double nz; + double s; + double w; + double y; + double z; + double polv; + ae_int_t i; + ae_int_t n; + ae_int_t negative; + double result; + + + negative = 0; + nz = 0.0; + if( ae_fp_less_eq(x,0) ) + { + negative = 1; + q = x; + p = ae_ifloor(q, _state); + if( ae_fp_eq(p,q) ) + { + ae_assert(ae_false, "Singularity in Psi(x)", _state); + result = ae_maxrealnumber; + return result; + } + nz = q-p; + if( ae_fp_neq(nz,0.5) ) + { + if( ae_fp_greater(nz,0.5) ) + { + p = p+1.0; + nz = q-p; + } + nz = ae_pi/ae_tan(ae_pi*nz, _state); + } + else + { + nz = 0.0; + } + x = 1.0-x; + } + if( ae_fp_less_eq(x,10.0)&&ae_fp_eq(x,ae_ifloor(x, _state)) ) + { + y = 0.0; + n = ae_ifloor(x, _state); + for(i=1; i<=n-1; i++) + { + w = i; + y = y+1.0/w; + } + y = y-0.57721566490153286061; + } + else + { + s = x; + w = 0.0; + while(ae_fp_less(s,10.0)) + { + w = w+1.0/s; + s = s+1.0; + } + if( ae_fp_less(s,1.0E17) ) + { + z = 1.0/(s*s); + polv = 8.33333333333333333333E-2; + polv = polv*z-2.10927960927960927961E-2; + polv = polv*z+7.57575757575757575758E-3; + polv = polv*z-4.16666666666666666667E-3; + polv = polv*z+3.96825396825396825397E-3; + polv = polv*z-8.33333333333333333333E-3; + polv = polv*z+8.33333333333333333333E-2; + y = z*polv; + } + else + { + y = 0.0; + } + y = ae_log(s, _state)-0.5/s-y-w; + } + if( negative!=0 ) + { + y = y-nz; + } + result = y; + return result; +} + + + + +/************************************************************************* +Student's t distribution + +Computes the integral from minus infinity to t of the Student +t distribution with integer k > 0 degrees of freedom: + + t + - + | | + - | 2 -(k+1)/2 + | ( (k+1)/2 ) | ( x ) + ---------------------- | ( 1 + --- ) dx + - | ( k ) + sqrt( k pi ) | ( k/2 ) | + | | + - + -inf. + +Relation to incomplete beta integral: + + 1 - stdtr(k,t) = 0.5 * incbet( k/2, 1/2, z ) +where + z = k/(k + t**2). + +For t < -2, this is the method of computation. For higher t, +a direct method is derived from integration by parts. +Since the function is symmetric about t=0, the area under the +right tail of the density is found by calling the function +with -t instead of t. + +ACCURACY: + +Tested at random 1 <= k <= 25. The "domain" refers to t. + Relative error: +arithmetic domain # trials peak rms + IEEE -100,-2 50000 5.9e-15 1.4e-15 + IEEE -2,100 500000 2.7e-15 4.9e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double studenttdistribution(ae_int_t k, double t, ae_state *_state) +{ + double x; + double rk; + double z; + double f; + double tz; + double p; + double xsqk; + ae_int_t j; + double result; + + + ae_assert(k>0, "Domain error in StudentTDistribution", _state); + if( ae_fp_eq(t,0) ) + { + result = 0.5; + return result; + } + if( ae_fp_less(t,-2.0) ) + { + rk = k; + z = rk/(rk+t*t); + result = 0.5*incompletebeta(0.5*rk, 0.5, z, _state); + return result; + } + if( ae_fp_less(t,0) ) + { + x = -t; + } + else + { + x = t; + } + rk = k; + z = 1.0+x*x/rk; + if( k%2!=0 ) + { + xsqk = x/ae_sqrt(rk, _state); + p = ae_atan(xsqk, _state); + if( k>1 ) + { + f = 1.0; + tz = 1.0; + j = 3; + while(j<=k-2&&ae_fp_greater(tz/f,ae_machineepsilon)) + { + tz = tz*((j-1)/(z*j)); + f = f+tz; + j = j+2; + } + p = p+f*xsqk/z; + } + p = p*2.0/ae_pi; + } + else + { + f = 1.0; + tz = 1.0; + j = 2; + while(j<=k-2&&ae_fp_greater(tz/f,ae_machineepsilon)) + { + tz = tz*((j-1)/(z*j)); + f = f+tz; + j = j+2; + } + p = f*x/ae_sqrt(z*rk, _state); + } + if( ae_fp_less(t,0) ) + { + p = -p; + } + result = 0.5+0.5*p; + return result; +} + + +/************************************************************************* +Functional inverse of Student's t distribution + +Given probability p, finds the argument t such that stdtr(k,t) +is equal to p. + +ACCURACY: + +Tested at random 1 <= k <= 100. The "domain" refers to p: + Relative error: +arithmetic domain # trials peak rms + IEEE .001,.999 25000 5.7e-15 8.0e-16 + IEEE 10^-6,.001 25000 2.0e-12 2.9e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invstudenttdistribution(ae_int_t k, double p, ae_state *_state) +{ + double t; + double rk; + double z; + ae_int_t rflg; + double result; + + + ae_assert((k>0&&ae_fp_greater(p,0))&&ae_fp_less(p,1), "Domain error in InvStudentTDistribution", _state); + rk = k; + if( ae_fp_greater(p,0.25)&&ae_fp_less(p,0.75) ) + { + if( ae_fp_eq(p,0.5) ) + { + result = 0; + return result; + } + z = 1.0-2.0*p; + z = invincompletebeta(0.5, 0.5*rk, ae_fabs(z, _state), _state); + t = ae_sqrt(rk*z/(1.0-z), _state); + if( ae_fp_less(p,0.5) ) + { + t = -t; + } + result = t; + return result; + } + rflg = -1; + if( ae_fp_greater_eq(p,0.5) ) + { + p = 1.0-p; + rflg = 1; + } + z = invincompletebeta(0.5*rk, 0.5, 2.0*p, _state); + if( ae_fp_less(ae_maxrealnumber*z,rk) ) + { + result = rflg*ae_maxrealnumber; + return result; + } + t = ae_sqrt(rk/z-rk, _state); + result = rflg*t; + return result; +} + + + + +/************************************************************************* +Sine and cosine integrals + +Evaluates the integrals + + x + - + | cos t - 1 + Ci(x) = eul + ln x + | --------- dt, + | t + - + 0 + x + - + | sin t + Si(x) = | ----- dt + | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are approximated by rational functions. +For x > 8 auxiliary functions f(x) and g(x) are employed +such that + +Ci(x) = f(x) sin(x) - g(x) cos(x) +Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x) + + +ACCURACY: + Test interval = [0,50]. +Absolute error, except relative when > 1: +arithmetic function # trials peak rms + IEEE Si 30000 4.4e-16 7.3e-17 + IEEE Ci 30000 6.9e-16 5.1e-17 + +Cephes Math Library Release 2.1: January, 1989 +Copyright 1984, 1987, 1989 by Stephen L. Moshier +*************************************************************************/ +void sinecosineintegrals(double x, + double* si, + double* ci, + ae_state *_state) +{ + double z; + double c; + double s; + double f; + double g; + ae_int_t sg; + double sn; + double sd; + double cn; + double cd; + double fn; + double fd; + double gn; + double gd; + + *si = 0; + *ci = 0; + + if( ae_fp_less(x,0) ) + { + sg = -1; + x = -x; + } + else + { + sg = 0; + } + if( ae_fp_eq(x,0) ) + { + *si = 0; + *ci = -ae_maxrealnumber; + return; + } + if( ae_fp_greater(x,1.0E9) ) + { + *si = 1.570796326794896619-ae_cos(x, _state)/x; + *ci = ae_sin(x, _state)/x; + return; + } + if( ae_fp_less_eq(x,4) ) + { + z = x*x; + sn = -8.39167827910303881427E-11; + sn = sn*z+4.62591714427012837309E-8; + sn = sn*z-9.75759303843632795789E-6; + sn = sn*z+9.76945438170435310816E-4; + sn = sn*z-4.13470316229406538752E-2; + sn = sn*z+1.00000000000000000302E0; + sd = 2.03269266195951942049E-12; + sd = sd*z+1.27997891179943299903E-9; + sd = sd*z+4.41827842801218905784E-7; + sd = sd*z+9.96412122043875552487E-5; + sd = sd*z+1.42085239326149893930E-2; + sd = sd*z+9.99999999999999996984E-1; + s = x*sn/sd; + cn = 2.02524002389102268789E-11; + cn = cn*z-1.35249504915790756375E-8; + cn = cn*z+3.59325051419993077021E-6; + cn = cn*z-4.74007206873407909465E-4; + cn = cn*z+2.89159652607555242092E-2; + cn = cn*z-1.00000000000000000080E0; + cd = 4.07746040061880559506E-12; + cd = cd*z+3.06780997581887812692E-9; + cd = cd*z+1.23210355685883423679E-6; + cd = cd*z+3.17442024775032769882E-4; + cd = cd*z+5.10028056236446052392E-2; + cd = cd*z+4.00000000000000000080E0; + c = z*cn/cd; + if( sg!=0 ) + { + s = -s; + } + *si = s; + *ci = 0.57721566490153286061+ae_log(x, _state)+c; + return; + } + s = ae_sin(x, _state); + c = ae_cos(x, _state); + z = 1.0/(x*x); + if( ae_fp_less(x,8) ) + { + fn = 4.23612862892216586994E0; + fn = fn*z+5.45937717161812843388E0; + fn = fn*z+1.62083287701538329132E0; + fn = fn*z+1.67006611831323023771E-1; + fn = fn*z+6.81020132472518137426E-3; + fn = fn*z+1.08936580650328664411E-4; + fn = fn*z+5.48900223421373614008E-7; + fd = 1.00000000000000000000E0; + fd = fd*z+8.16496634205391016773E0; + fd = fd*z+7.30828822505564552187E0; + fd = fd*z+1.86792257950184183883E0; + fd = fd*z+1.78792052963149907262E-1; + fd = fd*z+7.01710668322789753610E-3; + fd = fd*z+1.10034357153915731354E-4; + fd = fd*z+5.48900252756255700982E-7; + f = fn/(x*fd); + gn = 8.71001698973114191777E-2; + gn = gn*z+6.11379109952219284151E-1; + gn = gn*z+3.97180296392337498885E-1; + gn = gn*z+7.48527737628469092119E-2; + gn = gn*z+5.38868681462177273157E-3; + gn = gn*z+1.61999794598934024525E-4; + gn = gn*z+1.97963874140963632189E-6; + gn = gn*z+7.82579040744090311069E-9; + gd = 1.00000000000000000000E0; + gd = gd*z+1.64402202413355338886E0; + gd = gd*z+6.66296701268987968381E-1; + gd = gd*z+9.88771761277688796203E-2; + gd = gd*z+6.22396345441768420760E-3; + gd = gd*z+1.73221081474177119497E-4; + gd = gd*z+2.02659182086343991969E-6; + gd = gd*z+7.82579218933534490868E-9; + g = z*gn/gd; + } + else + { + fn = 4.55880873470465315206E-1; + fn = fn*z+7.13715274100146711374E-1; + fn = fn*z+1.60300158222319456320E-1; + fn = fn*z+1.16064229408124407915E-2; + fn = fn*z+3.49556442447859055605E-4; + fn = fn*z+4.86215430826454749482E-6; + fn = fn*z+3.20092790091004902806E-8; + fn = fn*z+9.41779576128512936592E-11; + fn = fn*z+9.70507110881952024631E-14; + fd = 1.00000000000000000000E0; + fd = fd*z+9.17463611873684053703E-1; + fd = fd*z+1.78685545332074536321E-1; + fd = fd*z+1.22253594771971293032E-2; + fd = fd*z+3.58696481881851580297E-4; + fd = fd*z+4.92435064317881464393E-6; + fd = fd*z+3.21956939101046018377E-8; + fd = fd*z+9.43720590350276732376E-11; + fd = fd*z+9.70507110881952025725E-14; + f = fn/(x*fd); + gn = 6.97359953443276214934E-1; + gn = gn*z+3.30410979305632063225E-1; + gn = gn*z+3.84878767649974295920E-2; + gn = gn*z+1.71718239052347903558E-3; + gn = gn*z+3.48941165502279436777E-5; + gn = gn*z+3.47131167084116673800E-7; + gn = gn*z+1.70404452782044526189E-9; + gn = gn*z+3.85945925430276600453E-12; + gn = gn*z+3.14040098946363334640E-15; + gd = 1.00000000000000000000E0; + gd = gd*z+1.68548898811011640017E0; + gd = gd*z+4.87852258695304967486E-1; + gd = gd*z+4.67913194259625806320E-2; + gd = gd*z+1.90284426674399523638E-3; + gd = gd*z+3.68475504442561108162E-5; + gd = gd*z+3.57043223443740838771E-7; + gd = gd*z+1.72693748966316146736E-9; + gd = gd*z+3.87830166023954706752E-12; + gd = gd*z+3.14040098946363335242E-15; + g = z*gn/gd; + } + *si = 1.570796326794896619-f*c-g*s; + if( sg!=0 ) + { + *si = -*si; + } + *ci = f*s-g*c; +} + + +/************************************************************************* +Hyperbolic sine and cosine integrals + +Approximates the integrals + + x + - + | | cosh t - 1 + Chi(x) = eul + ln x + | ----------- dt, + | | t + - + 0 + + x + - + | | sinh t + Shi(x) = | ------ dt + | | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are evaluated by power series for x < 8 +and by Chebyshev expansions for x between 8 and 88. +For large x, both functions approach exp(x)/2x. +Arguments greater than 88 in magnitude return MAXNUM. + + +ACCURACY: + +Test interval 0 to 88. + Relative error: +arithmetic function # trials peak rms + IEEE Shi 30000 6.9e-16 1.6e-16 + Absolute error, except relative when |Chi| > 1: + IEEE Chi 30000 8.4e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void hyperbolicsinecosineintegrals(double x, + double* shi, + double* chi, + ae_state *_state) +{ + double k; + double z; + double c; + double s; + double a; + ae_int_t sg; + double b0; + double b1; + double b2; + + *shi = 0; + *chi = 0; + + if( ae_fp_less(x,0) ) + { + sg = -1; + x = -x; + } + else + { + sg = 0; + } + if( ae_fp_eq(x,0) ) + { + *shi = 0; + *chi = -ae_maxrealnumber; + return; + } + if( ae_fp_less(x,8.0) ) + { + z = x*x; + a = 1.0; + s = 1.0; + c = 0.0; + k = 2.0; + do + { + a = a*z/k; + c = c+a/k; + k = k+1.0; + a = a/k; + s = s+a/k; + k = k+1.0; + } + while(ae_fp_greater_eq(ae_fabs(a/s, _state),ae_machineepsilon)); + s = s*x; + } + else + { + if( ae_fp_less(x,18.0) ) + { + a = (576.0/x-52.0)/10.0; + k = ae_exp(x, _state)/x; + b0 = 1.83889230173399459482E-17; + b1 = 0.0; + trigintegrals_chebiterationshichi(a, -9.55485532279655569575E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.04326105980879882648E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.09896949074905343022E-15, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.31313534344092599234E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 5.93976226264314278932E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.47197010497749154755E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.40059764613117131000E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 9.49044626224223543299E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.61596181145435454033E-11, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.77899784436430310321E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.35455469767246947469E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.03257121792819495123E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.56699611114982536845E-8, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.44818877384267342057E-7, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 7.82018215184051295296E-7, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -5.39919118403805073710E-6, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.12458202168959833422E-5, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 8.90136741950727517826E-5, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.02558474743846862168E-3, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.96064440855633256972E-2, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.11847751047257036625E0, &b0, &b1, &b2, _state); + s = k*0.5*(b0-b2); + b0 = -8.12435385225864036372E-18; + b1 = 0.0; + trigintegrals_chebiterationshichi(a, 2.17586413290339214377E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 5.22624394924072204667E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -9.48812110591690559363E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 5.35546311647465209166E-15, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.21009970113732918701E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -6.00865178553447437951E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 7.16339649156028587775E-13, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -2.93496072607599856104E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.40359438136491256904E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 8.76302288609054966081E-11, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -4.40092476213282340617E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.87992075640569295479E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.31458150989474594064E-8, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -4.75513930924765465590E-8, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -2.21775018801848880741E-7, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.94635531373272490962E-6, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 4.33505889257316408893E-6, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -6.13387001076494349496E-5, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.13085477492997465138E-4, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 4.97164789823116062801E-4, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.64347496031374526641E-2, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.11446150876699213025E0, &b0, &b1, &b2, _state); + c = k*0.5*(b0-b2); + } + else + { + if( ae_fp_less_eq(x,88.0) ) + { + a = (6336.0/x-212.0)/70.0; + k = ae_exp(x, _state)/x; + b0 = -1.05311574154850938805E-17; + b1 = 0.0; + trigintegrals_chebiterationshichi(a, 2.62446095596355225821E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 8.82090135625368160657E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.38459811878103047136E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -8.30608026366935789136E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 3.93397875437050071776E-15, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.01765565969729044505E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -4.21128170307640802703E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.60818204519802480035E-13, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 3.34714954175994481761E-13, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.72600352129153073807E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.66894954752839083608E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.49278141024730899554E-11, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.58580661666482709598E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.79289437183355633342E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.76281629144264523277E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.69050228879421288846E-8, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.25391771228487041649E-7, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.16229947068677338732E-6, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.61038260117376323993E-5, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 3.49810375601053973070E-4, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.28478065259647610779E-2, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.03665722588798326712E0, &b0, &b1, &b2, _state); + s = k*0.5*(b0-b2); + b0 = 8.06913408255155572081E-18; + b1 = 0.0; + trigintegrals_chebiterationshichi(a, -2.08074168180148170312E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -5.98111329658272336816E-17, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.68533951085945765591E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 4.52313941698904694774E-16, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.10734917335299464535E-15, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -4.42823207332531972288E-15, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 3.49639695410806959872E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 6.63406731718911586609E-14, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.71902448093119218395E-13, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.27135418132338309016E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.74851141935315395333E-12, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.33781843985453438400E-11, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 2.71436006377612442764E-11, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -2.56600180000355990529E-10, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -1.61021375163803438552E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -4.72543064876271773512E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, -3.00095178028681682282E-9, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 7.79387474390914922337E-8, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.06942765566401507066E-6, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.59503164802313196374E-5, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 3.49592575153777996871E-4, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.28475387530065247392E-2, &b0, &b1, &b2, _state); + trigintegrals_chebiterationshichi(a, 1.03665693917934275131E0, &b0, &b1, &b2, _state); + c = k*0.5*(b0-b2); + } + else + { + if( sg!=0 ) + { + *shi = -ae_maxrealnumber; + } + else + { + *shi = ae_maxrealnumber; + } + *chi = ae_maxrealnumber; + return; + } + } + } + if( sg!=0 ) + { + s = -s; + } + *shi = s; + *chi = 0.57721566490153286061+ae_log(x, _state)+c; +} + + +static void trigintegrals_chebiterationshichi(double x, + double c, + double* b0, + double* b1, + double* b2, + ae_state *_state) +{ + + + *b2 = *b1; + *b1 = *b0; + *b0 = x*(*b1)-(*b2)+c; +} + + + +} + diff --git a/src/inc/alglib/specialfunctions.h b/src/inc/alglib/specialfunctions.h new file mode 100644 index 0000000..167aed3 --- /dev/null +++ b/src/inc/alglib/specialfunctions.h @@ -0,0 +1,1976 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _specialfunctions_pkg_h +#define _specialfunctions_pkg_h +#include "ap.h" +#include "alglibinternal.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Gamma function + +Input parameters: + X - argument + +Domain: + 0 < X < 171.6 + -170 < X < 0, X is not an integer. + +Relative error: + arithmetic domain # trials peak rms + IEEE -170,-33 20000 2.3e-15 3.3e-16 + IEEE -33, 33 20000 9.4e-16 2.2e-16 + IEEE 33, 171.6 20000 2.3e-15 3.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Original copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double gammafunction(const double x); + + +/************************************************************************* +Natural logarithm of gamma function + +Input parameters: + X - argument + +Result: + logarithm of the absolute value of the Gamma(X). + +Output parameters: + SgnGam - sign(Gamma(X)) + +Domain: + 0 < X < 2.55e305 + -2.55e305 < X < 0, X is not an integer. + +ACCURACY: +arithmetic domain # trials peak rms + IEEE 0, 3 28000 5.4e-16 1.1e-16 + IEEE 2.718, 2.556e305 40000 3.5e-16 8.3e-17 +The error criterion was relative when the function magnitude +was greater than one but absolute when it was less than one. + +The following test used the relative error criterion, though +at certain points the relative error could be much higher than +indicated. + IEEE -200, -4 10000 4.8e-16 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +Translated to AlgoPascal by Bochkanov Sergey (2005, 2006, 2007). +*************************************************************************/ +double lngamma(const double x, double &sgngam); + +/************************************************************************* +Error function + +The integral is + + x + - + 2 | | 2 + erf(x) = -------- | exp( - t ) dt. + sqrt(pi) | | + - + 0 + +For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise +erf(x) = 1 - erfc(x). + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 3.7e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunction(const double x); + + +/************************************************************************* +Complementary error function + + 1 - erf(x) = + + inf. + - + 2 | | 2 + erfc(x) = -------- | exp( - t ) dt + sqrt(pi) | | + - + x + + +For small x, erfc(x) = 1 - erf(x); otherwise rational +approximations are computed. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,26.6417 30000 5.7e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double errorfunctionc(const double x); + + +/************************************************************************* +Normal distribution function + +Returns the area under the Gaussian probability density +function, integrated from minus infinity to x: + + x + - + 1 | | 2 + ndtr(x) = --------- | exp( - t /2 ) dt + sqrt(2pi) | | + - + -inf. + + = ( 1 + erf(z) ) / 2 + = erfc(z) / 2 + +where z = x/sqrt(2). Computation is via the functions +erf and erfc. + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE -13,0 30000 3.4e-14 6.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double normaldistribution(const double x); + + +/************************************************************************* +Inverse of the error function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double inverf(const double e); + + +/************************************************************************* +Inverse of Normal distribution function + +Returns the argument, x, for which the area under the +Gaussian probability density function (integrated from +minus infinity to x) is equal to y. + + +For small arguments 0 < y < exp(-2), the program computes +z = sqrt( -2.0 * log(y) ); then the approximation is +x = z - log(z)/z - (1/z) P(1/z) / Q(1/z). +There are two rational functions P/Q, one for 0 < y < exp(-32) +and the other for y up to exp(-2). For larger arguments, +w = y - 0.5, and x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0.125, 1 20000 7.2e-16 1.3e-16 + IEEE 3e-308, 0.135 50000 4.6e-16 9.8e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double invnormaldistribution(const double y0); + +/************************************************************************* +Incomplete gamma integral + +The function is defined by + + x + - + 1 | | -t a-1 + igam(a,x) = ----- | e t dt. + - | | + | (a) - + 0 + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 200000 3.6e-14 2.9e-15 + IEEE 0,100 300000 9.9e-14 1.5e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegamma(const double a, const double x); + + +/************************************************************************* +Complemented incomplete gamma integral + +The function is defined by + + + igamc(a,x) = 1 - igam(a,x) + + inf. + - + 1 | | -t a-1 + = ----- | e t dt. + - | | + | (a) - + x + + +In this implementation both arguments must be positive. +The integral is evaluated by either a power series or +continued fraction expansion, depending on the relative +values of a and x. + +ACCURACY: + +Tested at random a, x. + a x Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,100 200000 1.9e-14 1.7e-15 + IEEE 0.01,0.5 0,100 200000 1.4e-13 1.6e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletegammac(const double a, const double x); + + +/************************************************************************* +Inverse of complemented imcomplete gamma integral + +Given p, the function finds x such that + + igamc( a, x ) = p. + +Starting with the approximate value + + 3 + x = a t + + where + + t = 1 - d - ndtri(p) sqrt(d) + +and + + d = 1/9a, + +the routine performs up to 10 Newton iterations to find the +root of igamc(a,x) - p = 0. + +ACCURACY: + +Tested at random a, p in the intervals indicated. + + a p Relative error: +arithmetic domain domain # trials peak rms + IEEE 0.5,100 0,0.5 100000 1.0e-14 1.7e-15 + IEEE 0.01,0.5 0,0.5 100000 9.0e-14 3.4e-15 + IEEE 0.5,10000 0,0.5 20000 2.3e-13 3.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletegammac(const double a, const double y0); + +/************************************************************************* +Airy function + +Solution of the differential equation + +y"(x) = xy. + +The function returns the two independent solutions Ai, Bi +and their first derivatives Ai'(x), Bi'(x). + +Evaluation is by power series summation for small x, +by rational minimax approximations for large x. + + + +ACCURACY: +Error criterion is absolute when function <= 1, relative +when function > 1, except * denotes relative error criterion. +For large negative x, the absolute error increases as x^1.5. +For large positive x, the relative error increases as x^1.5. + +Arithmetic domain function # trials peak rms +IEEE -10, 0 Ai 10000 1.6e-15 2.7e-16 +IEEE 0, 10 Ai 10000 2.3e-14* 1.8e-15* +IEEE -10, 0 Ai' 10000 4.6e-15 7.6e-16 +IEEE 0, 10 Ai' 10000 1.8e-14* 1.5e-15* +IEEE -10, 10 Bi 30000 4.2e-15 5.3e-16 +IEEE -10, 10 Bi' 30000 4.9e-15 7.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void airy(const double x, double &ai, double &aip, double &bi, double &bip); + +/************************************************************************* +Bessel function of order zero + +Returns Bessel function of order zero of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval the following rational +approximation is used: + + + 2 2 +(w - r ) (w - r ) P (w) / Q (w) + 1 2 3 8 + + 2 +where w = x and the two r's are zeros of the function. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 60000 4.2e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj0(const double x); + + +/************************************************************************* +Bessel function of order one + +Returns Bessel function of order one of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 24 term Chebyshev +expansion is used. In the second, the asymptotic +trigonometric representation is employed using two +rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 2.6e-16 1.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselj1(const double x); + + +/************************************************************************* +Bessel function of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The ratio of jn(x) to j0(x) is computed by backward +recurrence. First the ratio jn/jn-1 is found by a +continued fraction expansion. Then the recurrence +relating successive orders is applied until j0 or j1 is +reached. + +If n = 0 or 1 the routine for j0 or j1 is called +directly. + +ACCURACY: + + Absolute error: +arithmetic range # trials peak rms + IEEE 0, 30 5000 4.4e-16 7.9e-17 + + +Not suitable for large n or x. Use jv() (fractional order) instead. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseljn(const ae_int_t n, const double x); + + +/************************************************************************* +Bessel function of the second kind, order zero + +Returns Bessel function of the second kind, of order +zero, of the argument. + +The domain is divided into the intervals [0, 5] and +(5, infinity). In the first interval a rational approximation +R(x) is employed to compute + y0(x) = R(x) + 2 * log(x) * j0(x) / PI. +Thus a call to j0() is required. + +In the second interval, the Hankel asymptotic expansion +is employed with two rational functions of degree 6/6 +and 7/7. + + + +ACCURACY: + + Absolute error, when y0(x) < 1; else relative error: + +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.3e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely0(const double x); + + +/************************************************************************* +Bessel function of second kind of order one + +Returns Bessel function of the second kind of order one +of the argument. + +The domain is divided into the intervals [0, 8] and +(8, infinity). In the first interval a 25 term Chebyshev +expansion is used, and a call to j1() is required. +In the second, the asymptotic trigonometric representation +is employed using two rational functions of degree 5/5. + +ACCURACY: + + Absolute error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.0e-15 1.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double bessely1(const double x); + + +/************************************************************************* +Bessel function of second kind of integer order + +Returns Bessel function of order n, where n is a +(possibly negative) integer. + +The function is evaluated by forward recurrence on +n, starting with values computed by the routines +y0() and y1(). + +If n = 0 or 1 the routine for y0 or y1 is called +directly. + +ACCURACY: + Absolute error, except relative + when y > 1: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 3.4e-15 4.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselyn(const ae_int_t n, const double x); + + +/************************************************************************* +Modified Bessel function of order zero + +Returns modified Bessel function of order zero of the +argument. + +The function is defined as i0(x) = j0( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 5.8e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli0(const double x); + + +/************************************************************************* +Modified Bessel function of order one + +Returns modified Bessel function of order one of the +argument. + +The function is defined as i1(x) = -i j1( ix ). + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.9e-15 2.1e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besseli1(const double x); + + +/************************************************************************* +Modified Bessel function, second kind, order zero + +Returns modified Bessel function of the second kind +of order zero of the argument. + +The range is partitioned into the two intervals [0,8] and +(8, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + +Tested at 2000 random points between 0 and 8. Peak absolute +error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15. + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk0(const double x); + + +/************************************************************************* +Modified Bessel function, second kind, order one + +Computes the modified Bessel function of the second kind +of order one of the argument. + +The range is partitioned into the two intervals [0,2] and +(2, infinity). Chebyshev polynomial expansions are employed +in each interval. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 30000 1.2e-15 1.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselk1(const double x); + + +/************************************************************************* +Modified Bessel function, second kind, integer order + +Returns modified Bessel function of the second kind +of order n of the argument. + +The range is partitioned into the two intervals [0,9.55] and +(9.55, infinity). An ascending power series is used in the +low range, and an asymptotic expansion in the high range. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 90000 1.8e-8 3.0e-10 + +Error is high only near the crossover point x = 9.55 +between the two expansions used. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier +*************************************************************************/ +double besselkn(const ae_int_t nn, const double x); + +/************************************************************************* +Beta function + + + - - + | (a) | (b) +beta( a, b ) = -----------. + - + | (a+b) + +For large arguments the logarithm of the function is +evaluated using lgam(), then exponentiated. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,30 30000 8.1e-14 1.1e-14 + +Cephes Math Library Release 2.0: April, 1987 +Copyright 1984, 1987 by Stephen L. Moshier +*************************************************************************/ +double beta(const double a, const double b); + +/************************************************************************* +Incomplete beta integral + +Returns incomplete beta integral of the arguments, evaluated +from zero to x. The function is defined as + + x + - - + | (a+b) | | a-1 b-1 + ----------- | t (1-t) dt. + - - | | + | (a) | (b) - + 0 + +The domain of definition is 0 <= x <= 1. In this +implementation a and b are restricted to positive values. +The integral from x to 1 may be obtained by the symmetry +relation + + 1 - incbet( a, b, x ) = incbet( b, a, 1-x ). + +The integral is evaluated by a continued fraction expansion +or, when b*x is small, by a power series. + +ACCURACY: + +Tested at uniformly distributed random points (a,b,x) with a and b +in "domain" and x between 0 and 1. + Relative error +arithmetic domain # trials peak rms + IEEE 0,5 10000 6.9e-15 4.5e-16 + IEEE 0,85 250000 2.2e-13 1.7e-14 + IEEE 0,1000 30000 5.3e-12 6.3e-13 + IEEE 0,10000 250000 9.3e-11 7.1e-12 + IEEE 0,100000 10000 8.7e-10 4.8e-11 +Outputs smaller than the IEEE gradual underflow threshold +were excluded from these statistics. + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompletebeta(const double a, const double b, const double x); + + +/************************************************************************* +Inverse of imcomplete beta integral + +Given y, the function finds x such that + + incbet( a, b, x ) = y . + +The routine performs interval halving or Newton iterations to find the +root of incbet(a,b,x) - y = 0. + + +ACCURACY: + + Relative error: + x a,b +arithmetic domain domain # trials peak rms + IEEE 0,1 .5,10000 50000 5.8e-12 1.3e-13 + IEEE 0,1 .25,100 100000 1.8e-13 3.9e-15 + IEEE 0,1 0,5 50000 1.1e-12 5.5e-15 +With a and b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 50000 5.8e-12 1.1e-13 + IEEE 0,1 .5,100 100000 1.7e-14 7.9e-16 +With a = .5, b constrained to half-integer or integer values: + IEEE 0,1 .5,10000 10000 8.3e-11 1.0e-11 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1996, 2000 by Stephen L. Moshier +*************************************************************************/ +double invincompletebeta(const double a, const double b, const double y); + +/************************************************************************* +Binomial distribution + +Returns the sum of the terms 0 through k of the Binomial +probability density: + + k + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=0 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p), with p between 0 and 1. + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 4.3e-15 2.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialdistribution(const ae_int_t k, const ae_int_t n, const double p); + + +/************************************************************************* +Complemented binomial distribution + +Returns the sum of the terms k+1 through n of the Binomial +probability density: + + n + -- ( n ) j n-j + > ( ) p (1-p) + -- ( j ) + j=k+1 + +The terms are not summed directly; instead the incomplete +beta integral is employed, according to the formula + +y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ). + +The arguments must be positive, with p ranging from 0 to 1. + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 6.7e-15 8.2e-16 + For p between 0 and .001: + IEEE 0,100 100000 1.5e-13 2.7e-15 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double binomialcdistribution(const ae_int_t k, const ae_int_t n, const double p); + + +/************************************************************************* +Inverse binomial distribution + +Finds the event probability p such that the sum of the +terms 0 through k of the Binomial probability density +is equal to the given cumulative probability y. + +This is accomplished using the inverse beta integral +function and the relation + +1 - p = incbi( n-k, k+1, y ). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between 0.001 and 1: + IEEE 0,100 100000 2.3e-14 6.4e-16 + IEEE 0,10000 100000 6.6e-12 1.2e-13 + For p between 10^-6 and 0.001: + IEEE 0,100 100000 2.0e-12 1.3e-14 + IEEE 0,10000 100000 1.5e-12 3.2e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invbinomialdistribution(const ae_int_t k, const ae_int_t n, const double y); + +/************************************************************************* +Calculation of the value of the Chebyshev polynomials of the +first and second kinds. + +Parameters: + r - polynomial kind, either 1 or 2. + n - degree, n>=0 + x - argument, -1 <= x <= 1 + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevcalculate(const ae_int_t r, const ae_int_t n, const double x); + + +/************************************************************************* +Summation of Chebyshev polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*T0(x) + c[1]*T1(x) + ... + c[N]*TN(x) +or + c[0]*U0(x) + c[1]*U1(x) + ... + c[N]*UN(x) +depending on the R. + +Parameters: + r - polynomial kind, either 1 or 2. + n - degree, n>=0 + x - argument + +Result: + the value of the Chebyshev polynomial at x +*************************************************************************/ +double chebyshevsum(const real_1d_array &c, const ae_int_t r, const ae_int_t n, const double x); + + +/************************************************************************* +Representation of Tn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void chebyshevcoefficients(const ae_int_t n, real_1d_array &c); + + +/************************************************************************* +Conversion of a series of Chebyshev polynomials to a power series. + +Represents A[0]*T0(x) + A[1]*T1(x) + ... + A[N]*Tn(x) as +B[0] + B[1]*X + ... + B[N]*X^N. + +Input parameters: + A - Chebyshev series coefficients + N - degree, N>=0 + +Output parameters + B - power series coefficients +*************************************************************************/ +void fromchebyshev(const real_1d_array &a, const ae_int_t n, real_1d_array &b); + +/************************************************************************* +Chi-square distribution + +Returns the area under the left hand tail (from 0 to x) +of the Chi square probability density function with +v degrees of freedom. + + + x + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + 0 + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igam( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquaredistribution(const double v, const double x); + + +/************************************************************************* +Complemented Chi-square distribution + +Returns the area under the right hand tail (from x to +infinity) of the Chi square probability density function +with v degrees of freedom: + + inf. + - + 1 | | v/2-1 -t/2 + P( x | v ) = ----------- | t e dt + v/2 - | | + 2 | (v/2) - + x + +where x is the Chi-square variable. + +The incomplete gamma integral is used, according to the +formula + +y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double chisquarecdistribution(const double v, const double x); + + +/************************************************************************* +Inverse of complemented Chi-square distribution + +Finds the Chi-square argument x such that the integral +from x to infinity of the Chi-square density is equal +to the given cumulative probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + x/2 = igami( df/2, y ); + +ACCURACY: + +See inverse incomplete gamma function + + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double invchisquaredistribution(const double v, const double y); + +/************************************************************************* +Dawson's Integral + +Approximates the integral + + x + - + 2 | | 2 + dawsn(x) = exp( -x ) | exp( t ) dt + | | + - + 0 + +Three different rational approximations are employed, for +the intervals 0 to 3.25; 3.25 to 6.25; and 6.25 up. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,10 10000 6.9e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double dawsonintegral(const double x); + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +using the approximation + + P(x) - log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegralk(const double m); + + +/************************************************************************* +Complete elliptic integral of the first kind + +Approximates the integral + + + + pi/2 + - + | | + | dt +K(m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +where m = 1 - m1, using the approximation + + P(x) - log x Q(x). + +The argument m1 is used rather than m so that the logarithmic +singularity at m = 1 will be shifted to the origin; this +preserves maximum accuracy. + +K(0) = pi/2. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,1 30000 2.5e-16 6.8e-17 + +Àëãîðèòì âçÿò èç áèáëèîòåêè Cephes +*************************************************************************/ +double ellipticintegralkhighprecision(const double m1); + + +/************************************************************************* +Incomplete elliptic integral of the first kind F(phi|m) + +Approximates the integral + + + + phi + - + | | + | dt +F(phi_\m) = | ------------------ + | 2 + | | sqrt( 1 - m sin t ) + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + + + + +ACCURACY: + +Tested at random points with m in [0, 1] and phi as indicated. + + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 200000 7.4e-16 1.0e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegralk(const double phi, const double m); + + +/************************************************************************* +Complete elliptic integral of the second kind + +Approximates the integral + + + pi/2 + - + | | 2 +E(m) = | sqrt( 1 - m sin t ) dt + | | + - + 0 + +using the approximation + + P(x) - x log x Q(x). + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 1 10000 2.1e-16 7.3e-17 + +Cephes Math Library, Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +double ellipticintegrale(const double m); + + +/************************************************************************* +Incomplete elliptic integral of the second kind + +Approximates the integral + + + phi + - + | | + | 2 +E(phi_\m) = | sqrt( 1 - m sin t ) dt + | + | | + - + 0 + +of amplitude phi and modulus m, using the arithmetic - +geometric mean algorithm. + +ACCURACY: + +Tested at random arguments with phi in [-10, 10] and m in +[0, 1]. + Relative error: +arithmetic domain # trials peak rms + IEEE -10,10 150000 3.3e-15 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1993, 2000 by Stephen L. Moshier +*************************************************************************/ +double incompleteellipticintegrale(const double phi, const double m); + +/************************************************************************* +Exponential integral Ei(x) + + x + - t + | | e + Ei(x) = -|- --- dt . + | | t + - + -inf + +Not defined for x <= 0. +See also expn.c. + + + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0,100 50000 8.6e-16 1.3e-16 + +Cephes Math Library Release 2.8: May, 1999 +Copyright 1999 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralei(const double x); + + +/************************************************************************* +Exponential integral En(x) + +Evaluates the exponential integral + + inf. + - + | | -xt + | e + E (x) = | ---- dt. + n | n + | | t + - + 1 + + +Both n and x must be nonnegative. + +The routine employs either a power series, a continued +fraction, or an asymptotic formula depending on the +relative values of n and x. + +ACCURACY: + + Relative error: +arithmetic domain # trials peak rms + IEEE 0, 30 10000 1.7e-15 3.6e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1985, 2000 by Stephen L. Moshier +*************************************************************************/ +double exponentialintegralen(const double x, const ae_int_t n); + +/************************************************************************* +F distribution + +Returns the area from zero to x under the F density +function (also known as Snedcor's density or the +variance ratio density). This is the density +of x = (u1/df1)/(u2/df2), where u1 and u2 are random +variables having Chi square distributions with df1 +and df2 degrees of freedom, respectively. +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ). + + +The arguments a and b are greater than zero, and x is +nonnegative. + +ACCURACY: + +Tested at random points (a,b,x). + + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 0,100 100000 9.8e-15 1.7e-15 + IEEE 1,5 0,100 100000 6.5e-15 3.5e-16 + IEEE 0,1 1,10000 100000 2.2e-11 3.3e-12 + IEEE 1,5 1,10000 100000 1.1e-11 1.7e-13 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fdistribution(const ae_int_t a, const ae_int_t b, const double x); + + +/************************************************************************* +Complemented F distribution + +Returns the area from x to infinity under the F density +function (also known as Snedcor's density or the +variance ratio density). + + + inf. + - + 1 | | a-1 b-1 +1-P(x) = ------ | t (1-t) dt + B(a,b) | | + - + x + + +The incomplete beta integral is used, according to the +formula + +P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ). + + +ACCURACY: + +Tested at random points (a,b,x) in the indicated intervals. + x a,b Relative error: +arithmetic domain domain # trials peak rms + IEEE 0,1 1,100 100000 3.7e-14 5.9e-16 + IEEE 1,5 1,100 100000 8.0e-15 1.6e-15 + IEEE 0,1 1,10000 100000 1.8e-11 3.5e-13 + IEEE 1,5 1,10000 100000 2.0e-11 3.0e-12 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double fcdistribution(const ae_int_t a, const ae_int_t b, const double x); + + +/************************************************************************* +Inverse of complemented F distribution + +Finds the F density argument x such that the integral +from x to infinity of the F density is equal to the +given probability p. + +This is accomplished using the inverse beta integral +function and the relations + + z = incbi( df2/2, df1/2, p ) + x = df2 (1-z) / (df1 z). + +Note: the following relations hold for the inverse of +the uncomplemented F distribution: + + z = incbi( df1/2, df2/2, p ) + x = df2 z / (df1 (1-z)). + +ACCURACY: + +Tested at random points (a,b,p). + + a,b Relative error: +arithmetic domain # trials peak rms + For p between .001 and 1: + IEEE 1,100 100000 8.3e-15 4.7e-16 + IEEE 1,10000 100000 2.1e-11 1.4e-13 + For p between 10^-6 and 10^-3: + IEEE 1,100 50000 1.3e-12 8.4e-15 + IEEE 1,10000 50000 3.0e-12 4.8e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invfdistribution(const ae_int_t a, const ae_int_t b, const double y); + +/************************************************************************* +Fresnel integral + +Evaluates the Fresnel integrals + + x + - + | | +C(x) = | cos(pi/2 t**2) dt, + | | + - + 0 + + x + - + | | +S(x) = | sin(pi/2 t**2) dt. + | | + - + 0 + + +The integrals are evaluated by a power series for x < 1. +For x >= 1 auxiliary functions f(x) and g(x) are employed +such that + +C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 ) +S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 ) + + + +ACCURACY: + + Relative error. + +Arithmetic function domain # trials peak rms + IEEE S(x) 0, 10 10000 2.0e-15 3.2e-16 + IEEE C(x) 0, 10 10000 1.8e-15 3.3e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier +*************************************************************************/ +void fresnelintegral(const double x, double &c, double &s); + +/************************************************************************* +Calculation of the value of the Hermite polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial Hn at x +*************************************************************************/ +double hermitecalculate(const ae_int_t n, const double x); + + +/************************************************************************* +Summation of Hermite polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*H0(x) + c[1]*H1(x) + ... + c[N]*HN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Hermite polynomial at x +*************************************************************************/ +double hermitesum(const real_1d_array &c, const ae_int_t n, const double x); + + +/************************************************************************* +Representation of Hn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void hermitecoefficients(const ae_int_t n, real_1d_array &c); + +/************************************************************************* +Jacobian Elliptic Functions + +Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m), +and dn(u|m) of parameter m between 0 and 1, and real +argument u. + +These functions are periodic, with quarter-period on the +real axis equal to the complete elliptic integral +ellpk(1.0-m). + +Relation to incomplete elliptic integral: +If u = ellik(phi,m), then sn(u|m) = sin(phi), +and cn(u|m) = cos(phi). Phi is called the amplitude of u. + +Computation is by means of the arithmetic-geometric mean +algorithm, except when m is within 1e-9 of 0 or 1. In the +latter case with m close to 1, the approximation applies +only for phi < pi/2. + +ACCURACY: + +Tested at random points with u between 0 and 10, m between +0 and 1. + + Absolute error (* = relative error): +arithmetic function # trials peak rms + IEEE phi 10000 9.2e-16* 1.4e-16* + IEEE sn 50000 4.1e-15 4.6e-16 + IEEE cn 40000 3.6e-15 4.4e-16 + IEEE dn 10000 1.3e-12 1.8e-14 + + Peak error observed in consistency check using addition +theorem for sn(u+v) was 4e-16 (absolute). Also tested by +the above relation to the incomplete elliptic integral. +Accuracy deteriorates when u is large. + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void jacobianellipticfunctions(const double u, const double m, double &sn, double &cn, double &dn, double &ph); + +/************************************************************************* +Calculation of the value of the Laguerre polynomial. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial Ln at x +*************************************************************************/ +double laguerrecalculate(const ae_int_t n, const double x); + + +/************************************************************************* +Summation of Laguerre polynomials using Clenshaw’s recurrence formula. + +This routine calculates c[0]*L0(x) + c[1]*L1(x) + ... + c[N]*LN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Laguerre polynomial at x +*************************************************************************/ +double laguerresum(const real_1d_array &c, const ae_int_t n, const double x); + + +/************************************************************************* +Representation of Ln as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void laguerrecoefficients(const ae_int_t n, real_1d_array &c); + +/************************************************************************* +Calculation of the value of the Legendre polynomial Pn. + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial Pn at x +*************************************************************************/ +double legendrecalculate(const ae_int_t n, const double x); + + +/************************************************************************* +Summation of Legendre polynomials using Clenshaw’s recurrence formula. + +This routine calculates + c[0]*P0(x) + c[1]*P1(x) + ... + c[N]*PN(x) + +Parameters: + n - degree, n>=0 + x - argument + +Result: + the value of the Legendre polynomial at x +*************************************************************************/ +double legendresum(const real_1d_array &c, const ae_int_t n, const double x); + + +/************************************************************************* +Representation of Pn as C[0] + C[1]*X + ... + C[N]*X^N + +Input parameters: + N - polynomial degree, n>=0 + +Output parameters: + C - coefficients +*************************************************************************/ +void legendrecoefficients(const ae_int_t n, real_1d_array &c); + +/************************************************************************* +Poisson distribution + +Returns the sum of the first k+1 terms of the Poisson +distribution: + + k j + -- -m m + > e -- + -- j! + j=0 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the relation + +y = pdtr( k, m ) = igamc( k+1, m ). + +The arguments must both be positive. +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissondistribution(const ae_int_t k, const double m); + + +/************************************************************************* +Complemented Poisson distribution + +Returns the sum of the terms k+1 to infinity of the Poisson +distribution: + + inf. j + -- -m m + > e -- + -- j! + j=k+1 + +The terms are not summed directly; instead the incomplete +gamma integral is employed, according to the formula + +y = pdtrc( k, m ) = igam( k+1, m ). + +The arguments must both be positive. + +ACCURACY: + +See incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double poissoncdistribution(const ae_int_t k, const double m); + + +/************************************************************************* +Inverse Poisson distribution + +Finds the Poisson variable x such that the integral +from 0 to x of the Poisson density is equal to the +given probability y. + +This is accomplished using the inverse gamma integral +function and the relation + + m = igami( k+1, y ). + +ACCURACY: + +See inverse incomplete gamma function + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invpoissondistribution(const ae_int_t k, const double y); + +/************************************************************************* +Psi (digamma) function + + d - + psi(x) = -- ln | (x) + dx + +is the logarithmic derivative of the gamma function. +For integer x, + n-1 + - +psi(n) = -EUL + > 1/k. + - + k=1 + +This formula is used for 0 < n <= 10. If x is negative, it +is transformed to a positive argument by the reflection +formula psi(1-x) = psi(x) + pi cot(pi x). +For general positive x, the argument is made greater than 10 +using the recurrence psi(x+1) = psi(x) + 1/x. +Then the following asymptotic expansion is applied: + + inf. B + - 2k +psi(x) = log(x) - 1/2x - > ------- + - 2k + k=1 2k x + +where the B2k are Bernoulli numbers. + +ACCURACY: + Relative error (except absolute when |psi| < 1): +arithmetic domain # trials peak rms + IEEE 0,30 30000 1.3e-15 1.4e-16 + IEEE -30,0 40000 1.5e-15 2.2e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier +*************************************************************************/ +double psi(const double x); + +/************************************************************************* +Student's t distribution + +Computes the integral from minus infinity to t of the Student +t distribution with integer k > 0 degrees of freedom: + + t + - + | | + - | 2 -(k+1)/2 + | ( (k+1)/2 ) | ( x ) + ---------------------- | ( 1 + --- ) dx + - | ( k ) + sqrt( k pi ) | ( k/2 ) | + | | + - + -inf. + +Relation to incomplete beta integral: + + 1 - stdtr(k,t) = 0.5 * incbet( k/2, 1/2, z ) +where + z = k/(k + t**2). + +For t < -2, this is the method of computation. For higher t, +a direct method is derived from integration by parts. +Since the function is symmetric about t=0, the area under the +right tail of the density is found by calling the function +with -t instead of t. + +ACCURACY: + +Tested at random 1 <= k <= 25. The "domain" refers to t. + Relative error: +arithmetic domain # trials peak rms + IEEE -100,-2 50000 5.9e-15 1.4e-15 + IEEE -2,100 500000 2.7e-15 4.9e-17 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double studenttdistribution(const ae_int_t k, const double t); + + +/************************************************************************* +Functional inverse of Student's t distribution + +Given probability p, finds the argument t such that stdtr(k,t) +is equal to p. + +ACCURACY: + +Tested at random 1 <= k <= 100. The "domain" refers to p: + Relative error: +arithmetic domain # trials peak rms + IEEE .001,.999 25000 5.7e-15 8.0e-16 + IEEE 10^-6,.001 25000 2.0e-12 2.9e-14 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 1995, 2000 by Stephen L. Moshier +*************************************************************************/ +double invstudenttdistribution(const ae_int_t k, const double p); + +/************************************************************************* +Sine and cosine integrals + +Evaluates the integrals + + x + - + | cos t - 1 + Ci(x) = eul + ln x + | --------- dt, + | t + - + 0 + x + - + | sin t + Si(x) = | ----- dt + | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are approximated by rational functions. +For x > 8 auxiliary functions f(x) and g(x) are employed +such that + +Ci(x) = f(x) sin(x) - g(x) cos(x) +Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x) + + +ACCURACY: + Test interval = [0,50]. +Absolute error, except relative when > 1: +arithmetic function # trials peak rms + IEEE Si 30000 4.4e-16 7.3e-17 + IEEE Ci 30000 6.9e-16 5.1e-17 + +Cephes Math Library Release 2.1: January, 1989 +Copyright 1984, 1987, 1989 by Stephen L. Moshier +*************************************************************************/ +void sinecosineintegrals(const double x, double &si, double &ci); + + +/************************************************************************* +Hyperbolic sine and cosine integrals + +Approximates the integrals + + x + - + | | cosh t - 1 + Chi(x) = eul + ln x + | ----------- dt, + | | t + - + 0 + + x + - + | | sinh t + Shi(x) = | ------ dt + | | t + - + 0 + +where eul = 0.57721566490153286061 is Euler's constant. +The integrals are evaluated by power series for x < 8 +and by Chebyshev expansions for x between 8 and 88. +For large x, both functions approach exp(x)/2x. +Arguments greater than 88 in magnitude return MAXNUM. + + +ACCURACY: + +Test interval 0 to 88. + Relative error: +arithmetic function # trials peak rms + IEEE Shi 30000 6.9e-16 1.6e-16 + Absolute error, except relative when |Chi| > 1: + IEEE Chi 30000 8.4e-16 1.4e-16 + +Cephes Math Library Release 2.8: June, 2000 +Copyright 1984, 1987, 2000 by Stephen L. Moshier +*************************************************************************/ +void hyperbolicsinecosineintegrals(const double x, double &shi, double &chi); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +double gammafunction(double x, ae_state *_state); +double lngamma(double x, double* sgngam, ae_state *_state); +double errorfunction(double x, ae_state *_state); +double errorfunctionc(double x, ae_state *_state); +double normaldistribution(double x, ae_state *_state); +double inverf(double e, ae_state *_state); +double invnormaldistribution(double y0, ae_state *_state); +double incompletegamma(double a, double x, ae_state *_state); +double incompletegammac(double a, double x, ae_state *_state); +double invincompletegammac(double a, double y0, ae_state *_state); +void airy(double x, + double* ai, + double* aip, + double* bi, + double* bip, + ae_state *_state); +double besselj0(double x, ae_state *_state); +double besselj1(double x, ae_state *_state); +double besseljn(ae_int_t n, double x, ae_state *_state); +double bessely0(double x, ae_state *_state); +double bessely1(double x, ae_state *_state); +double besselyn(ae_int_t n, double x, ae_state *_state); +double besseli0(double x, ae_state *_state); +double besseli1(double x, ae_state *_state); +double besselk0(double x, ae_state *_state); +double besselk1(double x, ae_state *_state); +double besselkn(ae_int_t nn, double x, ae_state *_state); +double beta(double a, double b, ae_state *_state); +double incompletebeta(double a, double b, double x, ae_state *_state); +double invincompletebeta(double a, double b, double y, ae_state *_state); +double binomialdistribution(ae_int_t k, + ae_int_t n, + double p, + ae_state *_state); +double binomialcdistribution(ae_int_t k, + ae_int_t n, + double p, + ae_state *_state); +double invbinomialdistribution(ae_int_t k, + ae_int_t n, + double y, + ae_state *_state); +double chebyshevcalculate(ae_int_t r, + ae_int_t n, + double x, + ae_state *_state); +double chebyshevsum(/* Real */ ae_vector* c, + ae_int_t r, + ae_int_t n, + double x, + ae_state *_state); +void chebyshevcoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +void fromchebyshev(/* Real */ ae_vector* a, + ae_int_t n, + /* Real */ ae_vector* b, + ae_state *_state); +double chisquaredistribution(double v, double x, ae_state *_state); +double chisquarecdistribution(double v, double x, ae_state *_state); +double invchisquaredistribution(double v, double y, ae_state *_state); +double dawsonintegral(double x, ae_state *_state); +double ellipticintegralk(double m, ae_state *_state); +double ellipticintegralkhighprecision(double m1, ae_state *_state); +double incompleteellipticintegralk(double phi, double m, ae_state *_state); +double ellipticintegrale(double m, ae_state *_state); +double incompleteellipticintegrale(double phi, double m, ae_state *_state); +double exponentialintegralei(double x, ae_state *_state); +double exponentialintegralen(double x, ae_int_t n, ae_state *_state); +double fdistribution(ae_int_t a, ae_int_t b, double x, ae_state *_state); +double fcdistribution(ae_int_t a, ae_int_t b, double x, ae_state *_state); +double invfdistribution(ae_int_t a, + ae_int_t b, + double y, + ae_state *_state); +void fresnelintegral(double x, double* c, double* s, ae_state *_state); +double hermitecalculate(ae_int_t n, double x, ae_state *_state); +double hermitesum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state); +void hermitecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +void jacobianellipticfunctions(double u, + double m, + double* sn, + double* cn, + double* dn, + double* ph, + ae_state *_state); +double laguerrecalculate(ae_int_t n, double x, ae_state *_state); +double laguerresum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state); +void laguerrecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +double legendrecalculate(ae_int_t n, double x, ae_state *_state); +double legendresum(/* Real */ ae_vector* c, + ae_int_t n, + double x, + ae_state *_state); +void legendrecoefficients(ae_int_t n, + /* Real */ ae_vector* c, + ae_state *_state); +double poissondistribution(ae_int_t k, double m, ae_state *_state); +double poissoncdistribution(ae_int_t k, double m, ae_state *_state); +double invpoissondistribution(ae_int_t k, double y, ae_state *_state); +double psi(double x, ae_state *_state); +double studenttdistribution(ae_int_t k, double t, ae_state *_state); +double invstudenttdistribution(ae_int_t k, double p, ae_state *_state); +void sinecosineintegrals(double x, + double* si, + double* ci, + ae_state *_state); +void hyperbolicsinecosineintegrals(double x, + double* shi, + double* chi, + ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/statistics.cpp b/src/inc/alglib/statistics.cpp new file mode 100644 index 0000000..4f0ef4e --- /dev/null +++ b/src/inc/alglib/statistics.cpp @@ -0,0 +1,19718 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#include "stdafx.h" +#include "statistics.h" + +// disable some irrelevant warnings +#if (AE_COMPILER==AE_MSVC) +#pragma warning(disable:4100) +#pragma warning(disable:4127) +#pragma warning(disable:4702) +#pragma warning(disable:4996) +#endif +using namespace std; + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Calculation of the distribution moments: mean, variance, skewness, kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +OUTPUT PARAMETERS + Mean - mean. + Variance- variance. + Skewness- skewness (if variance<>0; zero otherwise). + Kurtosis- kurtosis (if variance<>0; zero otherwise). + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemoments(const real_1d_array &x, const ae_int_t n, double &mean, double &variance, double &skewness, double &kurtosis) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplemoments(const_cast(x.c_ptr()), n, &mean, &variance, &skewness, &kurtosis, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the distribution moments: mean, variance, skewness, kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +OUTPUT PARAMETERS + Mean - mean. + Variance- variance. + Skewness- skewness (if variance<>0; zero otherwise). + Kurtosis- kurtosis (if variance<>0; zero otherwise). + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemoments(const real_1d_array &x, double &mean, double &variance, double &skewness, double &kurtosis) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplemoments(const_cast(x.c_ptr()), n, &mean, &variance, &skewness, &kurtosis, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the mean. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Mean' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplemean(const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplemean(const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the mean. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Mean' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplemean(const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplemean(const_cast(x.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the variance. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Variance' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplevariance(const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplevariance(const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the variance. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Variance' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplevariance(const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplevariance(const_cast(x.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the skewness. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Skewness' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double sampleskewness(const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::sampleskewness(const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the skewness. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Skewness' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double sampleskewness(const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::sampleskewness(const_cast(x.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Kurtosis' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplekurtosis(const real_1d_array &x, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplekurtosis(const_cast(x.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Calculation of the kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Kurtosis' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplekurtosis(const real_1d_array &x) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::samplekurtosis(const_cast(x.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +ADev + +Input parameters: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + ADev- ADev + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void sampleadev(const real_1d_array &x, const ae_int_t n, double &adev) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sampleadev(const_cast(x.c_ptr()), n, &adev, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +ADev + +Input parameters: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + ADev- ADev + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void sampleadev(const real_1d_array &x, double &adev) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::sampleadev(const_cast(x.c_ptr()), n, &adev, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Median calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + Median + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemedian(const real_1d_array &x, const ae_int_t n, double &median) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplemedian(const_cast(x.c_ptr()), n, &median, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Median calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + Median + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemedian(const real_1d_array &x, double &median) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplemedian(const_cast(x.c_ptr()), n, &median, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Percentile calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + P - percentile (0<=P<=1) + +Output parameters: + V - percentile + + -- ALGLIB -- + Copyright 01.03.2008 by Bochkanov Sergey +*************************************************************************/ +void samplepercentile(const real_1d_array &x, const ae_int_t n, const double p, double &v) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplepercentile(const_cast(x.c_ptr()), n, p, &v, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Percentile calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + P - percentile (0<=P<=1) + +Output parameters: + V - percentile + + -- ALGLIB -- + Copyright 01.03.2008 by Bochkanov Sergey +*************************************************************************/ +void samplepercentile(const real_1d_array &x, const double p, double &v) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::samplepercentile(const_cast(x.c_ptr()), n, p, &v, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +2-sample covariance + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + covariance (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double cov2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cov2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +2-sample covariance + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + covariance (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double cov2(const real_1d_array &x, const real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'cov2': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::cov2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Pearson product-moment correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorr2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::pearsoncorr2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Pearson product-moment correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorr2(const real_1d_array &x, const real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'pearsoncorr2': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::pearsoncorr2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Spearman's rank correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmancorr2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spearmancorr2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Spearman's rank correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmancorr2(const real_1d_array &x, const real_1d_array &y) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + if( (x.length()!=y.length())) + throw ap_error("Error while calling 'spearmancorr2': looks like one of arguments has wrong size"); + n = x.length(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spearmancorr2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::covm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_covm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_covm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::covm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_covm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_covm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pearsoncorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_pearsoncorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_pearsoncorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pearsoncorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_pearsoncorrm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_pearsoncorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spearmancorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_spearmancorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_spearmancorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spearmancorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_spearmancorrm(const real_2d_array &x, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m; + + n = x.rows(); + m = x.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_spearmancorrm(const_cast(x.c_ptr()), n, m, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cross-covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::covm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_covm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_covm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Cross-covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'covm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::covm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_covm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'covm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_covm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pearsoncorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_pearsoncorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson product-moment cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'pearsoncorrm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pearsoncorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'pearsoncorrm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_pearsoncorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spearmancorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_spearmancorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_spearmancorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'spearmancorrm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spearmancorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_spearmancorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t n; + ae_int_t m1; + ae_int_t m2; + if( (x.rows()!=y.rows())) + throw ap_error("Error while calling 'spearmancorrm2': looks like one of arguments has wrong size"); + n = x.rows(); + m1 = x.cols(); + m2 = y.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_spearmancorrm2(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, m1, m2, const_cast(c.c_ptr()), &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rankdata(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rankdata(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rankdata(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rankdata(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rankdata(real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + ae_int_t nfeatures; + + npoints = xy.rows(); + nfeatures = xy.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rankdata(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rankdata(real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + ae_int_t nfeatures; + + npoints = xy.rows(); + nfeatures = xy.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rankdata(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rankdatacentered(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rankdatacentered(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rankdatacentered(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rankdatacentered(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* + +*************************************************************************/ +void rankdatacentered(real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + ae_int_t nfeatures; + + npoints = xy.rows(); + nfeatures = xy.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::rankdatacentered(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + + +void smp_rankdatacentered(real_2d_array &xy) +{ + alglib_impl::ae_state _alglib_env_state; + ae_int_t npoints; + ae_int_t nfeatures; + + npoints = xy.rows(); + nfeatures = xy.cols(); + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::_pexec_rankdatacentered(const_cast(xy.c_ptr()), npoints, nfeatures, &_alglib_env_state); + + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete function, we recommend to use PearsonCorr2(). + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorrelation(const real_1d_array &x, const real_1d_array &y, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::pearsoncorrelation(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Obsolete function, we recommend to use SpearmanCorr2(). + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmanrankcorrelation(const real_1d_array &x, const real_1d_array &y, const ae_int_t n) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + double result = alglib_impl::spearmanrankcorrelation(const_cast(x.c_ptr()), const_cast(y.c_ptr()), n, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return *(reinterpret_cast(&result)); + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Pearson's correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5 + * normality of distributions of X and Y. + +Input parameters: + R - Pearson's correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrelationsignificance(const double r, const ae_int_t n, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::pearsoncorrelationsignificance(r, n, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Spearman's rank correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5. + +The test is non-parametric and doesn't require distributions X and Y to be +normal. + +Input parameters: + R - Spearman's rank correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void spearmanrankcorrelationsignificance(const double r, const ae_int_t n, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::spearmanrankcorrelationsignificance(r, n, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Jarque-Bera test + +This test checks hypotheses about the fact that a given sample X is a +sample of normal random variable. + +Requirements: + * the number of elements in the sample is not less than 5. + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +Accuracy of the approximation used (5<=N<=1951): + +p-value relative error (5<=N<=1951) +[1, 0.1] < 1% +[0.1, 0.01] < 2% +[0.01, 0.001] < 6% +[0.001, 0] wasn't measured + +For N>1951 accuracy wasn't measured but it shouldn't be sharply different +from table values. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void jarqueberatest(const real_1d_array &x, const ae_int_t n, double &p) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::jarqueberatest(const_cast(x.c_ptr()), n, &p, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Mann-Whitney U-test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions of the same shape and same median or whether +their medians are different. + +The following tests are performed: + * two-tailed test (null hypothesis - the medians are equal) + * left-tailed test (null hypothesis - the median of the first sample + is greater than or equal to the median of the second sample) + * right-tailed test (null hypothesis - the median of the first sample + is less than or equal to the median of the second sample). + +Requirements: + * the samples are independent + * X and Y are continuous distributions (or discrete distributions well- + approximating continuous distributions) + * distributions of X and Y have the same shape. The only possible + difference is their position (i.e. the value of the median) + * the number of elements in each sample is not less than 5 + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distributions to be normal. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. M>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with satisfactory accuracy in interval [0.0001, 1]. +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + +Relative precision of approximation of p-value: + +N M Max.err. Rms.err. +5..10 N..10 1.4e-02 6.0e-04 +5..10 N..100 2.2e-02 5.3e-06 +10..15 N..15 1.0e-02 3.2e-04 +10..15 N..100 1.0e-02 2.2e-05 +15..100 N..100 6.1e-03 2.7e-06 + +For N,M>100 accuracy checks weren't put into practice, but taking into +account characteristics of asymptotic approximation used, precision should +not be sharply different from the values for interval [5, 100]. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void mannwhitneyutest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::mannwhitneyutest(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Sign test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +While calculating p-values high-precision binomial distribution +approximation is used, so significance levels have about 15 exact digits. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplesigntest(const real_1d_array &x, const ae_int_t n, const double median, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::onesamplesigntest(const_cast(x.c_ptr()), n, median, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +One-sample t-test + +This test checks three hypotheses about the mean of the given sample. The +following tests are performed: + * two-tailed test (null hypothesis - the mean is equal to the given + value) + * left-tailed test (null hypothesis - the mean is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the mean is less than or equal + to the given value). + +The test is based on the assumption that a given sample has a normal +distribution and an unknown dispersion. If the distribution sharply +differs from normal, the test will work incorrectly. + +INPUT PARAMETERS: + X - sample. Array whose index goes from 0 to N-1. + N - size of sample, N>=0 + Mean - assumed value of the mean. + +OUTPUT PARAMETERS: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0, all p-values are set to 1.0 + * when variance of X[] is exactly zero, p-values are set + to 1.0 or 0.0, depending on difference between sample mean and + value of mean being tested. + + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest1(const real_1d_array &x, const ae_int_t n, const double mean, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::studentttest1(const_cast(x.c_ptr()), n, mean, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Two-sample pooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * dispersions are equal + * samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has exactly zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest2(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::studentttest2(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Two-sample unpooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * samples are independent. +Equality of variances is NOT required. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + * when only one sample has zero variance, test reduces to 1-sample + version. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void unequalvariancettest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::unequalvariancettest(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Two-sample F-test + +This test checks three hypotheses about dispersions of the given samples. +The following tests are performed: + * two-tailed test (null hypothesis - the dispersions are equal) + * left-tailed test (null hypothesis - the dispersion of the first + sample is greater than or equal to the dispersion of the second + sample). + * right-tailed test (null hypothesis - the dispersion of the first + sample is less than or equal to the dispersion of the second sample) + +The test is based on the following assumptions: + * the given samples have normal distributions + * the samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - sample size. + Y - sample 2. Array whose index goes from 0 to M-1. + M - sample size. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void ftest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::ftest(const_cast(x.c_ptr()), n, const_cast(y.c_ptr()), m, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +One-sample chi-square test + +This test checks three hypotheses about the dispersion of the given sample +The following tests are performed: + * two-tailed test (null hypothesis - the dispersion equals the given + number) + * left-tailed test (null hypothesis - the dispersion is greater than + or equal to the given number) + * right-tailed test (null hypothesis - dispersion is less than or + equal to the given number). + +Test is based on the following assumptions: + * the given sample has a normal distribution. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Variance - dispersion value to compare with. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplevariancetest(const real_1d_array &x, const ae_int_t n, const double variance, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::onesamplevariancetest(const_cast(x.c_ptr()), n, variance, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} + +/************************************************************************* +Wilcoxon signed-rank test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + * the distribution should be continuous and symmetric relative to its + median. + * number of distinct values in the X array should be greater than 4 + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with two decimal places in interval [0.0001, 1]. + +"Two decimal places" does not sound very impressive, but in practice the +relative error of less than 1% is enough to make a decision. + +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void wilcoxonsignedranktest(const real_1d_array &x, const ae_int_t n, const double e, double &bothtails, double &lefttail, double &righttail) +{ + alglib_impl::ae_state _alglib_env_state; + alglib_impl::ae_state_init(&_alglib_env_state); + try + { + alglib_impl::wilcoxonsignedranktest(const_cast(x.c_ptr()), n, e, &bothtails, &lefttail, &righttail, &_alglib_env_state); + alglib_impl::ae_state_clear(&_alglib_env_state); + return; + } + catch(alglib_impl::ae_error_type) + { + throw ap_error(_alglib_env_state.error_msg); + } +} +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS IMPLEMENTATION OF COMPUTATIONAL CORE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +static void basestat_rankdatarec(/* Real */ ae_matrix* xy, + ae_int_t i0, + ae_int_t i1, + ae_int_t nfeatures, + ae_bool iscentered, + ae_shared_pool* pool, + ae_int_t basecasecost, + ae_state *_state); +static void basestat_rankdatabasecase(/* Real */ ae_matrix* xy, + ae_int_t i0, + ae_int_t i1, + ae_int_t nfeatures, + ae_bool iscentered, + apbuffers* buf0, + apbuffers* buf1, + ae_state *_state); + + +static double correlationtests_spearmantail5(double s, ae_state *_state); +static double correlationtests_spearmantail6(double s, ae_state *_state); +static double correlationtests_spearmantail7(double s, ae_state *_state); +static double correlationtests_spearmantail8(double s, ae_state *_state); +static double correlationtests_spearmantail9(double s, ae_state *_state); +static double correlationtests_spearmantail(double t, + ae_int_t n, + ae_state *_state); + + +static void jarquebera_jarqueberastatistic(/* Real */ ae_vector* x, + ae_int_t n, + double* s, + ae_state *_state); +static double jarquebera_jarqueberaapprox(ae_int_t n, + double s, + ae_state *_state); +static double jarquebera_jbtbl5(double s, ae_state *_state); +static double jarquebera_jbtbl6(double s, ae_state *_state); +static double jarquebera_jbtbl7(double s, ae_state *_state); +static double jarquebera_jbtbl8(double s, ae_state *_state); +static double jarquebera_jbtbl9(double s, ae_state *_state); +static double jarquebera_jbtbl10(double s, ae_state *_state); +static double jarquebera_jbtbl11(double s, ae_state *_state); +static double jarquebera_jbtbl12(double s, ae_state *_state); +static double jarquebera_jbtbl13(double s, ae_state *_state); +static double jarquebera_jbtbl14(double s, ae_state *_state); +static double jarquebera_jbtbl15(double s, ae_state *_state); +static double jarquebera_jbtbl16(double s, ae_state *_state); +static double jarquebera_jbtbl17(double s, ae_state *_state); +static double jarquebera_jbtbl18(double s, ae_state *_state); +static double jarquebera_jbtbl19(double s, ae_state *_state); +static double jarquebera_jbtbl20(double s, ae_state *_state); +static double jarquebera_jbtbl30(double s, ae_state *_state); +static double jarquebera_jbtbl50(double s, ae_state *_state); +static double jarquebera_jbtbl65(double s, ae_state *_state); +static double jarquebera_jbtbl100(double s, ae_state *_state); +static double jarquebera_jbtbl130(double s, ae_state *_state); +static double jarquebera_jbtbl200(double s, ae_state *_state); +static double jarquebera_jbtbl301(double s, ae_state *_state); +static double jarquebera_jbtbl501(double s, ae_state *_state); +static double jarquebera_jbtbl701(double s, ae_state *_state); +static double jarquebera_jbtbl1401(double s, ae_state *_state); +static void jarquebera_jbcheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state); + + +static void mannwhitneyu_ucheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state); +static double mannwhitneyu_uninterpolate(double p1, + double p2, + double p3, + ae_int_t n, + ae_state *_state); +static double mannwhitneyu_usigma000(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma075(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma150(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma225(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma300(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma333(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma367(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_usigma400(ae_int_t n1, + ae_int_t n2, + ae_state *_state); +static double mannwhitneyu_utbln5n5(double s, ae_state *_state); +static double mannwhitneyu_utbln5n6(double s, ae_state *_state); +static double mannwhitneyu_utbln5n7(double s, ae_state *_state); +static double mannwhitneyu_utbln5n8(double s, ae_state *_state); +static double mannwhitneyu_utbln5n9(double s, ae_state *_state); +static double mannwhitneyu_utbln5n10(double s, ae_state *_state); +static double mannwhitneyu_utbln5n11(double s, ae_state *_state); +static double mannwhitneyu_utbln5n12(double s, ae_state *_state); +static double mannwhitneyu_utbln5n13(double s, ae_state *_state); +static double mannwhitneyu_utbln5n14(double s, ae_state *_state); +static double mannwhitneyu_utbln5n15(double s, ae_state *_state); +static double mannwhitneyu_utbln5n16(double s, ae_state *_state); +static double mannwhitneyu_utbln5n17(double s, ae_state *_state); +static double mannwhitneyu_utbln5n18(double s, ae_state *_state); +static double mannwhitneyu_utbln5n19(double s, ae_state *_state); +static double mannwhitneyu_utbln5n20(double s, ae_state *_state); +static double mannwhitneyu_utbln5n21(double s, ae_state *_state); +static double mannwhitneyu_utbln5n22(double s, ae_state *_state); +static double mannwhitneyu_utbln5n23(double s, ae_state *_state); +static double mannwhitneyu_utbln5n24(double s, ae_state *_state); +static double mannwhitneyu_utbln5n25(double s, ae_state *_state); +static double mannwhitneyu_utbln5n26(double s, ae_state *_state); +static double mannwhitneyu_utbln5n27(double s, ae_state *_state); +static double mannwhitneyu_utbln5n28(double s, ae_state *_state); +static double mannwhitneyu_utbln5n29(double s, ae_state *_state); +static double mannwhitneyu_utbln5n30(double s, ae_state *_state); +static double mannwhitneyu_utbln5n100(double s, ae_state *_state); +static double mannwhitneyu_utbln6n6(double s, ae_state *_state); +static double mannwhitneyu_utbln6n7(double s, ae_state *_state); +static double mannwhitneyu_utbln6n8(double s, ae_state *_state); +static double mannwhitneyu_utbln6n9(double s, ae_state *_state); +static double mannwhitneyu_utbln6n10(double s, ae_state *_state); +static double mannwhitneyu_utbln6n11(double s, ae_state *_state); +static double mannwhitneyu_utbln6n12(double s, ae_state *_state); +static double mannwhitneyu_utbln6n13(double s, ae_state *_state); +static double mannwhitneyu_utbln6n14(double s, ae_state *_state); +static double mannwhitneyu_utbln6n15(double s, ae_state *_state); +static double mannwhitneyu_utbln6n30(double s, ae_state *_state); +static double mannwhitneyu_utbln6n100(double s, ae_state *_state); +static double mannwhitneyu_utbln7n7(double s, ae_state *_state); +static double mannwhitneyu_utbln7n8(double s, ae_state *_state); +static double mannwhitneyu_utbln7n9(double s, ae_state *_state); +static double mannwhitneyu_utbln7n10(double s, ae_state *_state); +static double mannwhitneyu_utbln7n11(double s, ae_state *_state); +static double mannwhitneyu_utbln7n12(double s, ae_state *_state); +static double mannwhitneyu_utbln7n13(double s, ae_state *_state); +static double mannwhitneyu_utbln7n14(double s, ae_state *_state); +static double mannwhitneyu_utbln7n15(double s, ae_state *_state); +static double mannwhitneyu_utbln7n30(double s, ae_state *_state); +static double mannwhitneyu_utbln7n100(double s, ae_state *_state); +static double mannwhitneyu_utbln8n8(double s, ae_state *_state); +static double mannwhitneyu_utbln8n9(double s, ae_state *_state); +static double mannwhitneyu_utbln8n10(double s, ae_state *_state); +static double mannwhitneyu_utbln8n11(double s, ae_state *_state); +static double mannwhitneyu_utbln8n12(double s, ae_state *_state); +static double mannwhitneyu_utbln8n13(double s, ae_state *_state); +static double mannwhitneyu_utbln8n14(double s, ae_state *_state); +static double mannwhitneyu_utbln8n15(double s, ae_state *_state); +static double mannwhitneyu_utbln8n30(double s, ae_state *_state); +static double mannwhitneyu_utbln8n100(double s, ae_state *_state); +static double mannwhitneyu_utbln9n9(double s, ae_state *_state); +static double mannwhitneyu_utbln9n10(double s, ae_state *_state); +static double mannwhitneyu_utbln9n11(double s, ae_state *_state); +static double mannwhitneyu_utbln9n12(double s, ae_state *_state); +static double mannwhitneyu_utbln9n13(double s, ae_state *_state); +static double mannwhitneyu_utbln9n14(double s, ae_state *_state); +static double mannwhitneyu_utbln9n15(double s, ae_state *_state); +static double mannwhitneyu_utbln9n30(double s, ae_state *_state); +static double mannwhitneyu_utbln9n100(double s, ae_state *_state); +static double mannwhitneyu_utbln10n10(double s, ae_state *_state); +static double mannwhitneyu_utbln10n11(double s, ae_state *_state); +static double mannwhitneyu_utbln10n12(double s, ae_state *_state); +static double mannwhitneyu_utbln10n13(double s, ae_state *_state); +static double mannwhitneyu_utbln10n14(double s, ae_state *_state); +static double mannwhitneyu_utbln10n15(double s, ae_state *_state); +static double mannwhitneyu_utbln10n30(double s, ae_state *_state); +static double mannwhitneyu_utbln10n100(double s, ae_state *_state); +static double mannwhitneyu_utbln11n11(double s, ae_state *_state); +static double mannwhitneyu_utbln11n12(double s, ae_state *_state); +static double mannwhitneyu_utbln11n13(double s, ae_state *_state); +static double mannwhitneyu_utbln11n14(double s, ae_state *_state); +static double mannwhitneyu_utbln11n15(double s, ae_state *_state); +static double mannwhitneyu_utbln11n30(double s, ae_state *_state); +static double mannwhitneyu_utbln11n100(double s, ae_state *_state); +static double mannwhitneyu_utbln12n12(double s, ae_state *_state); +static double mannwhitneyu_utbln12n13(double s, ae_state *_state); +static double mannwhitneyu_utbln12n14(double s, ae_state *_state); +static double mannwhitneyu_utbln12n15(double s, ae_state *_state); +static double mannwhitneyu_utbln12n30(double s, ae_state *_state); +static double mannwhitneyu_utbln12n100(double s, ae_state *_state); +static double mannwhitneyu_utbln13n13(double s, ae_state *_state); +static double mannwhitneyu_utbln13n14(double s, ae_state *_state); +static double mannwhitneyu_utbln13n15(double s, ae_state *_state); +static double mannwhitneyu_utbln13n30(double s, ae_state *_state); +static double mannwhitneyu_utbln13n100(double s, ae_state *_state); +static double mannwhitneyu_utbln14n14(double s, ae_state *_state); +static double mannwhitneyu_utbln14n15(double s, ae_state *_state); +static double mannwhitneyu_utbln14n30(double s, ae_state *_state); +static double mannwhitneyu_utbln14n100(double s, ae_state *_state); +static double mannwhitneyu_usigma(double s, + ae_int_t n1, + ae_int_t n2, + ae_state *_state); + + + + + + + + +static void wsr_wcheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state); +static double wsr_w5(double s, ae_state *_state); +static double wsr_w6(double s, ae_state *_state); +static double wsr_w7(double s, ae_state *_state); +static double wsr_w8(double s, ae_state *_state); +static double wsr_w9(double s, ae_state *_state); +static double wsr_w10(double s, ae_state *_state); +static double wsr_w11(double s, ae_state *_state); +static double wsr_w12(double s, ae_state *_state); +static double wsr_w13(double s, ae_state *_state); +static double wsr_w14(double s, ae_state *_state); +static double wsr_w15(double s, ae_state *_state); +static double wsr_w16(double s, ae_state *_state); +static double wsr_w17(double s, ae_state *_state); +static double wsr_w18(double s, ae_state *_state); +static double wsr_w19(double s, ae_state *_state); +static double wsr_w20(double s, ae_state *_state); +static double wsr_w21(double s, ae_state *_state); +static double wsr_w22(double s, ae_state *_state); +static double wsr_w23(double s, ae_state *_state); +static double wsr_w24(double s, ae_state *_state); +static double wsr_w25(double s, ae_state *_state); +static double wsr_w26(double s, ae_state *_state); +static double wsr_w27(double s, ae_state *_state); +static double wsr_w28(double s, ae_state *_state); +static double wsr_w29(double s, ae_state *_state); +static double wsr_w30(double s, ae_state *_state); +static double wsr_w40(double s, ae_state *_state); +static double wsr_w60(double s, ae_state *_state); +static double wsr_w120(double s, ae_state *_state); +static double wsr_w200(double s, ae_state *_state); +static double wsr_wsigma(double s, ae_int_t n, ae_state *_state); + + + + + +/************************************************************************* +Calculation of the distribution moments: mean, variance, skewness, kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +OUTPUT PARAMETERS + Mean - mean. + Variance- variance. + Skewness- skewness (if variance<>0; zero otherwise). + Kurtosis- kurtosis (if variance<>0; zero otherwise). + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemoments(/* Real */ ae_vector* x, + ae_int_t n, + double* mean, + double* variance, + double* skewness, + double* kurtosis, + ae_state *_state) +{ + ae_int_t i; + double v; + double v1; + double v2; + double stddev; + + *mean = 0; + *variance = 0; + *skewness = 0; + *kurtosis = 0; + + ae_assert(n>=0, "SampleMoments: N<0", _state); + ae_assert(x->cnt>=n, "SampleMoments: Length(X)ptr.p_double[i]; + } + *mean = *mean/n; + + /* + * Variance (using corrected two-pass algorithm) + */ + if( n!=1 ) + { + v1 = 0; + for(i=0; i<=n-1; i++) + { + v1 = v1+ae_sqr(x->ptr.p_double[i]-(*mean), _state); + } + v2 = 0; + for(i=0; i<=n-1; i++) + { + v2 = v2+(x->ptr.p_double[i]-(*mean)); + } + v2 = ae_sqr(v2, _state)/n; + *variance = (v1-v2)/(n-1); + if( ae_fp_less(*variance,0) ) + { + *variance = 0; + } + stddev = ae_sqrt(*variance, _state); + } + + /* + * Skewness and kurtosis + */ + if( ae_fp_neq(stddev,0) ) + { + for(i=0; i<=n-1; i++) + { + v = (x->ptr.p_double[i]-(*mean))/stddev; + v2 = ae_sqr(v, _state); + *skewness = *skewness+v2*v; + *kurtosis = *kurtosis+ae_sqr(v2, _state); + } + *skewness = *skewness/n; + *kurtosis = *kurtosis/n-3; + } +} + + +/************************************************************************* +Calculation of the mean. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Mean' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplemean(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double mean; + double tmp0; + double tmp1; + double tmp2; + double result; + + + samplemoments(x, n, &mean, &tmp0, &tmp1, &tmp2, _state); + result = mean; + return result; +} + + +/************************************************************************* +Calculation of the variance. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Variance' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplevariance(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double variance; + double tmp0; + double tmp1; + double tmp2; + double result; + + + samplemoments(x, n, &tmp0, &variance, &tmp1, &tmp2, _state); + result = variance; + return result; +} + + +/************************************************************************* +Calculation of the skewness. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Skewness' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double sampleskewness(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double skewness; + double tmp0; + double tmp1; + double tmp2; + double result; + + + samplemoments(x, n, &tmp0, &tmp1, &skewness, &tmp2, _state); + result = skewness; + return result; +} + + +/************************************************************************* +Calculation of the kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Kurtosis' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplekurtosis(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state) +{ + double kurtosis; + double tmp0; + double tmp1; + double tmp2; + double result; + + + samplemoments(x, n, &tmp0, &tmp1, &tmp2, &kurtosis, _state); + result = kurtosis; + return result; +} + + +/************************************************************************* +ADev + +Input parameters: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + ADev- ADev + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void sampleadev(/* Real */ ae_vector* x, + ae_int_t n, + double* adev, + ae_state *_state) +{ + ae_int_t i; + double mean; + + *adev = 0; + + ae_assert(n>=0, "SampleADev: N<0", _state); + ae_assert(x->cnt>=n, "SampleADev: Length(X)ptr.p_double[i]; + } + mean = mean/n; + + /* + * ADev + */ + for(i=0; i<=n-1; i++) + { + *adev = *adev+ae_fabs(x->ptr.p_double[i]-mean, _state); + } + *adev = *adev/n; +} + + +/************************************************************************* +Median calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + Median + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemedian(/* Real */ ae_vector* x, + ae_int_t n, + double* median, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_int_t i; + ae_int_t ir; + ae_int_t j; + ae_int_t l; + ae_int_t midp; + ae_int_t k; + double a; + double tval; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + *median = 0; + + ae_assert(n>=0, "SampleMedian: N<0", _state); + ae_assert(x->cnt>=n, "SampleMedian: Length(X)ptr.p_double[0]; + ae_frame_leave(_state); + return; + } + if( n==2 ) + { + *median = 0.5*(x->ptr.p_double[0]+x->ptr.p_double[1]); + ae_frame_leave(_state); + return; + } + + /* + * Common case, N>=3. + * Choose X[(N-1)/2] + */ + l = 0; + ir = n-1; + k = (n-1)/2; + for(;;) + { + if( ir<=l+1 ) + { + + /* + * 1 or 2 elements in partition + */ + if( ir==l+1&&ae_fp_less(x->ptr.p_double[ir],x->ptr.p_double[l]) ) + { + tval = x->ptr.p_double[l]; + x->ptr.p_double[l] = x->ptr.p_double[ir]; + x->ptr.p_double[ir] = tval; + } + break; + } + else + { + midp = (l+ir)/2; + tval = x->ptr.p_double[midp]; + x->ptr.p_double[midp] = x->ptr.p_double[l+1]; + x->ptr.p_double[l+1] = tval; + if( ae_fp_greater(x->ptr.p_double[l],x->ptr.p_double[ir]) ) + { + tval = x->ptr.p_double[l]; + x->ptr.p_double[l] = x->ptr.p_double[ir]; + x->ptr.p_double[ir] = tval; + } + if( ae_fp_greater(x->ptr.p_double[l+1],x->ptr.p_double[ir]) ) + { + tval = x->ptr.p_double[l+1]; + x->ptr.p_double[l+1] = x->ptr.p_double[ir]; + x->ptr.p_double[ir] = tval; + } + if( ae_fp_greater(x->ptr.p_double[l],x->ptr.p_double[l+1]) ) + { + tval = x->ptr.p_double[l]; + x->ptr.p_double[l] = x->ptr.p_double[l+1]; + x->ptr.p_double[l+1] = tval; + } + i = l+1; + j = ir; + a = x->ptr.p_double[l+1]; + for(;;) + { + do + { + i = i+1; + } + while(ae_fp_less(x->ptr.p_double[i],a)); + do + { + j = j-1; + } + while(ae_fp_greater(x->ptr.p_double[j],a)); + if( jptr.p_double[i]; + x->ptr.p_double[i] = x->ptr.p_double[j]; + x->ptr.p_double[j] = tval; + } + x->ptr.p_double[l+1] = x->ptr.p_double[j]; + x->ptr.p_double[j] = a; + if( j>=k ) + { + ir = j-1; + } + if( j<=k ) + { + l = i; + } + } + } + + /* + * If N is odd, return result + */ + if( n%2==1 ) + { + *median = x->ptr.p_double[k]; + ae_frame_leave(_state); + return; + } + a = x->ptr.p_double[n-1]; + for(i=k+1; i<=n-1; i++) + { + if( ae_fp_less(x->ptr.p_double[i],a) ) + { + a = x->ptr.p_double[i]; + } + } + *median = 0.5*(x->ptr.p_double[k]+a); + ae_frame_leave(_state); +} + + +/************************************************************************* +Percentile calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + P - percentile (0<=P<=1) + +Output parameters: + V - percentile + + -- ALGLIB -- + Copyright 01.03.2008 by Bochkanov Sergey +*************************************************************************/ +void samplepercentile(/* Real */ ae_vector* x, + ae_int_t n, + double p, + double* v, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_int_t i1; + double t; + ae_vector rbuf; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + *v = 0; + ae_vector_init(&rbuf, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "SamplePercentile: N<0", _state); + ae_assert(x->cnt>=n, "SamplePercentile: Length(X)ptr.p_double[0]; + ae_frame_leave(_state); + return; + } + if( ae_fp_eq(p,1) ) + { + *v = x->ptr.p_double[n-1]; + ae_frame_leave(_state); + return; + } + t = p*(n-1); + i1 = ae_ifloor(t, _state); + t = t-ae_ifloor(t, _state); + *v = x->ptr.p_double[i1]*(1-t)+x->ptr.p_double[i1+1]*t; + ae_frame_leave(_state); +} + + +/************************************************************************* +2-sample covariance + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + covariance (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double cov2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + double xmean; + double ymean; + double v; + double x0; + double y0; + double s; + ae_bool samex; + ae_bool samey; + double result; + + + ae_assert(n>=0, "Cov2: N<0", _state); + ae_assert(x->cnt>=n, "Cov2: Length(X)cnt>=n, "Cov2: Length(Y)ptr.p_double[0]; + y0 = y->ptr.p_double[0]; + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + s = x->ptr.p_double[i]; + samex = samex&&ae_fp_eq(s,x0); + xmean = xmean+s*v; + s = y->ptr.p_double[i]; + samey = samey&&ae_fp_eq(s,y0); + ymean = ymean+s*v; + } + if( samex||samey ) + { + result = 0; + return result; + } + + /* + * covariance + */ + v = (double)1/(double)(n-1); + result = 0; + for(i=0; i<=n-1; i++) + { + result = result+v*(x->ptr.p_double[i]-xmean)*(y->ptr.p_double[i]-ymean); + } + return result; +} + + +/************************************************************************* +Pearson product-moment correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Pearson product-moment correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorr2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state) +{ + ae_int_t i; + double xmean; + double ymean; + double v; + double x0; + double y0; + double s; + ae_bool samex; + ae_bool samey; + double xv; + double yv; + double t1; + double t2; + double result; + + + ae_assert(n>=0, "PearsonCorr2: N<0", _state); + ae_assert(x->cnt>=n, "PearsonCorr2: Length(X)cnt>=n, "PearsonCorr2: Length(Y)ptr.p_double[0]; + y0 = y->ptr.p_double[0]; + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + s = x->ptr.p_double[i]; + samex = samex&&ae_fp_eq(s,x0); + xmean = xmean+s*v; + s = y->ptr.p_double[i]; + samey = samey&&ae_fp_eq(s,y0); + ymean = ymean+s*v; + } + if( samex||samey ) + { + result = 0; + return result; + } + + /* + * numerator and denominator + */ + s = 0; + xv = 0; + yv = 0; + for(i=0; i<=n-1; i++) + { + t1 = x->ptr.p_double[i]-xmean; + t2 = y->ptr.p_double[i]-ymean; + xv = xv+ae_sqr(t1, _state); + yv = yv+ae_sqr(t2, _state); + s = s+t1*t2; + } + if( ae_fp_eq(xv,0)||ae_fp_eq(yv,0) ) + { + result = 0; + } + else + { + result = s/(ae_sqrt(xv, _state)*ae_sqrt(yv, _state)); + } + return result; +} + + +/************************************************************************* +Spearman's rank correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Spearman's rank correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmancorr2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_vector _y; + apbuffers buf; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_vector_init_copy(&_y, y, _state, ae_true); + y = &_y; + _apbuffers_init(&buf, _state, ae_true); + + ae_assert(n>=0, "SpearmanCorr2: N<0", _state); + ae_assert(x->cnt>=n, "SpearmanCorr2: Length(X)cnt>=n, "SpearmanCorr2: Length(Y)=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _x; + ae_int_t i; + ae_int_t j; + double v; + ae_vector t; + ae_vector x0; + ae_vector same; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_matrix_clear(c); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&same, 0, DT_BOOL, _state, ae_true); + + ae_assert(n>=0, "CovM: N<0", _state); + ae_assert(m>=1, "CovM: M<1", _state); + ae_assert(x->rows>=n, "CovM: Rows(X)cols>=m||n==0, "CovM: Cols(X)ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Calculate means, + * check for constant columns + */ + ae_vector_set_length(&t, m, _state); + ae_vector_set_length(&x0, m, _state); + ae_vector_set_length(&same, m, _state); + ae_matrix_set_length(c, m, m, _state); + for(i=0; i<=m-1; i++) + { + t.ptr.p_double[i] = 0; + same.ptr.p_bool[i] = ae_true; + } + ae_v_move(&x0.ptr.p_double[0], 1, &x->ptr.pp_double[0][0], 1, ae_v_len(0,m-1)); + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + ae_v_addd(&t.ptr.p_double[0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m-1), v); + for(j=0; j<=m-1; j++) + { + same.ptr.p_bool[j] = same.ptr.p_bool[j]&&ae_fp_eq(x->ptr.pp_double[i][j],x0.ptr.p_double[j]); + } + } + + /* + * * center variables; + * * if we have constant columns, these columns are + * artificially zeroed (they must be zero in exact arithmetics, + * but unfortunately floating point ops are not exact). + * * calculate upper half of symmetric covariance matrix + */ + for(i=0; i<=n-1; i++) + { + ae_v_sub(&x->ptr.pp_double[i][0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,m-1)); + for(j=0; j<=m-1; j++) + { + if( same.ptr.p_bool[j] ) + { + x->ptr.pp_double[i][j] = 0; + } + } + } + rmatrixsyrk(m, n, (double)1/(double)(n-1), x, 0, 0, 1, 0.0, c, 0, 0, ae_true, _state); + rmatrixenforcesymmetricity(c, m, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_covm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state) +{ + covm(x,n,m,c, _state); +} + + +/************************************************************************* +Pearson product-moment correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector t; + ae_int_t i; + ae_int_t j; + double v; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(c); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "PearsonCorrM: N<0", _state); + ae_assert(m>=1, "PearsonCorrM: M<1", _state); + ae_assert(x->rows>=n, "PearsonCorrM: Rows(X)cols>=m||n==0, "PearsonCorrM: Cols(X)ptr.pp_double[i][i],0) ) + { + t.ptr.p_double[i] = 1/ae_sqrt(c->ptr.pp_double[i][i], _state); + } + else + { + t.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m-1; i++) + { + v = t.ptr.p_double[i]; + for(j=0; j<=m-1; j++) + { + c->ptr.pp_double[i][j] = c->ptr.pp_double[i][j]*v*t.ptr.p_double[j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_pearsoncorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state) +{ + pearsoncorrm(x,n,m,c, _state); +} + + +/************************************************************************* +Spearman's rank correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + apbuffers buf; + ae_matrix xc; + ae_vector t; + double v; + double vv; + double x0; + ae_bool b; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(c); + _apbuffers_init(&buf, _state, ae_true); + ae_matrix_init(&xc, 0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + + ae_assert(n>=0, "SpearmanCorrM: N<0", _state); + ae_assert(m>=1, "SpearmanCorrM: M<1", _state); + ae_assert(x->rows>=n, "SpearmanCorrM: Rows(X)cols>=m||n==0, "SpearmanCorrM: Cols(X)ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Allocate + */ + ae_vector_set_length(&t, ae_maxint(n, m, _state), _state); + ae_matrix_set_length(c, m, m, _state); + + /* + * Replace data with ranks + */ + ae_matrix_set_length(&xc, m, n, _state); + rmatrixtranspose(n, m, x, 0, 0, &xc, 0, 0, _state); + rankdata(&xc, m, n, _state); + + /* + * 1. Calculate means, check for constant columns + * 2. Center variables, constant columns are + * artificialy zeroed (they must be zero in exact arithmetics, + * but unfortunately floating point is not exact). + */ + for(i=0; i<=m-1; i++) + { + + /* + * Calculate: + * * V - mean value of I-th variable + * * B - True in case all variable values are same + */ + v = 0; + b = ae_true; + x0 = xc.ptr.pp_double[i][0]; + for(j=0; j<=n-1; j++) + { + vv = xc.ptr.pp_double[i][j]; + v = v+vv; + b = b&&ae_fp_eq(vv,x0); + } + v = v/n; + + /* + * Center/zero I-th variable + */ + if( b ) + { + + /* + * Zero + */ + for(j=0; j<=n-1; j++) + { + xc.ptr.pp_double[i][j] = 0.0; + } + } + else + { + + /* + * Center + */ + for(j=0; j<=n-1; j++) + { + xc.ptr.pp_double[i][j] = xc.ptr.pp_double[i][j]-v; + } + } + } + + /* + * Calculate upper half of symmetric covariance matrix + */ + rmatrixsyrk(m, n, (double)1/(double)(n-1), &xc, 0, 0, 0, 0.0, c, 0, 0, ae_true, _state); + + /* + * Calculate Pearson coefficients (upper triangle) + */ + for(i=0; i<=m-1; i++) + { + if( ae_fp_greater(c->ptr.pp_double[i][i],0) ) + { + t.ptr.p_double[i] = 1/ae_sqrt(c->ptr.pp_double[i][i], _state); + } + else + { + t.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m-1; i++) + { + v = t.ptr.p_double[i]; + for(j=i; j<=m-1; j++) + { + c->ptr.pp_double[i][j] = c->ptr.pp_double[i][j]*v*t.ptr.p_double[j]; + } + } + + /* + * force symmetricity + */ + rmatrixenforcesymmetricity(c, m, ae_true, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_spearmancorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state) +{ + spearmancorrm(x,n,m,c, _state); +} + + +/************************************************************************* +Cross-covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _x; + ae_matrix _y; + ae_int_t i; + ae_int_t j; + double v; + ae_vector t; + ae_vector x0; + ae_vector y0; + ae_vector samex; + ae_vector samey; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_matrix_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_matrix_clear(c); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&samex, 0, DT_BOOL, _state, ae_true); + ae_vector_init(&samey, 0, DT_BOOL, _state, ae_true); + + ae_assert(n>=0, "CovM2: N<0", _state); + ae_assert(m1>=1, "CovM2: M1<1", _state); + ae_assert(m2>=1, "CovM2: M2<1", _state); + ae_assert(x->rows>=n, "CovM2: Rows(X)cols>=m1||n==0, "CovM2: Cols(X)rows>=n, "CovM2: Rows(Y)cols>=m2||n==0, "CovM2: Cols(Y)ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Allocate + */ + ae_vector_set_length(&t, ae_maxint(m1, m2, _state), _state); + ae_vector_set_length(&x0, m1, _state); + ae_vector_set_length(&y0, m2, _state); + ae_vector_set_length(&samex, m1, _state); + ae_vector_set_length(&samey, m2, _state); + ae_matrix_set_length(c, m1, m2, _state); + + /* + * * calculate means of X + * * center X + * * if we have constant columns, these columns are + * artificially zeroed (they must be zero in exact arithmetics, + * but unfortunately floating point ops are not exact). + */ + for(i=0; i<=m1-1; i++) + { + t.ptr.p_double[i] = 0; + samex.ptr.p_bool[i] = ae_true; + } + ae_v_move(&x0.ptr.p_double[0], 1, &x->ptr.pp_double[0][0], 1, ae_v_len(0,m1-1)); + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + ae_v_addd(&t.ptr.p_double[0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m1-1), v); + for(j=0; j<=m1-1; j++) + { + samex.ptr.p_bool[j] = samex.ptr.p_bool[j]&&ae_fp_eq(x->ptr.pp_double[i][j],x0.ptr.p_double[j]); + } + } + for(i=0; i<=n-1; i++) + { + ae_v_sub(&x->ptr.pp_double[i][0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,m1-1)); + for(j=0; j<=m1-1; j++) + { + if( samex.ptr.p_bool[j] ) + { + x->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * Repeat same steps for Y + */ + for(i=0; i<=m2-1; i++) + { + t.ptr.p_double[i] = 0; + samey.ptr.p_bool[i] = ae_true; + } + ae_v_move(&y0.ptr.p_double[0], 1, &y->ptr.pp_double[0][0], 1, ae_v_len(0,m2-1)); + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + ae_v_addd(&t.ptr.p_double[0], 1, &y->ptr.pp_double[i][0], 1, ae_v_len(0,m2-1), v); + for(j=0; j<=m2-1; j++) + { + samey.ptr.p_bool[j] = samey.ptr.p_bool[j]&&ae_fp_eq(y->ptr.pp_double[i][j],y0.ptr.p_double[j]); + } + } + for(i=0; i<=n-1; i++) + { + ae_v_sub(&y->ptr.pp_double[i][0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,m2-1)); + for(j=0; j<=m2-1; j++) + { + if( samey.ptr.p_bool[j] ) + { + y->ptr.pp_double[i][j] = 0; + } + } + } + + /* + * calculate cross-covariance matrix + */ + rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), x, 0, 0, 1, y, 0, 0, 0, 0.0, c, 0, 0, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_covm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state) +{ + covm2(x,y,n,m1,m2,c, _state); +} + + +/************************************************************************* +Pearson product-moment cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_matrix _x; + ae_matrix _y; + ae_int_t i; + ae_int_t j; + double v; + ae_vector t; + ae_vector x0; + ae_vector y0; + ae_vector sx; + ae_vector sy; + ae_vector samex; + ae_vector samey; + + ae_frame_make(_state, &_frame_block); + ae_matrix_init_copy(&_x, x, _state, ae_true); + x = &_x; + ae_matrix_init_copy(&_y, y, _state, ae_true); + y = &_y; + ae_matrix_clear(c); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&x0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&y0, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sy, 0, DT_REAL, _state, ae_true); + ae_vector_init(&samex, 0, DT_BOOL, _state, ae_true); + ae_vector_init(&samey, 0, DT_BOOL, _state, ae_true); + + ae_assert(n>=0, "PearsonCorrM2: N<0", _state); + ae_assert(m1>=1, "PearsonCorrM2: M1<1", _state); + ae_assert(m2>=1, "PearsonCorrM2: M2<1", _state); + ae_assert(x->rows>=n, "PearsonCorrM2: Rows(X)cols>=m1||n==0, "PearsonCorrM2: Cols(X)rows>=n, "PearsonCorrM2: Rows(Y)cols>=m2||n==0, "PearsonCorrM2: Cols(Y)ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Allocate + */ + ae_vector_set_length(&t, ae_maxint(m1, m2, _state), _state); + ae_vector_set_length(&x0, m1, _state); + ae_vector_set_length(&y0, m2, _state); + ae_vector_set_length(&sx, m1, _state); + ae_vector_set_length(&sy, m2, _state); + ae_vector_set_length(&samex, m1, _state); + ae_vector_set_length(&samey, m2, _state); + ae_matrix_set_length(c, m1, m2, _state); + + /* + * * calculate means of X + * * center X + * * if we have constant columns, these columns are + * artificially zeroed (they must be zero in exact arithmetics, + * but unfortunately floating point ops are not exact). + * * calculate column variances + */ + for(i=0; i<=m1-1; i++) + { + t.ptr.p_double[i] = 0; + samex.ptr.p_bool[i] = ae_true; + sx.ptr.p_double[i] = 0; + } + ae_v_move(&x0.ptr.p_double[0], 1, &x->ptr.pp_double[0][0], 1, ae_v_len(0,m1-1)); + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + ae_v_addd(&t.ptr.p_double[0], 1, &x->ptr.pp_double[i][0], 1, ae_v_len(0,m1-1), v); + for(j=0; j<=m1-1; j++) + { + samex.ptr.p_bool[j] = samex.ptr.p_bool[j]&&ae_fp_eq(x->ptr.pp_double[i][j],x0.ptr.p_double[j]); + } + } + for(i=0; i<=n-1; i++) + { + ae_v_sub(&x->ptr.pp_double[i][0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,m1-1)); + for(j=0; j<=m1-1; j++) + { + if( samex.ptr.p_bool[j] ) + { + x->ptr.pp_double[i][j] = 0; + } + sx.ptr.p_double[j] = sx.ptr.p_double[j]+x->ptr.pp_double[i][j]*x->ptr.pp_double[i][j]; + } + } + for(j=0; j<=m1-1; j++) + { + sx.ptr.p_double[j] = ae_sqrt(sx.ptr.p_double[j]/(n-1), _state); + } + + /* + * Repeat same steps for Y + */ + for(i=0; i<=m2-1; i++) + { + t.ptr.p_double[i] = 0; + samey.ptr.p_bool[i] = ae_true; + sy.ptr.p_double[i] = 0; + } + ae_v_move(&y0.ptr.p_double[0], 1, &y->ptr.pp_double[0][0], 1, ae_v_len(0,m2-1)); + v = (double)1/(double)n; + for(i=0; i<=n-1; i++) + { + ae_v_addd(&t.ptr.p_double[0], 1, &y->ptr.pp_double[i][0], 1, ae_v_len(0,m2-1), v); + for(j=0; j<=m2-1; j++) + { + samey.ptr.p_bool[j] = samey.ptr.p_bool[j]&&ae_fp_eq(y->ptr.pp_double[i][j],y0.ptr.p_double[j]); + } + } + for(i=0; i<=n-1; i++) + { + ae_v_sub(&y->ptr.pp_double[i][0], 1, &t.ptr.p_double[0], 1, ae_v_len(0,m2-1)); + for(j=0; j<=m2-1; j++) + { + if( samey.ptr.p_bool[j] ) + { + y->ptr.pp_double[i][j] = 0; + } + sy.ptr.p_double[j] = sy.ptr.p_double[j]+y->ptr.pp_double[i][j]*y->ptr.pp_double[i][j]; + } + } + for(j=0; j<=m2-1; j++) + { + sy.ptr.p_double[j] = ae_sqrt(sy.ptr.p_double[j]/(n-1), _state); + } + + /* + * calculate cross-covariance matrix + */ + rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), x, 0, 0, 1, y, 0, 0, 0, 0.0, c, 0, 0, _state); + + /* + * Divide by standard deviations + */ + for(i=0; i<=m1-1; i++) + { + if( ae_fp_neq(sx.ptr.p_double[i],0) ) + { + sx.ptr.p_double[i] = 1/sx.ptr.p_double[i]; + } + else + { + sx.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m2-1; i++) + { + if( ae_fp_neq(sy.ptr.p_double[i],0) ) + { + sy.ptr.p_double[i] = 1/sy.ptr.p_double[i]; + } + else + { + sy.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m1-1; i++) + { + v = sx.ptr.p_double[i]; + for(j=0; j<=m2-1; j++) + { + c->ptr.pp_double[i][j] = c->ptr.pp_double[i][j]*v*sy.ptr.p_double[j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_pearsoncorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state) +{ + pearsoncorrm2(x,y,n,m1,m2,c, _state); +} + + +/************************************************************************* +Spearman's rank cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + double v; + double v2; + double vv; + ae_bool b; + ae_vector t; + double x0; + double y0; + ae_vector sx; + ae_vector sy; + ae_matrix xc; + ae_matrix yc; + apbuffers buf; + + ae_frame_make(_state, &_frame_block); + ae_matrix_clear(c); + ae_vector_init(&t, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&sy, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&xc, 0, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&yc, 0, 0, DT_REAL, _state, ae_true); + _apbuffers_init(&buf, _state, ae_true); + + ae_assert(n>=0, "SpearmanCorrM2: N<0", _state); + ae_assert(m1>=1, "SpearmanCorrM2: M1<1", _state); + ae_assert(m2>=1, "SpearmanCorrM2: M2<1", _state); + ae_assert(x->rows>=n, "SpearmanCorrM2: Rows(X)cols>=m1||n==0, "SpearmanCorrM2: Cols(X)rows>=n, "SpearmanCorrM2: Rows(Y)cols>=m2||n==0, "SpearmanCorrM2: Cols(Y)ptr.pp_double[i][j] = 0; + } + } + ae_frame_leave(_state); + return; + } + + /* + * Allocate + */ + ae_vector_set_length(&t, ae_maxint(ae_maxint(m1, m2, _state), n, _state), _state); + ae_vector_set_length(&sx, m1, _state); + ae_vector_set_length(&sy, m2, _state); + ae_matrix_set_length(c, m1, m2, _state); + + /* + * Replace data with ranks + */ + ae_matrix_set_length(&xc, m1, n, _state); + ae_matrix_set_length(&yc, m2, n, _state); + rmatrixtranspose(n, m1, x, 0, 0, &xc, 0, 0, _state); + rmatrixtranspose(n, m2, y, 0, 0, &yc, 0, 0, _state); + rankdata(&xc, m1, n, _state); + rankdata(&yc, m2, n, _state); + + /* + * 1. Calculate means, variances, check for constant columns + * 2. Center variables, constant columns are + * artificialy zeroed (they must be zero in exact arithmetics, + * but unfortunately floating point is not exact). + * + * Description of variables: + * * V - mean value of I-th variable + * * V2- variance + * * VV-temporary + * * B - True in case all variable values are same + */ + for(i=0; i<=m1-1; i++) + { + v = 0; + v2 = 0.0; + b = ae_true; + x0 = xc.ptr.pp_double[i][0]; + for(j=0; j<=n-1; j++) + { + vv = xc.ptr.pp_double[i][j]; + v = v+vv; + b = b&&ae_fp_eq(vv,x0); + } + v = v/n; + if( b ) + { + for(j=0; j<=n-1; j++) + { + xc.ptr.pp_double[i][j] = 0.0; + } + } + else + { + for(j=0; j<=n-1; j++) + { + vv = xc.ptr.pp_double[i][j]; + xc.ptr.pp_double[i][j] = vv-v; + v2 = v2+(vv-v)*(vv-v); + } + } + sx.ptr.p_double[i] = ae_sqrt(v2/(n-1), _state); + } + for(i=0; i<=m2-1; i++) + { + v = 0; + v2 = 0.0; + b = ae_true; + y0 = yc.ptr.pp_double[i][0]; + for(j=0; j<=n-1; j++) + { + vv = yc.ptr.pp_double[i][j]; + v = v+vv; + b = b&&ae_fp_eq(vv,y0); + } + v = v/n; + if( b ) + { + for(j=0; j<=n-1; j++) + { + yc.ptr.pp_double[i][j] = 0.0; + } + } + else + { + for(j=0; j<=n-1; j++) + { + vv = yc.ptr.pp_double[i][j]; + yc.ptr.pp_double[i][j] = vv-v; + v2 = v2+(vv-v)*(vv-v); + } + } + sy.ptr.p_double[i] = ae_sqrt(v2/(n-1), _state); + } + + /* + * calculate cross-covariance matrix + */ + rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), &xc, 0, 0, 0, &yc, 0, 0, 1, 0.0, c, 0, 0, _state); + + /* + * Divide by standard deviations + */ + for(i=0; i<=m1-1; i++) + { + if( ae_fp_neq(sx.ptr.p_double[i],0) ) + { + sx.ptr.p_double[i] = 1/sx.ptr.p_double[i]; + } + else + { + sx.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m2-1; i++) + { + if( ae_fp_neq(sy.ptr.p_double[i],0) ) + { + sy.ptr.p_double[i] = 1/sy.ptr.p_double[i]; + } + else + { + sy.ptr.p_double[i] = 0.0; + } + } + for(i=0; i<=m1-1; i++) + { + v = sx.ptr.p_double[i]; + for(j=0; j<=m2-1; j++) + { + c->ptr.pp_double[i][j] = c->ptr.pp_double[i][j]*v*sy.ptr.p_double[j]; + } + } + ae_frame_leave(_state); +} + + +/************************************************************************* +Single-threaded stub. HPC ALGLIB replaces it by multithreaded code. +*************************************************************************/ +void _pexec_spearmancorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state) +{ + spearmancorrm2(x,y,n,m1,m2,c, _state); +} + + +void rankdata(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_state *_state) +{ + ae_frame _frame_block; + apbuffers buf0; + apbuffers buf1; + ae_int_t basecasecost; + ae_shared_pool pool; + + ae_frame_make(_state, &_frame_block); + _apbuffers_init(&buf0, _state, ae_true); + _apbuffers_init(&buf1, _state, ae_true); + ae_shared_pool_init(&pool, _state, ae_true); + + ae_assert(npoints>=0, "RankData: NPoints<0", _state); + ae_assert(nfeatures>=1, "RankData: NFeatures<1", _state); + ae_assert(xy->rows>=npoints, "RankData: Rows(XY)cols>=nfeatures||npoints==0, "RankData: Cols(XY)=0, "RankData: NPoints<0", _state); + ae_assert(nfeatures>=1, "RankData: NFeatures<1", _state); + ae_assert(xy->rows>=npoints, "RankData: Rows(XY)cols>=nfeatures||npoints==0, "RankData: Cols(XY)=i0, "RankDataRec: internal error", _state); + + /* + * Recursively split problem, if it is too large + */ + problemcost = inttoreal(i1-i0, _state)*inttoreal(nfeatures, _state)*log2(nfeatures, _state); + if( i1-i0>=2&&ae_fp_greater(problemcost,basecasecost) ) + { + im = (i1+i0)/2; + basestat_rankdatarec(xy, i0, im, nfeatures, iscentered, pool, basecasecost, _state); + basestat_rankdatarec(xy, im, i1, nfeatures, iscentered, pool, basecasecost, _state); + ae_frame_leave(_state); + return; + } + + /* + * Retrieve buffers from pool, call serial code, return buffers to pool + */ + ae_shared_pool_retrieve(pool, &_buf0, _state); + ae_shared_pool_retrieve(pool, &_buf1, _state); + basestat_rankdatabasecase(xy, i0, i1, nfeatures, iscentered, buf0, buf1, _state); + ae_shared_pool_recycle(pool, &_buf0, _state); + ae_shared_pool_recycle(pool, &_buf1, _state); + ae_frame_leave(_state); +} + + +static void basestat_rankdatabasecase(/* Real */ ae_matrix* xy, + ae_int_t i0, + ae_int_t i1, + ae_int_t nfeatures, + ae_bool iscentered, + apbuffers* buf0, + apbuffers* buf1, + ae_state *_state) +{ + ae_int_t i; + + + ae_assert(i1>=i0, "RankDataBasecase: internal error", _state); + if( buf1->ra0.cntra0, nfeatures, _state); + } + for(i=i0; i<=i1-1; i++) + { + ae_v_move(&buf1->ra0.ptr.p_double[0], 1, &xy->ptr.pp_double[i][0], 1, ae_v_len(0,nfeatures-1)); + rankx(&buf1->ra0, nfeatures, iscentered, buf0, _state); + ae_v_move(&xy->ptr.pp_double[i][0], 1, &buf1->ra0.ptr.p_double[0], 1, ae_v_len(0,nfeatures-1)); + } +} + + + + +/************************************************************************* +Pearson's correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5 + * normality of distributions of X and Y. + +Input parameters: + R - Pearson's correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrelationsignificance(double r, + ae_int_t n, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + double t; + double p; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + + /* + * Some special cases + */ + if( ae_fp_greater_eq(r,1) ) + { + *bothtails = 0.0; + *lefttail = 1.0; + *righttail = 0.0; + return; + } + if( ae_fp_less_eq(r,-1) ) + { + *bothtails = 0.0; + *lefttail = 0.0; + *righttail = 1.0; + return; + } + if( n<5 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * General case + */ + t = r*ae_sqrt((n-2)/(1-ae_sqr(r, _state)), _state); + p = studenttdistribution(n-2, t, _state); + *bothtails = 2*ae_minreal(p, 1-p, _state); + *lefttail = p; + *righttail = 1-p; +} + + +/************************************************************************* +Spearman's rank correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5. + +The test is non-parametric and doesn't require distributions X and Y to be +normal. + +Input parameters: + R - Spearman's rank correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void spearmanrankcorrelationsignificance(double r, + ae_int_t n, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + double t; + double p; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + + /* + * Special case + */ + if( n<5 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * General case + */ + if( ae_fp_greater_eq(r,1) ) + { + t = 1.0E10; + } + else + { + if( ae_fp_less_eq(r,-1) ) + { + t = -1.0E10; + } + else + { + t = r*ae_sqrt((n-2)/(1-ae_sqr(r, _state)), _state); + } + } + if( ae_fp_less(t,0) ) + { + p = correlationtests_spearmantail(t, n, _state); + *bothtails = 2*p; + *lefttail = p; + *righttail = 1-p; + } + else + { + p = correlationtests_spearmantail(-t, n, _state); + *bothtails = 2*p; + *lefttail = 1-p; + *righttail = p; + } +} + + +/************************************************************************* +Tail(S, 5) +*************************************************************************/ +static double correlationtests_spearmantail5(double s, ae_state *_state) +{ + double result; + + + if( ae_fp_less(s,0.000e+00) ) + { + result = studenttdistribution(3, -s, _state); + return result; + } + if( ae_fp_greater_eq(s,3.580e+00) ) + { + result = 8.304e-03; + return result; + } + if( ae_fp_greater_eq(s,2.322e+00) ) + { + result = 4.163e-02; + return result; + } + if( ae_fp_greater_eq(s,1.704e+00) ) + { + result = 6.641e-02; + return result; + } + if( ae_fp_greater_eq(s,1.303e+00) ) + { + result = 1.164e-01; + return result; + } + if( ae_fp_greater_eq(s,1.003e+00) ) + { + result = 1.748e-01; + return result; + } + if( ae_fp_greater_eq(s,7.584e-01) ) + { + result = 2.249e-01; + return result; + } + if( ae_fp_greater_eq(s,5.468e-01) ) + { + result = 2.581e-01; + return result; + } + if( ae_fp_greater_eq(s,3.555e-01) ) + { + result = 3.413e-01; + return result; + } + if( ae_fp_greater_eq(s,1.759e-01) ) + { + result = 3.911e-01; + return result; + } + if( ae_fp_greater_eq(s,1.741e-03) ) + { + result = 4.747e-01; + return result; + } + if( ae_fp_greater_eq(s,0.000e+00) ) + { + result = 5.248e-01; + return result; + } + result = 0; + return result; +} + + +/************************************************************************* +Tail(S, 6) +*************************************************************************/ +static double correlationtests_spearmantail6(double s, ae_state *_state) +{ + double result; + + + if( ae_fp_less(s,1.001e+00) ) + { + result = studenttdistribution(4, -s, _state); + return result; + } + if( ae_fp_greater_eq(s,5.663e+00) ) + { + result = 1.366e-03; + return result; + } + if( ae_fp_greater_eq(s,3.834e+00) ) + { + result = 8.350e-03; + return result; + } + if( ae_fp_greater_eq(s,2.968e+00) ) + { + result = 1.668e-02; + return result; + } + if( ae_fp_greater_eq(s,2.430e+00) ) + { + result = 2.921e-02; + return result; + } + if( ae_fp_greater_eq(s,2.045e+00) ) + { + result = 5.144e-02; + return result; + } + if( ae_fp_greater_eq(s,1.747e+00) ) + { + result = 6.797e-02; + return result; + } + if( ae_fp_greater_eq(s,1.502e+00) ) + { + result = 8.752e-02; + return result; + } + if( ae_fp_greater_eq(s,1.295e+00) ) + { + result = 1.210e-01; + return result; + } + if( ae_fp_greater_eq(s,1.113e+00) ) + { + result = 1.487e-01; + return result; + } + if( ae_fp_greater_eq(s,1.001e+00) ) + { + result = 1.780e-01; + return result; + } + result = 0; + return result; +} + + +/************************************************************************* +Tail(S, 7) +*************************************************************************/ +static double correlationtests_spearmantail7(double s, ae_state *_state) +{ + double result; + + + if( ae_fp_less(s,1.001e+00) ) + { + result = studenttdistribution(5, -s, _state); + return result; + } + if( ae_fp_greater_eq(s,8.159e+00) ) + { + result = 2.081e-04; + return result; + } + if( ae_fp_greater_eq(s,5.620e+00) ) + { + result = 1.393e-03; + return result; + } + if( ae_fp_greater_eq(s,4.445e+00) ) + { + result = 3.398e-03; + return result; + } + if( ae_fp_greater_eq(s,3.728e+00) ) + { + result = 6.187e-03; + return result; + } + if( ae_fp_greater_eq(s,3.226e+00) ) + { + result = 1.200e-02; + return result; + } + if( ae_fp_greater_eq(s,2.844e+00) ) + { + result = 1.712e-02; + return result; + } + if( ae_fp_greater_eq(s,2.539e+00) ) + { + result = 2.408e-02; + return result; + } + if( ae_fp_greater_eq(s,2.285e+00) ) + { + result = 3.320e-02; + return result; + } + if( ae_fp_greater_eq(s,2.068e+00) ) + { + result = 4.406e-02; + return result; + } + if( ae_fp_greater_eq(s,1.879e+00) ) + { + result = 5.478e-02; + return result; + } + if( ae_fp_greater_eq(s,1.710e+00) ) + { + result = 6.946e-02; + return result; + } + if( ae_fp_greater_eq(s,1.559e+00) ) + { + result = 8.331e-02; + return result; + } + if( ae_fp_greater_eq(s,1.420e+00) ) + { + result = 1.001e-01; + return result; + } + if( ae_fp_greater_eq(s,1.292e+00) ) + { + result = 1.180e-01; + return result; + } + if( ae_fp_greater_eq(s,1.173e+00) ) + { + result = 1.335e-01; + return result; + } + if( ae_fp_greater_eq(s,1.062e+00) ) + { + result = 1.513e-01; + return result; + } + if( ae_fp_greater_eq(s,1.001e+00) ) + { + result = 1.770e-01; + return result; + } + result = 0; + return result; +} + + +/************************************************************************* +Tail(S, 8) +*************************************************************************/ +static double correlationtests_spearmantail8(double s, ae_state *_state) +{ + double result; + + + if( ae_fp_less(s,2.001e+00) ) + { + result = studenttdistribution(6, -s, _state); + return result; + } + if( ae_fp_greater_eq(s,1.103e+01) ) + { + result = 2.194e-05; + return result; + } + if( ae_fp_greater_eq(s,7.685e+00) ) + { + result = 2.008e-04; + return result; + } + if( ae_fp_greater_eq(s,6.143e+00) ) + { + result = 5.686e-04; + return result; + } + if( ae_fp_greater_eq(s,5.213e+00) ) + { + result = 1.138e-03; + return result; + } + if( ae_fp_greater_eq(s,4.567e+00) ) + { + result = 2.310e-03; + return result; + } + if( ae_fp_greater_eq(s,4.081e+00) ) + { + result = 3.634e-03; + return result; + } + if( ae_fp_greater_eq(s,3.697e+00) ) + { + result = 5.369e-03; + return result; + } + if( ae_fp_greater_eq(s,3.381e+00) ) + { + result = 7.708e-03; + return result; + } + if( ae_fp_greater_eq(s,3.114e+00) ) + { + result = 1.087e-02; + return result; + } + if( ae_fp_greater_eq(s,2.884e+00) ) + { + result = 1.397e-02; + return result; + } + if( ae_fp_greater_eq(s,2.682e+00) ) + { + result = 1.838e-02; + return result; + } + if( ae_fp_greater_eq(s,2.502e+00) ) + { + result = 2.288e-02; + return result; + } + if( ae_fp_greater_eq(s,2.340e+00) ) + { + result = 2.883e-02; + return result; + } + if( ae_fp_greater_eq(s,2.192e+00) ) + { + result = 3.469e-02; + return result; + } + if( ae_fp_greater_eq(s,2.057e+00) ) + { + result = 4.144e-02; + return result; + } + if( ae_fp_greater_eq(s,2.001e+00) ) + { + result = 4.804e-02; + return result; + } + result = 0; + return result; +} + + +/************************************************************************* +Tail(S, 9) +*************************************************************************/ +static double correlationtests_spearmantail9(double s, ae_state *_state) +{ + double result; + + + if( ae_fp_less(s,2.001e+00) ) + { + result = studenttdistribution(7, -s, _state); + return result; + } + if( ae_fp_greater_eq(s,9.989e+00) ) + { + result = 2.306e-05; + return result; + } + if( ae_fp_greater_eq(s,8.069e+00) ) + { + result = 8.167e-05; + return result; + } + if( ae_fp_greater_eq(s,6.890e+00) ) + { + result = 1.744e-04; + return result; + } + if( ae_fp_greater_eq(s,6.077e+00) ) + { + result = 3.625e-04; + return result; + } + if( ae_fp_greater_eq(s,5.469e+00) ) + { + result = 6.450e-04; + return result; + } + if( ae_fp_greater_eq(s,4.991e+00) ) + { + result = 1.001e-03; + return result; + } + if( ae_fp_greater_eq(s,4.600e+00) ) + { + result = 1.514e-03; + return result; + } + if( ae_fp_greater_eq(s,4.272e+00) ) + { + result = 2.213e-03; + return result; + } + if( ae_fp_greater_eq(s,3.991e+00) ) + { + result = 2.990e-03; + return result; + } + if( ae_fp_greater_eq(s,3.746e+00) ) + { + result = 4.101e-03; + return result; + } + if( ae_fp_greater_eq(s,3.530e+00) ) + { + result = 5.355e-03; + return result; + } + if( ae_fp_greater_eq(s,3.336e+00) ) + { + result = 6.887e-03; + return result; + } + if( ae_fp_greater_eq(s,3.161e+00) ) + { + result = 8.598e-03; + return result; + } + if( ae_fp_greater_eq(s,3.002e+00) ) + { + result = 1.065e-02; + return result; + } + if( ae_fp_greater_eq(s,2.855e+00) ) + { + result = 1.268e-02; + return result; + } + if( ae_fp_greater_eq(s,2.720e+00) ) + { + result = 1.552e-02; + return result; + } + if( ae_fp_greater_eq(s,2.595e+00) ) + { + result = 1.836e-02; + return result; + } + if( ae_fp_greater_eq(s,2.477e+00) ) + { + result = 2.158e-02; + return result; + } + if( ae_fp_greater_eq(s,2.368e+00) ) + { + result = 2.512e-02; + return result; + } + if( ae_fp_greater_eq(s,2.264e+00) ) + { + result = 2.942e-02; + return result; + } + if( ae_fp_greater_eq(s,2.166e+00) ) + { + result = 3.325e-02; + return result; + } + if( ae_fp_greater_eq(s,2.073e+00) ) + { + result = 3.800e-02; + return result; + } + if( ae_fp_greater_eq(s,2.001e+00) ) + { + result = 4.285e-02; + return result; + } + result = 0; + return result; +} + + +/************************************************************************* +Tail(T,N), accepts T<0 +*************************************************************************/ +static double correlationtests_spearmantail(double t, + ae_int_t n, + ae_state *_state) +{ + double result; + + + if( n==5 ) + { + result = correlationtests_spearmantail5(-t, _state); + return result; + } + if( n==6 ) + { + result = correlationtests_spearmantail6(-t, _state); + return result; + } + if( n==7 ) + { + result = correlationtests_spearmantail7(-t, _state); + return result; + } + if( n==8 ) + { + result = correlationtests_spearmantail8(-t, _state); + return result; + } + if( n==9 ) + { + result = correlationtests_spearmantail9(-t, _state); + return result; + } + result = studenttdistribution(n-2, t, _state); + return result; +} + + + + +/************************************************************************* +Jarque-Bera test + +This test checks hypotheses about the fact that a given sample X is a +sample of normal random variable. + +Requirements: + * the number of elements in the sample is not less than 5. + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +Accuracy of the approximation used (5<=N<=1951): + +p-value relative error (5<=N<=1951) +[1, 0.1] < 1% +[0.1, 0.01] < 2% +[0.01, 0.001] < 6% +[0.001, 0] wasn't measured + +For N>1951 accuracy wasn't measured but it shouldn't be sharply different +from table values. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void jarqueberatest(/* Real */ ae_vector* x, + ae_int_t n, + double* p, + ae_state *_state) +{ + double s; + + *p = 0; + + + /* + * N is too small + */ + if( n<5 ) + { + *p = 1.0; + return; + } + + /* + * N is large enough + */ + jarquebera_jarqueberastatistic(x, n, &s, _state); + *p = jarquebera_jarqueberaapprox(n, s, _state); +} + + +static void jarquebera_jarqueberastatistic(/* Real */ ae_vector* x, + ae_int_t n, + double* s, + ae_state *_state) +{ + ae_int_t i; + double v; + double v1; + double v2; + double stddev; + double mean; + double variance; + double skewness; + double kurtosis; + + *s = 0; + + mean = 0; + variance = 0; + skewness = 0; + kurtosis = 0; + stddev = 0; + ae_assert(n>1, "Assertion failed", _state); + + /* + * Mean + */ + for(i=0; i<=n-1; i++) + { + mean = mean+x->ptr.p_double[i]; + } + mean = mean/n; + + /* + * Variance (using corrected two-pass algorithm) + */ + if( n!=1 ) + { + v1 = 0; + for(i=0; i<=n-1; i++) + { + v1 = v1+ae_sqr(x->ptr.p_double[i]-mean, _state); + } + v2 = 0; + for(i=0; i<=n-1; i++) + { + v2 = v2+(x->ptr.p_double[i]-mean); + } + v2 = ae_sqr(v2, _state)/n; + variance = (v1-v2)/(n-1); + if( ae_fp_less(variance,0) ) + { + variance = 0; + } + stddev = ae_sqrt(variance, _state); + } + + /* + * Skewness and kurtosis + */ + if( ae_fp_neq(stddev,0) ) + { + for(i=0; i<=n-1; i++) + { + v = (x->ptr.p_double[i]-mean)/stddev; + v2 = ae_sqr(v, _state); + skewness = skewness+v2*v; + kurtosis = kurtosis+ae_sqr(v2, _state); + } + skewness = skewness/n; + kurtosis = kurtosis/n-3; + } + + /* + * Statistic + */ + *s = (double)n/(double)6*(ae_sqr(skewness, _state)+ae_sqr(kurtosis, _state)/4); +} + + +static double jarquebera_jarqueberaapprox(ae_int_t n, + double s, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector vx; + ae_vector vy; + ae_matrix ctbl; + double t1; + double t2; + double t3; + double t; + double f1; + double f2; + double f3; + double f12; + double f23; + double x; + double result; + + ae_frame_make(_state, &_frame_block); + ae_vector_init(&vx, 0, DT_REAL, _state, ae_true); + ae_vector_init(&vy, 0, DT_REAL, _state, ae_true); + ae_matrix_init(&ctbl, 0, 0, DT_REAL, _state, ae_true); + + result = 1; + x = s; + if( n<5 ) + { + ae_frame_leave(_state); + return result; + } + + /* + * N = 5..20 are tabulated + */ + if( n>=5&&n<=20 ) + { + if( n==5 ) + { + result = ae_exp(jarquebera_jbtbl5(x, _state), _state); + } + if( n==6 ) + { + result = ae_exp(jarquebera_jbtbl6(x, _state), _state); + } + if( n==7 ) + { + result = ae_exp(jarquebera_jbtbl7(x, _state), _state); + } + if( n==8 ) + { + result = ae_exp(jarquebera_jbtbl8(x, _state), _state); + } + if( n==9 ) + { + result = ae_exp(jarquebera_jbtbl9(x, _state), _state); + } + if( n==10 ) + { + result = ae_exp(jarquebera_jbtbl10(x, _state), _state); + } + if( n==11 ) + { + result = ae_exp(jarquebera_jbtbl11(x, _state), _state); + } + if( n==12 ) + { + result = ae_exp(jarquebera_jbtbl12(x, _state), _state); + } + if( n==13 ) + { + result = ae_exp(jarquebera_jbtbl13(x, _state), _state); + } + if( n==14 ) + { + result = ae_exp(jarquebera_jbtbl14(x, _state), _state); + } + if( n==15 ) + { + result = ae_exp(jarquebera_jbtbl15(x, _state), _state); + } + if( n==16 ) + { + result = ae_exp(jarquebera_jbtbl16(x, _state), _state); + } + if( n==17 ) + { + result = ae_exp(jarquebera_jbtbl17(x, _state), _state); + } + if( n==18 ) + { + result = ae_exp(jarquebera_jbtbl18(x, _state), _state); + } + if( n==19 ) + { + result = ae_exp(jarquebera_jbtbl19(x, _state), _state); + } + if( n==20 ) + { + result = ae_exp(jarquebera_jbtbl20(x, _state), _state); + } + ae_frame_leave(_state); + return result; + } + + /* + * N = 20, 30, 50 are tabulated. + * In-between values are interpolated + * using interpolating polynomial of the second degree. + */ + if( n>20&&n<=50 ) + { + t1 = -1.0/20.0; + t2 = -1.0/30.0; + t3 = -1.0/50.0; + t = -1.0/n; + f1 = jarquebera_jbtbl20(x, _state); + f2 = jarquebera_jbtbl30(x, _state); + f3 = jarquebera_jbtbl50(x, _state); + f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2); + f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3); + result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + + /* + * N = 50, 65, 100 are tabulated. + * In-between values are interpolated + * using interpolating polynomial of the second degree. + */ + if( n>50&&n<=100 ) + { + t1 = -1.0/50.0; + t2 = -1.0/65.0; + t3 = -1.0/100.0; + t = -1.0/n; + f1 = jarquebera_jbtbl50(x, _state); + f2 = jarquebera_jbtbl65(x, _state); + f3 = jarquebera_jbtbl100(x, _state); + f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2); + f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3); + result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + + /* + * N = 100, 130, 200 are tabulated. + * In-between values are interpolated + * using interpolating polynomial of the second degree. + */ + if( n>100&&n<=200 ) + { + t1 = -1.0/100.0; + t2 = -1.0/130.0; + t3 = -1.0/200.0; + t = -1.0/n; + f1 = jarquebera_jbtbl100(x, _state); + f2 = jarquebera_jbtbl130(x, _state); + f3 = jarquebera_jbtbl200(x, _state); + f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2); + f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3); + result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + + /* + * N = 200, 301, 501 are tabulated. + * In-between values are interpolated + * using interpolating polynomial of the second degree. + */ + if( n>200&&n<=501 ) + { + t1 = -1.0/200.0; + t2 = -1.0/301.0; + t3 = -1.0/501.0; + t = -1.0/n; + f1 = jarquebera_jbtbl200(x, _state); + f2 = jarquebera_jbtbl301(x, _state); + f3 = jarquebera_jbtbl501(x, _state); + f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2); + f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3); + result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + + /* + * N = 501, 701, 1401 are tabulated. + * In-between values are interpolated + * using interpolating polynomial of the second degree. + */ + if( n>501&&n<=1401 ) + { + t1 = -1.0/501.0; + t2 = -1.0/701.0; + t3 = -1.0/1401.0; + t = -1.0/n; + f1 = jarquebera_jbtbl501(x, _state); + f2 = jarquebera_jbtbl701(x, _state); + f3 = jarquebera_jbtbl1401(x, _state); + f12 = ((t-t2)*f1+(t1-t)*f2)/(t1-t2); + f23 = ((t-t3)*f2+(t2-t)*f3)/(t2-t3); + result = ((t-t3)*f12+(t1-t)*f23)/(t1-t3); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + + /* + * Asymptotic expansion + */ + if( n>1401 ) + { + result = -0.5*x+(jarquebera_jbtbl1401(x, _state)+0.5*x)*ae_sqrt((double)1401/(double)n, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + result = ae_exp(result, _state); + ae_frame_leave(_state); + return result; + } + ae_frame_leave(_state); + return result; +} + + +static double jarquebera_jbtbl5(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,0.4000) ) + { + x = 2*(s-0.000000)/0.400000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.097885e-20, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.854501e-20, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.756616e-20, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,1.1000) ) + { + x = 2*(s-0.400000)/0.700000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.324545e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.075941e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.772272e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.175686e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.576162e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.126861e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.434425e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.790359e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.809178e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.479704e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.717040e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.294170e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.880632e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.023344e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.601531e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.920403e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -5.188419e+02*(s-1.100000e+00)-4.767297e+00; + return result; +} + + +static double jarquebera_jbtbl6(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,0.2500) ) + { + x = 2*(s-0.000000)/0.250000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.274707e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.700471e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.425764e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,1.3000) ) + { + x = 2*(s-0.250000)/1.050000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.339000e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.011104e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.168177e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.085666e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.738606e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.022876e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.462402e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.908270e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.230772e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.006996e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.410222e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.893768e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.114564e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,1.8500) ) + { + x = 2*(s-1.300000)/0.550000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.794311e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.578700e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.394664e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.928290e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.813273e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.076063e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.835380e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.013013e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.058903e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.856915e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.710887e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.770029e+02*(s-1.850000e+00)-1.371015e+01; + return result; +} + + +static double jarquebera_jbtbl7(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.4000) ) + { + x = 2*(s-0.000000)/1.400000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.093681e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.695911e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.473192e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.203236e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.590379e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.291876e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.132007e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.411147e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.180067e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.487610e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.436561e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-1.400000)/1.600000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.947854e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.772675e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.707912e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.691171e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.132795e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.481310e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.867536e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.772327e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.033387e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.378277e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.497964e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.636814e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.581640e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,3.2000) ) + { + x = 2*(s-3.000000)/0.200000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -7.511008e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.140472e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.682053e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.568561e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.933930e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.140472e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.895025e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.140472e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.933930e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.568561e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.682053e+00, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.824116e+03*(s-3.200000e+00)-1.440330e+01; + return result; +} + + +static double jarquebera_jbtbl8(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.3000) ) + { + x = 2*(s-0.000000)/1.300000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -7.199015e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.095921e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.736828e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.047438e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.484320e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.937923e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.810470e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.139780e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.708443e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,2.0000) ) + { + x = 2*(s-1.300000)/0.700000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.378966e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.802461e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.547593e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.241042e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.203274e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.201990e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.125597e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.584426e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.546069e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,5.0000) ) + { + x = 2*(s-2.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.828366e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.137533e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.016671e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.745637e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.189801e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.621610e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.741122e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.516368e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.552085e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.787029e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.359774e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -5.087028e+00*(s-5.000000e+00)-1.071300e+01; + return result; +} + + +static double jarquebera_jbtbl9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.3000) ) + { + x = 2*(s-0.000000)/1.300000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.279320e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.277151e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.669339e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.086149e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.333816e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.871249e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.007048e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.482245e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.355615e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,2.0000) ) + { + x = 2*(s-1.300000)/0.700000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.981430e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.972248e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.747737e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.808530e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.888305e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.001302e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.378767e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.108510e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.915372e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,7.0000) ) + { + x = 2*(s-2.000000)/5.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.387463e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.845231e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.809956e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.543461e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.880397e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.160074e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.356527e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.394428e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.619892e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.758763e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.790977e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.020952e+00*(s-7.000000e+00)-9.516623e+00; + return result; +} + + +static double jarquebera_jbtbl10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.2000) ) + { + x = 2*(s-0.000000)/1.200000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.590993e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.562730e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.353934e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.069933e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.849151e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.931406e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.636295e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.178340e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.917749e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,2.0000) ) + { + x = 2*(s-1.200000)/0.800000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.537658e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.962401e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.838715e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.055792e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.580316e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.781701e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.770362e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.838983e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.999052e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,7.0000) ) + { + x = 2*(s-2.000000)/5.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.337524e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.877029e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.734650e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.249254e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.320250e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.432266e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -8.711035e-01*(s-7.000000e+00)-7.212811e+00; + return result; +} + + +static double jarquebera_jbtbl11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.2000) ) + { + x = 2*(s-0.000000)/1.200000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.339517e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.051558e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.000992e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.022547e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.808401e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.592870e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.575081e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.086173e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.089011e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,2.2500) ) + { + x = 2*(s-1.200000)/1.050000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.523221e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.068388e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.179661e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.555524e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.238964e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.364320e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.895771e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.762774e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.201340e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,8.0000) ) + { + x = 2*(s-2.250000)/5.750000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.212179e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.684579e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.299519e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.606261e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.310869e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.320115e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -5.715445e-01*(s-8.000000e+00)-6.845834e+00; + return result; +} + + +static double jarquebera_jbtbl12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.0000) ) + { + x = 2*(s-0.000000)/1.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.736742e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.657836e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.047209e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.319599e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.545631e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.280445e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.815679e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.213519e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.256838e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-1.000000)/2.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.573947e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.515287e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.611880e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.271311e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.495815e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.141186e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.180886e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.388211e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.890761e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.233175e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.946156e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,12.0000) ) + { + x = 2*(s-3.000000)/9.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.947819e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.034157e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.878986e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.078603e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.990977e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.866215e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.897866e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.512252e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.073743e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.022621e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.501343e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.877243e-01*(s-1.200000e+01)-7.936839e+00; + return result; +} + + +static double jarquebera_jbtbl13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.0000) ) + { + x = 2*(s-0.000000)/1.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.713276e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.557541e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.459092e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.044145e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.546132e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.002374e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.349456e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.025669e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.590242e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-1.000000)/2.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.454383e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.467539e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.270774e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.075763e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.611647e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.990785e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.109212e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.135031e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.915919e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.522390e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.144701e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,13.0000) ) + { + x = 2*(s-3.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.736127e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.920809e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.175858e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.002049e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.158966e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.157781e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.762172e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.780347e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.193310e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.442421e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.547756e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.799944e-01*(s-1.300000e+01)-7.566269e+00; + return result; +} + + +static double jarquebera_jbtbl14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,1.0000) ) + { + x = 2*(s-0.000000)/1.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.698527e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.479081e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.640733e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.466899e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.469485e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.150009e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.965975e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.710210e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.327808e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-1.000000)/2.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -2.350359e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.421365e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.960468e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.149167e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.361109e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.976022e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.082700e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.563328e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.453123e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.917559e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.151067e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-3.000000)/12.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.746892e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.010441e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.566146e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.129690e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.929724e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.524227e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.192933e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.254730e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.620685e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.289618e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.112350e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.590621e-01*(s-1.500000e+01)-7.632238e+00; + return result; +} + + +static double jarquebera_jbtbl15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,2.0000) ) + { + x = 2*(s-0.000000)/2.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.043660e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.361653e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.009497e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.951784e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.377903e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.003253e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.271309e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,5.0000) ) + { + x = 2*(s-2.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.582778e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.349578e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.476514e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.717385e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.222591e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.635124e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.815993e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,17.0000) ) + { + x = 2*(s-5.000000)/12.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.115476e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.655936e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.404310e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.663794e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.868618e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.381447e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.444801e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.581503e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.468696e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.728509e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.206470e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.927937e-01*(s-1.700000e+01)-7.700983e+00; + return result; +} + + +static double jarquebera_jbtbl16(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,2.0000) ) + { + x = 2*(s-0.000000)/2.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.002570e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.298141e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.832803e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.877026e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.539436e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.439658e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.756911e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,5.0000) ) + { + x = 2*(s-2.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.486198e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.242944e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.020002e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.130531e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.512373e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.054876e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.556839e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,20.0000) ) + { + x = 2*(s-5.000000)/15.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.241608e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.832655e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.340545e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.361143e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.283219e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.484549e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.805968e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.057243e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.454439e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.177513e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.819209e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.391580e-01*(s-2.000000e+01)-7.963205e+00; + return result; +} + + +static double jarquebera_jbtbl17(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-0.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.566973e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.810330e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.840039e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.337294e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.383549e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.556515e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.656965e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.404569e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.447867e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,6.0000) ) + { + x = 2*(s-3.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.905684e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.222920e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.146667e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.809176e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.057028e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.211838e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.099683e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.161105e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.225465e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,24.0000) ) + { + x = 2*(s-6.000000)/18.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.594282e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.917838e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.455980e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.999589e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.604263e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.484445e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.819937e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.930390e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.771761e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.232581e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.029083e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.127771e-01*(s-2.400000e+01)-8.400197e+00; + return result; +} + + +static double jarquebera_jbtbl18(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-0.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.526802e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.762373e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.598890e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.189437e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.971721e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.823067e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.064501e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.014932e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.953513e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,6.0000) ) + { + x = 2*(s-3.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.818669e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.070918e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.277196e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.879817e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.887357e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.638451e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.502800e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.165796e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.034960e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,20.0000) ) + { + x = 2*(s-6.000000)/14.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.010656e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.496296e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.002227e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.338250e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.137036e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.586202e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.736384e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.332251e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.877982e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.160963e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.547247e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.684623e-01*(s-2.000000e+01)-7.428883e+00; + return result; +} + + +static double jarquebera_jbtbl19(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,3.0000) ) + { + x = 2*(s-0.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.490213e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.719633e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.459123e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.034878e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.113868e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.030922e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.054022e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.525623e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.277360e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,6.0000) ) + { + x = 2*(s-3.000000)/3.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -3.744750e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.977749e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.223716e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.363889e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.711774e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.557257e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.254794e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.034207e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.498107e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,20.0000) ) + { + x = 2*(s-6.000000)/14.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.872768e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.430689e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.136575e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.726627e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.421110e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.581510e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.559520e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.838208e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.428839e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.170682e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.006647e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.539373e-01*(s-2.000000e+01)-7.206941e+00; + return result; +} + + +static double jarquebera_jbtbl20(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.854794e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.948947e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.632184e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.139397e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.006237e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.810031e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.573620e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.951242e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.274092e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.464196e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.882139e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.575144e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.822804e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.061348e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.908404e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.978353e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.030989e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.327151e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.346404e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.840051e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.578551e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.813886e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.905973e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.358489e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.450795e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.941157e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.432418e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.070537e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.375654e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.367378e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.890859e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.679782e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -7.015854e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.487737e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.244254e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.318007e-01*(s-2.500000e+01)-7.742185e+00; + return result; +} + + +static double jarquebera_jbtbl30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.630822e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.724298e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.872756e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.658268e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.573597e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.994157e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.994825e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.394303e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.785029e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.990264e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.037838e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.755546e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.774473e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.821395e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.392603e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.353313e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.539322e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.197018e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.396848e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.804293e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.867928e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.768758e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.211792e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.925799e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.046235e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.536469e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.489642e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.263462e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.177316e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.590637e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.028212e-01*(s-2.500000e+01)-6.855288e+00; + return result; +} + + +static double jarquebera_jbtbl50(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.436279e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.519711e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.148699e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.001204e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.207620e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.034778e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.220322e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.033260e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.588280e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.851653e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.287733e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.234645e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.189127e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.429738e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.058822e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 9.086776e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.445783e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.311671e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.261298e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.496987e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.605249e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.162282e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.921095e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.888603e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.080113e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -9.313116e-02*(s-2.500000e+01)-6.479154e+00; + return result; +} + + +static double jarquebera_jbtbl65(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.360024e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.434631e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.514580e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 7.332038e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.158197e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.121233e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.051056e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.148601e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.214233e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.487977e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.424720e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.116715e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.043152e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.718149e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.313701e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.097305e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.181031e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.256975e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.858951e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.895179e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.933237e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -9.443768e-02*(s-2.500000e+01)-6.419137e+00; + return result; +} + + +static double jarquebera_jbtbl100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.257021e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.313418e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.628931e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.264287e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.518487e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.499826e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.836044e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.056508e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.279690e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.665746e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.290012e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.487632e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.704465e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.211669e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.866099e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.399767e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.498208e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.080097e-01*(s-2.500000e+01)-6.481094e+00; + return result; +} + + +static double jarquebera_jbtbl130(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.207999e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.253864e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.618032e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.112729e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.210546e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.732602e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.410527e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.026324e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.331990e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.779129e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.674749e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.669077e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.679136e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 8.833221e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -5.893951e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.475304e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.116734e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.045722e-01*(s-2.500000e+01)-6.510314e+00; + return result; +} + + +static double jarquebera_jbtbl200(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.146155e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.177398e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.297970e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.869745e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.717288e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.982108e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.427636e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.034235e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.455006e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.942996e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.973795e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.418812e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.156778e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.896705e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.086071e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.152176e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.725393e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.132404e-01*(s-2.500000e+01)-6.764034e+00; + return result; +} + + +static double jarquebera_jbtbl301(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.104290e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.125800e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.595847e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.219666e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.502210e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.414543e-05, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.754115e-05, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.065955e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.582060e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.004472e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -4.709092e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.105779e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.197391e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.386780e-04, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.311384e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.918763e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.626584e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.293626e-01*(s-2.500000e+01)-7.066995e+00; + return result; +} + + +static double jarquebera_jbtbl501(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.067426e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.079765e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -5.463005e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 6.875659e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.127574e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.740694e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.044502e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.746714e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 3.810594e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.197111e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.628194e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -8.846221e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.386405e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.418332e-01*(s-2.500000e+01)-7.468952e+00; + return result; +} + + +static double jarquebera_jbtbl701(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.050999e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.059769e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -3.922680e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 4.847054e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.192182e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.860007e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.963942e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.838711e-02, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.893112e-04, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.159788e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -6.917851e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -9.817020e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.383727e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -1.532706e-01*(s-2.500000e+01)-7.845715e+00; + return result; +} + + +static double jarquebera_jbtbl1401(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + if( ae_fp_less_eq(s,4.0000) ) + { + x = 2*(s-0.000000)/4.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -1.026266e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.030061e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.259222e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 2.536254e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,15.0000) ) + { + x = 2*(s-4.000000)/11.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -4.329849e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -2.095443e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 1.759363e-01, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -7.751359e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -6.124368e-03, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.793114e-03, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + if( ae_fp_less_eq(s,25.0000) ) + { + x = 2*(s-15.000000)/10.000000-1; + tj = 1; + tj1 = x; + jarquebera_jbcheb(x, -7.544330e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, -1.225382e+00, &tj, &tj1, &result, _state); + jarquebera_jbcheb(x, 5.392349e-02, &tj, &tj1, &result, _state); + if( ae_fp_greater(result,0) ) + { + result = 0; + } + return result; + } + result = -2.019375e-01*(s-2.500000e+01)-8.715788e+00; + return result; +} + + +static void jarquebera_jbcheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state) +{ + double t; + + + *r = *r+c*(*tj); + t = 2*x*(*tj1)-(*tj); + *tj = *tj1; + *tj1 = t; +} + + + + +/************************************************************************* +Mann-Whitney U-test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions of the same shape and same median or whether +their medians are different. + +The following tests are performed: + * two-tailed test (null hypothesis - the medians are equal) + * left-tailed test (null hypothesis - the median of the first sample + is greater than or equal to the median of the second sample) + * right-tailed test (null hypothesis - the median of the first sample + is less than or equal to the median of the second sample). + +Requirements: + * the samples are independent + * X and Y are continuous distributions (or discrete distributions well- + approximating continuous distributions) + * distributions of X and Y have the same shape. The only possible + difference is their position (i.e. the value of the median) + * the number of elements in each sample is not less than 5 + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distributions to be normal. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. M>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with satisfactory accuracy in interval [0.0001, 1]. +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + +Relative precision of approximation of p-value: + +N M Max.err. Rms.err. +5..10 N..10 1.4e-02 6.0e-04 +5..10 N..100 2.2e-02 5.3e-06 +10..15 N..15 1.0e-02 3.2e-04 +10..15 N..100 1.0e-02 2.2e-05 +15..100 N..100 6.1e-03 2.7e-06 + +For N,M>100 accuracy checks weren't put into practice, but taking into +account characteristics of asymptotic approximation used, precision should +not be sharply different from the values for interval [5, 100]. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void mannwhitneyutest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_frame _frame_block; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t t; + double tmp; + ae_int_t tmpi; + ae_int_t ns; + ae_vector r; + ae_vector c; + double u; + double p; + double mp; + double s; + double sigma; + double mu; + ae_int_t tiecount; + ae_vector tiesize; + + ae_frame_make(_state, &_frame_block); + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + ae_vector_init(&r, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_INT, _state, ae_true); + ae_vector_init(&tiesize, 0, DT_INT, _state, ae_true); + + + /* + * Prepare + */ + if( n<=4||m<=4 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + ae_frame_leave(_state); + return; + } + ns = n+m; + ae_vector_set_length(&r, ns-1+1, _state); + ae_vector_set_length(&c, ns-1+1, _state); + for(i=0; i<=n-1; i++) + { + r.ptr.p_double[i] = x->ptr.p_double[i]; + c.ptr.p_int[i] = 0; + } + for(i=0; i<=m-1; i++) + { + r.ptr.p_double[n+i] = y->ptr.p_double[i]; + c.ptr.p_int[n+i] = 1; + } + + /* + * sort {R, C} + */ + if( ns!=1 ) + { + i = 2; + do + { + t = i; + while(t!=1) + { + k = t/2; + if( ae_fp_greater_eq(r.ptr.p_double[k-1],r.ptr.p_double[t-1]) ) + { + t = 1; + } + else + { + tmp = r.ptr.p_double[k-1]; + r.ptr.p_double[k-1] = r.ptr.p_double[t-1]; + r.ptr.p_double[t-1] = tmp; + tmpi = c.ptr.p_int[k-1]; + c.ptr.p_int[k-1] = c.ptr.p_int[t-1]; + c.ptr.p_int[t-1] = tmpi; + t = k; + } + } + i = i+1; + } + while(i<=ns); + i = ns-1; + do + { + tmp = r.ptr.p_double[i]; + r.ptr.p_double[i] = r.ptr.p_double[0]; + r.ptr.p_double[0] = tmp; + tmpi = c.ptr.p_int[i]; + c.ptr.p_int[i] = c.ptr.p_int[0]; + c.ptr.p_int[0] = tmpi; + t = 1; + while(t!=0) + { + k = 2*t; + if( k>i ) + { + t = 0; + } + else + { + if( k=1); + } + + /* + * compute tied ranks + */ + i = 0; + tiecount = 0; + ae_vector_set_length(&tiesize, ns-1+1, _state); + while(i<=ns-1) + { + j = i+1; + while(j<=ns-1) + { + if( ae_fp_neq(r.ptr.p_double[j],r.ptr.p_double[i]) ) + { + break; + } + j = j+1; + } + for(k=i; k<=j-1; k++) + { + r.ptr.p_double[k] = 1+(double)(i+j-1)/(double)2; + } + tiesize.ptr.p_int[tiecount] = j-i; + tiecount = tiecount+1; + i = j; + } + + /* + * Compute U + */ + u = 0; + for(i=0; i<=ns-1; i++) + { + if( c.ptr.p_int[i]==0 ) + { + u = u+r.ptr.p_double[i]; + } + } + u = n*m+n*(n+1)/2-u; + + /* + * Result + */ + mu = (double)(n*m)/(double)2; + tmp = ns*(ae_sqr(ns, _state)-1)/12; + for(i=0; i<=tiecount-1; i++) + { + tmp = tmp-tiesize.ptr.p_int[i]*(ae_sqr(tiesize.ptr.p_int[i], _state)-1)/12; + } + sigma = ae_sqrt((double)(m*n)/(double)ns/(ns-1)*tmp, _state); + s = (u-mu)/sigma; + if( ae_fp_less_eq(s,0) ) + { + p = ae_exp(mannwhitneyu_usigma(-(u-mu)/sigma, n, m, _state), _state); + mp = 1-ae_exp(mannwhitneyu_usigma(-(u-1-mu)/sigma, n, m, _state), _state); + } + else + { + mp = ae_exp(mannwhitneyu_usigma((u-mu)/sigma, n, m, _state), _state); + p = 1-ae_exp(mannwhitneyu_usigma((u+1-mu)/sigma, n, m, _state), _state); + } + *bothtails = ae_maxreal(2*ae_minreal(p, mp, _state), 1.0E-4, _state); + *lefttail = ae_maxreal(mp, 1.0E-4, _state); + *righttail = ae_maxreal(p, 1.0E-4, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Sequential Chebyshev interpolation. +*************************************************************************/ +static void mannwhitneyu_ucheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state) +{ + double t; + + + *r = *r+c*(*tj); + t = 2*x*(*tj1)-(*tj); + *tj = *tj1; + *tj1 = t; +} + + +/************************************************************************* +Three-point polynomial interpolation. +*************************************************************************/ +static double mannwhitneyu_uninterpolate(double p1, + double p2, + double p3, + ae_int_t n, + ae_state *_state) +{ + double t1; + double t2; + double t3; + double t; + double p12; + double p23; + double result; + + + t1 = 1.0/15.0; + t2 = 1.0/30.0; + t3 = 1.0/100.0; + t = 1.0/n; + p12 = ((t-t2)*p1+(t1-t)*p2)/(t1-t2); + p23 = ((t-t3)*p2+(t2-t)*p3)/(t2-t3); + result = ((t-t3)*p12+(t1-t)*p23)/(t1-t3); + return result; +} + + +/************************************************************************* +Tail(0, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma000(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-6.76984e-01, -6.83700e-01, -6.89873e-01, n2, _state); + p2 = mannwhitneyu_uninterpolate(-6.83700e-01, -6.87311e-01, -6.90957e-01, n2, _state); + p3 = mannwhitneyu_uninterpolate(-6.89873e-01, -6.90957e-01, -6.92175e-01, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(0.75, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma075(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-1.44500e+00, -1.45906e+00, -1.47063e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-1.45906e+00, -1.46856e+00, -1.47644e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-1.47063e+00, -1.47644e+00, -1.48100e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(1.5, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma150(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-2.65380e+00, -2.67352e+00, -2.69011e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-2.67352e+00, -2.68591e+00, -2.69659e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-2.69011e+00, -2.69659e+00, -2.70192e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(2.25, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma225(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-4.41465e+00, -4.42260e+00, -4.43702e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-4.42260e+00, -4.41639e+00, -4.41928e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-4.43702e+00, -4.41928e+00, -4.41030e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(3.0, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma300(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-6.89839e+00, -6.83477e+00, -6.82340e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-6.83477e+00, -6.74559e+00, -6.71117e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-6.82340e+00, -6.71117e+00, -6.64929e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(3.33, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma333(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-8.31272e+00, -8.17096e+00, -8.13125e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-8.17096e+00, -8.00156e+00, -7.93245e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-8.13125e+00, -7.93245e+00, -7.82502e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(3.66, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma367(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-9.98837e+00, -9.70844e+00, -9.62087e+00, n2, _state); + p2 = mannwhitneyu_uninterpolate(-9.70844e+00, -9.41156e+00, -9.28998e+00, n2, _state); + p3 = mannwhitneyu_uninterpolate(-9.62087e+00, -9.28998e+00, -9.11686e+00, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(4.0, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma400(ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double p1; + double p2; + double p3; + double result; + + + p1 = mannwhitneyu_uninterpolate(-1.20250e+01, -1.14911e+01, -1.13231e+01, n2, _state); + p2 = mannwhitneyu_uninterpolate(-1.14911e+01, -1.09927e+01, -1.07937e+01, n2, _state); + p3 = mannwhitneyu_uninterpolate(-1.13231e+01, -1.07937e+01, -1.05285e+01, n2, _state); + result = mannwhitneyu_uninterpolate(p1, p2, p3, n1, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 5) +*************************************************************************/ +static double mannwhitneyu_utbln5n5(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/2.611165e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -2.596264e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.412086e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.858542e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.614282e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.372686e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.524731e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.435331e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.284665e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.184141e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.298360e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 7.447272e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.938769e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.276205e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.138481e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.684625e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.558104e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 6) +*************************************************************************/ +static double mannwhitneyu_utbln5n6(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/2.738613e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -2.810459e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.684429e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.712858e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.009324e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.644391e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.034173e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.953498e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.279293e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.563485e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.971952e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.506309e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.541406e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.283205e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.016347e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.221626e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.286752e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 7) +*************************************************************************/ +static double mannwhitneyu_utbln5n7(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/2.841993e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -2.994677e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.923264e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.506190e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.054280e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.794587e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.726290e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.534180e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.517845e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.904428e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.882443e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.482988e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.114875e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.515082e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.996056e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.293581e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.349444e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 8) +*************************************************************************/ +static double mannwhitneyu_utbln5n8(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/2.927700e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.155727e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.135078e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.247203e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.309697e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.993725e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.567219e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.383704e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.002188e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.487322e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.443899e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.688270e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.600339e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.874948e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.811593e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.072353e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.659457e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 9) +*************************************************************************/ +static double mannwhitneyu_utbln5n9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.298162e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.325016e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.939852e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.563029e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.222652e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.195200e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.445665e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.204792e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.775217e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.527781e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.221948e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.242968e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.607959e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.771285e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.694026e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.481190e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 10) +*************************************************************************/ +static double mannwhitneyu_utbln5n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.061862e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.425360e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.496710e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.587658e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.812005e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.427637e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.515702e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.406867e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.796295e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.237591e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.654249e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.181165e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.011665e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.417927e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.534880e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.791255e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.871512e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 11) +*************************************************************************/ +static double mannwhitneyu_utbln5n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.115427e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.539959e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.652998e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.196503e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.054363e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.618848e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.109411e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.786668e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.215648e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.484220e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.935991e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.396191e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.894177e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.206979e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.519055e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.210326e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.189679e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 12) +*************************************************************************/ +static double mannwhitneyu_utbln5n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.162278e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.644007e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.796173e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.771177e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.290043e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.794686e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.702110e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.185959e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.416259e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.592056e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.201530e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.754365e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.978945e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.012032e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.304579e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.100378e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.728269e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 13) +*************************************************************************/ +static double mannwhitneyu_utbln5n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.203616e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.739120e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.928117e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.031605e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.519403e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.962648e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.292183e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.809293e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.465156e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.456278e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.446055e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.109490e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.218256e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.941479e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.058603e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.824402e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.830947e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 14) +*************************************************************************/ +static double mannwhitneyu_utbln5n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.240370e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.826559e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.050370e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.083408e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.743164e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.012030e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.884686e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.059656e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.327521e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.134026e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.584201e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.440618e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.524133e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.990007e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.887334e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.534977e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.705395e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 15) +*************************************************************************/ +static double mannwhitneyu_utbln5n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.851572e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.082033e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.095983e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.814595e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.073148e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.420213e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.517175e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.344180e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.371393e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.711443e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.228569e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.683483e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.267112e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.156044e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.131316e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.301023e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 16) +*************************************************************************/ +static double mannwhitneyu_utbln5n16(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.852210e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.077482e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.091186e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.797282e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.084994e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.667054e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.843909e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.456732e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.039830e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.723508e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.940608e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.478285e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.649144e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.237703e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.707410e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.874293e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 17) +*************************************************************************/ +static double mannwhitneyu_utbln5n17(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.851752e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.071259e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.084700e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.758898e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.073846e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.684838e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.964936e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.782442e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.956362e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.984727e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.196936e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.558262e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.690746e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.364855e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.401006e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.546748e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 18) +*************************************************************************/ +static double mannwhitneyu_utbln5n18(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.850840e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.064799e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.077651e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.712659e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.049217e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.571333e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.929809e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.752044e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.949464e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.896101e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.614460e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.384357e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.489113e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.445725e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.945636e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.424653e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 19) +*************************************************************************/ +static double mannwhitneyu_utbln5n19(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.850027e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.059159e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.071106e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.669960e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.022780e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.442555e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.851335e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.433865e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.514465e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.332989e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.606099e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.341945e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.402164e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.039761e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.512831e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.284427e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 20) +*************************************************************************/ +static double mannwhitneyu_utbln5n20(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.849651e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.054729e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.065747e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.636243e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.003234e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.372789e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.831551e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.763090e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.830626e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.122384e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.108328e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.557983e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.945666e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.965696e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.493236e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.162591e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 21) +*************************************************************************/ +static double mannwhitneyu_utbln5n21(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.849649e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.051155e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.061430e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.608869e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.902788e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.346562e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.874709e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.682887e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.026206e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.534551e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.990575e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.713334e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.737011e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.304571e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.133110e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.123457e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 22) +*************************************************************************/ +static double mannwhitneyu_utbln5n22(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.849598e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.047605e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.057264e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.579513e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.749602e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.275137e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.881768e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.177374e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.981056e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.696290e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.886803e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.085378e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.675242e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.426367e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.039613e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.662378e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 23) +*************************************************************************/ +static double mannwhitneyu_utbln5n23(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.849269e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.043761e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.052735e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.544683e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.517503e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.112082e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.782070e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.549483e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.747329e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.694263e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.147141e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.526209e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.039173e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.235615e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.656546e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.014423e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 24) +*************************************************************************/ +static double mannwhitneyu_utbln5n24(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.848925e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.040178e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.048355e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.510198e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.261134e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.915864e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.627423e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.307345e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.732992e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.869652e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.494176e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.047533e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.178439e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.424171e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.829195e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.840810e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 25) +*************************************************************************/ +static double mannwhitneyu_utbln5n25(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.848937e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.037512e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.044866e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.483269e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.063682e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.767778e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.508540e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.332756e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.881511e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.124041e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.368456e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.930499e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.779630e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.029528e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.658678e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.289695e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 26) +*************************************************************************/ +static double mannwhitneyu_utbln5n26(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.849416e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.035915e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.042493e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.466021e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.956432e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.698914e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.465689e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.035254e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.674614e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.492734e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.014021e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.944953e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.255750e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.075841e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.989330e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.134862e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 27) +*************************************************************************/ +static double mannwhitneyu_utbln5n27(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.850070e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.034815e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.040650e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.453117e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.886426e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.661702e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.452346e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.002476e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.720126e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.001400e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.729826e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.740640e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.206333e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.366093e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.193471e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.804091e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 28) +*************************************************************************/ +static double mannwhitneyu_utbln5n28(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.850668e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.033786e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.038853e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.440281e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.806020e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.612883e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.420436e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.787982e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.535230e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.263121e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.849609e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.863967e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.391610e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.720294e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.952273e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.901413e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 29) +*************************************************************************/ +static double mannwhitneyu_utbln5n29(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.851217e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.032834e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.037113e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.427762e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.719146e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.557172e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.375498e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.452033e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.187516e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.916936e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.065533e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.067301e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.615824e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.432244e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.417795e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.710038e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 30) +*************************************************************************/ +static double mannwhitneyu_utbln5n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.851845e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.032148e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.035679e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.417758e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.655330e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.522132e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.352106e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.326911e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.064969e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.813321e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.683881e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.813346e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.627085e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.832107e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.519336e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.888530e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 5, 100) +*************************************************************************/ +static double mannwhitneyu_utbln5n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.250000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.877940e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.039324e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.022243e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.305825e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.960119e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.112000e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.138868e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.418164e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.174520e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.489617e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.878301e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.302233e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.054113e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.458862e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.186591e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.623412e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 6) +*************************************************************************/ +static double mannwhitneyu_utbln6n6(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/2.882307e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.054075e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.998804e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.681518e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.067578e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.709435e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.952661e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.641700e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.304572e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.336275e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.770385e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.401891e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.246148e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.442663e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.502866e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.105855e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.739371e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 7) +*************************************************************************/ +static double mannwhitneyu_utbln6n7(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.265287e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.274613e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.582352e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.334293e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.915502e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.108091e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.546701e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.298827e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.891501e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.313717e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.989501e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.914594e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.062372e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.158841e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.596443e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.185662e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 8) +*************************************************************************/ +static double mannwhitneyu_utbln6n8(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.098387e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.450954e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.520462e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.420299e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.604853e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.165840e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.008756e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.723402e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.843521e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.883405e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.720980e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.301709e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.948034e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.776243e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.623736e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.742068e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.796927e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 9) +*************************************************************************/ +static double mannwhitneyu_utbln6n9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.181981e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.616113e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.741650e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.204487e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.873068e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.446794e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.632286e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.266481e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.280067e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.780687e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.480242e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.592200e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.581019e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.264231e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.347174e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.167535e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.092185e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 10) +*************************************************************************/ +static double mannwhitneyu_utbln6n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.253957e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.764382e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.942366e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.939896e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.137812e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.720270e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.281070e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.901060e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.824937e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.802812e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.258132e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.233536e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.085530e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.212151e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.001329e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.226048e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.035298e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 11) +*************************************************************************/ +static double mannwhitneyu_utbln6n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.316625e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.898597e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.125710e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.063297e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.396852e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.990126e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.927977e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.726500e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.858745e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.654590e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.217736e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.989770e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.768493e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.924364e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.140215e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.647914e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.924802e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 12) +*************************************************************************/ +static double mannwhitneyu_utbln6n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.371709e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.020941e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.294250e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.128842e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.650389e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.248611e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.578510e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.162852e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.746982e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.454209e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.128042e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.936650e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.530794e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.665192e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.994144e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.662249e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.368541e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 13) +*************************************************************************/ +static double mannwhitneyu_utbln6n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.420526e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.133167e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.450016e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.191088e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.898220e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.050249e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.226901e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.471113e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.007470e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.049420e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.059074e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.881249e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.452780e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.441805e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.787493e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.483957e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.481590e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 14) +*************************************************************************/ +static double mannwhitneyu_utbln6n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.450000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.201268e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.542568e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.226965e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.046029e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.136657e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.786757e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.843748e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.588022e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.253029e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.667188e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.788330e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.474545e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.540494e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.951188e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.863323e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.220904e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 15) +*************************************************************************/ +static double mannwhitneyu_utbln6n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.450000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.195689e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.526567e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.213617e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.975035e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.118480e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.859142e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.083312e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.298720e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.766708e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.026356e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.093113e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.135168e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.136376e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.190870e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.435972e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.413129e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 30) +*************************************************************************/ +static double mannwhitneyu_utbln6n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.450000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.166269e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.427399e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.118239e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.360847e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.745885e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.025041e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.187179e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.432089e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.408451e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.388774e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.795560e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.304136e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.258516e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.180236e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.388679e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.836027e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 6, 100) +*************************************************************************/ +static double mannwhitneyu_utbln6n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.450000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.181350e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.417919e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.094201e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.195883e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.818937e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.514202e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.125047e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.022148e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.284181e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.157766e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.023752e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.127985e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.221690e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.516179e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.501398e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.380220e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 7) +*************************************************************************/ +static double mannwhitneyu_utbln7n7(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.130495e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.501264e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.584790e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.577311e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.617002e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.145186e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.023462e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.408251e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.626515e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.072492e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.722926e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.095445e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.842602e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.751427e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.008927e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.892431e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.772386e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 8) +*************************************************************************/ +static double mannwhitneyu_utbln7n8(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.240370e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.709965e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.862154e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.504541e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.900195e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.439995e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.678028e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.485540e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.437047e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.440092e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.114227e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.516569e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.829457e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.787550e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.761866e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.991911e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.533481e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 9) +*************************************************************************/ +static double mannwhitneyu_utbln7n9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.334314e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.896550e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.112671e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.037277e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.181695e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.765190e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.360116e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.695960e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.780578e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.963843e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.616148e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.852104e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.390744e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.014041e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.888101e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.467474e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.004611e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 10) +*************************************************************************/ +static double mannwhitneyu_utbln7n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.415650e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.064844e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.340749e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.118888e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.459730e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.097781e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.057688e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.097406e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.209262e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.065641e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.196677e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.313994e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.827157e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.822284e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.389090e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.340850e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.395172e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 11) +*************************************************************************/ +static double mannwhitneyu_utbln7n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.486817e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.217795e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.549783e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.195905e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.733093e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.428447e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.760093e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.431676e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.717152e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.032199e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.832423e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.905979e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.302799e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.464371e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.456211e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.736244e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.140712e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 12) +*************************************************************************/ +static double mannwhitneyu_utbln7n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.235822e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.564100e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.190813e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.686546e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.395083e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.967359e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.747096e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.304144e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.903198e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.134906e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.175035e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.266224e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.892931e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.604706e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.070459e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.427010e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 13) +*************************************************************************/ +static double mannwhitneyu_utbln7n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.222204e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.532300e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.164642e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.523768e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.531984e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.467857e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.483804e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.524136e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.077740e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.745218e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.602085e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.828831e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.994070e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.873879e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.341937e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.706444e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 14) +*************************************************************************/ +static double mannwhitneyu_utbln7n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.211763e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.507542e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.143640e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.395755e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.808020e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.044259e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.182308e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.057325e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.724255e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.303900e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.113148e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.102514e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.559442e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.634986e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.776476e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.054489e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 15) +*************************************************************************/ +static double mannwhitneyu_utbln7n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.204898e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.489960e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.129172e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.316741e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.506107e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.983676e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.258013e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.262515e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.984156e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.912108e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.974023e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.056195e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.090842e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.232620e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.816339e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.020421e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 30) +*************************************************************************/ +static double mannwhitneyu_utbln7n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.176536e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.398705e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.045481e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.821982e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.962304e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.698132e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.062667e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.282353e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.014836e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.035683e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.004137e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.801453e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.920705e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.518735e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.821501e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.801008e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 7, 100) +*************************************************************************/ +static double mannwhitneyu_utbln7n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.500000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.188337e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.386949e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.022834e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.686517e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.323516e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.399392e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.644333e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.617044e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.031396e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.792066e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.675457e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.673416e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.258552e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.174214e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.073644e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.349958e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 8) +*************************************************************************/ +static double mannwhitneyu_utbln8n8(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.360672e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -3.940217e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.168913e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.051485e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.195325e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.775196e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.385506e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.244902e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.525632e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.771275e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.332874e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.079599e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.882551e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.407944e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.769844e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.062433e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.872535e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 9) +*************************************************************************/ +static double mannwhitneyu_utbln8n9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.464102e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.147004e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.446939e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.146155e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.488561e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.144561e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.116917e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.205667e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.515661e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.618616e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.599011e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.457324e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.482917e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.488267e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.469823e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.957591e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.058326e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 10) +*************************************************************************/ +static double mannwhitneyu_utbln8n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.554093e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.334282e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.700860e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.235253e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.778489e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.527324e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.862885e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.589781e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.507355e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.717526e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.215726e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.848696e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.918854e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.219614e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.753761e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.573688e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.602177e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 11) +*************************************************************************/ +static double mannwhitneyu_utbln8n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.421882e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.812457e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.266153e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.849344e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.971527e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.258944e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.944820e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.894685e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.031836e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.514330e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.351660e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.206748e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.492600e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.005338e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.780099e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.673599e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 12) +*************************************************************************/ +static double mannwhitneyu_utbln8n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.398211e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.762214e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.226296e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.603837e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.643223e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.502438e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.544574e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.647734e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.442259e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.011484e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.384758e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.998259e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.659985e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.331046e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.638478e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.056785e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 13) +*************************************************************************/ +static double mannwhitneyu_utbln8n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.380670e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.724511e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.195851e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.420511e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.609928e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.893999e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.115919e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.291410e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.339664e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.801548e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.534710e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.793250e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.806718e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.384624e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.120582e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.936453e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 14) +*************************************************************************/ +static double mannwhitneyu_utbln8n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.368494e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.697171e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.174440e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.300621e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.087393e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.685826e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.085254e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.525658e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.966647e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.453388e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.826066e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.501958e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.336297e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.251972e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.118456e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.415959e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 15) +*************************************************************************/ +static double mannwhitneyu_utbln8n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.358397e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.674485e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.155941e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.195780e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.544830e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.426183e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.309902e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.650956e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.068874e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.538544e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.192525e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.073905e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.079673e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.423572e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.579647e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.765904e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 30) +*************************************************************************/ +static double mannwhitneyu_utbln8n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.318823e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.567159e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.064864e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.688413e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.153712e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.309389e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.226861e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.523815e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.780987e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.166866e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.922431e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.466397e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.690036e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.008185e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.271903e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.534751e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 8, 100) +*************************************************************************/ +static double mannwhitneyu_utbln8n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.600000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.324531e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.547071e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.038129e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.541549e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.525605e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.044992e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.085713e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.017871e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.459226e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.092064e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.024349e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 7.366347e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.385637e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.321722e-08, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.439286e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.058079e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 9) +*************************************************************************/ +static double mannwhitneyu_utbln9n9(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.576237e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.372857e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.750859e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.248233e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.792868e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.559372e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.894941e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.643256e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.091370e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.285034e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.112997e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.806229e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.150741e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.509825e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.891051e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.485013e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.343653e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 10) +*************************************************************************/ +static double mannwhitneyu_utbln9n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.516726e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.939333e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.305046e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.935326e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.029141e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.420592e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.053140e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.065930e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.523581e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.544888e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.813741e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.510631e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.536057e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.833815e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.189692e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.615050e-03, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 11) +*************************************************************************/ +static double mannwhitneyu_utbln9n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.481308e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.867483e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.249072e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.591790e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.400128e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.341992e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.463680e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.487211e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.671196e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.343472e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.544146e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.802335e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.117084e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.217443e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.858766e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.193687e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 12) +*************************************************************************/ +static double mannwhitneyu_utbln9n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.456776e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.817037e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.209788e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.362108e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.171356e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.661557e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.026141e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.361908e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.093885e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.298389e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.663603e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.768522e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.579015e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.868677e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.440652e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.523037e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 13) +*************************************************************************/ +static double mannwhitneyu_utbln9n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.438840e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.779308e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.180614e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.196489e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.346621e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.234857e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.796211e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.575715e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.525647e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.964651e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.275235e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.299124e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.397416e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.295781e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.237619e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 7.269692e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 14) +*************************************************************************/ +static double mannwhitneyu_utbln9n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.425981e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.751545e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.159543e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.086570e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.917446e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.120112e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.175519e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.515473e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.727772e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.070629e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.677569e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.876953e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.233502e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.508182e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.120389e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.847212e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 15) +*************************************************************************/ +static double mannwhitneyu_utbln9n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.414952e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.727612e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.140634e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.981231e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.382635e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.853575e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.571051e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.567625e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.214197e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.448700e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.712669e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.015050e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.438610e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.301363e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.309386e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.164772e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 30) +*************************************************************************/ +static double mannwhitneyu_utbln9n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.370720e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.615712e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.050023e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.504775e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.318265e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.646826e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.741492e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.735360e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.966911e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.100738e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.348991e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.527687e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.917286e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.397466e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.360175e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.892252e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 9, 100) +*************************************************************************/ +static double mannwhitneyu_utbln9n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.372506e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.590966e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.021758e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.359849e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.755519e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.533166e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.936659e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.634913e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.730053e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.791845e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.030682e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.228663e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.631175e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.636749e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.404599e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.789872e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 10) +*************************************************************************/ +static double mannwhitneyu_utbln10n10(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.468831e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.844398e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.231728e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.486073e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.781321e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.971425e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.215371e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.828451e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.419872e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.430165e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.740363e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.049211e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.269371e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.211393e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.232314e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.016081e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 11) +*************************************************************************/ +static double mannwhitneyu_utbln10n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.437998e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.782296e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.184732e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.219585e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.457012e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.296008e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.481501e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.527940e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.953426e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.563840e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.574403e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.535775e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.338037e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.002654e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.852676e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.318132e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 12) +*************************************************************************/ +static double mannwhitneyu_utbln10n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.416082e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.737458e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.150952e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.036884e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.609030e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.908684e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.439666e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.162647e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.451601e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.148757e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.803981e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.731621e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.346903e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.013151e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.956148e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.438381e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 13) +*************************************************************************/ +static double mannwhitneyu_utbln10n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.399480e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.702863e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.124829e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.897428e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.979802e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.634368e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.180461e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.484926e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.864376e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.186576e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.886925e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.836828e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.074756e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.209547e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.883266e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.380143e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 14) +*************************************************************************/ +static double mannwhitneyu_utbln10n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.386924e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.676124e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.104740e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.793826e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.558886e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.492462e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.052903e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.917782e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.878696e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.576046e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.764551e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.288778e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.757658e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.299101e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.265197e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.384503e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 15) +*************************************************************************/ +static double mannwhitneyu_utbln10n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.376846e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.654247e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.088083e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.705945e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.169677e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.317213e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.264836e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.548024e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.633910e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.505621e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.658588e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.320254e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.175277e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.122317e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.675688e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.661363e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 30) +*************************************************************************/ +static double mannwhitneyu_utbln10n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.333977e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.548099e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.004444e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.291014e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.523674e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.828211e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.716917e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.894256e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.433371e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.522675e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.764192e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.140235e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.629230e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.541895e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.944946e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.726360e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 10, 100) +*************************************************************************/ +static double mannwhitneyu_utbln10n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.650000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.334008e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.522316e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.769627e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.158110e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.053650e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.242235e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.173571e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.033661e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.824732e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.084420e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.610036e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.728155e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.217130e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.340966e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.001235e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.694052e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 11) +*************************************************************************/ +static double mannwhitneyu_utbln11n11(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.519760e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.880694e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.200698e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.174092e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.072304e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.054773e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.506613e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.813942e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.223644e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.417416e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.499166e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.194332e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 7.369096e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.968590e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.630532e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.061000e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 12) +*************************************************************************/ +static double mannwhitneyu_utbln11n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.495790e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.832622e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.165420e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.987306e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.265621e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.723537e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.347406e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.353464e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.613369e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.102522e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.237709e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.665652e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.626903e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.167518e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.564455e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.047320e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 13) +*************************************************************************/ +static double mannwhitneyu_utbln11n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.477880e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.796242e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.138769e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.851739e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.722104e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.548304e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.176683e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.817895e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.842451e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.935870e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.421777e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.238831e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.867026e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.458255e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.306259e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.961487e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 14) +*************************************************************************/ +static double mannwhitneyu_utbln11n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.463683e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.766969e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.117082e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.739574e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.238865e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.350306e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.425871e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.640172e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.660633e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.879883e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.349658e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.271795e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.304544e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.024201e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.816867e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.596787e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 15) +*************************************************************************/ +static double mannwhitneyu_utbln11n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.452526e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.743570e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.099705e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.650612e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.858285e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.187036e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.689241e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.294360e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.072623e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.278008e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.322382e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.131558e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.305669e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.825627e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.332689e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.120973e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 30) +*************************************************************************/ +static double mannwhitneyu_utbln11n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.402621e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.627440e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.011333e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.224126e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.232856e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.859347e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.377381e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.756709e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.033230e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.875472e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.608399e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.102943e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.740693e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.343139e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.196878e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.658062e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 11, 100) +*************************************************************************/ +static double mannwhitneyu_utbln11n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.398795e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.596486e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.814761e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.085187e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.766529e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.379425e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.986351e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.214705e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.360075e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.260869e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.033307e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.727087e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.393883e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.242989e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.111928e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.898823e-09, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 12) +*************************************************************************/ +static double mannwhitneyu_utbln12n12(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.472616e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.786627e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.132099e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.817523e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.570179e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.479511e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.799492e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.565350e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.530139e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.380132e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.242761e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.576269e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.018771e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.933911e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.002799e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.022048e-06, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 13) +*************************************************************************/ +static double mannwhitneyu_utbln12n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.454800e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.750794e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.105988e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.684754e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.011826e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.262579e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.044492e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.478741e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.322165e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.621104e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.068753e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.468396e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.056235e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.327375e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.914877e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.784191e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 14) +*************************************************************************/ +static double mannwhitneyu_utbln12n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.440910e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.722404e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.085254e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.579439e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.563738e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.066730e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.129346e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.014531e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.129679e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.000909e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.996174e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.377924e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.936304e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.051098e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.025820e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 8.730585e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 15) +*************************************************************************/ +static double mannwhitneyu_utbln12n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.430123e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.700008e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.068971e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.499725e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.250897e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.473145e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.680008e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.483350e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.766992e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.891081e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.015140e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.977756e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.707414e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.114786e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.238865e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.381445e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 30) +*************************************************************************/ +static double mannwhitneyu_utbln12n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.380023e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.585782e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.838583e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.103394e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.834015e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.635212e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.948212e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.574169e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.747980e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.833672e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.722433e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.181038e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.206473e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.716003e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.476434e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.217700e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 12, 100) +*************************************************************************/ +static double mannwhitneyu_utbln12n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.700000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.374567e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.553481e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.541334e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.701907e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.414757e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.404103e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.234388e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.453762e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.311060e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.317501e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.713888e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.309583e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.019804e-08, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.224829e-09, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.349019e-08, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.893302e-08, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 13, 13) +*************************************************************************/ +static double mannwhitneyu_utbln13n13(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.541046e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.859047e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.130164e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.689719e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.950693e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.231455e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.976550e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.538455e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.245603e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.142647e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.831434e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.032483e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.488405e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.156927e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.949279e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.532700e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 13, 14) +*************************************************************************/ +static double mannwhitneyu_utbln13n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.525655e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.828341e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.108110e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.579552e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.488307e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.032328e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.988741e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.766394e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.388950e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.338179e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.133440e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.023518e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.110570e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.202332e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.056132e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.536323e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 13, 15) +*************************************************************************/ +static double mannwhitneyu_utbln13n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.513585e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.803952e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.090686e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.495310e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.160314e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.073124e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.480313e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.478239e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.140914e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.311541e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.677105e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.115464e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.578563e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.044604e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.888939e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 2.395644e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 13, 30) +*************************************************************************/ +static double mannwhitneyu_utbln13n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.455999e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.678434e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.995491e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.078100e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.705220e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.258739e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.671526e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.185458e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.507764e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.411446e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.044355e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.285765e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.345282e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.066940e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.962037e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.723644e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 13, 100) +*************************************************************************/ +static double mannwhitneyu_utbln13n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.446787e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.640804e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.671552e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.364990e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.274444e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.047440e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.161439e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.171729e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.562171e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.359762e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.275494e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.747635e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.700292e-08, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.565559e-09, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 5.005396e-09, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 3.335794e-09, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 14, 14) +*************************************************************************/ +static double mannwhitneyu_utbln14n14(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.510624e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.798584e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.087107e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.478532e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.098050e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.855986e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.409083e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.299536e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.176177e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.479417e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.812761e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -5.225872e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 4.516521e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 6.730551e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 9.237563e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.611820e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 14, 15) +*************************************************************************/ +static double mannwhitneyu_utbln14n15(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.498681e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.774668e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.070267e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.399348e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.807239e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.845763e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.071773e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.261698e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.011695e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.305946e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.879295e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.999439e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.904438e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.944986e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.373908e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.140794e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 14, 30) +*************************************************************************/ +static double mannwhitneyu_utbln14n30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.440378e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.649587e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.807829e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.989753e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.463646e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.586580e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -6.745917e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.635398e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.923172e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.446699e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.613892e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.214073e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.651683e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.272777e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.464988e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.109803e-07, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 14, 100) +*************************************************************************/ +static double mannwhitneyu_utbln14n100(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/3.750000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + mannwhitneyu_ucheb(x, -4.429701e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -4.610577e+00, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -9.482675e-01, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.605550e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.062151e-02, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.525154e-03, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.835983e-04, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -8.411440e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.744901e-05, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.318850e-06, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.692100e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -1.536270e-07, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -3.705888e-08, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -7.999599e-09, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, -2.908395e-09, &tj, &tj1, &result, _state); + mannwhitneyu_ucheb(x, 1.546923e-09, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, N1, N2) +*************************************************************************/ +static double mannwhitneyu_usigma(double s, + ae_int_t n1, + ae_int_t n2, + ae_state *_state) +{ + double f0; + double f1; + double f2; + double f3; + double f4; + double s0; + double s1; + double s2; + double s3; + double s4; + double result; + + + result = 0; + + /* + * N1=5, N2 = 5, 6, 7, ... + */ + if( ae_minint(n1, n2, _state)==5 ) + { + if( ae_maxint(n1, n2, _state)==5 ) + { + result = mannwhitneyu_utbln5n5(s, _state); + } + if( ae_maxint(n1, n2, _state)==6 ) + { + result = mannwhitneyu_utbln5n6(s, _state); + } + if( ae_maxint(n1, n2, _state)==7 ) + { + result = mannwhitneyu_utbln5n7(s, _state); + } + if( ae_maxint(n1, n2, _state)==8 ) + { + result = mannwhitneyu_utbln5n8(s, _state); + } + if( ae_maxint(n1, n2, _state)==9 ) + { + result = mannwhitneyu_utbln5n9(s, _state); + } + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln5n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln5n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln5n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln5n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln5n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln5n15(s, _state); + } + if( ae_maxint(n1, n2, _state)==16 ) + { + result = mannwhitneyu_utbln5n16(s, _state); + } + if( ae_maxint(n1, n2, _state)==17 ) + { + result = mannwhitneyu_utbln5n17(s, _state); + } + if( ae_maxint(n1, n2, _state)==18 ) + { + result = mannwhitneyu_utbln5n18(s, _state); + } + if( ae_maxint(n1, n2, _state)==19 ) + { + result = mannwhitneyu_utbln5n19(s, _state); + } + if( ae_maxint(n1, n2, _state)==20 ) + { + result = mannwhitneyu_utbln5n20(s, _state); + } + if( ae_maxint(n1, n2, _state)==21 ) + { + result = mannwhitneyu_utbln5n21(s, _state); + } + if( ae_maxint(n1, n2, _state)==22 ) + { + result = mannwhitneyu_utbln5n22(s, _state); + } + if( ae_maxint(n1, n2, _state)==23 ) + { + result = mannwhitneyu_utbln5n23(s, _state); + } + if( ae_maxint(n1, n2, _state)==24 ) + { + result = mannwhitneyu_utbln5n24(s, _state); + } + if( ae_maxint(n1, n2, _state)==25 ) + { + result = mannwhitneyu_utbln5n25(s, _state); + } + if( ae_maxint(n1, n2, _state)==26 ) + { + result = mannwhitneyu_utbln5n26(s, _state); + } + if( ae_maxint(n1, n2, _state)==27 ) + { + result = mannwhitneyu_utbln5n27(s, _state); + } + if( ae_maxint(n1, n2, _state)==28 ) + { + result = mannwhitneyu_utbln5n28(s, _state); + } + if( ae_maxint(n1, n2, _state)==29 ) + { + result = mannwhitneyu_utbln5n29(s, _state); + } + if( ae_maxint(n1, n2, _state)>29 ) + { + f0 = mannwhitneyu_utbln5n15(s, _state); + f1 = mannwhitneyu_utbln5n30(s, _state); + f2 = mannwhitneyu_utbln5n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=6, N2 = 6, 7, 8, ... + */ + if( ae_minint(n1, n2, _state)==6 ) + { + if( ae_maxint(n1, n2, _state)==6 ) + { + result = mannwhitneyu_utbln6n6(s, _state); + } + if( ae_maxint(n1, n2, _state)==7 ) + { + result = mannwhitneyu_utbln6n7(s, _state); + } + if( ae_maxint(n1, n2, _state)==8 ) + { + result = mannwhitneyu_utbln6n8(s, _state); + } + if( ae_maxint(n1, n2, _state)==9 ) + { + result = mannwhitneyu_utbln6n9(s, _state); + } + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln6n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln6n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln6n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln6n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln6n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln6n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln6n15(s, _state); + f1 = mannwhitneyu_utbln6n30(s, _state); + f2 = mannwhitneyu_utbln6n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=7, N2 = 7, 8, ... + */ + if( ae_minint(n1, n2, _state)==7 ) + { + if( ae_maxint(n1, n2, _state)==7 ) + { + result = mannwhitneyu_utbln7n7(s, _state); + } + if( ae_maxint(n1, n2, _state)==8 ) + { + result = mannwhitneyu_utbln7n8(s, _state); + } + if( ae_maxint(n1, n2, _state)==9 ) + { + result = mannwhitneyu_utbln7n9(s, _state); + } + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln7n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln7n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln7n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln7n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln7n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln7n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln7n15(s, _state); + f1 = mannwhitneyu_utbln7n30(s, _state); + f2 = mannwhitneyu_utbln7n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=8, N2 = 8, 9, 10, ... + */ + if( ae_minint(n1, n2, _state)==8 ) + { + if( ae_maxint(n1, n2, _state)==8 ) + { + result = mannwhitneyu_utbln8n8(s, _state); + } + if( ae_maxint(n1, n2, _state)==9 ) + { + result = mannwhitneyu_utbln8n9(s, _state); + } + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln8n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln8n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln8n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln8n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln8n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln8n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln8n15(s, _state); + f1 = mannwhitneyu_utbln8n30(s, _state); + f2 = mannwhitneyu_utbln8n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=9, N2 = 9, 10, ... + */ + if( ae_minint(n1, n2, _state)==9 ) + { + if( ae_maxint(n1, n2, _state)==9 ) + { + result = mannwhitneyu_utbln9n9(s, _state); + } + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln9n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln9n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln9n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln9n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln9n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln9n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln9n15(s, _state); + f1 = mannwhitneyu_utbln9n30(s, _state); + f2 = mannwhitneyu_utbln9n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=10, N2 = 10, 11, ... + */ + if( ae_minint(n1, n2, _state)==10 ) + { + if( ae_maxint(n1, n2, _state)==10 ) + { + result = mannwhitneyu_utbln10n10(s, _state); + } + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln10n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln10n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln10n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln10n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln10n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln10n15(s, _state); + f1 = mannwhitneyu_utbln10n30(s, _state); + f2 = mannwhitneyu_utbln10n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=11, N2 = 11, 12, ... + */ + if( ae_minint(n1, n2, _state)==11 ) + { + if( ae_maxint(n1, n2, _state)==11 ) + { + result = mannwhitneyu_utbln11n11(s, _state); + } + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln11n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln11n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln11n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln11n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln11n15(s, _state); + f1 = mannwhitneyu_utbln11n30(s, _state); + f2 = mannwhitneyu_utbln11n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=12, N2 = 12, 13, ... + */ + if( ae_minint(n1, n2, _state)==12 ) + { + if( ae_maxint(n1, n2, _state)==12 ) + { + result = mannwhitneyu_utbln12n12(s, _state); + } + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln12n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln12n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln12n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln12n15(s, _state); + f1 = mannwhitneyu_utbln12n30(s, _state); + f2 = mannwhitneyu_utbln12n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=13, N2 = 13, 14, ... + */ + if( ae_minint(n1, n2, _state)==13 ) + { + if( ae_maxint(n1, n2, _state)==13 ) + { + result = mannwhitneyu_utbln13n13(s, _state); + } + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln13n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln13n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln13n15(s, _state); + f1 = mannwhitneyu_utbln13n30(s, _state); + f2 = mannwhitneyu_utbln13n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1=14, N2 = 14, 15, ... + */ + if( ae_minint(n1, n2, _state)==14 ) + { + if( ae_maxint(n1, n2, _state)==14 ) + { + result = mannwhitneyu_utbln14n14(s, _state); + } + if( ae_maxint(n1, n2, _state)==15 ) + { + result = mannwhitneyu_utbln14n15(s, _state); + } + if( ae_maxint(n1, n2, _state)>15 ) + { + f0 = mannwhitneyu_utbln14n15(s, _state); + f1 = mannwhitneyu_utbln14n30(s, _state); + f2 = mannwhitneyu_utbln14n100(s, _state); + result = mannwhitneyu_uninterpolate(f0, f1, f2, ae_maxint(n1, n2, _state), _state); + } + return result; + } + + /* + * N1 >= 15, N2 >= 15 + */ + if( ae_fp_greater(s,4) ) + { + s = 4; + } + if( ae_fp_less(s,3) ) + { + s0 = 0.000000e+00; + f0 = mannwhitneyu_usigma000(n1, n2, _state); + s1 = 7.500000e-01; + f1 = mannwhitneyu_usigma075(n1, n2, _state); + s2 = 1.500000e+00; + f2 = mannwhitneyu_usigma150(n1, n2, _state); + s3 = 2.250000e+00; + f3 = mannwhitneyu_usigma225(n1, n2, _state); + s4 = 3.000000e+00; + f4 = mannwhitneyu_usigma300(n1, n2, _state); + f1 = ((s-s0)*f1-(s-s1)*f0)/(s1-s0); + f2 = ((s-s0)*f2-(s-s2)*f0)/(s2-s0); + f3 = ((s-s0)*f3-(s-s3)*f0)/(s3-s0); + f4 = ((s-s0)*f4-(s-s4)*f0)/(s4-s0); + f2 = ((s-s1)*f2-(s-s2)*f1)/(s2-s1); + f3 = ((s-s1)*f3-(s-s3)*f1)/(s3-s1); + f4 = ((s-s1)*f4-(s-s4)*f1)/(s4-s1); + f3 = ((s-s2)*f3-(s-s3)*f2)/(s3-s2); + f4 = ((s-s2)*f4-(s-s4)*f2)/(s4-s2); + f4 = ((s-s3)*f4-(s-s4)*f3)/(s4-s3); + result = f4; + } + else + { + s0 = 3.000000e+00; + f0 = mannwhitneyu_usigma300(n1, n2, _state); + s1 = 3.333333e+00; + f1 = mannwhitneyu_usigma333(n1, n2, _state); + s2 = 3.666667e+00; + f2 = mannwhitneyu_usigma367(n1, n2, _state); + s3 = 4.000000e+00; + f3 = mannwhitneyu_usigma400(n1, n2, _state); + f1 = ((s-s0)*f1-(s-s1)*f0)/(s1-s0); + f2 = ((s-s0)*f2-(s-s2)*f0)/(s2-s0); + f3 = ((s-s0)*f3-(s-s3)*f0)/(s3-s0); + f2 = ((s-s1)*f2-(s-s2)*f1)/(s2-s1); + f3 = ((s-s1)*f3-(s-s3)*f1)/(s3-s1); + f3 = ((s-s2)*f3-(s-s3)*f2)/(s3-s2); + result = f3; + } + return result; +} + + + + +/************************************************************************* +Sign test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +While calculating p-values high-precision binomial distribution +approximation is used, so significance levels have about 15 exact digits. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplesigntest(/* Real */ ae_vector* x, + ae_int_t n, + double median, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + ae_int_t gtcnt; + ae_int_t necnt; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=1 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Calculate: + * GTCnt - count of x[i]>Median + * NECnt - count of x[i]<>Median + */ + gtcnt = 0; + necnt = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_greater(x->ptr.p_double[i],median) ) + { + gtcnt = gtcnt+1; + } + if( ae_fp_neq(x->ptr.p_double[i],median) ) + { + necnt = necnt+1; + } + } + if( necnt==0 ) + { + + /* + * all x[i] are equal to Median. + * So we can conclude that Median is a true median :) + */ + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + *bothtails = ae_minreal(2*binomialdistribution(ae_minint(gtcnt, necnt-gtcnt, _state), necnt, 0.5, _state), 1.0, _state); + *lefttail = binomialdistribution(gtcnt, necnt, 0.5, _state); + *righttail = binomialcdistribution(gtcnt-1, necnt, 0.5, _state); +} + + + + +/************************************************************************* +One-sample t-test + +This test checks three hypotheses about the mean of the given sample. The +following tests are performed: + * two-tailed test (null hypothesis - the mean is equal to the given + value) + * left-tailed test (null hypothesis - the mean is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the mean is less than or equal + to the given value). + +The test is based on the assumption that a given sample has a normal +distribution and an unknown dispersion. If the distribution sharply +differs from normal, the test will work incorrectly. + +INPUT PARAMETERS: + X - sample. Array whose index goes from 0 to N-1. + N - size of sample, N>=0 + Mean - assumed value of the mean. + +OUTPUT PARAMETERS: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0, all p-values are set to 1.0 + * when variance of X[] is exactly zero, p-values are set + to 1.0 or 0.0, depending on difference between sample mean and + value of mean being tested. + + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest1(/* Real */ ae_vector* x, + ae_int_t n, + double mean, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + double xmean; + double x0; + double v; + ae_bool samex; + double xvariance; + double xstddev; + double v1; + double v2; + double stat; + double s; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=0 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Mean + */ + xmean = 0; + x0 = x->ptr.p_double[0]; + samex = ae_true; + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_double[i]; + xmean = xmean+v; + samex = samex&&ae_fp_eq(v,x0); + } + if( samex ) + { + xmean = x0; + } + else + { + xmean = xmean/n; + } + + /* + * Variance (using corrected two-pass algorithm) + */ + xvariance = 0; + xstddev = 0; + if( n!=1&&!samex ) + { + v1 = 0; + for(i=0; i<=n-1; i++) + { + v1 = v1+ae_sqr(x->ptr.p_double[i]-xmean, _state); + } + v2 = 0; + for(i=0; i<=n-1; i++) + { + v2 = v2+(x->ptr.p_double[i]-xmean); + } + v2 = ae_sqr(v2, _state)/n; + xvariance = (v1-v2)/(n-1); + if( ae_fp_less(xvariance,0) ) + { + xvariance = 0; + } + xstddev = ae_sqrt(xvariance, _state); + } + if( ae_fp_eq(xstddev,0) ) + { + if( ae_fp_eq(xmean,mean) ) + { + *bothtails = 1.0; + } + else + { + *bothtails = 0.0; + } + if( ae_fp_greater_eq(xmean,mean) ) + { + *lefttail = 1.0; + } + else + { + *lefttail = 0.0; + } + if( ae_fp_less_eq(xmean,mean) ) + { + *righttail = 1.0; + } + else + { + *righttail = 0.0; + } + return; + } + + /* + * Statistic + */ + stat = (xmean-mean)/(xstddev/ae_sqrt(n, _state)); + s = studenttdistribution(n-1, stat, _state); + *bothtails = 2*ae_minreal(s, 1-s, _state); + *lefttail = s; + *righttail = 1-s; +} + + +/************************************************************************* +Two-sample pooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * dispersions are equal + * samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has exactly zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest2(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + ae_bool samex; + ae_bool samey; + double x0; + double y0; + double xmean; + double ymean; + double v; + double stat; + double s; + double p; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=0||m<=0 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Mean + */ + xmean = 0; + x0 = x->ptr.p_double[0]; + samex = ae_true; + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_double[i]; + xmean = xmean+v; + samex = samex&&ae_fp_eq(v,x0); + } + if( samex ) + { + xmean = x0; + } + else + { + xmean = xmean/n; + } + ymean = 0; + y0 = y->ptr.p_double[0]; + samey = ae_true; + for(i=0; i<=m-1; i++) + { + v = y->ptr.p_double[i]; + ymean = ymean+v; + samey = samey&&ae_fp_eq(v,y0); + } + if( samey ) + { + ymean = y0; + } + else + { + ymean = ymean/m; + } + + /* + * S + */ + s = 0; + if( n+m>2 ) + { + for(i=0; i<=n-1; i++) + { + s = s+ae_sqr(x->ptr.p_double[i]-xmean, _state); + } + for(i=0; i<=m-1; i++) + { + s = s+ae_sqr(y->ptr.p_double[i]-ymean, _state); + } + s = ae_sqrt(s*((double)1/(double)n+(double)1/(double)m)/(n+m-2), _state); + } + if( ae_fp_eq(s,0) ) + { + if( ae_fp_eq(xmean,ymean) ) + { + *bothtails = 1.0; + } + else + { + *bothtails = 0.0; + } + if( ae_fp_greater_eq(xmean,ymean) ) + { + *lefttail = 1.0; + } + else + { + *lefttail = 0.0; + } + if( ae_fp_less_eq(xmean,ymean) ) + { + *righttail = 1.0; + } + else + { + *righttail = 0.0; + } + return; + } + + /* + * Statistic + */ + stat = (xmean-ymean)/s; + p = studenttdistribution(n+m-2, stat, _state); + *bothtails = 2*ae_minreal(p, 1-p, _state); + *lefttail = p; + *righttail = 1-p; +} + + +/************************************************************************* +Two-sample unpooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * samples are independent. +Equality of variances is NOT required. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + * when only one sample has zero variance, test reduces to 1-sample + version. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void unequalvariancettest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + ae_bool samex; + ae_bool samey; + double x0; + double y0; + double xmean; + double ymean; + double xvar; + double yvar; + double v; + double df; + double p; + double stat; + double c; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=0||m<=0 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Mean + */ + xmean = 0; + x0 = x->ptr.p_double[0]; + samex = ae_true; + for(i=0; i<=n-1; i++) + { + v = x->ptr.p_double[i]; + xmean = xmean+v; + samex = samex&&ae_fp_eq(v,x0); + } + if( samex ) + { + xmean = x0; + } + else + { + xmean = xmean/n; + } + ymean = 0; + y0 = y->ptr.p_double[0]; + samey = ae_true; + for(i=0; i<=m-1; i++) + { + v = y->ptr.p_double[i]; + ymean = ymean+v; + samey = samey&&ae_fp_eq(v,y0); + } + if( samey ) + { + ymean = y0; + } + else + { + ymean = ymean/m; + } + + /* + * Variance (using corrected two-pass algorithm) + */ + xvar = 0; + if( n>=2&&!samex ) + { + for(i=0; i<=n-1; i++) + { + xvar = xvar+ae_sqr(x->ptr.p_double[i]-xmean, _state); + } + xvar = xvar/(n-1); + } + yvar = 0; + if( m>=2&&!samey ) + { + for(i=0; i<=m-1; i++) + { + yvar = yvar+ae_sqr(y->ptr.p_double[i]-ymean, _state); + } + yvar = yvar/(m-1); + } + + /* + * Handle different special cases + * (one or both variances are zero). + */ + if( ae_fp_eq(xvar,0)&&ae_fp_eq(yvar,0) ) + { + if( ae_fp_eq(xmean,ymean) ) + { + *bothtails = 1.0; + } + else + { + *bothtails = 0.0; + } + if( ae_fp_greater_eq(xmean,ymean) ) + { + *lefttail = 1.0; + } + else + { + *lefttail = 0.0; + } + if( ae_fp_less_eq(xmean,ymean) ) + { + *righttail = 1.0; + } + else + { + *righttail = 0.0; + } + return; + } + if( ae_fp_eq(xvar,0) ) + { + + /* + * X is constant, unpooled 2-sample test reduces to 1-sample test. + * + * NOTE: right-tail and left-tail must be passed to 1-sample + * t-test in reverse order because we reverse order of + * of samples. + */ + studentttest1(y, m, xmean, bothtails, righttail, lefttail, _state); + return; + } + if( ae_fp_eq(yvar,0) ) + { + + /* + * Y is constant, unpooled 2-sample test reduces to 1-sample test. + */ + studentttest1(x, n, ymean, bothtails, lefttail, righttail, _state); + return; + } + + /* + * Statistic + */ + stat = (xmean-ymean)/ae_sqrt(xvar/n+yvar/m, _state); + c = xvar/n/(xvar/n+yvar/m); + df = (n-1)*(m-1)/((m-1)*ae_sqr(c, _state)+(n-1)*ae_sqr(1-c, _state)); + if( ae_fp_greater(stat,0) ) + { + p = 1-0.5*incompletebeta(df/2, 0.5, df/(df+ae_sqr(stat, _state)), _state); + } + else + { + p = 0.5*incompletebeta(df/2, 0.5, df/(df+ae_sqr(stat, _state)), _state); + } + *bothtails = 2*ae_minreal(p, 1-p, _state); + *lefttail = p; + *righttail = 1-p; +} + + + + +/************************************************************************* +Two-sample F-test + +This test checks three hypotheses about dispersions of the given samples. +The following tests are performed: + * two-tailed test (null hypothesis - the dispersions are equal) + * left-tailed test (null hypothesis - the dispersion of the first + sample is greater than or equal to the dispersion of the second + sample). + * right-tailed test (null hypothesis - the dispersion of the first + sample is less than or equal to the dispersion of the second sample) + +The test is based on the following assumptions: + * the given samples have normal distributions + * the samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - sample size. + Y - sample 2. Array whose index goes from 0 to M-1. + M - sample size. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void ftest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + double xmean; + double ymean; + double xvar; + double yvar; + ae_int_t df1; + ae_int_t df2; + double stat; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=2||m<=2 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Mean + */ + xmean = 0; + for(i=0; i<=n-1; i++) + { + xmean = xmean+x->ptr.p_double[i]; + } + xmean = xmean/n; + ymean = 0; + for(i=0; i<=m-1; i++) + { + ymean = ymean+y->ptr.p_double[i]; + } + ymean = ymean/m; + + /* + * Variance (using corrected two-pass algorithm) + */ + xvar = 0; + for(i=0; i<=n-1; i++) + { + xvar = xvar+ae_sqr(x->ptr.p_double[i]-xmean, _state); + } + xvar = xvar/(n-1); + yvar = 0; + for(i=0; i<=m-1; i++) + { + yvar = yvar+ae_sqr(y->ptr.p_double[i]-ymean, _state); + } + yvar = yvar/(m-1); + if( ae_fp_eq(xvar,0)||ae_fp_eq(yvar,0) ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Statistic + */ + df1 = n-1; + df2 = m-1; + stat = ae_minreal(xvar/yvar, yvar/xvar, _state); + *bothtails = 1-(fdistribution(df1, df2, 1/stat, _state)-fdistribution(df1, df2, stat, _state)); + *lefttail = fdistribution(df1, df2, xvar/yvar, _state); + *righttail = 1-(*lefttail); +} + + +/************************************************************************* +One-sample chi-square test + +This test checks three hypotheses about the dispersion of the given sample +The following tests are performed: + * two-tailed test (null hypothesis - the dispersion equals the given + number) + * left-tailed test (null hypothesis - the dispersion is greater than + or equal to the given number) + * right-tailed test (null hypothesis - dispersion is less than or + equal to the given number). + +Test is based on the following assumptions: + * the given sample has a normal distribution. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Variance - dispersion value to compare with. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplevariancetest(/* Real */ ae_vector* x, + ae_int_t n, + double variance, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_int_t i; + double xmean; + double xvar; + double s; + double stat; + + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + + if( n<=1 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Mean + */ + xmean = 0; + for(i=0; i<=n-1; i++) + { + xmean = xmean+x->ptr.p_double[i]; + } + xmean = xmean/n; + + /* + * Variance + */ + xvar = 0; + for(i=0; i<=n-1; i++) + { + xvar = xvar+ae_sqr(x->ptr.p_double[i]-xmean, _state); + } + xvar = xvar/(n-1); + if( ae_fp_eq(xvar,0) ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + return; + } + + /* + * Statistic + */ + stat = (n-1)*xvar/variance; + s = chisquaredistribution(n-1, stat, _state); + *bothtails = 2*ae_minreal(s, 1-s, _state); + *lefttail = s; + *righttail = 1-(*lefttail); +} + + + + +/************************************************************************* +Wilcoxon signed-rank test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + * the distribution should be continuous and symmetric relative to its + median. + * number of distinct values in the X array should be greater than 4 + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with two decimal places in interval [0.0001, 1]. + +"Two decimal places" does not sound very impressive, but in practice the +relative error of less than 1% is enough to make a decision. + +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void wilcoxonsignedranktest(/* Real */ ae_vector* x, + ae_int_t n, + double e, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state) +{ + ae_frame _frame_block; + ae_vector _x; + ae_int_t i; + ae_int_t j; + ae_int_t k; + ae_int_t t; + double tmp; + ae_int_t tmpi; + ae_int_t ns; + ae_vector r; + ae_vector c; + double w; + double p; + double mp; + double s; + double sigma; + double mu; + + ae_frame_make(_state, &_frame_block); + ae_vector_init_copy(&_x, x, _state, ae_true); + x = &_x; + *bothtails = 0; + *lefttail = 0; + *righttail = 0; + ae_vector_init(&r, 0, DT_REAL, _state, ae_true); + ae_vector_init(&c, 0, DT_INT, _state, ae_true); + + + /* + * Prepare + */ + if( n<5 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + ae_frame_leave(_state); + return; + } + ns = 0; + for(i=0; i<=n-1; i++) + { + if( ae_fp_eq(x->ptr.p_double[i],e) ) + { + continue; + } + x->ptr.p_double[ns] = x->ptr.p_double[i]; + ns = ns+1; + } + if( ns<5 ) + { + *bothtails = 1.0; + *lefttail = 1.0; + *righttail = 1.0; + ae_frame_leave(_state); + return; + } + ae_vector_set_length(&r, ns-1+1, _state); + ae_vector_set_length(&c, ns-1+1, _state); + for(i=0; i<=ns-1; i++) + { + r.ptr.p_double[i] = ae_fabs(x->ptr.p_double[i]-e, _state); + c.ptr.p_int[i] = i; + } + + /* + * sort {R, C} + */ + if( ns!=1 ) + { + i = 2; + do + { + t = i; + while(t!=1) + { + k = t/2; + if( ae_fp_greater_eq(r.ptr.p_double[k-1],r.ptr.p_double[t-1]) ) + { + t = 1; + } + else + { + tmp = r.ptr.p_double[k-1]; + r.ptr.p_double[k-1] = r.ptr.p_double[t-1]; + r.ptr.p_double[t-1] = tmp; + tmpi = c.ptr.p_int[k-1]; + c.ptr.p_int[k-1] = c.ptr.p_int[t-1]; + c.ptr.p_int[t-1] = tmpi; + t = k; + } + } + i = i+1; + } + while(i<=ns); + i = ns-1; + do + { + tmp = r.ptr.p_double[i]; + r.ptr.p_double[i] = r.ptr.p_double[0]; + r.ptr.p_double[0] = tmp; + tmpi = c.ptr.p_int[i]; + c.ptr.p_int[i] = c.ptr.p_int[0]; + c.ptr.p_int[0] = tmpi; + t = 1; + while(t!=0) + { + k = 2*t; + if( k>i ) + { + t = 0; + } + else + { + if( k=1); + } + + /* + * compute tied ranks + */ + i = 0; + while(i<=ns-1) + { + j = i+1; + while(j<=ns-1) + { + if( ae_fp_neq(r.ptr.p_double[j],r.ptr.p_double[i]) ) + { + break; + } + j = j+1; + } + for(k=i; k<=j-1; k++) + { + r.ptr.p_double[k] = 1+(double)(i+j-1)/(double)2; + } + i = j; + } + + /* + * Compute W+ + */ + w = 0; + for(i=0; i<=ns-1; i++) + { + if( ae_fp_greater(x->ptr.p_double[c.ptr.p_int[i]],e) ) + { + w = w+r.ptr.p_double[i]; + } + } + + /* + * Result + */ + mu = (double)(ns*(ns+1))/(double)4; + sigma = ae_sqrt((double)(ns*(ns+1)*(2*ns+1))/(double)24, _state); + s = (w-mu)/sigma; + if( ae_fp_less_eq(s,0) ) + { + p = ae_exp(wsr_wsigma(-(w-mu)/sigma, ns, _state), _state); + mp = 1-ae_exp(wsr_wsigma(-(w-1-mu)/sigma, ns, _state), _state); + } + else + { + mp = ae_exp(wsr_wsigma((w-mu)/sigma, ns, _state), _state); + p = 1-ae_exp(wsr_wsigma((w+1-mu)/sigma, ns, _state), _state); + } + *bothtails = ae_maxreal(2*ae_minreal(p, mp, _state), 1.0E-4, _state); + *lefttail = ae_maxreal(p, 1.0E-4, _state); + *righttail = ae_maxreal(mp, 1.0E-4, _state); + ae_frame_leave(_state); +} + + +/************************************************************************* +Sequential Chebyshev interpolation. +*************************************************************************/ +static void wsr_wcheb(double x, + double c, + double* tj, + double* tj1, + double* r, + ae_state *_state) +{ + double t; + + + *r = *r+c*(*tj); + t = 2*x*(*tj1)-(*tj); + *tj = *tj1; + *tj1 = t; +} + + +/************************************************************************* +Tail(S, 5) +*************************************************************************/ +static double wsr_w5(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-3.708099e+00*s+7.500000e+00, _state); + if( w>=7 ) + { + r = -6.931e-01; + } + if( w==6 ) + { + r = -9.008e-01; + } + if( w==5 ) + { + r = -1.163e+00; + } + if( w==4 ) + { + r = -1.520e+00; + } + if( w==3 ) + { + r = -1.856e+00; + } + if( w==2 ) + { + r = -2.367e+00; + } + if( w==1 ) + { + r = -2.773e+00; + } + if( w<=0 ) + { + r = -3.466e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 6) +*************************************************************************/ +static double wsr_w6(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-4.769696e+00*s+1.050000e+01, _state); + if( w>=10 ) + { + r = -6.931e-01; + } + if( w==9 ) + { + r = -8.630e-01; + } + if( w==8 ) + { + r = -1.068e+00; + } + if( w==7 ) + { + r = -1.269e+00; + } + if( w==6 ) + { + r = -1.520e+00; + } + if( w==5 ) + { + r = -1.856e+00; + } + if( w==4 ) + { + r = -2.213e+00; + } + if( w==3 ) + { + r = -2.549e+00; + } + if( w==2 ) + { + r = -3.060e+00; + } + if( w==1 ) + { + r = -3.466e+00; + } + if( w<=0 ) + { + r = -4.159e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 7) +*************************************************************************/ +static double wsr_w7(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-5.916080e+00*s+1.400000e+01, _state); + if( w>=14 ) + { + r = -6.325e-01; + } + if( w==13 ) + { + r = -7.577e-01; + } + if( w==12 ) + { + r = -9.008e-01; + } + if( w==11 ) + { + r = -1.068e+00; + } + if( w==10 ) + { + r = -1.241e+00; + } + if( w==9 ) + { + r = -1.451e+00; + } + if( w==8 ) + { + r = -1.674e+00; + } + if( w==7 ) + { + r = -1.908e+00; + } + if( w==6 ) + { + r = -2.213e+00; + } + if( w==5 ) + { + r = -2.549e+00; + } + if( w==4 ) + { + r = -2.906e+00; + } + if( w==3 ) + { + r = -3.243e+00; + } + if( w==2 ) + { + r = -3.753e+00; + } + if( w==1 ) + { + r = -4.159e+00; + } + if( w<=0 ) + { + r = -4.852e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 8) +*************************************************************************/ +static double wsr_w8(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-7.141428e+00*s+1.800000e+01, _state); + if( w>=18 ) + { + r = -6.399e-01; + } + if( w==17 ) + { + r = -7.494e-01; + } + if( w==16 ) + { + r = -8.630e-01; + } + if( w==15 ) + { + r = -9.913e-01; + } + if( w==14 ) + { + r = -1.138e+00; + } + if( w==13 ) + { + r = -1.297e+00; + } + if( w==12 ) + { + r = -1.468e+00; + } + if( w==11 ) + { + r = -1.653e+00; + } + if( w==10 ) + { + r = -1.856e+00; + } + if( w==9 ) + { + r = -2.079e+00; + } + if( w==8 ) + { + r = -2.326e+00; + } + if( w==7 ) + { + r = -2.601e+00; + } + if( w==6 ) + { + r = -2.906e+00; + } + if( w==5 ) + { + r = -3.243e+00; + } + if( w==4 ) + { + r = -3.599e+00; + } + if( w==3 ) + { + r = -3.936e+00; + } + if( w==2 ) + { + r = -4.447e+00; + } + if( w==1 ) + { + r = -4.852e+00; + } + if( w<=0 ) + { + r = -5.545e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 9) +*************************************************************************/ +static double wsr_w9(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-8.440972e+00*s+2.250000e+01, _state); + if( w>=22 ) + { + r = -6.931e-01; + } + if( w==21 ) + { + r = -7.873e-01; + } + if( w==20 ) + { + r = -8.912e-01; + } + if( w==19 ) + { + r = -1.002e+00; + } + if( w==18 ) + { + r = -1.120e+00; + } + if( w==17 ) + { + r = -1.255e+00; + } + if( w==16 ) + { + r = -1.394e+00; + } + if( w==15 ) + { + r = -1.547e+00; + } + if( w==14 ) + { + r = -1.717e+00; + } + if( w==13 ) + { + r = -1.895e+00; + } + if( w==12 ) + { + r = -2.079e+00; + } + if( w==11 ) + { + r = -2.287e+00; + } + if( w==10 ) + { + r = -2.501e+00; + } + if( w==9 ) + { + r = -2.742e+00; + } + if( w==8 ) + { + r = -3.019e+00; + } + if( w==7 ) + { + r = -3.294e+00; + } + if( w==6 ) + { + r = -3.599e+00; + } + if( w==5 ) + { + r = -3.936e+00; + } + if( w==4 ) + { + r = -4.292e+00; + } + if( w==3 ) + { + r = -4.629e+00; + } + if( w==2 ) + { + r = -5.140e+00; + } + if( w==1 ) + { + r = -5.545e+00; + } + if( w<=0 ) + { + r = -6.238e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 10) +*************************************************************************/ +static double wsr_w10(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-9.810708e+00*s+2.750000e+01, _state); + if( w>=27 ) + { + r = -6.931e-01; + } + if( w==26 ) + { + r = -7.745e-01; + } + if( w==25 ) + { + r = -8.607e-01; + } + if( w==24 ) + { + r = -9.551e-01; + } + if( w==23 ) + { + r = -1.057e+00; + } + if( w==22 ) + { + r = -1.163e+00; + } + if( w==21 ) + { + r = -1.279e+00; + } + if( w==20 ) + { + r = -1.402e+00; + } + if( w==19 ) + { + r = -1.533e+00; + } + if( w==18 ) + { + r = -1.674e+00; + } + if( w==17 ) + { + r = -1.826e+00; + } + if( w==16 ) + { + r = -1.983e+00; + } + if( w==15 ) + { + r = -2.152e+00; + } + if( w==14 ) + { + r = -2.336e+00; + } + if( w==13 ) + { + r = -2.525e+00; + } + if( w==12 ) + { + r = -2.727e+00; + } + if( w==11 ) + { + r = -2.942e+00; + } + if( w==10 ) + { + r = -3.170e+00; + } + if( w==9 ) + { + r = -3.435e+00; + } + if( w==8 ) + { + r = -3.713e+00; + } + if( w==7 ) + { + r = -3.987e+00; + } + if( w==6 ) + { + r = -4.292e+00; + } + if( w==5 ) + { + r = -4.629e+00; + } + if( w==4 ) + { + r = -4.986e+00; + } + if( w==3 ) + { + r = -5.322e+00; + } + if( w==2 ) + { + r = -5.833e+00; + } + if( w==1 ) + { + r = -6.238e+00; + } + if( w<=0 ) + { + r = -6.931e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 11) +*************************************************************************/ +static double wsr_w11(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.124722e+01*s+3.300000e+01, _state); + if( w>=33 ) + { + r = -6.595e-01; + } + if( w==32 ) + { + r = -7.279e-01; + } + if( w==31 ) + { + r = -8.002e-01; + } + if( w==30 ) + { + r = -8.782e-01; + } + if( w==29 ) + { + r = -9.615e-01; + } + if( w==28 ) + { + r = -1.050e+00; + } + if( w==27 ) + { + r = -1.143e+00; + } + if( w==26 ) + { + r = -1.243e+00; + } + if( w==25 ) + { + r = -1.348e+00; + } + if( w==24 ) + { + r = -1.459e+00; + } + if( w==23 ) + { + r = -1.577e+00; + } + if( w==22 ) + { + r = -1.700e+00; + } + if( w==21 ) + { + r = -1.832e+00; + } + if( w==20 ) + { + r = -1.972e+00; + } + if( w==19 ) + { + r = -2.119e+00; + } + if( w==18 ) + { + r = -2.273e+00; + } + if( w==17 ) + { + r = -2.437e+00; + } + if( w==16 ) + { + r = -2.607e+00; + } + if( w==15 ) + { + r = -2.788e+00; + } + if( w==14 ) + { + r = -2.980e+00; + } + if( w==13 ) + { + r = -3.182e+00; + } + if( w==12 ) + { + r = -3.391e+00; + } + if( w==11 ) + { + r = -3.617e+00; + } + if( w==10 ) + { + r = -3.863e+00; + } + if( w==9 ) + { + r = -4.128e+00; + } + if( w==8 ) + { + r = -4.406e+00; + } + if( w==7 ) + { + r = -4.680e+00; + } + if( w==6 ) + { + r = -4.986e+00; + } + if( w==5 ) + { + r = -5.322e+00; + } + if( w==4 ) + { + r = -5.679e+00; + } + if( w==3 ) + { + r = -6.015e+00; + } + if( w==2 ) + { + r = -6.526e+00; + } + if( w==1 ) + { + r = -6.931e+00; + } + if( w<=0 ) + { + r = -7.625e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 12) +*************************************************************************/ +static double wsr_w12(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.274755e+01*s+3.900000e+01, _state); + if( w>=39 ) + { + r = -6.633e-01; + } + if( w==38 ) + { + r = -7.239e-01; + } + if( w==37 ) + { + r = -7.878e-01; + } + if( w==36 ) + { + r = -8.556e-01; + } + if( w==35 ) + { + r = -9.276e-01; + } + if( w==34 ) + { + r = -1.003e+00; + } + if( w==33 ) + { + r = -1.083e+00; + } + if( w==32 ) + { + r = -1.168e+00; + } + if( w==31 ) + { + r = -1.256e+00; + } + if( w==30 ) + { + r = -1.350e+00; + } + if( w==29 ) + { + r = -1.449e+00; + } + if( w==28 ) + { + r = -1.552e+00; + } + if( w==27 ) + { + r = -1.660e+00; + } + if( w==26 ) + { + r = -1.774e+00; + } + if( w==25 ) + { + r = -1.893e+00; + } + if( w==24 ) + { + r = -2.017e+00; + } + if( w==23 ) + { + r = -2.148e+00; + } + if( w==22 ) + { + r = -2.285e+00; + } + if( w==21 ) + { + r = -2.429e+00; + } + if( w==20 ) + { + r = -2.581e+00; + } + if( w==19 ) + { + r = -2.738e+00; + } + if( w==18 ) + { + r = -2.902e+00; + } + if( w==17 ) + { + r = -3.076e+00; + } + if( w==16 ) + { + r = -3.255e+00; + } + if( w==15 ) + { + r = -3.443e+00; + } + if( w==14 ) + { + r = -3.645e+00; + } + if( w==13 ) + { + r = -3.852e+00; + } + if( w==12 ) + { + r = -4.069e+00; + } + if( w==11 ) + { + r = -4.310e+00; + } + if( w==10 ) + { + r = -4.557e+00; + } + if( w==9 ) + { + r = -4.821e+00; + } + if( w==8 ) + { + r = -5.099e+00; + } + if( w==7 ) + { + r = -5.373e+00; + } + if( w==6 ) + { + r = -5.679e+00; + } + if( w==5 ) + { + r = -6.015e+00; + } + if( w==4 ) + { + r = -6.372e+00; + } + if( w==3 ) + { + r = -6.708e+00; + } + if( w==2 ) + { + r = -7.219e+00; + } + if( w==1 ) + { + r = -7.625e+00; + } + if( w<=0 ) + { + r = -8.318e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 13) +*************************************************************************/ +static double wsr_w13(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.430909e+01*s+4.550000e+01, _state); + if( w>=45 ) + { + r = -6.931e-01; + } + if( w==44 ) + { + r = -7.486e-01; + } + if( w==43 ) + { + r = -8.068e-01; + } + if( w==42 ) + { + r = -8.683e-01; + } + if( w==41 ) + { + r = -9.328e-01; + } + if( w==40 ) + { + r = -1.001e+00; + } + if( w==39 ) + { + r = -1.072e+00; + } + if( w==38 ) + { + r = -1.146e+00; + } + if( w==37 ) + { + r = -1.224e+00; + } + if( w==36 ) + { + r = -1.306e+00; + } + if( w==35 ) + { + r = -1.392e+00; + } + if( w==34 ) + { + r = -1.481e+00; + } + if( w==33 ) + { + r = -1.574e+00; + } + if( w==32 ) + { + r = -1.672e+00; + } + if( w==31 ) + { + r = -1.773e+00; + } + if( w==30 ) + { + r = -1.879e+00; + } + if( w==29 ) + { + r = -1.990e+00; + } + if( w==28 ) + { + r = -2.104e+00; + } + if( w==27 ) + { + r = -2.224e+00; + } + if( w==26 ) + { + r = -2.349e+00; + } + if( w==25 ) + { + r = -2.479e+00; + } + if( w==24 ) + { + r = -2.614e+00; + } + if( w==23 ) + { + r = -2.755e+00; + } + if( w==22 ) + { + r = -2.902e+00; + } + if( w==21 ) + { + r = -3.055e+00; + } + if( w==20 ) + { + r = -3.215e+00; + } + if( w==19 ) + { + r = -3.380e+00; + } + if( w==18 ) + { + r = -3.551e+00; + } + if( w==17 ) + { + r = -3.733e+00; + } + if( w==16 ) + { + r = -3.917e+00; + } + if( w==15 ) + { + r = -4.113e+00; + } + if( w==14 ) + { + r = -4.320e+00; + } + if( w==13 ) + { + r = -4.534e+00; + } + if( w==12 ) + { + r = -4.762e+00; + } + if( w==11 ) + { + r = -5.004e+00; + } + if( w==10 ) + { + r = -5.250e+00; + } + if( w==9 ) + { + r = -5.514e+00; + } + if( w==8 ) + { + r = -5.792e+00; + } + if( w==7 ) + { + r = -6.066e+00; + } + if( w==6 ) + { + r = -6.372e+00; + } + if( w==5 ) + { + r = -6.708e+00; + } + if( w==4 ) + { + r = -7.065e+00; + } + if( w==3 ) + { + r = -7.401e+00; + } + if( w==2 ) + { + r = -7.912e+00; + } + if( w==1 ) + { + r = -8.318e+00; + } + if( w<=0 ) + { + r = -9.011e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 14) +*************************************************************************/ +static double wsr_w14(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.592953e+01*s+5.250000e+01, _state); + if( w>=52 ) + { + r = -6.931e-01; + } + if( w==51 ) + { + r = -7.428e-01; + } + if( w==50 ) + { + r = -7.950e-01; + } + if( w==49 ) + { + r = -8.495e-01; + } + if( w==48 ) + { + r = -9.067e-01; + } + if( w==47 ) + { + r = -9.664e-01; + } + if( w==46 ) + { + r = -1.029e+00; + } + if( w==45 ) + { + r = -1.094e+00; + } + if( w==44 ) + { + r = -1.162e+00; + } + if( w==43 ) + { + r = -1.233e+00; + } + if( w==42 ) + { + r = -1.306e+00; + } + if( w==41 ) + { + r = -1.383e+00; + } + if( w==40 ) + { + r = -1.463e+00; + } + if( w==39 ) + { + r = -1.546e+00; + } + if( w==38 ) + { + r = -1.632e+00; + } + if( w==37 ) + { + r = -1.722e+00; + } + if( w==36 ) + { + r = -1.815e+00; + } + if( w==35 ) + { + r = -1.911e+00; + } + if( w==34 ) + { + r = -2.011e+00; + } + if( w==33 ) + { + r = -2.115e+00; + } + if( w==32 ) + { + r = -2.223e+00; + } + if( w==31 ) + { + r = -2.334e+00; + } + if( w==30 ) + { + r = -2.450e+00; + } + if( w==29 ) + { + r = -2.570e+00; + } + if( w==28 ) + { + r = -2.694e+00; + } + if( w==27 ) + { + r = -2.823e+00; + } + if( w==26 ) + { + r = -2.956e+00; + } + if( w==25 ) + { + r = -3.095e+00; + } + if( w==24 ) + { + r = -3.238e+00; + } + if( w==23 ) + { + r = -3.387e+00; + } + if( w==22 ) + { + r = -3.541e+00; + } + if( w==21 ) + { + r = -3.700e+00; + } + if( w==20 ) + { + r = -3.866e+00; + } + if( w==19 ) + { + r = -4.038e+00; + } + if( w==18 ) + { + r = -4.215e+00; + } + if( w==17 ) + { + r = -4.401e+00; + } + if( w==16 ) + { + r = -4.592e+00; + } + if( w==15 ) + { + r = -4.791e+00; + } + if( w==14 ) + { + r = -5.004e+00; + } + if( w==13 ) + { + r = -5.227e+00; + } + if( w==12 ) + { + r = -5.456e+00; + } + if( w==11 ) + { + r = -5.697e+00; + } + if( w==10 ) + { + r = -5.943e+00; + } + if( w==9 ) + { + r = -6.208e+00; + } + if( w==8 ) + { + r = -6.485e+00; + } + if( w==7 ) + { + r = -6.760e+00; + } + if( w==6 ) + { + r = -7.065e+00; + } + if( w==5 ) + { + r = -7.401e+00; + } + if( w==4 ) + { + r = -7.758e+00; + } + if( w==3 ) + { + r = -8.095e+00; + } + if( w==2 ) + { + r = -8.605e+00; + } + if( w==1 ) + { + r = -9.011e+00; + } + if( w<=0 ) + { + r = -9.704e+00; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 15) +*************************************************************************/ +static double wsr_w15(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.760682e+01*s+6.000000e+01, _state); + if( w>=60 ) + { + r = -6.714e-01; + } + if( w==59 ) + { + r = -7.154e-01; + } + if( w==58 ) + { + r = -7.613e-01; + } + if( w==57 ) + { + r = -8.093e-01; + } + if( w==56 ) + { + r = -8.593e-01; + } + if( w==55 ) + { + r = -9.114e-01; + } + if( w==54 ) + { + r = -9.656e-01; + } + if( w==53 ) + { + r = -1.022e+00; + } + if( w==52 ) + { + r = -1.081e+00; + } + if( w==51 ) + { + r = -1.142e+00; + } + if( w==50 ) + { + r = -1.205e+00; + } + if( w==49 ) + { + r = -1.270e+00; + } + if( w==48 ) + { + r = -1.339e+00; + } + if( w==47 ) + { + r = -1.409e+00; + } + if( w==46 ) + { + r = -1.482e+00; + } + if( w==45 ) + { + r = -1.558e+00; + } + if( w==44 ) + { + r = -1.636e+00; + } + if( w==43 ) + { + r = -1.717e+00; + } + if( w==42 ) + { + r = -1.801e+00; + } + if( w==41 ) + { + r = -1.888e+00; + } + if( w==40 ) + { + r = -1.977e+00; + } + if( w==39 ) + { + r = -2.070e+00; + } + if( w==38 ) + { + r = -2.166e+00; + } + if( w==37 ) + { + r = -2.265e+00; + } + if( w==36 ) + { + r = -2.366e+00; + } + if( w==35 ) + { + r = -2.472e+00; + } + if( w==34 ) + { + r = -2.581e+00; + } + if( w==33 ) + { + r = -2.693e+00; + } + if( w==32 ) + { + r = -2.809e+00; + } + if( w==31 ) + { + r = -2.928e+00; + } + if( w==30 ) + { + r = -3.051e+00; + } + if( w==29 ) + { + r = -3.179e+00; + } + if( w==28 ) + { + r = -3.310e+00; + } + if( w==27 ) + { + r = -3.446e+00; + } + if( w==26 ) + { + r = -3.587e+00; + } + if( w==25 ) + { + r = -3.732e+00; + } + if( w==24 ) + { + r = -3.881e+00; + } + if( w==23 ) + { + r = -4.036e+00; + } + if( w==22 ) + { + r = -4.195e+00; + } + if( w==21 ) + { + r = -4.359e+00; + } + if( w==20 ) + { + r = -4.531e+00; + } + if( w==19 ) + { + r = -4.707e+00; + } + if( w==18 ) + { + r = -4.888e+00; + } + if( w==17 ) + { + r = -5.079e+00; + } + if( w==16 ) + { + r = -5.273e+00; + } + if( w==15 ) + { + r = -5.477e+00; + } + if( w==14 ) + { + r = -5.697e+00; + } + if( w==13 ) + { + r = -5.920e+00; + } + if( w==12 ) + { + r = -6.149e+00; + } + if( w==11 ) + { + r = -6.390e+00; + } + if( w==10 ) + { + r = -6.636e+00; + } + if( w==9 ) + { + r = -6.901e+00; + } + if( w==8 ) + { + r = -7.178e+00; + } + if( w==7 ) + { + r = -7.453e+00; + } + if( w==6 ) + { + r = -7.758e+00; + } + if( w==5 ) + { + r = -8.095e+00; + } + if( w==4 ) + { + r = -8.451e+00; + } + if( w==3 ) + { + r = -8.788e+00; + } + if( w==2 ) + { + r = -9.299e+00; + } + if( w==1 ) + { + r = -9.704e+00; + } + if( w<=0 ) + { + r = -1.040e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 16) +*************************************************************************/ +static double wsr_w16(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-1.933908e+01*s+6.800000e+01, _state); + if( w>=68 ) + { + r = -6.733e-01; + } + if( w==67 ) + { + r = -7.134e-01; + } + if( w==66 ) + { + r = -7.551e-01; + } + if( w==65 ) + { + r = -7.986e-01; + } + if( w==64 ) + { + r = -8.437e-01; + } + if( w==63 ) + { + r = -8.905e-01; + } + if( w==62 ) + { + r = -9.391e-01; + } + if( w==61 ) + { + r = -9.895e-01; + } + if( w==60 ) + { + r = -1.042e+00; + } + if( w==59 ) + { + r = -1.096e+00; + } + if( w==58 ) + { + r = -1.152e+00; + } + if( w==57 ) + { + r = -1.210e+00; + } + if( w==56 ) + { + r = -1.270e+00; + } + if( w==55 ) + { + r = -1.331e+00; + } + if( w==54 ) + { + r = -1.395e+00; + } + if( w==53 ) + { + r = -1.462e+00; + } + if( w==52 ) + { + r = -1.530e+00; + } + if( w==51 ) + { + r = -1.600e+00; + } + if( w==50 ) + { + r = -1.673e+00; + } + if( w==49 ) + { + r = -1.748e+00; + } + if( w==48 ) + { + r = -1.825e+00; + } + if( w==47 ) + { + r = -1.904e+00; + } + if( w==46 ) + { + r = -1.986e+00; + } + if( w==45 ) + { + r = -2.071e+00; + } + if( w==44 ) + { + r = -2.158e+00; + } + if( w==43 ) + { + r = -2.247e+00; + } + if( w==42 ) + { + r = -2.339e+00; + } + if( w==41 ) + { + r = -2.434e+00; + } + if( w==40 ) + { + r = -2.532e+00; + } + if( w==39 ) + { + r = -2.632e+00; + } + if( w==38 ) + { + r = -2.735e+00; + } + if( w==37 ) + { + r = -2.842e+00; + } + if( w==36 ) + { + r = -2.951e+00; + } + if( w==35 ) + { + r = -3.064e+00; + } + if( w==34 ) + { + r = -3.179e+00; + } + if( w==33 ) + { + r = -3.298e+00; + } + if( w==32 ) + { + r = -3.420e+00; + } + if( w==31 ) + { + r = -3.546e+00; + } + if( w==30 ) + { + r = -3.676e+00; + } + if( w==29 ) + { + r = -3.810e+00; + } + if( w==28 ) + { + r = -3.947e+00; + } + if( w==27 ) + { + r = -4.088e+00; + } + if( w==26 ) + { + r = -4.234e+00; + } + if( w==25 ) + { + r = -4.383e+00; + } + if( w==24 ) + { + r = -4.538e+00; + } + if( w==23 ) + { + r = -4.697e+00; + } + if( w==22 ) + { + r = -4.860e+00; + } + if( w==21 ) + { + r = -5.029e+00; + } + if( w==20 ) + { + r = -5.204e+00; + } + if( w==19 ) + { + r = -5.383e+00; + } + if( w==18 ) + { + r = -5.569e+00; + } + if( w==17 ) + { + r = -5.762e+00; + } + if( w==16 ) + { + r = -5.960e+00; + } + if( w==15 ) + { + r = -6.170e+00; + } + if( w==14 ) + { + r = -6.390e+00; + } + if( w==13 ) + { + r = -6.613e+00; + } + if( w==12 ) + { + r = -6.842e+00; + } + if( w==11 ) + { + r = -7.083e+00; + } + if( w==10 ) + { + r = -7.329e+00; + } + if( w==9 ) + { + r = -7.594e+00; + } + if( w==8 ) + { + r = -7.871e+00; + } + if( w==7 ) + { + r = -8.146e+00; + } + if( w==6 ) + { + r = -8.451e+00; + } + if( w==5 ) + { + r = -8.788e+00; + } + if( w==4 ) + { + r = -9.144e+00; + } + if( w==3 ) + { + r = -9.481e+00; + } + if( w==2 ) + { + r = -9.992e+00; + } + if( w==1 ) + { + r = -1.040e+01; + } + if( w<=0 ) + { + r = -1.109e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 17) +*************************************************************************/ +static double wsr_w17(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-2.112463e+01*s+7.650000e+01, _state); + if( w>=76 ) + { + r = -6.931e-01; + } + if( w==75 ) + { + r = -7.306e-01; + } + if( w==74 ) + { + r = -7.695e-01; + } + if( w==73 ) + { + r = -8.097e-01; + } + if( w==72 ) + { + r = -8.514e-01; + } + if( w==71 ) + { + r = -8.946e-01; + } + if( w==70 ) + { + r = -9.392e-01; + } + if( w==69 ) + { + r = -9.853e-01; + } + if( w==68 ) + { + r = -1.033e+00; + } + if( w==67 ) + { + r = -1.082e+00; + } + if( w==66 ) + { + r = -1.133e+00; + } + if( w==65 ) + { + r = -1.185e+00; + } + if( w==64 ) + { + r = -1.240e+00; + } + if( w==63 ) + { + r = -1.295e+00; + } + if( w==62 ) + { + r = -1.353e+00; + } + if( w==61 ) + { + r = -1.412e+00; + } + if( w==60 ) + { + r = -1.473e+00; + } + if( w==59 ) + { + r = -1.536e+00; + } + if( w==58 ) + { + r = -1.600e+00; + } + if( w==57 ) + { + r = -1.666e+00; + } + if( w==56 ) + { + r = -1.735e+00; + } + if( w==55 ) + { + r = -1.805e+00; + } + if( w==54 ) + { + r = -1.877e+00; + } + if( w==53 ) + { + r = -1.951e+00; + } + if( w==52 ) + { + r = -2.028e+00; + } + if( w==51 ) + { + r = -2.106e+00; + } + if( w==50 ) + { + r = -2.186e+00; + } + if( w==49 ) + { + r = -2.269e+00; + } + if( w==48 ) + { + r = -2.353e+00; + } + if( w==47 ) + { + r = -2.440e+00; + } + if( w==46 ) + { + r = -2.530e+00; + } + if( w==45 ) + { + r = -2.621e+00; + } + if( w==44 ) + { + r = -2.715e+00; + } + if( w==43 ) + { + r = -2.812e+00; + } + if( w==42 ) + { + r = -2.911e+00; + } + if( w==41 ) + { + r = -3.012e+00; + } + if( w==40 ) + { + r = -3.116e+00; + } + if( w==39 ) + { + r = -3.223e+00; + } + if( w==38 ) + { + r = -3.332e+00; + } + if( w==37 ) + { + r = -3.445e+00; + } + if( w==36 ) + { + r = -3.560e+00; + } + if( w==35 ) + { + r = -3.678e+00; + } + if( w==34 ) + { + r = -3.799e+00; + } + if( w==33 ) + { + r = -3.924e+00; + } + if( w==32 ) + { + r = -4.052e+00; + } + if( w==31 ) + { + r = -4.183e+00; + } + if( w==30 ) + { + r = -4.317e+00; + } + if( w==29 ) + { + r = -4.456e+00; + } + if( w==28 ) + { + r = -4.597e+00; + } + if( w==27 ) + { + r = -4.743e+00; + } + if( w==26 ) + { + r = -4.893e+00; + } + if( w==25 ) + { + r = -5.047e+00; + } + if( w==24 ) + { + r = -5.204e+00; + } + if( w==23 ) + { + r = -5.367e+00; + } + if( w==22 ) + { + r = -5.534e+00; + } + if( w==21 ) + { + r = -5.706e+00; + } + if( w==20 ) + { + r = -5.884e+00; + } + if( w==19 ) + { + r = -6.066e+00; + } + if( w==18 ) + { + r = -6.254e+00; + } + if( w==17 ) + { + r = -6.451e+00; + } + if( w==16 ) + { + r = -6.654e+00; + } + if( w==15 ) + { + r = -6.864e+00; + } + if( w==14 ) + { + r = -7.083e+00; + } + if( w==13 ) + { + r = -7.306e+00; + } + if( w==12 ) + { + r = -7.535e+00; + } + if( w==11 ) + { + r = -7.776e+00; + } + if( w==10 ) + { + r = -8.022e+00; + } + if( w==9 ) + { + r = -8.287e+00; + } + if( w==8 ) + { + r = -8.565e+00; + } + if( w==7 ) + { + r = -8.839e+00; + } + if( w==6 ) + { + r = -9.144e+00; + } + if( w==5 ) + { + r = -9.481e+00; + } + if( w==4 ) + { + r = -9.838e+00; + } + if( w==3 ) + { + r = -1.017e+01; + } + if( w==2 ) + { + r = -1.068e+01; + } + if( w==1 ) + { + r = -1.109e+01; + } + if( w<=0 ) + { + r = -1.178e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 18) +*************************************************************************/ +static double wsr_w18(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-2.296193e+01*s+8.550000e+01, _state); + if( w>=85 ) + { + r = -6.931e-01; + } + if( w==84 ) + { + r = -7.276e-01; + } + if( w==83 ) + { + r = -7.633e-01; + } + if( w==82 ) + { + r = -8.001e-01; + } + if( w==81 ) + { + r = -8.381e-01; + } + if( w==80 ) + { + r = -8.774e-01; + } + if( w==79 ) + { + r = -9.179e-01; + } + if( w==78 ) + { + r = -9.597e-01; + } + if( w==77 ) + { + r = -1.003e+00; + } + if( w==76 ) + { + r = -1.047e+00; + } + if( w==75 ) + { + r = -1.093e+00; + } + if( w==74 ) + { + r = -1.140e+00; + } + if( w==73 ) + { + r = -1.188e+00; + } + if( w==72 ) + { + r = -1.238e+00; + } + if( w==71 ) + { + r = -1.289e+00; + } + if( w==70 ) + { + r = -1.342e+00; + } + if( w==69 ) + { + r = -1.396e+00; + } + if( w==68 ) + { + r = -1.452e+00; + } + if( w==67 ) + { + r = -1.509e+00; + } + if( w==66 ) + { + r = -1.568e+00; + } + if( w==65 ) + { + r = -1.628e+00; + } + if( w==64 ) + { + r = -1.690e+00; + } + if( w==63 ) + { + r = -1.753e+00; + } + if( w==62 ) + { + r = -1.818e+00; + } + if( w==61 ) + { + r = -1.885e+00; + } + if( w==60 ) + { + r = -1.953e+00; + } + if( w==59 ) + { + r = -2.023e+00; + } + if( w==58 ) + { + r = -2.095e+00; + } + if( w==57 ) + { + r = -2.168e+00; + } + if( w==56 ) + { + r = -2.244e+00; + } + if( w==55 ) + { + r = -2.321e+00; + } + if( w==54 ) + { + r = -2.400e+00; + } + if( w==53 ) + { + r = -2.481e+00; + } + if( w==52 ) + { + r = -2.564e+00; + } + if( w==51 ) + { + r = -2.648e+00; + } + if( w==50 ) + { + r = -2.735e+00; + } + if( w==49 ) + { + r = -2.824e+00; + } + if( w==48 ) + { + r = -2.915e+00; + } + if( w==47 ) + { + r = -3.008e+00; + } + if( w==46 ) + { + r = -3.104e+00; + } + if( w==45 ) + { + r = -3.201e+00; + } + if( w==44 ) + { + r = -3.301e+00; + } + if( w==43 ) + { + r = -3.403e+00; + } + if( w==42 ) + { + r = -3.508e+00; + } + if( w==41 ) + { + r = -3.615e+00; + } + if( w==40 ) + { + r = -3.724e+00; + } + if( w==39 ) + { + r = -3.836e+00; + } + if( w==38 ) + { + r = -3.950e+00; + } + if( w==37 ) + { + r = -4.068e+00; + } + if( w==36 ) + { + r = -4.188e+00; + } + if( w==35 ) + { + r = -4.311e+00; + } + if( w==34 ) + { + r = -4.437e+00; + } + if( w==33 ) + { + r = -4.565e+00; + } + if( w==32 ) + { + r = -4.698e+00; + } + if( w==31 ) + { + r = -4.833e+00; + } + if( w==30 ) + { + r = -4.971e+00; + } + if( w==29 ) + { + r = -5.113e+00; + } + if( w==28 ) + { + r = -5.258e+00; + } + if( w==27 ) + { + r = -5.408e+00; + } + if( w==26 ) + { + r = -5.561e+00; + } + if( w==25 ) + { + r = -5.717e+00; + } + if( w==24 ) + { + r = -5.878e+00; + } + if( w==23 ) + { + r = -6.044e+00; + } + if( w==22 ) + { + r = -6.213e+00; + } + if( w==21 ) + { + r = -6.388e+00; + } + if( w==20 ) + { + r = -6.569e+00; + } + if( w==19 ) + { + r = -6.753e+00; + } + if( w==18 ) + { + r = -6.943e+00; + } + if( w==17 ) + { + r = -7.144e+00; + } + if( w==16 ) + { + r = -7.347e+00; + } + if( w==15 ) + { + r = -7.557e+00; + } + if( w==14 ) + { + r = -7.776e+00; + } + if( w==13 ) + { + r = -7.999e+00; + } + if( w==12 ) + { + r = -8.228e+00; + } + if( w==11 ) + { + r = -8.469e+00; + } + if( w==10 ) + { + r = -8.715e+00; + } + if( w==9 ) + { + r = -8.980e+00; + } + if( w==8 ) + { + r = -9.258e+00; + } + if( w==7 ) + { + r = -9.532e+00; + } + if( w==6 ) + { + r = -9.838e+00; + } + if( w==5 ) + { + r = -1.017e+01; + } + if( w==4 ) + { + r = -1.053e+01; + } + if( w==3 ) + { + r = -1.087e+01; + } + if( w==2 ) + { + r = -1.138e+01; + } + if( w==1 ) + { + r = -1.178e+01; + } + if( w<=0 ) + { + r = -1.248e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 19) +*************************************************************************/ +static double wsr_w19(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-2.484955e+01*s+9.500000e+01, _state); + if( w>=95 ) + { + r = -6.776e-01; + } + if( w==94 ) + { + r = -7.089e-01; + } + if( w==93 ) + { + r = -7.413e-01; + } + if( w==92 ) + { + r = -7.747e-01; + } + if( w==91 ) + { + r = -8.090e-01; + } + if( w==90 ) + { + r = -8.445e-01; + } + if( w==89 ) + { + r = -8.809e-01; + } + if( w==88 ) + { + r = -9.185e-01; + } + if( w==87 ) + { + r = -9.571e-01; + } + if( w==86 ) + { + r = -9.968e-01; + } + if( w==85 ) + { + r = -1.038e+00; + } + if( w==84 ) + { + r = -1.080e+00; + } + if( w==83 ) + { + r = -1.123e+00; + } + if( w==82 ) + { + r = -1.167e+00; + } + if( w==81 ) + { + r = -1.213e+00; + } + if( w==80 ) + { + r = -1.259e+00; + } + if( w==79 ) + { + r = -1.307e+00; + } + if( w==78 ) + { + r = -1.356e+00; + } + if( w==77 ) + { + r = -1.407e+00; + } + if( w==76 ) + { + r = -1.458e+00; + } + if( w==75 ) + { + r = -1.511e+00; + } + if( w==74 ) + { + r = -1.565e+00; + } + if( w==73 ) + { + r = -1.621e+00; + } + if( w==72 ) + { + r = -1.678e+00; + } + if( w==71 ) + { + r = -1.736e+00; + } + if( w==70 ) + { + r = -1.796e+00; + } + if( w==69 ) + { + r = -1.857e+00; + } + if( w==68 ) + { + r = -1.919e+00; + } + if( w==67 ) + { + r = -1.983e+00; + } + if( w==66 ) + { + r = -2.048e+00; + } + if( w==65 ) + { + r = -2.115e+00; + } + if( w==64 ) + { + r = -2.183e+00; + } + if( w==63 ) + { + r = -2.253e+00; + } + if( w==62 ) + { + r = -2.325e+00; + } + if( w==61 ) + { + r = -2.398e+00; + } + if( w==60 ) + { + r = -2.472e+00; + } + if( w==59 ) + { + r = -2.548e+00; + } + if( w==58 ) + { + r = -2.626e+00; + } + if( w==57 ) + { + r = -2.706e+00; + } + if( w==56 ) + { + r = -2.787e+00; + } + if( w==55 ) + { + r = -2.870e+00; + } + if( w==54 ) + { + r = -2.955e+00; + } + if( w==53 ) + { + r = -3.042e+00; + } + if( w==52 ) + { + r = -3.130e+00; + } + if( w==51 ) + { + r = -3.220e+00; + } + if( w==50 ) + { + r = -3.313e+00; + } + if( w==49 ) + { + r = -3.407e+00; + } + if( w==48 ) + { + r = -3.503e+00; + } + if( w==47 ) + { + r = -3.601e+00; + } + if( w==46 ) + { + r = -3.702e+00; + } + if( w==45 ) + { + r = -3.804e+00; + } + if( w==44 ) + { + r = -3.909e+00; + } + if( w==43 ) + { + r = -4.015e+00; + } + if( w==42 ) + { + r = -4.125e+00; + } + if( w==41 ) + { + r = -4.236e+00; + } + if( w==40 ) + { + r = -4.350e+00; + } + if( w==39 ) + { + r = -4.466e+00; + } + if( w==38 ) + { + r = -4.585e+00; + } + if( w==37 ) + { + r = -4.706e+00; + } + if( w==36 ) + { + r = -4.830e+00; + } + if( w==35 ) + { + r = -4.957e+00; + } + if( w==34 ) + { + r = -5.086e+00; + } + if( w==33 ) + { + r = -5.219e+00; + } + if( w==32 ) + { + r = -5.355e+00; + } + if( w==31 ) + { + r = -5.493e+00; + } + if( w==30 ) + { + r = -5.634e+00; + } + if( w==29 ) + { + r = -5.780e+00; + } + if( w==28 ) + { + r = -5.928e+00; + } + if( w==27 ) + { + r = -6.080e+00; + } + if( w==26 ) + { + r = -6.235e+00; + } + if( w==25 ) + { + r = -6.394e+00; + } + if( w==24 ) + { + r = -6.558e+00; + } + if( w==23 ) + { + r = -6.726e+00; + } + if( w==22 ) + { + r = -6.897e+00; + } + if( w==21 ) + { + r = -7.074e+00; + } + if( w==20 ) + { + r = -7.256e+00; + } + if( w==19 ) + { + r = -7.443e+00; + } + if( w==18 ) + { + r = -7.636e+00; + } + if( w==17 ) + { + r = -7.837e+00; + } + if( w==16 ) + { + r = -8.040e+00; + } + if( w==15 ) + { + r = -8.250e+00; + } + if( w==14 ) + { + r = -8.469e+00; + } + if( w==13 ) + { + r = -8.692e+00; + } + if( w==12 ) + { + r = -8.921e+00; + } + if( w==11 ) + { + r = -9.162e+00; + } + if( w==10 ) + { + r = -9.409e+00; + } + if( w==9 ) + { + r = -9.673e+00; + } + if( w==8 ) + { + r = -9.951e+00; + } + if( w==7 ) + { + r = -1.023e+01; + } + if( w==6 ) + { + r = -1.053e+01; + } + if( w==5 ) + { + r = -1.087e+01; + } + if( w==4 ) + { + r = -1.122e+01; + } + if( w==3 ) + { + r = -1.156e+01; + } + if( w==2 ) + { + r = -1.207e+01; + } + if( w==1 ) + { + r = -1.248e+01; + } + if( w<=0 ) + { + r = -1.317e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 20) +*************************************************************************/ +static double wsr_w20(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-2.678619e+01*s+1.050000e+02, _state); + if( w>=105 ) + { + r = -6.787e-01; + } + if( w==104 ) + { + r = -7.078e-01; + } + if( w==103 ) + { + r = -7.378e-01; + } + if( w==102 ) + { + r = -7.686e-01; + } + if( w==101 ) + { + r = -8.004e-01; + } + if( w==100 ) + { + r = -8.330e-01; + } + if( w==99 ) + { + r = -8.665e-01; + } + if( w==98 ) + { + r = -9.010e-01; + } + if( w==97 ) + { + r = -9.363e-01; + } + if( w==96 ) + { + r = -9.726e-01; + } + if( w==95 ) + { + r = -1.010e+00; + } + if( w==94 ) + { + r = -1.048e+00; + } + if( w==93 ) + { + r = -1.087e+00; + } + if( w==92 ) + { + r = -1.128e+00; + } + if( w==91 ) + { + r = -1.169e+00; + } + if( w==90 ) + { + r = -1.211e+00; + } + if( w==89 ) + { + r = -1.254e+00; + } + if( w==88 ) + { + r = -1.299e+00; + } + if( w==87 ) + { + r = -1.344e+00; + } + if( w==86 ) + { + r = -1.390e+00; + } + if( w==85 ) + { + r = -1.438e+00; + } + if( w==84 ) + { + r = -1.486e+00; + } + if( w==83 ) + { + r = -1.536e+00; + } + if( w==82 ) + { + r = -1.587e+00; + } + if( w==81 ) + { + r = -1.639e+00; + } + if( w==80 ) + { + r = -1.692e+00; + } + if( w==79 ) + { + r = -1.746e+00; + } + if( w==78 ) + { + r = -1.802e+00; + } + if( w==77 ) + { + r = -1.859e+00; + } + if( w==76 ) + { + r = -1.916e+00; + } + if( w==75 ) + { + r = -1.976e+00; + } + if( w==74 ) + { + r = -2.036e+00; + } + if( w==73 ) + { + r = -2.098e+00; + } + if( w==72 ) + { + r = -2.161e+00; + } + if( w==71 ) + { + r = -2.225e+00; + } + if( w==70 ) + { + r = -2.290e+00; + } + if( w==69 ) + { + r = -2.357e+00; + } + if( w==68 ) + { + r = -2.426e+00; + } + if( w==67 ) + { + r = -2.495e+00; + } + if( w==66 ) + { + r = -2.566e+00; + } + if( w==65 ) + { + r = -2.639e+00; + } + if( w==64 ) + { + r = -2.713e+00; + } + if( w==63 ) + { + r = -2.788e+00; + } + if( w==62 ) + { + r = -2.865e+00; + } + if( w==61 ) + { + r = -2.943e+00; + } + if( w==60 ) + { + r = -3.023e+00; + } + if( w==59 ) + { + r = -3.104e+00; + } + if( w==58 ) + { + r = -3.187e+00; + } + if( w==57 ) + { + r = -3.272e+00; + } + if( w==56 ) + { + r = -3.358e+00; + } + if( w==55 ) + { + r = -3.446e+00; + } + if( w==54 ) + { + r = -3.536e+00; + } + if( w==53 ) + { + r = -3.627e+00; + } + if( w==52 ) + { + r = -3.721e+00; + } + if( w==51 ) + { + r = -3.815e+00; + } + if( w==50 ) + { + r = -3.912e+00; + } + if( w==49 ) + { + r = -4.011e+00; + } + if( w==48 ) + { + r = -4.111e+00; + } + if( w==47 ) + { + r = -4.214e+00; + } + if( w==46 ) + { + r = -4.318e+00; + } + if( w==45 ) + { + r = -4.425e+00; + } + if( w==44 ) + { + r = -4.534e+00; + } + if( w==43 ) + { + r = -4.644e+00; + } + if( w==42 ) + { + r = -4.757e+00; + } + if( w==41 ) + { + r = -4.872e+00; + } + if( w==40 ) + { + r = -4.990e+00; + } + if( w==39 ) + { + r = -5.109e+00; + } + if( w==38 ) + { + r = -5.232e+00; + } + if( w==37 ) + { + r = -5.356e+00; + } + if( w==36 ) + { + r = -5.484e+00; + } + if( w==35 ) + { + r = -5.614e+00; + } + if( w==34 ) + { + r = -5.746e+00; + } + if( w==33 ) + { + r = -5.882e+00; + } + if( w==32 ) + { + r = -6.020e+00; + } + if( w==31 ) + { + r = -6.161e+00; + } + if( w==30 ) + { + r = -6.305e+00; + } + if( w==29 ) + { + r = -6.453e+00; + } + if( w==28 ) + { + r = -6.603e+00; + } + if( w==27 ) + { + r = -6.757e+00; + } + if( w==26 ) + { + r = -6.915e+00; + } + if( w==25 ) + { + r = -7.076e+00; + } + if( w==24 ) + { + r = -7.242e+00; + } + if( w==23 ) + { + r = -7.411e+00; + } + if( w==22 ) + { + r = -7.584e+00; + } + if( w==21 ) + { + r = -7.763e+00; + } + if( w==20 ) + { + r = -7.947e+00; + } + if( w==19 ) + { + r = -8.136e+00; + } + if( w==18 ) + { + r = -8.330e+00; + } + if( w==17 ) + { + r = -8.530e+00; + } + if( w==16 ) + { + r = -8.733e+00; + } + if( w==15 ) + { + r = -8.943e+00; + } + if( w==14 ) + { + r = -9.162e+00; + } + if( w==13 ) + { + r = -9.386e+00; + } + if( w==12 ) + { + r = -9.614e+00; + } + if( w==11 ) + { + r = -9.856e+00; + } + if( w==10 ) + { + r = -1.010e+01; + } + if( w==9 ) + { + r = -1.037e+01; + } + if( w==8 ) + { + r = -1.064e+01; + } + if( w==7 ) + { + r = -1.092e+01; + } + if( w==6 ) + { + r = -1.122e+01; + } + if( w==5 ) + { + r = -1.156e+01; + } + if( w==4 ) + { + r = -1.192e+01; + } + if( w==3 ) + { + r = -1.225e+01; + } + if( w==2 ) + { + r = -1.276e+01; + } + if( w==1 ) + { + r = -1.317e+01; + } + if( w<=0 ) + { + r = -1.386e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 21) +*************************************************************************/ +static double wsr_w21(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-2.877064e+01*s+1.155000e+02, _state); + if( w>=115 ) + { + r = -6.931e-01; + } + if( w==114 ) + { + r = -7.207e-01; + } + if( w==113 ) + { + r = -7.489e-01; + } + if( w==112 ) + { + r = -7.779e-01; + } + if( w==111 ) + { + r = -8.077e-01; + } + if( w==110 ) + { + r = -8.383e-01; + } + if( w==109 ) + { + r = -8.697e-01; + } + if( w==108 ) + { + r = -9.018e-01; + } + if( w==107 ) + { + r = -9.348e-01; + } + if( w==106 ) + { + r = -9.685e-01; + } + if( w==105 ) + { + r = -1.003e+00; + } + if( w==104 ) + { + r = -1.039e+00; + } + if( w==103 ) + { + r = -1.075e+00; + } + if( w==102 ) + { + r = -1.112e+00; + } + if( w==101 ) + { + r = -1.150e+00; + } + if( w==100 ) + { + r = -1.189e+00; + } + if( w==99 ) + { + r = -1.229e+00; + } + if( w==98 ) + { + r = -1.269e+00; + } + if( w==97 ) + { + r = -1.311e+00; + } + if( w==96 ) + { + r = -1.353e+00; + } + if( w==95 ) + { + r = -1.397e+00; + } + if( w==94 ) + { + r = -1.441e+00; + } + if( w==93 ) + { + r = -1.486e+00; + } + if( w==92 ) + { + r = -1.533e+00; + } + if( w==91 ) + { + r = -1.580e+00; + } + if( w==90 ) + { + r = -1.628e+00; + } + if( w==89 ) + { + r = -1.677e+00; + } + if( w==88 ) + { + r = -1.728e+00; + } + if( w==87 ) + { + r = -1.779e+00; + } + if( w==86 ) + { + r = -1.831e+00; + } + if( w==85 ) + { + r = -1.884e+00; + } + if( w==84 ) + { + r = -1.939e+00; + } + if( w==83 ) + { + r = -1.994e+00; + } + if( w==82 ) + { + r = -2.051e+00; + } + if( w==81 ) + { + r = -2.108e+00; + } + if( w==80 ) + { + r = -2.167e+00; + } + if( w==79 ) + { + r = -2.227e+00; + } + if( w==78 ) + { + r = -2.288e+00; + } + if( w==77 ) + { + r = -2.350e+00; + } + if( w==76 ) + { + r = -2.414e+00; + } + if( w==75 ) + { + r = -2.478e+00; + } + if( w==74 ) + { + r = -2.544e+00; + } + if( w==73 ) + { + r = -2.611e+00; + } + if( w==72 ) + { + r = -2.679e+00; + } + if( w==71 ) + { + r = -2.748e+00; + } + if( w==70 ) + { + r = -2.819e+00; + } + if( w==69 ) + { + r = -2.891e+00; + } + if( w==68 ) + { + r = -2.964e+00; + } + if( w==67 ) + { + r = -3.039e+00; + } + if( w==66 ) + { + r = -3.115e+00; + } + if( w==65 ) + { + r = -3.192e+00; + } + if( w==64 ) + { + r = -3.270e+00; + } + if( w==63 ) + { + r = -3.350e+00; + } + if( w==62 ) + { + r = -3.432e+00; + } + if( w==61 ) + { + r = -3.515e+00; + } + if( w==60 ) + { + r = -3.599e+00; + } + if( w==59 ) + { + r = -3.685e+00; + } + if( w==58 ) + { + r = -3.772e+00; + } + if( w==57 ) + { + r = -3.861e+00; + } + if( w==56 ) + { + r = -3.952e+00; + } + if( w==55 ) + { + r = -4.044e+00; + } + if( w==54 ) + { + r = -4.138e+00; + } + if( w==53 ) + { + r = -4.233e+00; + } + if( w==52 ) + { + r = -4.330e+00; + } + if( w==51 ) + { + r = -4.429e+00; + } + if( w==50 ) + { + r = -4.530e+00; + } + if( w==49 ) + { + r = -4.632e+00; + } + if( w==48 ) + { + r = -4.736e+00; + } + if( w==47 ) + { + r = -4.842e+00; + } + if( w==46 ) + { + r = -4.950e+00; + } + if( w==45 ) + { + r = -5.060e+00; + } + if( w==44 ) + { + r = -5.172e+00; + } + if( w==43 ) + { + r = -5.286e+00; + } + if( w==42 ) + { + r = -5.402e+00; + } + if( w==41 ) + { + r = -5.520e+00; + } + if( w==40 ) + { + r = -5.641e+00; + } + if( w==39 ) + { + r = -5.763e+00; + } + if( w==38 ) + { + r = -5.889e+00; + } + if( w==37 ) + { + r = -6.016e+00; + } + if( w==36 ) + { + r = -6.146e+00; + } + if( w==35 ) + { + r = -6.278e+00; + } + if( w==34 ) + { + r = -6.413e+00; + } + if( w==33 ) + { + r = -6.551e+00; + } + if( w==32 ) + { + r = -6.692e+00; + } + if( w==31 ) + { + r = -6.835e+00; + } + if( w==30 ) + { + r = -6.981e+00; + } + if( w==29 ) + { + r = -7.131e+00; + } + if( w==28 ) + { + r = -7.283e+00; + } + if( w==27 ) + { + r = -7.439e+00; + } + if( w==26 ) + { + r = -7.599e+00; + } + if( w==25 ) + { + r = -7.762e+00; + } + if( w==24 ) + { + r = -7.928e+00; + } + if( w==23 ) + { + r = -8.099e+00; + } + if( w==22 ) + { + r = -8.274e+00; + } + if( w==21 ) + { + r = -8.454e+00; + } + if( w==20 ) + { + r = -8.640e+00; + } + if( w==19 ) + { + r = -8.829e+00; + } + if( w==18 ) + { + r = -9.023e+00; + } + if( w==17 ) + { + r = -9.223e+00; + } + if( w==16 ) + { + r = -9.426e+00; + } + if( w==15 ) + { + r = -9.636e+00; + } + if( w==14 ) + { + r = -9.856e+00; + } + if( w==13 ) + { + r = -1.008e+01; + } + if( w==12 ) + { + r = -1.031e+01; + } + if( w==11 ) + { + r = -1.055e+01; + } + if( w==10 ) + { + r = -1.079e+01; + } + if( w==9 ) + { + r = -1.106e+01; + } + if( w==8 ) + { + r = -1.134e+01; + } + if( w==7 ) + { + r = -1.161e+01; + } + if( w==6 ) + { + r = -1.192e+01; + } + if( w==5 ) + { + r = -1.225e+01; + } + if( w==4 ) + { + r = -1.261e+01; + } + if( w==3 ) + { + r = -1.295e+01; + } + if( w==2 ) + { + r = -1.346e+01; + } + if( w==1 ) + { + r = -1.386e+01; + } + if( w<=0 ) + { + r = -1.456e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 22) +*************************************************************************/ +static double wsr_w22(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-3.080179e+01*s+1.265000e+02, _state); + if( w>=126 ) + { + r = -6.931e-01; + } + if( w==125 ) + { + r = -7.189e-01; + } + if( w==124 ) + { + r = -7.452e-01; + } + if( w==123 ) + { + r = -7.722e-01; + } + if( w==122 ) + { + r = -7.999e-01; + } + if( w==121 ) + { + r = -8.283e-01; + } + if( w==120 ) + { + r = -8.573e-01; + } + if( w==119 ) + { + r = -8.871e-01; + } + if( w==118 ) + { + r = -9.175e-01; + } + if( w==117 ) + { + r = -9.486e-01; + } + if( w==116 ) + { + r = -9.805e-01; + } + if( w==115 ) + { + r = -1.013e+00; + } + if( w==114 ) + { + r = -1.046e+00; + } + if( w==113 ) + { + r = -1.080e+00; + } + if( w==112 ) + { + r = -1.115e+00; + } + if( w==111 ) + { + r = -1.151e+00; + } + if( w==110 ) + { + r = -1.187e+00; + } + if( w==109 ) + { + r = -1.224e+00; + } + if( w==108 ) + { + r = -1.262e+00; + } + if( w==107 ) + { + r = -1.301e+00; + } + if( w==106 ) + { + r = -1.340e+00; + } + if( w==105 ) + { + r = -1.381e+00; + } + if( w==104 ) + { + r = -1.422e+00; + } + if( w==103 ) + { + r = -1.464e+00; + } + if( w==102 ) + { + r = -1.506e+00; + } + if( w==101 ) + { + r = -1.550e+00; + } + if( w==100 ) + { + r = -1.594e+00; + } + if( w==99 ) + { + r = -1.640e+00; + } + if( w==98 ) + { + r = -1.686e+00; + } + if( w==97 ) + { + r = -1.733e+00; + } + if( w==96 ) + { + r = -1.781e+00; + } + if( w==95 ) + { + r = -1.830e+00; + } + if( w==94 ) + { + r = -1.880e+00; + } + if( w==93 ) + { + r = -1.930e+00; + } + if( w==92 ) + { + r = -1.982e+00; + } + if( w==91 ) + { + r = -2.034e+00; + } + if( w==90 ) + { + r = -2.088e+00; + } + if( w==89 ) + { + r = -2.142e+00; + } + if( w==88 ) + { + r = -2.198e+00; + } + if( w==87 ) + { + r = -2.254e+00; + } + if( w==86 ) + { + r = -2.312e+00; + } + if( w==85 ) + { + r = -2.370e+00; + } + if( w==84 ) + { + r = -2.429e+00; + } + if( w==83 ) + { + r = -2.490e+00; + } + if( w==82 ) + { + r = -2.551e+00; + } + if( w==81 ) + { + r = -2.614e+00; + } + if( w==80 ) + { + r = -2.677e+00; + } + if( w==79 ) + { + r = -2.742e+00; + } + if( w==78 ) + { + r = -2.808e+00; + } + if( w==77 ) + { + r = -2.875e+00; + } + if( w==76 ) + { + r = -2.943e+00; + } + if( w==75 ) + { + r = -3.012e+00; + } + if( w==74 ) + { + r = -3.082e+00; + } + if( w==73 ) + { + r = -3.153e+00; + } + if( w==72 ) + { + r = -3.226e+00; + } + if( w==71 ) + { + r = -3.300e+00; + } + if( w==70 ) + { + r = -3.375e+00; + } + if( w==69 ) + { + r = -3.451e+00; + } + if( w==68 ) + { + r = -3.529e+00; + } + if( w==67 ) + { + r = -3.607e+00; + } + if( w==66 ) + { + r = -3.687e+00; + } + if( w==65 ) + { + r = -3.769e+00; + } + if( w==64 ) + { + r = -3.851e+00; + } + if( w==63 ) + { + r = -3.935e+00; + } + if( w==62 ) + { + r = -4.021e+00; + } + if( w==61 ) + { + r = -4.108e+00; + } + if( w==60 ) + { + r = -4.196e+00; + } + if( w==59 ) + { + r = -4.285e+00; + } + if( w==58 ) + { + r = -4.376e+00; + } + if( w==57 ) + { + r = -4.469e+00; + } + if( w==56 ) + { + r = -4.563e+00; + } + if( w==55 ) + { + r = -4.659e+00; + } + if( w==54 ) + { + r = -4.756e+00; + } + if( w==53 ) + { + r = -4.855e+00; + } + if( w==52 ) + { + r = -4.955e+00; + } + if( w==51 ) + { + r = -5.057e+00; + } + if( w==50 ) + { + r = -5.161e+00; + } + if( w==49 ) + { + r = -5.266e+00; + } + if( w==48 ) + { + r = -5.374e+00; + } + if( w==47 ) + { + r = -5.483e+00; + } + if( w==46 ) + { + r = -5.594e+00; + } + if( w==45 ) + { + r = -5.706e+00; + } + if( w==44 ) + { + r = -5.821e+00; + } + if( w==43 ) + { + r = -5.938e+00; + } + if( w==42 ) + { + r = -6.057e+00; + } + if( w==41 ) + { + r = -6.177e+00; + } + if( w==40 ) + { + r = -6.300e+00; + } + if( w==39 ) + { + r = -6.426e+00; + } + if( w==38 ) + { + r = -6.553e+00; + } + if( w==37 ) + { + r = -6.683e+00; + } + if( w==36 ) + { + r = -6.815e+00; + } + if( w==35 ) + { + r = -6.949e+00; + } + if( w==34 ) + { + r = -7.086e+00; + } + if( w==33 ) + { + r = -7.226e+00; + } + if( w==32 ) + { + r = -7.368e+00; + } + if( w==31 ) + { + r = -7.513e+00; + } + if( w==30 ) + { + r = -7.661e+00; + } + if( w==29 ) + { + r = -7.813e+00; + } + if( w==28 ) + { + r = -7.966e+00; + } + if( w==27 ) + { + r = -8.124e+00; + } + if( w==26 ) + { + r = -8.285e+00; + } + if( w==25 ) + { + r = -8.449e+00; + } + if( w==24 ) + { + r = -8.617e+00; + } + if( w==23 ) + { + r = -8.789e+00; + } + if( w==22 ) + { + r = -8.965e+00; + } + if( w==21 ) + { + r = -9.147e+00; + } + if( w==20 ) + { + r = -9.333e+00; + } + if( w==19 ) + { + r = -9.522e+00; + } + if( w==18 ) + { + r = -9.716e+00; + } + if( w==17 ) + { + r = -9.917e+00; + } + if( w==16 ) + { + r = -1.012e+01; + } + if( w==15 ) + { + r = -1.033e+01; + } + if( w==14 ) + { + r = -1.055e+01; + } + if( w==13 ) + { + r = -1.077e+01; + } + if( w==12 ) + { + r = -1.100e+01; + } + if( w==11 ) + { + r = -1.124e+01; + } + if( w==10 ) + { + r = -1.149e+01; + } + if( w==9 ) + { + r = -1.175e+01; + } + if( w==8 ) + { + r = -1.203e+01; + } + if( w==7 ) + { + r = -1.230e+01; + } + if( w==6 ) + { + r = -1.261e+01; + } + if( w==5 ) + { + r = -1.295e+01; + } + if( w==4 ) + { + r = -1.330e+01; + } + if( w==3 ) + { + r = -1.364e+01; + } + if( w==2 ) + { + r = -1.415e+01; + } + if( w==1 ) + { + r = -1.456e+01; + } + if( w<=0 ) + { + r = -1.525e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 23) +*************************************************************************/ +static double wsr_w23(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-3.287856e+01*s+1.380000e+02, _state); + if( w>=138 ) + { + r = -6.813e-01; + } + if( w==137 ) + { + r = -7.051e-01; + } + if( w==136 ) + { + r = -7.295e-01; + } + if( w==135 ) + { + r = -7.544e-01; + } + if( w==134 ) + { + r = -7.800e-01; + } + if( w==133 ) + { + r = -8.061e-01; + } + if( w==132 ) + { + r = -8.328e-01; + } + if( w==131 ) + { + r = -8.601e-01; + } + if( w==130 ) + { + r = -8.880e-01; + } + if( w==129 ) + { + r = -9.166e-01; + } + if( w==128 ) + { + r = -9.457e-01; + } + if( w==127 ) + { + r = -9.755e-01; + } + if( w==126 ) + { + r = -1.006e+00; + } + if( w==125 ) + { + r = -1.037e+00; + } + if( w==124 ) + { + r = -1.069e+00; + } + if( w==123 ) + { + r = -1.101e+00; + } + if( w==122 ) + { + r = -1.134e+00; + } + if( w==121 ) + { + r = -1.168e+00; + } + if( w==120 ) + { + r = -1.202e+00; + } + if( w==119 ) + { + r = -1.237e+00; + } + if( w==118 ) + { + r = -1.273e+00; + } + if( w==117 ) + { + r = -1.309e+00; + } + if( w==116 ) + { + r = -1.347e+00; + } + if( w==115 ) + { + r = -1.384e+00; + } + if( w==114 ) + { + r = -1.423e+00; + } + if( w==113 ) + { + r = -1.462e+00; + } + if( w==112 ) + { + r = -1.502e+00; + } + if( w==111 ) + { + r = -1.543e+00; + } + if( w==110 ) + { + r = -1.585e+00; + } + if( w==109 ) + { + r = -1.627e+00; + } + if( w==108 ) + { + r = -1.670e+00; + } + if( w==107 ) + { + r = -1.714e+00; + } + if( w==106 ) + { + r = -1.758e+00; + } + if( w==105 ) + { + r = -1.804e+00; + } + if( w==104 ) + { + r = -1.850e+00; + } + if( w==103 ) + { + r = -1.897e+00; + } + if( w==102 ) + { + r = -1.944e+00; + } + if( w==101 ) + { + r = -1.993e+00; + } + if( w==100 ) + { + r = -2.042e+00; + } + if( w==99 ) + { + r = -2.093e+00; + } + if( w==98 ) + { + r = -2.144e+00; + } + if( w==97 ) + { + r = -2.195e+00; + } + if( w==96 ) + { + r = -2.248e+00; + } + if( w==95 ) + { + r = -2.302e+00; + } + if( w==94 ) + { + r = -2.356e+00; + } + if( w==93 ) + { + r = -2.412e+00; + } + if( w==92 ) + { + r = -2.468e+00; + } + if( w==91 ) + { + r = -2.525e+00; + } + if( w==90 ) + { + r = -2.583e+00; + } + if( w==89 ) + { + r = -2.642e+00; + } + if( w==88 ) + { + r = -2.702e+00; + } + if( w==87 ) + { + r = -2.763e+00; + } + if( w==86 ) + { + r = -2.825e+00; + } + if( w==85 ) + { + r = -2.888e+00; + } + if( w==84 ) + { + r = -2.951e+00; + } + if( w==83 ) + { + r = -3.016e+00; + } + if( w==82 ) + { + r = -3.082e+00; + } + if( w==81 ) + { + r = -3.149e+00; + } + if( w==80 ) + { + r = -3.216e+00; + } + if( w==79 ) + { + r = -3.285e+00; + } + if( w==78 ) + { + r = -3.355e+00; + } + if( w==77 ) + { + r = -3.426e+00; + } + if( w==76 ) + { + r = -3.498e+00; + } + if( w==75 ) + { + r = -3.571e+00; + } + if( w==74 ) + { + r = -3.645e+00; + } + if( w==73 ) + { + r = -3.721e+00; + } + if( w==72 ) + { + r = -3.797e+00; + } + if( w==71 ) + { + r = -3.875e+00; + } + if( w==70 ) + { + r = -3.953e+00; + } + if( w==69 ) + { + r = -4.033e+00; + } + if( w==68 ) + { + r = -4.114e+00; + } + if( w==67 ) + { + r = -4.197e+00; + } + if( w==66 ) + { + r = -4.280e+00; + } + if( w==65 ) + { + r = -4.365e+00; + } + if( w==64 ) + { + r = -4.451e+00; + } + if( w==63 ) + { + r = -4.539e+00; + } + if( w==62 ) + { + r = -4.628e+00; + } + if( w==61 ) + { + r = -4.718e+00; + } + if( w==60 ) + { + r = -4.809e+00; + } + if( w==59 ) + { + r = -4.902e+00; + } + if( w==58 ) + { + r = -4.996e+00; + } + if( w==57 ) + { + r = -5.092e+00; + } + if( w==56 ) + { + r = -5.189e+00; + } + if( w==55 ) + { + r = -5.287e+00; + } + if( w==54 ) + { + r = -5.388e+00; + } + if( w==53 ) + { + r = -5.489e+00; + } + if( w==52 ) + { + r = -5.592e+00; + } + if( w==51 ) + { + r = -5.697e+00; + } + if( w==50 ) + { + r = -5.804e+00; + } + if( w==49 ) + { + r = -5.912e+00; + } + if( w==48 ) + { + r = -6.022e+00; + } + if( w==47 ) + { + r = -6.133e+00; + } + if( w==46 ) + { + r = -6.247e+00; + } + if( w==45 ) + { + r = -6.362e+00; + } + if( w==44 ) + { + r = -6.479e+00; + } + if( w==43 ) + { + r = -6.598e+00; + } + if( w==42 ) + { + r = -6.719e+00; + } + if( w==41 ) + { + r = -6.842e+00; + } + if( w==40 ) + { + r = -6.967e+00; + } + if( w==39 ) + { + r = -7.094e+00; + } + if( w==38 ) + { + r = -7.224e+00; + } + if( w==37 ) + { + r = -7.355e+00; + } + if( w==36 ) + { + r = -7.489e+00; + } + if( w==35 ) + { + r = -7.625e+00; + } + if( w==34 ) + { + r = -7.764e+00; + } + if( w==33 ) + { + r = -7.905e+00; + } + if( w==32 ) + { + r = -8.049e+00; + } + if( w==31 ) + { + r = -8.196e+00; + } + if( w==30 ) + { + r = -8.345e+00; + } + if( w==29 ) + { + r = -8.498e+00; + } + if( w==28 ) + { + r = -8.653e+00; + } + if( w==27 ) + { + r = -8.811e+00; + } + if( w==26 ) + { + r = -8.974e+00; + } + if( w==25 ) + { + r = -9.139e+00; + } + if( w==24 ) + { + r = -9.308e+00; + } + if( w==23 ) + { + r = -9.481e+00; + } + if( w==22 ) + { + r = -9.658e+00; + } + if( w==21 ) + { + r = -9.840e+00; + } + if( w==20 ) + { + r = -1.003e+01; + } + if( w==19 ) + { + r = -1.022e+01; + } + if( w==18 ) + { + r = -1.041e+01; + } + if( w==17 ) + { + r = -1.061e+01; + } + if( w==16 ) + { + r = -1.081e+01; + } + if( w==15 ) + { + r = -1.102e+01; + } + if( w==14 ) + { + r = -1.124e+01; + } + if( w==13 ) + { + r = -1.147e+01; + } + if( w==12 ) + { + r = -1.169e+01; + } + if( w==11 ) + { + r = -1.194e+01; + } + if( w==10 ) + { + r = -1.218e+01; + } + if( w==9 ) + { + r = -1.245e+01; + } + if( w==8 ) + { + r = -1.272e+01; + } + if( w==7 ) + { + r = -1.300e+01; + } + if( w==6 ) + { + r = -1.330e+01; + } + if( w==5 ) + { + r = -1.364e+01; + } + if( w==4 ) + { + r = -1.400e+01; + } + if( w==3 ) + { + r = -1.433e+01; + } + if( w==2 ) + { + r = -1.484e+01; + } + if( w==1 ) + { + r = -1.525e+01; + } + if( w<=0 ) + { + r = -1.594e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 24) +*************************************************************************/ +static double wsr_w24(double s, ae_state *_state) +{ + ae_int_t w; + double r; + double result; + + + r = 0; + w = ae_round(-3.500000e+01*s+1.500000e+02, _state); + if( w>=150 ) + { + r = -6.820e-01; + } + if( w==149 ) + { + r = -7.044e-01; + } + if( w==148 ) + { + r = -7.273e-01; + } + if( w==147 ) + { + r = -7.507e-01; + } + if( w==146 ) + { + r = -7.746e-01; + } + if( w==145 ) + { + r = -7.990e-01; + } + if( w==144 ) + { + r = -8.239e-01; + } + if( w==143 ) + { + r = -8.494e-01; + } + if( w==142 ) + { + r = -8.754e-01; + } + if( w==141 ) + { + r = -9.020e-01; + } + if( w==140 ) + { + r = -9.291e-01; + } + if( w==139 ) + { + r = -9.567e-01; + } + if( w==138 ) + { + r = -9.849e-01; + } + if( w==137 ) + { + r = -1.014e+00; + } + if( w==136 ) + { + r = -1.043e+00; + } + if( w==135 ) + { + r = -1.073e+00; + } + if( w==134 ) + { + r = -1.103e+00; + } + if( w==133 ) + { + r = -1.135e+00; + } + if( w==132 ) + { + r = -1.166e+00; + } + if( w==131 ) + { + r = -1.198e+00; + } + if( w==130 ) + { + r = -1.231e+00; + } + if( w==129 ) + { + r = -1.265e+00; + } + if( w==128 ) + { + r = -1.299e+00; + } + if( w==127 ) + { + r = -1.334e+00; + } + if( w==126 ) + { + r = -1.369e+00; + } + if( w==125 ) + { + r = -1.405e+00; + } + if( w==124 ) + { + r = -1.441e+00; + } + if( w==123 ) + { + r = -1.479e+00; + } + if( w==122 ) + { + r = -1.517e+00; + } + if( w==121 ) + { + r = -1.555e+00; + } + if( w==120 ) + { + r = -1.594e+00; + } + if( w==119 ) + { + r = -1.634e+00; + } + if( w==118 ) + { + r = -1.675e+00; + } + if( w==117 ) + { + r = -1.716e+00; + } + if( w==116 ) + { + r = -1.758e+00; + } + if( w==115 ) + { + r = -1.800e+00; + } + if( w==114 ) + { + r = -1.844e+00; + } + if( w==113 ) + { + r = -1.888e+00; + } + if( w==112 ) + { + r = -1.932e+00; + } + if( w==111 ) + { + r = -1.978e+00; + } + if( w==110 ) + { + r = -2.024e+00; + } + if( w==109 ) + { + r = -2.070e+00; + } + if( w==108 ) + { + r = -2.118e+00; + } + if( w==107 ) + { + r = -2.166e+00; + } + if( w==106 ) + { + r = -2.215e+00; + } + if( w==105 ) + { + r = -2.265e+00; + } + if( w==104 ) + { + r = -2.316e+00; + } + if( w==103 ) + { + r = -2.367e+00; + } + if( w==102 ) + { + r = -2.419e+00; + } + if( w==101 ) + { + r = -2.472e+00; + } + if( w==100 ) + { + r = -2.526e+00; + } + if( w==99 ) + { + r = -2.580e+00; + } + if( w==98 ) + { + r = -2.636e+00; + } + if( w==97 ) + { + r = -2.692e+00; + } + if( w==96 ) + { + r = -2.749e+00; + } + if( w==95 ) + { + r = -2.806e+00; + } + if( w==94 ) + { + r = -2.865e+00; + } + if( w==93 ) + { + r = -2.925e+00; + } + if( w==92 ) + { + r = -2.985e+00; + } + if( w==91 ) + { + r = -3.046e+00; + } + if( w==90 ) + { + r = -3.108e+00; + } + if( w==89 ) + { + r = -3.171e+00; + } + if( w==88 ) + { + r = -3.235e+00; + } + if( w==87 ) + { + r = -3.300e+00; + } + if( w==86 ) + { + r = -3.365e+00; + } + if( w==85 ) + { + r = -3.432e+00; + } + if( w==84 ) + { + r = -3.499e+00; + } + if( w==83 ) + { + r = -3.568e+00; + } + if( w==82 ) + { + r = -3.637e+00; + } + if( w==81 ) + { + r = -3.708e+00; + } + if( w==80 ) + { + r = -3.779e+00; + } + if( w==79 ) + { + r = -3.852e+00; + } + if( w==78 ) + { + r = -3.925e+00; + } + if( w==77 ) + { + r = -4.000e+00; + } + if( w==76 ) + { + r = -4.075e+00; + } + if( w==75 ) + { + r = -4.151e+00; + } + if( w==74 ) + { + r = -4.229e+00; + } + if( w==73 ) + { + r = -4.308e+00; + } + if( w==72 ) + { + r = -4.387e+00; + } + if( w==71 ) + { + r = -4.468e+00; + } + if( w==70 ) + { + r = -4.550e+00; + } + if( w==69 ) + { + r = -4.633e+00; + } + if( w==68 ) + { + r = -4.718e+00; + } + if( w==67 ) + { + r = -4.803e+00; + } + if( w==66 ) + { + r = -4.890e+00; + } + if( w==65 ) + { + r = -4.978e+00; + } + if( w==64 ) + { + r = -5.067e+00; + } + if( w==63 ) + { + r = -5.157e+00; + } + if( w==62 ) + { + r = -5.249e+00; + } + if( w==61 ) + { + r = -5.342e+00; + } + if( w==60 ) + { + r = -5.436e+00; + } + if( w==59 ) + { + r = -5.531e+00; + } + if( w==58 ) + { + r = -5.628e+00; + } + if( w==57 ) + { + r = -5.727e+00; + } + if( w==56 ) + { + r = -5.826e+00; + } + if( w==55 ) + { + r = -5.927e+00; + } + if( w==54 ) + { + r = -6.030e+00; + } + if( w==53 ) + { + r = -6.134e+00; + } + if( w==52 ) + { + r = -6.240e+00; + } + if( w==51 ) + { + r = -6.347e+00; + } + if( w==50 ) + { + r = -6.456e+00; + } + if( w==49 ) + { + r = -6.566e+00; + } + if( w==48 ) + { + r = -6.678e+00; + } + if( w==47 ) + { + r = -6.792e+00; + } + if( w==46 ) + { + r = -6.907e+00; + } + if( w==45 ) + { + r = -7.025e+00; + } + if( w==44 ) + { + r = -7.144e+00; + } + if( w==43 ) + { + r = -7.265e+00; + } + if( w==42 ) + { + r = -7.387e+00; + } + if( w==41 ) + { + r = -7.512e+00; + } + if( w==40 ) + { + r = -7.639e+00; + } + if( w==39 ) + { + r = -7.768e+00; + } + if( w==38 ) + { + r = -7.899e+00; + } + if( w==37 ) + { + r = -8.032e+00; + } + if( w==36 ) + { + r = -8.167e+00; + } + if( w==35 ) + { + r = -8.305e+00; + } + if( w==34 ) + { + r = -8.445e+00; + } + if( w==33 ) + { + r = -8.588e+00; + } + if( w==32 ) + { + r = -8.733e+00; + } + if( w==31 ) + { + r = -8.881e+00; + } + if( w==30 ) + { + r = -9.031e+00; + } + if( w==29 ) + { + r = -9.185e+00; + } + if( w==28 ) + { + r = -9.341e+00; + } + if( w==27 ) + { + r = -9.501e+00; + } + if( w==26 ) + { + r = -9.664e+00; + } + if( w==25 ) + { + r = -9.830e+00; + } + if( w==24 ) + { + r = -1.000e+01; + } + if( w==23 ) + { + r = -1.017e+01; + } + if( w==22 ) + { + r = -1.035e+01; + } + if( w==21 ) + { + r = -1.053e+01; + } + if( w==20 ) + { + r = -1.072e+01; + } + if( w==19 ) + { + r = -1.091e+01; + } + if( w==18 ) + { + r = -1.110e+01; + } + if( w==17 ) + { + r = -1.130e+01; + } + if( w==16 ) + { + r = -1.151e+01; + } + if( w==15 ) + { + r = -1.172e+01; + } + if( w==14 ) + { + r = -1.194e+01; + } + if( w==13 ) + { + r = -1.216e+01; + } + if( w==12 ) + { + r = -1.239e+01; + } + if( w==11 ) + { + r = -1.263e+01; + } + if( w==10 ) + { + r = -1.287e+01; + } + if( w==9 ) + { + r = -1.314e+01; + } + if( w==8 ) + { + r = -1.342e+01; + } + if( w==7 ) + { + r = -1.369e+01; + } + if( w==6 ) + { + r = -1.400e+01; + } + if( w==5 ) + { + r = -1.433e+01; + } + if( w==4 ) + { + r = -1.469e+01; + } + if( w==3 ) + { + r = -1.503e+01; + } + if( w==2 ) + { + r = -1.554e+01; + } + if( w==1 ) + { + r = -1.594e+01; + } + if( w<=0 ) + { + r = -1.664e+01; + } + result = r; + return result; +} + + +/************************************************************************* +Tail(S, 25) +*************************************************************************/ +static double wsr_w25(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.150509e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.695528e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.437637e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.611906e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -7.625722e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.579892e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.086876e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.906543e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.354881e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 1.007195e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -8.437327e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 26) +*************************************************************************/ +static double wsr_w26(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.117622e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.635159e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.395167e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.382823e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -6.531987e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.060112e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -8.203697e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.516523e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.431364e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 6.384553e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -3.238369e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 27) +*************************************************************************/ +static double wsr_w27(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.089731e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.584248e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.359966e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.203696e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.753344e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.761891e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -7.096897e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.419108e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.581214e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 3.033766e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.901441e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 28) +*************************************************************************/ +static double wsr_w28(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.065046e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.539163e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.328939e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.046376e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.061515e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.469271e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.711578e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -8.389153e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.250575e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 4.047245e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.128555e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 29) +*************************************************************************/ +static double wsr_w29(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.043413e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.499756e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.302137e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.915129e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.516329e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.260064e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.817269e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.478130e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.111668e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 4.093451e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.135860e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 30) +*************************************************************************/ +static double wsr_w30(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -5.024071e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.464515e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.278342e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.800030e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.046294e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.076162e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -3.968677e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.911679e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -8.619185e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, 5.125362e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -3.984370e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 40) +*************************************************************************/ +static double wsr_w40(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -4.904809e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.248327e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.136698e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.170982e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.824427e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -3.888648e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.344929e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, 2.790407e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.619858e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, 3.359121e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.883026e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 60) +*************************************************************************/ +static double wsr_w60(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -4.809656e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.077191e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.029402e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -7.507931e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, -6.506226e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.391278e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.263635e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, 2.302271e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.384348e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, 1.865587e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.622355e-04, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 120) +*************************************************************************/ +static double wsr_w120(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -4.729426e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.934426e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -9.433231e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.492504e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, 1.673948e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, -6.077014e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -7.215768e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, 9.086734e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, -8.447980e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, 6.705028e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.828507e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S, 200) +*************************************************************************/ +static double wsr_w200(double s, ae_state *_state) +{ + double x; + double tj; + double tj1; + double result; + + + result = 0; + x = ae_minreal(2*(s-0.000000e+00)/4.000000e+00-1, 1.0, _state); + tj = 1; + tj1 = x; + wsr_wcheb(x, -4.700240e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.883080e+00, &tj, &tj1, &result, _state); + wsr_wcheb(x, -9.132168e-01, &tj, &tj1, &result, _state); + wsr_wcheb(x, -3.512684e-02, &tj, &tj1, &result, _state); + wsr_wcheb(x, 1.726342e-03, &tj, &tj1, &result, _state); + wsr_wcheb(x, -5.189796e-04, &tj, &tj1, &result, _state); + wsr_wcheb(x, -1.628659e-06, &tj, &tj1, &result, _state); + wsr_wcheb(x, 4.261786e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, -4.002498e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, 3.146287e-05, &tj, &tj1, &result, _state); + wsr_wcheb(x, -2.727576e-05, &tj, &tj1, &result, _state); + return result; +} + + +/************************************************************************* +Tail(S,N), S>=0 +*************************************************************************/ +static double wsr_wsigma(double s, ae_int_t n, ae_state *_state) +{ + double f0; + double f1; + double f2; + double f3; + double f4; + double x0; + double x1; + double x2; + double x3; + double x4; + double x; + double result; + + + result = 0; + if( n==5 ) + { + result = wsr_w5(s, _state); + } + if( n==6 ) + { + result = wsr_w6(s, _state); + } + if( n==7 ) + { + result = wsr_w7(s, _state); + } + if( n==8 ) + { + result = wsr_w8(s, _state); + } + if( n==9 ) + { + result = wsr_w9(s, _state); + } + if( n==10 ) + { + result = wsr_w10(s, _state); + } + if( n==11 ) + { + result = wsr_w11(s, _state); + } + if( n==12 ) + { + result = wsr_w12(s, _state); + } + if( n==13 ) + { + result = wsr_w13(s, _state); + } + if( n==14 ) + { + result = wsr_w14(s, _state); + } + if( n==15 ) + { + result = wsr_w15(s, _state); + } + if( n==16 ) + { + result = wsr_w16(s, _state); + } + if( n==17 ) + { + result = wsr_w17(s, _state); + } + if( n==18 ) + { + result = wsr_w18(s, _state); + } + if( n==19 ) + { + result = wsr_w19(s, _state); + } + if( n==20 ) + { + result = wsr_w20(s, _state); + } + if( n==21 ) + { + result = wsr_w21(s, _state); + } + if( n==22 ) + { + result = wsr_w22(s, _state); + } + if( n==23 ) + { + result = wsr_w23(s, _state); + } + if( n==24 ) + { + result = wsr_w24(s, _state); + } + if( n==25 ) + { + result = wsr_w25(s, _state); + } + if( n==26 ) + { + result = wsr_w26(s, _state); + } + if( n==27 ) + { + result = wsr_w27(s, _state); + } + if( n==28 ) + { + result = wsr_w28(s, _state); + } + if( n==29 ) + { + result = wsr_w29(s, _state); + } + if( n==30 ) + { + result = wsr_w30(s, _state); + } + if( n>30 ) + { + x = 1.0/n; + x0 = 1.0/30; + f0 = wsr_w30(s, _state); + x1 = 1.0/40; + f1 = wsr_w40(s, _state); + x2 = 1.0/60; + f2 = wsr_w60(s, _state); + x3 = 1.0/120; + f3 = wsr_w120(s, _state); + x4 = 1.0/200; + f4 = wsr_w200(s, _state); + f1 = ((x-x0)*f1-(x-x1)*f0)/(x1-x0); + f2 = ((x-x0)*f2-(x-x2)*f0)/(x2-x0); + f3 = ((x-x0)*f3-(x-x3)*f0)/(x3-x0); + f4 = ((x-x0)*f4-(x-x4)*f0)/(x4-x0); + f2 = ((x-x1)*f2-(x-x2)*f1)/(x2-x1); + f3 = ((x-x1)*f3-(x-x3)*f1)/(x3-x1); + f4 = ((x-x1)*f4-(x-x4)*f1)/(x4-x1); + f3 = ((x-x2)*f3-(x-x3)*f2)/(x3-x2); + f4 = ((x-x2)*f4-(x-x4)*f2)/(x4-x2); + f4 = ((x-x3)*f4-(x-x4)*f3)/(x4-x3); + result = f4; + } + return result; +} + + + +} + diff --git a/src/inc/alglib/statistics.h b/src/inc/alglib/statistics.h new file mode 100644 index 0000000..d324946 --- /dev/null +++ b/src/inc/alglib/statistics.h @@ -0,0 +1,1305 @@ +/************************************************************************* +Copyright (c) Sergey Bochkanov (ALGLIB project). + +>>> SOURCE LICENSE >>> +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation (www.fsf.org); either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +A copy of the GNU General Public License is available at +http://www.fsf.org/licensing/licenses +>>> END OF LICENSE >>> +*************************************************************************/ +#ifndef _statistics_pkg_h +#define _statistics_pkg_h +#include "ap.h" +#include "alglibinternal.h" +#include "linalg.h" +#include "specialfunctions.h" + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (DATATYPES) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ + +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS C++ INTERFACE +// +///////////////////////////////////////////////////////////////////////// +namespace alglib +{ + + +/************************************************************************* +Calculation of the distribution moments: mean, variance, skewness, kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +OUTPUT PARAMETERS + Mean - mean. + Variance- variance. + Skewness- skewness (if variance<>0; zero otherwise). + Kurtosis- kurtosis (if variance<>0; zero otherwise). + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemoments(const real_1d_array &x, const ae_int_t n, double &mean, double &variance, double &skewness, double &kurtosis); +void samplemoments(const real_1d_array &x, double &mean, double &variance, double &skewness, double &kurtosis); + + +/************************************************************************* +Calculation of the mean. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Mean' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplemean(const real_1d_array &x, const ae_int_t n); +double samplemean(const real_1d_array &x); + + +/************************************************************************* +Calculation of the variance. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Variance' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplevariance(const real_1d_array &x, const ae_int_t n); +double samplevariance(const real_1d_array &x); + + +/************************************************************************* +Calculation of the skewness. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Skewness' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double sampleskewness(const real_1d_array &x, const ae_int_t n); +double sampleskewness(const real_1d_array &x); + + +/************************************************************************* +Calculation of the kurtosis. + +INPUT PARAMETERS: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +NOTE: + +This function return result which calculated by 'SampleMoments' function +and stored at 'Kurtosis' variable. + + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +double samplekurtosis(const real_1d_array &x, const ae_int_t n); +double samplekurtosis(const real_1d_array &x); + + +/************************************************************************* +ADev + +Input parameters: + X - sample + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + ADev- ADev + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void sampleadev(const real_1d_array &x, const ae_int_t n, double &adev); +void sampleadev(const real_1d_array &x, double &adev); + + +/************************************************************************* +Median calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + +Output parameters: + Median + + -- ALGLIB -- + Copyright 06.09.2006 by Bochkanov Sergey +*************************************************************************/ +void samplemedian(const real_1d_array &x, const ae_int_t n, double &median); +void samplemedian(const real_1d_array &x, double &median); + + +/************************************************************************* +Percentile calculation. + +Input parameters: + X - sample (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only leading N elements of X are processed + * if not given, automatically determined from size of X + P - percentile (0<=P<=1) + +Output parameters: + V - percentile + + -- ALGLIB -- + Copyright 01.03.2008 by Bochkanov Sergey +*************************************************************************/ +void samplepercentile(const real_1d_array &x, const ae_int_t n, const double p, double &v); +void samplepercentile(const real_1d_array &x, const double p, double &v); + + +/************************************************************************* +2-sample covariance + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + covariance (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double cov2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n); +double cov2(const real_1d_array &x, const real_1d_array &y); + + +/************************************************************************* +Pearson product-moment correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Pearson product-moment correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorr2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n); +double pearsoncorr2(const real_1d_array &x, const real_1d_array &y); + + +/************************************************************************* +Spearman's rank correlation coefficient + +Input parameters: + X - sample 1 (array indexes: [0..N-1]) + Y - sample 2 (array indexes: [0..N-1]) + N - N>=0, sample size: + * if given, only N leading elements of X/Y are processed + * if not given, automatically determined from input sizes + +Result: + Spearman's rank correlation coefficient + (zero for N=0 or N=1) + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmancorr2(const real_1d_array &x, const real_1d_array &y, const ae_int_t n); +double spearmancorr2(const real_1d_array &x, const real_1d_array &y); + + +/************************************************************************* +Covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void smp_covm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void covm(const real_2d_array &x, real_2d_array &c); +void smp_covm(const real_2d_array &x, real_2d_array &c); + + +/************************************************************************* +Pearson product-moment correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void smp_pearsoncorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void pearsoncorrm(const real_2d_array &x, real_2d_array &c); +void smp_pearsoncorrm(const real_2d_array &x, real_2d_array &c); + + +/************************************************************************* +Spearman's rank correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X are used + * if not given, automatically determined from input size + M - M>0, number of variables: + * if given, only leading M columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M,M], correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void smp_spearmancorrm(const real_2d_array &x, const ae_int_t n, const ae_int_t m, real_2d_array &c); +void spearmancorrm(const real_2d_array &x, real_2d_array &c); +void smp_spearmancorrm(const real_2d_array &x, real_2d_array &c); + + +/************************************************************************* +Cross-covariance matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with covariance matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-covariance matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void covm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void smp_covm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void covm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); +void smp_covm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); + + +/************************************************************************* +Pearson product-moment cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void smp_pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); +void smp_pearsoncorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); + + +/************************************************************************* +Spearman's rank cross-correlation matrix + +SMP EDITION OF ALGLIB: + + ! This function can utilize multicore capabilities of your system. In + ! order to do this you have to call version with "smp_" prefix, which + ! indicates that multicore code will be used. + ! + ! This note is given for users of SMP edition; if you use GPL edition, + ! or commercial edition of ALGLIB without SMP support, you still will + ! be able to call smp-version of this function, but all computations + ! will be done serially. + ! + ! We recommend you to carefully read ALGLIB Reference Manual, section + ! called 'SMP support', before using parallel version of this function. + ! + ! You should remember that starting/stopping worker thread always have + ! non-zero cost. Although multicore version is pretty efficient on + ! large problems, we do not recommend you to use it on small problems - + ! with correlation matrices smaller than 128*128. + +INPUT PARAMETERS: + X - array[N,M1], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + Y - array[N,M2], sample matrix: + * J-th column corresponds to J-th variable + * I-th row corresponds to I-th observation + N - N>=0, number of observations: + * if given, only leading N rows of X/Y are used + * if not given, automatically determined from input sizes + M1 - M1>0, number of variables in X: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + M2 - M2>0, number of variables in Y: + * if given, only leading M1 columns of X are used + * if not given, automatically determined from input size + +OUTPUT PARAMETERS: + C - array[M1,M2], cross-correlation matrix (zero if N=0 or N=1) + + -- ALGLIB -- + Copyright 28.10.2010 by Bochkanov Sergey +*************************************************************************/ +void spearmancorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void smp_spearmancorrm2(const real_2d_array &x, const real_2d_array &y, const ae_int_t n, const ae_int_t m1, const ae_int_t m2, real_2d_array &c); +void spearmancorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); +void smp_spearmancorrm2(const real_2d_array &x, const real_2d_array &y, real_2d_array &c); + + +/************************************************************************* + +*************************************************************************/ +void rankdata(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures); +void smp_rankdata(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures); +void rankdata(real_2d_array &xy); +void smp_rankdata(real_2d_array &xy); + + +/************************************************************************* + +*************************************************************************/ +void rankdatacentered(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures); +void smp_rankdatacentered(const real_2d_array &xy, const ae_int_t npoints, const ae_int_t nfeatures); +void rankdatacentered(real_2d_array &xy); +void smp_rankdatacentered(real_2d_array &xy); + + +/************************************************************************* +Obsolete function, we recommend to use PearsonCorr2(). + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double pearsoncorrelation(const real_1d_array &x, const real_1d_array &y, const ae_int_t n); + + +/************************************************************************* +Obsolete function, we recommend to use SpearmanCorr2(). + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +double spearmanrankcorrelation(const real_1d_array &x, const real_1d_array &y, const ae_int_t n); + +/************************************************************************* +Pearson's correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5 + * normality of distributions of X and Y. + +Input parameters: + R - Pearson's correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void pearsoncorrelationsignificance(const double r, const ae_int_t n, double &bothtails, double &lefttail, double &righttail); + + +/************************************************************************* +Spearman's rank correlation coefficient significance test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions having zero correlation or whether their +correlation is non-zero. + +The following tests are performed: + * two-tailed test (null hypothesis - X and Y have zero correlation) + * left-tailed test (null hypothesis - the correlation coefficient is + greater than or equal to 0) + * right-tailed test (null hypothesis - the correlation coefficient is + less than or equal to 0). + +Requirements: + * the number of elements in each sample is not less than 5. + +The test is non-parametric and doesn't require distributions X and Y to be +normal. + +Input parameters: + R - Spearman's rank correlation coefficient for X and Y + N - number of elements in samples, N>=5. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void spearmanrankcorrelationsignificance(const double r, const ae_int_t n, double &bothtails, double &lefttail, double &righttail); + +/************************************************************************* +Jarque-Bera test + +This test checks hypotheses about the fact that a given sample X is a +sample of normal random variable. + +Requirements: + * the number of elements in the sample is not less than 5. + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +Accuracy of the approximation used (5<=N<=1951): + +p-value relative error (5<=N<=1951) +[1, 0.1] < 1% +[0.1, 0.01] < 2% +[0.01, 0.001] < 6% +[0.001, 0] wasn't measured + +For N>1951 accuracy wasn't measured but it shouldn't be sharply different +from table values. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void jarqueberatest(const real_1d_array &x, const ae_int_t n, double &p); + +/************************************************************************* +Mann-Whitney U-test + +This test checks hypotheses about whether X and Y are samples of two +continuous distributions of the same shape and same median or whether +their medians are different. + +The following tests are performed: + * two-tailed test (null hypothesis - the medians are equal) + * left-tailed test (null hypothesis - the median of the first sample + is greater than or equal to the median of the second sample) + * right-tailed test (null hypothesis - the median of the first sample + is less than or equal to the median of the second sample). + +Requirements: + * the samples are independent + * X and Y are continuous distributions (or discrete distributions well- + approximating continuous distributions) + * distributions of X and Y have the same shape. The only possible + difference is their position (i.e. the value of the median) + * the number of elements in each sample is not less than 5 + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distributions to be normal. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. N>=5 + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. M>=5 + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with satisfactory accuracy in interval [0.0001, 1]. +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + +Relative precision of approximation of p-value: + +N M Max.err. Rms.err. +5..10 N..10 1.4e-02 6.0e-04 +5..10 N..100 2.2e-02 5.3e-06 +10..15 N..15 1.0e-02 3.2e-04 +10..15 N..100 1.0e-02 2.2e-05 +15..100 N..100 6.1e-03 2.7e-06 + +For N,M>100 accuracy checks weren't put into practice, but taking into +account characteristics of asymptotic approximation used, precision should +not be sharply different from the values for interval [5, 100]. + + -- ALGLIB -- + Copyright 09.04.2007 by Bochkanov Sergey +*************************************************************************/ +void mannwhitneyutest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail); + +/************************************************************************* +Sign test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +While calculating p-values high-precision binomial distribution +approximation is used, so significance levels have about 15 exact digits. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplesigntest(const real_1d_array &x, const ae_int_t n, const double median, double &bothtails, double &lefttail, double &righttail); + +/************************************************************************* +One-sample t-test + +This test checks three hypotheses about the mean of the given sample. The +following tests are performed: + * two-tailed test (null hypothesis - the mean is equal to the given + value) + * left-tailed test (null hypothesis - the mean is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the mean is less than or equal + to the given value). + +The test is based on the assumption that a given sample has a normal +distribution and an unknown dispersion. If the distribution sharply +differs from normal, the test will work incorrectly. + +INPUT PARAMETERS: + X - sample. Array whose index goes from 0 to N-1. + N - size of sample, N>=0 + Mean - assumed value of the mean. + +OUTPUT PARAMETERS: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0, all p-values are set to 1.0 + * when variance of X[] is exactly zero, p-values are set + to 1.0 or 0.0, depending on difference between sample mean and + value of mean being tested. + + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest1(const real_1d_array &x, const ae_int_t n, const double mean, double &bothtails, double &lefttail, double &righttail); + + +/************************************************************************* +Two-sample pooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * dispersions are equal + * samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has exactly zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void studentttest2(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail); + + +/************************************************************************* +Two-sample unpooled test + +This test checks three hypotheses about the mean of the given samples. The +following tests are performed: + * two-tailed test (null hypothesis - the means are equal) + * left-tailed test (null hypothesis - the mean of the first sample is + greater than or equal to the mean of the second sample) + * right-tailed test (null hypothesis - the mean of the first sample is + less than or equal to the mean of the second sample). + +Test is based on the following assumptions: + * given samples have normal distributions + * samples are independent. +Equality of variances is NOT required. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Y - sample 2. Array whose index goes from 0 to M-1. + M - size of the sample. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +NOTE: this function correctly handles degenerate cases: + * when N=0 or M=0, all p-values are set to 1.0 + * when both samples has zero variance, p-values are set + to 1.0 or 0.0, depending on difference between means. + * when only one sample has zero variance, test reduces to 1-sample + version. + + -- ALGLIB -- + Copyright 18.09.2006 by Bochkanov Sergey +*************************************************************************/ +void unequalvariancettest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail); + +/************************************************************************* +Two-sample F-test + +This test checks three hypotheses about dispersions of the given samples. +The following tests are performed: + * two-tailed test (null hypothesis - the dispersions are equal) + * left-tailed test (null hypothesis - the dispersion of the first + sample is greater than or equal to the dispersion of the second + sample). + * right-tailed test (null hypothesis - the dispersion of the first + sample is less than or equal to the dispersion of the second sample) + +The test is based on the following assumptions: + * the given samples have normal distributions + * the samples are independent. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - sample size. + Y - sample 2. Array whose index goes from 0 to M-1. + M - sample size. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void ftest(const real_1d_array &x, const ae_int_t n, const real_1d_array &y, const ae_int_t m, double &bothtails, double &lefttail, double &righttail); + + +/************************************************************************* +One-sample chi-square test + +This test checks three hypotheses about the dispersion of the given sample +The following tests are performed: + * two-tailed test (null hypothesis - the dispersion equals the given + number) + * left-tailed test (null hypothesis - the dispersion is greater than + or equal to the given number) + * right-tailed test (null hypothesis - dispersion is less than or + equal to the given number). + +Test is based on the following assumptions: + * the given sample has a normal distribution. + +Input parameters: + X - sample 1. Array whose index goes from 0 to N-1. + N - size of the sample. + Variance - dispersion value to compare with. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + + -- ALGLIB -- + Copyright 19.09.2006 by Bochkanov Sergey +*************************************************************************/ +void onesamplevariancetest(const real_1d_array &x, const ae_int_t n, const double variance, double &bothtails, double &lefttail, double &righttail); + +/************************************************************************* +Wilcoxon signed-rank test + +This test checks three hypotheses about the median of the given sample. +The following tests are performed: + * two-tailed test (null hypothesis - the median is equal to the given + value) + * left-tailed test (null hypothesis - the median is greater than or + equal to the given value) + * right-tailed test (null hypothesis - the median is less than or + equal to the given value) + +Requirements: + * the scale of measurement should be ordinal, interval or ratio (i.e. + the test could not be applied to nominal variables). + * the distribution should be continuous and symmetric relative to its + median. + * number of distinct values in the X array should be greater than 4 + +The test is non-parametric and doesn't require distribution X to be normal + +Input parameters: + X - sample. Array whose index goes from 0 to N-1. + N - size of the sample. + Median - assumed median value. + +Output parameters: + BothTails - p-value for two-tailed test. + If BothTails is less than the given significance level + the null hypothesis is rejected. + LeftTail - p-value for left-tailed test. + If LeftTail is less than the given significance level, + the null hypothesis is rejected. + RightTail - p-value for right-tailed test. + If RightTail is less than the given significance level + the null hypothesis is rejected. + +To calculate p-values, special approximation is used. This method lets us +calculate p-values with two decimal places in interval [0.0001, 1]. + +"Two decimal places" does not sound very impressive, but in practice the +relative error of less than 1% is enough to make a decision. + +There is no approximation outside the [0.0001, 1] interval. Therefore, if +the significance level outlies this interval, the test returns 0.0001. + + -- ALGLIB -- + Copyright 08.09.2006 by Bochkanov Sergey +*************************************************************************/ +void wilcoxonsignedranktest(const real_1d_array &x, const ae_int_t n, const double e, double &bothtails, double &lefttail, double &righttail); +} + +///////////////////////////////////////////////////////////////////////// +// +// THIS SECTION CONTAINS COMPUTATIONAL CORE DECLARATIONS (FUNCTIONS) +// +///////////////////////////////////////////////////////////////////////// +namespace alglib_impl +{ +void samplemoments(/* Real */ ae_vector* x, + ae_int_t n, + double* mean, + double* variance, + double* skewness, + double* kurtosis, + ae_state *_state); +double samplemean(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +double samplevariance(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +double sampleskewness(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +double samplekurtosis(/* Real */ ae_vector* x, + ae_int_t n, + ae_state *_state); +void sampleadev(/* Real */ ae_vector* x, + ae_int_t n, + double* adev, + ae_state *_state); +void samplemedian(/* Real */ ae_vector* x, + ae_int_t n, + double* median, + ae_state *_state); +void samplepercentile(/* Real */ ae_vector* x, + ae_int_t n, + double p, + double* v, + ae_state *_state); +double cov2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +double pearsoncorr2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +double spearmancorr2(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +void covm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_covm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state); +void pearsoncorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_pearsoncorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state); +void spearmancorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_spearmancorrm(/* Real */ ae_matrix* x, + ae_int_t n, + ae_int_t m, + /* Real */ ae_matrix* c, ae_state *_state); +void covm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_covm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state); +void pearsoncorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_pearsoncorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state); +void spearmancorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, + ae_state *_state); +void _pexec_spearmancorrm2(/* Real */ ae_matrix* x, + /* Real */ ae_matrix* y, + ae_int_t n, + ae_int_t m1, + ae_int_t m2, + /* Real */ ae_matrix* c, ae_state *_state); +void rankdata(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_state *_state); +void _pexec_rankdata(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, ae_state *_state); +void rankdatacentered(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, + ae_state *_state); +void _pexec_rankdatacentered(/* Real */ ae_matrix* xy, + ae_int_t npoints, + ae_int_t nfeatures, ae_state *_state); +double pearsoncorrelation(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +double spearmanrankcorrelation(/* Real */ ae_vector* x, + /* Real */ ae_vector* y, + ae_int_t n, + ae_state *_state); +void pearsoncorrelationsignificance(double r, + ae_int_t n, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void spearmanrankcorrelationsignificance(double r, + ae_int_t n, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void jarqueberatest(/* Real */ ae_vector* x, + ae_int_t n, + double* p, + ae_state *_state); +void mannwhitneyutest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void onesamplesigntest(/* Real */ ae_vector* x, + ae_int_t n, + double median, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void studentttest1(/* Real */ ae_vector* x, + ae_int_t n, + double mean, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void studentttest2(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void unequalvariancettest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void ftest(/* Real */ ae_vector* x, + ae_int_t n, + /* Real */ ae_vector* y, + ae_int_t m, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void onesamplevariancetest(/* Real */ ae_vector* x, + ae_int_t n, + double variance, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); +void wilcoxonsignedranktest(/* Real */ ae_vector* x, + ae_int_t n, + double e, + double* bothtails, + double* lefttail, + double* righttail, + ae_state *_state); + +} +#endif + diff --git a/src/inc/alglib/stdafx.h b/src/inc/alglib/stdafx.h new file mode 100644 index 0000000..99a8091 --- /dev/null +++ b/src/inc/alglib/stdafx.h @@ -0,0 +1,2 @@ + + diff --git a/src/inc/lag_rms.hpp b/src/inc/lag_rms.hpp new file mode 100644 index 0000000..5c933e1 --- /dev/null +++ b/src/inc/lag_rms.hpp @@ -0,0 +1,58 @@ +/* + * lag.hpp + * + * Created on: Jun 1, 2013 + * Author: azoghbi + */ + +#ifndef LAG_RMS_HPP_ +#define LAG_RMS_HPP_ + +#include "mod.hpp" + +class lag_rms : public mod { + + int n1; + vec p1,p2; + ivec icx,iphi; + double mean1,mean2,mean1sq,mean2sq,mean12; + + void _C( vec ); + void _dC( vec , int ); + void _dCx( vec , int ); + void _dPhi( vec , int ); + + +public: + lag_rms( lcurve , lcurve , vec , vec ); + virtual ~lag_rms(); + + void step_pars( int , vec& , vec& ); + void print_pars( vec& , vec& ); +}; + +// ----------------------------------- // + +class lag10_rms : public mod { + + int n1; + vec p1,p2; + ivec icx,iphi; + double mean1,mean2,mean1sq,mean2sq,mean12; + + void _C( vec ); + void _dC( vec , int ); + void _dCx( vec , int ); + void _dPhi( vec , int ); + + +public: + lag10_rms( lcurve , lcurve , vec , vec ); + virtual ~lag10_rms(); + + void step_pars( int , vec& , vec& ); + void print_pars( vec& , vec& ); + void what_pars( int& , int& ); +}; + +#endif /* LAG_HPP_ */ diff --git a/src/inc/psd_rms.hpp b/src/inc/psd_rms.hpp new file mode 100644 index 0000000..dce3e40 --- /dev/null +++ b/src/inc/psd_rms.hpp @@ -0,0 +1,41 @@ +/* + * psd.hpp + * + * Created on: May 31, 2013 + * Author: azoghbi + */ + +#ifndef PSD_RMS_HPP_ +#define PSD_RMS_HPP_ + +#include "mod.hpp" + +class psd_rms: public mod { + + double mean,mean2; + + void _C( vec ); + void _dC( vec , int ); + +public: + psd_rms( lcurve , vec ); + virtual ~psd_rms(); +}; + +// ---------------------- // + +class psd10_rms: public mod { + + double mean,mean2; + + void _C( vec ); + void _dC( vec , int ); + +public: + psd10_rms( lcurve , vec ); + virtual ~psd10_rms(); + + void step_pars( int , vec& , vec& ); +}; + +#endif /* PSD_RMS_HPP_ */ diff --git a/src/inc/psdlag_rms.hpp b/src/inc/psdlag_rms.hpp new file mode 100644 index 0000000..81efdf8 --- /dev/null +++ b/src/inc/psdlag_rms.hpp @@ -0,0 +1,56 @@ +/* + * psdlag.hpp + * + * Created on: Jun 1, 2013 + * Author: azoghbi + */ + +#include "mod.hpp" + +#ifndef PSDLAG_RMS_HPP_ +#define PSDLAG_RMS_HPP_ + +class psdlag_rms : public mod { + + int n1; + ivec ip1,ip2,icx,iphi; + double mean1,mean2,mean1sq,mean2sq,mean12; + + void _C( vec ); + void _dC( vec , int ); + void _dP1( vec , int ); + void _dP2( vec , int ); + void _dCx( vec , int ); + void _dPhi( vec , int ); + +public: + psdlag_rms( lcurve , lcurve , vec ); + virtual ~psdlag_rms(); + + void step_pars( int , vec& , vec& ); + void print_pars( vec& , vec& ); +}; + +class psdlag10_rms : public mod { + + int n1; + ivec ip1,ip2,icx,iphi; + double mean1,mean2,mean1sq,mean2sq,mean12; + + void _C( vec ); + void _dC( vec , int ); + void _dP1( vec , int ); + void _dP2( vec , int ); + void _dCx( vec , int ); + void _dPhi( vec , int ); + +public: + psdlag10_rms( lcurve , lcurve , vec ); + virtual ~psdlag10_rms(); + + void step_pars( int , vec& , vec& ); + void print_pars( vec& , vec& ); + void what_pars( int&, int& ); +}; + +#endif /* PSDLAG_RMS_HPP_ */ diff --git a/src/lag_rms.cpp b/src/lag_rms.cpp new file mode 100644 index 0000000..9ac355a --- /dev/null +++ b/src/lag_rms.cpp @@ -0,0 +1,230 @@ +/* + * lag.cpp + * + * Created on: Jun 1, 2013 + * Author: azoghbi + */ + +#include "inc/lag_rms.hpp" + +lag_rms::lag_rms( lcurve lc1 , lcurve lc2 , vec fqL , vec pars ) { + + // ----------- initial parameters ------------ // + n1 = lc1.len; + n = n1 + lc2.len; + dt = lc1.dt; + // ------------------------------------------ // + + + // ----------- light curve setup ------------ // + setlc(); + mean1 = lc1.demean(); mean2 = lc2.demean(); + mean1sq = mean1*mean1; mean2sq = mean2*mean2; mean12 = mean1*mean2; + int i; + for( i=0 ; i3000 ){dpar[i] = 3000;} if( dpar[i]<-3000 ){dpar[i] = -3000;} + pars[i] += dpar[i]/((n<10)?10:1); + } +} + + +void lag_rms::print_pars( vec& pars , vec& errs ){ + for( int i=0 ; i M_PI ){ pars[i+nfq] -= 2*M_PI; } + while( pars[i+nfq] <-M_PI ){ pars[i+nfq] += 2*M_PI; } + } + mod::print_pars( pars , errs ); +} + + +// ****************************************** // + + +lag10_rms::lag10_rms( lcurve lc1 , lcurve lc2 , vec fqL , vec pars ) { + + // ----------- initial parameters ------------ // + n1 = lc1.len; + n = n1 + lc2.len; + dt = lc1.dt; + // ------------------------------------------ // + + + // ----------- light curve setup ------------ // + setlc(); + mean1 = lc1.demean(); mean2 = lc2.demean(); + mean1sq = mean1*mean1; mean2sq = mean2*mean2; mean12 = mean1*mean2; + int i; + for( i=0 ; i3 ){dpar[i] = 3;} if( dpar[i]<-3 ){dpar[i] = -3;} + //pars[i] += dpar[i]; + pars[i] += dpar[i]/((n<5)?10:1); + } +} + + +void lag10_rms::print_pars( vec& pars , vec& errs ){ + for( int i=0 ; i M_PI ){ pars[i+nfq] -= 2*M_PI; } + while( pars[i+nfq] <-M_PI ){ pars[i+nfq] += 2*M_PI; } + } + mod::print_pars( pars , errs ); +} + +void lag10_rms::what_pars( int& ip1 , int& ip2 ){ + ip1 = nfq; ip2 = npar; +} diff --git a/src/main.cpp b/src/main.cpp index f9fc67c..a17e05c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,20 +59,11 @@ void do_work( char* fname ){ fp.close(); /********* END Reading the input file **********/ - std::cout << "Completed reading file." << bin1 << bin2 - << files[0] - //<< files[1] - << std::endl; - /********** Read the light curves **************/ - //std::cout << "hello"; - //std::cout << bin1 << bin2; vector > LC,LC_ref; if ( ref == -10 ){ - //std::cout << "yo"; for(i=0;i 0 or mode==-1 ){ vec errs; errs.setlength(nfq); vector lc1; for( i=0 ; i3 ){dpar[i] = 3;} if( dpar[i]<-3 ){dpar[i] = -3;} + pars[i] += dpar[i]; + } +} diff --git a/src/psdlag_rms.cpp b/src/psdlag_rms.cpp new file mode 100644 index 0000000..1f78912 --- /dev/null +++ b/src/psdlag_rms.cpp @@ -0,0 +1,255 @@ +/* + * psdlag.cpp + * + * Created on: Jun 1, 2013 + * Author: azoghbi + */ + +#include "inc/psdlag_rms.hpp" + +psdlag_rms::psdlag_rms( lcurve lc1, lcurve lc2 , vec fqL ) { + + // ----------- initial parameters ------------ // + n1 = lc1.len; + n = n1 + lc2.len; + dt = lc1.dt; + // ------------------------------------------ // + + + // ----------- light curve setup ------------ // + setlc(); + mean1 = lc1.demean(); mean2 = lc2.demean(); + mean1sq = mean1*mean1; mean2sq = mean2*mean2; mean12 = mean1*mean2; + int i; + for( i=0 ; i3000 ){dpar[i] = 3000;} if( dpar[i]<-3000 ){dpar[i] = -3000;} + pars[i] += dpar[i]/((n<10)?10:1); + } +} + + +void psdlag_rms::print_pars( vec& pars , vec& errs ){ + for( int i=0 ; i M_PI ){ pars[i+3*nfq] -= 2*M_PI; } + while( pars[i+3*nfq] <-M_PI ){ pars[i+3*nfq] += 2*M_PI; } + mod::print_pars( pars , errs ); + } +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++ // + +psdlag10_rms::psdlag10_rms( lcurve lc1, lcurve lc2 , vec fqL ) { + + // ----------- initial parameters ------------ // + n1 = lc1.len; + n = n1 + lc2.len; + dt = lc1.dt; + // ------------------------------------------ // + + + // ----------- light curve setup ------------ // + setlc(); + mean1 = lc1.demean(); mean2 = lc2.demean(); + mean1sq = mean1*mean1; mean2sq = mean2*mean2; mean12 = mean1*mean2; + int i; + for( i=0 ; i3 ){dpar[i] = 3;} if( dpar[i]<-3 ){dpar[i] = -3;} + //pars[i] += dpar[i]; + pars[i] += dpar[i]/((n<5)?10:1); + } +} + + +void psdlag10_rms::print_pars( vec& pars , vec& errs ){ + for( int i=0 ; i M_PI ){ pars[i+3*nfq] -= 2*M_PI; } + while( pars[i+3*nfq] <-M_PI ){ pars[i+3*nfq] += 2*M_PI; } + } + mod::print_pars( pars , errs ); +} + +void psdlag10_rms::what_pars( int& ip1 , int& ip2 ){ + ip1 = 3*nfq; ip2 = 4*nfq; +}

    `g8yHmffEqjSrqy6#x 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ξ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�BaLW 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℞Ot3$*p?f+=bYU24T3PNYYeoyO}YZ6XD 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 z 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%0Dis%h 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%0Ac86atx+5XKn5RyDJ;Wb{Ul+<%L=BKMs^)b3 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