1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkVASPTessellationReader.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 
16 #include "vtkVASPTessellationReader.h"
17 
18 #include "vtkBoundingBox.h"
19 #include "vtkCellData.h"
20 #include "vtkCellType.h"
21 #include "vtkDataSetAttributes.h"
22 #include "vtkFloatArray.h"
23 #include "vtkIdTypeArray.h"
24 #include "vtkInformation.h"
25 #include "vtkInformationVector.h"
26 #include "vtkMolecule.h"
27 #include "vtkNew.h"
28 #include "vtkObjectFactory.h"
29 #include "vtkPointLocator.h"
30 #include "vtkPoints.h"
31 #include "vtkStreamingDemandDrivenPipeline.h"
32 #include "vtkUnsignedShortArray.h"
33 #include "vtkUnstructuredGrid.h"
34 #include "vtkVector.h"
35 #include "vtkVectorOperators.h"
36 
37 #include <vtksys/RegularExpression.hxx>
38 
39 #include <algorithm>
40 #include <cassert>
41 #include <cmath>
42 #include <fstream>
43 #include <set>
44 #include <sstream>
45 #include <vector>
46 
47 vtkStandardNewMacro(vtkVASPTessellationReader)
48 
49 typedef vtksys::RegularExpression RegEx;
50 typedef vtkStreamingDemandDrivenPipeline vtkSDDP;
51 
52 namespace {
53 
54 template <typename T>
parse(const std::string & str,T & result)55 bool parse(const std::string &str, T &result)
56 {
57   if (!str.empty())
58   {
59     std::istringstream tmp(str);
60     tmp >> result;
61     return !tmp.fail();
62   }
63   return false;
64 }
65 
66 template <typename T>
parseCommaSepList(const std::string & input,std::vector<T> & data)67 bool parseCommaSepList(const std::string &input, std::vector<T> &data)
68 {
69   std::istringstream in(input);
70   for (std::string valStr; std::getline(in, valStr, ',');)
71   {
72     T tmp;
73     if (!parse(valStr, tmp))
74     {
75       return false;
76     }
77     data.push_back(tmp);
78   }
79   return true;
80 }
81 
82 // This parses the voronoi points/faces list. The input is expected to be:
83 // [number of commaSepLists], ([commaSepList]) ([commaSepList]) ...
84 template <typename T>
parseVariableLists(const std::string & input,std::vector<std::vector<T>> & data,RegEx * parenExtract)85 bool parseVariableLists(const std::string &input,
86                         std::vector<std::vector<T> > &data,
87                         RegEx *parenExtract)
88 {
89   std::istringstream in(input);
90   size_t nLists;
91   in >> nLists;
92   if (in.fail())
93   {
94     return false;
95   }
96 
97   data.resize(nLists);
98 
99   std::string parseBuffer = input;
100   for (size_t i = 0; i < nLists; ++i)
101   {
102     if (!parenExtract->find(parseBuffer))
103     {
104       return false;
105     }
106     if (!parseCommaSepList(parenExtract->match(1), data[i]))
107     {
108       return false;
109     }
110     // Chop the current match off of the buffer to prepare for the next iter.
111     parseBuffer = parseBuffer.substr(parenExtract->end());
112   }
113 
114   return true;
115 }
116 
117 } // end anon namespace
118 
119 //------------------------------------------------------------------------------
PrintSelf(std::ostream & os,vtkIndent indent)120 void vtkVASPTessellationReader::PrintSelf(std::ostream &os, vtkIndent indent)
121 {
122   this->Superclass::PrintSelf(os, indent);
123 }
124 
125 //------------------------------------------------------------------------------
vtkVASPTessellationReader()126 vtkVASPTessellationReader::vtkVASPTessellationReader()
127   : FileName(nullptr),
128     TimeParser(new RegEx("^ *time *= *([0-9EeDd.+-]+) *$")), // time = (timeVal)
129     LatticeParser(new RegEx("^ *Rx1 *= *([0-9EeDd.+-]+) *," // Rx1
130                             " *Rx2 *= *([0-9EeDd.+-]+) *," // Rx2
131                             " *Rx3 *= *([0-9EeDd.+-]+) *," // Rx3
132                             " *Ry2 *= *([0-9EeDd.+-]+) *," // Ry2
133                             " *Ry3 *= *([0-9EeDd.+-]+) *," // Ry3
134                             " *Rz3 *= *([0-9EeDd.+-]+) *$" // Rz3
135                             )),
136     AtomCountParser(new RegEx("^ *Natoms *= *([0-9]+) *$")), // Natoms = (int)),
137     AtomParser(new RegEx("^ *([0-9]+) *," // Atom index
138                          " *\\(" // Open paren
139                          " *([0-9EeDd.+-]+) *," // X coord
140                          " *([0-9EeDd.+-]+) *," // Y coord
141                          " *([0-9EeDd.+-]+)" // Z coord
142                          " *\\) *," // Close paren
143                          " *([0-9EeDd.+-]+) *$" // Radius
144                          )),
145     ParenExtract(new RegEx("\\(([^(]+)\\)")) // Extract contents of (...)
146 {
147   this->SetNumberOfInputPorts(0);
148   this->SetNumberOfOutputPorts(2);
149 }
150 
151 //------------------------------------------------------------------------------
~vtkVASPTessellationReader()152 vtkVASPTessellationReader::~vtkVASPTessellationReader()
153 {
154   this->SetFileName(nullptr);
155   delete this->TimeParser;
156   delete this->LatticeParser;
157   delete this->AtomCountParser;
158   delete this->AtomParser;
159   delete this->ParenExtract;
160 }
161 
162 //------------------------------------------------------------------------------
RequestData(vtkInformation *,vtkInformationVector **,vtkInformationVector * outInfos)163 int vtkVASPTessellationReader::RequestData(vtkInformation *,
164                                            vtkInformationVector **,
165                                            vtkInformationVector *outInfos)
166 {
167   vtkInformation *outInfo0 = outInfos->GetInformationObject(0);
168   vtkInformation *outInfo1 = outInfos->GetInformationObject(1);
169 
170   vtkMolecule *molecule = vtkMolecule::SafeDownCast(
171         outInfo0->Get(vtkDataObject::DATA_OBJECT()));
172   assert(molecule);
173 
174   vtkUnstructuredGrid *voronoi = vtkUnstructuredGrid::SafeDownCast(
175         outInfo1->Get(vtkDataObject::DATA_OBJECT()));
176   assert(voronoi);
177 
178   std::ifstream in(this->FileName);
179   if (!in)
180   {
181     vtkErrorMacro("Could not open file for reading: "
182                   << (this->FileName ? this->FileName : ""));
183     return 1;
184   }
185 
186   // Advance to the selected timestep:
187   size_t stepIdx = this->SelectTimeStepIndex(outInfo0);
188   double time = 0.;
189   for (size_t i = 0; i <= stepIdx; ++i) // <= to read the "time=" line
190   {
191     if (!this->NextTimeStep(in, time))
192     {
193       vtkErrorMacro("Error -- attempting to read timestep #" << (stepIdx + 1)
194                     << " but encountered a parsing error at timestep #"
195                     << (i + 1) << ".");
196       return 1;
197     }
198   }
199 
200   if (this->ReadTimeStep(in, molecule, voronoi))
201   {
202     molecule->GetInformation()->Set(vtkDataObject::DATA_TIME_STEP(), time);
203     voronoi->GetInformation()->Set(vtkDataObject::DATA_TIME_STEP(), time);
204   }
205   else
206   {
207     molecule->Initialize();
208     voronoi->Initialize();
209   }
210 
211   return 1;
212 }
213 
214 //------------------------------------------------------------------------------
RequestInformation(vtkInformation *,vtkInformationVector **,vtkInformationVector * outInfos)215 int vtkVASPTessellationReader::RequestInformation(
216     vtkInformation *, vtkInformationVector **,
217     vtkInformationVector *outInfos)
218 {
219   std::ifstream in(this->FileName);
220   if (!in)
221   {
222     vtkErrorMacro("Could not open file for reading: "
223                   << (this->FileName ? this->FileName : ""));
224     return 1;
225   }
226 
227   // Scan the file for timesteps:
228   double time;
229   std::vector<double> times;
230   double timeRange[2] = { VTK_DOUBLE_MAX, VTK_DOUBLE_MIN };
231   while (this->NextTimeStep(in, time))
232   {
233     times.push_back(time);
234     timeRange[0] = std::min(timeRange[0], time);
235     timeRange[1] = std::max(timeRange[1], time);
236   }
237 
238   if (!times.empty())
239   {
240     for (int port = 0; port < 2; ++port)
241     {
242       vtkInformation *outInfo = outInfos->GetInformationObject(port);
243       outInfo->Set(vtkSDDP::TIME_RANGE(), timeRange, 2);
244       outInfo->Set(vtkSDDP::TIME_STEPS(), &times[0],
245           static_cast<int>(times.size()));
246     }
247   }
248 
249   return 1;
250 }
251 
252 //------------------------------------------------------------------------------
FillOutputPortInformation(int port,vtkInformation * info)253 int vtkVASPTessellationReader::FillOutputPortInformation(int port,
254                                                          vtkInformation *info)
255 {
256   switch (port)
257   {
258     case 0:
259       info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkMolecule");
260       break;
261     case 1:
262       info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkUnstructuredGrid");
263       break;
264     default:
265       return 0;
266   }
267 
268   return 1;
269 }
270 
271 //------------------------------------------------------------------------------
NextTimeStep(std::istream & in,double & time)272 bool vtkVASPTessellationReader::NextTimeStep(std::istream &in, double &time)
273 {
274   std::string line;
275   while (std::getline(in, line))
276   {
277     if (this->TimeParser->find(line))
278     {
279       // Parse timestamp:
280       if (!parse(this->TimeParser->match(1), time))
281       {
282         vtkErrorMacro("Error parsing time information from line: " << line);
283         return false;
284       }
285       return true;
286     }
287   }
288 
289   return false;
290 }
291 
292 //------------------------------------------------------------------------------
SelectTimeStepIndex(vtkInformation * info)293 size_t vtkVASPTessellationReader::SelectTimeStepIndex(vtkInformation *info)
294 {
295   if (!info->Has(vtkSDDP::TIME_STEPS()) ||
296       !info->Has(vtkSDDP::UPDATE_TIME_STEP()))
297   {
298     return 0;
299   }
300 
301   double *times = info->Get(vtkSDDP::TIME_STEPS());
302   int nTimes = info->Length(vtkSDDP::TIME_STEPS());
303   double t = info->Get(vtkSDDP::UPDATE_TIME_STEP());
304 
305   double resultDiff = VTK_DOUBLE_MAX;
306   size_t result = 0;
307   for (int i = 0; i < nTimes; ++i)
308   {
309     double diff = std::fabs(times[i] - t);
310     if (diff < resultDiff)
311     {
312       resultDiff = diff;
313       result = static_cast<size_t>(i);
314     }
315   }
316 
317   return result;
318 }
319 
320 //------------------------------------------------------------------------------
ReadTimeStep(std::istream & in,vtkMolecule * molecule,vtkUnstructuredGrid * voronoi)321 bool vtkVASPTessellationReader::ReadTimeStep(std::istream &in,
322                                              vtkMolecule *molecule,
323                                              vtkUnstructuredGrid *voronoi)
324 {
325   // Assume the 'time = ...' line has already been read.
326   std::string line;
327 
328   // Read the lattice info:
329   if (!std::getline(in, line))
330   {
331     vtkErrorMacro("Unexpected EOF while reading lattice info.");
332     return false;
333   }
334   if (!this->LatticeParser->find(line))
335   {
336     vtkErrorMacro("Error parsing lattice info from line: " << line);
337     return false;
338   }
339 
340   vtkVector3d latA(0.);
341   vtkVector3d latB(0.);
342   vtkVector3d latC(0.);
343   vtkVector3d latO(0.);
344 
345   // Rxx
346   if (!parse(this->LatticeParser->match(1), latA[0]))
347   {
348     vtkErrorMacro("Error parsing Rxx component '"
349                   << this->LatticeParser->match(1) << "' from line: " << line);
350     return false;
351   }
352   // Rxy
353   if (!parse(this->LatticeParser->match(2), latB[0]))
354   {
355     vtkErrorMacro("Error parsing Rxy component '"
356                   << this->LatticeParser->match(2) << "' from line: " << line);
357     return false;
358   }
359   // Rxz
360   if (!parse(this->LatticeParser->match(3), latC[0]))
361   {
362     vtkErrorMacro("Error parsing Rxz component '"
363                   << this->LatticeParser->match(3) << "' from line: " << line);
364     return false;
365   }
366   // Ryy
367   if (!parse(this->LatticeParser->match(4), latB[1]))
368   {
369     vtkErrorMacro("Error parsing Ryy component '"
370                   << this->LatticeParser->match(4) << "' from line: " << line);
371     return false;
372   }
373   // Ryz
374   if (!parse(this->LatticeParser->match(5), latC[1]))
375   {
376     vtkErrorMacro("Error parsing Ryz component '"
377                   << this->LatticeParser->match(5) << "' from line: " << line);
378     return false;
379   }
380   // Rzz
381   if (!parse(this->LatticeParser->match(6), latC[2]))
382   {
383     vtkErrorMacro("Error parsing Rzz component '"
384                   << this->LatticeParser->match(6) << "' from line: " << line);
385     return false;
386   }
387 
388   molecule->SetLattice(latA, latB, latC);
389   molecule->SetLatticeOrigin(latO);
390 
391   // Number of atoms:
392   if (!std::getline(in, line))
393   {
394     vtkErrorMacro("Unexpected EOF while parsing number of atoms.");
395     return false;
396   }
397   if (!this->AtomCountParser->find(line))
398   {
399     vtkErrorMacro("Error parsing number of atoms from line: " << line);
400     return false;
401   }
402   vtkIdType nAtoms;
403   if (!parse(this->AtomCountParser->match(1), nAtoms))
404   {
405     vtkErrorMacro("Error parsing number atoms '"
406                   << this->AtomCountParser->match(1) << "'' from line: "
407                   << line);
408     return false;
409   }
410 
411   // Skip 'Atomic_Numbers =' line:
412   if (!std::getline(in, line))
413   {
414     vtkErrorMacro("Unexpected EOF.");
415     return false;
416   }
417 
418   // Atomic numbers:
419   std::vector<unsigned short> atomicNumbers;
420   atomicNumbers.reserve(static_cast<size_t>(nAtoms));
421   if (!std::getline(in, line))
422   {
423     vtkErrorMacro("Unexpected EOF while reading atomic number list.");
424     return false;
425   }
426   if (!parseCommaSepList(line, atomicNumbers))
427   {
428     vtkErrorMacro("Error while parsing atomic number list: " << line);
429     return false;
430   }
431 
432   // Initialize the molecule with atoms, setting just the atomic number.
433   // We'll add positions as we parse them later.
434   if (static_cast<size_t>(nAtoms) != atomicNumbers.size())
435   {
436     vtkErrorMacro("Error: expected " << nAtoms << " atomic numbers, but only "
437                   "parsed " << atomicNumbers.size());
438     return false;
439   }
440   vtkVector3f pos(0.f);
441   for (size_t i = 0; i < atomicNumbers.size(); ++i)
442   {
443     molecule->AppendAtom(atomicNumbers[i], pos);
444   }
445 
446   // Now for the actual list of atoms/tessellations
447   vtkNew<vtkFloatArray> radii;
448   radii->SetNumberOfTuples(nAtoms);
449 
450   // Compute unit cell bounds to initialize point merging:
451   vtkBoundingBox bbox;
452   bbox.AddPoint(latO.GetData());
453   bbox.AddPoint((latO + latA).GetData());
454   bbox.AddPoint((latO + latB).GetData());
455   bbox.AddPoint((latO + latC).GetData());
456   bbox.AddPoint((latO + latA + latB).GetData());
457   bbox.AddPoint((latO + latA + latC).GetData());
458   bbox.AddPoint((latO + latB + latC).GetData());
459   bbox.AddPoint((latO + latA + latB + latC).GetData());
460   double bounds[6];
461   bbox.GetBounds(bounds);
462 
463   // Merge the tessellation points using a locator:
464   vtkNew<vtkPointLocator> locator;
465   vtkNew<vtkPoints> tessPoints;
466   tessPoints->SetDataTypeToFloat();
467   voronoi->SetPoints(tessPoints);
468   voronoi->Allocate(nAtoms);
469 
470   // Cell attributes for the voronoi tessellation:
471   vtkNew<vtkUnsignedShortArray> tessAtomicNumbers;
472   tessAtomicNumbers->SetName("Atomic Numbers");
473   tessAtomicNumbers->Allocate(nAtoms);
474   vtkNew<vtkIdTypeArray> tessAtomIds;
475   tessAtomIds->SetName("Atom Ids");
476   tessAtomIds->Allocate(nAtoms);
477 
478   // Estimate 10 unique points per atom:
479   locator->InitPointInsertion(tessPoints, bounds, nAtoms * 10);
480 
481   // Storage for parsing the tessellation points/faces info
482   std::vector<vtkIdType> faceStream;
483   std::vector<vtkIdType> pointIds;
484   std::set<vtkIdType> uniquePointIds;
485   // parse as doubles for locator API, but store as floats:
486   std::vector<std::vector<double> > pointData;
487   std::vector<std::vector<vtkIdType> > faceData;
488 
489   for (vtkIdType atomEntry = 0; atomEntry < nAtoms; ++atomEntry)
490   {
491     // Skip any blank lines:
492     line.clear();
493     while (line.empty() || line.find_first_not_of(" \t") == std::string::npos)
494     {
495       if (!std::getline(in, line))
496       {
497         vtkErrorMacro("Unexpected EOF while reading atom entry " << atomEntry);
498         return false;
499       }
500     }
501 
502     if (!this->AtomParser->find(line))
503     {
504       vtkErrorMacro("Error parsing atom position/radius specification: "
505                     << line);
506       return false;
507     }
508     vtkIdType atomId;
509     if (!parse(this->AtomParser->match(1), atomId))
510     {
511       vtkErrorMacro("Error parsing atomId '" << this->AtomParser->match(1)
512                     << "' from line: " << line);
513       return false;
514     }
515     if (!parse(this->AtomParser->match(2), pos[0]))
516     {
517       vtkErrorMacro("Error parsing x coordinate '" << this->AtomParser->match(2)
518                     << "' from line: " << line);
519       return false;
520     }
521     if (!parse(this->AtomParser->match(3), pos[1]))
522     {
523       vtkErrorMacro("Error parsing y coordinate '" << this->AtomParser->match(3)
524                     << "' from line: " << line);
525       return false;
526     }
527     if (!parse(this->AtomParser->match(4), pos[2]))
528     {
529       vtkErrorMacro("Error parsing z coordinate '" << this->AtomParser->match(4)
530                     << "' from line: " << line);
531       return false;
532     }
533     float radius;
534     if (!parse(this->AtomParser->match(5), radius))
535     {
536       vtkErrorMacro("Error parsing radius '" << this->AtomParser->match(5)
537                     << "' from line: " << line);
538       return false;
539     }
540 
541     if (atomId >= nAtoms)
542     {
543       vtkErrorMacro("Found entry for atom with id " << atomId << ", but "
544                     "only " << nAtoms << " atoms exist.");
545       return false;
546     }
547     vtkAtom atom = molecule->GetAtom(atomId);
548     atom.SetPosition(pos);
549     radii->SetTypedComponent(atomId, 0, radius);
550 
551     // Extract tessellation points:
552     pointData.clear();
553     if (!std::getline(in, line))
554     {
555       vtkErrorMacro("Unexpected EOF while reading voronoi points for atom "
556                     << atomId);
557       return false;
558     }
559     if (!parseVariableLists(line, pointData, this->ParenExtract))
560     {
561       vtkErrorMacro("Error while parsing voronoi point data for atom "
562                     << atomId << ". Input: " << line);
563       return false;
564     }
565 
566     // Extract tessellation faces:
567     faceData.clear();
568     if (!std::getline(in, line))
569     {
570       vtkErrorMacro("Unexpected EOF while reading voronoi faces for atom "
571                     << atomId);
572       return false;
573     }
574     if (!parseVariableLists(line, faceData, this->ParenExtract))
575     {
576       vtkErrorMacro("Error while parsing voronoi face data for atom "
577                     << atomId << ". Input: " << line);
578       return false;
579     }
580 
581     // Add points to locator:
582     pointIds.resize(pointData.size());
583     uniquePointIds.clear();
584     for (size_t i = 0; i < pointData.size(); ++i)
585     {
586       const std::vector<double> &p = pointData[i];
587       if (p.size() != 3)
588       {
589         vtkErrorMacro("Error: Tessellation point " << i << " for atom "
590                       << atomId << " has " << p.size() << " components. "
591                       "Expected a 3D coordinate.");
592         return false;
593       }
594       locator->InsertUniquePoint(&p[0], pointIds[i]);
595       uniquePointIds.insert(pointIds[i]);
596     }
597 
598     // Create face stream:
599     faceStream.clear();
600     for (size_t faceId = 0; faceId < faceData.size(); ++faceId)
601     {
602       const std::vector<vtkIdType> &face = faceData[faceId];
603       faceStream.push_back(static_cast<vtkIdType>(face.size()));
604       for (std::vector<vtkIdType>::const_iterator it = face.begin(),
605            itEnd = face.end(); it != itEnd; ++it)
606       {
607         // Convert the local point id into the dataset point id:
608         vtkIdType datasetId = pointIds[*it];
609         faceStream.push_back(datasetId);
610       }
611     }
612 
613     // Reuse pointIds to prepare a contiguous buffer of the unique pointId set
614     pointIds.resize(uniquePointIds.size());
615     std::copy(uniquePointIds.begin(), uniquePointIds.end(), pointIds.begin());
616 
617     // Add cell to tessellation dataset:
618     voronoi->InsertNextCell(VTK_POLYHEDRON,
619                             static_cast<vtkIdType>(pointIds.size()),
620                             pointIds.empty() ? nullptr : &pointIds[0],
621                             static_cast<vtkIdType>(faceData.size()),
622                             faceStream.empty() ? nullptr : &faceStream[0]);
623     tessAtomicNumbers->InsertNextValue(atom.GetAtomicNumber());
624     tessAtomIds->InsertNextValue(atom.GetId());
625   }
626 
627   molecule->GetVertexData()->AddArray(radii);
628   voronoi->GetCellData()->SetScalars(tessAtomicNumbers);
629   voronoi->GetCellData()->AddArray(tessAtomIds);
630 
631   return true;
632 }
633