ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/OpenMD/branches/development/src/io/DumpWriter.cpp
(Generate patch)

Comparing:
trunk/src/io/DumpWriter.cpp (file contents), Revision 1024 by tim, Wed Aug 30 18:42:29 2006 UTC vs.
branches/development/src/io/DumpWriter.cpp (file contents), Revision 1764 by gezelter, Tue Jul 3 18:32:27 2012 UTC

# Line 1 | Line 1
1   /*
2 < * Copyright (c) 2005 The University of Notre Dame. All Rights Reserved.
2 > * Copyright (c) 2009 The University of Notre Dame. All Rights Reserved.
3   *
4   * The University of Notre Dame grants you ("Licensee") a
5   * non-exclusive, royalty free, license to use, modify and
6   * redistribute this software in source and binary code form, provided
7   * that the following conditions are met:
8   *
9 < * 1. Acknowledgement of the program authors must be made in any
10 < *    publication of scientific results based in part on use of the
11 < *    program.  An acceptable form of acknowledgement is citation of
12 < *    the article in which the program was described (Matthew
13 < *    A. Meineke, Charles F. Vardeman II, Teng Lin, Christopher
14 < *    J. Fennell and J. Daniel Gezelter, "OOPSE: An Object-Oriented
15 < *    Parallel Simulation Engine for Molecular Dynamics,"
16 < *    J. Comput. Chem. 26, pp. 252-271 (2005))
17 < *
18 < * 2. Redistributions of source code must retain the above copyright
9 > * 1. Redistributions of source code must retain the above copyright
10   *    notice, this list of conditions and the following disclaimer.
11   *
12 < * 3. Redistributions in binary form must reproduce the above copyright
12 > * 2. Redistributions in binary form must reproduce the above copyright
13   *    notice, this list of conditions and the following disclaimer in the
14   *    documentation and/or other materials provided with the
15   *    distribution.
# Line 37 | Line 28
28   * arising out of the use of or inability to use software, even if the
29   * University of Notre Dame has been advised of the possibility of
30   * such damages.
31 + *
32 + * SUPPORT OPEN SCIENCE!  If you use OpenMD or its source code in your
33 + * research, please cite the appropriate papers when you publish your
34 + * work.  Good starting points are:
35 + *                                                                      
36 + * [1]  Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).            
37 + * [2]  Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).          
38 + * [3]  Sun, Lin & Gezelter, J. Chem. Phys. 128, 24107 (2008).          
39 + * [4]  Kuang & Gezelter,  J. Chem. Phys. 133, 164101 (2010).
40 + * [5]  Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
41   */
42  
43   #include "io/DumpWriter.hpp"
# Line 46 | Line 47
47   #include "io/gzstream.hpp"
48   #include "io/Globals.hpp"
49  
50 +
51   #ifdef IS_MPI
52   #include <mpi.h>
53 < #endif //is_mpi
53 > #endif
54  
55 < namespace oopse {
55 > using namespace std;
56 > namespace OpenMD {
57  
58    DumpWriter::DumpWriter(SimInfo* info)
59      : info_(info), filename_(info->getDumpFileName()), eorFilename_(info->getFinalConfigFileName()){
60  
61      Globals* simParams = info->getSimParams();
62 <    needCompression_ = simParams->getCompressDumpFile();
63 <    needForceVector_ = simParams->getOutputForceVector();
62 >    needCompression_   = simParams->getCompressDumpFile();
63 >    needForceVector_   = simParams->getOutputForceVector();
64 >    needParticlePot_   = simParams->getOutputParticlePotential();
65 >    needFlucQ_         = simParams->getOutputFluctuatingCharges();
66 >    needElectricField_ = simParams->getOutputElectricField();
67 >
68 >    if (needParticlePot_ || needFlucQ_ || needElectricField_) {
69 >      doSiteData_ = true;
70 >    } else {
71 >      doSiteData_ = false;
72 >    }
73 >
74      createDumpFile_ = true;
75   #ifdef HAVE_LIBZ
76      if (needCompression_) {
# Line 95 | Line 108 | namespace oopse {
108      Globals* simParams = info->getSimParams();
109      eorFilename_ = filename_.substr(0, filename_.rfind(".")) + ".eor";    
110  
111 <    needCompression_ = simParams->getCompressDumpFile();
112 <    needForceVector_ = simParams->getOutputForceVector();
111 >    needCompression_   = simParams->getCompressDumpFile();
112 >    needForceVector_   = simParams->getOutputForceVector();
113 >    needParticlePot_   = simParams->getOutputParticlePotential();
114 >    needFlucQ_         = simParams->getOutputFluctuatingCharges();
115 >    needElectricField_ = simParams->getOutputElectricField();
116 >
117 >    if (needParticlePot_ || needFlucQ_ || needElectricField_) {
118 >      doSiteData_ = true;
119 >    } else {
120 >      doSiteData_ = false;
121 >    }
122 >
123      createDumpFile_ = true;
124   #ifdef HAVE_LIBZ
125      if (needCompression_) {
# Line 134 | Line 157 | namespace oopse {
157      Globals* simParams = info->getSimParams();
158      eorFilename_ = filename_.substr(0, filename_.rfind(".")) + ".eor";    
159      
160 <    needCompression_ = simParams->getCompressDumpFile();
161 <    needForceVector_ = simParams->getOutputForceVector();
162 <    
160 >    needCompression_   = simParams->getCompressDumpFile();
161 >    needForceVector_   = simParams->getOutputForceVector();
162 >    needParticlePot_   = simParams->getOutputParticlePotential();
163 >    needFlucQ_         = simParams->getOutputFluctuatingCharges();
164 >    needElectricField_ = simParams->getOutputElectricField();
165 >
166 >    if (needParticlePot_ || needFlucQ_ || needElectricField_) {
167 >      doSiteData_ = true;
168 >    } else {
169 >      doSiteData_ = false;
170 >    }
171 >
172   #ifdef HAVE_LIBZ
173      if (needCompression_) {
174        filename_ += ".gz";
# Line 194 | Line 226 | namespace oopse {
226      os << "    <FrameData>\n";
227  
228      RealType currentTime = s->getTime();
229 <    sprintf(buffer, "        Time: %.10g\n", time);
229 >
230 >    if (isinf(currentTime) || isnan(currentTime)) {      
231 >      sprintf( painCave.errMsg,
232 >               "DumpWriter detected a numerical error writing the time");      
233 >      painCave.isFatal = 1;
234 >      simError();
235 >    }
236 >    
237 >    sprintf(buffer, "        Time: %.10g\n", currentTime);
238      os << buffer;
239  
240      Mat3x3d hmat;
241      hmat = s->getHmat();
242 +
243 +    for (unsigned int i = 0; i < 3; i++) {
244 +      for (unsigned int j = 0; j < 3; j++) {
245 +        if (isinf(hmat(i,j)) || isnan(hmat(i,j))) {      
246 +          sprintf( painCave.errMsg,
247 +                   "DumpWriter detected a numerical error writing the box");
248 +          painCave.isFatal = 1;
249 +          simError();
250 +        }        
251 +      }
252 +    }
253 +    
254      sprintf(buffer, "        Hmat: {{ %.10g, %.10g, %.10g }, { %.10g, %.10g, %.10g }, { %.10g, %.10g, %.10g }}\n",
255              hmat(0, 0), hmat(1, 0), hmat(2, 0),
256              hmat(0, 1), hmat(1, 1), hmat(2, 1),
257              hmat(0, 2), hmat(1, 2), hmat(2, 2));
258      os << buffer;
259  
260 <    RealType chi = s->getChi();
261 <    RealType integralOfChiDt = s->getIntegralOfChiDt();
262 <    sprintf(buffer, "  Thermostat: %.10g , %.10g\n", chi, integralOfChiDt);
260 >    pair<RealType, RealType> thermostat = s->getThermostat();
261 >
262 >    if (isinf(thermostat.first)  || isnan(thermostat.first) ||
263 >        isinf(thermostat.second) || isnan(thermostat.second)) {      
264 >      sprintf( painCave.errMsg,
265 >               "DumpWriter detected a numerical error writing the thermostat");
266 >      painCave.isFatal = 1;
267 >      simError();
268 >    }
269 >    sprintf(buffer, "  Thermostat: %.10g , %.10g\n", thermostat.first,
270 >            thermostat.second);
271      os << buffer;
272  
273      Mat3x3d eta;
274 <    eta = s->getEta();
274 >    eta = s->getBarostat();
275 >
276 >    for (unsigned int i = 0; i < 3; i++) {
277 >      for (unsigned int j = 0; j < 3; j++) {
278 >        if (isinf(eta(i,j)) || isnan(eta(i,j))) {      
279 >          sprintf( painCave.errMsg,
280 >                   "DumpWriter detected a numerical error writing the barostat");
281 >          painCave.isFatal = 1;
282 >          simError();
283 >        }        
284 >      }
285 >    }
286 >
287      sprintf(buffer, "    Barostat: {{ %.10g, %.10g, %.10g }, { %.10g, %.10g, %.10g }, { %.10g, %.10g, %.10g }}\n",
288              eta(0, 0), eta(1, 0), eta(2, 0),
289              eta(0, 1), eta(1, 1), eta(2, 1),
# Line 231 | Line 303 | namespace oopse {
303      StuntDouble* integrableObject;
304      SimInfo::MoleculeIterator mi;
305      Molecule::IntegrableObjectIterator ii;
306 +    RigidBody::AtomIterator ai;
307 +    Atom* atom;
308  
309   #ifndef IS_MPI
310      os << "  <Snapshot>\n";
311 <
311 >
312      writeFrameProperties(os, info_->getSnapshotManager()->getCurrentSnapshot());
313  
314      os << "    <StuntDoubles>\n";
315      for (mol = info_->beginMolecule(mi); mol != NULL; mol = info_->nextMolecule(mi)) {
316  
317 <      for (integrableObject = mol->beginIntegrableObject(ii); integrableObject != NULL;
317 >      
318 >      for (integrableObject = mol->beginIntegrableObject(ii); integrableObject != NULL;  
319             integrableObject = mol->nextIntegrableObject(ii)) {  
320 <        os << prepareDumpLine(integrableObject);
321 <
320 >          os << prepareDumpLine(integrableObject);
321 >          
322        }
323      }    
324      os << "    </StuntDoubles>\n";
325  
326 +    if (doSiteData_) {
327 +      os << "    <SiteData>\n";
328 +      for (mol = info_->beginMolecule(mi); mol != NULL; mol = info_->nextMolecule(mi)) {
329 +              
330 +        for (integrableObject = mol->beginIntegrableObject(ii); integrableObject != NULL;  
331 +           integrableObject = mol->nextIntegrableObject(ii)) {  
332 +
333 +          int ioIndex = integrableObject->getGlobalIntegrableObjectIndex();
334 +          // do one for the IO itself
335 +          os << prepareSiteLine(integrableObject, ioIndex, 0);
336 +
337 +          if (integrableObject->isRigidBody()) {
338 +            
339 +            RigidBody* rb = static_cast<RigidBody*>(integrableObject);
340 +            int siteIndex = 0;
341 +            for (atom = rb->beginAtom(ai); atom != NULL;  
342 +                 atom = rb->nextAtom(ai)) {                                            
343 +              os << prepareSiteLine(atom, ioIndex, siteIndex);
344 +              siteIndex++;
345 +            }
346 +          }
347 +        }
348 +      }    
349 +      os << "    </SiteData>\n";
350 +    }
351      os << "  </Snapshot>\n";
352  
353      os.flush();
# Line 255 | Line 355 | namespace oopse {
355      //every node prepares the dump lines for integrable objects belong to itself
356      std::string buffer;
357      for (mol = info_->beginMolecule(mi); mol != NULL; mol = info_->nextMolecule(mi)) {
358 <      
358 >
359 >
360        for (integrableObject = mol->beginIntegrableObject(ii); integrableObject != NULL;
361             integrableObject = mol->nextIntegrableObject(ii)) {  
362 <        buffer += prepareDumpLine(integrableObject);
362 >          buffer += prepareDumpLine(integrableObject);
363        }
364      }
365      
366      const int masterNode = 0;
367 <
367 >    int nProc;
368 >    MPI_Comm_size(MPI_COMM_WORLD, &nProc);
369      if (worldRank == masterNode) {      
370        os << "  <Snapshot>\n";  
371        writeFrameProperties(os, info_->getSnapshotManager()->getCurrentSnapshot());
270      os << buffer;    
372        os << "    <StuntDoubles>\n";
373          
374 <      int nProc;
375 <      MPI_Comm_size(MPI_COMM_WORLD, &nProc);
374 >      os << buffer;
375 >
376        for (int i = 1; i < nProc; ++i) {
377  
378          // receive the length of the string buffer that was
379          // prepared by processor i
380  
381 +        MPI_Bcast(&i, 1, MPI_INT,masterNode,MPI_COMM_WORLD);
382          int recvLength;
383          MPI_Recv(&recvLength, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &istatus);
384          char* recvBuffer = new char[recvLength];
385          if (recvBuffer == NULL) {
284                        
386          } else {
387 <          MPI_Recv(&recvBuffer, recvLength, MPI_CHAR, i, 0, MPI_COMM_WORLD, &istatus);
387 >          MPI_Recv(recvBuffer, recvLength, MPI_CHAR, i, 0, MPI_COMM_WORLD, &istatus);
388            os << recvBuffer;
389 <          delete recvBuffer;
389 >          delete [] recvBuffer;
390          }
290            
391        }
392        os << "    </StuntDoubles>\n";
393        
394        os << "  </Snapshot>\n";
395        os.flush();
396      } else {
397 <      int sendBufferLength = buffer.size();
398 <      MPI_Send(&sendBufferLength, 1, MPI_INT, masterNode, 0, MPI_COMM_WORLD);
399 <      MPI_Send((void *)buffer.c_str(), sendBufferLength, MPI_CHAR, masterNode, 0, MPI_COMM_WORLD);                              
397 >      int sendBufferLength = buffer.size() + 1;
398 >      int myturn = 0;
399 >      for (int i = 1; i < nProc; ++i){
400 >        MPI_Bcast(&myturn,1, MPI_INT,masterNode,MPI_COMM_WORLD);
401 >        if (myturn == worldRank){
402 >          MPI_Send(&sendBufferLength, 1, MPI_INT, masterNode, 0, MPI_COMM_WORLD);
403 >          MPI_Send((void *)buffer.c_str(), sendBufferLength, MPI_CHAR, masterNode, 0, MPI_COMM_WORLD);
404 >        }
405 >      }
406      }
407  
408   #endif // is_mpi
# Line 313 | Line 419 | namespace oopse {
419      Vector3d pos;
420      Vector3d vel;
421      pos = integrableObject->getPos();
422 +
423 +    if (isinf(pos[0]) || isnan(pos[0]) ||
424 +        isinf(pos[1]) || isnan(pos[1]) ||
425 +        isinf(pos[2]) || isnan(pos[2]) ) {      
426 +      sprintf( painCave.errMsg,
427 +               "DumpWriter detected a numerical error writing the position"
428 +               " for object %d", index);      
429 +      painCave.isFatal = 1;
430 +      simError();
431 +    }
432 +
433      vel = integrableObject->getVel();          
434 <    sprintf(tempBuffer, "%18.10g\t%18.10g\t%18.10g\t%14.10g\t%14.10g\t%14.10g",
434 >
435 >    if (isinf(vel[0]) || isnan(vel[0]) ||
436 >        isinf(vel[1]) || isnan(vel[1]) ||
437 >        isinf(vel[2]) || isnan(vel[2]) ) {      
438 >      sprintf( painCave.errMsg,
439 >               "DumpWriter detected a numerical error writing the velocity"
440 >               " for object %d", index);      
441 >      painCave.isFatal = 1;
442 >      simError();
443 >    }
444 >
445 >    sprintf(tempBuffer, "%18.10g %18.10g %18.10g %13e %13e %13e",
446              pos[0], pos[1], pos[2],
447              vel[0], vel[1], vel[2]);                    
448      line += tempBuffer;
# Line 324 | Line 452 | namespace oopse {
452        Quat4d q;
453        Vector3d ji;
454        q = integrableObject->getQ();
455 +
456 +      if (isinf(q[0]) || isnan(q[0]) ||
457 +          isinf(q[1]) || isnan(q[1]) ||
458 +          isinf(q[2]) || isnan(q[2]) ||
459 +          isinf(q[3]) || isnan(q[3]) ) {      
460 +        sprintf( painCave.errMsg,
461 +                 "DumpWriter detected a numerical error writing the quaternion"
462 +                 " for object %d", index);      
463 +        painCave.isFatal = 1;
464 +        simError();
465 +      }
466 +
467        ji = integrableObject->getJ();
468 <      sprintf(tempBuffer, "\t%14.10g\t%14.10g\t%14.10g\t%14.10g\t%14.10g\t%14.10g\t%14.10g",
468 >
469 >      if (isinf(ji[0]) || isnan(ji[0]) ||
470 >          isinf(ji[1]) || isnan(ji[1]) ||
471 >          isinf(ji[2]) || isnan(ji[2]) ) {      
472 >        sprintf( painCave.errMsg,
473 >                 "DumpWriter detected a numerical error writing the angular"
474 >                 " momentum for object %d", index);      
475 >        painCave.isFatal = 1;
476 >        simError();
477 >      }
478 >
479 >      sprintf(tempBuffer, " %13e %13e %13e %13e %13e %13e %13e",
480                q[0], q[1], q[2], q[3],
481                ji[0], ji[1], ji[2]);
482        line += tempBuffer;
483      }
484  
485      if (needForceVector_) {
486 <      type += "ft";
487 <      Vector3d frc;
488 <      Vector3d trq;
489 <      frc = integrableObject->getFrc();
490 <      trq = integrableObject->getTrq();
491 <              
492 <      sprintf(tempBuffer, "\t%14.10g\t%14.10g\t%14.10g\t%14.10g\t%14.10g\t%14.10g",
493 <              frc[0], frc[1], frc[2],
494 <              trq[0], trq[1], trq[2]);
486 >      type += "f";
487 >      Vector3d frc = integrableObject->getFrc();
488 >      if (isinf(frc[0]) || isnan(frc[0]) ||
489 >          isinf(frc[1]) || isnan(frc[1]) ||
490 >          isinf(frc[2]) || isnan(frc[2]) ) {      
491 >        sprintf( painCave.errMsg,
492 >                 "DumpWriter detected a numerical error writing the force"
493 >                 " for object %d", index);      
494 >        painCave.isFatal = 1;
495 >        simError();
496 >      }
497 >      sprintf(tempBuffer, " %13e %13e %13e",
498 >              frc[0], frc[1], frc[2]);
499        line += tempBuffer;
500 +      
501 +      if (integrableObject->isDirectional()) {
502 +        type += "t";
503 +        Vector3d trq = integrableObject->getTrq();        
504 +        if (isinf(trq[0]) || isnan(trq[0]) ||
505 +            isinf(trq[1]) || isnan(trq[1]) ||
506 +            isinf(trq[2]) || isnan(trq[2]) ) {      
507 +          sprintf( painCave.errMsg,
508 +                   "DumpWriter detected a numerical error writing the torque"
509 +                   " for object %d", index);      
510 +          painCave.isFatal = 1;
511 +          simError();
512 +        }        
513 +        sprintf(tempBuffer, " %13e %13e %13e",
514 +                trq[0], trq[1], trq[2]);
515 +        line += tempBuffer;
516 +      }      
517      }
518 +
519 +    sprintf(tempBuffer, "%10d %7s %s\n", index, type.c_str(), line.c_str());
520 +    return std::string(tempBuffer);
521 +  }
522 +
523 +  std::string DumpWriter::prepareSiteLine(StuntDouble* integrableObject, int ioIndex, int siteIndex) {
524          
525 <    sprintf(tempBuffer, "%d\t%s\t%s\n", index, type.c_str(), line.c_str());
525 >
526 >    std::string id;
527 >    std::string type;
528 >    std::string line;
529 >    char tempBuffer[4096];
530 >
531 >    if (integrableObject->isRigidBody()) {
532 >      sprintf(tempBuffer, "%10d           ", ioIndex);
533 >      id = std::string(tempBuffer);
534 >    } else {
535 >      sprintf(tempBuffer, "%10d %10d", ioIndex, siteIndex);
536 >      id = std::string(tempBuffer);
537 >    }
538 >              
539 >    if (needFlucQ_) {
540 >      type += "cw";
541 >      RealType fqPos = integrableObject->getFlucQPos();
542 >      if (isinf(fqPos) || isnan(fqPos) ) {      
543 >        sprintf( painCave.errMsg,
544 >                 "DumpWriter detected a numerical error writing the"
545 >                 " fluctuating charge for object %s", id.c_str());      
546 >        painCave.isFatal = 1;
547 >        simError();
548 >      }
549 >      sprintf(tempBuffer, " %13e ", fqPos);
550 >      line += tempBuffer;
551 >    
552 >      RealType fqVel = integrableObject->getFlucQVel();
553 >      if (isinf(fqVel) || isnan(fqVel) ) {      
554 >        sprintf( painCave.errMsg,
555 >                 "DumpWriter detected a numerical error writing the"
556 >                 " fluctuating charge velocity for object %s", id.c_str());      
557 >        painCave.isFatal = 1;
558 >        simError();
559 >      }
560 >      sprintf(tempBuffer, " %13e ", fqVel);
561 >      line += tempBuffer;
562 >
563 >      if (needForceVector_) {
564 >        type += "g";
565 >        RealType fqFrc = integrableObject->getFlucQFrc();        
566 >        if (isinf(fqFrc) || isnan(fqFrc) ) {      
567 >          sprintf( painCave.errMsg,
568 >                   "DumpWriter detected a numerical error writing the"
569 >                   " fluctuating charge force for object %s", id.c_str());      
570 >          painCave.isFatal = 1;
571 >          simError();
572 >        }
573 >        sprintf(tempBuffer, " %13e ", fqFrc);        
574 >        line += tempBuffer;
575 >      }
576 >    }
577 >
578 >    if (needElectricField_) {
579 >      type += "e";
580 >      Vector3d eField= integrableObject->getElectricField();
581 >      if (isinf(eField[0]) || isnan(eField[0]) ||
582 >          isinf(eField[1]) || isnan(eField[1]) ||
583 >          isinf(eField[2]) || isnan(eField[2]) ) {      
584 >        sprintf( painCave.errMsg,
585 >                 "DumpWriter detected a numerical error writing the electric"
586 >                 " field for object %s", id.c_str());      
587 >        painCave.isFatal = 1;
588 >        simError();
589 >      }
590 >      sprintf(tempBuffer, " %13e %13e %13e",
591 >              eField[0], eField[1], eField[2]);
592 >      line += tempBuffer;
593 >    }
594 >
595 >
596 >    if (needParticlePot_) {
597 >      type += "u";
598 >      RealType particlePot = integrableObject->getParticlePot();
599 >      if (isinf(particlePot) || isnan(particlePot)) {      
600 >        sprintf( painCave.errMsg,
601 >                 "DumpWriter detected a numerical error writing the particle "
602 >                 " potential for object %s", id.c_str());      
603 >        painCave.isFatal = 1;
604 >        simError();
605 >      }
606 >      sprintf(tempBuffer, " %13e", particlePot);
607 >      line += tempBuffer;
608 >    }
609 >    
610 >
611 >    sprintf(tempBuffer, "%s %7s %s\n", id.c_str(), type.c_str(), line.c_str());
612      return std::string(tempBuffer);
613    }
614  
# Line 425 | Line 689 | namespace oopse {
689      newOStream = new std::ofstream(filename.c_str());
690   #endif
691      //write out MetaData first
692 <    (*newOStream) << "<OOPSE version=4>" << std::endl;
692 >    (*newOStream) << "<OpenMD version=2>" << std::endl;
693      (*newOStream) << "  <MetaData>" << std::endl;
694      (*newOStream) << info_->getRawMetaData();
695      (*newOStream) << "  </MetaData>" << std::endl;
# Line 434 | Line 698 | namespace oopse {
698  
699    void DumpWriter::writeClosing(std::ostream& os) {
700  
701 <    os << "</OOPSE>\n";
701 >    os << "</OpenMD>\n";
702      os.flush();
703    }
704  
705 < }//end namespace oopse
705 > }//end namespace OpenMD

Comparing:
trunk/src/io/DumpWriter.cpp (property svn:keywords), Revision 1024 by tim, Wed Aug 30 18:42:29 2006 UTC vs.
branches/development/src/io/DumpWriter.cpp (property svn:keywords), Revision 1764 by gezelter, Tue Jul 3 18:32:27 2012 UTC

# Line 0 | Line 1
1 + Author Id Revision Date

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines