Closed TTitscher closed 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.
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.
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
.
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
?
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
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.
@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!
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?
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
.
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?
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
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.
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 ...
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:
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");
(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 CellHandler
s.
Let us hear what you think.
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...)
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.
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.
Somewhat solved in PDE. Complaints in a new issue please :)
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
Points
- ONE list of 3d coordinates, each coordinate has a point indexCells
- ONE list of point indices that connect to cellsPointData
- MANY lists of data for each point index (scalar data, vector data, ...)CellData
- MANY lists of data for each cell index (scalar data, vector data, ...)It is important that the data fields are consistent and available for all points and 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
Points
andCells
) and data (= fieldsPointData
andCellData
). The geometry is constant and needs to be calculated only once. (If the geometry changes, we call theExtractGeometry()
method more often, no problemo.)different types of visualization = different visualization groups:
CellData
, onlyPointData
.could correspond to
Visualize::GroupVoronoi, Visualize::GroupAverage, Visualize::GroupExtrapolate
. There should be a common interfacevisualization cell geometry
contains all the points (in natural coordinates xi/eta/...) and cell indices for defining the geometry (=
Points
andCells
)e.g. a quad with "averaged cells"
e.g. a quad with four integration points and "voronoi cells"
allows decoupling: The definition of the voronoi cells is no longer stored at the integration type. They can be defined manually. And there is a
VisualizeCellGeometryFactory
with methods likestatic CellGeometryVoronoi CreateVoronoi(IntegrationTypeBase&); static CellGeometryVoronoi CreateVoronoi(std::vector<> cornerPoints, std::vector<> integrationPoints); // uses voronoi triangulation lib