00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028 #ifndef CMatrixTemplateNumeric_H
00029 #define CMatrixTemplateNumeric_H
00030
00031 #include <mrpt/math/CMatrixTemplate.h>
00032 #include <mrpt/system/os.h>
00033 #include <cmath>
00034 #include <limits>
00035
00036 namespace mrpt
00037 {
00038 namespace poses
00039 {
00040 class CPose2D;
00041 class CPose3D;
00042 class CPoint2D;
00043 class CPoint3D;
00044 }
00045 namespace math
00046 {
00047 using namespace mrpt::system;
00048
00049
00050 template <class T> class CVectorTemplate;
00051 template <typename T,size_t NROWS,size_t NCOLS> class CMatrixFixedNumeric;
00052 template <class T> class MRPTDLLIMPEXP CMatrixTemplateNumeric;
00053
00054 template <typename T,size_t NROWS,size_t NCOLS> void fixedToDynMatrix( const CMatrixFixedNumeric<T,NROWS,NCOLS> &SRC, CMatrixTemplateNumeric<T> &DST);
00055 template <typename T,size_t N,size_t M> CMatrixTemplateNumeric<T>& add(CMatrixTemplateNumeric<T> &A, const CMatrixFixedNumeric<T,N,M>& B);
00056 template <typename T,size_t N,size_t M> CMatrixTemplateNumeric<T>& substract(CMatrixTemplateNumeric<T> &A, const CMatrixFixedNumeric<T,N,M>& B);
00057 template <class MAT_X,class MAT_A,class MAT_OUT> void multiplySubMatrix (const MAT_X &X,const MAT_A &A,MAT_OUT &outResult,const size_t &A_cols_offset,const size_t &A_rows_offset,const size_t &A_col_count);
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114 template <class T>
00115 class MRPTDLLIMPEXP CMatrixTemplateNumeric : public CMatrixTemplate<T>
00116 {
00117 public:
00118
00119
00120 template <class R>
00121 CMatrixTemplateNumeric(const CMatrixTemplate<R>& m)
00122 {
00123 CMatrixTemplate<T>::realloc( m.getRowCount(), m.getColCount() );
00124
00125 for (size_t i=0; i < CMatrixTemplate<T>::getRowCount(); i++)
00126 for (size_t j=0; j < CMatrixTemplate<T>::getColCount(); j++)
00127 CMatrixTemplate<T>::m_Val[i][j] = static_cast<T> (m.get_unsafe(i,j));
00128 }
00129
00130
00131 CMatrixTemplateNumeric();
00132
00133
00134 CMatrixTemplateNumeric(size_t row, size_t col);
00135
00136
00137 CMatrixTemplateNumeric(const CMatrixTemplate<T> & m, const size_t cropRowCount, const size_t cropColCount) : CMatrixTemplate<T>(m,cropRowCount,cropColCount)
00138 { }
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148 template <typename V, size_t N>
00149 CMatrixTemplateNumeric(size_t row, size_t col, V (&theArray)[N] ) : CMatrixTemplate<T>( row, col, theArray )
00150 { }
00151
00152
00153
00154 template <typename V>
00155 CMatrixTemplateNumeric(size_t row, size_t col, const V &theVector ) : CMatrixTemplate<T>( row, col, theVector )
00156 { }
00157
00158
00159
00160 virtual ~CMatrixTemplateNumeric()
00161 { }
00162
00163
00164
00165 explicit CMatrixTemplateNumeric( const mrpt::poses::CPose2D &p);
00166
00167
00168
00169 explicit CMatrixTemplateNumeric( const mrpt::poses::CPose3D &p);
00170
00171
00172
00173 explicit CMatrixTemplateNumeric( const mrpt::poses::CPoint2D &p);
00174
00175
00176
00177 explicit CMatrixTemplateNumeric( const mrpt::poses::CPoint3D &p);
00178
00179
00180
00181
00182 template <size_t NROWS,size_t NCOLS>
00183 explicit CMatrixTemplateNumeric(const CMatrixFixedNumeric<T,NROWS,NCOLS> &M ) {
00184 fixedToDynMatrix(M,*this);
00185 }
00186
00187
00188
00189
00190 template <size_t NROWS,size_t NCOLS>
00191 CMatrixTemplateNumeric& operator =(const CMatrixFixedNumeric<T,NROWS,NCOLS> &M ) {
00192 fixedToDynMatrix(M,*this);
00193 return *this;
00194 }
00195
00196
00197
00198 template <class R>
00199 CMatrixTemplateNumeric<T>& operator = (const CMatrixTemplateNumeric<R>& m)
00200 {
00201 CMatrixTemplate<T>::realloc( m.getRowCount(), m.getColCount() );
00202
00203 for (size_t i=0; i < CMatrixTemplate<T>::getRowCount(); i++)
00204 for (size_t j=0; j < CMatrixTemplate<T>::getColCount(); j++)
00205 CMatrixTemplate<T>::m_Val[i][j] = static_cast<T>(m.get_unsafe(i,j));
00206 return *this;
00207 }
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219 template <typename V, size_t N>
00220 CMatrixTemplateNumeric& operator = (V (&theArray)[N] )
00221 {
00222 CMatrixTemplate<T>::operator = (theArray);
00223 return *this;
00224 }
00225
00226
00227
00228 CMatrixTemplateNumeric<T>& operator = (const CMatrixTemplateNumeric<T>& m);
00229
00230
00231
00232
00233 void setSize(size_t row, size_t col);
00234
00235
00236
00237
00238 void resize(size_t row, size_t col);
00239
00240
00241
00242
00243
00244 void laplacian( CMatrixTemplateNumeric<T> &ret ) const;
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255 void svd(CMatrixTemplateNumeric<T> &U, std::vector<T> &W,CMatrixTemplateNumeric<T> &V) const;
00256
00257
00258
00259
00260
00261
00262
00263 void eigenVectors(CMatrixTemplateNumeric<T> &Z, CMatrixTemplateNumeric<T> &D) const;
00264
00265
00266
00267
00268
00269
00270 CMatrixTemplateNumeric<T> largestEigenvector(
00271 T resolution = 0.01f,
00272 size_t maxIterations = 6,
00273 int *out_Iterations = NULL,
00274 float *out_estimatedResolution = NULL ) const;
00275
00276
00277
00278
00279 CMatrixTemplateNumeric<T>& Sqrt();
00280
00281
00282
00283
00284 CMatrixTemplateNumeric<T>& Abs();
00285
00286
00287
00288
00289 CMatrixTemplateNumeric<T>& Square();
00290
00291
00292
00293
00294
00295 template <class F>
00296 CMatrixTemplateNumeric<T>& applyToAllElements( F function )
00297 {
00298 for (size_t i=0; i < CMatrixTemplate<T>::m_Rows; i++)
00299 for (size_t j=0; j < CMatrixTemplate<T>::m_Cols; j++)
00300 CMatrixTemplate<T>::m_Val[i][j] = function( CMatrixTemplate<T>::m_Val[i][j] );
00301 return *this;
00302 }
00303
00304
00305 CMatrixTemplateNumeric<T> operator + ();
00306
00307
00308 CMatrixTemplateNumeric<T> operator - ();
00309
00310
00311 CMatrixTemplateNumeric<T>& operator += (const CMatrixTemplateNumeric<T>& m);
00312
00313
00314 template <size_t N,size_t M>
00315 CMatrixTemplateNumeric<T>& operator += (const CMatrixFixedNumeric<T,N,M>& m) { return add(*this,m); }
00316
00317
00318
00319 CMatrixTemplateNumeric<T>& add_At(const CMatrixTemplateNumeric<T>& m);
00320
00321
00322
00323 CMatrixTemplateNumeric<T>& add_AAt(const CMatrixTemplateNumeric<T>& m);
00324
00325
00326 CMatrixTemplateNumeric<T>& add_Ac(const CMatrixTemplateNumeric<T>& m, const T c);
00327
00328
00329
00330 CMatrixTemplateNumeric<T>& operator -= (const CMatrixTemplateNumeric<T>& m);
00331
00332
00333 template <size_t N,size_t M>
00334 CMatrixTemplateNumeric<T>& operator -= (const CMatrixFixedNumeric<T,N,M>& m) { return substract(*this,m); }
00335
00336
00337 CMatrixTemplateNumeric<T>& substract_Ac(const CMatrixTemplateNumeric<T>& m, const T c);
00338
00339
00340 CMatrixTemplateNumeric<T>& substract_An(const CMatrixTemplateNumeric<T>& m, const size_t n)
00341 {
00342 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
00343 if (CMatrixTemplate<T>::m_Rows != m.m_Rows || CMatrixTemplate<T>::m_Cols != m.m_Cols)
00344 THROW_EXCEPTION( "Inconsistent matrix sizes");
00345 #endif
00346
00347 for (size_t i=0; i < CMatrixTemplate<T>::m_Rows; i++)
00348 for (size_t j=0; j < CMatrixTemplate<T>::m_Cols; j++)
00349 for (size_t k=0;k<n;k++)
00350 CMatrixTemplate<T>::m_Val[i][j] -= m.m_Val[i][j];
00351 return *this;
00352 }
00353
00354
00355
00356 CMatrixTemplateNumeric<T>& operator *= (const T& c);
00357
00358
00359
00360 CMatrixTemplateNumeric<T>& operator *= (const CMatrixTemplateNumeric<T>& m);
00361
00362
00363
00364 void multiply(const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2);
00365
00366
00367
00368 void multiply(const CMatrixTemplateNumeric<T>& m1, const CVectorTemplate<T>& m2);
00369
00370
00371
00372 void multiply_ABt(const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2);
00373
00374
00375
00376 void multiply_AAt( const CMatrixTemplateNumeric<T>& m1 );
00377
00378
00379
00380 void multiply_AtA( const CMatrixTemplateNumeric<T>& m1 );
00381
00382
00383
00384 void multiply_Ab( const std::vector<T>& a, std::vector<T>& out_v, bool accum_to_output = false );
00385
00386
00387
00388 void multiply_Atb( const std::vector<T>& a, std::vector<T>& out_v, bool accum_to_output = false );
00389
00390
00391
00392
00393 void multiply_result_is_symmetric(const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2);
00394
00395
00396
00397
00398 template <class MAT_A,class MAT_OUT>
00399 void multiplySubMatrix (
00400 const MAT_A &A,
00401 MAT_OUT &outResult,
00402 const size_t &A_cols_offset,
00403 const size_t &A_rows_offset,
00404 const size_t &A_col_count )
00405 {
00406 multiplySubMatrix(*this,A,outResult,A_cols_offset,A_rows_offset,A_col_count);
00407 }
00408
00409
00410
00411 CMatrixTemplateNumeric<T>& operator /= (const CMatrixTemplateNumeric<T>& m);
00412
00413
00414
00415 CMatrixTemplateNumeric<T>& operator /= (const T& c);
00416
00417
00418
00419 CMatrixTemplateNumeric<T>& operator += (const T& c);
00420
00421
00422
00423 CMatrixTemplateNumeric<T>& operator -= (const T& c);
00424
00425
00426
00427 CMatrixTemplateNumeric<T>& operator ^= (const unsigned int& pow);
00428
00429
00430
00431 void scalarPow(T s);
00432
00433
00434
00435 void zeros(const size_t& row, const size_t& col);
00436
00437
00438
00439 void zeros();
00440
00441
00442
00443 void ones(const size_t& row, const size_t& col);
00444
00445
00446
00447 void ones();
00448
00449
00450
00451 void unit (const size_t& row);
00452
00453
00454
00455 void unit();
00456
00457
00458
00459 CMatrixTemplateNumeric<T> solve (const CMatrixTemplateNumeric<T>& v) const;
00460
00461
00462
00463 CMatrixTemplateNumeric<T> adj() const;
00464
00465
00466
00467
00468
00469 void inv(CMatrixTemplateNumeric<T> &out_inv) const;
00470
00471
00472
00473
00474
00475 CMatrixTemplateNumeric<T> inv() const {
00476 CMatrixTemplateNumeric<T> inv_res;
00477 this->inv(inv_res);
00478 return inv_res;
00479 }
00480
00481
00482
00483
00484
00485 void inv_fast( CMatrixTemplateNumeric<T> &out_inv );
00486
00487
00488 void pseudoInverse( CMatrixTemplateNumeric<T> &out ) const;
00489
00490
00491 CMatrixTemplateNumeric<T> pseudoInverse() const
00492 {
00493 CMatrixTemplateNumeric<T> M;
00494 pseudoInverse(M);
00495 return M;
00496 }
00497
00498
00499
00500
00501 T det() const;
00502
00503
00504
00505 size_t rank(T eps=0.0) const;
00506
00507
00508
00509 T norm() const;
00510
00511
00512
00513 T cofact (size_t row, size_t col) const;
00514
00515
00516
00517 T cond();
00518
00519
00520
00521 bool isSingular() const;
00522
00523
00524
00525 bool isDiagonal() const;
00526
00527
00528
00529 bool isScalar() const;
00530
00531
00532
00533 bool isUnit() const;
00534
00535
00536
00537 bool isNull() const;
00538
00539
00540
00541 bool isSymmetric() const;
00542
00543
00544
00545 bool isSkewSymmetric() const;
00546
00547
00548
00549 bool isUpperTriangular() const;
00550
00551
00552
00553 bool isLowerTriangular() const;
00554
00555
00556
00557
00558 void matrix_floor();
00559
00560
00561
00562
00563 void matrix_floor(CMatrixTemplateNumeric<T> &out);
00564
00565
00566
00567
00568 void matrix_ceil();
00569
00570
00571
00572
00573 void find_index_max_value(size_t &umax, size_t &vmax, T &max_val) const;
00574
00575
00576
00577 T maximumDiagonal() const;
00578
00579
00580
00581 T maximum() const;
00582
00583
00584
00585 T minimum() const;
00586
00587
00588
00589
00590 void find_index_min_value(size_t &umin, size_t &vmin, T &min_val) const;
00591
00592
00593
00594
00595 void force_symmetry();
00596
00597
00598
00599
00600 void mean( std::vector<T> &outMeanVector ) const;
00601
00602
00603
00604
00605 void meanAndStd(
00606 std::vector<T> &outMeanVector,
00607 std::vector<T> &outStdVector ) const;
00608
00609
00610
00611
00612 void meanAndStdAll(
00613 T &outMean,
00614 T &outStd ) const;
00615
00616 void asCol(CMatrixTemplateNumeric<T> &aux) const;
00617
00618 void asRow(CMatrixTemplateNumeric<T> &aux) const;
00619
00620
00621
00622
00623
00624
00625
00626
00627 void findElementsPassingMahalanobisThreshold(
00628 double stdTimes,
00629 std::vector<size_t> &rowIndexes,
00630 std::vector<size_t> &colIndexes,
00631 bool below = false ) const;
00632
00633
00634
00635
00636 inline void normalize( T minVal=0,T maxVal=1)
00637 {
00638 adjustRange(minVal,maxVal);
00639 }
00640
00641
00642
00643 void adjustRange( T minVal=0,T maxVal=1);
00644
00645
00646
00647
00648 T sumAll() const;
00649
00650
00651
00652
00653
00654 T sum(
00655 size_t firstRow = 0,
00656 size_t firstCol = 0,
00657 size_t lastRow = std::numeric_limits<size_t>::max(),
00658 size_t lastCol = std::numeric_limits<size_t>::max() ) const;
00659
00660
00661
00662
00663 void multiplyByMatrixAndByTransposeNonSymmetric(
00664 const CMatrixTemplateNumeric<T> &C,
00665 CMatrixTemplateNumeric<T> &R,
00666 bool accumOnOutput = false,
00667 bool substractInsteadOfSum = false
00668 ) const;
00669
00670
00671
00672
00673
00674 void multiply_ABC(
00675 const CMatrixTemplateNumeric<T> &A,
00676 const CMatrixTemplateNumeric<T> &B,
00677 const CMatrixTemplateNumeric<T> &C);
00678
00679
00680
00681
00682 void multiply_ABCt(
00683 const CMatrixTemplateNumeric<T> &A,
00684 const CMatrixTemplateNumeric<T> &B,
00685 const CMatrixTemplateNumeric<T> &C);
00686
00687
00688
00689
00690
00691
00692
00693
00694
00695
00696
00697
00698
00699
00700
00701
00702
00703 void multiply_HCHt(
00704 const CMatrixTemplateNumeric<T> &C,
00705 CMatrixTemplateNumeric<T> &R,
00706 bool allowSubMatrixMultiplication = false,
00707 size_t subMatrixOffset = 0,
00708 bool accumResultInOutput = false ) const;
00709
00710
00711
00712
00713
00714
00715
00716
00717
00718 T multiply_HCHt_scalar(
00719 const CMatrixTemplateNumeric<T> &C ) const;
00720
00721
00722
00723
00724
00725
00726 void leftDivideSquare(const CMatrixTemplateNumeric<T> &A,CMatrixTemplateNumeric<T> &B) const;
00727
00728
00729
00730
00731
00732
00733 void rightDivideSquare(const CMatrixTemplateNumeric<T> &B,CMatrixTemplateNumeric<T> &A) const;
00734
00735
00736
00737
00738
00739
00740 void fastLeftDivideSquare(CMatrixTemplateNumeric<T> &A,CMatrixTemplateNumeric<T> &B) const;
00741
00742
00743
00744
00745
00746
00747 void fastRightDivideSquare(CMatrixTemplateNumeric<T> &B,CMatrixTemplateNumeric<T> &A) const;
00748
00749 private:
00750
00751 int pivot(size_t row);
00752
00753 };
00754
00755
00756
00757
00758 template <class T>
00759 bool operator == (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00760 {
00761 if (m1.getRowCount() != m2.getRowCount() || m1.getColCount() != m2.getColCount())
00762 return false;
00763
00764 for (size_t i=0; i < m1.getRowCount(); i++)
00765 for (size_t j=0; j < m1.getColCount(); j++)
00766 if (m1(i,j) != m2(i,j))
00767 return false;
00768
00769 return true;
00770 }
00771
00772
00773
00774 template <class T>
00775 bool operator != (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00776 {
00777 return !(m1 == m2);
00778 }
00779
00780
00781
00782 template <class T>
00783 CMatrixTemplateNumeric<T> operator + (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00784 {
00785 CMatrixTemplateNumeric<T> temp(m1);
00786 temp += m2;
00787 return temp;
00788 }
00789
00790
00791
00792 template <class T>
00793 CMatrixTemplateNumeric<T> operator - (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00794 {
00795 CMatrixTemplateNumeric<T> temp(m1);
00796 temp -= m2;
00797 return temp;
00798 }
00799
00800
00801
00802 template <class T>
00803 CMatrixTemplateNumeric<T> operator * (const CMatrixTemplateNumeric<T>& m, const T& no)
00804 {
00805 CMatrixTemplateNumeric<T> temp(m);
00806 temp*=no;
00807 return temp;
00808 }
00809
00810
00811
00812 template <class T>
00813 CMatrixTemplateNumeric<T> operator * (const T& no, const CMatrixTemplateNumeric<T>& m)
00814 {
00815 CMatrixTemplateNumeric<T> temp(m);
00816 temp*=no;
00817 return temp;
00818 }
00819
00820
00821
00822 template <class T>
00823 CMatrixTemplateNumeric<T> operator * (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00824 {
00825 CMatrixTemplateNumeric<T> res(m1);
00826 res*=m2;
00827 return res;
00828 }
00829
00830
00831
00832 template <class T>
00833 CMatrixTemplateNumeric<T> operator * (const CMatrixTemplateNumeric<T>& m1, const CVectorTemplate<T>& m2)
00834 {
00835 CMatrixTemplateNumeric<T> res(m1.getRowCount(),1);
00836 res.multiply(m1,m2);
00837 return res;
00838 }
00839
00840
00841
00842 template <class T>
00843 CMatrixTemplateNumeric<T> operator / (const CMatrixTemplateNumeric<T>& m, const T& no)
00844 {
00845 return (m * (T(1) / no));
00846 }
00847
00848
00849
00850 template <class T>
00851 CMatrixTemplateNumeric<T> operator / (const T& no, const CMatrixTemplateNumeric<T>& m)
00852 {
00853 return (!m * no);
00854 }
00855
00856
00857
00858 template <class T>
00859 CMatrixTemplateNumeric<T> operator / (const CMatrixTemplateNumeric<T>& m1, const CMatrixTemplateNumeric<T>& m2)
00860 {
00861 return (m1 * !m2);
00862 }
00863
00864
00865
00866 template <class T>
00867 CMatrixTemplateNumeric<T> operator ^ (const CMatrixTemplateNumeric<T>& m, const unsigned int& pow)
00868 {
00869 CMatrixTemplateNumeric<T> temp(m);
00870 temp ^= pow;
00871 return temp;
00872 }
00873
00874
00875
00876 template <class T>
00877 CMatrixTemplateNumeric<T> operator ~ (const CMatrixTemplateNumeric<T>& m)
00878 {
00879 CMatrixTemplateNumeric<T> temp(m.getColCount(),m.getRowCount());
00880
00881 for (size_t i=0; i < m.getRowCount(); i++)
00882 for (size_t j=0; j < m.getColCount(); j++)
00883 {
00884 T x = m(i,j);
00885 temp(j,i) = x;
00886 }
00887 return temp;
00888 }
00889
00890
00891
00892 template <class T>
00893 CMatrixTemplateNumeric<T> operator ! (const CMatrixTemplateNumeric<T> &m)
00894 {
00895 return m.inv();
00896 }
00897
00898
00899
00900
00901 #define DEBUG_SAVE_MATRIX(M) \
00902 { \
00903 char auxStr[100]; \
00904 os::sprintf(auxStr,99,"%s.txt",#M); \
00905 M.saveToTextFile(auxStr); \
00906 } \
00907
00908
00909
00910
00911
00912
00913 template<class T>
00914 T MRPTDLLIMPEXP multiply_HCHt_scalar(
00915 const std::vector<T> &H,
00916 const CMatrixTemplateNumeric<T> &C );
00917
00918
00919
00920
00921
00922 typedef CMatrixTemplateNumeric<float> CMatrixFloat;
00923
00924
00925
00926
00927
00928 typedef CMatrixTemplateNumeric<double> CMatrixDouble;
00929
00930
00931
00932
00933 typedef CMatrixTemplateNumeric<unsigned int> CMatrixUInt;
00934
00935
00936
00937
00938 typedef CMatrixTemplate<bool> CMatrixBool;
00939
00940 #ifdef HAVE_LONG_DOUBLE
00941
00942
00943
00944 typedef CMatrixTemplateNumeric<long double> CMatrixLongDouble;
00945 #else
00946
00947
00948
00949 typedef CMatrixTemplateNumeric<double> CMatrixLongDouble;
00950 #endif
00951
00952 namespace detail {
00953
00954
00955
00956 template<typename T> class VicinityTraits<CMatrixTemplateNumeric<T> > {
00957 public:
00958 inline static void initialize(CMatrixTemplateNumeric<T> &mat,size_t N) {
00959 mat.setSize(N,N);
00960 mat.fill(0);
00961 }
00962 inline static void insertInContainer(CMatrixTemplateNumeric<T> &mat,size_t r,size_t c,const T &t) {
00963 mat.get_unsafe(r,c)=t;
00964 }
00965 };
00966 }
00967
00968 }
00969 }
00970
00971 #endif