KASKADE 7 development version
vtkwriter.hh
Go to the documentation of this file.
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2/* */
3/* This file is part of the library KASKADE 7 */
4/* https://www.zib.de/research/projects/kaskade7-finite-element-toolbox */
5/* */
6/* Copyright (C) 2015-2018 Zuse Institute Berlin */
7/* */
8/* KASKADE 7 is distributed under the terms of the ZIB Academic License. */
9/* see $KASKADE/academic.txt */
10/* */
11/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12
13
14#ifndef VTKWRITER_HH
15#define VTKWRITER_HH
16
17#include <algorithm>
18#include <cstring>
19#include <fstream>
20#include <iomanip>
21#include <iostream>
22#include <memory>
23#include <sstream>
24#include <type_traits>
25#include <vector>
26
27#include <boost/fusion/include/zip.hpp>
28#include <boost/range/iterator_range.hpp>
29
30#include <dune/grid/common/grid.hh>
31#include <dune/geometry/quadraturerules.hh>
32#include <dune/geometry/referenceelements.hh>
33
35#include "io/iobase.hh"
37#include "utilities/timing.hh"
38#undef min
39
40
41
42
43
44namespace Kaskade
45{
50 namespace FunctionSpace_Detail {
51 // forward declaration
52 template <class> class ToDomainRepresentation;
53 }
54
55 namespace VTKWriterDetail
56 {
57 // returns the textual VTK notation of this system's endianness.
58 std::string vtkEndianness();
59
60 // Numberings of mesh entity types in VTK files.
61 // See the VTKk source code for definitions:
62 // https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html
63 enum VTKGeometryType
64 {
65 vtkLine = 3,
66 vtkTriangle = 5,
67 vtkQuadrilateral = 9,
68 vtkTetrahedron = 10,
69 vtkHexahedron = 12,
70 vtkPrism = 13,
71 vtkPyramid = 14,
72 vtkquadLine = 21,
73 vtkquadTriangle = 22,
74 vtkquadQuadrilateral = 23,
75 vtkquadTetrahedron = 24,
76 vtkquadHexahedron = 25,
77 vtkLagrangeTriangle = 69,
78 vtkLagrangeQuadrilateral = 70,
79 vtkLagrangeTetrahedron = 71,
80 vtkLagrangeHexahedron = 72
81 };
82
84 VTKGeometryType vtkType(Dune::GeometryType const& t, int order);
85
93 std::pair<int,int> vtkPointToDune(Dune::GeometryType const& type, int vtkPoint);
94
95
96 // forward declaration
97 class Base64Writer;
98
100 // T is a scalar type (float,double,int,...)
101 template <class T>
102 class VTKDataArrayWriter
103 {
104 public:
114 VTKDataArrayWriter(std::ostream &s, IoOptions::OutputType outputType,
115 std::string const& name, int ncomps,
116 size_t entries, int indentCount, int precision);
117
121 template <int m>
122 void write (Dune::FieldVector<T,m> const&);
123
127 void write(T);
128
130 ~VTKDataArrayWriter();
131
132 private:
133 std::ostream& s;
134 IoOptions::OutputType outputType;
135 int ncomps;
136 int counter;
137 int numPerLine;
138 int indentCount;
139 int precision;
140 std::unique_ptr<Base64Writer> base64;
141 };
142
143 // ---------------------------------------------------------------------------------------------
144
146 void indent(std::ostream& s, int indentCount);
147
148 // ---------------------------------------------------------------------------------------------
149
150 // A quadrature rule with equally weighted quadrature nodes at the VTK points for
151 // first order (corners only) and second order (corners and edge midpoints).
152 // This is not intended for actual quadrature, but to speed up evaluation of shape functions
153 // in the evaluator used when evaluating point values.
154 template <class ct, int dim>
155 class CornerQuadratureRule: public Dune::QuadratureRule<ct,dim>
156 {
157 public:
158 CornerQuadratureRule(Dune::GeometryType gt, int order)
159 : Dune::QuadratureRule<ct,dim>(gt,order)
160 {
161 assert(1<=order && order<=2);
162
163 auto refCell = Dune::referenceElement<ct,dim>(gt);
164
165 // Number of points. All vertices and, if order is 2, also all edge midpoints.
166 int const n = refCell.size(dim) + (order==2 ? refCell.size(dim-1) : 0);
167
168 // Trivial integration weights (provided even if not needed here).
169 double w = 1.0/n;
170
171 // Step through all affected VTK points on the cell and record their position.
172 for (int i=0; i<n; ++i)
173 {
174 auto [k,codim] = vtkPointToDune(gt,i);
175 this->push_back(Dune::QuadraturePoint<ct,dim>(refCell.position(k,codim),w));
176 }
177 }
178 };
179
180 // ---------------------------------------------------------------------------------------------
181
183 template <class GridView>
184 struct VTKGridInfo
185 {
186 using Cell = typename GridView::template Codim<0>::Entity;
187 using IndexSet = typename GridView::IndexSet;
188 using CellIterator = typename GridView::template Codim<0>::Iterator;
189
190 static int constexpr dim = GridView::dimension;
191
192
193 VTKGridInfo(GridView const& gridView_, IoOptions const& options_)
194 : gridView(gridView_),
195 cells(gridView.template begin<0>(),gridView.template end<0>()),
196 ncells(gridView.size(0)),
197 options(options_)
198 {
199 // For counting the corners (i.e. all corners of all cells) we step through the
200 // cells and count their respective corners (and edges for order two). This is
201 // general enough to treat mixed grids containing different cell types.
202 ncorners = 0;
203 for (auto const& cell: cells)
204 {
205 ncorners += cell.subEntities(dim);
206 if (options.order==2)
207 ncorners += cell.subEntities(dim-1);
208 }
209
210 // In conforming mode, the number of points is just the number of vertices
211 // (plus edges for oder two). Otherwise, points and corners coincide.
212 if (options.dataMode==IoOptions::conforming)
213 {
214 npoints = gridView.size(dim);
215 if (options.order==2)
216 npoints += gridView.size(dim-1);
217 }
218 else
219 npoints = ncorners;
220 }
221
222
223 // returns entity center coordinates
224 template <class Cell>
225 auto center(Cell const& cell, int subindex, int codim) const
226 {
227 return Dune::ReferenceElements<typename GridView::ctype,dim>::general(cell.type()).position(subindex,codim);
228 }
229
230 // returns a globally unique index for vertices and edges in conforming mode.
231 // Vertices come first, then edges.
232 template <class Cell>
233 int conformingIndex(Cell const& cell, int subindex, int codim) const
234 {
235 // due to the sorting vertices first then edges there is no need to switch between order 1 and 2
236 if (codim==dim)
237 return gridView.indexSet().subIndex(cell,subindex,codim);
238 else
239 return gridView.size(dim) + gridView.indexSet().subIndex(cell,subindex,codim);
240 }
241
242 // Writes the point coordinates to the VTK XML stream
243 template <class Scalar=typename GridView::ctype>
244 void writeGridPoints (int indentCount, std::ostream& s) const
245 {
246 VTKDataArrayWriter<Scalar> p(s, options.outputType, "Coordinates", 3,
247 npoints, indentCount, options.precision);
249
250 if (options.dataMode==IoOptions::conforming)
251 {
252 // We need to iterate over the cells to visit each point, but visit points multiple times this way.
253 // We just collect the coordinates in a buffer sorted according to the point's index as defined
254 // by conformingIndex(), and simply overwrite the previous data -- in conforming grids, it is
255 // anyways the same.
256 std::vector<Vector> xs(npoints);
257 for (auto const& cell: cells)
258 {
259 // Simplex vertices come first, independent of the order.
260 for (int corner=0; corner<cell.subEntities(dim); ++corner) // global coordinates of all corners
261 xs[conformingIndex(cell,corner,dim)] = cell.geometry().corner(corner);
262
263 // For second order, the edge midpoints are appended.
264 if (options.order == 2)
265 for (int edge=0; edge<cell.subEntities(dim-1); ++edge) // global coordinates of all edges
266 xs[conformingIndex(cell,edge,dim-1)] = cell.geometry().global(center(cell,edge,dim-1));
267
268 // For higher order, we need to follow VTK's Lagrange cell definition.
269 // See https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/
270 // for some explanation.
271 if (options.order > 2)
272 {
273 assert("VTK output of order > 2 not yet implemented\n"==0);
274 }
275 }
276
277 for (auto const& x: xs)
278 p.write(x);
279 }
280 else // nonconforming
281 {
282 // In nonconforming mode, the cells are just concatenated.
283 for (auto const& cell: cells)
284 {
285 // First come vertices.
286 for (int corner=0; corner<cell.subEntities(dim); ++corner)
287 p.write(static_cast<Vector>(cell.geometry().corner(corner))); // perform scalar type conversion on the fly
288
289 // For order 2, edge midpoints come next if requested.
290 if (options.order == 2)
291 for (int edge=0; edge<cell.subEntities(dim-1); ++edge)
292 p.write(static_cast<Vector>(cell.geometry().global(center(cell,edge,dim-1)))); // perform scalar type conversion on the fly
293
294 // For higher order, we need to follow VTK's Lagrange cell definition. See
295 // https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/
296 // for some explanation.
297 if (options.order == 3)
298 {
299 assert("VTK output of order > 2 not yet implemented\n"==0);
300 }
301 }
302 }
303 }
304
305 void writeGridCells (int indentCount, std::ostream& s) const
306 {
307 // connectivity
308 auto p1 = std::make_unique<VTKDataArrayWriter<int>>(s, options.outputType, "connectivity", 1,
309 ncorners, indentCount, options.precision);
310
311 int offset = 0;
312 std::vector<int> offsets; offsets.reserve(ncells);
313 for (auto const& cell: cells) // step through all cells
314 {
315 int const numCorners = cell.subEntities(dim);
316 int const numEdges = cell.subEntities(dim-1);
317 int const numPoints = numCorners + (options.order==2? numEdges: 0);
318
319 for (int vtkPoint=0; vtkPoint<numPoints; ++vtkPoint) // visit each point in that cell
320 {
321 auto subentity = vtkPointToDune(cell.type(),vtkPoint); // and find its Dune coordinates
322 p1->write( options.dataMode==IoOptions::conforming?
323 conformingIndex(cell,subentity.first,subentity.second):
324 offset + (subentity.second==dim? 0: numCorners) + subentity.first );
325 }
326 offset += numPoints; // in nonconforming mode, the
327 offsets.push_back(offset); // points are stored blockwise,
328 } // cell by cell, just advance the offset
329 p1.reset(); // write end tag on destruction
330
331 // write offsets
332 auto p2 = std::make_unique<VTKDataArrayWriter<int>>(s, options.outputType, "offsets", 1,
333 ncells, indentCount, options.precision);
334 for (int off: offsets)
335 p2->write(off);
336 p2.reset(); // write end tag on destruction
337
338 // write cell types only if dimension greater than 1 - there is only one 1D cell type: line
339 if (dim>1)
340 {
341 VTKDataArrayWriter<unsigned char> p(s, options.outputType, "types", 1,
342 ncells, indentCount, options.precision);
343 for (auto const& cell: cells)
344 p.write(static_cast<unsigned char>(vtkType(cell.type(),options.order)));
345 }
346 }
347
348
349 GridView const& gridView;
350 boost::iterator_range<CellIterator> cells;
351 size_t ncorners;
352 size_t npoints;
353 size_t ncells;
354 IoOptions const& options;
355 };
356
357
358 // ----------------------------------------------------------------------------------------
359
360
377 template <class GridView, class Function, class Names>
378 void writeCellData (VTKGridInfo<GridView> const& gridInfo, Function const& pair, Names const& names, int indentCount, std::ostream& s)
379 {
380 using Scalar = typename std::remove_reference_t<typename boost::fusion::result_of::value_at_c<Function,0>::type>::Scalar;
381
382 auto const& f = boost::fusion::at_c<0>(pair);
383
384 // Check whether this is best represented as cell data: only if it is (piecewise) constant.
385 if (f.space().mapper().maxOrder() > 0)
386 return;
387
388 auto varDesc = boost::fusion::at_c<1>(pair);
389 VTKDataArrayWriter<Scalar> p(s, gridInfo.options.outputType, names[varDesc.id], varDesc.m, gridInfo.ncells, indentCount, gridInfo.options.precision);
390 for (auto const& cell: gridInfo.cells)
391 p.write(f.value(cell,gridInfo.center(cell,0,0)));
392 }
393
394
395
396 template <class GridView, class Function, class Names>
397 void writeVertexData (VTKGridInfo<GridView> const& gridInfo, Function const& pair,
398 Names const& names, int indentCount, std::ostream& s, Timings& timer)
399 {
400 using ValueType = typename std::remove_reference_t<typename boost::fusion::result_of::value_at_c<Function,0>::type>::ValueType;
401 using Grid = typename GridView::Grid;
402 using Scalar = typename ValueType::field_type;
403 constexpr int dim = GridView::dimension;
404
405 auto const& f = boost::fusion::at_c<0>(pair);
406 auto varDesc = boost::fusion::at_c<1>(pair);
407
408 // Check whether this is best represented as vertex data: only if it is not (piecewise) constant.
409 if (f.space().mapper().maxOrder() == 0)
410 return;
411
412 // If no name is given (empty string), we just call the function by it's variable id. Empty names are
413 // infeasible, since multiple point data shall not have the same name.
414 std::string const& name = names[varDesc.id].empty() ? "var"+paddedString(varDesc.id,2)
415 : names[varDesc.id];
416 VTKDataArrayWriter<Scalar> p(s, gridInfo.options.outputType, name,
417 varDesc.m, gridInfo.npoints, indentCount,
418 gridInfo.options.precision);
419
420
421 if (gridInfo.options.dataMode==Kaskade::IoOptions::conforming)
422 {
423 std::vector<ValueType> fs(gridInfo.npoints,ValueType(0.0)); // function values, in conforming mode average over multiple points
424 std::vector<short> count(gridInfo.npoints,0); // count how many points contribute to an entity
425
426 // TODO: Currently boundary FE functions are zero on cells which only touch the boundary by one vertex (or edge in 3D). This will lead to unwanted results when values are averaged.
427 // Therefore, we use the following two lines to transform boundary FE functions to continuous FE functions over the whole domain, then averaging works as expected.
428 // Remove this when not necessary any longer. (And also delete ToDomainRepresetation from functionspace.hh)
429 FunctionSpace_Detail::ToDomainRepresentation<std::remove_const_t<std::remove_reference_t<typename boost::fusion::result_of::value_at_c<Function,0>::type>>> tdr(f);
430 auto const& df = tdr.get();
431
432 // As shape function evaluation is expensive, we make use of a shape function cache
433 // and the evaluator storing shape function values in it. This requires formulating
434 // the evaluation in terms of a quadrature rule.
435 ShapeFunctionCache<Grid,Scalar> sfCache;
436 auto evaluator = f.space().evaluator(&sfCache,0); // no derivatives needed
437 CornerQuadratureRule<typename Grid::ctype,dim> qr(gridInfo.cells.begin()->type(),
438 gridInfo.options.order);
439
440 timer.start("eval at vertices");
441 for (auto const& cell: gridInfo.cells)
442 {
443 evaluator.moveTo(cell);
444 assert(cell.type()==qr.type());
445 for (int point=0; point<qr.size(); ++point)
446 {
447 auto [k,codim] = vtkPointToDune(cell.type(),point);
448 int idx = gridInfo.conformingIndex(cell,k,codim); // global index of point
449 ++count[idx];
450
451 auto qp = qr[point];
452 evaluator.evaluateAt(qp.position(),qr,point,0);
453 fs[idx] += df.value(evaluator);
454 }
455 }
456 timer.stop("eval at vertices");
457
458 timer.start("write out");
459 for (int i=0; i<gridInfo.npoints; ++i) // now write out the values in vertex order
460 p.write(fs[i]/static_cast<typename ValueType::field_type>(count[i])); // and average them on writing
461 timer.stop("write out");
462 }
463 else // nonconforming
464 {
465 for (auto const& cell: gridInfo.cells)
466 {
467 // First write corner values
468 for (int corner=0; corner<cell.subEntities(dim); ++corner)
469 p.write(f.value(cell,gridInfo.center(cell,corner,dim)));
470
471 // If required, write edge midpoint values as well.
472 if (gridInfo.options.order == 2)
473 for (int edge=0; edge<cell.subEntities(dim-1); ++edge)
474 p.write(f.value(cell,gridInfo.center(cell,edge,dim-1)));
475
476 // If arbitrary higher order, write edges, faces, and interior nodes.
477 // This is done recursively from outside to inside, like onion shells, and is
478 // therefore done on an equidistant grid.
479 // TODO: implement this. See https://blog.kitware.com/modeling-arbitrary-order-lagrange-finite-elements-in-the-visualization-toolkit/
480 // TODO: If correctly implemented, this will include the order 2 case.
481 if (gridInfo.options.order > 2)
482 {
483 }
484 }
485 }
486 }
487
488 // The following piece of code defines the "active" scalar and vectorial variables (those used to immediately color the output in Paraview).
489 // According to VTK file formats, there need not be any active scalar or vectorial variable. Taking simply the first one as is done here makes little sense.
490 // TODO: make this configurable via IoOptions.
491 template <class Functions, class Names>
492 std::string guessActiveFields(Functions const& functions, Names const& names, bool cellData)
493 {
494 std::string scalar = "";
495 std::string vector = "";
496
497 boost::fusion::for_each(functions,[&](auto const& pair)
498 {
499 auto const& f = boost::fusion::at_c<0>(pair);
500 auto varDesc = boost::fusion::at_c<1>(pair);
501
502 // Check whether this is best represented as cell or point data.
503 if ( (f.space().mapper().maxOrder()==0 && cellData)
504 || (f.space().mapper().maxOrder()>0 && !cellData) )
505 {
506 if (f.components>1 && vector.empty())
507 vector = " Vectors=\"" + names[varDesc.id] + "\"";
508 if (f.components==1 && scalar.empty())
509 scalar = " Scalars=\"" + names[varDesc.id] + "\"" ;
510 }
511 });
512 return scalar+vector;
513 }
514
515 }
520 // ---------------------------------------------------------------------------------------------
521
529 template <class VariableSet>
530 void writeVTK(VariableSet const& vars, IoOptions options, std::ostream& s)
531 {
532 using namespace VTKWriterDetail;
533
534 // Measure time spent.
535 auto& timer = Timings::instance();
536 timer.start("VTK output");
537
538 // Standard VTK files support at most order 2. (There's a new higher order
539 // type, but that's not yet supported by Kaskade 7.
540 options.order = std::max(1,std::min(options.order,2));
541
542 // If data mode "inferred" is requested, try to select either conforming or nonconforming
543 // based on rules.
544 if (options.dataMode==IoOptions::inferred)
545 options.dataMode = VariableSet::Descriptions::continuity>=0 ? IoOptions::conforming
547 int indentCount = 0;
548 constexpr int dim = VariableSet::Descriptions::Grid::dimension;
549
550 timer.start("grid info");
551 VTKGridInfo<typename VariableSet::Descriptions::GridView> gridInfo(vars.descriptions.gridView,options);
552 timer.stop("grid info");
553
554 // xml header
555 s << "<?xml version=\"1.0\"?>" << std::endl;
556
557 // VTKFile
558 std::string const gridTag = dim>1? "UnstructuredGrid": "PolyData";
559 s << "<VTKFile type=\"" << gridTag << "\" version=\"0.1\" byte_order=\"" << vtkEndianness() << "\">\n";
560 ++indentCount;
561
562 // UnstructuredGrid
563 indent(s,indentCount);
564 s << "<" << gridTag << ">" << std::endl;
565 ++indentCount;
566
567 // Piece
568 indent(s,indentCount);
569 if (dim>1)
570 s << "<Piece NumberOfPoints=\"" << gridInfo.npoints << "\" NumberOfCells=\"" << gridInfo.ncells << "\">\n";
571 else
572 s << "<Piece NumberOfPoints=\"" << gridInfo.npoints << "\" NumberOfVerts=\"0\" NumberOfLines=\"" << gridInfo.ncells << "\" NumberOfPolys=\"0\">\n";
573 ++indentCount;
574
575 // Write grid points
576 timer.start("write vertices");
577 indent(s,indentCount); s << "<Points>" << std::endl;
578 if (options.precision <= 6)
579 gridInfo.template writeGridPoints<float>(indentCount+1,s);
580 else
581 gridInfo.template writeGridPoints<double>(indentCount+1,s);
582 indent(s,indentCount); s << "</Points>" << std::endl;
583 timer.stop("write vertices");
584
585 // Write grid cells
586 timer.start("write cells");
587 std::string const cellTagName = dim>1? "Cells": "Lines";
588 indent(s,indentCount); s << '<' << cellTagName << ">\n";
589 gridInfo.writeGridCells(indentCount+1,s);
590 indent(s,indentCount); s << "</" << cellTagName << ">\n";
591 timer.stop("write cells");
592
593 // associate the FE functions with their variable descriptions
594 auto varDesc = typename VariableSet::Descriptions::Variables();
595 auto functions = boost::fusion::zip(vars.data,varDesc);
596
597 // Write all functions' cell data. If a function is best represented as vertex data,
598 // it will be skipped here.
599 timer.start("write cell data");
600 indent(s,indentCount); s << "<CellData"
601 << guessActiveFields(functions,vars.descriptions.names,true) << ">\n";
602 boost::fusion::for_each(functions,[&](auto const& f)
603 {
604 writeCellData(gridInfo,f,vars.descriptions.names,indentCount+1,s);
605 });
606 indent(s,indentCount); s << "</CellData>" << std::endl;
607 timer.stop("write cell data");
608
609 // Write all functions' vertex data. If a function is best represented as cell data,
610 // it will be skipped here.
611 timer.start("write point data");
612 indent(s,indentCount); s << "<PointData"
613 << guessActiveFields(functions,vars.descriptions.names,false) << ">\n";
614 boost::fusion::for_each(functions,[&](auto const& f)
615 {
616 writeVertexData(gridInfo,f,vars.descriptions.names,indentCount+1,s,timer);
617 });
618 indent(s,indentCount); s << "</PointData>\n";
619 timer.stop("write point data");
620
621 // /Piece
622 --indentCount;
623 indent(s,indentCount); s << "</Piece>\n";
624
625 // /UnstructuredGrid
626 --indentCount;
627 indent(s,indentCount); s << "</" << gridTag << ">\n";
628
629 // /VTKFile
630 s << "</VTKFile>" << std::endl;
631
632 timer.stop("VTK output");
633 }
634
635 // -----------------------------------------------------------------------------------------------
636
649 template <class VariableSet>
650 void writeVTK(VariableSet const& vars, std::string fname, IoOptions const& options)
651 {
652 // generate filename for process data
653 fname += (VariableSet::Descriptions::GridView::dimension>1? ".vtu" : ".vtp");
654
655 // write process data
656 std::ofstream file(fname);
657 if (!file)
658 throw Kaskade::FileIOException("Opening failed.\n",fname,__FILE__,__LINE__);
659 writeVTK(vars,options,file);
660 if (!file)
661 throw Kaskade::FileIOException("Writing failed.\n",fname,__FILE__,__LINE__);
662 file.close();
663 }
664
665}
666
667// --------------------------------------------------------------------------------------------
668
669
670
671#endif
Function is the interface for evaluatable functions .
Definition: concepts.hh:324
An exception class for file IO errors.
static Timings & instance()
Returns a reference to a single default instance.
A class for storing a heterogeneous collection of FunctionSpaceElement s.
Definition: variables.hh:341
Descriptions const & descriptions
Descriptions of variable set, of type VariableSetDescription (lots of useful infos)
Definition: variables.hh:510
int count(Cell const &cell, int codim)
Computes the number of subentities of codimension c of a given entity.
Definition: fixdune.hh:716
std::string paddedString(int n, int places=3)
creates a zero-padded string representation of the given number
void writeVTK(Function const &f, std::string const &filename, IoOptions options, std::string fname)
Writes a single finite element function to a VTK file.
Definition: vtk.hh:56
typename GridView::template Codim< 0 >::Entity Cell
The type of cells (entities of codimension 0) in the grid view.
Definition: gridBasics.hh:35
Dune::FieldVector< T, n > max(Dune::FieldVector< T, n > x, Dune::FieldVector< T, n > const &y)
Componentwise maximum.
Definition: fixdune.hh:110
Dune::FieldVector< T, n > min(Dune::FieldVector< T, n > x, Dune::FieldVector< T, n > const &y)
Componentwise minimum.
Definition: fixdune.hh:122
Output of mesh and solution for visualization software.
Dune::FieldVector< Scalar, dim > Vector
Definition: base64.hh:38
options for VTK/AMIRA output
Definition: iobase.hh:77
DataMode dataMode
Definition: iobase.hh:150
OutputType
Determines text or binary output format. Currently this is only used by VTK output.
Definition: iobase.hh:83