Biswajit Banerjee

Particle data in VTK XML

Outputting simulation data in C++

In my previous post on writing VTK output files I described how mesh data can be output in VTK XML format. In this article I will talk about how I output particle data from my simulations.

These simulations use a number of techniques depending on the requirements. I use the MPM (Material Point Method) and RBD (Rigid body dynamics) with the occasional DEM (Discrete/Distict element method) and Peridynamics simulation. When I am running RBD or DEM simulations, the particles may have not spherical shapes and the shape and orientation information have to be encoded into the output data in addition to other physical state variables.

Note: The visualization of non-spherical particles in Visit/Paraview requires the implementation of a plugin that can perform the appropriate transformation. Alternatively, we can convert each particle into a surface mesh before writing the output. We will not discuss these issues at this stage. However, in future posts we will show how these plots can be generated using Javascript.

Particle data

We will assume that the particles are ellipsoids. The particle data that we will output in this example are:

  • The particle ID
  • The particle radii in the three principal axis directions
  • The three principal axis orientations
  • The particle position
  • The particle velocity
Writing the particle data

To write the particles data to the disk, we follow the same approach as we did for the grid/mesh data in the previous post. The writeParticles function has the form

void
OutputVTK::writeParticles(double time,
                          const ParticlePArray* particles,
                          std::ostringstream& fileName)
{
  // Create a writer
  auto writer = vtkXMLUnstructuredGridWriterP::New();
  // Get the filename with extension
  fileName << "." << writer->GetDefaultFileExtension();
  writer->SetFileName((fileName.str()).c_str());
  // Create a pointer to a VTK Unstructured Grid data set
  auto dataSet = vtkUnstructuredGridP::New();
  // Set up pointer to point data
  auto pts = vtkPointsP::New();
  // Count the total number of points
  int num_pts = static_cast<int>(particles->size());
  pts->SetNumberOfPoints(num_pts);
  // Add the time
  addTimeToVTKDataSet(time, dataSet);
  // Add the particle data to the unstructured grid
  addParticlesToVTKDataSet(particles, pts, dataSet);
  // Set the points
  dataSet->SetPoints(pts);
  // Remove unused memory
  dataSet->Squeeze();
  // Write the data
  writer->SetInput(dataSet);
  writer->SetDataModeToBinary();
  writer->Write();
}

We have already seen the addTimeToVTKDataSet method in the previous post. The main difference in this case is the new addParticlesToVTKDataSet method:

void
OutputVTK::addParticlesToVTKDataSet(const ParticlePArray* particles,
                                    vtkPointsP& pts,
                                    vtkUnstructuredGridP& dataSet)
{
  // Set up pointers for material property data
  auto ID = vtkDoubleArrayP::New();
  ID->SetNumberOfComponents(1);
  ID->SetNumberOfTuples(pts->GetNumberOfPoints());
  ID->SetName("ID");

  auto radii = vtkDoubleArrayP::New();
  radii->SetNumberOfComponents(3);
  radii->SetNumberOfTuples(pts->GetNumberOfPoints());
  radii->SetName("Radius");

  auto axis_a = vtkDoubleArrayP::New();
  axis_a->SetNumberOfComponents(3);
  axis_a->SetNumberOfTuples(pts->GetNumberOfPoints());
  axis_a->SetName("Axis a");

  auto axis_b = vtkDoubleArrayP::New();
  vtkDoubleArrayP axis_b = vtkDoubleArrayP::New();
  axis_b->SetNumberOfComponents(3);
  axis_b->SetNumberOfTuples(pts->GetNumberOfPoints());
  axis_b->SetName("Axis b");

  auto axis_c = vtkDoubleArrayP::New();
  axis_c->SetNumberOfComponents(3);
  axis_c->SetNumberOfTuples(pts->GetNumberOfPoints());
  axis_c->SetName("Axis c");

  auto position = vtkDoubleArrayP::New();
  position->SetNumberOfComponents(3);
  position->SetNumberOfTuples(pts->GetNumberOfPoints());
  position->SetName("Position");

  auto velocity = vtkDoubleArrayP::New();
  velocity->SetNumberOfComponents(3);
  velocity->SetNumberOfTuples(pts->GetNumberOfPoints());
  velocity->SetName("Velocity");

  // Loop through particles
  Vec vObj;
  int id = 0;
  double vec[3];
  for (const auto& particle : *particles) {

    // Position
    vObj = particle->getCurrPos();
    vec[0] = vObj.getX();
    vec[1] = vObj.getY();
    vec[2] = vObj.getZ();
    pts->SetPoint(id, vec);

    // ID
    ID->InsertValue(id, particle->getId());

    // Ellipsoid radii
    vec[0] = particle->getA();
    vec[1] = particle->getB();
    vec[2] = particle->getC();
    radii->InsertTuple(id, vec);

    // Current direction A
    vObj = particle->getCurrDirecA();
    vec[0] = vObj.getX();
    vec[1] = vObj.getY();
    vec[2] = vObj.getZ();
    axis_a->InsertTuple(id, vec);

    // Current direction B
    vObj = particle->getCurrDirecB();
    vec[0] = vObj.getX();
    vec[1] = vObj.getY();
    vec[2] = vObj.getZ();
    axis_b->InsertTuple(id, vec);

    // Current direction C
    vObj = particle->getCurrDirecC();
    vec[0] = vObj.getX();
    vec[1] = vObj.getY();
    vec[2] = vObj.getZ();
    axis_c->InsertTuple(id, vec);

    // Velocity
    vObj = particle->getCurrVeloc();
    vec[0] = vObj.getX();
    vec[1] = vObj.getY();
    vec[2] = vObj.getZ();
    velocity->InsertTuple(id, vec);
    
    ++id;
  }

  // Add points to data set
  dataSet->GetPointData()->AddArray(ID);
  dataSet->GetPointData()->AddArray(radii);
  dataSet->GetPointData()->AddArray(axis_a);
  dataSet->GetPointData()->AddArray(axis_b);
  dataSet->GetPointData()->AddArray(axis_c);
  dataSet->GetPointData()->AddArray(velocity);
}

You can see a working example of this approach in the Matiti code.

Displaying the output

The VTK XML file output to disk using this approach can be visualized in Visit or ParaView. Notice that we are not able to use the particle geometry and orientation information in either of these tools without extra work. The plot below shows the output of a rigid body dynamics simulation in the presence of Coriolis forces.

Plot produced by Visit

Remarks

Now that the basic input/output issues have been addressed, we will be able to explore possible designs of an integrated user interface to automate the generation of input files and perform simple visualizations of the results. Future blog posts will discuss our exploration of Javascript frameworks and libraries that can potentially aid this process.

If you have questions/comments/corrections, please contact banerjee at parresianz dot com dot zen (without the dot zen).

 