nutofem / nuto

NuTo - yet another finite element library
https://nuto.readthedocs.io
Boost Software License 1.0
17 stars 5 forks source link

PDE visualize #130

Closed TTitscher closed 6 years ago

TTitscher commented 6 years ago

I tried to implement a visualization prototype and wanted to discuss it, before we continue working on that. If you want to jump directly at the code, you can have a look a visualization group and its usage.

Prerequiste

Currently, we use the VTK-XML file format with the UnstructuredGrid type. This mainly contains four field types

It is important that the data fields are consistent and available for all points and cells.

Visualizing a multiphysics mesh, where e.g. half the points have displacement point data and the other half has temperature point data is not possible in the same file. Each data field must contain data for all points/cells.

This may not be the case for other file formats - who knows. But we currently work with VTK.

Some classes

NuTo::Visualize::UnstructuredGrid

Visualization groups

visualization cell geometry

static CellGeometryVoronoi CreateVoronoi(IntegrationTypeBase&); static CellGeometryVoronoi CreateVoronoi(std::vector<> cornerPoints, std::vector<> integrationPoints); // uses voronoi triangulation lib



# Extracting data

The cell has a `CellInterface::GetElementCollection()` that is able to interpolate the dof values to all natural coordinates. So it is trivial to calculate all the `PointData`.

The `CellData` fields are filled with integration point values. No clue. Hopefully solved soon in #129.

# Discussion

Do you like the concept? What about the naming? Is the interface understandable? How would your ideal API in a main file look like?
I have not looked into other file formats. Do you think/know if this approach is applicable for formats like HDF5? Can we just plug in a `HDF5Writer` instead of our [`XMLWriter`](https://github.com/nutofem/nuto/blob/PDE_visualize/src/visualize/XMLWriter.h)?
And have a look at #129. This is urgent :)
Psirus commented 6 years ago

Visualizing a multiphysics mesh, where e.g. half the points have displacement point data and the other half has temperature point data is not possible in the same file. Each data field must contain data for all points/cells.

While this is true, you could assign NaN to every point that doesn't have that field, and choose how you deal with that in Paraview. Normally, you just choose a NaN color in the "Color Map Editor" (usually on the right). So maybe this doesn't have to be such a big constraint.

However, this would mean writing dummy data, and depending on your problem, maybe quite a lot. So a mechanism for "write file A for these cells, and file B for those" would still be very useful.

Visualizers

I agree on reusing Visualize::UnstructuredGrid and to avoid the above problem, have the option to create multiple "visualizers" or "visualization groups".

I also agree that the visualizer should be polymorphic in the way it handles the cells etc. So when you call visualizer.VisualizeCell(cell), how the cell is written out (Voronoi, average, whatever) depends on the type of the visualizer. So far, everything as you suggested, although I would prefer the names VoronoiVisualizer, AverageVisualizer, etc.

Interfaces

The interface you suggest seems really aimed at the person writing the output writer. As a user, I just want to write my cell using something like visualizer.VisualizeCell(cell) and be done with it. If that needs to extract the geometry and nodal values, that shouldn't be my concern.

So in reference to #129, my interface would probably look something more like this:

struct Visualizer
{
    Visualizer(CellGroup, Integrand);
    virtual void Visualize(std::vector<std::string> outputList) = 0;
};

and the things you mention (ExtractGeometry, ExtractPointData, ExtractCellData, Write) are just private methods that get called by Visualize.

Cell Geometry

I like this, especially

allows decoupling: The definition of the voronoi cells is no longer stored at the integration type. They can be defined manually. [...]

However, why does there need to be this factory? Can't e.g. the VoronoiVisualizer simply call its own CreateVoronoi whenever it encounters a new IntegrationType?

joergfunger commented 6 years ago

Just a very short remark regarding the current ideas of moving everything into a visualizer that knows how to visualize the cell and just gets the data from the cell. This is very much focused on our current cell. We should keep in mind that there will be other types of cells that derive from the general cell interface (such as e.g. beams and shells or interface elements). Maybe there should be a special visualize wrapper for the standard cells, but all other types should be handled by the unstructuredGrid export as well. More comments as soon as I'm back.

Von meinem Samsung Galaxy Smartphone gesendet.

-------- Ursprüngliche Nachricht -------- Von: Christoph Pohl notifications@github.com Datum: 02.11.17 01:59 (GMT-08:00) An: nutofem/nuto nuto@noreply.github.com Cc: Subscribed subscribed@noreply.github.com Betreff: Re: [nutofem/nuto] PDE visualize (#130)

Visualizing a multiphysics mesh, where e.g. half the points have displacement point data and the other half has temperature point data is not possible in the same file. Each data field must contain data for all points/cells.

While this is true, you could assign NaN to every point that doesn't have that field, and choose how you deal with that in Paraview. Normally, you just choose a NaN color in the "Color Map Editor" (usually on the right). So maybe this doesn't have to be such a big constraint.

However, this would mean writing dummy data, and depending on your problem, maybe quite a lot. So a mechanism for "write file A for these cells, and file B for those" would still be very useful.

Visualizers

I agree on reusing Visualize::UnstructuredGrid and to avoid the above problem, have the option to create multiple "visualizers" or "visualization groups".

I also agree that the visualizer should be polymorphic in the way it handles the cells etc. So when you call visualizer.VisualizeCell(cell), how the cell is written out (Voronoi, average, whatever) depends on the type of the visualizer. So far, everything as you suggested, although I would prefer the names VoronoiVisualizer, AverageVisualizer, etc.

Interfaces

The interface you suggest seems really aimed at the person writing the output writer. As a user, I just want to write my cell using something like visualizer.VisualizeCell(cell) and be done with it. If that needs to extract the geometry and nodal values, that shouldn't be my concern.

So in reference to #129https://github.com/nutofem/nuto/issues/129, my interface would probably look something more like this:

struct Visualizer { Visualizer(CellGroup, Integrand); virtual void Visualize(std::vector outputList) = 0; };

and the things you mention (ExtractGeometry, ExtractPointData, ExtractCellData, Write) are just private methods that get called by Visualize.

Cell Geometry

I like this, especially

allows decoupling: The definition of the voronoi cells is no longer stored at the integration type. They can be defined manually. [...]

However, why does there need to be this factory? Can't e.g. the VoronoiVisualizer simply call its own CreateVoronoi whenever it encounters a new IntegrationType?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://github.com/nutofem/nuto/issues/130#issuecomment-341355790, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ATjSJU-EycdmkxURUHm3cqB-WDbYLH-Qks5syYMggaJpZM4QOwqb.

TTitscher commented 6 years ago

@Psirus Single visualization file for many pdes. The thing with NaN may work. But you have to be careful about the field names. Imagine one pde visualizes "Stress" and another "EngineeringStress", only a different name. In Paraview, if you want to show both in one plot, you now have to open the file twice, .... So it is certainly possible but comes with other problems.


Agree on the interfaces. All this Extract... stuff can be called internally. How would you decide on what is member and what is argument? A more realistic signature would be

virtual void Visualize(Group<DofType> dofs, vector<string> ipOutput, string filename, Writer& writer) = 0;

For now, we can leave out the writer, since we only have our XML writer. Or we keep the writer and it contains the file name. Maybe the filename is the only thing that may change from one Visualize call to another. Should all other arguments be members?


The factory stuff is also helpful for decoupling. I do not want to depend on an IntegrationType in each cell. But if I have access to one, I can use it to automatically create a CellGeometry. This requires that each cell in one visualizer has the same CellGeometry. If we do not want that, then we have to think about a fully automatic generation of the CellGeometry based on some information provided by CellInterface, e.g. CellInterface::GetIntegrationType(). I don't know, my gut feeling says no. Certainly not the strongest argument though... :)


@joergfunger The visualize design is based on CellInterface. So I see no problem as long as the beam/shell/custom cell implements CellInterface. Maybe you can elaborate more on your concerns. The earlier we find some problems, the better!

Psirus commented 6 years ago

As a last word on this "single file - multiple PDE": I would not want this to be the default case. I just don't want this "weak" constraint to have too much influence on the API and implementation. If there is no value to be found - write NaN. But it shouldn't happen most of the time.


So right now ("old NuTo"), there is a list of element groups, and for each group you set the desired outputs. We've agreed to have multiple visualization groups or visualizers, one for each group, where the elements probably don't change between writing different timesteps (so that you could e.g. do ExtractGeometry only once). Therefore, the cells would be part of the constructor.

vis = VoronoiVisualizer(cells)
# or, depending on your "integrand philosophy":
vis = VoronoiVisualizer(cells, integrand)

The writer can be set at compile-time imho. So for now just hardcode it, and when someone writes a different backend -> template; can be discussed when that time comes.

The filename and outputs is what differentiates one call of Visualize from the next, so

vis.Visualize(outputList, filename)

As for the dofs argument, this is just for selecting which nodal values are plotted, right? If so, I would just write them all out by default, and if you don't want that

vis = VoronoiVisualizer(cells, integrand, writeNodalValue=False)

Then you wouldn't be able to select "I want displacements but no temperatures in my output", but who needs that anyway?

TTitscher commented 6 years ago

If the outputList and the dofList are members, the only remaining parameter is the name. It may even be advantageous to make the name a member as well and pass something like a time step as an argument. Any post-processing scheme can, without knowing dofList or outputList call

for (auto visualizer& : mVisualizerList)
  visualizer.Visualize(timestep /*=42*/);

to produce an output, say FancyGroupName_42.vtu.

Psirus commented 6 years ago

Do we need to save the dofList? Can't it just simply visualize all it finds?

As for the outputList, I would prefer explicit passing instead of a member, you know, purer, looser coupling, yada yada. But in the end, not important either way.

"Any post-processing scheme": do we know what those are going to look like?

And regarding the timestep, initially I wanted to say that I would rather have createFilename(timestep) in a time-integration callback (or whatever) rather than visualize(timestep) here. But then the visualizer also has to write these *.pvd files, and needs the timesteps there, right? Or do we want to handle that somehow differently?

TTitscher commented 6 years ago

I have not really thought about the post processing. But the less arguments I have to provide, the easier. Memberize everything!

And API wise, it want to provide a name to the visualizer (instead of old nuto style Group42_xyz.vtu). So the name should be stored somewhere. And it makes sense to do that at the visualizer. So we could overload like

VisualizerAverage visu("Aggregates", cells, {dof2, dof5}, {"Stress", "Damage"});
visu.Visualize(); // writes Aggregates.vtu
visu.Visualize("Random") // writes Random.vtu
visu.Visualize(PostProcess::NameWithTimestep(visu.Name(), 42)); // writes Aggregates_42.vtu
TTitscher commented 6 years ago

Do we need to save the dofList? Can't it just simply visualize all it finds?

The DofContainer classes have no idea what dofs they contain. We can question that (in another issue maybe). So I can only access the entries with a dof type. This dof type has to be provided every time I want to do something with dofs. E.g. the SimpleAssembler cannot assemble all dofs, you have to provide them. Same goes for the visualizer.

But then the visualizer also has to write these *.pvd files, and needs the timesteps there, right? Or do we want to handle that somehow differently?

I think of a postprocess class that contains many visualizers and calls something like

void VisualizeTimeStep(int timestep, double globalTime)
{
  for (auto& visu : mVisualizers)
  {
    auto filename = NameWithTimestep(visu.Name(), timestep);
    visu.Visualize(filename);
    // or
    auto filename = visu.Visualize(timestep); // But I do not like...
    mPvdFiles[visu.Name()].Add(globalTime, filename);
  }
}

So the pvd files should not be written by the visualizer.

pmueller2 commented 6 years ago

To have a solution that is flexible and works in many cases but may be slow one could write a VTK file for every output type. If I have a mesh with multiple element types and interpolations and different integration points for different DoFs this is probably the only reasonable way. If I have a uniform mesh or multiple DoFs sharing integration points / interpolation this could be optimized and done in one file, sure. But if not ...

Psirus commented 6 years ago

Team Visualize held a preliminary meeting, and we would like to gather comments before starting our implementation. There are two concepts we have in mind:

Visualizer

This is just the basic proposal for the interface of the visualizer. The constructor takes the filename and a group of cells.

Visualizer visualize(fileName, cells);

Visualizing of "cell data" (IP values, postprocessed quantities, etc.) should be flexible, so we thought we give it a std::function. Additionally, you'll have to pass a string which will show up in Paraview as name of the data set.

auto fiveTimesStrain = [integrand, dofType](const CellData& cellData, const CellIpData& cellIpData){
    return 5.0 * cellIpData.B(dofType) * cellData.NodalValues(dofType);
    };
visualize.CellData(fiveTimesStrain, "5-Epsilon");

Alternatively, if you have a suitable function defined in your integrand, you should be able to call that conveniently:

visualize.CellData(integrand, &Integrand::Stress, "Stress");

Lastly, nodal values are visualized as point data as before, so the interface could be

visualize.PointData(dofType, "Displacements");

CellHandler

(Better Name needed)

The visualizer does the things like

For these things, it doesn't need to care about Voronoi vs. Average vs. Whatever. Therefore, we propose CellHandler classes, that take over the transformation from Mechanics::Cell to (a group of) Visualize::Cell(s). The visualizer would just get this handler as a template argument, since you'll know at compile time what sort of visualization you want. We could set a default argument, so when the user doesn't care, she gets a Voronoi visualizer for example.

template <class TCellHandler=Voronoi>
class Visualizer
{
// ...
private:
    TCellHandler handler;
}

The "visualizers" that have been written (src/visualize/AverageVisualizer, src/visualize/VoronoiVisualizer) will be turned into CellHandlers.

Let us hear what you think.

TTitscher commented 6 years ago

Nice! I like both concepts. For Cellhandler, I used the term VisualizeGeometry. But obviously, you didn't like that either. What about SubCells?

We had this topic like "extract ip data from the cell". This did not only affect the visualization but also postprocessing stuff like plotting the stress of one IP over time or even control values like a maximal errors from some constitutive law. So you propose to have something like vector<SomeType> Cell::Eval(f) - as opposed to vector<SomeType> Cell::IpValue(std::string). With regards to the integrand library, I totally prefer your concept. I can easily write my own visualize function f without modifying some IpValue(...) function inside the integrand. Nice! (Is SomeType just a Eigen::MatrixXd?)

As for the template Visualizer I wonder, if a template is the right choice. Is this for performance reasons? Maybe the compile times are more annoying that the benefit. So you could think of dynamic polymorphism or even separate classes (VisualizeAverage, VisualizeVoronoi, ...) with shared code in some free functions. (Tiny remark. If you have only one template argument, you have to write the template brackets, even if that is defaulted. So it would be Visualizer<> visu(file, cells);. And for me, that always looks a bit strange. No reason not to do it though...)

Psirus commented 6 years ago

Yes, I'd probably prefer the Eval approach. Instead of std::vector<SomeType> Cell::Eval(f), I could imagine SomeType Cell::Eval(f, ipId), and the visualizer does the loop.

As for the template; I could also imagine doing this dynamically. The empty brackets are indeed a bit strange. However, in case of non-default, I find the "static" syntax nicer:

Visualizer<Average> visualize(fileName, cells);
// vs
Visualizer visualize(fileName, cells, Average()); // copy, reference, move?

In the end, I don't care either way.

joergfunger commented 6 years ago

I like the concept with the std::function. I wouldn't do the loop within the visualizer, since in most cases we would need that for all ips (and in this case the CellData has to be evaluated only once for all ips). As for the naming the cellHandler, what about CellVisualizer. Regarding the template vs. dynamic allocation of the CellHandler - I would probably use the dynamic version with a reference, but this is only of minor importance.

Psirus commented 6 years ago

Somewhat solved in PDE. Complaints in a new issue please :)