source: src/Fragmentation/MatrixContainer.cpp@ 5aa337

Candidate_v1.7.0 stable
Last change on this file since 5aa337 was 0f9eb0, checked in by Frederik Heber <frederik.heber@…>, 5 years ago

Modified fstream checks to good() or eof().

  • not using != or == NULL.
  • Property mode set to 100644
File size: 20.4 KB
Line 
1/*
2 * Project: MoleCuilder
3 * Description: creates and alters molecular systems
4 * Copyright (C) 2010-2012 University of Bonn. All rights reserved.
5 *
6 *
7 * This file is part of MoleCuilder.
8 *
9 * MoleCuilder is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * MoleCuilder is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MoleCuilder. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * MatrixContainer.cpp
25 *
26 * Created on: Sep 15, 2011
27 * Author: heber
28 */
29
30// include config.h
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34
35// include headers that implement a archive in simple text format
36// otherwise BOOST_CLASS_EXPORT_IMPLEMENT has no effect
37#include <boost/archive/text_oarchive.hpp>
38#include <boost/archive/text_iarchive.hpp>
39
40//#include "CodePatterns/MemDebug.hpp"
41
42#include <cstring>
43#include <fstream>
44#include <iomanip>
45
46#include "CodePatterns/Log.hpp"
47#include "KeySetsContainer.hpp"
48
49#include "Fragmentation/helpers.hpp"
50#include "Helpers/defs.hpp"
51#include "Helpers/helpers.hpp"
52
53#include "MatrixContainer.hpp"
54
55/** Constructor of MatrixContainer class.
56 */
57MatrixContainer::MatrixContainer()
58{
59 Header.resize(1);
60 RowCounter.resize(1);
61 ColumnCounter.resize(1);
62 ColumnCounter[0] = -1;
63 MatrixCounter = 0;
64};
65
66/** Destructor of MatrixContainer class.
67 */
68MatrixContainer::~MatrixContainer()
69{}
70
71/** Either copies index matrix from another MatrixContainer or initialises with trivial mapping if NULL.
72 * This either copies the index matrix or just maps 1 to 1, 2 to 2 and so on for all fragments.
73 * \param *Matrix pointer to other MatrixContainer
74 * \return true - copy/initialisation sucessful, false - dimension false for copying
75 */
76bool MatrixContainer::InitialiseIndices(class MatrixContainer *_container)
77{
78 if (_container == NULL) {
79 LOG(3, "INFO: Initialising indices with trivial mapping.");
80 Indices.resize(MatrixCounter + 1);
81 for(int i=MatrixCounter+1;i--;) {
82 Indices[i].resize(RowCounter[i]);
83 for(int j=RowCounter[i];j--;)
84 Indices[i][j] = j;
85 }
86 } else {
87 std::stringstream output;
88 if (MatrixCounter != _container->MatrixCounter)
89 return false;
90 Indices.resize(MatrixCounter + 1);
91 for(int i=MatrixCounter+1;i--;) {
92 if (RowCounter[i] != _container->RowCounter[i])
93 return false;
94 Indices[i].resize(_container->RowCounter[i]);
95 for(int j=_container->RowCounter[i];j--;) {
96 Indices[i][j] = _container->Indices[i][j];
97 output << Indices[i][j] << "\t";
98 }
99 }
100 LOG(3, "INFO: Initialising indices from other MatrixContainer: " << output.str());
101 }
102 return true;
103};
104
105/** Parsing a number of matrices.
106 * -# open the matrix file
107 * -# skip some lines (\a skiplines)
108 * -# scan header lines for number of columns
109 * -# scan lines for number of rows
110 * -# allocate a temporary matrix
111 * -# loop over found column and row counts and parse in each entry
112 * -# use MatrixContainer::AddMatrix() to add the parsed matrix to the internal
113 * \param &input input stream
114 * \param skiplines number of inital lines to skip
115 * \param skiplines number of inital columns to skip
116 * \param MatrixNr index number in Matrix array to parse into
117 * \return parsing successful
118 */
119bool MatrixContainer::ParseMatrix(std::istream &input, int skiplines, int skipcolumns, size_t MatrixNr)
120{
121 stringstream line;
122 string token;
123 char filename[1023];
124
125 if (input.fail()) {
126 ELOG(1, endl << "MatrixContainer::ParseMatrix: Unable to parse istream.");
127 //performCriticalExit();
128 return false;
129 }
130
131 // parse header
132 std::string header;
133 char dummy[1024];
134 for (int m=skiplines+1;m--;)
135 input.getline(dummy, 1023);
136 line.str(dummy);
137 for(int k=skipcolumns;k--;)
138 line >> header;
139 LOG(3, "INFO: Header of Matrix " << MatrixNr << " :" << line.str());
140
141 // scan header for number of columns
142 size_t ColumnCounter = 0;
143 while ( getline(line,token, '\t') ) {
144 if (token.length() > 0)
145 ColumnCounter++;
146 }
147 LOG(3, "INFO: "+line.str());
148
149 // scan rest for number of rows/lines
150 size_t RowCounter = -1;
151 while (!input.eof()) {
152 input.getline(filename, 1023);
153 LOG(3, "INFO: Comparing: " << strncmp(filename,"MeanForce",9));
154 RowCounter++; // then line was not last MeanForce
155 if (strncmp(filename,"MeanForce", 9) == 0) {
156 break;
157 }
158 }
159
160 // allocate temporary matrix
161 MatrixArray temp_matrix;
162 temp_matrix.resize(RowCounter);
163 for(MatrixArray::iterator iter = temp_matrix.begin(); iter != temp_matrix.end(); ++iter)
164 (*iter).resize(ColumnCounter);
165
166 // parse in each entry for this matrix
167 input.clear();
168 input.seekg(ios::beg);
169 for (int m=skiplines+1;m--;)
170 input.getline(dummy, 1023); // skip header
171 line.str(dummy);
172 LOG(3, "INFO: Header: " << line.str());
173 for(int k=skipcolumns;k--;) // skip columns in header too
174 line >> filename;
175 header = line.str();
176 for(size_t j=0;j<RowCounter;j++) {
177 input.getline(filename, 1023);
178 std::stringstream lines(filename);
179 std::stringstream output;
180 output << "INFO: Matrix at level " << j << ":";// << filename << endl;
181 for(int k=skipcolumns;k--;)
182 lines >> filename;
183 for(size_t k=0;(k<ColumnCounter) && (!lines.eof());k++) {
184 lines >> temp_matrix[j][k];
185 output << " " << std::setprecision(2) << temp_matrix[j][k] << endl;
186 }
187 LOG(3, output.str());
188 }
189
190 // finally add the matrix
191 return AddMatrix(header, temp_matrix, MatrixNr);
192}
193
194/** Adds a matrix at position \a MatrixNr to MatrixContainer::Matrix.
195 *
196 * @param header header to add for this matrix
197 * @param matrix to add
198 * @param MatrixNr position in MatrixContainer::Matrix.
199 * @return true - insertion ok, false - invalid matrix
200 */
201bool MatrixContainer::AddMatrix(const std::string &header, const MatrixArray &matrix, size_t MatrixNr)
202{
203 // make some pre-checks
204 if (header.size() == 0)
205 ELOG(2, "The given header of the matrix to add is empty.");
206 if (matrix.size() == 0) {
207 ELOG(1, "RowCounter[" << MatrixNr << "]: " << RowCounter[MatrixNr] << " from input stream.");
208 return false;
209 }
210 if (matrix[0].size() == 0) {
211 ELOG(1, "ColumnCounter[" << MatrixNr << "]: " << ColumnCounter[MatrixNr] << " from ostream.");
212 return false;
213 }
214
215 // add header
216 if (Header.size() <= MatrixNr)
217 Header.resize(MatrixNr+1);
218 Header[MatrixNr] = header;
219
220 // row count
221 if (RowCounter.size() <= MatrixNr)
222 RowCounter.resize(MatrixNr+1);
223 RowCounter[MatrixNr] = matrix.size();
224 LOG(4, "INFO: RowCounter[" << MatrixNr << "]: " << RowCounter[MatrixNr] << " from input stream.");
225
226 // column count
227 if (ColumnCounter.size() <= MatrixNr)
228 ColumnCounter.resize(MatrixNr+1);
229 ColumnCounter[MatrixNr] = matrix[0].size();
230 LOG(4, "INFO: ColumnCounter[" << MatrixNr << "]: " << ColumnCounter[MatrixNr] << ".");
231
232 // allocate matrix ...
233 if (Matrix.size() <= MatrixNr)
234 Matrix.resize(MatrixNr+1);
235 MatrixCounter = Matrix.size()-1;
236 Matrix[MatrixNr].resize(RowCounter[MatrixNr] + 1);
237 for(int j=0;j<=RowCounter[MatrixNr];++j)
238 Matrix[MatrixNr][j].resize(ColumnCounter[MatrixNr]+1);
239
240 // .. and copy values
241 for(int j=0;j<RowCounter[MatrixNr];++j)
242 for(int k=0;k<ColumnCounter[MatrixNr];++k)
243 Matrix[MatrixNr][j][k] = matrix[j][k];
244 // reset last column
245 for(int j=0;j<RowCounter[MatrixNr];++j)
246 Matrix[MatrixNr][j][ ColumnCounter[MatrixNr] ] = 0.;
247 // reset last row
248 for(int k=0;k<=ColumnCounter[MatrixNr];++k)
249 Matrix[MatrixNr][ RowCounter[MatrixNr] ][ k ] = 0.;
250
251 return true;
252}
253
254/** Parsing a number of matrices.
255 * -# First, count the number of matrices by counting lines in KEYSETFILE
256 * -# Then,
257 * -# construct the fragment number
258 * -# open the matrix file
259 * -# skip some lines (\a skiplines)
260 * -# scan header lines for number of columns
261 * -# scan lines for number of rows
262 * -# allocate matrix
263 * -# loop over found column and row counts and parse in each entry
264 * -# Finally, allocate one additional matrix (\a MatrixCounter) containing combined or temporary values
265 * \param *name directory with files
266 * \param *prefix prefix of each matrix file
267 * \param *suffix suffix of each matrix file
268 * \param skiplines number of inital lines to skip
269 * \param skiplines number of inital columns to skip
270 * \return parsing successful
271 */
272bool MatrixContainer::ParseFragmentMatrix(const std::string name, const std::string prefix, std::string suffix, int skiplines, int skipcolumns)
273{
274 char filename[1023];
275 ifstream input;
276 char *FragmentNumber = NULL;
277 stringstream file;
278 string token;
279
280 // count the number of matrices
281 MatrixCounter = -1; // we count one too much
282 file << name << FRAGMENTPREFIX << KEYSETFILE;
283 input.open(file.str().c_str(), ios::in);
284 if (input.bad()) {
285 ELOG(1, "MatrixContainer::ParseFragmentMatrix: Unable to open " << file.str() << ", is the directory correct?");
286 return false;
287 }
288 while (!input.eof()) {
289 input.getline(filename, 1023);
290 stringstream zeile(filename);
291 MatrixCounter++;
292 }
293 input.close();
294 LOG(2, "INFO: Determined " << MatrixCounter << " fragments.");
295
296 LOG(1, "STATUS: Parsing through each fragment and retrieving " << prefix << suffix << ".");
297 Header.clear();
298 Matrix.clear();
299 RowCounter.clear();
300 ColumnCounter.clear();
301 Header.resize(MatrixCounter + 1); // one more each for the total molecule
302 Matrix.resize(MatrixCounter + 1); // one more each for the total molecule
303 RowCounter.resize(MatrixCounter + 1);
304 ColumnCounter.resize(MatrixCounter + 1);
305 for(int i=0; i < MatrixCounter;i++) {
306 // open matrix file
307 FragmentNumber = FixedDigitNumber(MatrixCounter, i);
308 file.str(" ");
309 file << name << FRAGMENTPREFIX << FragmentNumber << prefix << suffix;
310 std::ifstream input(file.str().c_str());
311 LOG(2, "INFO: Opening " << file.str() << " ... ");
312 if (!ParseMatrix(input, skiplines, skipcolumns, i)) {
313 input.close();
314 return false;
315 }
316 input.close();
317 delete[](FragmentNumber);
318 }
319 return true;
320};
321
322/** Allocates and resets the memory for a number \a MCounter of matrices.
323 * \param **GivenHeader Header line for each matrix
324 * \param MCounter number of matrices
325 * \param *RCounter number of rows for each matrix
326 * \param *CCounter number of columns for each matrix
327 * \return Allocation successful
328 */
329bool MatrixContainer::AllocateMatrix(StringVector GivenHeader, int MCounter, IntVector RCounter, IntVector CCounter)
330{
331 MatrixCounter = MCounter;
332 Header.resize(MatrixCounter + 1);
333 Matrix.resize(MatrixCounter + 1); // one more each for the total molecule
334 RowCounter.resize(MatrixCounter + 1);
335 ColumnCounter.resize(MatrixCounter + 1);
336 for(int i=MatrixCounter+1;i--;) {
337 Header[i] = GivenHeader[i];
338 RowCounter[i] = RCounter[i];
339 ColumnCounter[i] = CCounter[i];
340 if ((int)Matrix[i].size() <= RowCounter[i] + 2)
341 Matrix[i].resize(RowCounter[i] + 1);
342 for(int j=0;j<=RowCounter[i];j++)
343 if ((int)Matrix[i][j].size() <= ColumnCounter[i]+1)
344 Matrix[i][j].resize(ColumnCounter[i]);
345 // allocation with 0 is guaranted by STL
346 }
347 return true;
348};
349
350/** Resets all values in MatrixContainer::Matrix.
351 * \return true if successful
352 */
353bool MatrixContainer::ResetMatrix()
354{
355 for(int i=MatrixCounter+1;i--;)
356 for(int j=RowCounter[i]+1;j--;)
357 for(int k=ColumnCounter[i];k--;)
358 Matrix[i][j][k] = 0.;
359 return true;
360};
361
362/** Scans all elements of MatrixContainer::Matrix for greatest absolute value.
363 * \return greatest value of MatrixContainer::Matrix
364 */
365double MatrixContainer::FindMaxValue()
366{
367 double max = Matrix[0][0][0];
368 for(int i=MatrixCounter+1;i--;)
369 for(int j=RowCounter[i]+1;j--;)
370 for(int k=ColumnCounter[i];k--;)
371 if (fabs(Matrix[i][j][k]) > max)
372 max = fabs(Matrix[i][j][k]);
373 if (fabs(max) < MYEPSILON)
374 max += MYEPSILON;
375 return max;
376};
377
378/** Scans all elements of MatrixContainer::Matrix for smallest absolute value.
379 * \return smallest value of MatrixContainer::Matrix
380 */
381double MatrixContainer::FindMinValue()
382{
383 double min = Matrix[0][0][0];
384 for(int i=MatrixCounter+1;i--;)
385 for(int j=RowCounter[i]+1;j--;)
386 for(int k=ColumnCounter[i];k--;)
387 if (fabs(Matrix[i][j][k]) < min)
388 min = fabs(Matrix[i][j][k]);
389 if (fabs(min) < MYEPSILON)
390 min += MYEPSILON;
391 return min;
392};
393
394/** Sets all values in the last of MatrixContainer::Matrix to \a value.
395 * \param value reset value
396 * \param skipcolumns skip initial columns
397 * \return true if successful
398 */
399bool MatrixContainer::SetLastMatrix(double value, int skipcolumns)
400{
401 for(int j=RowCounter[MatrixCounter]+1;j--;)
402 for(int k=skipcolumns;k<ColumnCounter[MatrixCounter];k++)
403 Matrix[MatrixCounter][j][k] = value;
404 return true;
405};
406
407/** Sets all values in the last of MatrixContainer::Matrix to \a value.
408 * \param **values matrix with each value (must have at least same dimensions!)
409 * \param skipcolumns skip initial columns
410 * \return true if successful
411 */
412bool MatrixContainer::SetLastMatrix(const MatrixArray &values, int skipcolumns)
413{
414 for(int j=RowCounter[MatrixCounter]+1;j--;)
415 for(int k=skipcolumns;k<ColumnCounter[MatrixCounter];k++)
416 Matrix[MatrixCounter][j][k] = values[j][k];
417 return true;
418};
419
420/** Sums the entries with each factor and put into last element of \a ***Matrix.
421 * Sums over "E"-terms to create the "F"-terms
422 * \param Matrix MatrixContainer with matrices (LevelCounter by *ColumnCounter) with all the energies.
423 * \param KeySets KeySetContainer with bond Order and association mapping of each fragment to an order
424 * \param Order bond order
425 * \return true if summing was successful
426 */
427bool MatrixContainer::SumSubManyBodyTerms(class MatrixContainer &MatrixValues, class KeySetsContainer &KeySets, int Order)
428{
429 // go through each order
430 for (int CurrentFragment=0;CurrentFragment<KeySets.FragmentsPerOrder[Order];CurrentFragment++) {
431 //LOG(0, "Current Fragment is " << CurrentFragment << "/" << KeySets.OrderSet[Order][CurrentFragment] << ".");
432 // then go per order through each suborder and pick together all the terms that contain this fragment
433 for(int SubOrder=0;SubOrder<=Order;SubOrder++) { // go through all suborders up to the desired order
434 for (int j=0;j<KeySets.FragmentsPerOrder[SubOrder];j++) { // go through all possible fragments of size suborder
435 if (KeySets.Contains(KeySets.OrderSet[Order][CurrentFragment], KeySets.OrderSet[SubOrder][j])) {
436 //LOG(0, "Current other fragment is " << j << "/" << KeySets.OrderSet[SubOrder][j] << ".");
437 // if the fragment's indices are all in the current fragment
438 for(int k=0;k<RowCounter[ KeySets.OrderSet[SubOrder][j] ];k++) { // go through all atoms in this fragment
439 int m = MatrixValues.Indices[ KeySets.OrderSet[SubOrder][j] ][k];
440 //LOG(0, "Current index is " << k << "/" << m << ".");
441 if (m != -1) { // if it's not an added hydrogen
442 for (int l=0;l<RowCounter[ KeySets.OrderSet[Order][CurrentFragment] ];l++) { // look for the corresponding index in the current fragment
443 //LOG(0, "Comparing " << m << " with " << MatrixValues.Indices[ KeySets.OrderSet[Order][CurrentFragment] ][l] << ".");
444 if (m == MatrixValues.Indices[ KeySets.OrderSet[Order][CurrentFragment] ][l]) {
445 m = l;
446 break;
447 }
448 }
449 //LOG(0, "Corresponding index in CurrentFragment is " << m << ".");
450 if (m > RowCounter[ KeySets.OrderSet[Order][CurrentFragment] ]) {
451 ELOG(0, "In fragment No. " << KeySets.OrderSet[Order][CurrentFragment] << " current force index " << m << " is greater than " << RowCounter[ KeySets.OrderSet[Order][CurrentFragment] ] << "!");
452 performCriticalExit();
453 return false;
454 }
455 if (Order == SubOrder) { // equal order is always copy from Energies
456 for(int l=ColumnCounter[ KeySets.OrderSet[SubOrder][j] ];l--;) // then adds/subtract each column
457 Matrix[ KeySets.OrderSet[Order][CurrentFragment] ][m][l] += MatrixValues.Matrix[ KeySets.OrderSet[SubOrder][j] ][k][l];
458 } else {
459 for(int l=ColumnCounter[ KeySets.OrderSet[SubOrder][j] ];l--;)
460 Matrix[ KeySets.OrderSet[Order][CurrentFragment] ][m][l] -= Matrix[ KeySets.OrderSet[SubOrder][j] ][k][l];
461 }
462 }
463 //if ((ColumnCounter[ KeySets.OrderSet[SubOrder][j] ]>1) && (RowCounter[0]-1 >= 1))
464 //LOG(0, "Fragments[ KeySets.OrderSet[" << Order << "][" << CurrentFragment << "]=" << KeySets.OrderSet[Order][CurrentFragment] << " ][" << RowCounter[0]-1 << "][" << 1 << "] = " << Matrix[ KeySets.OrderSet[Order][CurrentFragment] ][RowCounter[0]-1][1]);
465 }
466 } else {
467 //LOG(0, "Fragment " << KeySets.OrderSet[SubOrder][j] << " is not contained in fragment " << KeySets.OrderSet[Order][CurrentFragment] << ".");
468 }
469 }
470 }
471 //LOG(0, "Final Fragments[ KeySets.OrderSet[" << Order << "][" << CurrentFragment << "]=" << KeySets.OrderSet[Order][CurrentFragment] << " ][" << KeySets.AtomCounter[0]-1 << "][" << 1 << "] = " << Matrix[ KeySets.OrderSet[Order][CurrentFragment] ][KeySets.AtomCounter[0]-1][1]);
472 }
473
474 return true;
475};
476
477/** Writes the summed total fragment terms \f$F_{ij}\f$ to file.
478 * \param *name inputdir
479 * \param *prefix prefix before \a EnergySuffix
480 * \return file was written
481 */
482bool MatrixContainer::WriteTotalFragments(const std::string name, const std::string prefix)
483{
484 ofstream output;
485 char *FragmentNumber = NULL;
486
487 LOG(1, "STATUS: Writing fragment files.");
488 for(int i=0;i<MatrixCounter;i++) {
489 stringstream line;
490 FragmentNumber = FixedDigitNumber(MatrixCounter, i);
491 line << name << FRAGMENTPREFIX << FragmentNumber << "/" << prefix;
492 delete[](FragmentNumber);
493 output.open(line.str().c_str(), ios::out);
494 if (!output.good()) {
495 ELOG(0, "MatrixContainer::WriteTotalFragments: Unable to open output energy file " << line.str() << "!");
496 performCriticalExit();
497 return false;
498 }
499 output << Header[i] << endl;
500 for(int j=0;j<RowCounter[i];j++) {
501 for(int k=0;k<ColumnCounter[i];k++)
502 output << scientific << Matrix[i][j][k] << "\t";
503 output << endl;
504 }
505 output.close();
506 }
507 return true;
508};
509
510/** Writes the summed total values in the last matrix to file.
511 * \param *name inputdir
512 * \param *prefix prefix
513 * \param *suffix suffix
514 * \return file was written
515 */
516bool MatrixContainer::WriteLastMatrix(const std::string name, const std::string prefix, const std::string suffix)
517{
518 ofstream output;
519 stringstream line;
520
521 LOG(1, "STATUS: Writing matrix values of " << suffix << ".");
522 line << name << prefix << suffix;
523 output.open(line.str().c_str(), ios::out);
524 if (output.eof()) {
525 ELOG(0, "MatrixContainer::WriteLastMatrix: Unable to open output matrix file " << line.str() << "!");
526 performCriticalExit();
527 return false;
528 }
529 output << Header[MatrixCounter] << endl;
530 for(int j=0;j<RowCounter[MatrixCounter];j++) {
531 for(int k=0;k<ColumnCounter[MatrixCounter];k++)
532 output << scientific << Matrix[MatrixCounter][j][k] << "\t";
533 output << endl;
534 }
535 output.close();
536 return true;
537};
538
539/** Comparison operator for class MatrixContainer.
540 *
541 * @param other instance to compare to
542 * @return true - both instances are the same in each member variable.
543 */
544bool MatrixContainer::operator==(const MatrixContainer &other) const
545{
546 // compare matrices
547 if (Matrix != other.Matrix)
548 return false;
549 // compare Indices
550 if (Indices != other.Indices)
551 return false;
552 // compare Headers
553 if (Header != other.Header)
554 return false;
555 // compare MatrixCounter
556 if (MatrixCounter != other.MatrixCounter)
557 return false;
558 // compare RowCounter
559 if (RowCounter != other.RowCounter)
560 return false;
561 // compare ColumnCounter
562 if (ColumnCounter != other.ColumnCounter)
563 return false;
564 return true;
565}
566
567std::ostream & operator << (std::ostream &ost, const MatrixContainer &m)
568{
569 for (int i=0;i<=m.MatrixCounter;++i) {
570 ost << "Matrix " << i << " in MatrixContainer:" << std::endl;
571 for (int j=0;j<=m.RowCounter[i];++j) {
572 for (int k=0;k<m.ColumnCounter[i];++k) {
573 ost << m.Matrix[i][j][k] << " ";
574 }
575 ost << std::endl;
576 }
577 }
578 ost << std::endl;
579 return ost;
580}
581
Note: See TracBrowser for help on using the repository browser.