// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "vtk.hpp" #include "../general/binaryio.hpp" #ifdef MFEM_USE_ZLIB #include #endif namespace mfem { const int VTKGeometry::Map[Geometry::NUM_GEOMETRIES] = { POINT, SEGMENT, TRIANGLE, SQUARE, TETRAHEDRON, CUBE, PRISM, PYRAMID }; const int VTKGeometry::QuadraticMap[Geometry::NUM_GEOMETRIES] = { POINT, QUADRATIC_SEGMENT, QUADRATIC_TRIANGLE, BIQUADRATIC_SQUARE, QUADRATIC_TETRAHEDRON, TRIQUADRATIC_CUBE, BIQUADRATIC_QUADRATIC_PRISM, QUADRATIC_PYRAMID }; const int VTKGeometry::HighOrderMap[Geometry::NUM_GEOMETRIES] = { POINT, LAGRANGE_SEGMENT, LAGRANGE_TRIANGLE, LAGRANGE_SQUARE, LAGRANGE_TETRAHEDRON, LAGRANGE_CUBE, LAGRANGE_PRISM, LAGRANGE_PYRAMID }; const int VTKGeometry::PrismMap[6] = {0, 2, 1, 3, 5, 4}; const int *VTKGeometry::VertexPermutation[Geometry::NUM_GEOMETRIES] = { NULL, NULL, NULL, NULL, NULL, NULL, VTKGeometry::PrismMap, NULL }; Geometry::Type VTKGeometry::GetMFEMGeometry(int vtk_geom) { switch (vtk_geom) { case POINT: return Geometry::POINT; case SEGMENT: case QUADRATIC_SEGMENT: case LAGRANGE_SEGMENT: return Geometry::SEGMENT; case TRIANGLE: case QUADRATIC_TRIANGLE: case LAGRANGE_TRIANGLE: return Geometry::TRIANGLE; case SQUARE: case BIQUADRATIC_SQUARE: case LAGRANGE_SQUARE: return Geometry::SQUARE; case TETRAHEDRON: case QUADRATIC_TETRAHEDRON: case LAGRANGE_TETRAHEDRON: return Geometry::TETRAHEDRON; case CUBE: case TRIQUADRATIC_CUBE: case LAGRANGE_CUBE: return Geometry::CUBE; case PRISM: case BIQUADRATIC_QUADRATIC_PRISM: case LAGRANGE_PRISM: return Geometry::PRISM; case PYRAMID: case QUADRATIC_PYRAMID: case LAGRANGE_PYRAMID: return Geometry::PYRAMID; default: return Geometry::INVALID; } } bool VTKGeometry::IsLagrange(int vtk_geom) { return vtk_geom >= LAGRANGE_SEGMENT && vtk_geom <= LAGRANGE_PYRAMID; } bool VTKGeometry::IsQuadratic(int vtk_geom) { return vtk_geom >= QUADRATIC_SEGMENT && vtk_geom <= BIQUADRATIC_QUADRATIC_PRISM; } int VTKGeometry::GetOrder(int vtk_geom, int npoints) { if (IsQuadratic(vtk_geom)) { return 2; } else if (IsLagrange(vtk_geom)) { switch (vtk_geom) { case LAGRANGE_SEGMENT: return npoints - 1; case LAGRANGE_TRIANGLE: return static_cast(std::sqrt(8*npoints + 1) - 3)/2; case LAGRANGE_SQUARE: return static_cast(std::round(std::sqrt(npoints))) - 1; case LAGRANGE_TETRAHEDRON: switch (npoints) { // Note that for given order, npoints is given by // npoints_order = (order + 1)*(order + 2)*(order + 3)/6, case 4: return 1; case 10: return 2; case 20: return 3; case 35: return 4; case 56: return 5; case 84: return 6; case 120: return 7; case 165: return 8; case 220: return 9; case 286: return 10; default: { constexpr int max_order = 20; int order = 11, npoints_order; for (; order(std::round(std::cbrt(npoints))) - 1; case LAGRANGE_PRISM: { const double n = npoints; static const double third = 1.0/3.0; static const double ninth = 1.0/9.0; static const double twentyseventh = 1.0/27.0; const double term = std::cbrt(third*sqrt(third)*sqrt((27.0*n - 2.0)*n) + n - twentyseventh); return static_cast(std::round(term + ninth / term - 4*third)); } case LAGRANGE_PYRAMID: MFEM_ABORT("Lagrange pyramids not currently supported in VTK."); return 0; } } return 1; } int BarycentricToVTKTriangle(int *b, int ref) { // Cf. https://git.io/JvW8f int max = ref; int min = 0; int bmin = std::min(std::min(b[0], b[1]), b[2]); int idx = 0; // scope into the correct triangle while (bmin > min) { idx += 3*ref; max -= 2; ++min; ref -= 3; } for (int d=0; d<3; ++d) { if (b[(d+2)%3] == max) { // we are on a vertex return idx; } ++idx; } for (int d=0; d<3; ++d) { if (b[(d+1)%3] == min) { // we are on an edge return idx + b[d] - (min + 1); } idx += max - (min + 1); } return idx; } int BarycentricToVTKTetra(int *b, int ref) { // Cf. https://git.io/JvW8c int idx = 0; int max = ref; int min = 0; int bmin = std::min(std::min(std::min(b[0], b[1]), b[2]), b[3]); // scope into the correct tetra while (bmin > min) { idx += 2*(ref*ref + 1); max -= 3; min++; ref -= 4; } // When a linearized tetra vertex is cast into barycentric coordinates, one of // its coordinates is maximal and the other three are minimal. These are the // indices of the maximal barycentric coordinate for each vertex. static const int VertexMaxCoords[4] = {3,0,1,2}; // Each linearized tetra edge holds two barycentric tetra coordinates constant // and varies the other two. These are the coordinates that are held constant // for each edge. static const int EdgeMinCoords[6][2] = {{1,2},{2,3},{0,2}, {0,1},{1,3},{0,3}}; // The coordinate that increments when traversing an edge (i.e. the coordinate // of the nonzero component of the second vertex of the edge). static const int EdgeCountingCoord[6] = {0,1,3,2,2,2}; // When describing a linearized tetra face, there is a mapping between the // four-component barycentric tetra system and the three-component barycentric // triangle system. These are the constant indices within the four-component // system for each face (e.g. face 0 holds barycentric tetra coordinate 1 // constant). static const int FaceMinCoord[4] = {1,3,0,2}; // When describing a linearized tetra face, there is a mapping between the // four-component barycentric tetra system and the three-component barycentric // triangle system. These are the relevant indices within the four-component // system for each face (e.g. face 0 varies across the barycentric tetra // coordinates 0, 2 and 3). static const int FaceBCoords[4][3] = {{0,2,3}, {2,0,1}, {2,1,3}, {1,0,3}}; for (int vertex = 0; vertex < 4; vertex++) { if (b[VertexMaxCoords[vertex]] == max) { // we are on a vertex return idx; } idx++; } for (int edge = 0; edge < 6; edge++) { if (b[EdgeMinCoords[edge][0]] == min && b[EdgeMinCoords[edge][1]] == min) { // we are on an edge return idx + b[EdgeCountingCoord[edge]] - (min + 1); } idx += max - (min + 1); } for (int face = 0; face < 4; face++) { if (b[FaceMinCoord[face]] == min) { // we are on a face int projectedb[3]; for (int i = 0; i < 3; i++) { projectedb[i] = b[FaceBCoords[face][i]] - min; } // we must subtract the indices of the face's vertices and edges, which // total to 3*ref return (idx + BarycentricToVTKTriangle(projectedb, ref) - 3*ref); } idx += (ref+1)*(ref+2)/2 - 3*ref; } return idx; } int VTKTriangleDOFOffset(int ref, int i, int j) { return i + ref*(j - 1) - (j*(j + 1))/2; } int CartesianToVTKPrism(int i, int j, int k, int ref) { // Cf. https://git.io/JvW0M int om1 = ref - 1; int ibdr = (i == 0); int jbdr = (j == 0); int ijbdr = (i + j == ref); int kbdr = (k == 0 || k == ref); // How many boundaries do we lie on at once? int nbdr = ibdr + jbdr + ijbdr + kbdr; // Return an invalid index given invalid coordinates if (i < 0 || i > ref || j < 0 || j > ref || i + j > ref || k < 0 || k > ref) { MFEM_ABORT("Invalid index") } if (nbdr == 3) // Vertex DOF { // ijk is a corner node. Return the proper index (somewhere in [0,5]): return (ibdr && jbdr ? 0 : (jbdr && ijbdr ? 1 : 2)) + (k ? 3 : 0); } int offset = 6; if (nbdr == 2) // Edge DOF { if (!kbdr) { // Must be on a vertical edge and 2 of {ibdr, jbdr, ijbdr} are true offset += om1*6; return offset + (k-1) + ((ibdr && jbdr) ? 0 : (jbdr && ijbdr ? 1 : 2))*om1; } else { // Must be on a horizontal edge and kbdr plus 1 of {ibdr, jbdr, ijbdr} is true // Skip past first 3 edges if we are on the top (k = ref) face: offset += (k == ref ? 3*om1 : 0); if (jbdr) { return offset + i - 1; } offset += om1; // Skip the i-axis edge if (ijbdr) { return offset + j - 1; } offset += om1; // Skip the ij-axis edge // if (ibdr) return offset + (ref - j - 1); } } offset += 9*om1; // Skip all the edges // Number of points on a triangular face (but not on edge/corner): int ntfdof = (om1 - 1)*om1/2; int nqfdof = om1*om1; if (nbdr == 1) // Face DOF { if (kbdr) { // We are on a triangular face. if (k > 0) { offset += ntfdof; } return offset + VTKTriangleDOFOffset(ref, i, j); } // Not a k-normal face, so skip them: offset += 2*ntfdof; // Face is quadrilateral (ref - 1) x (ref - 1) // First face is i-normal, then ij-normal, then j-normal if (jbdr) // On i-normal face { return offset + (i - 1) + om1*(k - 1); } offset += nqfdof; // Skip i-normal face if (ijbdr) // on ij-normal face { return offset + (ref - i - 1) + om1*(k - 1); } offset += nqfdof; // Skip ij-normal face return offset + j - 1 + om1*(k - 1); } // Skip all face DOF offset += 2*ntfdof + 3*nqfdof; // nbdr == 0: Body DOF return offset + VTKTriangleDOFOffset(ref, i, j) + ntfdof*(k - 1); // (i - 1) + (ref-1)*((j - 1) + (ref - 1)*(k - 1))); } int CartesianToVTKTensor(int idx_in, int ref, Geometry::Type geom) { int n = ref + 1; switch (geom) { case Geometry::POINT: return idx_in; case Geometry::SEGMENT: if (idx_in == 0 || idx_in == ref) { return idx_in ? 1 : 0; } return idx_in + 1; case Geometry::SQUARE: { // Cf: https://git.io/JvZLT int i = idx_in % n; int j = idx_in / n; // Do we lie on any of the edges bool ibdr = (i == 0 || i == ref); bool jbdr = (j == 0 || j == ref); if (ibdr && jbdr) // Vertex DOF { return (i ? (j ? 2 : 1) : (j ? 3 : 0)); } int offset = 4; if (jbdr) // Edge DOF on j==0 or j==ref { return (i - 1) + (j ? ref - 1 + ref - 1 : 0) + offset; } else if (ibdr) // Edge DOF on i==0 or i==ref { return (j - 1) + (i ? ref - 1 : 2 * (ref - 1) + ref - 1) + offset; } else // Interior DOF { offset += 2 * (ref - 1 + ref - 1); return offset + (i - 1) + (ref - 1) * ((j - 1)); } } case Geometry::CUBE: { // Cf: https://git.io/JvZLe int i = idx_in % n; int j = (idx_in / n) % n; int k = idx_in / (n*n); bool ibdr = (i == 0 || i == ref); bool jbdr = (j == 0 || j == ref); bool kbdr = (k == 0 || k == ref); // How many boundaries do we lie on at once? int nbdr = (ibdr ? 1 : 0) + (jbdr ? 1 : 0) + (kbdr ? 1 : 0); if (nbdr == 3) // Vertex DOF { // ijk is a corner node. Return the proper index (in [0,7]) return (i ? (j ? 2 : 1) : (j ? 3 : 0)) + (k ? 4 : 0); } int offset = 8; if (nbdr == 2) // Edge DOF { if (!ibdr) { // On i axis return (i - 1) + (j ? ref - 1 + ref - 1 : 0) + (k ? 2*(ref - 1 + ref - 1) : 0) + offset; } if (!jbdr) { // On j axis return (j - 1) + (i ? ref - 1 : 2*(ref - 1) + ref - 1) + (k ? 2*(ref - 1 + ref - 1) : 0) + offset; } // !kbdr, On k axis offset += 4*(ref - 1) + 4*(ref - 1); return (k - 1) + (ref - 1)*(i ? (j ? 3 : 1) : (j ? 2 : 0)) + offset; } offset += 4*(ref - 1 + ref - 1 + ref - 1); if (nbdr == 1) // Face DOF { if (ibdr) // On i-normal face { return (j - 1) + ((ref - 1)*(k - 1)) + (i ? (ref - 1)*(ref - 1) : 0) + offset; } offset += 2*(ref - 1)*(ref - 1); if (jbdr) // On j-normal face { return (i - 1) + ((ref - 1)*(k - 1)) + (j ? (ref - 1)*(ref - 1) : 0) + offset; } offset += 2*(ref - 1)*(ref - 1); // kbdr, On k-normal face return (i - 1) + ((ref - 1)*(j - 1)) + (k ? (ref - 1)*(ref - 1) : 0) + offset; } // nbdr == 0: Interior DOF offset += 2*((ref - 1)*(ref - 1) + (ref - 1)*(ref - 1) + (ref - 1)*(ref - 1)); return offset + (i - 1) + (ref - 1)*((j - 1) + (ref - 1)*(k - 1)); } default: MFEM_ABORT("CartesianToVTKOrderingTensor only supports tensor" " geometries."); return -1; } } void CreateVTKElementConnectivity(Array &con, Geometry::Type geom, int ref) { RefinedGeometry *RefG = GlobGeometryRefiner.Refine(geom, ref, 1); int nnodes = RefG->RefPts.GetNPoints(); con.SetSize(nnodes); if (geom == Geometry::TRIANGLE) { int b[3]; int idx = 0; for (b[1]=0; b[1]<=ref; ++b[1]) { for (b[0]=0; b[0]<=ref-b[1]; ++b[0]) { b[2] = ref - b[0] - b[1]; con[BarycentricToVTKTriangle(b, ref)] = idx++; } } } else if (geom == Geometry::TETRAHEDRON) { int idx = 0; int b[4]; for (b[2]=0; b[2]<=ref; b[2]++) { for (b[1]=0; b[1]<=ref-b[2]; b[1]++) { for (b[0]=0; b[0]<=ref-b[1]-b[2]; b[0]++) { b[3] = ref-b[0]-b[1]-b[2]; con[BarycentricToVTKTetra(b, ref)] = idx++; } } } } else if (geom == Geometry::PRISM) { int idx = 0; for (int k=0; k<=ref; k++) { for (int j=0; j<=ref; j++) { for (int i=0; i<=ref-j; i++) { con[CartesianToVTKPrism(i, j, k, ref)] = idx++; } } } } else if (geom == Geometry::PYRAMID) { MFEM_ABORT("Lagrange pyramid elements not currently supported in VTK."); } else { for (int idx=0; idx= -1 && compression_level <= 9, "Compression level must be between -1 and 9 (inclusive)."); uLongf buf_sz = compressBound(nbytes); std::vector buf(buf_sz); compress2(buf.data(), &buf_sz, static_cast(bytes), nbytes, compression_level); // Write the header std::vector header(4); header[0] = 1; // number of blocks header[1] = nbytes; // uncompressed size header[2] = 0; // size of partial block header[3] = buf_sz; // compressed size bin_io::WriteBase64(os, header.data(), header.size()*sizeof(uint32_t)); // Write the compressed data bin_io::WriteBase64(os, buf.data(), buf_sz); #else MFEM_ABORT("MFEM must be compiled with ZLib support to output " "compressed binary data.") #endif } } bool IsBigEndian() { int16_t x16 = 1; int8_t *x8 = reinterpret_cast(&x16); return !*x8; } const char *VTKByteOrder() { if (IsBigEndian()) { return "BigEndian"; } else { return "LittleEndian"; } } // Ensure ASCII output of uint8_t to stream is integer rather than character template <> void WriteBinaryOrASCII(std::ostream &os, std::vector &buf, const uint8_t &val, const char *suffix, VTKFormat format) { if (format == VTKFormat::ASCII) { os << static_cast(val) << suffix; } else { bin_io::AppendBytes(buf, val); } } template <> void WriteBinaryOrASCII(std::ostream &os, std::vector &buf, const double &val, const char *suffix, VTKFormat format) { if (format == VTKFormat::BINARY32) { bin_io::AppendBytes(buf, float(val)); } else if (format == VTKFormat::BINARY) { bin_io::AppendBytes(buf, val); } else { os << ZeroSubnormal(val) << suffix; } } template <> void WriteBinaryOrASCII(std::ostream &os, std::vector &buf, const float &val, const char *suffix, VTKFormat format) { if (format == VTKFormat::BINARY) { bin_io::AppendBytes(buf, val); } else if (format == VTKFormat::BINARY32) { bin_io::AppendBytes(buf, val); } else { os << ZeroSubnormal(val) << suffix; } } void WriteBase64WithSizeAndClear(std::ostream &os, std::vector &buf, int compression_level) { WriteVTKEncodedCompressed(os, buf.data(), buf.size(), compression_level); os << '\n'; buf.clear(); } } // namespace mfem