Changeset 1098


Ignore:
Timestamp:
Feb 18, 2008, 4:13:53 AM (13 years ago)
Author:
Jari Häkkinen
Message:

Fixes #299. Memory leak in matrix was found and removed.

Location:
trunk
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/test/alignment_test.cc

    r1000 r1098  
    7979             std::vector<std::pair<size_t,size_t> >& path)
    8080{
    81   dot_matrix.clone(theplu::yat::utility::matrix(l1.size(),l2.size()));
     81  dot_matrix.resize(l1.size(),l2.size());
    8282  for (size_t i=0; i<l1.size(); i++)
    8383    for (size_t j=0; j<l2.size(); j++) {
  • trunk/test/kernel_test.cc

    r1000 r1098  
    126126  delete kf;
    127127
    128   data_core.clone(utility::matrix(1,5));
     128  data_core.resize(1,5);
    129129  for (size_t i=0; i<data_core.columns(); i++)
    130130    data_core(0,i)=i;
  • trunk/test/matrix_test.cc

    r1028 r1098  
    9191  }
    9292
    93   *error << "\tsub-matrix" << std::endl;
    94   // Checking that sub-matrices work, i.e. mutation to the view are
    95   // reflected in the viewed object.
    9693  is.open("data/knni_matrix.data");
    9794  // The stream input is a proper matrix file, with some stray empty
     
    104101    for (size_t j=0; j<m5.columns(); ++j)
    105102      m5_sum+=m5(i,j);
    106   utility::matrix* m5sub=new utility::matrix(m5,3,3,3,3);
    107   double m5sub_sum=0;
    108   for (size_t i=0; i<m5sub->rows(); ++i)
    109     for (size_t j=0; j<m5sub->columns(); ++j) {
    110       m5sub_sum+=(*m5sub)(i,j);
    111       (*m5sub)(i,j)=1;
    112     }
    113   delete m5sub;
    114   double m5_sum2=0;
    115   for (size_t i=0; i<m5.rows(); ++i)
    116     for (size_t j=0; j<m5.columns(); ++j)
    117       m5_sum2+=m5(i,j);
    118   if (m5_sum2-m5_sum-9+m5sub_sum>1e-13) {
    119     ok=false;
    120     *error << "error sub-matrix test" << std::endl;
    121   }
    122 
    123   // Test of const view implementation (make sure no zero pointer is
    124   // used). Here we use the bad style of not making the view const!
    125   // If test fails with a null pointer exception it is an uncatchable
    126   // core dump!
    127   {
    128     *error << "\tconst view implementation" << std::endl;
    129     // Change the next statement to
    130     // const utility::matrix m(10,10,3.0);
    131     // when ticket:202 is fixes
    132     utility::matrix m(10,10,3.0);
    133     utility::matrix mview(m,3,3,3,3);
    134     // const utility::vector vview(vv,0,5,1); // this is the proper line
    135     utility::matrix m2(3,3,2.0);
    136     m2.mul(mview); // should work even without const since const arg passing
    137     m2.div(mview); // should work even without const since const arg passing
    138   }
    139103
    140104  // checking that copy constructor creates an independent object when
     
    147111    ok &= (m2==m5);
    148112    ok &= (&m2 != &m5);
    149     ok &= !m2.isview();
    150   }
    151 
    152   // checking that copy constructor creates an independent object when
    153   // a view matrix is copied
    154   {
    155     *error << "\tcopy contructor on view" << std::endl;
    156     // Change the next statement to
    157     // const utility::matrix m(10,10,3.0);
    158     // when ticket:202 is fixes
    159     utility::matrix m(10,10,3.0);
    160     utility::matrix mview(m,3,3,3,3);
    161     utility::matrix m3(mview);
    162     ok &= (mview.rows()==m3.rows());
    163     ok &= (mview.columns()==m3.columns());
    164     ok &= (m3 == mview);
    165     ok &= (&m3 != &mview);
    166     ok &= !m3.isview();
    167   }
    168 
    169   // checking that assignment operator throws an exception if matrices
    170   // differ in size
    171   {
    172     *error << "\tassignment operator" << std::endl;
    173     // GSL will catch the error in this test there for the GSL error
    174     // handling must be disabled until after the exception is
    175     // catched. The GSL error handler is reinstated after the
    176     // try-catch construct.
    177     gsl_error_handler_t* err_handler=gsl_set_error_handler_off();
    178     bool exception_happens=false;
    179     try {
    180       utility::matrix m(m5.rows()+1,3,0.0);
    181       m=m5;
    182     } catch (utility::GSL_error& err) {
    183       exception_happens=true;
    184     }
    185     if (!exception_happens) {
    186       *error << "Matrix assignment operator did not throw expected exception"
    187              << std::endl;
    188       ok=false;
    189     }
    190     gsl_set_error_handler(err_handler);
    191   }
    192 
    193   // checking that assignment operator changes the underlying object when
    194   // a view is changed.
    195   {
    196     *error << "\tassignment operator on view" << std::endl;
    197     bool this_ok(true);
    198     utility::matrix mat_view(m5,3,3,3,3);
    199     utility::matrix m2(3,3,12.0);
    200     mat_view=m2;
    201     for (u_int i=0; i<mat_view.rows(); ++i)
    202       for (u_int j=0; j<mat_view.columns(); ++j)
    203         if (m5(i+3,j+3)!=mat_view(i,j))
    204           this_ok=false;
    205     if (!this_ok) {
    206       *error << "FAIL: assignemnt operator on view" << std::endl;
    207       ok=false;
    208     }
    209   }
    210 
    211   // checking clone functionality
    212   {
    213     *error << "\tclone functionality" << std::endl;
    214     bool this_ok=true;
    215     *error << "\t\tcloning normal matrix" << std::endl;
    216     utility::matrix mat2;
    217     mat2.clone(m5);
    218     if (mat2.rows()!=m5.rows() || mat2.columns()!=m5.columns())
    219       this_ok=false;
    220     else
    221       for (u_int i=0; i<m5.rows(); ++i)
    222         for (u_int j=0; j<m5.columns(); ++j)
    223           if (mat2(i,j)!=m5(i,j))
    224             this_ok=false;
    225     if (mat2.gsl_matrix_p()==m5.gsl_matrix_p())
    226       this_ok=false;
    227     *error << "\t\tcloning matrix view (sub matrix)" << std::endl;
    228     utility::matrix* mat_view=new utility::matrix(m5,3,3,3,3);
    229     utility::matrix mat_view2;
    230     mat_view2.clone(*mat_view);
    231     if (!mat_view2.isview())
    232       this_ok=false;
    233     if ( (mat_view->rows()!=mat_view2.rows()) ||
    234          (mat_view->columns()!=mat_view2.columns()) )
    235       this_ok=false;
    236     else
    237       for (u_int i=0; i<mat_view2.rows(); ++i)
    238         for (u_int j=0; j<mat_view2.columns(); ++j)
    239           if ((*mat_view)(i,j)!=mat_view2(i,j))
    240             this_ok=false;
    241     *error << "\t\tcloned matrix view independence" << std::endl;
    242     delete mat_view;
    243     for (u_int i=0; i<mat_view2.rows(); ++i)
    244       for (u_int j=0; j<mat_view2.columns(); ++j)
    245         if (m5(i+3,j+3)!=mat_view2(i,j))
    246           this_ok=false;
    247     if (!this_ok) {
    248       *error << "FAIL: clone test" << std::endl;
    249       ok=false;
    250     }
    251113  }
    252114
     
    254116  // Checking that the row view works, i.e. mutation to the view are
    255117  // reflected in the viewed object.
    256   m5_sum2=0;
     118  double m5_sum2=0;
    257119  for (size_t i=0; i<m5.rows(); ++i)
    258120    for (size_t j=0; j<m5.columns(); ++j)
     
    338200  }
    339201
    340   *error << "\tsub-matrix of a sub-matrix" << std::endl;
    341   // Checking that a sub-matrix of a sub-matrix can be created.
    342   utility::matrix sub(m5,3,3,3,3);
    343   utility::matrix subsub(sub,2,1,1,2);
    344   subsub(0,0)=23221121;
    345   if (&sub(2,1)!=&subsub(0,0) || subsub(0,0)!=m5(5,4) || &subsub(0,0)!=&m5(5,4)){
    346     ok=false;
    347     *error << "error sub-matrix test 1" << std::endl;
    348   }
    349 
    350202  *error << "\tmatrix::nan()" << std::endl;
    351203  is.open("data/sorlie_centroid_data.txt");
  • trunk/test/pca_test.cc

    r1000 r1098  
    3434
    3535
    36 using namespace theplu::yat;
    37 int main()
     36int main(const int argc,const char* argv[])
    3837{
    39   utility::matrix A( 3, 4 );
    40   for( size_t i = 0; i < 3; ++i )
     38  using namespace theplu::yat;
     39  utility::matrix A( 3, 4 );
     40  for( size_t i = 0; i < 3; ++i )
    4141    for( size_t j = 0; j < 4; ++j )
    4242      A(i,j)= sin( static_cast<double>(i+j+2+(i+1)*(j+1)) );
     
    4444  utility::PCA pca(A);
    4545
    46   return 0;
     46  return 0;
    4747}
    4848
  • trunk/yat/classifier/KNN.h

    r1050 r1098  
    251251    // for each test sample (column in distances) find the closest
    252252    // training samples
    253     prediction.clone(utility::matrix(target_.nof_classes(),test.columns(),0.0));
     253    prediction.resize(target_.nof_classes(),test.columns(),0.0);
    254254    for(size_t sample=0;sample<distances->columns();sample++) {
    255255      std::vector<size_t> k_index;
  • trunk/yat/classifier/Kernel_SEV.cc

    r1000 r1098  
    4747    : Kernel(data,kf, own)
    4848  {
    49     kernel_matrix_.clone(utility::matrix(data_->columns(),data_->columns()));
     49    kernel_matrix_.resize(data_->columns(),data_->columns());
    5050    for (size_t i=0; i<kernel_matrix_.rows(); i++)
    5151      for (size_t j=i; j<kernel_matrix_.columns(); j++)
     
    6666  void Kernel_SEV::build_kernel(void)
    6767  {
    68     kernel_matrix_.clone(utility::matrix(data_->columns(),data_->columns()));
     68    kernel_matrix_.resize(data_->columns(),data_->columns());
    6969    for (size_t i=0; i<kernel_matrix_.rows(); i++)
    7070      for (size_t j=i; j<kernel_matrix_.columns(); j++)
  • trunk/yat/classifier/NCC.h

    r1089 r1098  
    232232       "NCC::predict test data with incorrect number of rows");
    233233   
    234     prediction.clone(utility::matrix(centroids_->columns(), test.columns()));       
     234    prediction.resize(centroids_->columns(), test.columns());
    235235
    236236    // unweighted test data
  • trunk/yat/classifier/SVM.cc

    r1087 r1098  
    158158      const KernelLookup& input_kernel =dynamic_cast<const KernelLookup&>(input);
    159159      assert(input.rows()==alpha_.size());
    160       prediction.clone(utility::matrix(2,input.columns(),0));
     160      prediction.resize(2,input.columns(),0);
    161161      for (size_t i = 0; i<input.columns(); i++){
    162162        for (size_t j = 0; j<input.rows(); j++){
  • trunk/yat/regression/MultiDimensional.cc

    r1021 r1098  
    5959  {
    6060    assert(x.rows()==y.size());
    61     covariance_.clone(utility::matrix(x.columns(),x.columns()));
     61    covariance_.resize(x.columns(),x.columns());
    6262    fit_parameters_ = utility::vector(x.columns());
    6363    if (work_)
  • trunk/yat/regression/MultiDimensionalWeighted.cc

    r1022 r1098  
    5858    assert(x.rows()==y.size());
    5959
    60     covariance_.clone(utility::matrix(x.columns(),x.columns()));
     60    covariance_.resize(x.columns(),x.columns());
    6161    fit_parameters_ = utility::vector(x.columns());
    6262    if (work_)
  • trunk/yat/utility/PCA.cc

    r1028 r1098  
    7272
    7373    // Read the eigenvectors and eigenvalues
    74     eigenvectors_.clone(U);
     74    eigenvectors_=U;
     75
    7576    eigenvectors_ .transpose();
    7677    eigenvalues_ = pSVD->s();
  • trunk/yat/utility/matrix.cc

    r1064 r1098  
    4545
    4646  matrix::matrix(void)
    47     : blas_result_(NULL), m_(NULL), view_(NULL), view_const_(NULL),
    48       proxy_m_(NULL)
     47    : blas_result_(NULL), m_(NULL)
    4948  {
    5049  }
     
    5251
    5352  matrix::matrix(const size_t& r, const size_t& c, double init_value)
    54     : blas_result_(NULL), m_(gsl_matrix_alloc(r,c)), view_(NULL),
    55       view_const_(NULL), proxy_m_(m_)
     53    : blas_result_(NULL), m_(gsl_matrix_alloc(r,c))
    5654  {
    5755    if (!m_)
     
    6260
    6361  matrix::matrix(const matrix& o)
    64     : blas_result_(NULL), m_(o.create_gsl_matrix_copy()), view_(NULL),
    65       view_const_(NULL), proxy_m_(m_)
    66   {
    67   }
    68 
    69 
    70   matrix::matrix(matrix& m, size_t offset_row, size_t offset_column,
    71                  size_t n_row, size_t n_column)
    72     : blas_result_(NULL), view_const_(NULL)
    73   {
    74     view_ = new gsl_matrix_view(gsl_matrix_submatrix(m.m_,
    75                                                      offset_row,offset_column,
    76                                                      n_row,n_column));
    77     if (!view_)
    78       throw utility::GSL_error("matrix::matrix failed to setup view");
    79     proxy_m_ = m_ = &(view_->matrix);
     62    : blas_result_(NULL), m_(o.create_gsl_matrix_copy())
     63  {
    8064  }
    8165
     
    8468  matrix::matrix(std::istream& is, char sep)
    8569    throw (utility::IO_error,std::exception)
    86     : blas_result_(NULL), view_(NULL), view_const_(NULL)
     70    : blas_result_(NULL)
    8771  {
    8872    // read the data file and store in stl vectors (dynamically
     
    140124    is.clear(std::ios::goodbit);
    141125    // convert the data to a gsl matrix
    142     proxy_m_ = m_ = gsl_matrix_alloc ( nof_rows, nof_columns );
     126    m_ = gsl_matrix_alloc ( nof_rows, nof_columns );
    143127    if (!m_)
    144128      throw utility::GSL_error("matrix::matrix failed to allocate memory");
     
    157141    if (blas_result_)
    158142      gsl_matrix_free(blas_result_);
     143    blas_result_=NULL;
     144  }
     145
     146
     147  void matrix::all(const double value)
     148  {
     149    assert(m_);
     150    gsl_matrix_set_all(m_, value);
    159151  }
    160152
     
    196188
    197189
    198   const matrix& matrix::clone(const matrix& other)
    199   {
    200     if (this!=&other) {
    201 
    202       delete_allocated_memory();
    203 
    204       if (other.view_) {
    205         view_ = new gsl_matrix_view(*other.view_);
    206         proxy_m_ = m_ = &(view_->matrix);
    207       }
    208       else if (other.view_const_) {
    209         view_const_ = new gsl_matrix_const_view(*other.view_const_);
    210         proxy_m_ = &(view_const_->matrix);
    211       }
    212       else if (other.m_)
    213         proxy_m_ = m_ = other.create_gsl_matrix_copy();
    214 
    215       // no need to delete blas_result_ if the number of rows fit, it
    216       // may be useful later.
    217       if (blas_result_ && (blas_result_->size1!=rows())) {
    218         gsl_matrix_free(blas_result_);
    219         blas_result_=NULL;
    220       }
    221     }
    222     return *this;
    223   }
     190  VectorView matrix::column_view(size_t col)
     191  {
     192    VectorView res(*this, col, false);
     193    return res;
     194  }
     195
     196
     197  const VectorConstView matrix::column_const_view(size_t col) const
     198  {
     199    return VectorConstView(*this, col, false);
     200  }
    224201
    225202
    226203  size_t matrix::columns(void) const
    227204  {
    228     if (!proxy_m_)
    229       return 0;
    230     return proxy_m_->size2;
     205    return (m_ ? m_->size2 : 0);
    231206  }
    232207
     
    237212    if (!m)
    238213      throw utility::GSL_error("matrix::create_gsl_matrix_copy failed to allocate memory");
    239     if (gsl_matrix_memcpy(m,proxy_m_))
     214    if (gsl_matrix_memcpy(m,m_))
    240215      throw utility::GSL_error("matrix::create_gsl_matrix_copy dimension mis-match");
    241216    return m;
     
    245220  void matrix::delete_allocated_memory(void)
    246221  {
    247     if (view_)
    248       delete view_;
    249     else if (view_const_)
    250       delete view_const_;
    251     else if (m_)
     222    if (m_)
    252223      gsl_matrix_free(m_);
    253     blas_result_=NULL;
    254     proxy_m_=m_=NULL;
     224    m_=NULL;
    255225  }
    256226
     
    319289  const gsl_matrix* matrix::gsl_matrix_p(void) const
    320290  {
    321     return proxy_m_;
     291    return m_;
    322292  }
    323293
     
    329299
    330300
    331   bool matrix::isview(void) const
    332   {
    333     return view_ || view_const_;
    334   }
    335 
    336 
    337301  void matrix::mul(const matrix& other)
    338302  {
     
    348312    delete_allocated_memory();
    349313
    350     proxy_m_ = m_ = gsl_matrix_alloc(r,c);
     314    m_ = gsl_matrix_alloc(r,c);
    351315    if (!m_)
    352316      throw utility::GSL_error("matrix::matrix failed to allocate memory");
     
    364328  size_t matrix::rows(void) const
    365329  {
    366     if (!proxy_m_)
    367       return 0;
    368     return proxy_m_->size1;
    369   }
    370 
    371 
    372   void matrix::all(const double value)
    373   {
    374     assert(m_);
    375     gsl_matrix_set_all(m_, value);
    376   }
    377 
    378 
    379   VectorView matrix::column_view(size_t col)
    380   {
    381     VectorView res(*this, col, false);
    382     return res;
    383   }
    384 
    385 
    386   const VectorConstView matrix::column_const_view(size_t col) const
    387   {
    388     return VectorConstView(*this, col, false);
     330    return (m_ ? m_->size1 : 0);
    389331  }
    390332
     
    442384      gsl_matrix_transpose_memcpy(transposed,m_);
    443385      gsl_matrix_free(m_);
    444       proxy_m_ = m_ = transposed;
     386      m_ = transposed;
    445387      if (blas_result_) {
    446388        gsl_matrix_free(blas_result_);
     
    467409    assert(row<rows());
    468410    assert(column<columns());
    469     const double* d=gsl_matrix_const_ptr(proxy_m_, row, column);
     411    const double* d=gsl_matrix_const_ptr(m_, row, column);
    470412    if (!d)
    471413      throw utility::GSL_error("matrix::operator()",GSL_EINVAL);
     
    488430  const matrix& matrix::operator=( const matrix& other )
    489431  {
    490     assert(m_);
    491     if (this!=&other)
     432    assert(other.m_);
     433    if (this!=&other) {
     434      if ( !m_ || (other.m_->size1!=m_->size1) || (other.m_->size2!=m_->size2) )
     435        resize(other.m_->size1,other.m_->size2);
    492436      if (gsl_matrix_memcpy(m_, other.gsl_matrix_p()))
    493437        throw utility::GSL_error("matrix::create_gsl_matrix_copy dimension mis-match");
     438    }
    494439    return *this;
    495440  }
     
    499444  {
    500445    assert(m_);
    501     int status=gsl_matrix_add(m_, other.proxy_m_);
     446    int status=gsl_matrix_add(m_, other.m_);
    502447    if (status)
    503448      throw utility::GSL_error(std::string("matrix::operator+=", status));
     
    517462  {
    518463    assert(m_);
    519     int status=gsl_matrix_sub(m_, other.proxy_m_);
     464    int status=gsl_matrix_sub(m_, other.m_);
    520465    if (status)
    521466      throw utility::GSL_error(std::string("matrix::operator-=", status));
     
    545490        throw utility::GSL_error("matrix::operator*= failed to allocate memory");
    546491    }
    547     gsl_blas_dgemm(CblasNoTrans, CblasNoTrans, 1.0, m_, other.proxy_m_, 0.0,
    548                    blas_result_);
     492    gsl_blas_dgemm(CblasNoTrans, CblasNoTrans, 1.0, m_, other.m_, 0.0, blas_result_);
    549493    gsl_matrix* tmp=m_;
    550     proxy_m_ = m_ = blas_result_;
     494    m_ = blas_result_;
    551495    blas_result_=tmp;
    552496    return *this;
     
    593537    size_t columns=templat.columns();
    594538    if (rows!=flag.rows() && columns!=flag.columns())
    595       flag.clone(matrix(rows,columns,1.0));
     539      flag.resize(rows,columns,1.0);
    596540    else
    597541      flag.all(1.0);
  • trunk/yat/utility/matrix.h

    r1068 r1098  
    5454  /// functionality, and thus binary read and write to streams are not
    5555  /// supported.
    56   ///
    57   /// \par[Matrix views] GSL matrix views are supported and these are
    58   /// disguised as ordinary utility::matrix'es. A support function is
    59   /// added, utility::matrix::isview(), that can be used to check if a
    60   /// matrix object is a view. Note that view matricess do not own the
    61   /// undelying data, and a view is not valid if the matrix owning the
    62   /// data is deallocated.
    6356  ///
    6457  /// @note All GSL matrix related functions are not implement but
     
    9992       \brief The copy constructor.
    10093
    101        \note If the object to be copied is a matrix view, the values
    102        of the view will be copied, i.e. the view is not copied.
    103 
    10494       \throw A GSL_error is indirectly thrown if memory allocation
    10595       fails.
    10696    */
    10797    matrix(const matrix&);
    108 
    109     /**
    110        \brief The matrix view constructor.
    111 
    112        Create a view of matrix \a m, with starting row \a offset_row,
    113        starting column \a offset_column, row size \a n_row, and column
    114        size \a n_column.
    115 
    116        A matrix view can be used as any matrix with the difference
    117        that changes made to the view will also change the object that
    118        is viewed. Also, using the copy constructor will create a new
    119        matrix object that is a copy of whatever is viewed. If a copy
    120        of the view is needed then you should use this constructor to
    121        obtain a copy.
    122 
    123        \note If the object viewed by the view goes out of scope or is
    124        deleted, the view becomes invalid and the result of further use
    125        is undefined.
    126 
    127        \throw GSL_error if a view cannot be set up.
    128     */
    129     matrix(matrix& m, size_t offset_row, size_t offset_column, size_t n_row,
    130            size_t n_column);
    13198
    13299    /**
     
    202169
    203170    /**
    204        \brief Make a copy of \a other.
    205 
    206        This function will make a deep copy of \a other. Memory is
    207        resized and view state is changed to same as in other.
    208     */
    209     const matrix& clone(const matrix& other);
    210 
    211     /**
    212171       \return Vector view of column \a i
    213172     */
     
    284243    gsl_matrix* gsl_matrix_p(void);
    285244
    286     ///
    287     /// @brief Check if the matrix object is a view (sub-matrix) to
    288     /// another matrix.
    289     ///
    290     /// @return True if the object is a view, false othwerwise.
    291     ///
    292     bool isview(void) const;
    293 
    294245    /**
    295246       Multiply the elements of matrix \a b with the elements of the
     
    306257       All elements are set to @a init_value.
    307258
    308        \note underlying GSL matrix is destroyed and a view into this
     259       \note underlying GSL matrix is destroyed and views into this
    309260       matrix becomes invalid.
    310261    */
     
    352303    /**
    353304       \brief Transpose the matrix.
    354 
    355        \note Invalidates view of matrix, unless matrix is square.
    356305
    357306       \throw GSL_error if memory allocation fails for the new
     
    412361       \brief The assignment operator.
    413362
    414        Dimensions of the matrices must match. If the LHS matrix is a
    415        view, the underlying data will be changed.
    416 
    417363       \return A const reference to the resulting matrix.
    418 
    419        \see void set(const matrix&).
    420 
    421        \throw GSL_error if dimensions mis-match.
    422364    */
    423365    const matrix& operator=(const matrix& other);
     
    512454    gsl_matrix* blas_result_;
    513455    gsl_matrix* m_;
    514     gsl_matrix_view* view_;
    515     const gsl_matrix_const_view* view_const_;
    516     // proxy_m_ is used to access the proper underlying gsl_matrix in
    517     // all const member functions. It is not used by design for
    518     // non-const vector functions and operators. This is to make sure
    519     // that runtime errors occur if a const matrix is used in an
    520     // inappropriate manner such as on left hand side in assignment
    521     // (remember, m_ is null for const matrix views).
    522     const gsl_matrix* proxy_m_;
    523456  };
    524457
Note: See TracChangeset for help on using the changeset viewer.