diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index be078f9..a6c4ebb 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -29,12 +29,16 @@ jobs: run: | git submodule update --init --recursive mkdir build_${{ matrix.build_type}} && cd build_${{ matrix.build_type}} - cmake .. -DCMAKE_PREFIX_PATH=${{ runner.workspace }}/opt -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + cmake .. -DCMAKE_PREFIX_PATH=${{ runner.workspace }}/opt -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMPIEXEC_PREFLAGS=--oversubscribe - name: Build working-directory: ${{ github.workspace }}/build_${{ matrix.build_type }} run: cmake --build . + - name: Test + working-directory: ${{ github.workspace }}/build_${{ matrix.build_type }} + run: ctest --output-on-failure + pre-commit-check: runs-on: ubuntu-latest steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d38090d..4034174 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,5 +59,7 @@ repos: hooks: - id: end-of-file-fixer name: '[GENERIC] newline eof' + exclude: ^tests/fixtures/ - id: trailing-whitespace name: '[GENERIC] remove trailing whitespace' + exclude: ^tests/fixtures/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 58e4d5f..3ae9833 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsa set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") option(SIMMETRIX "Use simmetrix libraries" OFF) +option(EASI "Build the velocity-aware sizing with easi (always on with SIMMETRIX)" OFF) set(SIMMETRIX_ROOT "" CACHE STRING "Root directory of simmetrix dev files") set(SIM_MPI "" CACHE STRING "MPI version used by simmetrix") option(NETCDF "Use netcdf" OFF) @@ -37,11 +38,13 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O2") add_executable(pumgen ${CMAKE_CURRENT_SOURCE_DIR}/src/pumgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/GambitReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/GMSHParser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/GMSH2Parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/GMSH4Parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/MshInput.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/Msh4Index.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/meshreader/DistributedGMSHReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/input/ParallelVertexFilter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/GMSHLexer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/GMSHParser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/GMSH2Parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/helper/InsphereCalculator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/helper/MPIConvenience.cpp ) @@ -93,10 +96,6 @@ target_link_libraries(pumgen PUBLIC tinyxml2) if (SIMMETRIX) find_package(SIMMETRIX REQUIRED) - # easi - find_package(easi 1.0.0 REQUIRED) - target_link_libraries(pumgen PUBLIC easi::easi) - target_sources(pumgen PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/input/AnalysisAttributes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/input/EasiMeshSize.cpp @@ -134,8 +133,40 @@ endfunction() cast_log_level_to_int(LOG_LEVEL LOG_LEVEL) target_compile_definitions(pumgen PUBLIC LOG_LEVEL=${LOG_LEVEL}) + +if (SIMMETRIX) + set(EASI ON) +endif() +if (EASI) + find_package(easi 1.0.0 REQUIRED) + add_library(pumgen_sizing STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/sizing/VelocityAwareSettings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/sizing/VelocityAwareMeshSize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/sizing/SizeField.cpp + ) + target_include_directories(pumgen_sizing PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/submodules + ) + target_compile_definitions(pumgen_sizing PRIVATE LOG_LEVEL=${LOG_LEVEL}) + target_link_libraries(pumgen_sizing PUBLIC easi::easi tinyxml2) + target_link_libraries(pumgen PUBLIC pumgen_sizing) + target_sources(pumgen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/sizing/VelocityCheck.cpp) + target_compile_definitions(pumgen PUBLIC USE_EASI) + + add_executable(pumgen-sizefield ${CMAKE_CURRENT_SOURCE_DIR}/src/sizefield.cpp) + target_compile_definitions(pumgen-sizefield PRIVATE LOG_LEVEL=${LOG_LEVEL}) + target_link_libraries(pumgen-sizefield PRIVATE pumgen_sizing MPI::MPI_CXX) + set_target_properties(pumgen-sizefield PROPERTIES INSTALL_RPATH "${INSTALL_RPATH}") + install(TARGETS pumgen-sizefield RUNTIME DESTINATION bin) +endif() if(SIMMETRIX) set(INSTALL_RPATH "${SIM_PS_KRNL_LIB_DIR}:${INSTALL_RPATH}") endif() set_target_properties(pumgen PROPERTIES INSTALL_RPATH "${INSTALL_RPATH}") install(TARGETS pumgen RUNTIME DESTINATION bin DESTINATION ${CMAKE_INSTALL_BINDIR}) + +include(CTest) +if (BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..c2f58e0 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2026 SeisSol Group +# +# SPDX-License-Identifier: BSD-3-Clause + +version = 1 + +[[annotations]] +path = ["tests/fixtures/*.msh", "tests/fixtures/*.neu", "tests/fixtures/*.h5", + "tests/fixtures/*.bin"] +precedence = "override" +SPDX-FileCopyrightText = "2026 SeisSol Group" +SPDX-License-Identifier = "BSD-3-Clause" diff --git a/src/helper/Distributor.h b/src/helper/Distributor.h index 02897f2..d583427 100644 --- a/src/helper/Distributor.h +++ b/src/helper/Distributor.h @@ -4,7 +4,9 @@ #ifndef PUMGEN_SRC_HELPER_DISTRIBUTOR_H_ #define PUMGEN_SRC_HELPER_DISTRIBUTOR_H_ +#include #include +#include #include // no namespace for now @@ -20,4 +22,17 @@ constexpr std::size_t getChunksum(std::size_t total, int until, int size) { return base + addon; } +/** + * The rank whose chunk (as given by getChunksize/getChunksum) contains the item index. + */ +constexpr int getChunkOwner(std::size_t total, std::size_t index, int size) { + const std::size_t base = total / size; + const std::size_t rest = total % size; + const std::size_t largeChunks = rest * (base + 1); + if (index < largeChunks) { + return static_cast(index / (base + 1)); + } + return static_cast(rest + (index - largeChunks) / base); +} + #endif // PUMGEN_SRC_HELPER_DISTRIBUTOR_H_ diff --git a/src/helper/InsphereCalculator.cpp b/src/helper/InsphereCalculator.cpp index 309e35e..cad62b7 100644 --- a/src/helper/InsphereCalculator.cpp +++ b/src/helper/InsphereCalculator.cpp @@ -12,11 +12,10 @@ #include "MPIConvenience.h" -std::vector calculateInsphere(const std::vector& connectivity, - const std::vector& geometry, MPI_Comm comm) { +CellVertices::CellVertices(const std::vector& connectivity, + const std::vector& geometry, std::size_t cellSize, MPI_Comm comm) + : connectivity(connectivity), geometry(geometry), cellSize(cellSize) { int commsize; - int commrank; - MPI_Comm_size(comm, &commsize); MPI_Comm_rank(comm, &commrank); @@ -30,7 +29,7 @@ std::vector calculateInsphere(const std::vector& connectivi std::size_t localVertices = geometry.size() / 3; - std::vector vertexDist(commsize + 1); + vertexDist.resize(commsize + 1); MPI_Allgather(&localVertices, 1, tndm::mpi_type_t(), vertexDist.data() + 1, 1, tndm::mpi_type_t(), comm); @@ -39,9 +38,13 @@ std::vector calculateInsphere(const std::vector& connectivi vertexDist[i + 1] += vertexDist[i]; } - std::vector> outidxmap(commsize); + outidxmap.resize(commsize); - for (const auto& vertex : connectivity) { + for (std::size_t node = 0; node < connectivity.size(); ++node) { + if (node % cellSize >= VerticesPerCell) { + continue; + } + const auto vertex = connectivity[node]; auto itPosition = std::upper_bound(vertexDist.begin(), vertexDist.end(), vertex); auto position = std::distance(vertexDist.begin(), itPosition) - 1; auto localVertex = vertex - vertexDist[position]; @@ -57,7 +60,7 @@ std::vector calculateInsphere(const std::vector& connectivi MPI_Alltoall(outrequests.data(), 1, tndm::mpi_type_t(), inrequests.data(), 1, tndm::mpi_type_t(), comm); - std::vector outdisp(commsize + 1); + outdisp.resize(commsize + 1); std::vector indisp(commsize + 1); for (std::size_t i = 1; i < commsize + 1; ++i) { outdisp[i] = outdisp[i - 1] + outrequests[i - 1]; @@ -84,7 +87,7 @@ std::vector calculateInsphere(const std::vector& connectivi tndm::mpi_type_t(), comm); } - std::vector outvertices(3 * connectivity.size()); + outvertices.resize(3 * outdisp[commsize]); { std::vector invertices(3 * indisp[commsize]); @@ -101,27 +104,36 @@ std::vector calculateInsphere(const std::vector& connectivi outvertices.data(), outrequests.data(), outdisp.data(), vertexType, comm); } - std::vector counter(commsize); - std::vector inspheres(connectivity.size() / 4); - - for (std::size_t i = 0; i < connectivity.size() / 4; ++i) { - std::array, 4> vertices; - for (int j = 0; j < 4; ++j) { - auto vertex = connectivity[i * 4 + j]; - auto itPosition = std::upper_bound(vertexDist.begin(), vertexDist.end(), vertex); - auto position = std::distance(vertexDist.begin(), itPosition) - 1; - auto localVertex = vertex - vertexDist[position]; - if (position == commrank) { - vertices[j][0] = geometry[localVertex * 3 + 0]; - vertices[j][1] = geometry[localVertex * 3 + 1]; - vertices[j][2] = geometry[localVertex * 3 + 2]; - } else { - auto transferidx = outidxmap[position][localVertex] + outdisp[position]; - vertices[j][0] = outvertices[transferidx * 3 + 0]; - vertices[j][1] = outvertices[transferidx * 3 + 1]; - vertices[j][2] = outvertices[transferidx * 3 + 2]; - } + MPI_Type_free(&vertexType); +} + +std::array, CellVertices::VerticesPerCell> +CellVertices::operator()(std::size_t cell) const { + std::array, VerticesPerCell> vertices; + for (std::size_t j = 0; j < VerticesPerCell; ++j) { + auto vertex = connectivity[cell * cellSize + j]; + auto itPosition = std::upper_bound(vertexDist.begin(), vertexDist.end(), vertex); + auto position = std::distance(vertexDist.begin(), itPosition) - 1; + auto localVertex = vertex - vertexDist[position]; + if (position == commrank) { + vertices[j][0] = geometry[localVertex * 3 + 0]; + vertices[j][1] = geometry[localVertex * 3 + 1]; + vertices[j][2] = geometry[localVertex * 3 + 2]; + } else { + auto transferidx = outidxmap[position].at(localVertex) + outdisp[position]; + vertices[j][0] = outvertices[transferidx * 3 + 0]; + vertices[j][1] = outvertices[transferidx * 3 + 1]; + vertices[j][2] = outvertices[transferidx * 3 + 2]; } + } + return vertices; +} + +std::vector calculateInsphere(const CellVertices& cells) { + std::vector inspheres(cells.numCells()); + + for (std::size_t i = 0; i < cells.numCells(); ++i) { + const auto vertices = cells(i); double a11 = vertices[1][0] - vertices[0][0]; double a12 = vertices[1][1] - vertices[0][1]; double a13 = vertices[1][2] - vertices[0][2]; @@ -162,7 +174,5 @@ std::vector calculateInsphere(const std::vector& connectivi inspheres[i] = gram / faces; } - MPI_Type_free(&vertexType); - return inspheres; } diff --git a/src/helper/InsphereCalculator.h b/src/helper/InsphereCalculator.h index 9a8e548..94feb2d 100644 --- a/src/helper/InsphereCalculator.h +++ b/src/helper/InsphereCalculator.h @@ -4,13 +4,43 @@ #ifndef PUMGEN_SRC_HELPER_INSPHERECALCULATOR_H_ #define PUMGEN_SRC_HELPER_INSPHERECALCULATOR_H_ +#include #include #include +#include #include -// assumes a contiguous distribution of all vertices over all processes +/** + * The vertex coordinates of the local cells; the coordinates of vertices owned by other ranks are + * fetched once. Assumes a contiguous distribution of all vertices over all processes. Each cell + * has cellSize nodes, the first four of which are its vertices. + */ +class CellVertices { + public: + static constexpr std::size_t VerticesPerCell = 4; -std::vector calculateInsphere(const std::vector& connectivity, - const std::vector& geometry, MPI_Comm comm); + CellVertices(const std::vector& connectivity, const std::vector& geometry, + std::size_t cellSize, MPI_Comm comm); + + [[nodiscard]] std::size_t numCells() const { return connectivity.size() / cellSize; } + + [[nodiscard]] std::array, VerticesPerCell> + operator()(std::size_t cell) const; + + private: + const std::vector& connectivity; + const std::vector& geometry; + std::size_t cellSize; + int commrank = 0; + std::vector vertexDist; + std::vector> outidxmap; + std::vector outdisp; + std::vector outvertices; +}; + +/** + * The insphere radius of each tetrahedron. + */ +std::vector calculateInsphere(const CellVertices& cells); #endif // PUMGEN_SRC_HELPER_INSPHERECALCULATOR_H_ diff --git a/src/input/EasiMeshSize.cpp b/src/input/EasiMeshSize.cpp index 6c82a96..d4a9e82 100644 --- a/src/input/EasiMeshSize.cpp +++ b/src/input/EasiMeshSize.cpp @@ -2,20 +2,15 @@ // // SPDX-License-Identifier: BSD-3-Clause #include "EasiMeshSize.h" -#include -#include -#include +#include +#include #include -EasiMeshSize::EasiMeshSize() : parser(nullptr), model(nullptr) {} - -EasiMeshSize::EasiMeshSize(VelocityAwareRefinementSettings refinementSettings, pGModel simModel, - std::unordered_map groupMap) - : refinementSettings(refinementSettings), simModel(simModel), parser(new easi::YAMLParser(3)), - groupMap(std::move(groupMap)) { - model = parser->parse(refinementSettings.getEasiFileName()); -} +EasiMeshSize::EasiMeshSize(const VelocityAwareRefinementSettings& refinementSettings, + pGModel simModel, std::unordered_map groupMap) + : meshSize(std::make_shared(refinementSettings)), simModel(simModel), + groupMap(std::move(groupMap)) {} int EasiMeshSize::findGroup(std::array point) { // GR_containsPoint can be expensive for large geometry, @@ -34,77 +29,20 @@ int EasiMeshSize::findGroup(std::array point) { return -1; } -std::tuple -EasiMeshSize::getTargetedFrequencyAndRegion(const std::array& point) { - const auto& refinementRegions = refinementSettings.getRefinementRegions(); - double targetedFrequency = 0.0; - int bypassFindRegionAndUseGroup = 0; - for (const auto& refinementCube : refinementRegions) { - auto& cuboid = refinementCube.cuboid; - bool isInCuboid = true; - - double u0 = (point[0] - cuboid.center[0]) * cuboid.cosSinRotationZ[0] + - (point[1] - cuboid.center[1]) * cuboid.cosSinRotationZ[1]; - isInCuboid &= std::abs(u0) <= cuboid.halfSize[0]; - double u1 = (point[0] - cuboid.center[0]) * -cuboid.cosSinRotationZ[1] + - (point[1] - cuboid.center[1]) * cuboid.cosSinRotationZ[0]; - isInCuboid &= std::abs(u1) <= cuboid.halfSize[1]; - double u2 = (point[2] - cuboid.center[2]); - isInCuboid &= std::abs(u2) <= cuboid.halfSize[2]; - - if (isInCuboid) { - if (refinementCube.targetedFrequency >= targetedFrequency) { - bypassFindRegionAndUseGroup = refinementCube.bypassFindRegionAndUseGroup; - } - targetedFrequency = std::max(targetedFrequency, refinementCube.targetedFrequency); - } - } - return std::make_tuple(targetedFrequency, bypassFindRegionAndUseGroup); -} - double EasiMeshSize::getMeshSize(const std::array& point) { - if (!model) { - logError() << "Model was not parsed correctly"; - } - - constexpr double defaultMeshSize = std::numeric_limits::max(); + constexpr double DefaultMeshSize = std::numeric_limits::max(); const auto [targetedFrequency, bypassFindRegionAndUseGroup] = - getTargetedFrequencyAndRegion(point); - + meshSize->targetedFrequencyAndGroup(point); if (targetedFrequency == 0.0) { - return defaultMeshSize; + return DefaultMeshSize; } - easi::Query query(1, 3); - query.x(0, 0) = point[0]; - query.x(0, 1) = point[1]; - query.x(0, 2) = point[2]; - query.group(0) = bypassFindRegionAndUseGroup ? bypassFindRegionAndUseGroup : findGroup(point); - + const int group = + bypassFindRegionAndUseGroup != 0 ? bypassFindRegionAndUseGroup : findGroup(point); // This means the point is slightly outside the geometry - if (query.group(0) <= 0) { - return defaultMeshSize; - } - - // Need array for easi interface - auto materials = std::array(); - auto& material = materials[0]; - auto adapter = easi::ArrayOfStructsAdapter(materials.data()); - adapter.addBindingPoint("lambda", &ElasticMaterial::lambda); - adapter.addBindingPoint("mu", &ElasticMaterial::mu); - adapter.addBindingPoint("rho", &ElasticMaterial::rho); - - model->evaluate(query, adapter); - - double waveSpeed = 0.0; - if (material.mu < 10e-14) { - // acoustic - waveSpeed = std::sqrt((material.lambda + 2 * material.mu) / material.rho); - } else { - // elastic - waveSpeed = std::sqrt(material.mu / material.rho); + if (group <= 0) { + return DefaultMeshSize; } - const auto waveLength = waveSpeed / targetedFrequency; - return waveLength / refinementSettings.getElementsPerWaveLength(); + return meshSize->meshSizes({point}, {group}, {targetedFrequency})[0]; } diff --git a/src/input/EasiMeshSize.h b/src/input/EasiMeshSize.h index a9fa62e..1df68ea 100644 --- a/src/input/EasiMeshSize.h +++ b/src/input/EasiMeshSize.h @@ -4,37 +4,26 @@ #ifndef PUMGEN_SRC_INPUT_EASIMESHSIZE_H_ #define PUMGEN_SRC_INPUT_EASIMESHSIZE_H_ -#include "MeshAttributes.h" +#include "sizing/VelocityAwareMeshSize.h" #include #include #include -#include -#include #include -#include - -struct ElasticMaterial { - double lambda, mu, rho; -}; +#include +/** + * Velocity-aware mesh size for SimModSuite, which asks for one point at a time. + */ class EasiMeshSize { private: - VelocityAwareRefinementSettings refinementSettings; - - easi::YAMLParser* parser; - easi::Component* model; // Unique ptr to model leads to segfault + std::shared_ptr meshSize; pGModel simModel; std::unordered_map groupMap; int findGroup(std::array point); - std::tuple - getTargetedFrequencyAndRegion(const std::array& point); - public: - EasiMeshSize(); - ; - EasiMeshSize(VelocityAwareRefinementSettings refinementSettings, pGModel simModel, + EasiMeshSize(const VelocityAwareRefinementSettings& refinementSettings, pGModel simModel, std::unordered_map groupMap); double getMeshSize(const std::array& point); diff --git a/src/input/MeshAttributes.cpp b/src/input/MeshAttributes.cpp index 4559e6f..500e40a 100644 --- a/src/input/MeshAttributes.cpp +++ b/src/input/MeshAttributes.cpp @@ -3,31 +3,6 @@ // SPDX-License-Identifier: BSD-3-Clause #include "MeshAttributes.h" -VelocityAwareRefinementSettings::VelocityAwareRefinementSettings(double elementsPerWaveLength, - std::string easiFileName) - : elementsPerWaveLength(elementsPerWaveLength), easiFileName(std::move(easiFileName)) {} - -void VelocityAwareRefinementSettings::addRefinementRegion(SimpleCuboid cuboid, - double targetedFrequency, - int bypassFindRegionAndUseGroup) { - refinementRegions.emplace_back(cuboid, targetedFrequency, bypassFindRegionAndUseGroup); -} - -bool VelocityAwareRefinementSettings::isVelocityAwareRefinementOn() const { - return !refinementRegions.empty(); -} - -const std::string& VelocityAwareRefinementSettings::getEasiFileName() const { return easiFileName; } - -double VelocityAwareRefinementSettings::getElementsPerWaveLength() const { - return elementsPerWaveLength; -} - -const std::vector& -VelocityAwareRefinementSettings::getRefinementRegions() const { - return refinementRegions; -} - MeshAttributes::MeshAttributes() : lpair_lVertexId_MSize(), lpair_lEdgeId_MSize(), lpair_lFaceId_MSize(), lpair_lRegionId_MSize() {} @@ -168,69 +143,7 @@ void MeshAttributes::set_regionMSize() { } void MeshAttributes::set_velocity_aware_meshing() { - int numChilds = 0; - const auto name = "VelocityAwareMeshing"; - for (auto velocityAwareMeshingElement = doc.FirstChildElement(name); velocityAwareMeshingElement; - velocityAwareMeshingElement = velocityAwareMeshingElement->NextSiblingElement(name)) { - const auto easiFileName = velocityAwareMeshingElement->Attribute("easiFile"); - const auto elementsPerWaveLength = - std::stof(velocityAwareMeshingElement->Attribute("elementsPerWaveLength")); - velocityAwareRefinementSettings = - VelocityAwareRefinementSettings(elementsPerWaveLength, easiFileName); - constexpr auto cuboidName = "VelocityRefinementCuboid"; - logInfo() << "Activating velocity aware meshing, using" << elementsPerWaveLength - << "elements per wavelength and easi file" << easiFileName; - for (auto child = velocityAwareMeshingElement->FirstChildElement(cuboidName); child; - child = child->NextSiblingElement(cuboidName)) { - - const char* attr = child->Attribute("rotationZAnticlockwiseFromX"); - double rotationZAnticlockwiseFromX = 0.0; - if (attr != nullptr) { - rotationZAnticlockwiseFromX = std::stof(attr); - } - attr = child->Attribute("bypassFindRegionAndUseGroup"); - int bypassFindRegionAndUseGroup = 0; - if (attr != nullptr) { - bypassFindRegionAndUseGroup = std::stoi(attr); - } - auto cuboid = SimpleCuboid{{ - std::stof(child->Attribute("centerX")), - std::stof(child->Attribute("centerY")), - std::stof(child->Attribute("centerZ")), - }, - { - std::stof(child->Attribute("halfSizeX")), - std::stof(child->Attribute("halfSizeY")), - std::stof(child->Attribute("halfSizeZ")), - }, - {std::cos(rotationZAnticlockwiseFromX * toRadians), - std::sin(rotationZAnticlockwiseFromX * toRadians)}, - rotationZAnticlockwiseFromX}; - const auto targetedFrequency = std::stof(child->Attribute("frequency")); - - velocityAwareRefinementSettings.addRefinementRegion(cuboid, targetedFrequency, - bypassFindRegionAndUseGroup); - logInfo() << "Adding velocity aware refinement region targeting" << targetedFrequency - << "Hz, centered at x =" << cuboid.center[0] << "y=" << cuboid.center[1] - << "z=" << cuboid.center[2] << "with half sizes" - << "x =" << cuboid.halfSize[0] << "y =" << cuboid.halfSize[1] - << "z =" << cuboid.halfSize[2]; - if (std::abs(cuboid.rotationZ) > 0.0) { - logInfo() << "rotated around z axis by " << cuboid.rotationZ - << "degree(s) counterclockwise from x axis."; - } - if (bypassFindRegionAndUseGroup) { - logInfo() << "bypass findRegion and use group =" << bypassFindRegionAndUseGroup; - } - } - if (!velocityAwareRefinementSettings.isVelocityAwareRefinementOn()) { - logWarning() << "Activated velocity aware meshing but did not specify any refinement region!"; - } - ++numChilds; - } - if (numChilds > 1) { - logError() << "Multiple definitions of velocityAwareMeshing"; - } + velocityAwareRefinementSettings = readVelocityAwareSettings(doc); } void MeshAttributes::set_global_mesh_attributes() { diff --git a/src/input/MeshAttributes.h b/src/input/MeshAttributes.h index cac7e40..337e743 100644 --- a/src/input/MeshAttributes.h +++ b/src/input/MeshAttributes.h @@ -10,6 +10,7 @@ #include #include +#include "sizing/VelocityAwareSettings.h" #include "split.h" #include "tinyxml2/tinyxml2.h" #include "utils/logger.h" @@ -25,46 +26,6 @@ struct Cube { double CubeMSize = 0, CubeCenter[3], CubeWidth[3], CubeHeight[3], CubeDepth[3]; }; -struct SimpleCuboid { - std::array center; - std::array halfSize; - std::array cosSinRotationZ; - double rotationZ; -}; - -struct VelocityRefinementCube { - VelocityRefinementCube(SimpleCuboid cuboid, double targetedFrequency, - int bypassFindRegionAndUseGroup) - : cuboid(cuboid), targetedFrequency(targetedFrequency), - bypassFindRegionAndUseGroup(bypassFindRegionAndUseGroup) {}; - - SimpleCuboid cuboid; - double targetedFrequency; - int bypassFindRegionAndUseGroup; -}; - -class VelocityAwareRefinementSettings { - public: - VelocityAwareRefinementSettings() = default; - VelocityAwareRefinementSettings(double elementsPerWaveLength, std::string easiFileName); - - void addRefinementRegion(SimpleCuboid cuboid, double targetedFrequency, - int bypassFindRegionAndUseGroup); - - [[nodiscard]] bool isVelocityAwareRefinementOn() const; - - [[nodiscard]] const std::string& getEasiFileName() const; - - [[nodiscard]] double getElementsPerWaveLength() const; - - [[nodiscard]] const std::vector& getRefinementRegions() const; - - private: - double elementsPerWaveLength{}; - std::string easiFileName; - std::vector refinementRegions{}; -}; - enum class ElementType { vertex = 0, edge = 1, face = 2, region = 3 }; using namespace tinyxml2; diff --git a/src/meshreader/DistributedGMSHReader.cpp b/src/meshreader/DistributedGMSHReader.cpp new file mode 100644 index 0000000..238030b --- /dev/null +++ b/src/meshreader/DistributedGMSHReader.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "DistributedGMSHReader.h" + +#include +#include +#include + +namespace puml::distributed { + +namespace { +template T swapBytesOf(T value) { + std::array bytes{}; + std::memcpy(bytes.data(), &value, sizeof(T)); + std::reverse(bytes.begin(), bytes.end()); + std::memcpy(&value, bytes.data(), sizeof(T)); + return value; +} + +std::uint64_t mix(std::uint64_t value) { + // splitmix64 finalizer + value ^= value >> 30; + value *= 0xbf58476d1ce4e5b9ULL; + value ^= value >> 27; + value *= 0x94d049bb133111ebULL; + value ^= value >> 31; + return value; +} +} // namespace + +RawMshFile::RawMshFile(const std::string& fileName, const Msh4Index& index) + : dataSize(index.dataSize), swapBytes(index.swapBytes) { + file = std::fopen(fileName.c_str(), "rb"); + if (file == nullptr) { + logError() << "Unable to open MSH file" << fileName; + } +} + +RawMshFile::~RawMshFile() { + if (file != nullptr) { + std::fclose(file); + } +} + +void RawMshFile::readBytes(std::uint64_t offset, std::size_t bytes, void* data) { + if (std::fseek(file, static_cast(offset), SEEK_SET) != 0 || + std::fread(data, 1, bytes, file) != bytes) { + logError() << "Unexpected end of file in binary data"; + } +} + +void RawMshFile::readSizes(std::uint64_t offset, std::size_t count, std::uint64_t* values) { + if (dataSize == sizeof(std::uint64_t)) { + readBytes(offset, count * sizeof(std::uint64_t), values); + if (swapBytes) { + std::transform(values, values + count, values, swapBytesOf); + } + } else { + narrowSizes.resize(count); + readBytes(offset, count * sizeof(std::uint32_t), narrowSizes.data()); + for (std::size_t i = 0; i < count; ++i) { + values[i] = swapBytes ? swapBytesOf(narrowSizes[i]) : narrowSizes[i]; + } + } +} + +void RawMshFile::readDoubles(std::uint64_t offset, std::size_t count, double* values) { + readBytes(offset, count * sizeof(double), values); + if (swapBytes) { + std::transform(values, values + count, values, swapBytesOf); + } +} + +std::uint64_t hashFace(const std::array& face) { + return mix(mix(mix(face[0]) ^ face[1]) ^ face[2]); +} + +} // namespace puml::distributed diff --git a/src/meshreader/DistributedGMSHReader.h b/src/meshreader/DistributedGMSHReader.h new file mode 100644 index 0000000..c390cd3 --- /dev/null +++ b/src/meshreader/DistributedGMSHReader.h @@ -0,0 +1,482 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_MESHREADER_DISTRIBUTEDGMSHREADER_H_ +#define PUMGEN_SRC_MESHREADER_DISTRIBUTEDGMSHREADER_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GMSHBuilder.h" +#include "Msh4Index.h" +#include "helper/Distributor.h" +#include "helper/MPIConvenience.h" +#include "third_party/MPITraits.h" +#include "utils/logger.h" + +namespace puml { + +namespace distributed { + +/** + * Reads values of a binary MSH 4.1 file at given offsets. + */ +class RawMshFile { + public: + RawMshFile(const std::string& fileName, const Msh4Index& index); + ~RawMshFile(); + RawMshFile(const RawMshFile&) = delete; + RawMshFile& operator=(const RawMshFile&) = delete; + + void readSizes(std::uint64_t offset, std::size_t count, std::uint64_t* values); + void readDoubles(std::uint64_t offset, std::size_t count, double* values); + + private: + void readBytes(std::uint64_t offset, std::size_t bytes, void* data); + + std::FILE* file = nullptr; + std::uint64_t dataSize; + bool swapBytes; + std::vector narrowSizes; +}; + +/** + * Sends outgoing[r] to rank r; returns what each rank has sent to this one. + */ +template +std::vector> exchange(const std::vector>& outgoing, MPI_Comm comm) { + static_assert(std::is_trivially_copyable_v); + int size = 1; + MPI_Comm_size(comm, &size); + std::vector sendCounts(size); + std::vector recvCounts(size); + for (int r = 0; r < size; ++r) { + sendCounts[r] = outgoing[r].size(); + } + MPI_Alltoall(sendCounts.data(), 1, tndm::mpi_type_t(), recvCounts.data(), 1, + tndm::mpi_type_t(), comm); + std::vector sendDisp(size + 1, 0); + std::vector recvDisp(size + 1, 0); + for (int r = 0; r < size; ++r) { + sendDisp[r + 1] = sendDisp[r] + sendCounts[r]; + recvDisp[r + 1] = recvDisp[r] + recvCounts[r]; + } + std::vector sendBuffer(sendDisp[size]); + for (int r = 0; r < size; ++r) { + std::copy(outgoing[r].begin(), outgoing[r].end(), sendBuffer.begin() + sendDisp[r]); + } + std::vector recvBuffer(recvDisp[size]); + + MPI_Datatype type; + MPI_Type_contiguous(sizeof(T), MPI_BYTE, &type); + MPI_Type_commit(&type); + sparseAlltoallv(sendBuffer.data(), sendCounts.data(), sendDisp.data(), type, recvBuffer.data(), + recvCounts.data(), recvDisp.data(), type, comm); + MPI_Type_free(&type); + + std::vector> incoming(size); + for (int r = 0; r < size; ++r) { + incoming[r].assign(recvBuffer.begin() + recvDisp[r], recvBuffer.begin() + recvDisp[r + 1]); + } + return incoming; +} + +/** + * Calls f(block, begin, count) for the parts of the items [first, last) of the concatenated + * blocks, where begin is relative to the block. + */ +template +void forBlockRanges(const std::vector& blocks, std::uint64_t first, std::uint64_t last, + F&& f) { + std::uint64_t blockStart = 0; + for (const auto& block : blocks) { + const std::uint64_t blockEnd = blockStart + block.count; + const std::uint64_t begin = std::max(first, blockStart); + const std::uint64_t end = std::min(last, blockEnd); + if (begin < end) { + f(block, begin - blockStart, end - begin); + } + blockStart = blockEnd; + } +} + +std::uint64_t hashFace(const std::array& face); + +// items are read in pieces of at most this many +constexpr std::uint64_t Piece = std::uint64_t{1} << 20; + +} // namespace distributed + +/** + * Reads a binary MSH 4.1 file with all ranks: rank 0 only locates the data, every rank reads a + * share of the nodes, cells and boundary faces, and the parts are sent to the ranks which own + * them in the chunk distribution of getChunksize. The boundary conditions are matched in a + * distributed table of the boundary faces; only faces whose vertices all lie on the surface mesh + * are looked up. + */ +template class DistributedGMSHReader { + public: + constexpr static std::size_t Order = OrderP; + constexpr static std::size_t Dim = 3; + constexpr static bool SupportsIdentify = true; + constexpr static std::size_t NodesPerCell = nodeCount(Dim, Order); + constexpr static std::size_t NodesPerFacet = nodeCount(Dim - 1, Order); + constexpr static std::size_t Facet2Nodes[Dim + 1][Dim] = { + {1, 0, 2}, {0, 1, 3}, {1, 2, 3}, {2, 0, 3}}; + constexpr static long BoundaryConditionOffset = 100; + + explicit DistributedGMSHReader(MPI_Comm comm = MPI_COMM_WORLD) : comm(comm) { + MPI_Comm_rank(comm, &rank); + MPI_Comm_size(comm, &size); + } + + void open(const char* meshFile) { + readIndex(meshFile); + distributed::RawMshFile file(meshFile, index); + readNodes(file); + readCells(file); + matchBoundaryConditions(file); + identifyPeriodicNodes(); + } + + [[nodiscard]] std::size_t nVertices() const { return index.numNodes; } + [[nodiscard]] std::size_t nElements() const { return numCells; } + + // Each read function hands over one part of the local mesh and releases it, so it can be called + // only once. + + void readVertices(double* vertices) { handOver(geometry, vertices); } + void readElements(std::size_t* elements) { handOver(connectivity, elements); } + void readGroups(int* groupsOut) { handOver(groups, groupsOut); } + void readBoundaries(int* boundariesOut) { handOver(boundaries, boundariesOut); } + [[nodiscard]] bool hasIdentify() const { return hasIdentify_; } + void readIdentify(std::size_t* vertices) { handOver(identify, vertices); } + + private: + struct NodeRecord { + std::uint64_t index; + double x[3]; + }; + struct FaceRecord { + std::array vertices; + std::int64_t bc; + }; + struct FaceQuery { + std::array vertices; + std::uint64_t localFace; + }; + struct FaceReply { + std::uint64_t localFace; + std::int64_t bc; + }; + struct Identification { + std::uint64_t vertex; + std::uint64_t representative; + }; + constexpr static std::int64_t NoMatch = -1; + + template static void handOver(std::vector& values, U* out) { + std::copy(values.begin(), values.end(), out); + std::vector().swap(values); + } + + [[nodiscard]] std::uint64_t localFirstVertex() const { + return getChunksum(index.numNodes, rank, size); + } + [[nodiscard]] std::uint64_t localVertexCount() const { + return getChunksize(index.numNodes, rank, size); + } + [[nodiscard]] int vertexOwner(std::uint64_t vertex) const { + return getChunkOwner(index.numNodes, vertex, size); + } + + [[nodiscard]] std::uint64_t nodeIndex(std::uint64_t tag) const { + if (tag < index.firstNodeTag || tag - index.firstNodeTag >= index.numNodes) { + logError() << "Unknown node tag" << tag; + } + return tag - index.firstNodeTag; + } + + template void broadcastVector(std::vector& values) { + std::uint64_t count = values.size(); + MPI_Bcast(&count, 1, MPI_UINT64_T, 0, comm); + values.resize(count); + MPI_Bcast(values.data(), static_cast(count * sizeof(T)), MPI_BYTE, 0, comm); + } + + void readIndex(const char* meshFile) { + if (rank == 0) { + Msh4Indexer indexer(GMSHSimplexType::type, GMSHSimplexType::type); + if (!indexer.parseFile(meshFile)) { + logError() << meshFile << std::endl << indexer.getErrorMessage(); + } + index = indexer.getIndex(); + } + std::array scalars{index.dataSize, index.swapBytes ? 1U : 0U, + index.firstNodeTag, index.numNodes, + index.periodic.empty() ? 0U : 1U}; + MPI_Bcast(scalars.data(), scalars.size(), MPI_UINT64_T, 0, comm); + index.dataSize = scalars[0]; + index.swapBytes = scalars[1] != 0; + index.firstNodeTag = scalars[2]; + index.numNodes = scalars[3]; + hasIdentify_ = scalars[4] != 0; + broadcastVector(index.nodeBlocks); + broadcastVector(index.cellBlocks); + broadcastVector(index.facetBlocks); + numCells = 0; + for (const auto& block : index.cellBlocks) { + numCells += block.count; + } + } + + void readNodes(distributed::RawMshFile& file) { + const std::uint64_t first = getChunksum(index.numNodes, rank, size); + const std::uint64_t last = first + getChunksize(index.numNodes, rank, size); + + std::vector> outgoing(size); + std::vector tags; + std::vector coordinates; + distributed::forBlockRanges( + index.nodeBlocks, first, last, + [&](const Msh4NodeBlock& block, std::uint64_t begin, std::uint64_t count) { + for (std::uint64_t done = 0; done < count; done += distributed::Piece) { + const auto n = std::min(distributed::Piece, count - done); + tags.resize(n); + coordinates.resize(n * block.valuesPerNode); + file.readSizes(block.tagsOffset + (begin + done) * index.dataSize, n, tags.data()); + file.readDoubles(block.coordinatesOffset + + (begin + done) * block.valuesPerNode * sizeof(double), + coordinates.size(), coordinates.data()); + for (std::uint64_t i = 0; i < n; ++i) { + const auto vertex = nodeIndex(tags[i]); + const double* x = &coordinates[i * block.valuesPerNode]; + outgoing[vertexOwner(vertex)].push_back({vertex, {x[0], x[1], x[2]}}); + } + } + }); + + const auto incoming = distributed::exchange(outgoing, comm); + std::vector>().swap(outgoing); + + const auto localFirst = localFirstVertex(); + geometry.assign(localVertexCount() * Dim, 0.0); + std::vector defined(localVertexCount(), false); + std::uint64_t numDefined = 0; + for (const auto& records : incoming) { + for (const auto& record : records) { + const auto local = record.index - localFirst; + if (defined[local]) { + logError() << "Duplicate node tag" << record.index + index.firstNodeTag; + } + defined[local] = true; + ++numDefined; + std::copy_n(record.x, Dim, &geometry[local * Dim]); + } + } + if (numDefined != localVertexCount()) { + logError() << "Missing nodes: the node tags are not unique"; + } + } + + void readCells(distributed::RawMshFile& file) { + const std::uint64_t first = getChunksum(numCells, rank, size); + const std::uint64_t count = getChunksize(numCells, rank, size); + connectivity.resize(count * NodesPerCell); + groups.resize(count); + + constexpr std::uint64_t ValuesPerCell = 1 + NodesPerCell; + std::vector values; + std::uint64_t cell = 0; + distributed::forBlockRanges( + index.cellBlocks, first, first + count, + [&](const Msh4ElementBlock& block, std::uint64_t begin, std::uint64_t blockCount) { + for (std::uint64_t done = 0; done < blockCount; done += distributed::Piece) { + const auto n = std::min(distributed::Piece, blockCount - done); + values.resize(n * ValuesPerCell); + file.readSizes(block.offset + (begin + done) * ValuesPerCell * index.dataSize, + values.size(), values.data()); + for (std::uint64_t e = 0; e < n; ++e, ++cell) { + for (std::size_t k = 0; k < NodesPerCell; ++k) { + connectivity[cell * NodesPerCell + k] = + nodeIndex(values[e * ValuesPerCell + 1 + k]); + } + groups[cell] = static_cast(block.physical); + } + } + }); + } + + void matchBoundaryConditions(distributed::RawMshFile& file) { + std::uint64_t numFacets = 0; + for (const auto& block : index.facetBlocks) { + numFacets += block.count; + } + const std::uint64_t first = getChunksum(numFacets, rank, size); + const std::uint64_t last = first + getChunksize(numFacets, rank, size); + + // the boundary faces go to the ranks given by their hash; their vertices are marked in a bit + // set of all vertices, which all ranks share + std::vector> outgoingFaces(size); + std::vector onBoundary((index.numNodes + 63) / 64, 0); + constexpr std::uint64_t ValuesPerFacet = 1 + NodesPerFacet; + std::vector values; + distributed::forBlockRanges( + index.facetBlocks, first, last, + [&](const Msh4ElementBlock& block, std::uint64_t begin, std::uint64_t count) { + for (std::uint64_t done = 0; done < count; done += distributed::Piece) { + const auto n = std::min(distributed::Piece, count - done); + values.resize(n * ValuesPerFacet); + file.readSizes(block.offset + (begin + done) * ValuesPerFacet * index.dataSize, + values.size(), values.data()); + for (std::uint64_t e = 0; e < n; ++e) { + std::array face{}; + for (std::size_t k = 0; k < Dim; ++k) { + face[k] = nodeIndex(values[e * ValuesPerFacet + 1 + k]); + onBoundary[face[k] / 64] |= std::uint64_t{1} << (face[k] % 64); + } + std::sort(face.begin(), face.end()); + outgoingFaces[distributed::hashFace(face) % size].push_back({face, block.physical}); + } + } + }); + + std::vector faceTable; + for (const auto& faces : distributed::exchange(outgoingFaces, comm)) { + faceTable.insert(faceTable.end(), faces.begin(), faces.end()); + } + std::vector>().swap(outgoingFaces); + const auto byVertices = [](const FaceRecord& a, const FaceRecord& b) { + return a.vertices < b.vertices; + }; + std::sort(faceTable.begin(), faceTable.end(), byVertices); + + MPI_Allreduce(MPI_IN_PLACE, onBoundary.data(), static_cast(onBoundary.size()), + MPI_UINT64_T, MPI_BOR, comm); + const auto vertexOnBoundary = [&](std::uint64_t vertex) { + return ((onBoundary[vertex / 64] >> (vertex % 64)) & 1) != 0; + }; + + const std::uint64_t numLocalCells = groups.size(); + // only faces with all vertices on the surface mesh can be part of it + std::vector> queries(size); + for (std::uint64_t cell = 0; cell < numLocalCells; ++cell) { + std::array flagged{}; + std::size_t numFlagged = 0; + for (std::size_t k = 0; k < Dim + 1; ++k) { + flagged[k] = vertexOnBoundary(connectivity[cell * NodesPerCell + k]); + numFlagged += flagged[k] ? 1 : 0; + } + if (numFlagged < Dim) { + continue; + } + for (std::size_t localFace = 0; localFace < Dim + 1; ++localFace) { + std::array face{}; + bool candidate = true; + for (std::size_t k = 0; k < Dim; ++k) { + candidate = candidate && flagged[Facet2Nodes[localFace][k]]; + face[k] = connectivity[cell * NodesPerCell + Facet2Nodes[localFace][k]]; + } + if (candidate) { + std::sort(face.begin(), face.end()); + queries[distributed::hashFace(face) % size].push_back( + {face, cell * (Dim + 1) + localFace}); + } + } + } + + const auto received = distributed::exchange(queries, comm); + std::vector>().swap(queries); + std::vector> replies(size); + for (int r = 0; r < size; ++r) { + for (const auto& query : received[r]) { + const FaceRecord key{query.vertices, 0}; + const auto [lower, upper] = + std::equal_range(faceTable.begin(), faceTable.end(), key, byVertices); + if (upper - lower > 1) { + logError() << "A face of an element exists multiple times in the surface mesh."; + } + replies[r].push_back({query.localFace, lower != upper ? lower->bc : NoMatch}); + } + } + + boundaries.assign(numLocalCells * (Dim + 1), 0); + for (const auto& answersFrom : distributed::exchange(replies, comm)) { + for (const auto& reply : answersFrom) { + if (reply.bc != NoMatch) { + const auto bc = + reply.bc >= BoundaryConditionOffset ? reply.bc - BoundaryConditionOffset : reply.bc; + boundaries[reply.localFace] = static_cast(bc); + } + } + } + } + + void identifyPeriodicNodes() { + if (!hasIdentify_) { + return; + } + // rank 0 joins the periodic nodes into classes, represented by their smallest vertex + std::vector> outgoing(size); + if (rank == 0) { + std::unordered_map parent; + const auto find = [&](std::uint64_t vertex) { + auto it = parent.try_emplace(vertex, vertex).first; + while (it->second != it->first) { + const auto next = parent.find(it->second); + it->second = next->second; + it = next; + } + return it->first; + }; + for (const auto& [nodeTag, masterTag] : index.periodic) { + const auto a = find(nodeIndex(nodeTag)); + const auto b = find(nodeIndex(masterTag)); + parent[std::max(a, b)] = std::min(a, b); + } + std::vector vertices; + vertices.reserve(parent.size()); + for (const auto& entry : parent) { + vertices.push_back(entry.first); + } + for (const auto vertex : vertices) { + outgoing[vertexOwner(vertex)].push_back({vertex, find(vertex)}); + } + } + + const auto localFirst = localFirstVertex(); + identify.resize(localVertexCount()); + std::iota(identify.begin(), identify.end(), localFirst); + for (const auto& identifications : distributed::exchange(outgoing, comm)) { + for (const auto& identification : identifications) { + identify[identification.vertex - localFirst] = identification.representative; + } + } + } + + MPI_Comm comm; + int rank = 0; + int size = 1; + Msh4Index index; + std::uint64_t numCells = 0; + bool hasIdentify_ = false; + std::vector geometry; + std::vector connectivity; + std::vector groups; + std::vector boundaries; + std::vector identify; +}; + +} // namespace puml + +#endif // PUMGEN_SRC_MESHREADER_DISTRIBUTEDGMSHREADER_H_ diff --git a/src/meshreader/GMSH2Parser.cpp b/src/meshreader/GMSH2Parser.cpp new file mode 100644 index 0000000..4365f3b --- /dev/null +++ b/src/meshreader/GMSH2Parser.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2022 SeisSol Group +// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München +// +// SPDX-License-Identifier: BSD-3-Clause +#include "GMSH2Parser.h" + +#include +#include +#include +#include + +namespace puml { + +void GMSH2Parser::parse_() { + const auto format = parseMeshFormatHeader(); + if (format.version < 2.0 || format.version >= 3.0) { + char buf[128]; + snprintf(buf, sizeof(buf), "Unsupported MSH version %.1lf", format.version); + failFile(buf); + } + if (format.fileType != 0) { + failFile("Binary MSH 2 files are not supported; write the mesh as ASCII (Mesh.Binary = 0)"); + } + expectToken("$EndMeshFormat"); + + bool hasElements = false; + while (true) { + const auto section = nextSection(); + if (section.empty()) { + break; + } + if (section == "$Nodes") { + parseNodes(); + } else if (section == "$Elements") { + parseElements(); + hasElements = true; + } else if (section == "$Periodic") { + parsePeriodic(); + } else { + skipSection(section); + } + } + + if (!hasNodes) { + failFile("Missing $Nodes section"); + } + if (!hasElements) { + failFile("Missing $Elements section"); + } +} + +std::size_t GMSH2Parser::readNodeIndex() { + input->skipWhitespace(); + const auto offset = input->offset(); + const auto tag = input->readInteger(); + if (!tag || *tag < 1 || *tag > numNodes) { + failAt(offset, "Unknown node tag"); + } + return *tag - 1; +} + +void GMSH2Parser::parseNodes() { + const std::size_t numVertices = expectSize(); + builder->setNumVertices(numVertices); + hasNodes = true; + numNodes = numVertices; + + // with numVertices distinct tags in [1, numVertices], every node is defined exactly once + std::vector defined(numVertices, false); + + for (std::size_t i = 0; i < numVertices; ++i) { + input->skipWhitespace(); + const auto offset = input->offset(); + const auto tag = input->readInteger(); + if (!tag || *tag < 1 || *tag > numVertices) { + failAt(offset, "Expected node-tag with 1 <= node-tag <= " + std::to_string(numVertices)); + } + const std::size_t id = *tag - 1; + if (defined[id]) { + failAt(offset, "Duplicate node tag " + std::to_string(*tag)); + } + defined[id] = true; + + std::array x{}; + for (auto& coordinate : x) { + coordinate = expectNumber(); + } + builder->setVertex(static_cast(id), x); + } + expectToken("$EndNodes"); +} + +void GMSH2Parser::parseElements() { + if (!hasNodes) { + fail("Expected $Nodes before $Elements"); + } + const std::size_t numElements = expectSize(); + builder->setNumElements(numElements); + + std::array nodes{}; + + for (std::size_t elementIdx = 0; elementIdx < numElements; ++elementIdx) { + expectInteger(); // element tag + input->skipWhitespace(); + const auto typeOffset = input->offset(); + const long type = expectInteger(); + if (type < 1 || type > static_cast(NumTypes) || NumNodes[type - 1] == 0) { + failAt(typeOffset, "Unknown element type " + std::to_string(type)); + } + const std::size_t numTags = expectSize(); + // the physical tag; elements without tags get 0 + long tag = 0; + for (std::size_t tagIdx = 0; tagIdx < numTags; ++tagIdx) { + const long value = expectInteger(); + if (tagIdx == 0) { + tag = value; + } + } + const std::size_t numNodesPerElement = NumNodes[type - 1]; + for (std::size_t nodeIdx = 0; nodeIdx < numNodesPerElement; ++nodeIdx) { + nodes[nodeIdx] = static_cast(readNodeIndex()); + } + builder->addElement(type, tag, nodes.data(), numNodesPerElement); + } + expectToken("$EndElements"); +} + +void GMSH2Parser::parsePeriodic() { + if (!hasNodes) { + fail("Expected $Nodes before $Periodic"); + } + const std::size_t numPeriodic = expectSize(); + + // only the node identification is needed + for (std::size_t blockIdx = 0; blockIdx < numPeriodic; ++blockIdx) { + expectSize(); // entity dimension + expectInteger(); // entity tag + expectInteger(); // master entity tag + if (input->peek() == 'A') { + // "Affine" and 16 values + expectToken("Affine"); + for (int i = 0; i < 16; ++i) { + expectNumber(); + } + } + const std::size_t numIdentified = expectSize(); + for (std::size_t i = 0; i < numIdentified; ++i) { + const std::size_t node = readNodeIndex(); + const std::size_t master = readNodeIndex(); + builder->addVertexLink(node, master); + } + } + expectToken("$EndPeriodic"); +} + +} // namespace puml diff --git a/src/meshreader/GMSH2Parser.h b/src/meshreader/GMSH2Parser.h new file mode 100644 index 0000000..9b561e6 --- /dev/null +++ b/src/meshreader/GMSH2Parser.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 SeisSol Group +// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_MESHREADER_GMSH2PARSER_H_ +#define PUMGEN_SRC_MESHREADER_GMSH2PARSER_H_ + +#include + +#include "GMSHParser.h" + +namespace puml { + +/** + * Parser for MSH 2 files (ASCII). + */ +class GMSH2Parser : public GMSHParser { + public: + using GMSHParser::GMSHParser; + + private: + void parse_() override; + void parseNodes(); + void parseElements(); + void parsePeriodic(); + + /** + * Reads a node tag and returns the index of the node. + */ + std::size_t readNodeIndex(); + + // the node tags are 1, ..., numNodes + bool hasNodes = false; + std::size_t numNodes = 0; +}; + +} // namespace puml + +#endif // PUMGEN_SRC_MESHREADER_GMSH2PARSER_H_ diff --git a/src/meshreader/GMSH4Parser.cpp b/src/meshreader/GMSH4Parser.cpp index ab36671..6e1b53c 100644 --- a/src/meshreader/GMSH4Parser.cpp +++ b/src/meshreader/GMSH4Parser.cpp @@ -3,321 +3,392 @@ // SPDX-License-Identifier: BSD-3-Clause #include "GMSH4Parser.h" -#include +#include +#include +#include #include +#include +#include +#include +#include #include "utils/logger.h" namespace puml { -bool GMSH4Parser::parse_() { - errorMsg.clear(); - getNextToken(); - const double version = parseMeshFormat(); - if (version < 4.0 || version >= 5.0) { +namespace { +template T swapBytesOf(T value) { + std::array bytes{}; + std::memcpy(bytes.data(), &value, sizeof(T)); + std::reverse(bytes.begin(), bytes.end()); + std::memcpy(&value, bytes.data(), sizeof(T)); + return value; +} + +// blocks of binary element data are read in pieces of this many elements +constexpr std::size_t ElementChunk = 1 << 16; +} // namespace + +void GMSH4Parser::parse_() { + const auto format = parseMeshFormatHeader(); + if (format.version < 4.0 || format.version >= 5.0) { char buf[128]; - sprintf(buf, "Unsupported MSH version %.1lf", version); - return logError(buf); + snprintf(buf, sizeof(buf), "Unsupported MSH version %.1lf", format.version); + failFile(buf); + } + if (format.version < 4.1) { + failFile("MSH 4.0 is not supported; write the mesh as MSH 4.1 (Mesh.MshFileVersion = 4.1)"); + } + if (format.fileType == 1) { + binary = true; + dataSize = format.dataSize; + if (dataSize != 4 && dataSize != 8) { + failFile("Unsupported data size " + std::to_string(dataSize) + " (expected 4 or 8)"); + } + beginSectionData(); + std::int32_t one = 0; + readBinary(&one, sizeof(one)); + if (swapBytesOf(one) == 1) { + swapBytes = true; + } else if (one != 1) { + failFile("Invalid byte order mark in the binary MSH file"); + } + } else if (format.fileType != 0) { + failFile("Expected file type 0 (ASCII) or 1 (binary)"); } + expectToken("$EndMeshFormat"); bool hasEntities = false; - bool hasNodes = false; bool hasElements = false; - bool hasPeriodic = false; - - while (curTok != tndm::GMSHToken::eof) { - switch (curTok) { - case tndm::GMSHToken::entities: - hasEntities = parseEntities(); - break; - case tndm::GMSHToken::nodes: - hasNodes = parseNodes(); - break; - case tndm::GMSHToken::elements: - hasElements = parseElements(); - break; - case tndm::GMSHToken::periodic: - hasPeriodic = parsePeriodic(version >= 4.1); - break; - default: - getNextToken(); + while (true) { + const auto section = nextSection(); + if (section.empty()) { break; } + if (section == "$Entities") { + parseEntities(); + hasEntities = true; + } else if (section == "$Nodes") { + parseNodes(); + } else if (section == "$Elements") { + parseElements(); + hasElements = true; + } else if (section == "$Periodic") { + parsePeriodic(); + } else if (section == "$PartitionedEntities") { + failFile("Partitioned MSH files are not supported; save the mesh without partitions"); + } else { + skipSection(section); + } } - return hasEntities && hasNodes && hasElements; + if (!hasEntities) { + failFile("Missing $Entities section"); + } + if (!hasNodes) { + failFile("Missing $Nodes section"); + } + if (!hasElements) { + failFile("Missing $Elements section"); + } + if (!unassignedVolumes.empty() || !unassignedSurfaces.empty()) { + logWarning() << "The elements of" << unassignedVolumes.size() << "volume(s) and" + << unassignedSurfaces.size() + << "surface(s) without a physical group get group or boundary condition 0"; + } } -bool GMSH4Parser::parseEntities() { - getNextToken(); - const std::size_t numPoints = expectNonNegativeInt(); - getNextToken(); - const std::size_t numCurves = expectNonNegativeInt(); - getNextToken(); - const std::size_t numSurfaces = expectNonNegativeInt(); - getNextToken(); - const std::size_t numVolumes = expectNonNegativeInt(); - - // ignore points - for (std::size_t i = 0; i < numPoints; ++i) { - getNextToken(); - [[maybe_unused]] const std::size_t id = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const double x_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_1 = expectNumber(); - getNextToken(); - const std::size_t numTags = expectNonNegativeInt(); - // ignore tags - for (std::size_t tagIdx = 0; tagIdx < numTags; tagIdx++) { - getNextToken(); +void GMSH4Parser::beginSectionData() { + if (binary) { + if (!input->readLineBreak()) { + fail("Expected a line break before the binary data"); } + binaryData = true; } +} - // ignore curves - for (std::size_t i = 0; i < numCurves; ++i) { - getNextToken(); - [[maybe_unused]] const std::size_t id = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const double x_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double x_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_2 = expectNumber(); - getNextToken(); - const std::size_t numPhysicalTags = expectNonNegativeInt(); - getNextToken(); - // ignore tags - for (std::size_t tagIdx = 0; tagIdx < numPhysicalTags; tagIdx++) { - getNextToken(); - } - std::size_t numBoundaryTags = expectNonNegativeInt(); - // ignore boundary - for (std::size_t tagIdx = 0; tagIdx < numBoundaryTags; tagIdx++) { - getNextToken(); - } +void GMSH4Parser::readBinary(void* data, std::size_t bytes) { + if (!input->readRaw(data, bytes)) { + failFile("Unexpected end of file in binary data"); + } +} + +std::size_t GMSH4Parser::readSize() { + if (!binary) { + return expectSize(); + } + std::size_t value = 0; + readSizes(&value, 1); + return value; +} + +long GMSH4Parser::readInt() { + if (!binary) { + return expectInteger(); + } + std::int32_t value = 0; + readBinary(&value, sizeof(value)); + return swapBytes ? swapBytesOf(value) : value; +} + +double GMSH4Parser::readDouble() { + if (!binary) { + return expectNumber(); } + double value = 0; + readDoubles(&value, 1); + return value; +} - // read surfaces - for (std::size_t i = 0; i < numSurfaces; ++i) { - getNextToken(); - const std::size_t id = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const double x_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double x_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_2 = expectNumber(); - getNextToken(); - const std::size_t numPhysicalTags = expectNonNegativeInt(); - std::vector tags; - for (std::size_t tagIdx = 0; tagIdx < numPhysicalTags; ++tagIdx) { - getNextToken(); - tags.push_back(expectNonNegativeInt()); +void GMSH4Parser::readSizes(std::size_t* values, std::size_t count) { + if (!binary) { + for (std::size_t i = 0; i < count; ++i) { + values[i] = expectSize(); } - if (!tags.empty()) { - physicalSurfaceIds.insert_or_assign(id, tags.at(0)); + return; + } + if (dataSize == sizeof(std::uint64_t)) { + static_assert(sizeof(std::size_t) == sizeof(std::uint64_t)); + readBinary(values, count * sizeof(std::uint64_t)); + if (swapBytes) { + std::transform(values, values + count, values, swapBytesOf); } - getNextToken(); - std::size_t numBoundaryTags = expectNonNegativeInt(); - // ignore boundary - for (std::size_t curveIdx = 0; curveIdx < numBoundaryTags; ++curveIdx) { - getNextToken(); + } else { + narrowSizes.resize(count); + readBinary(narrowSizes.data(), count * sizeof(std::uint32_t)); + for (std::size_t i = 0; i < count; ++i) { + values[i] = swapBytes ? swapBytesOf(narrowSizes[i]) : narrowSizes[i]; } } +} - // read volumes - for (std::size_t i = 0; i < numVolumes; ++i) { - getNextToken(); - const std::size_t id = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const double x_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_1 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double x_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double y_2 = expectNumber(); - getNextToken(); - [[maybe_unused]] const double z_2 = expectNumber(); - getNextToken(); - const std::size_t numPhysicalTags = expectNonNegativeInt(); - std::vector tags; - for (std::size_t tagIdx = 0; tagIdx < numPhysicalTags; ++tagIdx) { - getNextToken(); - tags.push_back(expectNonNegativeInt()); - } - if (!tags.empty()) { - physicalVolumeIds.insert_or_assign(id, tags.at(0)); - } - getNextToken(); - const std::size_t numBoundaryTags = expectNonNegativeInt(); - // ignore boundary - for (std::size_t curveIdx = 0; curveIdx < numBoundaryTags; ++curveIdx) { - getNextToken(); +void GMSH4Parser::readDoubles(double* values, std::size_t count) { + if (!binary) { + for (std::size_t i = 0; i < count; ++i) { + values[i] = expectNumber(); } + return; } - getNextToken(); - if (curTok != tndm::GMSHToken::end_entities) { - return logErrorAnnotated("Expected $EndEntities"); + readBinary(values, count * sizeof(double)); + if (swapBytes) { + std::transform(values, values + count, values, swapBytesOf); } - getNextToken(); - return true; } -bool GMSH4Parser::parseNodes() { - getNextToken(); - const std::size_t numBlocks = expectNonNegativeInt(); - getNextToken(); - const std::size_t numVertices = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t minNodeTag = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t maxNodeTag = expectNonNegativeInt(); - builder->setNumVertices(numVertices); +std::size_t GMSH4Parser::position() { + if (!binary) { + input->skipWhitespace(); + } + return input->offset(); +} + +std::size_t GMSH4Parser::nodeIndex(std::size_t tag, std::size_t offset) { + if (tag < firstNodeTag || tag - firstNodeTag >= numNodes) { + failAt(offset, "Unknown node tag " + std::to_string(tag)); + } + return tag - firstNodeTag; +} + +void GMSH4Parser::parseEntities() { + beginSectionData(); + const std::size_t numPoints = readSize(); + const std::size_t numCurves = readSize(); + const std::size_t numSurfaces = readSize(); + const std::size_t numVolumes = readSize(); - for (std::size_t blockIdx = 0; blockIdx < numBlocks; blockIdx++) { - getNextToken(); - [[maybe_unused]] const std::size_t dim = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t entityTag = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t parametric = expectNonNegativeInt(); - getNextToken(); - const std::size_t numVerticesInBlock = expectNonNegativeInt(); - std::vector vertexIds; - vertexIds.reserve(numVerticesInBlock); - // first read vertex ids - for (std::size_t vertexIdx = 0; vertexIdx < numVerticesInBlock; ++vertexIdx) { - getNextToken(); - vertexIds.push_back(expectNonNegativeInt() - 1); + for (std::size_t i = 0; i < numPoints; ++i) { + readInt(); + for (int k = 0; k < 3; ++k) { + readDouble(); } - // then read vertex data - for (std::size_t vertexIdx = 0; vertexIdx < numVerticesInBlock; ++vertexIdx) { - std::array x{}; - for (std::size_t i = 0; i < 3; i++) { - getNextToken(); - x[i] = expectNumber(); - } - assert(vertexIds.at(vertexIdx) < numVertices); - builder->setVertex(vertexIds.at(vertexIdx), x); + const std::size_t numPhysicalTags = readSize(); + for (std::size_t j = 0; j < numPhysicalTags; ++j) { + readInt(); } } - getNextToken(); - if (curTok != tndm::GMSHToken::end_nodes) { - return logErrorAnnotated("Expected $EndNodes"); - } - getNextToken(); - return true; + + const auto readEntities = [&](std::size_t count, std::map* physicalIds) { + for (std::size_t i = 0; i < count; ++i) { + const long tag = readInt(); + for (int k = 0; k < 6; ++k) { + readDouble(); + } + const std::size_t numPhysicalTags = readSize(); + for (std::size_t j = 0; j < numPhysicalTags; ++j) { + const long physical = readInt(); + if (j == 0 && physicalIds != nullptr) { + physicalIds->insert_or_assign(tag, physical); + } + } + const std::size_t numBoundingEntities = readSize(); + for (std::size_t j = 0; j < numBoundingEntities; ++j) { + readInt(); + } + } + }; + readEntities(numCurves, nullptr); + readEntities(numSurfaces, &physicalSurfaceIds); + readEntities(numVolumes, &physicalVolumeIds); + + expectToken("$EndEntities"); } -bool GMSH4Parser::parseElements() { - getNextToken(); - const std::size_t numBlocks = expectNonNegativeInt(); - getNextToken(); - const std::size_t numElements = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t minElementTag = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t maxElementTag = expectNonNegativeInt(); - builder->setNumElements(numElements); +void GMSH4Parser::parseNodes() { + beginSectionData(); + const std::size_t numBlocks = readSize(); + const std::size_t numVertices = readSize(); + const std::size_t minNodeTag = readSize(); + const std::size_t maxNodeTag = readSize(); + if (numVertices > 0 && + (minNodeTag == 0 || maxNodeTag < minNodeTag || maxNodeTag - minNodeTag + 1 != numVertices)) { + char buf[192]; + snprintf(buf, sizeof(buf), + "Non-contiguous node tags are not supported (%zu nodes with tags from %zu to %zu)", + numVertices, minNodeTag, maxNodeTag); + fail(buf); + } + hasNodes = true; + firstNodeTag = minNodeTag; + numNodes = numVertices; + builder->setNumVertices(numVertices); - constexpr std::size_t MaxNodes = sizeof(NumNodes) / sizeof(std::size_t); - std::array nodes{}; - builder->setNumElements(numElements); + // with as many distinct tags as the tag range is long, every node is defined exactly once + std::vector defined(numVertices, false); + std::vector indices; + std::vector coordinates; + + for (std::size_t blockIdx = 0; blockIdx < numBlocks; ++blockIdx) { + const long dim = readInt(); + readInt(); // entity tag + const long parametric = readInt(); + const std::size_t numVerticesInBlock = readSize(); - for (std::size_t blockIdx = 0; blockIdx < numBlocks; blockIdx++) { - getNextToken(); - [[maybe_unused]] const std::size_t dim = expectNonNegativeInt(); - getNextToken(); - const std::size_t entityTag = expectNonNegativeInt(); - getNextToken(); - const std::size_t type = expectNonNegativeInt(); - getNextToken(); - const std::size_t numElementsInBlock = expectNonNegativeInt(); - for (std::size_t elementIdx = 0; elementIdx < numElementsInBlock; ++elementIdx) { - getNextToken(); - [[maybe_unused]] const std::size_t id = expectNonNegativeInt(); - for (std::size_t nodeIdx = 0; nodeIdx < NumNodes[type - 1]; nodeIdx++) { - getNextToken(); - nodes.at(nodeIdx) = expectNonNegativeInt() - 1; + indices.resize(numVerticesInBlock); + const auto tagsOffset = input->offset(); + if (binary) { + readSizes(indices.data(), numVerticesInBlock); + } + for (std::size_t i = 0; i < numVerticesInBlock; ++i) { + auto offset = tagsOffset + i * dataSize; + if (!binary) { + offset = position(); + indices[i] = expectSize(); + } + const std::size_t index = nodeIndex(indices[i], offset); + if (defined[index]) { + failAt(offset, "Duplicate node tag " + std::to_string(indices[i])); } - std::size_t tag = (type == 2) ? physicalSurfaceIds[entityTag] : physicalVolumeIds[entityTag]; - builder->addElement(type, tag, nodes.data(), NumNodes[type - 1]); + defined[index] = true; + indices[i] = index; + } + + // parametric nodes carry dim additional parametric coordinates + const std::size_t valuesPerNode = 3 + (parametric != 0 ? static_cast(dim) : 0); + coordinates.resize(numVerticesInBlock * valuesPerNode); + readDoubles(coordinates.data(), coordinates.size()); + for (std::size_t i = 0; i < numVerticesInBlock; ++i) { + const double* x = &coordinates[i * valuesPerNode]; + builder->setVertex(static_cast(indices[i]), {x[0], x[1], x[2]}); } } - getNextToken(); - if (curTok != tndm::GMSHToken::end_elements) { - return logErrorAnnotated("Expected $EndElements"); + expectToken("$EndNodes"); +} + +void GMSH4Parser::parseElements() { + if (!hasNodes) { + fail("Expected $Nodes before $Elements"); } - getNextToken(); + beginSectionData(); + const std::size_t numBlocks = readSize(); + const std::size_t numElements = readSize(); + readSize(); // minimal element tag + readSize(); // maximal element tag + builder->setNumElements(numElements); - return true; -} + std::array nodes{}; + std::vector values; + + for (std::size_t blockIdx = 0; blockIdx < numBlocks; ++blockIdx) { + const long dim = readInt(); + const long entityTag = readInt(); + const auto typeOffset = position(); + const long type = readInt(); + if (type < 1 || type > static_cast(NumTypes) || NumNodes[type - 1] == 0) { + failAt(typeOffset, "Unknown element type " + std::to_string(type)); + } + const std::size_t numNodesPerElement = NumNodes[type - 1]; + const std::size_t numElementsInBlock = readSize(); + const long tag = numElementsInBlock > 0 ? physicalTag(dim, entityTag) : 0; -bool GMSH4Parser::parsePeriodic(bool variableAffine) { - getNextToken(); - const auto numPeriodic = expectNonNegativeInt(); - - // ignore everything but the node identification - - for (std::size_t blockIdx = 0; blockIdx < numPeriodic; blockIdx++) { - getNextToken(); - [[maybe_unused]] const std::size_t entityDim = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t entityId = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t entityIdentifyId = expectNonNegativeInt(); - - // for MSH4.1 vs MSH4.0 - const auto affineSize = [&]() -> std::size_t { - if (variableAffine) { - getNextToken(); - return expectNonNegativeInt(); - } else { - return 16; + // each element consists of its tag and its node tags + const std::size_t valuesPerElement = 1 + numNodesPerElement; + for (std::size_t first = 0; first < numElementsInBlock; first += ElementChunk) { + const std::size_t count = std::min(ElementChunk, numElementsInBlock - first); + const auto chunkOffset = input->offset(); + if (binary) { + values.resize(count * valuesPerElement); + readSizes(values.data(), values.size()); + } + for (std::size_t element = 0; element < count; ++element) { + if (!binary) { + readSize(); // element tag + } + for (std::size_t node = 0; node < numNodesPerElement; ++node) { + if (binary) { + const std::size_t valueIdx = element * valuesPerElement + 1 + node; + nodes[node] = + static_cast(nodeIndex(values[valueIdx], chunkOffset + valueIdx * dataSize)); + } else { + const auto offset = position(); + nodes[node] = static_cast(nodeIndex(expectSize(), offset)); + } + } + builder->addElement(type, tag, nodes.data(), numNodesPerElement); } - }(); - for (std::size_t i = 0; i < affineSize; ++i) { - getNextToken(); - [[maybe_unused]] const double affineValue = expectNumber(); } + } + expectToken("$EndElements"); +} - getNextToken(); - const std::size_t identifySize = expectNonNegativeInt(); +void GMSH4Parser::parsePeriodic() { + if (!hasNodes) { + fail("Expected $Nodes before $Periodic"); + } + beginSectionData(); + const std::size_t numPeriodic = readSize(); - for (std::size_t i = 0; i < identifySize; ++i) { - getNextToken(); - const std::size_t nodeId = expectNonNegativeInt() - 1; - getNextToken(); - const std::size_t nodeIdentifyId = expectNonNegativeInt() - 1; - builder->addVertexLink(nodeId, nodeIdentifyId); + // only the node identification is needed + for (std::size_t blockIdx = 0; blockIdx < numPeriodic; ++blockIdx) { + readInt(); // entity dimension + readInt(); // entity tag + readInt(); // master entity tag + const std::size_t numAffine = readSize(); + for (std::size_t i = 0; i < numAffine; ++i) { + readDouble(); + } + const std::size_t numIdentified = readSize(); + for (std::size_t i = 0; i < numIdentified; ++i) { + const auto nodeOffset = position(); + const std::size_t node = nodeIndex(readSize(), nodeOffset); + const auto masterOffset = position(); + const std::size_t master = nodeIndex(readSize(), masterOffset); + builder->addVertexLink(node, master); } } + expectToken("$EndPeriodic"); +} - getNextToken(); - if (curTok != tndm::GMSHToken::end_periodic) { - return logErrorAnnotated("Expected $EndPeriodic"); +long GMSH4Parser::physicalTag(std::size_t dim, long entityTag) { + if (dim != 2 && dim != 3) { + return 0; } - getNextToken(); - - return true; + const auto& physicalIds = dim == 3 ? physicalVolumeIds : physicalSurfaceIds; + const auto it = physicalIds.find(entityTag); + if (it == physicalIds.end()) { + (dim == 3 ? unassignedVolumes : unassignedSurfaces).insert(entityTag); + return 0; + } + return it->second; } } // namespace puml diff --git a/src/meshreader/GMSH4Parser.h b/src/meshreader/GMSH4Parser.h index 92f7ff4..bc7794c 100644 --- a/src/meshreader/GMSH4Parser.h +++ b/src/meshreader/GMSH4Parser.h @@ -4,44 +4,75 @@ #ifndef PUMGEN_SRC_MESHREADER_GMSH4PARSER_H_ #define PUMGEN_SRC_MESHREADER_GMSH4PARSER_H_ +#include +#include #include -#include -#include +#include +#include -#include "meshreader/GMSHBuilder.h" -#include "third_party/GMSHLexer.h" -#include "third_party/GMSHParser.h" +#include "GMSHParser.h" namespace puml { -class GMSH4Parser : public tndm::GMSHParser { +/** + * Parser for MSH 4.1 files, ASCII or binary. + */ +class GMSH4Parser : public GMSHParser { public: - using tndm::GMSHParser::GMSHParser; - - private: - std::map physicalSurfaceIds; - std::map physicalVolumeIds; - - unsigned long expectNonNegativeInt() { - if (curTok != tndm::GMSHToken::integer || lexer.getInteger() < 0) { - return logErrorAnnotated("Expected non-negative integer"); - } - return static_cast(lexer.getInteger()); - } - - double expectNumber() { - auto num = getNumber(); - if (!num) { - return logErrorAnnotated("Expected number"); - } - return num.value(); - } - - bool parseEntities(); - bool parseNodes(); - bool parseElements(); - bool parsePeriodic(bool variableAffine); - virtual bool parse_() override; + using GMSHParser::GMSHParser; + + protected: + void parse_() override; + void parseEntities(); + // overridden by readers which only locate the data of these sections + virtual void parseNodes(); + virtual void parseElements(); + virtual void parsePeriodic(); + + /** + * Starts the data of a section; in binary files, the data begins after the line break. + */ + void beginSectionData(); + + // the values of the sections: text, or binary with sizeof(size_t) == dataSize and 4-byte ints + std::size_t readSize(); + long readInt(); + double readDouble(); + // bulk reading of binary sizes and doubles + void readSizes(std::size_t* values, std::size_t count); + void readDoubles(double* values, std::size_t count); + void readBinary(void* data, std::size_t bytes); + + /** + * The offset of the next value, e.g. for error messages. + */ + std::size_t position(); + + /** + * The index of the node with the given tag; the tag started at the given file offset. + */ + std::size_t nodeIndex(std::size_t tag, std::size_t offset); + + bool binary = false; + bool swapBytes = false; + std::size_t dataSize = 8; + std::vector narrowSizes; + + /** + * The physical tag of the elements of an entity; 0 if the entity has no physical group. + */ + long physicalTag(std::size_t dim, long entityTag); + + std::map physicalSurfaceIds; + std::map physicalVolumeIds; + // surface and volume entities with elements, but without a physical group + std::set unassignedSurfaces; + std::set unassignedVolumes; + + // the node tags form the contiguous range [firstNodeTag, firstNodeTag + numNodes) + bool hasNodes = false; + std::size_t firstNodeTag = 0; + std::size_t numNodes = 0; }; } // namespace puml diff --git a/src/meshreader/GMSHBuilder.h b/src/meshreader/GMSHBuilder.h index d47cebc..287eda1 100644 --- a/src/meshreader/GMSHBuilder.h +++ b/src/meshreader/GMSHBuilder.h @@ -14,7 +14,7 @@ #include "utils/logger.h" -#include "third_party/GMSHMeshBuilder.h" +#include "GMSHMeshBuilder.h" namespace puml { @@ -143,11 +143,11 @@ template <> struct GMSHSimplexType<3U, 10U> { static constexpr long type = 75; }; -template class GMSHBuilder : public tndm::GMSHMeshBuilder { +template class GMSHBuilder : public GMSHMeshBuilder { public: using vertex_t = std::array; using element_t = std::array; - using facet_t = std::array; + using facet_t = std::array; std::vector vertices; std::vector identify; diff --git a/src/third_party/GMSHMeshBuilder.h b/src/meshreader/GMSHMeshBuilder.h similarity index 73% rename from src/third_party/GMSHMeshBuilder.h rename to src/meshreader/GMSHMeshBuilder.h index a66ac2a..d72d1d8 100644 --- a/src/third_party/GMSHMeshBuilder.h +++ b/src/meshreader/GMSHMeshBuilder.h @@ -1,12 +1,13 @@ // SPDX-FileCopyrightText: 2022 SeisSol Group // // SPDX-License-Identifier: BSD-3-Clause -#ifndef PUMGEN_SRC_THIRD_PARTY_GMSHMESHBUILDER_H_ -#define PUMGEN_SRC_THIRD_PARTY_GMSHMESHBUILDER_H_ +#ifndef PUMGEN_SRC_MESHREADER_GMSHMESHBUILDER_H_ +#define PUMGEN_SRC_MESHREADER_GMSHMESHBUILDER_H_ #include +#include -namespace tndm { +namespace puml { class GMSHMeshBuilder { public: @@ -19,6 +20,6 @@ class GMSHMeshBuilder { virtual void postprocess() = 0; }; -} // namespace tndm +} // namespace puml -#endif // PUMGEN_SRC_THIRD_PARTY_GMSHMESHBUILDER_H_ +#endif // PUMGEN_SRC_MESHREADER_GMSHMESHBUILDER_H_ diff --git a/src/meshreader/GMSHParser.cpp b/src/meshreader/GMSHParser.cpp new file mode 100644 index 0000000..5c0c3bd --- /dev/null +++ b/src/meshreader/GMSHParser.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2022 SeisSol Group +// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München +// +// SPDX-License-Identifier: BSD-3-Clause +#include "GMSHParser.h" + +#include +#include +#include + +namespace puml { + +bool GMSHParser::parseFile(const std::string& fileName) { + errorMsg.clear(); + try { + input = std::make_unique(fileName, bufferSize); + parse_(); + input.reset(); + return true; + } catch (const ParseError&) { + } catch (const std::exception& error) { + errorMsg += "GMSH parser error:\n\t"; + errorMsg += error.what(); + errorMsg += '\n'; + } + input.reset(); + return false; +} + +void GMSHParser::failAt(std::size_t offset, std::string_view message) { + std::stringstream stream; + if (binaryData) { + stream << "GMSH parser error at byte " << offset << ":\n"; + } else { + const auto [line, column] = input->location(offset); + stream << "GMSH parser error in line " << line << " in column " << column << ":\n"; + } + stream << '\t' << message << '\n'; + errorMsg += stream.str(); + throw ParseError{}; +} + +void GMSHParser::fail(std::string_view message) { + if (!binaryData) { + input->skipWhitespace(); + } + failAt(input->offset(), message); +} + +void GMSHParser::failFile(std::string_view message) { + errorMsg += "GMSH parser error:\n\t"; + errorMsg += message; + errorMsg += '\n'; + throw ParseError{}; +} + +void GMSHParser::expectToken(std::string_view token) { + binaryData = false; + input->skipWhitespace(); + const auto offset = input->offset(); + if (input->readToken() != token) { + failAt(offset, "Expected " + std::string(token)); + } +} + +GMSHParser::MeshFormat GMSHParser::parseMeshFormatHeader() { + expectToken("$MeshFormat"); + MeshFormat format{}; + format.version = expectNumber(); + format.fileType = expectInteger(); + format.dataSize = expectSize(); + return format; +} + +std::string_view GMSHParser::nextSection() { + if (!input->skipWhitespace()) { + return {}; + } + const auto offset = input->offset(); + const auto section = input->readToken(); + if (section.empty() || section.front() != '$') { + failAt(offset, "Expected a section"); + } + return section; +} + +void GMSHParser::skipSection(std::string_view section) { + const std::string endMarker = "$End" + std::string(section.substr(1)); + if (!input->skipTo(endMarker)) { + failFile("Missing " + endMarker); + } +} + +} // namespace puml diff --git a/src/third_party/GMSHParser.h b/src/meshreader/GMSHParser.h similarity index 58% rename from src/third_party/GMSHParser.h rename to src/meshreader/GMSHParser.h index dbb7974..f71f32c 100644 --- a/src/third_party/GMSHParser.h +++ b/src/meshreader/GMSHParser.h @@ -2,20 +2,32 @@ // SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München // // SPDX-License-Identifier: BSD-3-Clause -#ifndef PUMGEN_SRC_THIRD_PARTY_GMSHPARSER_H_ -#define PUMGEN_SRC_THIRD_PARTY_GMSHPARSER_H_ +#ifndef PUMGEN_SRC_MESHREADER_GMSHPARSER_H_ +#define PUMGEN_SRC_MESHREADER_GMSHPARSER_H_ -#include -#include -#include +#include +#include #include #include -#include "GMSHLexer.h" #include "GMSHMeshBuilder.h" +#include "MshInput.h" -namespace tndm { +namespace puml { +namespace detail { +template constexpr std::size_t maxEntry(const std::size_t (&values)[N]) { + std::size_t result = 0; + for (std::size_t i = 0; i < N; ++i) { + result = values[i] > result ? values[i] : result; + } + return result; +} +} // namespace detail + +/** + * Common parts of the MSH parsers: element types, error handling and reading of text values. + */ class GMSHParser { public: // Look-up table from gmsh type to number of nodes @@ -149,59 +161,98 @@ class GMSHParser { 61, // MSH_PYR_61 69, // MSH_PYR_69 }; + static constexpr std::size_t NumTypes = sizeof(NumNodes) / sizeof(NumNodes[0]); + static constexpr std::size_t MaxNodesPerElement = detail::maxEntry(NumNodes); + + explicit GMSHParser(GMSHMeshBuilder* builder, + std::size_t bufferSize = MshInput::DefaultBufferSize) + : builder(builder), bufferSize(bufferSize) {} + virtual ~GMSHParser() = default; + + /** + * Parses the file; stops at the first error, whose description getErrorMessage() returns. + */ + bool parseFile(const std::string& fileName); - explicit GMSHParser(GMSHMeshBuilder* builder) : builder(builder) {}; [[nodiscard]] std::string_view getErrorMessage() const { return errorMsg; } - bool parseFile(std::string const& fileName) { - std::ifstream in(fileName); - if (!in.is_open()) { - return logError("Unable to open MSH file"); + protected: + struct MeshFormat { + double version; + long fileType; + std::size_t dataSize; + }; + + /** + * Thrown after an error has been recorded in errorMsg. + */ + struct ParseError {}; + + /** + * Records an error at the given file offset and aborts parsing. + */ + [[noreturn]] void failAt(std::size_t offset, std::string_view message); + + /** + * Records an error at the next token and aborts parsing. + */ + [[noreturn]] void fail(std::string_view message); + + /** + * Records an error concerning the whole file and aborts parsing. + */ + [[noreturn]] void failFile(std::string_view message); + + std::size_t expectSize() { + if (const auto value = input->readInteger()) { + return *value; } - lexer.setIStream(&in); - return parse_(); + fail("Expected non-negative integer"); } - protected: - template T logErrorAnnotated(std::string_view msg) { - std::stringstream ss; - ss << "GMSH parser error in line " << curLoc.line << " in column " << curLoc.col << ":\n"; - ss << '\t' << msg << '\n'; - errorMsg += ss.str(); - return {}; + long expectInteger() { + if (const auto value = input->readInteger()) { + return *value; + } + fail("Expected integer"); } - template T logError(std::string_view msg) { - errorMsg += "GMSH parser error:\n\t"; - errorMsg += msg; - errorMsg += '\n'; - return {}; + double expectNumber() { + if (const auto value = input->readReal()) { + return *value; + } + fail("Expected number"); } - GMSHMeshBuilder* builder; - GMSHSourceLocation curLoc = {0, 0}; - GMSHToken curTok; - GMSHLexer lexer; - std::string errorMsg; + void expectToken(std::string_view token); - GMSHToken getNextToken() { - curLoc = lexer.getSourceLoc(); - return curTok = lexer.getToken(); - } + /** + * Reads the $MeshFormat section up to (and including) the data size. + */ + MeshFormat parseMeshFormatHeader(); - std::optional getNumber() { - if (curTok == GMSHToken::integer) { - return {lexer.getInteger()}; - } else if (curTok == GMSHToken::real) { - return {lexer.getReal()}; - } - return std::nullopt; - } + /** + * Returns the name of the next section (e.g. "$Nodes"), or an empty view at the end of the file. + */ + std::string_view nextSection(); + + /** + * Skips the rest of a section which is not needed. + */ + void skipSection(std::string_view section); + + virtual void parse_() = 0; - virtual bool parse_() = 0; + GMSHMeshBuilder* builder; + std::unique_ptr input; + // errors in binary data are located by byte offset instead of line and column + bool binaryData = false; - double parseMeshFormat(); + private: + std::size_t bufferSize; + std::string errorMsg; }; -} // namespace tndm -#endif // PUMGEN_SRC_THIRD_PARTY_GMSHPARSER_H_ +} // namespace puml + +#endif // PUMGEN_SRC_MESHREADER_GMSHPARSER_H_ diff --git a/src/meshreader/GambitReader.h b/src/meshreader/GambitReader.h index a00d653..260f649 100644 --- a/src/meshreader/GambitReader.h +++ b/src/meshreader/GambitReader.h @@ -393,7 +393,7 @@ class GambitReader : public MeshReader { // Get the group, were we should start reading std::vector::const_iterator section; - for (section = m_groups.begin(); section != m_groups.end() && section->nLines < start; + for (section = m_groups.begin(); section != m_groups.end() && section->nLines <= start; section++) { start -= section->nLines; } @@ -417,7 +417,9 @@ class GambitReader : public MeshReader { start = 0; section++; - m_mesh.seekg(section->seekPosition); + if (section != m_groups.end()) { + m_mesh.seekg(section->seekPosition); + } } else if (start % ELEMENTS_PER_LINE_GROUP == 0) // Skip newline char at end of line m_mesh.seekg(section->lineSize - section->elementSize * ELEMENTS_PER_LINE_GROUP, @@ -456,7 +458,7 @@ class GambitReader : public MeshReader { // Get the boundary, were we should start reading std::vector::const_iterator section; - for (section = m_boundaries.begin(); section != m_boundaries.end() && section->nLines < start; + for (section = m_boundaries.begin(); section != m_boundaries.end() && section->nLines <= start; section++) { start -= section->nLines; } diff --git a/src/meshreader/Msh4Index.cpp b/src/meshreader/Msh4Index.cpp new file mode 100644 index 0000000..a73b71c --- /dev/null +++ b/src/meshreader/Msh4Index.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "Msh4Index.h" + +#include +#include + +namespace puml { + +void Msh4Indexer::skipData(std::size_t bytes) { + if (!input->seek(input->offset() + bytes)) { + failFile("Unexpected end of file in binary data"); + } +} + +void Msh4Indexer::parseNodes() { + if (!binary) { + failFile("Only binary MSH 4.1 files can be read in parallel"); + } + beginSectionData(); + const std::size_t numBlocks = readSize(); + const std::size_t numVertices = readSize(); + const std::size_t minNodeTag = readSize(); + const std::size_t maxNodeTag = readSize(); + if (numVertices > 0 && + (minNodeTag == 0 || maxNodeTag < minNodeTag || maxNodeTag - minNodeTag + 1 != numVertices)) { + char buf[192]; + snprintf(buf, sizeof(buf), + "Non-contiguous node tags are not supported (%zu nodes with tags from %zu to %zu)", + numVertices, minNodeTag, maxNodeTag); + fail(buf); + } + hasNodes = true; + firstNodeTag = minNodeTag; + numNodes = numVertices; + index.dataSize = dataSize; + index.swapBytes = swapBytes; + index.firstNodeTag = minNodeTag; + index.numNodes = numVertices; + + std::size_t numInBlocks = 0; + for (std::size_t blockIdx = 0; blockIdx < numBlocks; ++blockIdx) { + const long dim = readInt(); + readInt(); // entity tag + const long parametric = readInt(); + const std::size_t count = readSize(); + Msh4NodeBlock block{}; + block.count = count; + block.valuesPerNode = 3 + (parametric != 0 ? static_cast(dim) : 0); + block.tagsOffset = input->offset(); + block.coordinatesOffset = block.tagsOffset + count * dataSize; + skipData(count * dataSize + count * block.valuesPerNode * sizeof(double)); + if (count > 0) { + index.nodeBlocks.push_back(block); + } + numInBlocks += count; + } + if (numInBlocks != numVertices) { + failFile("The node blocks contain " + std::to_string(numInBlocks) + " nodes instead of " + + std::to_string(numVertices)); + } + expectToken("$EndNodes"); +} + +void Msh4Indexer::parseElements() { + if (!hasNodes) { + fail("Expected $Nodes before $Elements"); + } + beginSectionData(); + const std::size_t numBlocks = readSize(); + readSize(); // number of elements + readSize(); // minimal element tag + readSize(); // maximal element tag + + for (std::size_t blockIdx = 0; blockIdx < numBlocks; ++blockIdx) { + const long dim = readInt(); + const long entityTag = readInt(); + const auto typeOffset = position(); + const long type = readInt(); + if (type < 1 || type > static_cast(NumTypes) || NumNodes[type - 1] == 0) { + failAt(typeOffset, "Unknown element type " + std::to_string(type)); + } + const std::size_t count = readSize(); + const Msh4ElementBlock block{count, input->offset(), + count > 0 ? physicalTag(dim, entityTag) : 0}; + skipData(count * (1 + NumNodes[type - 1]) * dataSize); + if (count > 0 && type == cellType) { + index.cellBlocks.push_back(block); + } else if (count > 0 && type == facetType) { + index.facetBlocks.push_back(block); + } + } + expectToken("$EndElements"); +} + +void Msh4Indexer::parsePeriodic() { + if (!hasNodes) { + fail("Expected $Nodes before $Periodic"); + } + beginSectionData(); + const std::size_t numPeriodic = readSize(); + for (std::size_t blockIdx = 0; blockIdx < numPeriodic; ++blockIdx) { + readInt(); // entity dimension + readInt(); // entity tag + readInt(); // master entity tag + const std::size_t numAffine = readSize(); + for (std::size_t i = 0; i < numAffine; ++i) { + readDouble(); + } + const std::size_t numIdentified = readSize(); + for (std::size_t i = 0; i < numIdentified; ++i) { + const auto nodeOffset = position(); + const std::size_t node = readSize(); + nodeIndex(node, nodeOffset); + const auto masterOffset = position(); + const std::size_t master = readSize(); + nodeIndex(master, masterOffset); + index.periodic.emplace_back(node, master); + } + } + expectToken("$EndPeriodic"); +} + +bool isBinaryMsh4(const std::string& fileName) { + try { + MshInput input(fileName); + if (input.readToken() != "$MeshFormat") { + return false; + } + const auto version = input.readReal(); + const auto fileType = input.readInteger(); + return version && fileType && *version >= 4.1 && *version < 5.0 && *fileType == 1; + } catch (const std::exception&) { + return false; + } +} + +} // namespace puml diff --git a/src/meshreader/Msh4Index.h b/src/meshreader/Msh4Index.h new file mode 100644 index 0000000..3d24d18 --- /dev/null +++ b/src/meshreader/Msh4Index.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_MESHREADER_MSH4INDEX_H_ +#define PUMGEN_SRC_MESHREADER_MSH4INDEX_H_ + +#include +#include +#include +#include + +#include "GMSH4Parser.h" + +namespace puml { + +/** + * A block of nodes in a binary MSH 4.1 file: first the tags, then the coordinates. + */ +struct Msh4NodeBlock { + std::uint64_t count; + std::uint64_t tagsOffset; + std::uint64_t coordinatesOffset; + // three coordinates and the parametric coordinates, if any + std::uint64_t valuesPerNode; +}; + +/** + * A block of elements of one type in a binary MSH 4.1 file: per element its tag and its nodes. + */ +struct Msh4ElementBlock { + std::uint64_t count; + std::uint64_t offset; + std::int64_t physical; +}; + +/** + * Where the data of a binary MSH 4.1 file lies. + */ +struct Msh4Index { + std::uint64_t dataSize = 8; + bool swapBytes = false; + // the node tags form the contiguous range [firstNodeTag, firstNodeTag + numNodes) + std::uint64_t firstNodeTag = 0; + std::uint64_t numNodes = 0; + std::vector nodeBlocks; + std::vector cellBlocks; + std::vector facetBlocks; + // pairs of node tags (node, master) from $Periodic + std::vector> periodic; +}; + +/** + * Reads the structure of a binary MSH 4.1 file (entities, block headers, periodic nodes) and + * skips the node and element data, recording where they lie. + */ +class Msh4Indexer : public GMSH4Parser { + public: + Msh4Indexer(long cellType, long facetType) + : GMSH4Parser(nullptr), cellType(cellType), facetType(facetType) {} + + [[nodiscard]] const Msh4Index& getIndex() const { return index; } + + private: + void parseNodes() override; + void parseElements() override; + void parsePeriodic() override; + + void skipData(std::size_t bytes); + + long cellType; + long facetType; + Msh4Index index; +}; + +/** + * Whether the file is a binary MSH 4.1 file; only reads its header. + */ +bool isBinaryMsh4(const std::string& fileName); + +} // namespace puml + +#endif // PUMGEN_SRC_MESHREADER_MSH4INDEX_H_ diff --git a/src/meshreader/MshInput.cpp b/src/meshreader/MshInput.cpp new file mode 100644 index 0000000..10683f2 --- /dev/null +++ b/src/meshreader/MshInput.cpp @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "MshInput.h" + +#include +#include +#include + +namespace puml { + +MshInput::MshInput(const std::string& fileName, std::size_t bufferSize) + : fileName(fileName), buffer(std::max(bufferSize, 2 * MaxTokenLength) + 1) { + file = std::fopen(fileName.c_str(), "rb"); + if (file == nullptr) { + throw std::runtime_error("Unable to open MSH file " + fileName); + } + buffer[0] = '\0'; +} + +MshInput::~MshInput() { + if (file != nullptr) { + std::fclose(file); + } +} + +std::string_view MshInput::readToken() { + if (!skipWhitespace()) { + return {}; + } + ensure(MaxTokenLength); + std::size_t stop = begin; + while (stop < end && !isSpace(buffer[stop]) && stop - begin < MaxTokenLength) { + ++stop; + } + const std::string_view token(buffer.data() + begin, stop - begin); + begin = stop; + return token; +} + +bool MshInput::readLineBreak() { + ensure(2); + if (begin < end && buffer[begin] == '\r') { + ++begin; + } + if (begin < end && buffer[begin] == '\n') { + ++begin; + return true; + } + return false; +} + +bool MshInput::readRaw(void* data, std::size_t bytes) { + auto* out = static_cast(data); + while (bytes > 0) { + if (begin == end && bytes >= (buffer.size() - 1) / 2) { + // large reads bypass the (empty) buffer + bufferOffset += end; + begin = end = 0; + const std::size_t read = std::fread(out, 1, bytes, file); + bufferOffset += read; + if (read < bytes) { + eof = true; + return false; + } + return true; + } + if (begin == end && !refill()) { + return false; + } + const std::size_t count = std::min(bytes, end - begin); + std::memcpy(out, buffer.data() + begin, count); + begin += count; + out += count; + bytes -= count; + } + return true; +} + +bool MshInput::seek(std::size_t offset) { + if (offset >= bufferOffset && offset <= bufferOffset + end) { + begin = offset - bufferOffset; + return true; + } + if (std::fseek(file, 0, SEEK_END) != 0) { + return false; + } + const auto size = static_cast(std::ftell(file)); + if (offset > size || std::fseek(file, static_cast(offset), SEEK_SET) != 0) { + return false; + } + bufferOffset = offset; + begin = end = 0; + buffer[0] = '\0'; + eof = false; + return true; +} + +bool MshInput::skipTo(std::string_view marker) { + while (true) { + const std::string_view data(buffer.data() + begin, end - begin); + const auto found = data.find(marker); + if (found != std::string_view::npos) { + begin += found + marker.size(); + return true; + } + // the tail may hold the beginning of the marker + begin = end - std::min(data.size(), marker.size() - 1); + if (!refill()) { + begin = end; + return false; + } + } +} + +std::pair MshInput::location(std::size_t offset) const { + std::size_t line = 1; + std::size_t column = 1; + std::FILE* scan = std::fopen(fileName.c_str(), "rb"); + if (scan == nullptr) { + return {line, column}; + } + std::vector chunk(std::size_t{1} << 20); + std::size_t position = 0; + while (position < offset) { + const std::size_t read = + std::fread(chunk.data(), 1, std::min(chunk.size(), offset - position), scan); + if (read == 0) { + break; + } + for (std::size_t i = 0; i < read; ++i) { + if (chunk[i] == '\n') { + ++line; + column = 1; + } else { + ++column; + } + } + position += read; + } + std::fclose(scan); + return {line, column}; +} + +bool MshInput::refill() { + if (eof) { + return false; + } + const std::size_t remaining = end - begin; + if (begin > 0) { + std::memmove(buffer.data(), buffer.data() + begin, remaining); + bufferOffset += begin; + begin = 0; + end = remaining; + } + const std::size_t capacity = buffer.size() - 1; + if (end == capacity) { + return false; + } + const std::size_t read = std::fread(buffer.data() + end, 1, capacity - end, file); + end += read; + buffer[end] = '\0'; + if (read == 0) { + eof = true; + } + return read > 0; +} + +} // namespace puml diff --git a/src/meshreader/MshInput.h b/src/meshreader/MshInput.h new file mode 100644 index 0000000..0cc77f5 --- /dev/null +++ b/src/meshreader/MshInput.h @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_MESHREADER_MSHINPUT_H_ +#define PUMGEN_SRC_MESHREADER_MSHINPUT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace puml { + +/** + * Buffered sequential reading of an MSH file: whitespace-separated text tokens for the ASCII + * parts and raw bytes for the binary parts. + */ +class MshInput { + public: + static constexpr std::size_t DefaultBufferSize = std::size_t{16} << 20; + // numbers and section names are shorter; the buffer always holds this many bytes ahead + static constexpr std::size_t MaxTokenLength = 128; + + /** + * @throws std::runtime_error if the file cannot be opened + */ + explicit MshInput(const std::string& fileName, std::size_t bufferSize = DefaultBufferSize); + ~MshInput(); + MshInput(const MshInput&) = delete; + MshInput& operator=(const MshInput&) = delete; + + /** + * Skips whitespace; returns false at the end of the file. + */ + bool skipWhitespace() { + while (true) { + while (begin < end && isSpace(buffer[begin])) { + ++begin; + } + if (begin < end) { + return true; + } + if (!refill()) { + return false; + } + } + } + + /** + * The first character of the next token, or '\0' at the end of the file. + */ + char peek() { return skipWhitespace() ? buffer[begin] : '\0'; } + + /** + * The next whitespace-separated token (at most MaxTokenLength characters); empty at the end of + * the file. The view is valid until the next read. + */ + std::string_view readToken(); + + /** + * Reads a whitespace-separated integer; std::nullopt (without consuming anything but + * whitespace) if the next token is not an integer of type T. + */ + template std::optional readInteger() { + if (!skipWhitespace()) { + return std::nullopt; + } + ensure(MaxTokenLength); + const char* first = buffer.data() + begin; + const char* last = buffer.data() + end; + if (*first == '+') { + ++first; + } + T value{}; + const auto [ptr, ec] = std::from_chars(first, last, value); + if (ec != std::errc() || (ptr != last && !isSpace(*ptr))) { + return std::nullopt; + } + begin = static_cast(ptr - buffer.data()); + return value; + } + + /** + * Reads a whitespace-separated floating-point number; std::nullopt (without consuming anything + * but whitespace) if the next token is not a number. + */ + std::optional readReal() { + if (!skipWhitespace()) { + return std::nullopt; + } + ensure(MaxTokenLength); + const char* first = buffer.data() + begin; + const char* last = buffer.data() + end; + if (*first == '+') { + ++first; + } + double value = 0; +#if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L + const auto [ptr, ec] = std::from_chars(first, last, value); + const bool parsed = ec == std::errc(); +#else + // the buffer is null-terminated behind the valid data + char* ptr = nullptr; + value = std::strtod(first, &ptr); + const bool parsed = ptr != first; +#endif + if (!parsed || (ptr != last && !isSpace(*ptr))) { + return std::nullopt; + } + begin = static_cast(ptr - buffer.data()); + return value; + } + + /** + * Consumes a single line break ("\n" or "\r\n"), as it separates the header line of a binary + * section from its data. + */ + bool readLineBreak(); + + /** + * Reads the next bytes as they are. + */ + bool readRaw(void* data, std::size_t bytes); + + /** + * Continues reading at the given file offset; returns false if the offset lies behind the end + * of the file. + */ + bool seek(std::size_t offset); + + /** + * Skips everything up to and including the next occurrence of the marker. + */ + bool skipTo(std::string_view marker); + + /** + * The offset of the next unread byte in the file. + */ + [[nodiscard]] std::size_t offset() const { return bufferOffset + begin; } + + /** + * Line and column (both starting at 1) of a byte offset; reads the file up to that offset. + */ + [[nodiscard]] std::pair location(std::size_t offset) const; + + private: + static bool isSpace(char c) { return c == ' ' || (c >= '\t' && c <= '\r'); } + + /** + * Moves the unread data to the front of the buffer and appends more data from the file; + * returns false if nothing could be appended. + */ + bool refill(); + + /** + * Makes at least count bytes available unless the end of the file comes first. + */ + void ensure(std::size_t count) { + while (end - begin < count && !eof) { + refill(); + } + } + + std::string fileName; + std::FILE* file = nullptr; + // one byte more than the capacity for the terminating null character + std::vector buffer; + // the unread data is buffer[begin, end) + std::size_t begin = 0; + std::size_t end = 0; + // the file offset of buffer[0] + std::size_t bufferOffset = 0; + bool eof = false; +}; + +} // namespace puml + +#endif // PUMGEN_SRC_MESHREADER_MSHINPUT_H_ diff --git a/src/meshreader/ParallelGMSHReader.h b/src/meshreader/ParallelGMSHReader.h index 19dcb99..718cca8 100644 --- a/src/meshreader/ParallelGMSHReader.h +++ b/src/meshreader/ParallelGMSHReader.h @@ -12,7 +12,9 @@ #include #include +#include #include +#include #include #include @@ -44,39 +46,63 @@ template class ParallelGMSHReader { } builder_.postprocess(); convertBoundaryConditions(); + release(builder_.facets); + release(builder_.bcs); nVertices_ = builder_.vertices.size(); nElements_ = builder_.elements.size(); + hasIdentify_ = builder_.identify.empty() ? 0 : 1; } MPI_Bcast(&nVertices_, 1, tndm::mpi_type_t(), 0, comm_); MPI_Bcast(&nElements_, 1, tndm::mpi_type_t(), 0, comm_); + MPI_Bcast(&hasIdentify_, 1, tndm::mpi_type_t(), 0, comm_); } [[nodiscard]] std::size_t nVertices() const { return nVertices_; } [[nodiscard]] std::size_t nElements() const { return nElements_; } - void readElements(std::size_t* elements) const { + + // Each read function distributes one part of the mesh and releases it on rank 0 afterwards, so + // it can be called only once. + + void readElements(std::size_t* elements) { static_assert(sizeof(typename GMSHBuilder::element_t) == nodeCount(Dim, Order) * sizeof(std::size_t)); - scatter(builder_.elements.data()->data(), elements, nElements(), nodeCount(Dim, Order)); + scatter(flatData(builder_.elements), elements, nElements(), nodeCount(Dim, Order)); + release(builder_.elements); } - void readVertices(double* vertices) const { + void readVertices(double* vertices) { static_assert(sizeof(typename GMSHBuilder::vertex_t) == Dim * sizeof(double)); - scatter(builder_.vertices.data()->data(), vertices, nVertices(), Dim); + scatter(flatData(builder_.vertices), vertices, nVertices(), Dim); + release(builder_.vertices); } - void readBoundaries(int* boundaries) const { + void readBoundaries(int* boundaries) { static_assert(sizeof(bc_t) == (Dim + 1) * sizeof(int)); - scatter(bcs_.data()->data(), boundaries, nElements(), Dim + 1); + scatter(flatData(bcs_), boundaries, nElements(), Dim + 1); + release(bcs_); + } + void readGroups(int* groups) { + scatter(builder_.groups.data(), groups, nElements(), 1); + release(builder_.groups); } - void readGroups(int* groups) const { scatter(builder_.groups.data(), groups, nElements(), 1); } constexpr static bool SupportsIdentify = true; - bool hasIdentify() const { return !builder_.identify.empty(); } - void readIdentify(std::size_t* vertices) const { + bool hasIdentify() const { return hasIdentify_ != 0; } + void readIdentify(std::size_t* vertices) { scatter(builder_.identify.data(), vertices, nVertices(), 1); + release(builder_.identify); } private: + template + static const T* flatData(const std::vector>& values) { + return values.empty() ? nullptr : values.front().data(); + } + + template static void release(std::vector& values) { + std::vector().swap(values); + } + /** * GMSH stores boundary conditions on a surface mesh whereas SeisSol expects * boundary conditions to be stored per element. In the following we convert @@ -84,53 +110,65 @@ template class ParallelGMSHReader { * with the surface mesh. */ void convertBoundaryConditions() { - const auto nVerts = builder_.vertices.size(); - const auto nFacets = builder_.facets.size(); - const auto nElements = builder_.elements.size(); - - bcs_.clear(); - bcs_.resize(nElements); - - constexpr auto nFacetsPerTet = Dim + 1u; - constexpr auto nVertsPerTri = Dim; - - // vertex to triangle map - std::vector> vertex2facets(nVerts); - for (std::size_t fctNo = 0; fctNo < nFacets; ++fctNo) { - for (auto const& vtxNo : builder_.facets[fctNo]) { - vertex2facets[vtxNo].push_back(fctNo); + using Face = std::array; + struct BoundaryFace { + Face vertices; + int bc; + }; + const auto sortedFace = [](Face face) { + std::sort(face.begin(), face.end()); + return face; + }; + + // the surface mesh as sorted vertex triples; the vertices of a facet come first + std::vector onBoundary(builder_.vertices.size(), 0); + std::vector boundaryFaces(builder_.facets.size()); + for (std::size_t fctNo = 0; fctNo < builder_.facets.size(); ++fctNo) { + Face face{}; + for (std::size_t i = 0; i < Dim; ++i) { + face[i] = builder_.facets[fctNo][i]; + onBoundary[face[i]] = 1; } + boundaryFaces[fctNo] = {sortedFace(face), builder_.bcs[fctNo]}; } - for (auto& v2f : vertex2facets) { - std::sort(v2f.begin(), v2f.end()); - } + const auto byVertices = [](const BoundaryFace& a, const BoundaryFace& b) { + return a.vertices < b.vertices; + }; + std::sort(boundaryFaces.begin(), boundaryFaces.end(), byVertices); + const auto nElements = builder_.elements.size(); + bcs_.assign(nElements, bc_t{}); for (std::size_t elNo = 0; elNo < nElements; ++elNo) { - for (int localFctNo = 0; localFctNo < nFacetsPerTet; ++localFctNo) { - std::array nodes; - for (int localNo = 0; localNo < nVertsPerTri; ++localNo) { - const auto localNoElement = Facet2Nodes[localFctNo][localNo]; - nodes[localNo] = builder_.elements[elNo][localNoElement]; + const auto& element = builder_.elements[elNo]; + // only faces with all vertices on the surface mesh can be part of it + std::array vertexOnBoundary{}; + std::size_t verticesOnBoundary = 0; + for (std::size_t i = 0; i < Dim + 1; ++i) { + vertexOnBoundary[i] = onBoundary[element[i]] != 0; + verticesOnBoundary += vertexOnBoundary[i] ? 1 : 0; + } + if (verticesOnBoundary < Dim) { + continue; + } + for (std::size_t localFctNo = 0; localFctNo < Dim + 1; ++localFctNo) { + Face face{}; + bool candidate = true; + for (std::size_t i = 0; i < Dim; ++i) { + const auto localVertex = Facet2Nodes[localFctNo][i]; + candidate = candidate && vertexOnBoundary[localVertex]; + face[i] = element[localVertex]; + } + if (!candidate) { + continue; } - std::array, nVertsPerTri - 1> intersect; - std::set_intersection(vertex2facets[nodes[0]].begin(), vertex2facets[nodes[0]].end(), - vertex2facets[nodes[1]].begin(), vertex2facets[nodes[1]].end(), - std::back_inserter(intersect[0])); - for (int node = 2; node < nVertsPerTri; ++node) { - std::set_intersection(intersect[node - 2].begin(), intersect[node - 2].end(), - vertex2facets[nodes[node]].begin(), - vertex2facets[nodes[node]].end(), - std::back_inserter(intersect[node - 1])); + const BoundaryFace key{sortedFace(face), 0}; + const auto [first, last] = + std::equal_range(boundaryFaces.begin(), boundaryFaces.end(), key, byVertices); + if (last - first > 1) { + logError() << "A face of an element exists multiple times in the surface mesh."; } - if (!intersect[nVertsPerTri - 2].empty()) { - if (intersect[nVertsPerTri - 2].size() > 1) { - logError() << "A face of an element exists multiple times in the " - "surface mesh."; - } - const auto fctNo = intersect[nVertsPerTri - 2][0]; - bcs_[elNo][localFctNo] = adjustBoundaryCondition(builder_.bcs[fctNo]); - } else { - bcs_[elNo][localFctNo] = 0; + if (first != last) { + bcs_[elNo][localFctNo] = adjustBoundaryCondition(first->bc); } } } @@ -175,6 +213,8 @@ template class ParallelGMSHReader { std::vector bcs_; std::size_t nVertices_ = 0; std::size_t nElements_ = 0; + // only rank 0 parses the file, so all ranks need this flag for the collective reads and writes + int hasIdentify_ = 0; }; } // namespace puml diff --git a/src/meshreader/ParallelGambitReader.h b/src/meshreader/ParallelGambitReader.h index bf63ab6..fac8859 100644 --- a/src/meshreader/ParallelGambitReader.h +++ b/src/meshreader/ParallelGambitReader.h @@ -9,11 +9,18 @@ #include +#include +#include +#include +#include +#include + #include "GambitReader.h" #include "ParallelMeshReader.h" #include "helper/Distributor.h" #include "third_party/MPITraits.h" +#include "utils/logger.h" namespace puml { @@ -25,180 +32,125 @@ class ParallelGambitReader : public ParallelMeshReader { : ParallelMeshReader(meshFile, comm) {} /** - * Reads all group numbers. - * Each process gets nElements() + processes - 1) / processes - * group numbers, except for the last, which gets the remaining + * Reads all group numbers. Each process gets the group numbers of its elements (see + * ParallelMeshReader::readElements). * * This is a collective operation. */ void readGroups(int* groups) { - std::size_t chunkSize = getChunksize(nElements(), m_rank, m_nProcs); - - if (m_rank == 0) { - std::size_t maxChunkSize = chunkSize; - std::vector map(maxChunkSize); - - std::vector> aggregator(m_nProcs - 1); - std::vector sizes(m_nProcs - 1); - std::vector requests((m_nProcs - 1) * 2, MPI_REQUEST_NULL); - - std::size_t position = 0; - - for (int i = 0; i < m_nProcs; i++) { - logInfo() << "Reading group information part" << (i + 1) << "of" << m_nProcs; - - chunkSize = getChunksize(nElements(), i, m_nProcs); - - m_serialReader.readGroups(position, chunkSize, map.data()); - position += chunkSize; - - // Wait for all sending from last iteration - MPI_Waitall((m_nProcs - 1) * 2, requests.data(), MPI_STATUSES_IGNORE); - for (int j = 0; j < m_nProcs - 1; j++) { - aggregator[j].clear(); - } - - // Sort group numbers into the corresponding aggregator - for (std::size_t j = 0; j < chunkSize; j++) { - if (map[j].element < maxChunkSize) - // Local element - groups[map[j].element] = map[j].group; - else { - // Element for another processor - // Serials the struct to make sending easier - unsigned int proc = map[j].element / maxChunkSize; - aggregator[proc - 1].push_back(map[j].element % maxChunkSize); - aggregator[proc - 1].push_back(map[j].group); - } - } - - // Send send aggregated mapping - for (int j = 0; j < m_nProcs - 1; j++) { - if (aggregator[j].empty()) { - continue; - } - - sizes[j] = aggregator[j].size() / 2; // element id + group number - MPI_Isend(&sizes[j], 1, tndm::mpi_type_t(), j + 1, 0, m_comm, - &requests[j * 2]); - MPI_Isend(&aggregator[j][0], aggregator[j].size(), MPI_INT, j + 1, 0, m_comm, - &requests[j * 2 + 1]); - } - } - - MPI_Waitall((m_nProcs - 1) * 2, requests.data(), MPI_STATUSES_IGNORE); - } else { - // Allocate enough memory - std::vector buf(chunkSize * 2); - - unsigned int recieved = 0; - - while (recieved < chunkSize) { - std::size_t size; - MPI_Recv(&size, 1, tndm::mpi_type_t(), 0, 0, m_comm, MPI_STATUS_IGNORE); - MPI_Recv(buf.data(), size * 2, MPI_INT, 0, 0, m_comm, MPI_STATUS_IGNORE); - - for (std::size_t i = 0; i < size * 2; i += 2) { - groups[buf[i]] = buf[i + 1]; - } - - recieved += size; - } - } + scatterToElementOwners( + nElements(), 1, groups, "group information", + [&](std::size_t start, std::size_t count, ElementGroup* entries) { + m_serialReader.readGroups(start, count, entries); + }, + [](const ElementGroup& entry) { + return std::make_tuple(entry.element, std::size_t{0}, entry.group); + }); } /** - * Reads all boundaries. - * Each process gets the boundaries for nElements() + processes - 1) / - * processes elements. Boundaries not specified in the mesh are not - * modified. + * Reads all boundaries. Each process gets the boundaries of the faces of its elements (see + * ParallelMeshReader::readElements). Boundaries not specified in the mesh are not modified. * - * This is a collective operation + * This is a collective operation. * * @todo Only tetrahedral meshes are supported */ void readBoundaries(int* boundaries) { - std::size_t chunkSize = getChunksize(nElements(), m_rank, m_nProcs); - - if (m_rank == 0) { - std::size_t maxChunkSize = chunkSize; - std::vector faces(maxChunkSize); - - std::vector> aggregator(m_nProcs - 1); - std::vector sizes(m_nProcs - 1); - std::vector requests((m_nProcs - 1) * 2, MPI_REQUEST_NULL); - - std::size_t nChunks = (nBoundaries() + chunkSize - 1) / chunkSize; - std::size_t position = 0; - for (std::size_t i = 0; i < nChunks; i++) { - logInfo() << "Reading boundary conditions part" << (i + 1) << "of" << nChunks; - - chunkSize = getChunksize(nBoundaries(), i, m_nProcs); + scatterToElementOwners( + nBoundaries(), 4, boundaries, "boundary conditions", + [&](std::size_t start, std::size_t count, GambitBoundaryFace* entries) { + m_serialReader.readBoundaries(start, count, entries); + }, + [](const GambitBoundaryFace& entry) { + return std::make_tuple(entry.element, static_cast(entry.face), entry.type); + }); + } - m_serialReader.readBoundaries(position, chunkSize, faces.data()); - position += chunkSize; + private: + /** + * Reads per-element entries in chunks on rank 0 and stores the value of each entry at + * local[localElement * slotsPerElement + slot] on the process owning the element. + * + * @param read Callable (start, count, entries) reading count entries starting at + * start + * @param decode Callable returning the tuple (element, slot, value) for an entry + */ + template + void scatterToElementOwners(std::size_t numEntries, std::size_t slotsPerElement, int* local, + const char* name, ReadF&& read, DecodeF&& decode) { + const auto messageType = tndm::mpi_type_t(); - // Wait for all sending from last iteration - MPI_Waitall((m_nProcs - 1) * 2, requests.data(), MPI_STATUSES_IGNORE); - for (int j = 0; j < m_nProcs - 1; j++) { - aggregator[j].clear(); + if (m_rank != 0) { + std::vector buffer; + while (true) { + std::size_t size = 0; + MPI_Recv(&size, 1, tndm::mpi_type_t(), 0, 0, m_comm, MPI_STATUS_IGNORE); + if (size == 0) { + break; } - - // Sort boundary conditions into the corresponding aggregator - for (std::size_t j = 0; j < chunkSize; j++) { - if (faces[j].element < maxChunkSize) - // Local element - boundaries[faces[j].element * 4 + faces[j].face] = faces[j].type; - else { - // Face for another processor - // Serials the struct to make sending easier - unsigned int proc = faces[j].element / maxChunkSize; - aggregator[proc - 1].push_back((faces[j].element % maxChunkSize) * 4 + faces[j].face); - aggregator[proc - 1].push_back(faces[j].type); - } - } - - // Send send aggregated values - for (int j = 0; j < m_nProcs - 1; j++) { - if (aggregator[j].empty()) { - continue; - } - - sizes[j] = aggregator[j].size() / 2; // element id + face type - MPI_Isend(&sizes[j], 1, tndm::mpi_type_t(), j + 1, 0, m_comm, - &requests[j * 2]); - MPI_Isend(&aggregator[j][0], aggregator[j].size(), MPI_INT, j + 1, 0, m_comm, - &requests[j * 2 + 1]); + buffer.resize(2 * size); + MPI_Recv(buffer.data(), messageSize(buffer.size()), messageType, 0, 0, m_comm, + MPI_STATUS_IGNORE); + for (std::size_t i = 0; i < size; ++i) { + local[buffer[2 * i]] = static_cast(buffer[2 * i + 1]); } } + return; + } - MPI_Waitall((m_nProcs - 1) * 2, requests.data(), MPI_STATUSES_IGNORE); - - // Send finish signal to all other processes - std::fill(sizes.begin(), sizes.end(), 0); - for (int i = 0; i < m_nProcs - 1; i++) { - MPI_Isend(&sizes[i], 1, tndm::mpi_type_t(), i + 1, 0, m_comm, &requests[i]); + const std::size_t chunkSize = std::max(getChunksize(nElements(), 0, m_nProcs), std::size_t{1}); + std::vector entries(std::min(chunkSize, numEntries)); + std::vector> outgoing(m_nProcs); + std::vector sizes(m_nProcs, 0); + std::vector requests(2 * m_nProcs, MPI_REQUEST_NULL); + + for (std::size_t start = 0; start < numEntries; start += chunkSize) { + const std::size_t count = std::min(chunkSize, numEntries - start); + logInfo() << "Reading" << name << start << "to" << start + count << "of" << numEntries; + read(start, count, entries.data()); + + // the send buffers of the previous chunk are reused + MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); + for (auto& buffer : outgoing) { + buffer.clear(); } - MPI_Waitall(m_nProcs - 1, requests.data(), MPI_STATUSES_IGNORE); - } else { - // Allocate enough memory - std::vector buf(chunkSize * 4); - while (true) { - std::size_t size; - MPI_Recv(&size, 1, tndm::mpi_type_t(), 0, 0, m_comm, MPI_STATUS_IGNORE); - if (size == 0) { - // Finished - break; + for (std::size_t i = 0; i < count; ++i) { + const auto [element, slot, value] = decode(entries[i]); + if (element >= nElements() || slot >= slotsPerElement) { + logError() << "Invalid" << name << "entry: element" << element << "slot" << slot; } + const int owner = getChunkOwner(nElements(), element, m_nProcs); + const std::size_t index = + (element - getChunksum(nElements(), owner, m_nProcs)) * slotsPerElement + slot; + if (owner == 0) { + local[index] = value; + } else { + outgoing[owner].push_back(static_cast(index)); + outgoing[owner].push_back(value); + } + } - MPI_Recv(buf.data(), size * 2, MPI_INT, 0, 0, m_comm, MPI_STATUS_IGNORE); - - for (std::size_t i = 0; i < size * 2; i += 2) { - boundaries[buf[i]] = buf[i + 1]; + for (int rank = 1; rank < m_nProcs; ++rank) { + if (outgoing[rank].empty()) { + continue; } + sizes[rank] = outgoing[rank].size() / 2; + MPI_Isend(&sizes[rank], 1, tndm::mpi_type_t(), rank, 0, m_comm, + &requests[2 * rank]); + MPI_Isend(outgoing[rank].data(), messageSize(outgoing[rank].size()), messageType, rank, 0, + m_comm, &requests[2 * rank + 1]); } } + MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); + + // an empty message ends the transfer + std::fill(sizes.begin(), sizes.end(), 0); + for (int rank = 1; rank < m_nProcs; ++rank) { + MPI_Isend(&sizes[rank], 1, tndm::mpi_type_t(), rank, 0, m_comm, &requests[rank]); + } + MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } }; diff --git a/src/meshreader/ParallelMeshReader.h b/src/meshreader/ParallelMeshReader.h index 205bce8..114f5b5 100644 --- a/src/meshreader/ParallelMeshReader.h +++ b/src/meshreader/ParallelMeshReader.h @@ -10,9 +10,13 @@ #include #include +#include +#include #include +#include "helper/Distributor.h" #include "third_party/MPITraits.h" +#include "utils/logger.h" template class ParallelMeshReader { private: @@ -77,129 +81,87 @@ template class ParallelMeshReader { std::size_t nBoundaries() const { return m_nBoundaries; } /** - * Reads all vertices - * Each process gets nVertices() + processes - 1) / processes - * elements, except for the last, which gets the remaining elements + * Reads all vertices. Each process gets the chunk given by getChunksize/getChunksum. * - * This is a collective operation + * This is a collective operation. * * @todo Only 3 dimensional meshes are supported */ void readVertices(double* vertices) { - std::size_t chunkSize = (m_nVertices + m_nProcs - 1) / m_nProcs; - - if (m_rank == 0) { - // Allocate second buffer so we can read and send in parallel - std::vector tempVertices(chunkSize * 3); - double* vertices2 = tempVertices.data(); - if (m_nProcs % 2 == 0) { - // swap once so we have the correct buffer at the end - swap(vertices, vertices2); - } - - MPI_Request request = MPI_REQUEST_NULL; - for (int i = 1; i < m_nProcs - 1; i++) { - logInfo() << "Reading vertices part" << i << "of" << m_nProcs; - m_serialReader.readVertices(i * chunkSize, chunkSize, vertices); - MPI_Wait(&request, MPI_STATUS_IGNORE); - MPI_Isend(vertices, chunkSize * 3, MPI_DOUBLE, i, 0, m_comm, &request); - swap(vertices, vertices2); - } - - if (m_nProcs > 1) { - // Read last one - const std::size_t lastChunkSize = m_nVertices - (m_nProcs - 1) * chunkSize; - logInfo() << "Reading vertices part" << (m_nProcs - 1) << "of" << m_nProcs; - m_serialReader.readVertices((m_nProcs - 1) * chunkSize, lastChunkSize, vertices); - MPI_Wait(&request, MPI_STATUS_IGNORE); - MPI_Isend(vertices, lastChunkSize * 3, MPI_DOUBLE, m_nProcs - 1, 0, m_comm, &request); - swap(vertices, vertices2); - } - - // Finally read the first part - logInfo() << "Reading vertices part" << m_nProcs << "of" << m_nProcs; - m_serialReader.readVertices(0, chunkSize, vertices); - MPI_Wait(&request, MPI_STATUS_IGNORE); - } else { - if (m_rank == m_nProcs - 1) - chunkSize = m_nVertices - (m_nProcs - 1) * chunkSize; - - MPI_Recv(vertices, chunkSize * 3, MPI_DOUBLE, 0, 0, m_comm, MPI_STATUS_IGNORE); - } + distributeChunks(m_nVertices, 3, vertices, "vertices", + [&](std::size_t start, std::size_t count, double* buffer) { + m_serialReader.readVertices(start, count, buffer); + }); } /** - * Reads all elements. - * Each process gets nElements() + processes - 1) / processes - * elements, except for the last, which gets the remaining elements + * Reads all elements. Each process gets the chunk given by getChunksize/getChunksum. * * This is a collective operation. * * @todo Only tetrahedral meshes are supported */ virtual void readElements(std::size_t* elements) { - std::size_t chunkSize = (m_nElements + m_nProcs - 1) / m_nProcs; + distributeChunks(m_nElements, 4, elements, "elements", + [&](std::size_t start, std::size_t count, std::size_t* buffer) { + m_serialReader.readElements(start, count, buffer); + }); + } - if (m_rank == 0) { - // Allocate second buffer so we can read and send in parallel - std::vector tempElements(chunkSize * 4); - std::size_t* elements2 = tempElements.data(); - if (m_nProcs % 2 == 0) { - // swap once so we have the correct buffer at the end - swap(elements, elements2); - } - - MPI_Request request = MPI_REQUEST_NULL; - - for (int i = 1; i < m_nProcs - 1; i++) { - logInfo() << "Reading elements part" << i << "of" << m_nProcs; - m_serialReader.readElements(i * chunkSize, chunkSize, elements); - MPI_Wait(&request, MPI_STATUS_IGNORE); - MPI_Isend(elements, chunkSize * 4, tndm::mpi_type_t(), i, 0, m_comm, &request); - swap(elements, elements2); - } - - if (m_nProcs > 1) { - // Read last one - const std::size_t lastChunkSize = m_nElements - (m_nProcs - 1) * chunkSize; - logInfo() << "Reading elements part" << (m_nProcs - 1) << "of" << m_nProcs; - m_serialReader.readElements((m_nProcs - 1) * chunkSize, lastChunkSize, elements); - MPI_Wait(&request, MPI_STATUS_IGNORE); - MPI_Isend(elements, lastChunkSize * 4, tndm::mpi_type_t(), m_nProcs - 1, 0, - m_comm, &request); - swap(elements, elements2); - } - - // Finally read the first part - logInfo() << "Reading elements part" << m_nProcs << "of" << m_nProcs; - m_serialReader.readElements(0, chunkSize, elements); - MPI_Wait(&request, MPI_STATUS_IGNORE); - } else { - if (m_rank == m_nProcs - 1) - chunkSize = m_nElements - (m_nProcs - 1) * chunkSize; - - MPI_Recv(elements, chunkSize * 4, tndm::mpi_type_t(), 0, 0, m_comm, - MPI_STATUS_IGNORE); + protected: + static int messageSize(std::size_t count) { + if (count > static_cast(std::numeric_limits::max())) { + logError() << "MPI message too large:" << count; } + return static_cast(count); } private: /** - * Initialize some parameters + * Reads the items on rank 0 and sends each process its chunk. + * + * @param read Callable (start, count, buffer) reading count items starting at + * start */ - void init() { - MPI_Comm_rank(m_comm, &m_rank); - MPI_Comm_size(m_comm, &m_nProcs); + template + void distributeChunks(std::size_t total, std::size_t valuesPerItem, T* local, const char* name, + ReadF&& read) { + const std::size_t localCount = getChunksize(total, m_rank, m_nProcs); + + if (m_rank != 0) { + MPI_Recv(local, messageSize(localCount * valuesPerItem), tndm::mpi_type_t(), 0, 0, m_comm, + MPI_STATUS_IGNORE); + return; + } + + // two send buffers, so that reading the next chunk overlaps with sending the previous one + const std::size_t maxCount = getChunksize(total, 0, m_nProcs); + std::array, 2> buffers; + std::array requests = {MPI_REQUEST_NULL, MPI_REQUEST_NULL}; + + for (int rank = 1; rank < m_nProcs; ++rank) { + const auto slot = rank % 2; + MPI_Wait(&requests[slot], MPI_STATUS_IGNORE); + buffers[slot].resize(maxCount * valuesPerItem); + + const std::size_t count = getChunksize(total, rank, m_nProcs); + logInfo() << "Reading" << name << "for rank" << rank << "of" << m_nProcs; + read(getChunksum(total, rank, m_nProcs), count, buffers[slot].data()); + MPI_Isend(buffers[slot].data(), messageSize(count * valuesPerItem), tndm::mpi_type_t(), + rank, 0, m_comm, &requests[slot]); + } + + logInfo() << "Reading" << name << "for rank 0 of" << m_nProcs; + read(0, localCount, local); + MPI_Waitall(2, requests.data(), MPI_STATUSES_IGNORE); } - protected: /** - * Swaps two pointers + * Initialize some parameters */ - template static void swap(T*& p1, T*& p2) { - T* tmp = p1; - p1 = p2; - p2 = tmp; + void init() { + MPI_Comm_rank(m_comm, &m_rank); + MPI_Comm_size(m_comm, &m_nProcs); } }; diff --git a/src/pumgen.cpp b/src/pumgen.cpp index 39d1f14..89b0eec 100644 --- a/src/pumgen.cpp +++ b/src/pumgen.cpp @@ -44,13 +44,21 @@ #ifdef USE_SIMMOD #include "input/SimModSuite.h" #endif // USE_SIMMOD +#include "meshreader/DistributedGMSHReader.h" +#include "meshreader/GMSH2Parser.h" #include "meshreader/GMSH4Parser.h" #include "meshreader/ParallelGMSHReader.h" #include "meshreader/ParallelGambitReader.h" -#include "third_party/GMSH2Parser.h" #include "helper/InsphereCalculator.h" +#ifdef USE_EASI +#include "sizing/VelocityAwareMeshSize.h" +#include "sizing/VelocityAwareSettings.h" +#include "sizing/VelocityCheck.h" +#include "tinyxml2/tinyxml2.h" +#endif + template static TT _checkH5Err(TT&& status, const char* file, int line) { if (status < 0) { logError() << utils::nospace << "An HDF5 error occurred (" << file << ": " << line << ")"; @@ -71,6 +79,33 @@ static int ilog(std::size_t value, int expbase = 1) { constexpr std::size_t NoSecondDim = 0; +/** + * The smallest size in bytes of an integer type with the given signedness which holds all values + * of the (distributed) data. Data with negative values is not reduced. + */ +template +static std::size_t requiredIntegerBytes(const F& data, std::size_t count, bool isSigned) { + using ValueT = typename F::value_type; + unsigned long long largest = 0; + int negative = 0; + for (std::size_t i = 0; i < count; ++i) { + if constexpr (std::is_signed_v) { + if (data[i] < 0) { + negative = 1; + continue; + } + } + largest = std::max(largest, static_cast(data[i])); + } + MPI_Allreduce(MPI_IN_PLACE, &largest, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, MPI_COMM_WORLD); + MPI_Allreduce(MPI_IN_PLACE, &negative, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD); + if (negative != 0) { + return sizeof(ValueT); + } + const std::size_t bits = ilog(largest) + (isSigned ? 1 : 0); + return std::max(static_cast(1), (bits + 7) / 8); +} + template static void writeH5Data(const F& handler, hid_t h5file, const std::string& name, void* mesh, int meshdim, hid_t h5memtype, hid_t h5outtype, std::size_t chunk, @@ -107,21 +142,27 @@ static void writeH5Data(const F& handler, hid_t h5file, const std::string& name, checkH5Err(H5Pset_dxpl_mpio(h5dxlist, H5FD_MPIO_COLLECTIVE)); hid_t h5type = h5outtype; - if (reduceInts && std::is_integral_v) { - h5type = H5Tcopy(h5outtype); - std::size_t bits = ilog(globalSize); - std::size_t unsignedSize = (bits + 7) / 8; - checkH5Err(h5type); - checkH5Err(H5Tset_size(h5type, unsignedSize)); - checkH5Err(H5Tcommit(h5file, (std::string("/") + name + std::string("Type")).c_str(), h5type, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)); + if constexpr (std::is_integral_v) { + if (reduceInts) { + const std::size_t bytes = requiredIntegerBytes(handler, localSize * secondSize, + H5Tget_sign(h5outtype) == H5T_SGN_2); + if (bytes < H5Tget_size(h5outtype)) { + h5type = checkH5Err(H5Tcopy(h5outtype)); + checkH5Err(H5Tset_size(h5type, bytes)); + checkH5Err(H5Tcommit(h5file, (std::string("/") + name + std::string("Type")).c_str(), + h5type, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)); + } + } } hid_t h5filter = H5P_DEFAULT; if (filterEnable > 0) { h5filter = checkH5Err(H5Pcreate(H5P_DATASET_CREATE)); - hsize_t chunk[2] = {std::min(filterChunksize, bufferSize), secondDim}; - checkH5Err(H5Pset_chunk(h5filter, 2, chunk)); + // the dataset creation is collective, so the chunk shape must not depend on the local size + const hsize_t chunkRows = + std::max(static_cast(1), std::min(filterChunksize, globalSize)); + hsize_t chunk[2] = {chunkRows, secondDim}; + checkH5Err(H5Pset_chunk(h5filter, dimensions, chunk)); if (filterEnable == 1 && std::is_integral_v) { checkH5Err(H5Pset_scaleoffset(h5filter, H5Z_SO_INT, H5Z_SO_INT_MINBITS_DEFAULT)); } else if (filterEnable < 11) { @@ -143,9 +184,15 @@ static void writeH5Data(const F& handler, hid_t h5file, const std::string& name, std::copy_n(handler.begin() + written * secondSize, count[0] * secondSize, data.begin()); - checkH5Err(H5Sselect_hyperslab(h5memspace, H5S_SELECT_SET, nullstart, nullptr, count, nullptr)); - - checkH5Err(H5Sselect_hyperslab(h5space, H5S_SELECT_SET, start, nullptr, count, nullptr)); + if (count[0] > 0) { + checkH5Err( + H5Sselect_hyperslab(h5memspace, H5S_SELECT_SET, nullstart, nullptr, count, nullptr)); + checkH5Err(H5Sselect_hyperslab(h5space, H5S_SELECT_SET, start, nullptr, count, nullptr)); + } else { + // ranks without (further) data still take part in the collective write + checkH5Err(H5Sselect_none(h5memspace)); + checkH5Err(H5Sselect_none(h5space)); + } checkH5Err(H5Dwrite(h5data, h5memtype, h5memspace, h5space, h5dxlist, data.data())); @@ -155,7 +202,7 @@ static void writeH5Data(const F& handler, hid_t h5file, const std::string& name, if (filterEnable > 0) { checkH5Err(H5Pclose(h5filter)); } - if (reduceInts) { + if (h5type != h5outtype) { checkH5Err(H5Tclose(h5type)); } checkH5Err(H5Sclose(h5space)); @@ -178,9 +225,11 @@ void addAttribute(hid_t h5file, const std::string& name, const std::string& valu } template -using SMF2 = SerialMeshFile>; +using SMF2 = SerialMeshFile>; template using SMF4 = SerialMeshFile>; +template +using SMF4Distributed = SerialMeshFile>; int main(int argc, char* argv[]) { int rank = 0; @@ -209,7 +258,7 @@ int main(int argc, char* argv[]) { "deflate7", "deflate8", "deflate9"}; args.addOption("compactify-datatypes", 0, "Compress index and group data types to minimum byte size (no HDF5 filters)", - utils::Args::Required, false); + utils::Args::No, false); args.addEnumOption("filter-enable", filters, 0, "Apply HDF5 filters (i.e. compression). Disabled by default.", false); args.addOption("filter-chunksize", 0, "Chunksize for filters (default=4096).", @@ -242,7 +291,16 @@ int main(int argc, char* argv[]) { args.addOption("sim_log", 0, "Create SimModSuite log", utils::Args::Required, false); args.addAdditionalOption("input", "Input file (mesh or model)"); args.addAdditionalOption("output", "Output parallel unstructured mesh file", false); - args.addOption("order", 'o', "Mesh order", utils::Args::Optional, false); + args.addOption("order", 'o', "Mesh order (default: 1)", utils::Args::Required, false); + const auto gmshReaders = std::vector{"auto", "serial"}; + args.addEnumOption("gmsh-reader", gmshReaders, 0, + "Reader for msh4 meshes (default: auto, which reads binary MSH 4.1 files " + "with all ranks; serial: rank 0 reads the file)", + false); + args.addOption("velocity-check", 0, + "Compare the cell sizes with the velocity-aware mesh size of the " + "VelocityAwareMeshing element of a mesh attributes file (XML)", + utils::Args::Required, false); if (args.parse(argc, argv, rank == 0) != utils::Args::Success) return 1; @@ -262,7 +320,7 @@ int main(int argc, char* argv[]) { outputFile.append(".puml.h5"); } - hsize_t chunksize = args.getArgument("filter-chunksize", static_cast(1) << 30); + hsize_t chunksize = args.getArgument("chunksize", static_cast(1) << 30); bool reduceInts = args.isSet("compactify-datatypes"); int filterEnable = args.getArgument("filter-enable", 0); @@ -325,6 +383,9 @@ int main(int argc, char* argv[]) { } const auto meshOrder = args.getArgument("order", 1); + if (meshOrder < 1) { + logError() << "The mesh order has to be at least 1, got" << meshOrder; + } // Create/read the mesh MeshData* meshInput = nullptr; @@ -337,10 +398,21 @@ int main(int argc, char* argv[]) { logInfo() << "Using GMSH mesh format 2 (msh2) mesh"; meshInput = puml::makePointer(meshOrder, inputFile, faceOffset); break; - case 2: - logInfo() << "Using GMSH mesh format 4 (msh4) mesh"; - meshInput = puml::makePointer(meshOrder, inputFile, faceOffset); + case 2: { + int distributed = 0; + if (rank == 0) { + distributed = args.getArgument("gmsh-reader", 0) == 0 && puml::isBinaryMsh4(inputFile); + } + MPI_Bcast(&distributed, 1, MPI_INT, 0, MPI_COMM_WORLD); + if (distributed != 0) { + logInfo() << "Using GMSH mesh format 4 (msh4) mesh, binary, read by all ranks"; + meshInput = puml::makePointer(meshOrder, inputFile, faceOffset); + } else { + logInfo() << "Using GMSH mesh format 4 (msh4) mesh"; + meshInput = puml::makePointer(meshOrder, inputFile, faceOffset); + } break; + } case 3: #ifdef USE_NETCDF logInfo() << "Using netCDF mesh"; @@ -413,12 +485,33 @@ int main(int argc, char* argv[]) { logInfo() << "Total vertex count:" << globalSize[1]; logInfo() << "Vertex identification:" << meshInput->hasIdentify(); - auto inspheres = - calculateInsphere(meshInput->connectivity(), meshInput->geometry(), MPI_COMM_WORLD); - double min = *std::min_element(inspheres.begin(), inspheres.end()); + const CellVertices cellVertices(meshInput->connectivity(), meshInput->geometry(), + meshInput->cellSize(), MPI_COMM_WORLD); + auto inspheres = calculateInsphere(cellVertices); + double min = inspheres.empty() ? std::numeric_limits::infinity() + : *std::min_element(inspheres.begin(), inspheres.end()); MPI_Reduce((rank == 0 ? MPI_IN_PLACE : &min), &min, 1, MPI_DOUBLE, MPI_MIN, 0, MPI_COMM_WORLD); logInfo() << "Minimum insphere found:" << min; + if (args.isSet("velocity-check")) { +#ifdef USE_EASI + const auto* settingsFile = args.getArgument("velocity-check"); + tinyxml2::XMLDocument doc; + if (doc.LoadFile(settingsFile) != tinyxml2::XML_SUCCESS) { + logError() << "Could not read" << settingsFile; + } + const auto settings = readVelocityAwareSettings(doc); + if (!settings.isVelocityAwareRefinementOn()) { + logError() << "No refinement cuboid in" << settingsFile; + } + const VelocityAwareMeshSize meshSize(settings); + checkVelocityAwareMeshSize(meshSize, cellVertices, meshInput->group(), MPI_COMM_WORLD); +#else + logError() << "This version of PUMGen has been compiled without easi; hence, --velocity-check " + "is not available."; +#endif + } + // Get offsets std::size_t offsets[2] = {localSize[0], localSize[1]}; MPI_Scan(MPI_IN_PLACE, offsets, 2, tndm::mpi_type_t(), MPI_SUM, MPI_COMM_WORLD); diff --git a/src/sizefield.cpp b/src/sizefield.cpp new file mode 100644 index 0000000..dcbea2d --- /dev/null +++ b/src/sizefield.cpp @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause + +// Writes gmsh Structured fields for velocity-aware meshing (one per refinement cuboid) and a .geo +// file which combines them into the background mesh size. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "helper/Distributor.h" +#include "sizing/SizeField.h" +#include "sizing/VelocityAwareMeshSize.h" +#include "sizing/VelocityAwareSettings.h" +#include "tinyxml2/tinyxml2.h" +#include "utils/args.h" +#include "utils/logger.h" + +namespace { + +/** + * Writes the binary format of gmsh's Structured field: origin and spacing (3 doubles each), node + * counts (3 4-byte ints), then the values. Each rank writes the nodes with x index from first. + */ +void writeGrid(const std::string& fileName, const StructuredGrid& grid, + const std::vector& values, std::size_t first, MPI_Comm comm) { + MPI_File file; + if (MPI_File_open(comm, fileName.c_str(), MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, + &file) != MPI_SUCCESS) { + logError() << "Could not open" << fileName; + } + MPI_File_set_size(file, 0); + + constexpr std::size_t HeaderSize = 6 * sizeof(double) + 3 * sizeof(std::int32_t); + int rank = 0; + MPI_Comm_rank(comm, &rank); + if (rank == 0) { + char header[HeaderSize]; + const double spacing[3] = {grid.spacing, grid.spacing, grid.spacing}; + std::int32_t count[3]; + for (int d = 0; d < 3; ++d) { + count[d] = static_cast(grid.count[d]); + } + std::memcpy(header, grid.origin.data(), 3 * sizeof(double)); + std::memcpy(header + 3 * sizeof(double), spacing, 3 * sizeof(double)); + std::memcpy(header + 6 * sizeof(double), count, 3 * sizeof(std::int32_t)); + MPI_File_write_at(file, 0, header, HeaderSize, MPI_BYTE, MPI_STATUS_IGNORE); + } + + // in pieces, since MPI counts are ints + constexpr std::size_t Piece = std::size_t{1} << 26; + const MPI_Offset offset = HeaderSize + first * grid.count[1] * grid.count[2] * sizeof(double); + for (std::size_t done = 0; done < values.size(); done += Piece) { + const auto count = static_cast(std::min(Piece, values.size() - done)); + MPI_File_write_at(file, offset + static_cast(done * sizeof(double)), + values.data() + done, count, MPI_DOUBLE, MPI_STATUS_IGNORE); + } + MPI_File_close(&file); +} + +std::string baseName(const std::string& path) { + const auto slash = path.find_last_of('/'); + return slash == std::string::npos ? path : path.substr(slash + 1); +} + +void writeGeo(const std::string& fileName, const std::string& settingsFile, + const std::vector& gridFiles, const std::vector& groups, + int firstField) { + std::ofstream geo(fileName); + geo << "// Velocity-aware mesh size written by pumgen-sizefield from " << settingsFile << ":\n" + << "// one Structured field per refinement cuboid, restricted to the physical volume of\n" + << "// the cuboid's group, and their minimum as background field. The grid files are\n" + << "// expected next to this file. Recommended options:\n" + << "// Mesh.MeshSizeExtendFromBoundary = 0; Mesh.MeshSizeFromPoints = 0;\n" + << "// Mesh.MeshSizeFromCurvature = 0;\n"; + std::string restricted; + for (std::size_t c = 0; c < gridFiles.size(); ++c) { + const int grid = firstField + 2 * static_cast(c); + const int restrict = grid + 1; + geo << "Field[" << grid << "] = Structured;\n" + << "Field[" << grid << "].FileName = StrCat(CurrentDirectory, \"" << baseName(gridFiles[c]) + << "\");\n" + << "Field[" << grid << "].TextFormat = 0;\n" + << "Field[" << grid << "].SetOutsideValue = 1;\n" + << "Field[" << grid << "].OutsideValue = " << UnconstrainedSize << ";\n" + << "Field[" << restrict << "] = Restrict;\n" + << "Field[" << restrict << "].InField = " << grid << ";\n" + << "Field[" << restrict << "].VolumesList = {Physical Volume{" << groups[c] << "}};\n" + << "Field[" << restrict << "].IncludeBoundary = 1;\n"; + restricted += (c == 0 ? "" : ", ") + std::to_string(restrict); + } + const int minimum = firstField + 2 * static_cast(gridFiles.size()); + geo << "Field[" << minimum << "] = Min;\n" + << "Field[" << minimum << "].FieldsList = {" << restricted << "};\n" + << "Background Field = " << minimum << ";\n"; +} + +} // namespace + +int main(int argc, char* argv[]) { + MPI_Init(&argc, &argv); + int rank = 0; + int size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + utils::Logger::setRank(rank); + + utils::Args args("Writes gmsh Structured fields for velocity-aware meshing, one per refinement " + "cuboid, and a .geo file which restricts each field to the physical volume of " + "the cuboid's group (bypassFindRegionAndUseGroup) and uses their minimum as " + "background field."); + args.addOption("spacing", 0, "Grid spacing; about the smallest mesh size", utils::Args::Required, + true); + args.addOption("first-field", 0, "Number of the first gmsh field (default: 1000)", + utils::Args::Required, false); + args.addAdditionalOption("settings", "Mesh attributes (XML) with a VelocityAwareMeshing element"); + args.addAdditionalOption("output", "Prefix of the output files"); + if (args.parse(argc, argv, rank == 0) != utils::Args::Success) { + MPI_Finalize(); + return 1; + } + const auto settingsFile = args.getAdditionalArgument("settings"); + const auto prefix = args.getAdditionalArgument("output"); + const auto spacing = args.getArgument("spacing"); + const auto firstField = args.getArgument("first-field", 1000); + if (!(spacing > 0)) { + logError() << "The grid spacing has to be positive"; + } + + tinyxml2::XMLDocument doc; + if (doc.LoadFile(settingsFile.c_str()) != tinyxml2::XML_SUCCESS) { + logError() << "Could not read" << settingsFile; + } + const auto settings = readVelocityAwareSettings(doc); + const auto& regions = settings.getRefinementRegions(); + if (regions.empty()) { + logError() << "No refinement cuboid in" << settingsFile; + } + for (std::size_t c = 0; c < regions.size(); ++c) { + if (regions[c].bypassFindRegionAndUseGroup <= 0) { + logError() << "Refinement cuboid" << c + << "has no group; pumgen-sizefield needs bypassFindRegionAndUseGroup"; + } + } + + const VelocityAwareMeshSize meshSize(settings); + std::vector gridFiles; + std::vector groups; + for (std::size_t c = 0; c < regions.size(); ++c) { + const auto grid = gridForCuboid(regions[c].cuboid, spacing); + for (const auto count : grid.count) { + if (count > static_cast(std::numeric_limits::max())) { + logError() << "Too many grid nodes for refinement cuboid" << c; + } + } + logInfo() << "Refinement cuboid" << c << "(group" << regions[c].bypassFindRegionAndUseGroup + << ", " << regions[c].targetedFrequency << "Hz): grid of" << grid.count[0] << "x" + << grid.count[1] << "x" << grid.count[2] << "nodes"; + + const std::size_t first = getChunksum(grid.count[0], rank, size); + const std::size_t last = first + getChunksize(grid.count[0], rank, size); + const auto values = cuboidMeshSizes(meshSize, regions[c], grid, first, last); + + double smallest = + values.empty() ? UnconstrainedSize : *std::min_element(values.begin(), values.end()); + MPI_Allreduce(MPI_IN_PLACE, &smallest, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD); + logInfo() << "Refinement cuboid" << c << ": smallest mesh size" << smallest; + + const std::string gridFile = prefix + "-" + std::to_string(c) + ".bin"; + writeGrid(gridFile, grid, values, first, MPI_COMM_WORLD); + gridFiles.push_back(gridFile); + groups.push_back(regions[c].bypassFindRegionAndUseGroup); + } + + if (rank == 0) { + writeGeo(prefix + ".geo", settingsFile, gridFiles, groups, firstField); + logInfo() << "Wrote" << (prefix + ".geo").c_str(); + } + + MPI_Finalize(); + return 0; +} diff --git a/src/sizing/SizeField.cpp b/src/sizing/SizeField.cpp new file mode 100644 index 0000000..93d3600 --- /dev/null +++ b/src/sizing/SizeField.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "SizeField.h" + +#include +#include + +StructuredGrid gridForCuboid(const SimpleCuboid& cuboid, double spacing) { + const auto box = cuboid.boundingBox(2 * spacing); + StructuredGrid grid{box[0], spacing, {}}; + for (int d = 0; d < 3; ++d) { + grid.count[d] = static_cast(std::ceil((box[1][d] - box[0][d]) / spacing)) + 1; + } + return grid; +} + +std::vector cuboidMeshSizes(const VelocityAwareMeshSize& meshSize, + const VelocityRefinementCube& region, + const StructuredGrid& grid, std::size_t first, + std::size_t last) { + const std::size_t ny = grid.count[1]; + const std::size_t nz = grid.count[2]; + const std::size_t layer = ny * nz; + // one layer more on each side for the neighbourhood minimum + const std::size_t haloFirst = first > 0 ? first - 1 : first; + const std::size_t haloLast = std::min(last + 1, grid.count[0]); + const std::size_t numLayers = haloLast - haloFirst; + + std::vector sizes(numLayers * layer, UnconstrainedSize); + std::vector> points; + std::vector indices; + for (std::size_t i = haloFirst; i < haloLast; ++i) { + for (std::size_t j = 0; j < ny; ++j) { + for (std::size_t k = 0; k < nz; ++k) { + const auto point = grid.node(i, j, k); + if (region.cuboid.contains(point, 2 * grid.spacing)) { + points.push_back(point); + indices.push_back(((i - haloFirst) * ny + j) * nz + k); + } + } + } + } + const std::vector groups(points.size(), region.bypassFindRegionAndUseGroup); + const std::vector frequencies(points.size(), region.targetedFrequency); + const auto values = meshSize.meshSizes(points, groups, frequencies); + for (std::size_t p = 0; p < points.size(); ++p) { + sizes[indices[p]] = std::min(values[p], UnconstrainedSize); + } + + // the minimum over the 3x3x3 neighbourhood, one direction after the other + std::vector previous; + const auto minimumAlong = [&](std::size_t count, std::size_t stride) { + previous = sizes; + for (std::size_t index = 0; index < sizes.size(); ++index) { + const std::size_t position = (index / stride) % count; + if (position > 0) { + sizes[index] = std::min(sizes[index], previous[index - stride]); + } + if (position + 1 < count) { + sizes[index] = std::min(sizes[index], previous[index + stride]); + } + } + }; + minimumAlong(nz, 1); + minimumAlong(ny, nz); + minimumAlong(numLayers, layer); + + // drop the halo layers + const std::size_t offset = (first - haloFirst) * layer; + return {sizes.begin() + static_cast(offset), + sizes.begin() + static_cast(offset + (last - first) * layer)}; +} diff --git a/src/sizing/SizeField.h b/src/sizing/SizeField.h new file mode 100644 index 0000000..c5b0758 --- /dev/null +++ b/src/sizing/SizeField.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_SIZING_SIZEFIELD_H_ +#define PUMGEN_SRC_SIZING_SIZEFIELD_H_ + +#include +#include +#include + +#include "VelocityAwareMeshSize.h" + +/** + * A regular grid for gmsh's Structured field: the value of node (i, j, k) is stored at + * (i * count[1] + j) * count[2] + k. + */ +struct StructuredGrid { + std::array origin; + double spacing; + std::array count; + + [[nodiscard]] std::array node(std::size_t i, std::size_t j, std::size_t k) const { + return {origin[0] + static_cast(i) * spacing, + origin[1] + static_cast(j) * spacing, + origin[2] + static_cast(k) * spacing}; + } +}; + +/** + * The value of grid nodes which do not constrain the mesh size. + */ +constexpr double UnconstrainedSize = 1e22; + +/** + * The grid covering a cuboid with a margin of two grid spacings, so that all grid cells which + * intersect the cuboid have their corners within the margin. + */ +StructuredGrid gridForCuboid(const SimpleCuboid& cuboid, double spacing); + +/** + * The mesh sizes of the grid nodes with x index in [first, last) for one refinement cuboid. + * + * The nodes within the margin of the cuboid get the mesh size of the material of the given group; + * the others get UnconstrainedSize. Each node then takes the minimum of its 3x3x3 neighbourhood, + * so that the trilinear interpolation in a grid cell never exceeds the smallest size sampled at the + * cell's corners. + */ +std::vector cuboidMeshSizes(const VelocityAwareMeshSize& meshSize, + const VelocityRefinementCube& region, + const StructuredGrid& grid, std::size_t first, + std::size_t last); + +#endif // PUMGEN_SRC_SIZING_SIZEFIELD_H_ diff --git a/src/sizing/VelocityAwareMeshSize.cpp b/src/sizing/VelocityAwareMeshSize.cpp new file mode 100644 index 0000000..dfbfae9 --- /dev/null +++ b/src/sizing/VelocityAwareMeshSize.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2021 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "VelocityAwareMeshSize.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "utils/logger.h" + +namespace { +struct ElasticMaterial { + double lambda; + double mu; + double rho; +}; + +// the material model is evaluated in batches of this many points +constexpr std::size_t QueryBatch = std::size_t{1} << 20; +} // namespace + +VelocityAwareMeshSize::VelocityAwareMeshSize(VelocityAwareRefinementSettings settings) + : settings(std::move(settings)), parser(std::make_unique(3)) { + model = parser->parse(this->settings.getEasiFileName()); + if (model == nullptr) { + logError() << "Could not parse the easi file" << this->settings.getEasiFileName(); + } +} + +VelocityAwareMeshSize::~VelocityAwareMeshSize() { delete model; } + +std::pair +VelocityAwareMeshSize::targetedFrequencyAndGroup(const std::array& point) const { + double targetedFrequency = 0.0; + int bypassFindRegionAndUseGroup = 0; + for (const auto& refinementCube : settings.getRefinementRegions()) { + if (refinementCube.cuboid.contains(point)) { + if (refinementCube.targetedFrequency >= targetedFrequency) { + bypassFindRegionAndUseGroup = refinementCube.bypassFindRegionAndUseGroup; + } + targetedFrequency = std::max(targetedFrequency, refinementCube.targetedFrequency); + } + } + return {targetedFrequency, bypassFindRegionAndUseGroup}; +} + +std::vector +VelocityAwareMeshSize::meshSizes(const std::vector>& points, + const std::vector& groups, + const std::vector& frequencies) const { + std::vector sizes(points.size(), std::numeric_limits::max()); + + std::vector evaluated; + for (std::size_t i = 0; i < points.size(); ++i) { + if (frequencies[i] > 0.0 && groups[i] > 0) { + evaluated.push_back(i); + } + } + + std::vector materials; + for (std::size_t first = 0; first < evaluated.size(); first += QueryBatch) { + const std::size_t count = std::min(QueryBatch, evaluated.size() - first); + easi::Query query(count, 3); + for (std::size_t j = 0; j < count; ++j) { + const auto i = evaluated[first + j]; + for (int k = 0; k < 3; ++k) { + query.x(j, k) = points[i][k]; + } + query.group(j) = groups[i]; + } + + materials.assign(count, ElasticMaterial{}); + easi::ArrayOfStructsAdapter adapter(materials.data()); + adapter.addBindingPoint("lambda", &ElasticMaterial::lambda); + adapter.addBindingPoint("mu", &ElasticMaterial::mu); + adapter.addBindingPoint("rho", &ElasticMaterial::rho); + model->evaluate(query, adapter); + + for (std::size_t j = 0; j < count; ++j) { + const auto& material = materials[j]; + // the shear wave speed, or the P wave speed in acoustic material + const double waveSpeed = material.mu < 10e-14 + ? std::sqrt((material.lambda + 2 * material.mu) / material.rho) + : std::sqrt(material.mu / material.rho); + const auto i = evaluated[first + j]; + const double waveLength = waveSpeed / frequencies[i]; + sizes[i] = waveLength / settings.getElementsPerWaveLength(); + } + } + return sizes; +} diff --git a/src/sizing/VelocityAwareMeshSize.h b/src/sizing/VelocityAwareMeshSize.h new file mode 100644 index 0000000..2036175 --- /dev/null +++ b/src/sizing/VelocityAwareMeshSize.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2021 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_SIZING_VELOCITYAWAREMESHSIZE_H_ +#define PUMGEN_SRC_SIZING_VELOCITYAWAREMESHSIZE_H_ + +#include +#include +#include +#include + +#include "VelocityAwareSettings.h" + +namespace easi { +class Component; +class YAMLParser; +} // namespace easi + +/** + * Mesh sizes from the wave speeds of an easi material model: the wavelength of the targeted + * frequency divided by the number of elements per wavelength. + */ +class VelocityAwareMeshSize { + public: + explicit VelocityAwareMeshSize(VelocityAwareRefinementSettings settings); + ~VelocityAwareMeshSize(); + VelocityAwareMeshSize(const VelocityAwareMeshSize&) = delete; + VelocityAwareMeshSize& operator=(const VelocityAwareMeshSize&) = delete; + + /** + * The highest targeted frequency of the refinement regions containing the point (0 if there is + * none) and the group fixed by that region (0 if the region does not fix one). + */ + [[nodiscard]] std::pair + targetedFrequencyAndGroup(const std::array& point) const; + + /** + * The mesh sizes at the points for the given frequencies, with the material of the given + * groups; points with a frequency of 0 or a group <= 0 get std::numeric_limits::max(). + * The material model is evaluated for all points at once. + */ + [[nodiscard]] std::vector meshSizes(const std::vector>& points, + const std::vector& groups, + const std::vector& frequencies) const; + + [[nodiscard]] const VelocityAwareRefinementSettings& getSettings() const { return settings; } + + private: + VelocityAwareRefinementSettings settings; + std::unique_ptr parser; + easi::Component* model = nullptr; +}; + +#endif // PUMGEN_SRC_SIZING_VELOCITYAWAREMESHSIZE_H_ diff --git a/src/sizing/VelocityAwareSettings.cpp b/src/sizing/VelocityAwareSettings.cpp new file mode 100644 index 0000000..98c9406 --- /dev/null +++ b/src/sizing/VelocityAwareSettings.cpp @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2020 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "VelocityAwareSettings.h" + +#include +#include +#include +#include + +#include "tinyxml2/tinyxml2.h" +#include "utils/logger.h" + +namespace { +const double ToRadians = std::acos(-1) / 180.0; +} // namespace + +bool SimpleCuboid::contains(const std::array& point, double margin) const { + const double u0 = + (point[0] - center[0]) * cosSinRotationZ[0] + (point[1] - center[1]) * cosSinRotationZ[1]; + const double u1 = + (point[0] - center[0]) * -cosSinRotationZ[1] + (point[1] - center[1]) * cosSinRotationZ[0]; + const double u2 = point[2] - center[2]; + return std::abs(u0) <= halfSize[0] + margin && std::abs(u1) <= halfSize[1] + margin && + std::abs(u2) <= halfSize[2] + margin; +} + +std::array, 2> SimpleCuboid::boundingBox(double margin) const { + const double hx = halfSize[0] + margin; + const double hy = halfSize[1] + margin; + const double hz = halfSize[2] + margin; + const double c = std::abs(cosSinRotationZ[0]); + const double s = std::abs(cosSinRotationZ[1]); + const double ex = hx * c + hy * s; + const double ey = hx * s + hy * c; + return {{{center[0] - ex, center[1] - ey, center[2] - hz}, + {center[0] + ex, center[1] + ey, center[2] + hz}}}; +} + +VelocityAwareRefinementSettings::VelocityAwareRefinementSettings(double elementsPerWaveLength, + std::string easiFileName) + : elementsPerWaveLength(elementsPerWaveLength), easiFileName(std::move(easiFileName)) {} + +void VelocityAwareRefinementSettings::addRefinementRegion(SimpleCuboid cuboid, + double targetedFrequency, + int bypassFindRegionAndUseGroup) { + refinementRegions.emplace_back(cuboid, targetedFrequency, bypassFindRegionAndUseGroup); +} + +bool VelocityAwareRefinementSettings::isVelocityAwareRefinementOn() const { + return !refinementRegions.empty(); +} + +const std::string& VelocityAwareRefinementSettings::getEasiFileName() const { return easiFileName; } + +double VelocityAwareRefinementSettings::getElementsPerWaveLength() const { + return elementsPerWaveLength; +} + +const std::vector& +VelocityAwareRefinementSettings::getRefinementRegions() const { + return refinementRegions; +} + +VelocityAwareRefinementSettings readVelocityAwareSettings(const tinyxml2::XMLDocument& doc) { + VelocityAwareRefinementSettings settings; + int numChilds = 0; + const auto name = "VelocityAwareMeshing"; + for (auto velocityAwareMeshingElement = doc.FirstChildElement(name); velocityAwareMeshingElement; + velocityAwareMeshingElement = velocityAwareMeshingElement->NextSiblingElement(name)) { + const auto easiFileName = velocityAwareMeshingElement->Attribute("easiFile"); + const auto elementsPerWaveLength = + std::stof(velocityAwareMeshingElement->Attribute("elementsPerWaveLength")); + settings = VelocityAwareRefinementSettings(elementsPerWaveLength, easiFileName); + constexpr auto cuboidName = "VelocityRefinementCuboid"; + logInfo() << "Activating velocity aware meshing, using" << elementsPerWaveLength + << "elements per wavelength and easi file" << easiFileName; + for (auto child = velocityAwareMeshingElement->FirstChildElement(cuboidName); child; + child = child->NextSiblingElement(cuboidName)) { + + const char* attr = child->Attribute("rotationZAnticlockwiseFromX"); + double rotationZAnticlockwiseFromX = 0.0; + if (attr != nullptr) { + rotationZAnticlockwiseFromX = std::stof(attr); + } + attr = child->Attribute("bypassFindRegionAndUseGroup"); + int bypassFindRegionAndUseGroup = 0; + if (attr != nullptr) { + bypassFindRegionAndUseGroup = std::stoi(attr); + } + auto cuboid = SimpleCuboid{{ + std::stof(child->Attribute("centerX")), + std::stof(child->Attribute("centerY")), + std::stof(child->Attribute("centerZ")), + }, + { + std::stof(child->Attribute("halfSizeX")), + std::stof(child->Attribute("halfSizeY")), + std::stof(child->Attribute("halfSizeZ")), + }, + {std::cos(rotationZAnticlockwiseFromX * ToRadians), + std::sin(rotationZAnticlockwiseFromX * ToRadians)}, + rotationZAnticlockwiseFromX}; + const auto targetedFrequency = std::stof(child->Attribute("frequency")); + + settings.addRefinementRegion(cuboid, targetedFrequency, bypassFindRegionAndUseGroup); + logInfo() << "Adding velocity aware refinement region targeting" << targetedFrequency + << "Hz, centered at x =" << cuboid.center[0] << "y=" << cuboid.center[1] + << "z=" << cuboid.center[2] << "with half sizes" + << "x =" << cuboid.halfSize[0] << "y =" << cuboid.halfSize[1] + << "z =" << cuboid.halfSize[2]; + if (std::abs(cuboid.rotationZ) > 0.0) { + logInfo() << "rotated around z axis by " << cuboid.rotationZ + << "degree(s) counterclockwise from x axis."; + } + if (bypassFindRegionAndUseGroup) { + logInfo() << "bypass findRegion and use group =" << bypassFindRegionAndUseGroup; + } + } + if (!settings.isVelocityAwareRefinementOn()) { + logWarning() << "Activated velocity aware meshing but did not specify any refinement region!"; + } + ++numChilds; + } + if (numChilds > 1) { + logError() << "Multiple definitions of velocityAwareMeshing"; + } + return settings; +} diff --git a/src/sizing/VelocityAwareSettings.h b/src/sizing/VelocityAwareSettings.h new file mode 100644 index 0000000..7a9b798 --- /dev/null +++ b/src/sizing/VelocityAwareSettings.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2020 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_SIZING_VELOCITYAWARESETTINGS_H_ +#define PUMGEN_SRC_SIZING_VELOCITYAWARESETTINGS_H_ + +#include +#include +#include + +namespace tinyxml2 { +class XMLDocument; +} // namespace tinyxml2 + +/** + * A cuboid, rotated around the z axis. + */ +struct SimpleCuboid { + std::array center; + std::array halfSize; + std::array cosSinRotationZ; + double rotationZ; + + /** + * Whether the point lies in the cuboid, enlarged by margin in each direction. + */ + [[nodiscard]] bool contains(const std::array& point, double margin = 0) const; + + /** + * The axis-aligned bounding box (minimum and maximum corner) of the enlarged cuboid. + */ + [[nodiscard]] std::array, 2> boundingBox(double margin = 0) const; +}; + +struct VelocityRefinementCube { + VelocityRefinementCube(SimpleCuboid cuboid, double targetedFrequency, + int bypassFindRegionAndUseGroup) + : cuboid(cuboid), targetedFrequency(targetedFrequency), + bypassFindRegionAndUseGroup(bypassFindRegionAndUseGroup) {} + + SimpleCuboid cuboid; + double targetedFrequency; + int bypassFindRegionAndUseGroup; +}; + +class VelocityAwareRefinementSettings { + public: + VelocityAwareRefinementSettings() = default; + VelocityAwareRefinementSettings(double elementsPerWaveLength, std::string easiFileName); + + void addRefinementRegion(SimpleCuboid cuboid, double targetedFrequency, + int bypassFindRegionAndUseGroup); + + [[nodiscard]] bool isVelocityAwareRefinementOn() const; + + [[nodiscard]] const std::string& getEasiFileName() const; + + [[nodiscard]] double getElementsPerWaveLength() const; + + [[nodiscard]] const std::vector& getRefinementRegions() const; + + private: + double elementsPerWaveLength{}; + std::string easiFileName; + std::vector refinementRegions{}; +}; + +/** + * Reads the VelocityAwareMeshing element of a mesh attributes document; the settings are empty + * (isVelocityAwareRefinementOn() is false) if the document has none. + */ +VelocityAwareRefinementSettings readVelocityAwareSettings(const tinyxml2::XMLDocument& doc); + +#endif // PUMGEN_SRC_SIZING_VELOCITYAWARESETTINGS_H_ diff --git a/src/sizing/VelocityCheck.cpp b/src/sizing/VelocityCheck.cpp new file mode 100644 index 0000000..04f6a59 --- /dev/null +++ b/src/sizing/VelocityCheck.cpp @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#include "VelocityCheck.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/logger.h" + +namespace { + +// log-spaced histogram of the ratios between MinRatio and MaxRatio +constexpr double MinRatio = 1e-3; +constexpr double MaxRatio = 1e3; +constexpr std::size_t NumBins = 6000; +constexpr std::size_t Batch = std::size_t{1} << 20; + +std::size_t binOf(double ratio) { + const double position = + std::log(ratio / MinRatio) / std::log(MaxRatio / MinRatio) * static_cast(NumBins); + return static_cast(std::clamp(position, 0.0, static_cast(NumBins - 1))); +} + +double upperEdgeOf(std::size_t bin) { + return MinRatio * std::pow(MaxRatio / MinRatio, static_cast(bin + 1) / NumBins); +} + +struct RatioStatistics { + std::vector histogram = std::vector(NumBins, 0); + std::uint64_t above = 0; + double maximum = 0; + + void add(double ratio) { + ++histogram[binOf(ratio)]; + above += ratio > 1 ? 1 : 0; + maximum = std::max(maximum, ratio); + } + + void reduce(MPI_Comm comm) { + MPI_Allreduce(MPI_IN_PLACE, histogram.data(), NumBins, MPI_UINT64_T, MPI_SUM, comm); + MPI_Allreduce(MPI_IN_PLACE, &above, 1, MPI_UINT64_T, MPI_SUM, comm); + MPI_Allreduce(MPI_IN_PLACE, &maximum, 1, MPI_DOUBLE, MPI_MAX, comm); + } + + /** + * The quantile, rounded up to the next bin edge (0.23 % apart). + */ + [[nodiscard]] double quantile(double q, std::uint64_t total) const { + const auto target = static_cast(std::ceil(q * static_cast(total))); + std::uint64_t cumulative = 0; + for (std::size_t bin = 0; bin < NumBins; ++bin) { + cumulative += histogram[bin]; + if (cumulative >= target && cumulative > 0) { + return std::min(upperEdgeOf(bin), maximum); + } + } + return maximum; + } + + [[nodiscard]] std::string summary(std::uint64_t total) const { + std::stringstream stream; + stream << std::setprecision(3) << "p50 " << quantile(0.5, total) << ", p90 " + << quantile(0.9, total) << ", p99 " << quantile(0.99, total) << ", max " << maximum + << "; " << above << " cells above 1"; + return stream.str(); + } +}; + +double distance(const std::array& a, const std::array& b) { + return std::sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]) + + (a[2] - b[2]) * (a[2] - b[2])); +} + +} // namespace + +void checkVelocityAwareMeshSize(const VelocityAwareMeshSize& meshSize, const CellVertices& cells, + const std::vector& groups, MPI_Comm comm) { + RatioStatistics meanEdge; + RatioStatistics longestEdge; + std::uint64_t checked = 0; + + std::vector> barycenters; + std::vector cellGroups; + std::vector frequencies; + std::vector> edges; + for (std::size_t first = 0; first < cells.numCells(); first += Batch) { + const std::size_t count = std::min(Batch, cells.numCells() - first); + barycenters.resize(count); + cellGroups.resize(count); + frequencies.resize(count); + edges.resize(count); + for (std::size_t c = 0; c < count; ++c) { + const auto vertices = cells(first + c); + std::array barycenter{}; + double sum = 0; + double longest = 0; + for (std::size_t a = 0; a < vertices.size(); ++a) { + for (int d = 0; d < 3; ++d) { + barycenter[d] += vertices[a][d] / static_cast(vertices.size()); + } + for (std::size_t b = a + 1; b < vertices.size(); ++b) { + const double length = distance(vertices[a], vertices[b]); + sum += length; + longest = std::max(longest, length); + } + } + barycenters[c] = barycenter; + cellGroups[c] = groups[first + c]; + frequencies[c] = meshSize.targetedFrequencyAndGroup(barycenter).first; + edges[c] = {sum / 6, longest}; + } + + const auto sizes = meshSize.meshSizes(barycenters, cellGroups, frequencies); + for (std::size_t c = 0; c < count; ++c) { + if (sizes[c] == std::numeric_limits::max()) { + continue; + } + ++checked; + meanEdge.add(edges[c][0] / sizes[c]); + longestEdge.add(edges[c][1] / sizes[c]); + } + } + + std::uint64_t total = cells.numCells(); + MPI_Allreduce(MPI_IN_PLACE, &checked, 1, MPI_UINT64_T, MPI_SUM, comm); + MPI_Allreduce(MPI_IN_PLACE, &total, 1, MPI_UINT64_T, MPI_SUM, comm); + meanEdge.reduce(comm); + longestEdge.reduce(comm); + + logInfo() << "Velocity check:" << checked << "of" << total + << "cells lie in refinement regions (ratios of edge length and mesh size)"; + if (checked > 0) { + logInfo() << "Velocity check, mean edge:" << meanEdge.summary(checked).c_str(); + logInfo() << "Velocity check, longest edge:" << longestEdge.summary(checked).c_str(); + } +} diff --git a/src/sizing/VelocityCheck.h b/src/sizing/VelocityCheck.h new file mode 100644 index 0000000..202a3b3 --- /dev/null +++ b/src/sizing/VelocityCheck.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause +#ifndef PUMGEN_SRC_SIZING_VELOCITYCHECK_H_ +#define PUMGEN_SRC_SIZING_VELOCITYCHECK_H_ + +#include +#include + +#include "VelocityAwareMeshSize.h" +#include "helper/InsphereCalculator.h" + +/** + * Compares the edge lengths of the cells in the refinement regions with the velocity-aware mesh + * size at their barycentre (for the material of the cell's group) and logs the distribution of the + * ratios, as mean and as longest edge length over mesh size. + */ +void checkVelocityAwareMeshSize(const VelocityAwareMeshSize& meshSize, const CellVertices& cells, + const std::vector& groups, MPI_Comm comm); + +#endif // PUMGEN_SRC_SIZING_VELOCITYCHECK_H_ diff --git a/src/third_party/GMSH2Parser.cpp b/src/third_party/GMSH2Parser.cpp deleted file mode 100644 index 0da1527..0000000 --- a/src/third_party/GMSH2Parser.cpp +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#include "GMSH2Parser.h" -#include "GMSHLexer.h" - -#include - -namespace tndm { - -bool GMSH2Parser::parse_() { - errorMsg.clear(); - getNextToken(); - - double version = parseMeshFormat(); - if (version < 2.0 || version >= 3.0) { - char buf[128]; - sprintf(buf, "Unsupported MSH version %.1lf", version); - return logError(buf); - } - - bool hasNodes = false; - bool hasElements = false; - - while (curTok != GMSHToken::eof) { - switch (curTok) { - case GMSHToken::nodes: - hasNodes = parseNodes(); - break; - case GMSHToken::elements: - hasElements = parseElements(); - break; - case GMSHToken::periodic: - parsePeriodic(); - break; - default: - getNextToken(); - break; - } - } - - return hasNodes && hasElements; -} - -bool GMSH2Parser::parseNodes() { - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 0) { - return logErrorAnnotated("Expected non-zero integer"); - } - std::size_t numVertices = lexer.getInteger(); - builder->setNumVertices(numVertices); - - for (std::size_t i = 0; i < numVertices; ++i) { - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 1 || - lexer.getInteger() > numVertices) { - char buf[128]; - sprintf(buf, "Expected node-tag with 1 <= node-tag <= %zu", numVertices); - return logErrorAnnotated(buf); - } - std::size_t id = lexer.getInteger() - 1; - - std::array x; - for (std::size_t i = 0; i < 3; ++i) { - getNextToken(); - auto coord = getNumber(); - if (!coord) { - return logErrorAnnotated("Expected coordinate"); - } - x[i] = *coord; - } - builder->setVertex(id, x); - } - getNextToken(); - if (curTok != GMSHToken::end_nodes) { - return logErrorAnnotated("Expected $EndNodes"); - } - getNextToken(); - return true; -} - -bool GMSH2Parser::parseElements() { - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 0) { - return logErrorAnnotated("Expected non-negative integer"); - } - const auto numElements = lexer.getInteger(); - - constexpr std::size_t MaxNodes = sizeof(NumNodes) / sizeof(std::size_t); - long tag{}; - std::array nodes; - - builder->setNumElements(numElements); - - for (std::size_t elementIdx = 0; elementIdx < numElements; ++elementIdx) { - getNextToken(); - if (curTok != GMSHToken::integer) { - return logErrorAnnotated("Expected element-tag"); - } - - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 1 || lexer.getInteger() > MaxNodes) { - char buf[128]; - sprintf(buf, "Expected element-type with 1 <= element-type <= %zu", MaxNodes); - return logErrorAnnotated(buf); - } - long type = lexer.getInteger(); - - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 0) { - return logErrorAnnotated("Expected number of tags"); - } - long numTags = lexer.getInteger(); - for (long tagIdx = 0; tagIdx < numTags; ++tagIdx) { - getNextToken(); - if (curTok != GMSHToken::integer) { - return logErrorAnnotated("Expected tag (integer)"); - } - if (tagIdx == 0) { - tag = lexer.getInteger(); - } - } - - for (std::size_t nodeIdx = 0; nodeIdx < NumNodes[type - 1]; ++nodeIdx) { - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() < 1) { - char buf[128]; - sprintf(buf, "Expected node number > 0 (%zu/%zu for type %li)", nodeIdx + 1, - NumNodes[type - 1], type); - return logErrorAnnotated(buf); - } - nodes[nodeIdx] = lexer.getInteger() - 1; - } - - builder->addElement(type, tag, nodes.data(), NumNodes[type - 1]); - } - getNextToken(); - if (curTok != GMSHToken::end_elements) { - return logErrorAnnotated("Expected $EndElements"); - } - getNextToken(); - - return true; -} - -bool GMSH2Parser::parsePeriodic() { - getNextToken(); - const auto numPeriodic = expectNonNegativeInt(); - - // ignore everything but the node identification - - for (std::size_t blockIdx = 0; blockIdx < numPeriodic; blockIdx++) { - getNextToken(); - [[maybe_unused]] const std::size_t entityDim = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t entityId = expectNonNegativeInt(); - getNextToken(); - [[maybe_unused]] const std::size_t entityIdentifyId = expectNonNegativeInt(); - - getNextToken(); - if (curTok == GMSHToken::string) { - // Gmsh msh2 may write: "Affine a11 a12 ... a44" (16 values) - constexpr std::size_t affineSize = 16; - for (std::size_t i = 0; i < affineSize; ++i) { - getNextToken(); - [[maybe_unused]] const double affineValue = expectNumber(); - } - getNextToken(); - } - const std::size_t identifySize = expectNonNegativeInt(); - - for (std::size_t i = 0; i < identifySize; ++i) { - getNextToken(); - const std::size_t nodeId = expectNonNegativeInt() - 1; - getNextToken(); - const std::size_t nodeIdentifyId = expectNonNegativeInt() - 1; - builder->addVertexLink(nodeId, nodeIdentifyId); - } - } - - getNextToken(); - if (curTok != tndm::GMSHToken::end_periodic) { - return logErrorAnnotated("Expected $EndPeriodic"); - } - getNextToken(); - - return true; -} - -} // namespace tndm diff --git a/src/third_party/GMSH2Parser.h b/src/third_party/GMSH2Parser.h deleted file mode 100644 index 83e33bb..0000000 --- a/src/third_party/GMSH2Parser.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#ifndef PUMGEN_SRC_THIRD_PARTY_GMSH2PARSER_H_ -#define PUMGEN_SRC_THIRD_PARTY_GMSH2PARSER_H_ - -#include "GMSHLexer.h" -#include "GMSHMeshBuilder.h" -#include "GMSHParser.h" - -#include -#include -#include -#include -#include - -namespace tndm { - -class GMSH2Parser : public GMSHParser { - public: - using GMSHParser::GMSHParser; - - private: - unsigned long expectNonNegativeInt() { - if (curTok != tndm::GMSHToken::integer || lexer.getInteger() < 0) { - return logErrorAnnotated("Expected non-negative integer"); - } - return static_cast(lexer.getInteger()); - } - - double expectNumber() { - auto num = getNumber(); - if (!num) { - return logErrorAnnotated("Expected number"); - } - return num.value(); - } - - bool parseNodes(); - bool parseElements(); - bool parsePeriodic(); - virtual bool parse_() override; -}; - -}; // namespace tndm - -#endif // PUMGEN_SRC_THIRD_PARTY_GMSH2PARSER_H_ diff --git a/src/third_party/GMSHLexer.cpp b/src/third_party/GMSHLexer.cpp deleted file mode 100644 index 65cbef6..0000000 --- a/src/third_party/GMSHLexer.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#include "GMSHLexer.h" -#include "Hash.h" - -namespace tndm { - -void GMSHLexer::advance() { - in->get(lastChar); - - if (lastChar == '\n' || lastChar == '\r') { - ++loc.line; - loc.col = 1; - } else { - ++loc.col; - } -} - -GMSHToken GMSHLexer::getToken() { - if (in == nullptr) { - return GMSHToken::eof; - } - in->peek(); - if (!in->good()) { - return GMSHToken::eof; - } - - while (isspace(lastChar) && in->good()) { - advance(); - } - if (!in->good()) { - return GMSHToken::eof; - } - - if (lastChar == '$') { - advance(); - auto hash = fnv1a0(); - while (isalpha(lastChar)) { - hash = fnv1a_step(hash, lastChar); - advance(); - } - GMSHToken token = GMSHToken::unknown_section; - switch (hash) { - case "MeshFormat"_fnv1a: - token = GMSHToken::mesh_format; - break; - case "EndMeshFormat"_fnv1a: - token = GMSHToken::end_mesh_format; - break; - case "Entities"_fnv1a: - token = GMSHToken::entities; - break; - case "EndEntities"_fnv1a: - token = GMSHToken::end_entities; - break; - case "Nodes"_fnv1a: - token = GMSHToken::nodes; - break; - case "EndNodes"_fnv1a: - token = GMSHToken::end_nodes; - break; - case "Elements"_fnv1a: - token = GMSHToken::elements; - break; - case "EndElements"_fnv1a: - token = GMSHToken::end_elements; - break; - case "Periodic"_fnv1a: - token = GMSHToken::periodic; - break; - case "EndPeriodic"_fnv1a: - token = GMSHToken::end_periodic; - break; - default: - break; - } - return token; - } - - auto mustbereal = [](char c) { return c == '.' || c == 'e' || c == 'E'; }; - auto isnumber = [&mustbereal](char c) { - return isdigit(c) || c == '+' || c == '-' || mustbereal(c); - }; - - if (isnumber(lastChar)) { - char buf[MaxNumberLength + 1]; - int pos = 0; - bool isreal = false; - do { - buf[pos++] = lastChar; - isreal = isreal || mustbereal(lastChar); - advance(); - } while (isnumber(lastChar) && pos < MaxNumberLength); - buf[pos] = 0; - if (pos == MaxNumberLength) { - throw std::runtime_error("Too large number encountered in GMSHLexer: " + std::string(buf)); - } - if (isreal) { - real = std::strtod(buf, 0); - return GMSHToken::real; - } - integer = std::strtol(buf, 0, 10); - return GMSHToken::integer; - } - - if (lastChar == '"') { - do { - advance(); - } while (lastChar != '"'); - advance(); - return GMSHToken::string; - } - if (isalpha(lastChar)) { - do { - advance(); - } while (isalpha(lastChar)); - return GMSHToken::string; - } - // Always consume at least one character for unknown tokens to avoid - // getting stuck in an infinite tokenization loop. - advance(); - return GMSHToken::unknown_token; -} - -} // namespace tndm diff --git a/src/third_party/GMSHLexer.h b/src/third_party/GMSHLexer.h deleted file mode 100644 index 2b9eb67..0000000 --- a/src/third_party/GMSHLexer.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#ifndef PUMGEN_SRC_THIRD_PARTY_GMSHLEXER_H_ -#define PUMGEN_SRC_THIRD_PARTY_GMSHLEXER_H_ - -#include -#include - -namespace tndm { - -enum class GMSHToken { - eof, - integer, - real, - string, - mesh_format, - end_mesh_format, - entities, - end_entities, - nodes, - end_nodes, - elements, - end_elements, - periodic, - end_periodic, - unknown_section, - unknown_token -}; - -struct GMSHSourceLocation { - std::size_t line; - std::size_t col; -}; - -class GMSHLexer { - private: - uint64_t identifier; - long integer; - double real; - char lastChar = ' '; - std::istream* in = nullptr; - GMSHSourceLocation loc = {1, 1}; - - protected: - void advance(); - - public: - static constexpr std::size_t MaxNumberLength = 128; - - void setIStream(std::istream* istream) { - in = istream; - loc = {1, 1}; - } - - GMSHToken getToken(); - uint64_t getIdentifier() const { return identifier; } - long getInteger() const { return integer; } - double getReal() const { return real; } - GMSHSourceLocation getSourceLoc() const { return loc; } -}; -} // namespace tndm - -#endif // PUMGEN_SRC_THIRD_PARTY_GMSHLEXER_H_ diff --git a/src/third_party/GMSHParser.cpp b/src/third_party/GMSHParser.cpp deleted file mode 100644 index 92881ee..0000000 --- a/src/third_party/GMSHParser.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#include "GMSHParser.h" - -namespace tndm { - -double GMSHParser::parseMeshFormat() { - if (curTok != GMSHToken::mesh_format) { - return logErrorAnnotated("Expected $MeshFormat"); - } - getNextToken(); - auto version = getNumber(); - if (!version) { - return logErrorAnnotated("Expected version number"); - } - getNextToken(); - if (curTok != GMSHToken::integer || lexer.getInteger() != 0) { - return logErrorAnnotated("Expected 0"); - } - getNextToken(); // skip data-size - getNextToken(); - if (curTok != GMSHToken::end_mesh_format) { - return logErrorAnnotated("Expected $EndMeshFormat"); - } - getNextToken(); - return *version; -} -} // namespace tndm diff --git a/src/third_party/Hash.h b/src/third_party/Hash.h deleted file mode 100644 index 11c328a..0000000 --- a/src/third_party/Hash.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2025 SeisSol Group -// SPDX-FileCopyrightText: 2020 Ludwig-Maximilians-Universität München -// -// SPDX-License-Identifier: BSD-3-Clause -#ifndef PUMGEN_SRC_THIRD_PARTY_HASH_H_ -#define PUMGEN_SRC_THIRD_PARTY_HASH_H_ - -#include -#include - -namespace tndm { - -// https://de.wikipedia.org/wiki/FNV_(Informatik) -constexpr uint64_t fnv1a0() { return 0xcbf29ce484222325; } -constexpr uint64_t fnv1a_step(uint64_t hash, char c) { return (hash ^ c) * 0x00000100000001b3; } -constexpr uint64_t fnv1a(char const* s, std::size_t len) { - return len > 0 ? fnv1a_step(fnv1a(s, len - 1), s[len - 1]) : fnv1a0(); -} -uint64_t fnv1a(std::string const& s) { return fnv1a(s.data(), s.size()); } -constexpr uint64_t operator""_fnv1a(char const* s, std::size_t len) { return tndm::fnv1a(s, len); } - -} // namespace tndm - -#endif // PUMGEN_SRC_THIRD_PARTY_HASH_H_ diff --git a/src/third_party/README.md b/src/third_party/README.md index 55b51fd..3721c9a 100644 --- a/src/third_party/README.md +++ b/src/third_party/README.md @@ -6,5 +6,8 @@ # Third party sources -Taken from ; -one further addition from . +`MPITraits.h` is taken from tandem, commit +. +The GMSH parsers in `src/meshreader` are derived from the tandem GMSH +parser of that commit and of commit +. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..d353628 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,245 @@ +# SPDX-FileCopyrightText: 2026 SeisSol Group +# +# SPDX-License-Identifier: BSD-3-Clause + +find_program(H5DIFF_EXECUTABLE NAMES h5diff DOC "h5diff, used to compare the test output") +if(NOT H5DIFF_EXECUTABLE) + message(WARNING "h5diff not found: the tests only check whether pumgen succeeds") +endif() + +set(PUMGEN_TEST_FIXTURES ${CMAKE_CURRENT_SOURCE_DIR}/fixtures) +set(PUMGEN_TEST_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/output) +file(MAKE_DIRECTORY ${PUMGEN_TEST_OUTPUT}) + +# pumgen_add_test( RANKS SOURCE INPUT +# [REFERENCE [TOLERANCE ] [IDENTIFY]] +# [EXPECT_OUTPUT ] [EXPECT_ERROR ] [ARGS ...]) +function(pumgen_add_test name) + cmake_parse_arguments(PARSE_ARGV 1 TEST "IDENTIFY" + "RANKS;SOURCE;INPUT;REFERENCE;TOLERANCE;EXPECT_OUTPUT;EXPECT_ERROR" "ARGS") + set(output ${PUMGEN_TEST_OUTPUT}/${name}.puml.h5) + if(DEFINED TEST_EXPECT_ERROR AND TEST_RANKS EQUAL 1) + # started as MPI singleton, since mpiexec may drop the output of a process ending with + # MPI_Abort + set(command $) + else() + set(command + ${MPIEXEC_EXECUTABLE} + ${MPIEXEC_NUMPROC_FLAG} + ${TEST_RANKS} + ${MPIEXEC_PREFLAGS} + $ + ${MPIEXEC_POSTFLAGS}) + endif() + list(APPEND command -s ${TEST_SOURCE} ${TEST_ARGS} ${PUMGEN_TEST_FIXTURES}/${TEST_INPUT} + ${output}) + string(REPLACE ";" "|" command "${command}") + + set(script_args -DCOMMAND=${command} -DOUTPUT=${output}) + if(DEFINED TEST_EXPECT_OUTPUT) + list(APPEND script_args "-DEXPECT_OUTPUT=${TEST_EXPECT_OUTPUT}") + endif() + if(DEFINED TEST_EXPECT_ERROR) + list(APPEND script_args "-DEXPECT_ERROR=${TEST_EXPECT_ERROR}") + elseif(DEFINED TEST_REFERENCE AND H5DIFF_EXECUTABLE) + list(APPEND script_args -DREFERENCE=${PUMGEN_TEST_FIXTURES}/${TEST_REFERENCE} + -DH5DIFF=${H5DIFF_EXECUTABLE}) + if(DEFINED TEST_TOLERANCE) + list(APPEND script_args -DTOLERANCE=${TEST_TOLERANCE}) + endif() + if(TEST_IDENTIFY) + list(APPEND script_args -DIDENTIFY=ON) + endif() + endif() + + add_test(NAME pumgen.${name} COMMAND ${CMAKE_COMMAND} ${script_args} -P + ${CMAKE_CURRENT_SOURCE_DIR}/RunPumgenTest.cmake) + set_tests_properties( + pumgen.${name} PROPERTIES PROCESSORS ${TEST_RANKS} TIMEOUT 120 ENVIRONMENT + "ASAN_OPTIONS=detect_leaks=0") +endfunction() + +# the GMSH parsers with a tiny read buffer +add_executable( + pumgen_reader_test + ReaderTest.cpp ${PROJECT_SOURCE_DIR}/src/meshreader/MshInput.cpp + ${PROJECT_SOURCE_DIR}/src/meshreader/GMSHParser.cpp + ${PROJECT_SOURCE_DIR}/src/meshreader/GMSH2Parser.cpp + ${PROJECT_SOURCE_DIR}/src/meshreader/GMSH4Parser.cpp) +target_include_directories(pumgen_reader_test PRIVATE ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/submodules) +target_compile_definitions(pumgen_reader_test PRIVATE LOG_LEVEL=${LOG_LEVEL}) +add_test(NAME reader.buffer-boundaries COMMAND pumgen_reader_test ${PUMGEN_TEST_FIXTURES}) +set_tests_properties(reader.buffer-boundaries PROPERTIES ENVIRONMENT "ASAN_OPTIONS=detect_leaks=0") + +# The fixtures are created by fixtures/generate.py; the reference files are computed from the +# gmsh model directly. The layered mesh has element and vertex counts which do not divide evenly +# among 3 or 4 ranks. + +foreach(ranks 1 2 3 4) + pumgen_add_test(msh4.layered.np${ranks} RANKS ${ranks} SOURCE msh4 INPUT layered-v41.msh + REFERENCE layered.puml.h5) +endforeach() +foreach(ranks 1 2 3) + pumgen_add_test(msh2.layered.np${ranks} RANKS ${ranks} SOURCE msh2 INPUT layered-v22.msh + REFERENCE layered.puml.h5) +endforeach() +foreach(ranks 1 2 3 4) + pumgen_add_test(gambit.layered.np${ranks} RANKS ${ranks} SOURCE gambit INPUT layered.neu + REFERENCE layered.puml.h5 TOLERANCE 1e-9) +endforeach() +pumgen_add_test(msh4.coarse.np2 RANKS 2 SOURCE msh4 INPUT coarse-v41.msh REFERENCE coarse.puml.h5) +pumgen_add_test(msh2.coarse-o2.np1 RANKS 1 SOURCE msh2 INPUT coarse-o2-v22.msh + REFERENCE coarse-o2.puml.h5 ARGS --order=2) +pumgen_add_test(msh4.tiny.np1 RANKS 1 SOURCE msh4 INPUT tiny-v41.msh REFERENCE tiny.puml.h5) +pumgen_add_test(msh2.tiny.np1 RANKS 1 SOURCE msh2 INPUT tiny-v22.msh REFERENCE tiny.puml.h5) + +# more ranks than cells +foreach(ranks 2 3) + pumgen_add_test(msh4.tiny.np${ranks} RANKS ${ranks} SOURCE msh4 INPUT tiny-v41.msh + REFERENCE tiny.puml.h5) +endforeach() + +# HDF5 filters +pumgen_add_test(msh4.layered.deflate.np3 RANKS 3 SOURCE msh4 INPUT layered-v41.msh + REFERENCE layered.puml.h5 ARGS --filter-enable=deflate1 --filter-chunksize=64) +pumgen_add_test(msh4.tiny.scaleoffset.np3 RANKS 3 SOURCE msh4 INPUT tiny-v41.msh + REFERENCE tiny.puml.h5 ARGS --filter-enable=scaleoffset) + +# command line options +pumgen_add_test(msh2.coarse-o2.shortopt.np1 RANKS 1 SOURCE msh2 INPUT coarse-o2-v22.msh + REFERENCE coarse-o2.puml.h5 ARGS -o 2) +pumgen_add_test(msh4.layered.chunksize.np3 RANKS 3 SOURCE msh4 INPUT layered-v41.msh + REFERENCE layered.puml.h5 ARGS --chunksize=4096) +pumgen_add_test(msh4.tiny.order0 RANKS 1 SOURCE msh4 INPUT tiny-v41.msh ARGS --order=0 + EXPECT_ERROR "mesh order has to be at least 1") + +# integer types reduced to the value range +pumgen_add_test(msh4.layered.compact.np3 RANKS 3 SOURCE msh4 INPUT layered-v41.msh + REFERENCE layered.puml.h5 ARGS --compactify-datatypes) +pumgen_add_test(msh2.coarse-o2.compact.np2 RANKS 2 SOURCE msh2 INPUT coarse-o2-v22.msh + REFERENCE coarse-o2.puml.h5 ARGS --compactify-datatypes --order=2) + +# malformed or unsupported input +pumgen_add_test(msh4.tiny-truncated RANKS 1 SOURCE msh4 INPUT tiny-truncated-v41.msh + EXPECT_ERROR "GMSH parser error") +pumgen_add_test(msh4.tiny-badnumber RANKS 1 SOURCE msh4 INPUT tiny-badnumber-v41.msh + EXPECT_ERROR "Expected number") +pumgen_add_test(msh4.tiny-unknowntype RANKS 1 SOURCE msh4 INPUT tiny-unknowntype-v41.msh + EXPECT_ERROR "Unknown element type 999") +pumgen_add_test(msh4.tiny-v40 RANKS 1 SOURCE msh4 INPUT tiny-v40.msh + EXPECT_ERROR "MSH 4.0 is not supported") +pumgen_add_test(msh2.tiny-as-msh4 RANKS 1 SOURCE msh2 INPUT tiny-v41.msh + EXPECT_ERROR "Unsupported MSH version 4.1") + +# periodic meshes +foreach(ranks 1 2 3) + pumgen_add_test(msh4.periodic.np${ranks} RANKS ${ranks} SOURCE msh4 INPUT periodic-v41.msh + REFERENCE periodic.puml.h5 IDENTIFY) +endforeach() + +# node tags +pumgen_add_test(msh4.coarse-offset.np2 RANKS 2 SOURCE msh4 INPUT coarse-offset-v41.msh + REFERENCE coarse.puml.h5) +pumgen_add_test(msh4.periodic-parametric.np2 RANKS 2 SOURCE msh4 INPUT periodic-parametric-v41.msh + REFERENCE periodic.puml.h5 IDENTIFY) +pumgen_add_test(msh4.tiny-sparse RANKS 1 SOURCE msh4 INPUT tiny-sparse-v41.msh + EXPECT_ERROR "Non-contiguous node tags are not supported") +pumgen_add_test(msh4.tiny-duplicate RANKS 1 SOURCE msh4 INPUT tiny-duplicate-v41.msh + EXPECT_ERROR "Duplicate node tag 2") +pumgen_add_test(msh4.tiny-badnode RANKS 1 SOURCE msh4 INPUT tiny-badnode-v41.msh + EXPECT_ERROR "Unknown node tag 9") +pumgen_add_test(msh2.tiny-duplicate RANKS 1 SOURCE msh2 INPUT tiny-duplicate-v22.msh + EXPECT_ERROR "Duplicate node tag 2") +pumgen_add_test(msh2.tiny-badnode RANKS 1 SOURCE msh2 INPUT tiny-badnode-v22.msh + EXPECT_ERROR "Unknown node tag") + +# higher-order meshes (with the inspheres of the linear mesh) and partitioned files +foreach(ranks 1 2) + pumgen_add_test(msh4.coarse-o2.np${ranks} RANKS ${ranks} SOURCE msh4 INPUT coarse-o2-v41.msh + REFERENCE coarse-o2.puml.h5 ARGS --order=2 + EXPECT_OUTPUT "Minimum insphere found: 0[.]03184[67]") +endforeach() +pumgen_add_test(msh2.coarse-o2.np2 RANKS 2 SOURCE msh2 INPUT coarse-o2-v22.msh + REFERENCE coarse-o2.puml.h5 ARGS --order=2 + EXPECT_OUTPUT "Minimum insphere found: 0[.]03184[67]") +pumgen_add_test(msh4.coarse.insphere RANKS 1 SOURCE msh4 INPUT coarse-v41.msh + EXPECT_OUTPUT "Minimum insphere found: 0[.]03184[67]") +pumgen_add_test(msh4.coarse-partitioned RANKS 1 SOURCE msh4 INPUT coarse-partitioned-v41.msh + EXPECT_ERROR "Partitioned MSH files are not supported") +pumgen_add_test(msh4.tiny-nophysical RANKS 1 SOURCE msh4 INPUT tiny-nophysical-v41.msh + EXPECT_OUTPUT "1 volume[(]s[)] and 0 surface[(]s[)] without a physical group") + +# binary MSH 4.1; the coordinates of the references come from the ASCII files +foreach(ranks 1 2 3) + pumgen_add_test(msh4.layered-binary.np${ranks} RANKS ${ranks} SOURCE msh4 + INPUT layered-binary-v41.msh REFERENCE layered.puml.h5 TOLERANCE 1e-12) +endforeach() +foreach(variant binary binary-bigendian binary-size4) + pumgen_add_test(msh4.coarse-${variant}.np2 RANKS 2 SOURCE msh4 INPUT coarse-${variant}-v41.msh + REFERENCE coarse.puml.h5 TOLERANCE 1e-12) +endforeach() +pumgen_add_test(msh4.coarse-o2-binary.np2 RANKS 2 SOURCE msh4 INPUT coarse-o2-binary-v41.msh + REFERENCE coarse-o2.puml.h5 TOLERANCE 1e-12 ARGS --order=2) +pumgen_add_test(msh4.periodic-binary.np2 RANKS 2 SOURCE msh4 INPUT periodic-binary-v41.msh + REFERENCE periodic.puml.h5 TOLERANCE 1e-12 IDENTIFY) +pumgen_add_test(msh4.coarse-binary-truncated RANKS 1 SOURCE msh4 + INPUT coarse-binary-truncated-v41.msh EXPECT_ERROR "Unexpected end of file") + +# binary MSH 4.1 files are read by all ranks unless --gmsh-reader=serial +foreach(ranks 4 7) + pumgen_add_test(msh4.layered-binary.np${ranks} RANKS ${ranks} SOURCE msh4 + INPUT layered-binary-v41.msh REFERENCE layered.puml.h5 TOLERANCE 1e-12) +endforeach() +pumgen_add_test(msh4.layered-binary-serial.np3 RANKS 3 SOURCE msh4 INPUT layered-binary-v41.msh + REFERENCE layered.puml.h5 TOLERANCE 1e-12 ARGS --gmsh-reader=serial) +pumgen_add_test(msh4.periodic-binary-serial.np2 RANKS 2 SOURCE msh4 INPUT periodic-binary-v41.msh + REFERENCE periodic.puml.h5 TOLERANCE 1e-12 IDENTIFY ARGS --gmsh-reader=serial) +pumgen_add_test(msh4.periodic-binary.np3 RANKS 3 SOURCE msh4 INPUT periodic-binary-v41.msh + REFERENCE periodic.puml.h5 TOLERANCE 1e-12 IDENTIFY) +pumgen_add_test(msh4.tiny-binary.np3 RANKS 3 SOURCE msh4 INPUT tiny-binary-v41.msh + REFERENCE tiny.puml.h5) +pumgen_add_test(msh4.tiny-binary-duplicate RANKS 1 SOURCE msh4 INPUT tiny-binary-duplicate-v41.msh + EXPECT_ERROR "Duplicate node tag 2") +pumgen_add_test(msh4.tiny-binary-badnode RANKS 1 SOURCE msh4 INPUT tiny-binary-badnode-v41.msh + EXPECT_ERROR "Unknown node tag 9") +pumgen_add_test(msh4.tiny-binary-sparse RANKS 1 SOURCE msh4 INPUT tiny-binary-sparse-v41.msh + EXPECT_ERROR "Non-contiguous node tags are not supported") + +# a face of the surface mesh given twice +pumgen_add_test(msh2.tiny-duplicateface RANKS 1 SOURCE msh2 INPUT tiny-duplicateface-v22.msh + EXPECT_ERROR "exists multiple times in the surface mesh") + +# velocity-aware sizing, with a material of shear wave speed 1 in group 1 and 2 in group 2 +if(EASI) + configure_file(fixtures/velocity-settings.xml.in ${CMAKE_CURRENT_BINARY_DIR}/velocity-settings.xml + @ONLY) + configure_file(fixtures/velocity-sizefield.xml.in + ${CMAKE_CURRENT_BINARY_DIR}/velocity-sizefield.xml @ONLY) + + # the counts are computed independently from the reference of the coarse mesh + pumgen_add_test( + msh4.coarse.velocity-check RANKS 2 SOURCE msh4 INPUT coarse-v41.msh + ARGS --velocity-check=${CMAKE_CURRENT_BINARY_DIR}/velocity-settings.xml + EXPECT_OUTPUT + "320 of 320 cells lie in refinement regions.*mean edge:[^\n]*; 4 cells above 1.*longest edge:[^\n]*; 143 cells above 1" + ) + + # the expected grids are verified independently; they must not depend on the number of ranks + foreach(ranks 1 3) + set(prefix ${PUMGEN_TEST_OUTPUT}/sizefield-np${ranks}) + set(command ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${ranks} ${MPIEXEC_PREFLAGS} + $ ${MPIEXEC_POSTFLAGS} --spacing 0.25 + ${CMAKE_CURRENT_BINARY_DIR}/velocity-sizefield.xml ${prefix}) + string(REPLACE ";" "|" command "${command}") + add_test(NAME sizefield.np${ranks} + COMMAND ${CMAKE_COMMAND} -DCOMMAND=${command} -DPREFIX=${prefix} + -DEXPECTED=${PUMGEN_TEST_FIXTURES}/velocity-sizefield -DGRIDS=2 -P + ${CMAKE_CURRENT_SOURCE_DIR}/RunSizefieldTest.cmake) + set_tests_properties(sizefield.np${ranks} PROPERTIES PROCESSORS ${ranks} TIMEOUT 120) + endforeach() +endif() +if(NOT EASI) + pumgen_add_test(msh4.tiny.velocity-check-without-easi RANKS 1 SOURCE msh4 INPUT tiny-v41.msh + ARGS --velocity-check=unused.xml EXPECT_ERROR "compiled without easi") +endif() diff --git a/tests/ReaderTest.cpp b/tests/ReaderTest.cpp new file mode 100644 index 0000000..ae43e60 --- /dev/null +++ b/tests/ReaderTest.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2026 SeisSol Group +// +// SPDX-License-Identifier: BSD-3-Clause + +// Parses the fixtures with a tiny read buffer, so that tokens and section markers straddle buffer +// boundaries, and compares the result with the one of the default buffer. + +#include +#include +#include +#include + +#include "meshreader/GMSH2Parser.h" +#include "meshreader/GMSH4Parser.h" +#include "meshreader/GMSHBuilder.h" +#include "meshreader/MshInput.h" + +namespace { + +constexpr std::size_t TinyBuffer = 2 * puml::MshInput::MaxTokenLength; + +template +bool parse(const std::string& file, std::size_t bufferSize, Builder& builder) { + Parser parser(&builder, bufferSize); + if (!parser.parseFile(file)) { + std::fprintf(stderr, "%s (buffer %zu): %s", file.c_str(), bufferSize, + std::string(parser.getErrorMessage()).c_str()); + return false; + } + builder.postprocess(); + return true; +} + +template bool check(const std::string& file) { + puml::GMSHBuilder<3, Order> reference; + puml::GMSHBuilder<3, Order> tiny; + if (!parse(file, puml::MshInput::DefaultBufferSize, reference) || + !parse(file, TinyBuffer, tiny)) { + return false; + } + const bool same = reference.vertices == tiny.vertices && reference.elements == tiny.elements && + reference.groups == tiny.groups && reference.facets == tiny.facets && + reference.bcs == tiny.bcs && reference.identify == tiny.identify && + !reference.elements.empty(); + std::printf("%s: %s\n", file.c_str(), same ? "ok" : "DIFFERENT"); + return same; +} + +std::string readFile(const std::string& file) { + std::ifstream in(file, std::ios::binary); + std::stringstream content; + content << in.rdbuf(); + return content.str(); +} + +// a section to be skipped, longer than the tiny buffer and with partial end markers +bool checkSkippedSection(const std::string& fixtures) { + std::string content = readFile(fixtures + "/tiny-v41.msh"); + std::string junk = "$Comments\n"; + for (int i = 0; i < 200; ++i) { + junk += "$EndComment $EndCommentz "; + } + junk += "\n$EndComments\n"; + content.insert(content.find("$Nodes"), junk); + const std::string file = "reader-test-skipped-section.msh"; + std::ofstream(file, std::ios::binary) << content; + + puml::GMSHBuilder<3, 1> reference; + puml::GMSHBuilder<3, 1> skipped; + const bool ok = parse(fixtures + "/tiny-v41.msh", + puml::MshInput::DefaultBufferSize, reference) && + parse(file, TinyBuffer, skipped) && + reference.vertices == skipped.vertices && + reference.elements == skipped.elements && reference.groups == skipped.groups; + std::remove(file.c_str()); + std::printf("skipped section: %s\n", ok ? "ok" : "DIFFERENT"); + return ok; +} + +} // namespace + +int main(int argc, char** argv) { + if (argc != 2) { + std::fprintf(stderr, "Usage: %s \n", argv[0]); + return 2; + } + const std::string fixtures = argv[1]; + bool ok = true; + ok &= check(fixtures + "/layered-v41.msh"); + ok &= check(fixtures + "/layered-v22.msh"); + ok &= check(fixtures + "/periodic-v41.msh"); + ok &= check(fixtures + "/periodic-parametric-v41.msh"); + ok &= check(fixtures + "/coarse-o2-v41.msh"); + ok &= check(fixtures + "/coarse-o2-v22.msh"); + ok &= check(fixtures + "/layered-binary-v41.msh"); + ok &= check(fixtures + "/periodic-binary-v41.msh"); + ok &= check(fixtures + "/coarse-binary-bigendian-v41.msh"); + ok &= check(fixtures + "/coarse-binary-size4-v41.msh"); + ok &= check(fixtures + "/coarse-o2-binary-v41.msh"); + ok &= checkSkippedSection(fixtures); + return ok ? 0 : 1; +} diff --git a/tests/RunPumgenTest.cmake b/tests/RunPumgenTest.cmake new file mode 100644 index 0000000..df04c6a --- /dev/null +++ b/tests/RunPumgenTest.cmake @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2026 SeisSol Group +# +# SPDX-License-Identifier: BSD-3-Clause + +# Runs pumgen for one test case and checks the result. +# +# Variables: +# COMMAND pumgen command line, with '|' as the argument separator +# OUTPUT file written by pumgen +# REFERENCE (optional) reference PUML file the output has to match +# TOLERANCE (optional) absolute tolerance for the vertex coordinates +# IDENTIFY (optional) also compare the vertex identification of periodic meshes +# H5DIFF h5diff executable, required together with REFERENCE +# EXPECT_ERROR (optional) pumgen has to fail with a message matching this regular expression +# EXPECT_OUTPUT (optional) the output of a successful run has to match this regular expression + +string(REPLACE "|" ";" command "${COMMAND}") +file(REMOVE "${OUTPUT}") + +execute_process( + COMMAND ${command} + RESULT_VARIABLE result + OUTPUT_VARIABLE log + ERROR_VARIABLE log + TIMEOUT 100) +message("${log}") + +if(DEFINED EXPECT_ERROR) + if(result EQUAL 0) + message(FATAL_ERROR "pumgen succeeded, but was expected to fail") + endif() + if(NOT log MATCHES "${EXPECT_ERROR}") + message(FATAL_ERROR "pumgen failed without a message matching \"${EXPECT_ERROR}\"") + endif() + return() +endif() + +if(NOT result EQUAL 0) + message(FATAL_ERROR "pumgen failed: ${result}") +endif() + +if(DEFINED EXPECT_OUTPUT AND NOT log MATCHES "${EXPECT_OUTPUT}") + message(FATAL_ERROR "The output does not match \"${EXPECT_OUTPUT}\"") +endif() + +if(DEFINED REFERENCE) + set(failures "") + set(datasets connect geometry group boundary) + if(IDENTIFY) + list(APPEND datasets identify) + endif() + foreach(dataset ${datasets}) + set(options "") + if(dataset STREQUAL "geometry" AND DEFINED TOLERANCE) + set(options -d ${TOLERANCE}) + endif() + execute_process( + COMMAND ${H5DIFF} -n 10 ${options} ${OUTPUT} ${REFERENCE} /${dataset} /${dataset} + RESULT_VARIABLE differs + OUTPUT_VARIABLE difflog + ERROR_VARIABLE difflog) + if(NOT differs EQUAL 0) + string(APPEND failures "\n/${dataset} differs from the reference:\n${difflog}") + endif() + endforeach() + if(NOT failures STREQUAL "") + message(FATAL_ERROR "${failures}") + endif() +endif() diff --git a/tests/RunSizefieldTest.cmake b/tests/RunSizefieldTest.cmake new file mode 100644 index 0000000..8be34df --- /dev/null +++ b/tests/RunSizefieldTest.cmake @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 SeisSol Group +# +# SPDX-License-Identifier: BSD-3-Clause + +# Runs pumgen-sizefield and compares the written grids with the expected ones. +# +# Variables: +# COMMAND pumgen-sizefield command line, with '|' as the argument separator +# PREFIX output prefix of the command +# EXPECTED prefix of the expected grids +# GRIDS number of grids + +string(REPLACE "|" ";" command "${COMMAND}") +file(REMOVE "${PREFIX}.geo") +execute_process( + COMMAND ${command} + RESULT_VARIABLE result + OUTPUT_VARIABLE log + ERROR_VARIABLE log + TIMEOUT 100) +message("${log}") +if(NOT result EQUAL 0) + message(FATAL_ERROR "pumgen-sizefield failed: ${result}") +endif() + +math(EXPR last "${GRIDS} - 1") +foreach(grid RANGE ${last}) + execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files "${PREFIX}-${grid}.bin" + "${EXPECTED}-${grid}.bin" RESULT_VARIABLE differs) + if(NOT differs EQUAL 0) + message(FATAL_ERROR "${PREFIX}-${grid}.bin differs from ${EXPECTED}-${grid}.bin") + endif() +endforeach() +if(NOT EXISTS "${PREFIX}.geo") + message(FATAL_ERROR "${PREFIX}.geo was not written") +endif() diff --git a/tests/fixtures/coarse-binary-bigendian-v41.msh b/tests/fixtures/coarse-binary-bigendian-v41.msh new file mode 100644 index 0000000..d47cc2c Binary files /dev/null and b/tests/fixtures/coarse-binary-bigendian-v41.msh differ diff --git a/tests/fixtures/coarse-binary-size4-v41.msh b/tests/fixtures/coarse-binary-size4-v41.msh new file mode 100644 index 0000000..ad69f1a Binary files /dev/null and b/tests/fixtures/coarse-binary-size4-v41.msh differ diff --git a/tests/fixtures/coarse-binary-truncated-v41.msh b/tests/fixtures/coarse-binary-truncated-v41.msh new file mode 100644 index 0000000..c87ecba Binary files /dev/null and b/tests/fixtures/coarse-binary-truncated-v41.msh differ diff --git a/tests/fixtures/coarse-binary-v41.msh b/tests/fixtures/coarse-binary-v41.msh new file mode 100644 index 0000000..26acd2b Binary files /dev/null and b/tests/fixtures/coarse-binary-v41.msh differ diff --git a/tests/fixtures/coarse-o2-binary-v41.msh b/tests/fixtures/coarse-o2-binary-v41.msh new file mode 100644 index 0000000..95693b1 Binary files /dev/null and b/tests/fixtures/coarse-o2-binary-v41.msh differ diff --git a/tests/fixtures/coarse-o2-v22.msh b/tests/fixtures/coarse-o2-v22.msh new file mode 100644 index 0000000..2da736c --- /dev/null +++ b/tests/fixtures/coarse-o2-v22.msh @@ -0,0 +1,1186 @@ +$MeshFormat +2.2 0 8 +$EndMeshFormat +$Nodes +629 +1 0 0 -0.25 +2 0 0 0 +3 0 2 0 +4 0 2 -0.25 +5 2 0 -0.25 +6 2 0 0 +7 2 2 0 +8 2 2 -0.25 +9 0 0 -1 +10 0 2 -1 +11 2 0 -1 +12 2 2 -1 +13 0 0 -0.125 +14 0 0.5 0 +15 0 1 0 +16 0 1.5 0 +17 0 0.25 0 +18 0 0.75 0 +19 0 1.25 0 +20 0 1.75 0 +21 0 2 -0.125 +22 0 0.5 -0.25 +23 0 1 -0.25 +24 0 1.5 -0.25 +25 0 0.25 -0.25 +26 0 0.75 -0.25 +27 0 1.25 -0.25 +28 0 1.75 -0.25 +29 2 0 -0.125 +30 2 0.5 0 +31 2 1 0 +32 2 1.5 0 +33 2 0.25 0 +34 2 0.75 0 +35 2 1.25 0 +36 2 1.75 0 +37 2 2 -0.125 +38 2 0.5 -0.25 +39 2 1 -0.25 +40 2 1.5 -0.25 +41 2 0.25 -0.25 +42 2 0.75 -0.25 +43 2 1.25 -0.25 +44 2 1.75 -0.25 +45 0.5 0 -0.25 +46 1 0 -0.25 +47 1.5 0 -0.25 +48 0.25 0 -0.25 +49 0.75 0 -0.25 +50 1.25 0 -0.25 +51 1.75 0 -0.25 +52 0.5 0 0 +53 1 0 0 +54 1.5 0 0 +55 0.25 0 0 +56 0.75 0 0 +57 1.25 0 0 +58 1.75 0 0 +59 0.5 2 -0.25 +60 1 2 -0.25 +61 1.5 2 -0.25 +62 0.25 2 -0.25 +63 0.75 2 -0.25 +64 1.25 2 -0.25 +65 1.75 2 -0.25 +66 0.5 2 0 +67 1 2 0 +68 1.5 2 0 +69 0.25 2 0 +70 0.75 2 0 +71 1.25 2 0 +72 1.75 2 0 +73 0 0 -0.5863498029562912 +74 0 0 -0.7931749014781456 +75 0 0 -0.4181749014781456 +76 0 2 -0.5863498029562912 +77 0 2 -0.7931749014781456 +78 0 2 -0.4181749014781456 +79 0 1 -1 +80 0 0.4999999999999999 -1 +81 0 1.5 -1 +82 2 0 -0.5863498029562912 +83 2 0 -0.7931749014781456 +84 2 0 -0.4181749014781456 +85 2 2 -0.5863498029562912 +86 2 2 -0.7931749014781456 +87 2 2 -0.4181749014781456 +88 2 1 -1 +89 2 0.4999999999999999 -1 +90 2 1.5 -1 +91 1 0 -1 +92 0.4999999999999999 0 -1 +93 1.5 0 -1 +94 1 2 -1 +95 0.4999999999999999 2 -1 +96 1.5 2 -1 +97 0 1.75 -0.125 +98 0 1.25 -0.125 +99 0 0.25 -0.125 +100 0 0.75 -0.125 +101 0 0.125 -0.0625 +102 0 0.125 -0.1875 +103 0 0.375 -0.1875 +104 0 0.375 -0.0625 +105 0 1.875 -0.1875 +106 0 1.875 -0.0625 +107 0 1.625 -0.0625 +108 0 1.625 -0.1875 +109 0 0.875 -0.0625 +110 0 0.625 -0.0625 +111 0 0.5 -0.125 +112 0 0.625 -0.1875 +113 0 1.375 -0.0625 +114 0 1.125 -0.0625 +115 0 1 -0.125 +116 0 1.125 -0.1875 +117 0 0.875 -0.1875 +118 0 1.5 -0.125 +119 0 1.375 -0.1875 +120 2 1.75 -0.125 +121 2 0.25 -0.125 +122 2 0.75 -0.125 +123 2 1.25 -0.125 +124 2 0.125 -0.1875 +125 2 0.125 -0.0625 +126 2 0.375 -0.1875 +127 2 0.375 -0.0625 +128 2 1.875 -0.0625 +129 2 1.875 -0.1875 +130 2 1.625 -0.0625 +131 2 1.625 -0.1875 +132 2 0.625 -0.0625 +133 2 0.875 -0.0625 +134 2 0.5 -0.125 +135 2 0.625 -0.1875 +136 2 1.125 -0.0625 +137 2 1.375 -0.0625 +138 2 0.875 -0.1875 +139 2 1 -0.125 +140 2 1.125 -0.1875 +141 2 1.5 -0.125 +142 2 1.375 -0.1875 +143 1.75 0 -0.125 +144 0.25 0 -0.125 +145 0.75 0 -0.125 +146 1.25 0 -0.125 +147 0.125 0 -0.1875 +148 0.125 0 -0.0625 +149 0.375 0 -0.1875 +150 0.375 0 -0.0625 +151 1.875 0 -0.0625 +152 1.875 0 -0.1875 +153 1.625 0 -0.1875 +154 1.625 0 -0.0625 +155 0.875 0 -0.1875 +156 0.625 0 -0.1875 +157 0.5 0 -0.125 +158 0.625 0 -0.0625 +159 1.375 0 -0.1875 +160 1.125 0 -0.1875 +161 1 0 -0.125 +162 0.875 0 -0.0625 +163 1.125 0 -0.0625 +164 1.5 0 -0.125 +165 1.375 0 -0.0625 +166 1.25 2 -0.125 +167 0.25 2 -0.125 +168 1.75 2 -0.125 +169 0.75 2 -0.125 +170 0.125 2 -0.0625 +171 0.125 2 -0.1875 +172 0.375 2 -0.0625 +173 0.375 2 -0.1875 +174 1.875 2 -0.1875 +175 1.875 2 -0.0625 +176 1.625 2 -0.0625 +177 1.625 2 -0.1875 +178 0.625 2 -0.1875 +179 0.875 2 -0.1875 +180 0.5 2 -0.125 +181 0.625 2 -0.0625 +182 1.125 2 -0.1875 +183 1.375 2 -0.1875 +184 1.125 2 -0.0625 +185 1 2 -0.125 +186 0.875 2 -0.0625 +187 1.375 2 -0.0625 +188 1.5 2 -0.125 +189 1.578151847792411 1.230662432702593 -0.25 +190 0.7183803468335941 0.3953531183916653 -0.25 +191 0.4514095486548143 1.234183475777031 -0.25 +192 1.577292096379071 0.7509441482399639 -0.25 +193 1.19048230709083 0.9938634724449638 -0.25 +194 1.211526285167907 1.539080572974888 -0.25 +195 0.389289068198761 0.7180466052574374 -0.25 +196 1.213941721673849 0.4470568705367829 -0.25 +197 0.7375873251999121 1.636990986207155 -0.25 +198 0.8638678665980398 1.245262305856504 -0.25 +199 1.633974596215561 0.3660254037844386 -0.25 +200 1.633974596215561 1.633974596215561 -0.25 +201 0.3660254037844386 0.3660254037844387 -0.25 +202 0.3630158654516971 1.636984134548303 -0.25 +203 0.799919109919719 0.8127176335914889 -0.25 +204 0.1830127018922193 0.4330127018922194 -0.25 +205 0.1830127018922193 0.1830127018922194 -0.25 +206 0.4330127018922193 0.1830127018922194 -0.25 +207 0.1815079327258486 1.818492067274152 -0.25 +208 0.1815079327258486 1.568492067274152 -0.25 +209 0.4315079327258485 1.818492067274152 -0.25 +210 1.816987298107781 0.1830127018922193 -0.25 +211 1.816987298107781 0.4330127018922193 -0.25 +212 1.566987298107781 0.1830127018922193 -0.25 +213 1.816987298107781 1.566987298107781 -0.25 +214 1.816987298107781 1.816987298107781 -0.25 +215 1.566987298107781 1.816987298107781 -0.25 +216 0.1946445340993805 0.8590233026287186 -0.25 +217 0.1946445340993805 0.6090233026287186 -0.25 +218 0.3776572359915998 0.542036004520938 -0.25 +219 0.2257047743274071 1.367091737888515 -0.25 +220 0.2257047743274071 1.117091737888515 -0.25 +221 0.4203493084267876 0.9761150405172343 -0.25 +222 0.4072127070532557 1.435583805162667 -0.25 +223 1.788646048189535 0.625472074119982 -0.25 +224 1.788646048189535 0.875472074119982 -0.25 +225 1.605633346297316 0.5584847760122013 -0.25 +226 1.789075923896205 1.115331216351297 -0.25 +227 1.789075923896205 1.365331216351297 -0.25 +228 1.577721972085741 0.9908032904712787 -0.25 +229 1.606063222003986 1.432318514459077 -0.25 +230 0.609190173416797 0.1976765591958326 -0.25 +231 0.859190173416797 0.1976765591958326 -0.25 +232 0.5422028753090163 0.380689261088052 -0.25 +233 1.106970860836924 0.2235284352683914 -0.25 +234 1.356970860836924 0.2235284352683914 -0.25 +235 0.9661610342537215 0.4212049944642241 -0.25 +236 1.423958158944705 0.4065411371606107 -0.25 +237 0.8687936625999561 1.818495493103577 -0.25 +238 0.6187936625999561 1.818495493103577 -0.25 +239 0.5503015953258046 1.636987560377729 -0.25 +240 1.355763142583954 1.769540286487444 -0.25 +241 1.105763142583954 1.769540286487444 -0.25 +242 0.9745568051839095 1.588035779591021 -0.25 +243 1.422750440691734 1.586527584595224 -0.25 +244 1.38388720173495 0.8724038103424638 -0.25 +245 1.38431707744162 1.112262952573779 -0.25 +246 1.201004296129369 1.266472022709926 -0.25 +247 1.394839066480159 1.384871502838741 -0.25 +248 0.5538347075161776 0.5566998618245513 -0.25 +249 0.59460408905924 0.7653821194244631 -0.25 +250 0.7591497283766566 0.6040353759915771 -0.25 +251 1.006930415796784 0.6298872520641359 -0.25 +252 0.6256643292872667 1.02345055468426 -0.25 +253 0.5944984369273631 1.435587230992093 -0.25 +254 0.8007275958989759 1.441126646031829 -0.25 +255 0.6576387076264271 1.239722890816767 -0.25 +256 0.8318934882588794 1.028989969723996 -0.25 +257 1.39561690902646 0.5990005093883735 -0.25 +258 1.20221201438234 0.7204601714908734 -0.25 +259 1.027175086844435 1.119562889150734 -0.25 +260 1.037697075882973 1.392171439415696 -0.25 +261 0.9952007085052745 0.9032905530182262 -0.25 +262 1.271813574233536 0.4264624335545749 0 +263 1.57791535636081 1.249332516611218 0 +264 0.7707992533266995 1.491026004178691 0 +265 0.3838980770800826 0.7512620920870399 0 +266 1.604157990548827 0.756865646419863 0 +267 1.138381937170266 0.9886994546006772 0 +268 1.25740778765016 1.566401986104808 0 +269 0.7720151968557594 0.504236995066732 0 +270 0.3774516866522022 1.228912045653614 0 +271 0.6885092302170019 0.9928273183173509 0 +272 1.633974596215561 1.633974596215561 0 +273 0.3660254037844387 0.3660254037844386 0 +274 1.633974596215561 0.3660254037844387 0 +275 0.3296501879957803 1.643987609966461 0 +276 0.1830127018922194 0.1830127018922193 0 +277 0.1830127018922194 0.4330127018922193 0 +278 0.4330127018922194 0.1830127018922193 0 +279 0.1648250939978902 1.57199380498323 0 +280 0.1648250939978902 1.82199380498323 0 +281 0.4148250939978901 1.82199380498323 0 +282 1.816987298107781 0.4330127018922194 0 +283 1.816987298107781 0.1830127018922194 0 +284 1.566987298107781 0.1830127018922194 0 +285 1.816987298107781 1.816987298107781 0 +286 1.816987298107781 1.566987298107781 0 +287 1.566987298107781 1.816987298107781 0 +288 0.1919490385400413 0.6256310460435199 0 +289 0.1919490385400413 0.8756310460435199 0 +290 0.3749617404322607 0.5586437479357392 0 +291 0.1887258433261011 1.114456022826807 0 +292 0.1887258433261011 1.364456022826807 0 +293 0.3806748818661424 0.9900870688703272 0 +294 0.3535509373239912 1.436449827810038 0 +295 1.802078995274414 0.8784328232099314 0 +296 1.802078995274414 0.6284328232099314 0 +297 1.619066293382194 0.5614455251021508 0 +298 1.788957678180405 1.374666258305609 0 +299 1.788957678180405 1.124666258305609 0 +300 1.591036673454818 1.003099081515541 0 +301 1.605944976288185 1.44165355641339 0 +302 0.8860075984278797 0.252118497533366 0 +303 0.6360075984278797 0.252118497533366 0 +304 0.5690203003200991 0.4351311994255853 0 +305 1.385906787116768 0.2132312167772875 0 +306 1.135906787116768 0.2132312167772875 0 +307 1.021914385544648 0.4653497143106535 0 +308 1.452894085224548 0.3962439186695068 0 +309 0.6353996266633497 1.745513002089345 0 +310 0.8853996266633497 1.745513002089345 0 +311 0.55022472066124 1.567506807072576 0 +312 1.12870389382508 1.783200993052404 0 +313 1.37870389382508 1.783200993052404 0 +314 1.01410352048843 1.528713995141749 0 +315 1.445691191932861 1.600188291160184 0 +316 1.371269963859547 0.8727825505102701 0 +317 1.205097755701901 0.7075809440776261 0 +318 1.437985782391181 0.5916640399872189 0 +319 0.9551985670130129 0.7464682248337047 0 +320 1.358148646765538 1.119015985605948 0 +321 1.417661572005485 1.407867251358013 0 +322 1.197894862410213 1.277550720352742 0 +323 0.954590595248483 1.239862729389684 0 +324 0.7296542417718508 1.241926661248021 0 +325 0.9134455836936342 0.9907633864590141 0 +326 0.5329804584346021 1.110869681985483 0 +327 0.5741254699894509 1.359969024916153 0 +328 0.7302622135363807 0.7485321566920415 0 +329 0.5362036536485423 0.8720447052021953 0 +330 0.577956636967921 0.6277495435768859 0 +331 0 0.8250160980116606 -0.5458226013255596 +332 0 1.189987121590671 -0.5299871215906714 +333 0 0.3875026830019433 -0.6053620673803085 +334 0 1.614997853598445 -0.6027228207578272 +335 0 0.4437513415009717 -0.4276810336901542 +336 0 0.1937513415009717 -0.4276810336901542 +337 0 0.1937513415009717 -0.5958559351682999 +338 0 1.807498926799223 -0.4263614103789136 +339 0 1.557498926799223 -0.4263614103789136 +340 0 1.807498926799223 -0.5945363118570592 +341 0 0.1937513415009717 -0.8026810336901542 +342 0 0.6937513415009717 -0.8026810336901542 +343 0 1.807498926799223 -0.8013614103789136 +344 0 1.307498926799223 -0.8013614103789136 +345 0 0.9125080490058304 -0.3979113006627798 +346 0 0.6625080490058304 -0.3979113006627798 +347 0 0.606259390506802 -0.575592334352934 +348 0 1.344993560795336 -0.3899935607953358 +349 0 1.094993560795336 -0.3899935607953358 +350 0 1.007501609801166 -0.5379048614581154 +351 0 1.402492487594558 -0.5663549711742493 +352 0 0.9125080490058304 -0.7729113006627798 +353 0 1.094993560795336 -0.7649935607953358 +354 2 0.8250160980116606 -0.5458226013255596 +355 2 1.189987121590671 -0.5299871215906714 +356 2 0.3875026830019433 -0.6053620673803085 +357 2 1.614997853598445 -0.6027228207578272 +358 2 0.1937513415009717 -0.4276810336901542 +359 2 0.4437513415009717 -0.4276810336901542 +360 2 0.1937513415009717 -0.5958559351682999 +361 2 1.557498926799223 -0.4263614103789136 +362 2 1.807498926799223 -0.4263614103789136 +363 2 1.807498926799223 -0.5945363118570592 +364 2 0.1937513415009717 -0.8026810336901542 +365 2 0.6937513415009717 -0.8026810336901542 +366 2 1.807498926799223 -0.8013614103789136 +367 2 1.307498926799223 -0.8013614103789136 +368 2 0.6625080490058304 -0.3979113006627798 +369 2 0.9125080490058304 -0.3979113006627798 +370 2 0.606259390506802 -0.575592334352934 +371 2 1.094993560795336 -0.3899935607953358 +372 2 1.344993560795336 -0.3899935607953358 +373 2 1.007501609801166 -0.5379048614581154 +374 2 1.402492487594558 -0.5663549711742493 +375 2 1.094993560795336 -0.7649935607953358 +376 2 0.9125080490058304 -0.7729113006627798 +377 0.8250160980116606 0 -0.5458226013255596 +378 1.189987121590671 0 -0.5299871215906714 +379 0.3875026830019435 0 -0.6053620673803086 +380 1.614997853598445 0 -0.6027228207578272 +381 0.1937513415009717 0 -0.4276810336901543 +382 0.4437513415009717 0 -0.4276810336901543 +383 0.1937513415009717 0 -0.5958559351682999 +384 1.557498926799223 0 -0.4263614103789136 +385 1.807498926799223 0 -0.4263614103789136 +386 1.807498926799223 0 -0.5945363118570592 +387 0.1937513415009717 0 -0.8026810336901543 +388 0.6937513415009717 0 -0.8026810336901543 +389 1.807498926799223 0 -0.8013614103789136 +390 1.307498926799223 0 -0.8013614103789136 +391 0.6625080490058304 0 -0.3979113006627798 +392 0.9125080490058304 0 -0.3979113006627798 +393 0.6062593905068021 0 -0.5755923343529341 +394 1.094993560795336 0 -0.3899935607953358 +395 1.344993560795336 0 -0.3899935607953358 +396 1.007501609801166 0 -0.5379048614581154 +397 1.402492487594558 0 -0.5663549711742493 +398 0.9125080490058304 0 -0.7729113006627798 +399 1.094993560795336 0 -0.7649935607953358 +400 0.8250160980116606 2 -0.5458226013255596 +401 1.189987121590671 2 -0.5299871215906714 +402 0.3875026830019435 2 -0.6053620673803086 +403 1.614997853598445 2 -0.6027228207578272 +404 0.4437513415009717 2 -0.4276810336901543 +405 0.1937513415009717 2 -0.4276810336901543 +406 0.1937513415009717 2 -0.5958559351682999 +407 1.807498926799223 2 -0.4263614103789136 +408 1.557498926799223 2 -0.4263614103789136 +409 1.807498926799223 2 -0.5945363118570592 +410 0.1937513415009717 2 -0.8026810336901543 +411 0.6937513415009717 2 -0.8026810336901543 +412 1.807498926799223 2 -0.8013614103789136 +413 1.307498926799223 2 -0.8013614103789136 +414 0.9125080490058304 2 -0.3979113006627798 +415 0.6625080490058304 2 -0.3979113006627798 +416 0.6062593905068021 2 -0.5755923343529341 +417 1.344993560795336 2 -0.3899935607953358 +418 1.094993560795336 2 -0.3899935607953358 +419 1.007501609801166 2 -0.5379048614581154 +420 1.402492487594558 2 -0.5663549711742493 +421 1.094993560795336 2 -0.7649935607953358 +422 0.9125080490058304 2 -0.7729113006627798 +423 1.5 1.5 -1 +424 1.25 0.7499999999999999 -1 +425 0.5625 0.5625000000000002 -1 +426 0.6914062499999998 1.30859375 -1 +427 0.28125 0.7812500000000001 -1 +428 0.28125 0.2812500000000001 -1 +429 0.78125 0.2812500000000001 -1 +430 0.3457031249999999 1.654296875 -1 +431 0.3457031249999999 1.154296875 -1 +432 0.8457031249999999 1.654296875 -1 +433 1.625 0.3749999999999999 -1 +434 1.625 0.875 -1 +435 1.125 0.3749999999999999 -1 +436 1.75 1.25 -1 +437 1.75 1.75 -1 +438 1.25 1.75 -1 +439 0.6269531249999999 0.9355468750000002 -1 +440 1.375 1.125 -1 +441 0.90625 0.65625 -1 +442 1.095703125 1.404296875 -1 +443 0.9707031249999999 1.029296875 -1 +444 1.788646048189535 0.750472074119982 -0.1875 +445 1.789075923896205 1.240331216351297 -0.1875 +446 1.788957678180405 1.249666258305609 -0.0625 +447 0.1919490385400413 0.7506310460435199 -0.0625 +448 0.734190173416797 0.1976765591958326 -0.1875 +449 1.230763142583954 1.769540286487444 -0.1875 +450 0.2257047743274071 1.242091737888515 -0.1875 +451 0.1887258433261011 1.239456022826807 -0.0625 +452 1.231970860836924 0.2235284352683914 -0.1875 +453 1.802078995274414 0.7534328232099314 -0.0625 +454 0.1946445340993805 0.7340233026287186 -0.1875 +455 0.1648250939978902 1.69699380498323 -0.0625 +456 0.1830127018922194 0.3080127018922193 -0.0625 +457 1.691987298107781 0.1830127018922194 -0.0625 +458 1.816987298107781 0.3080127018922194 -0.0625 +459 1.816987298107781 1.691987298107781 -0.0625 +460 1.691987298107781 1.816987298107781 -0.0625 +461 0.7437936625999561 1.818495493103577 -0.1875 +462 1.816987298107781 1.691987298107781 -0.1875 +463 1.816987298107781 0.3080127018922193 -0.1875 +464 0.3080127018922193 0.1830127018922194 -0.1875 +465 0.1830127018922193 0.3080127018922194 -0.1875 +466 0.3065079327258485 1.818492067274152 -0.1875 +467 0.7610075984278797 0.252118497533366 -0.0625 +468 0.7603996266633497 1.745513002089345 -0.0625 +469 0.1815079327258486 1.693492067274152 -0.1875 +470 1.691987298107781 0.1830127018922193 -0.1875 +471 1.260906787116768 0.2132312167772875 -0.0625 +472 1.691987298107781 1.816987298107781 -0.1875 +473 1.25370389382508 1.783200993052404 -0.0625 +474 0.2898250939978901 1.82199380498323 -0.0625 +475 0.3080127018922194 0.1830127018922193 -0.0625 +476 1.875 1.875 -0.125 +477 0.125 0.125 -0.125 +478 1.875 0.125 -0.125 +479 0.125 1.875 -0.125 +480 1.788957678180405 1.374666258305609 -0.125 +481 1.57803360207661 1.239997474656906 -0.125 +482 1.789075923896205 1.115331216351297 -0.125 +483 0.1919490385400413 0.8756310460435199 -0.125 +484 0.1887258433261011 1.114456022826807 -0.125 +485 0.3865935726394218 0.7346543486722386 -0.125 +486 0.3833703774254816 0.9734793254555258 -0.125 +487 0.859190173416797 0.1976765591958326 -0.125 +488 0.1887258433261011 1.364456022826807 -0.125 +489 1.802078995274414 0.6284328232099314 -0.125 +490 1.590725043463949 0.7539048973299134 -0.125 +491 1.105763142583954 1.769540286487444 -0.125 +492 0.4144306176535082 1.231547760715323 -0.125 +493 1.802078995274414 0.8784328232099314 -0.125 +494 1.591154919170619 0.9937640395612282 -0.125 +495 1.106970860836924 0.2235284352683914 -0.125 +496 1.242877647953692 0.4367596520456789 -0.125 +497 1.231147940662183 0.7101629529997693 -0.125 +498 1.424552835306303 0.5887032908972694 -0.125 +499 0.7451977718446767 0.4497950567291986 -0.125 +500 0.609190173416797 0.1976765591958326 -0.125 +501 0.6187936625999561 1.818495493103577 -0.125 +502 0.7541932892633059 1.564008495192923 -0.125 +503 1.37870389382508 1.783200993052404 -0.125 +504 0.8173335599623697 1.368144155017597 -0.125 +505 0.7761885484075208 1.119044812086927 -0.125 +506 1.001124901884153 1.116980880228591 -0.125 +507 1.397320148819829 0.8753645594324133 -0.125 +508 1.0606378271241 1.405832145980656 -0.125 +509 1.234467036409034 1.552741279539848 -0.125 +510 1.38419883172582 1.121597994528091 -0.125 +511 0.8687936625999561 1.818495493103577 -0.125 +512 1.385906787116768 0.2132312167772875 -0.125 +513 0.3660254037844387 0.3660254037844387 -0.125 +514 1.633974596215561 0.3660254037844387 -0.125 +515 1.633974596215561 1.633974596215561 -0.125 +516 0.1946445340993805 0.6090233026287186 -0.125 +517 0.3463330267237387 1.640485872257382 -0.125 +518 0.9929784592648041 0.4756469328017575 -0.125 +519 0.9974975564250362 1.601696486155981 -0.125 +520 1.816987298107781 1.566987298107781 -0.125 +521 1.605944976288185 1.44165355641339 -0.125 +522 0.1648250939978902 1.57199380498323 -0.125 +523 0.3702337760519496 1.432948090100959 -0.125 +524 1.164432122130548 0.9912814635228204 -0.125 +525 0.5699593894359081 1.113505397047191 -0.125 +526 0.7442141700683604 0.9027724759544199 -0.125 +527 0.5388991492078814 0.8554369617873941 -0.125 +528 0.7859671533877393 0.6584773143291105 -0.125 +529 0.9691505235449926 0.900708544096083 -0.125 +530 1.619066293382194 0.5614455251021508 -0.125 +531 1.816987298107781 0.4330127018922194 -0.125 +532 0.9911627692473033 1.515053288576789 -0.125 +533 0.5336187565978462 1.640489298086808 -0.125 +534 1.566987298107781 1.816987298107781 -0.125 +535 1.445691191932861 1.600188291160184 -0.125 +536 0.5422028753090165 0.3806892610880519 -0.125 +537 1.566987298107781 0.1830127018922193 -0.125 +538 1.452894085224548 0.3962439186695068 -0.125 +539 0.4330127018922194 0.1830127018922193 -0.125 +540 0.3776572359915998 0.542036004520938 -0.125 +541 0.1830127018922193 0.4330127018922194 -0.125 +542 0.4315079327258485 1.818492067274152 -0.125 +543 1.417779817721286 1.3985322094037 -0.125 +544 0.6111044009907569 1.362604739977861 -0.125 +545 0.5806521325272602 0.6111418001620847 -0.125 +546 0.5919085934999009 0.7819898628392643 -0.125 +547 0.5669075593891983 1.564005069363497 -0.125 +548 1.035866342076627 0.6195900335730319 -0.125 +549 1.223945047370495 1.280132729274886 -0.125 +550 1.355763142583954 1.519540286487444 -0.625 +551 1.18193393329902 1.372631152928252 -0.625 +552 1.345241153545415 1.246931736222482 -0.625 +553 0.3457031249999999 1.06680492400583 -0.7729113006627798 +554 0.571407899327407 1.271388612888516 -0.625 +555 0.2257047743274071 1.029599786894346 -0.3979113006627798 +556 0.1946445340993805 0.771531351634549 -0.3979113006627798 +557 0.5403476590993803 1.013320177628719 -0.625 +558 0.9514662675839534 1.423837161487444 -0.625 +559 0.7582111740058302 1.654296875 -0.7729113006627798 +560 1.018271191589784 1.769540286487444 -0.3979113006627798 +561 1.200756703379289 1.769540286487444 -0.3899935607953357 +562 0.9406966857953356 1.654296875 -0.7649935607953358 +563 1.539075923896205 1.365331216351297 -0.625 +564 0.7776370582990197 1.276928027928252 -0.625 +565 0.7456626799598594 1.060655691795745 -0.625 +566 0.940944278545415 1.151228611222482 -0.625 +567 1.789075923896205 1.027839265357127 -0.3979113006627798 +568 1.789075923896205 1.210324777146632 -0.3899935607953357 +569 1.625 0.7875080490058303 -0.7729113006627798 +570 1.625 0.9699935607953356 -0.7649935607953358 +571 1.414075923896205 0.9903312163512967 -0.625 +572 0.3457031249999999 1.249290435795336 -0.7649935607953358 +573 0.2257047743274071 1.212085298683851 -0.3899935607953357 +574 1.788646048189535 0.7879801231258123 -0.3979113006627798 +575 1.413646048189535 0.750472074119982 -0.625 +576 0.7716982224226274 0.1976765591958326 -0.3979113006627798 +577 1.019478909842755 0.2235284352683914 -0.3979113006627798 +578 1.03750804900583 0.3749999999999999 -0.7729113006627798 +579 1.231970860836924 0.5985284352683914 -0.625 +580 0.984190173416797 0.5726765591958326 -0.625 +581 0.7813017116057863 1.818495493103577 -0.3979113006627798 +582 0.7144967875999559 1.472792368103577 -0.625 +583 1.20196442163226 0.2235284352683914 -0.3899935607953357 +584 1.219993560795336 0.3749999999999999 -0.7649935607953358 +585 1.02495955495986 0.7813588167957444 -0.625 +586 0.28125 0.6937580490058304 -0.7729113006627798 +587 0.4758945340993805 0.6402733026287188 -0.625 +588 1.220241153545415 0.8719317362224819 -0.625 +589 1.344993560795336 1.75 -0.7649935607953358 +590 0.6937580490058304 0.2812500000000001 -0.7729113006627798 +591 0.640440173416797 0.4789265591958327 -0.625 +592 0.6812095549598596 0.6876088167957446 -0.625 +593 1.441987298107781 0.5580127018922192 -0.625 +594 0.4642627018922193 0.4642627018922195 -0.625 +595 1.75 1.344993560795336 -0.7649935607953358 +596 1.566987298107781 1.566987298107781 -0.625 +597 0.5272110577258484 1.472788942274152 -0.625 +598 1.625 0.5687513415009716 -0.8026810336901542 +599 1.816987298107781 0.376764043393191 -0.4276810336901543 +600 1.788646048189535 0.5692234156209537 -0.4276810336901543 +601 1.432498926799223 0.3749999999999999 -0.8013614103789136 +602 1.414469787636147 0.2235284352683914 -0.4263614103789136 +603 1.624486224907003 0.1830127018922193 -0.4263614103789136 +604 0.5625450041009278 1.818495493103577 -0.4276810336901543 +605 0.5394544665009716 1.654296875 -0.8026810336901543 +606 0.3752592742268203 1.818492067274152 -0.4276810336901543 +607 0.4750013415009717 0.2812500000000001 -0.8026810336901543 +608 0.5529415149177688 0.1976765591958326 -0.4276810336901543 +609 0.28125 0.4750013415009718 -0.8026810336901542 +610 0.1946445340993805 0.5527746441296904 -0.4276810336901543 +611 0.376764043393191 0.1830127018922194 -0.4276810336901543 +612 0.1830127018922193 0.376764043393191 -0.4276810336901543 +613 1.789075923896205 1.422830143150519 -0.4263614103789136 +614 1.75 1.557498926799223 -0.8013614103789136 +615 1.413262069383176 1.769540286487444 -0.4263614103789136 +616 1.557498926799223 1.75 -0.8013614103789136 +617 1.816987298107781 1.624486224907003 -0.4263614103789136 +618 1.807498926799223 0.1937513415009717 -0.6040424440690679 +619 1.624486224907003 1.816987298107781 -0.4263614103789136 +620 0.1815079327258486 1.413485628069487 -0.3899935607953357 +621 0.1937513415009717 1.807498926799223 -0.6040424440690679 +622 0.3457031249999999 1.461795801799223 -0.8013614103789136 +623 0.1815079327258486 1.625990994073374 -0.4263614103789136 +624 0.1937513415009717 0.1937513415009717 -0.6053620673803086 +625 0.1830127018922193 0.1830127018922194 -0.4181749014781456 +626 1.816987298107781 0.1830127018922193 -0.4181749014781456 +627 1.816987298107781 1.816987298107781 -0.4181749014781456 +628 0.1815079327258486 1.818492067274152 -0.4181749014781456 +629 1.807498926799223 1.807498926799223 -0.6027228207578272 +$EndNodes +$Elements +548 +1 9 2 105 1 1 2 99 13 101 102 +2 9 2 105 1 22 1 99 25 102 103 +3 9 2 105 1 2 14 99 17 104 101 +4 9 2 105 1 3 4 97 21 105 106 +5 9 2 105 1 16 3 97 20 106 107 +6 9 2 105 1 4 24 97 28 108 105 +7 9 2 105 1 14 15 100 18 109 110 +8 9 2 105 1 14 22 99 111 103 104 +9 9 2 105 1 22 14 100 111 110 112 +10 9 2 105 1 15 16 98 19 113 114 +11 9 2 105 1 23 15 98 115 114 116 +12 9 2 105 1 15 23 100 115 117 109 +13 9 2 105 1 24 16 97 118 107 108 +14 9 2 105 1 16 24 98 118 119 113 +15 9 2 105 1 23 22 100 26 112 117 +16 9 2 105 1 24 23 98 27 116 119 +17 9 2 105 2 5 121 6 124 125 29 +18 9 2 105 2 38 121 5 126 124 41 +19 9 2 105 2 6 121 30 125 127 33 +20 9 2 105 2 7 120 8 128 129 37 +21 9 2 105 2 32 120 7 130 128 36 +22 9 2 105 2 8 120 40 129 131 44 +23 9 2 105 2 30 122 31 132 133 34 +24 9 2 105 2 30 121 38 127 126 134 +25 9 2 105 2 38 122 30 135 132 134 +26 9 2 105 2 31 123 32 136 137 35 +27 9 2 105 2 31 122 39 133 138 139 +28 9 2 105 2 39 123 31 140 136 139 +29 9 2 105 2 40 120 32 131 130 141 +30 9 2 105 2 32 123 40 137 142 141 +31 9 2 105 2 39 122 38 138 135 42 +32 9 2 105 2 40 123 39 142 140 43 +33 9 2 105 3 2 1 144 13 147 148 +34 9 2 105 3 1 45 144 48 149 147 +35 9 2 105 3 52 2 144 55 148 150 +36 9 2 105 3 5 6 143 29 151 152 +37 9 2 105 3 47 5 143 51 152 153 +38 9 2 105 3 6 54 143 58 154 151 +39 9 2 105 3 45 46 145 49 155 156 +40 9 2 105 3 45 52 144 157 150 149 +41 9 2 105 3 52 45 145 157 156 158 +42 9 2 105 3 46 47 146 50 159 160 +43 9 2 105 3 46 53 145 161 162 155 +44 9 2 105 3 53 46 146 161 160 163 +45 9 2 105 3 54 47 143 164 153 154 +46 9 2 105 3 47 54 146 164 165 159 +47 9 2 105 3 53 52 145 56 158 162 +48 9 2 105 3 54 53 146 57 163 165 +49 9 2 105 4 3 167 4 170 171 21 +50 9 2 105 4 66 167 3 172 170 69 +51 9 2 105 4 4 167 59 171 173 62 +52 9 2 105 4 8 168 7 174 175 37 +53 9 2 105 4 7 168 68 175 176 72 +54 9 2 105 4 61 168 8 177 174 65 +55 9 2 105 4 59 169 60 178 179 63 +56 9 2 105 4 59 167 66 173 172 180 +57 9 2 105 4 66 169 59 181 178 180 +58 9 2 105 4 60 166 61 182 183 64 +59 9 2 105 4 67 166 60 184 182 185 +60 9 2 105 4 60 169 67 179 186 185 +61 9 2 105 4 61 166 68 183 187 188 +62 9 2 105 4 68 168 61 176 177 188 +63 9 2 105 4 67 169 66 186 181 70 +64 9 2 105 4 68 166 67 187 184 71 +65 9 2 103 5 1 22 201 25 204 205 +66 9 2 103 5 45 1 201 48 205 206 +67 9 2 103 5 24 4 202 28 207 208 +68 9 2 103 5 4 59 202 62 209 207 +69 9 2 103 5 38 5 199 41 210 211 +70 9 2 103 5 5 47 199 51 212 210 +71 9 2 103 5 8 40 200 44 213 214 +72 9 2 103 5 61 8 200 65 214 215 +73 9 2 103 5 22 23 195 26 216 217 +74 9 2 103 5 22 195 201 217 218 204 +75 9 2 103 5 23 24 191 27 219 220 +76 9 2 103 5 23 191 195 220 221 216 +77 9 2 103 5 191 24 202 219 208 222 +78 9 2 103 5 39 38 192 42 223 224 +79 9 2 103 5 192 38 199 223 211 225 +80 9 2 103 5 40 39 189 43 226 227 +81 9 2 103 5 189 39 192 226 224 228 +82 9 2 103 5 40 189 200 227 229 213 +83 9 2 103 5 46 45 190 49 230 231 +84 9 2 103 5 190 45 201 230 206 232 +85 9 2 103 5 47 46 196 50 233 234 +86 9 2 103 5 46 190 196 231 235 233 +87 9 2 103 5 47 196 199 234 236 212 +88 9 2 103 5 59 60 197 63 237 238 +89 9 2 103 5 59 197 202 238 239 209 +90 9 2 103 5 60 61 194 64 240 241 +91 9 2 103 5 60 194 197 241 242 237 +92 9 2 103 5 194 61 200 240 215 243 +93 9 2 103 5 189 192 193 228 244 245 +94 9 2 103 5 189 193 194 245 246 247 +95 9 2 103 5 189 194 200 247 243 229 +96 9 2 103 5 195 190 201 248 232 218 +97 9 2 103 5 190 195 203 248 249 250 +98 9 2 103 5 196 190 203 235 250 251 +99 9 2 103 5 195 191 203 221 252 249 +100 9 2 103 5 191 197 198 253 254 255 +101 9 2 103 5 197 191 202 253 222 239 +102 9 2 103 5 191 198 203 255 256 252 +103 9 2 103 5 193 192 196 244 257 258 +104 9 2 103 5 196 192 199 257 225 236 +105 9 2 103 5 194 193 198 246 259 260 +106 9 2 103 5 193 196 203 258 251 261 +107 9 2 103 5 198 193 203 259 261 256 +108 9 2 103 5 197 194 198 242 260 254 +109 9 2 101 6 2 273 14 276 277 17 +110 9 2 101 6 52 273 2 278 276 55 +111 9 2 101 6 16 275 3 279 280 20 +112 9 2 101 6 3 275 66 280 281 69 +113 9 2 101 6 30 274 6 282 283 33 +114 9 2 101 6 6 274 54 283 284 58 +115 9 2 101 6 7 272 32 285 286 36 +116 9 2 101 6 68 272 7 287 285 72 +117 9 2 101 6 14 265 15 288 289 18 +118 9 2 101 6 14 273 265 277 290 288 +119 9 2 101 6 15 270 16 291 292 19 +120 9 2 101 6 265 270 15 293 291 289 +121 9 2 101 6 270 275 16 294 279 292 +122 9 2 101 6 31 266 30 295 296 34 +123 9 2 101 6 266 274 30 297 282 296 +124 9 2 101 6 32 263 31 298 299 35 +125 9 2 101 6 263 266 31 300 295 299 +126 9 2 101 6 32 272 263 286 301 298 +127 9 2 101 6 53 269 52 302 303 56 +128 9 2 101 6 269 273 52 304 278 303 +129 9 2 101 6 54 262 53 305 306 57 +130 9 2 101 6 262 269 53 307 302 306 +131 9 2 101 6 54 274 262 284 308 305 +132 9 2 101 6 66 264 67 309 310 70 +133 9 2 101 6 66 275 264 281 311 309 +134 9 2 101 6 67 268 68 312 313 71 +135 9 2 101 6 264 268 67 314 312 310 +136 9 2 101 6 268 272 68 315 287 313 +137 9 2 101 6 266 267 262 316 317 318 +138 9 2 101 6 262 274 266 308 297 318 +139 9 2 101 6 267 269 262 319 307 317 +140 9 2 101 6 263 267 266 320 316 300 +141 9 2 101 6 263 268 267 321 322 320 +142 9 2 101 6 263 272 268 301 315 321 +143 9 2 101 6 267 268 264 322 314 323 +144 9 2 101 6 264 271 267 324 325 323 +145 9 2 101 6 270 271 264 326 324 327 +146 9 2 101 6 264 275 270 311 294 327 +147 9 2 101 6 269 271 265 328 329 330 +148 9 2 101 6 265 273 269 290 304 330 +149 9 2 101 6 265 271 270 329 326 293 +150 9 2 101 6 267 271 269 325 328 319 +151 9 2 105 7 1 22 333 25 335 336 +152 9 2 105 7 73 1 333 75 336 337 +153 9 2 105 7 24 4 334 28 338 339 +154 9 2 105 7 4 76 334 78 340 338 +155 9 2 105 7 9 73 333 74 337 341 +156 9 2 105 7 79 9 333 80 341 342 +157 9 2 105 7 76 10 334 77 343 340 +158 9 2 105 7 10 79 334 81 344 343 +159 9 2 105 7 22 23 331 26 345 346 +160 9 2 105 7 22 331 333 346 347 335 +161 9 2 105 7 23 24 332 27 348 349 +162 9 2 105 7 331 23 332 345 349 350 +163 9 2 105 7 332 24 334 348 339 351 +164 9 2 105 7 79 331 332 352 350 353 +165 9 2 105 7 331 79 333 352 342 347 +166 9 2 105 7 79 332 334 353 351 344 +167 9 2 105 8 5 356 38 358 359 41 +168 9 2 105 8 82 356 5 360 358 84 +169 9 2 105 8 40 357 8 361 362 44 +170 9 2 105 8 8 357 85 362 363 87 +171 9 2 105 8 11 356 82 364 360 83 +172 9 2 105 8 88 356 11 365 364 89 +173 9 2 105 8 85 357 12 363 366 86 +174 9 2 105 8 12 357 88 366 367 90 +175 9 2 105 8 38 354 39 368 369 42 +176 9 2 105 8 38 356 354 359 370 368 +177 9 2 105 8 39 355 40 371 372 43 +178 9 2 105 8 354 355 39 373 371 369 +179 9 2 105 8 355 357 40 374 361 372 +180 9 2 105 8 88 355 354 375 373 376 +181 9 2 105 8 354 356 88 370 365 376 +182 9 2 105 8 88 357 355 367 374 375 +183 9 2 105 9 45 1 379 48 381 382 +184 9 2 105 9 1 73 379 75 383 381 +185 9 2 105 9 5 47 380 51 384 385 +186 9 2 105 9 82 5 380 84 385 386 +187 9 2 105 9 73 9 379 74 387 383 +188 9 2 105 9 9 91 379 92 388 387 +189 9 2 105 9 11 82 380 83 386 389 +190 9 2 105 9 91 11 380 93 389 390 +191 9 2 105 9 46 45 377 49 391 392 +192 9 2 105 9 377 45 379 391 382 393 +193 9 2 105 9 47 46 378 50 394 395 +194 9 2 105 9 46 377 378 392 396 394 +195 9 2 105 9 47 378 380 395 397 384 +196 9 2 105 9 377 91 378 398 399 396 +197 9 2 105 9 91 377 379 398 393 388 +198 9 2 105 9 378 91 380 399 390 397 +199 9 2 105 10 59 402 4 404 405 62 +200 9 2 105 10 4 402 76 405 406 78 +201 9 2 105 10 8 403 61 407 408 65 +202 9 2 105 10 85 403 8 409 407 87 +203 9 2 105 10 76 402 10 406 410 77 +204 9 2 105 10 10 402 94 410 411 95 +205 9 2 105 10 12 403 85 412 409 86 +206 9 2 105 10 94 403 12 413 412 96 +207 9 2 105 10 60 400 59 414 415 63 +208 9 2 105 10 400 402 59 416 404 415 +209 9 2 105 10 61 401 60 417 418 64 +210 9 2 105 10 60 401 400 418 419 414 +211 9 2 105 10 61 403 401 408 420 417 +212 9 2 105 10 400 401 94 419 421 422 +213 9 2 105 10 94 402 400 411 416 422 +214 9 2 105 10 401 403 94 420 413 421 +215 9 2 105 11 9 79 425 80 427 428 +216 9 2 105 11 91 9 425 92 428 429 +217 9 2 105 11 79 10 426 81 430 431 +218 9 2 105 11 10 94 426 95 432 430 +219 9 2 105 11 88 11 424 89 433 434 +220 9 2 105 11 11 91 424 93 435 433 +221 9 2 105 11 12 88 423 90 436 437 +222 9 2 105 11 94 12 423 96 437 438 +223 9 2 105 11 425 79 426 427 431 439 +224 9 2 105 11 423 88 424 436 434 440 +225 9 2 105 11 424 91 425 435 429 441 +226 9 2 105 11 94 423 426 438 442 432 +227 9 2 105 11 423 424 426 440 443 442 +228 9 2 105 11 424 425 426 441 439 443 +229 11 2 1 1 38 39 192 122 42 224 223 135 444 138 +230 11 2 1 1 39 40 189 123 43 227 226 140 445 142 +231 11 2 1 1 31 32 123 263 35 137 136 299 446 298 +232 11 2 1 1 100 15 14 265 109 18 110 447 288 289 +233 11 2 1 1 46 45 145 190 49 156 155 231 448 230 +234 11 2 1 1 61 60 194 166 64 241 240 183 449 182 +235 11 2 1 1 24 23 191 98 27 220 219 119 450 116 +236 11 2 1 1 16 98 270 15 113 451 292 19 291 114 +237 11 2 1 1 46 47 196 146 50 234 233 160 452 159 +238 11 2 1 1 122 266 30 31 453 296 132 133 34 295 +239 11 2 1 1 23 22 195 100 26 217 216 117 454 112 +240 11 2 1 1 3 97 275 16 106 455 280 20 279 107 +241 11 2 1 1 2 14 273 99 17 277 276 101 456 104 +242 11 2 1 1 54 6 143 274 58 151 154 284 457 283 +243 11 2 1 1 6 121 274 30 125 458 283 33 282 127 +244 11 2 1 1 272 7 32 120 285 36 286 459 130 128 +245 11 2 1 1 272 68 7 168 287 72 285 460 175 176 +246 11 2 1 1 60 59 197 169 63 238 237 179 461 178 +247 11 2 1 1 40 8 200 120 44 214 213 131 462 129 +248 11 2 1 1 5 38 199 121 41 211 210 124 463 126 +249 11 2 1 1 45 1 144 201 48 147 149 206 464 205 +250 11 2 1 1 1 22 99 201 25 103 102 205 465 204 +251 11 2 1 1 59 4 202 167 62 207 209 173 466 171 +252 11 2 1 1 269 52 145 53 303 158 467 302 162 56 +253 11 2 1 1 264 169 66 67 468 181 309 310 70 186 +254 11 2 1 1 4 24 202 97 28 208 207 105 469 108 +255 11 2 1 1 5 47 143 199 51 153 152 210 470 212 +256 11 2 1 1 262 54 53 146 305 57 306 471 163 165 +257 11 2 1 1 8 61 200 168 65 215 214 174 472 177 +258 11 2 1 1 166 67 268 68 184 312 473 187 313 71 +259 11 2 1 1 275 167 3 66 474 170 280 281 69 172 +260 11 2 1 1 144 273 2 52 475 276 148 150 55 278 +261 11 2 1 1 120 272 7 168 459 285 128 476 175 460 +262 11 2 1 1 144 2 273 99 148 276 475 477 456 101 +263 11 2 1 1 143 6 121 274 151 125 478 457 458 283 +264 11 2 1 1 168 8 120 200 174 129 476 472 462 214 +265 11 2 1 1 99 144 1 201 477 147 102 465 205 464 +266 11 2 1 1 121 199 5 143 463 210 124 478 152 470 +267 11 2 1 1 167 4 202 97 171 207 466 479 469 105 +268 11 2 1 1 97 167 3 275 479 170 106 455 280 474 +269 11 2 1 1 32 123 263 40 137 446 298 141 480 142 +270 11 2 1 1 263 123 189 40 446 445 481 480 227 142 +271 11 2 1 1 189 123 31 39 445 136 482 226 139 140 +272 11 2 1 1 265 23 270 195 483 484 293 485 486 216 +273 11 2 1 1 53 145 190 46 162 448 487 161 231 155 +274 11 2 1 1 263 123 31 189 446 136 299 481 482 445 +275 11 2 1 1 98 270 24 16 451 488 119 113 118 292 +276 11 2 1 1 266 38 192 122 489 223 490 453 444 135 +277 11 2 1 1 266 30 38 122 296 134 489 453 135 132 +278 11 2 1 1 60 166 67 194 182 184 185 241 491 449 +279 11 2 1 1 23 270 15 265 484 291 115 483 289 293 +280 11 2 1 1 270 24 191 98 488 219 492 451 450 119 +281 11 2 1 1 100 23 15 265 117 115 109 447 289 483 +282 11 2 1 1 122 192 266 39 444 490 453 138 493 224 +283 11 2 1 1 189 31 266 39 482 295 494 226 493 139 +284 11 2 1 1 266 192 189 39 490 228 494 493 226 224 +285 11 2 1 1 265 100 23 195 447 117 483 485 216 454 +286 11 2 1 1 196 53 190 46 495 487 235 233 231 161 +287 11 2 1 1 31 122 266 39 133 453 295 139 493 138 +288 11 2 1 1 196 146 53 46 452 163 495 233 161 160 +289 11 2 1 1 23 191 270 195 220 492 484 216 486 221 +290 11 2 1 1 263 31 266 189 299 295 300 481 494 482 +291 11 2 1 1 262 196 193 192 496 258 497 498 244 257 +292 11 2 1 1 269 145 190 53 467 448 499 302 487 162 +293 11 2 1 1 52 145 190 269 158 448 500 303 499 467 +294 11 2 1 1 169 66 197 264 181 501 461 468 502 309 +295 11 2 1 1 268 166 68 61 473 187 313 503 188 183 +296 11 2 1 1 198 264 271 267 504 324 505 506 325 323 +297 11 2 1 1 266 192 193 189 490 244 507 494 245 228 +298 11 2 1 1 198 264 267 268 504 323 506 508 322 314 +299 11 2 1 1 268 166 61 194 473 183 503 509 240 449 +300 11 2 1 1 270 191 23 98 492 220 484 451 116 450 +301 11 2 1 1 263 266 193 189 300 507 510 481 245 494 +302 11 2 1 1 60 67 197 194 185 511 237 241 242 491 +303 11 2 1 1 262 54 146 47 305 165 471 512 159 164 +304 11 2 1 1 273 144 99 201 475 477 456 513 465 464 +305 11 2 1 1 143 121 199 274 478 463 470 457 514 458 +306 11 2 1 1 168 120 272 200 476 459 460 472 515 462 +307 11 2 1 1 14 100 265 195 110 447 288 516 485 454 +308 11 2 1 1 202 167 97 275 466 479 469 517 455 474 +309 11 2 1 1 197 169 264 67 461 468 502 511 310 186 +310 11 2 1 1 23 270 98 15 484 451 116 115 114 291 +311 11 2 1 1 268 67 166 194 312 184 473 509 449 491 +312 11 2 1 1 196 262 146 47 496 471 452 234 159 512 +313 11 2 1 1 100 14 22 195 110 111 112 454 217 516 +314 11 2 1 1 53 190 269 196 487 499 302 495 518 235 +315 11 2 1 1 146 53 262 196 163 306 471 452 496 495 +316 11 2 1 1 197 169 67 60 461 186 511 237 185 179 +317 11 2 1 1 67 268 197 194 312 519 511 491 242 509 +318 11 2 1 1 190 45 145 52 230 156 448 500 158 157 +319 11 2 1 1 200 32 263 40 520 298 521 213 480 141 +320 11 2 1 1 189 200 263 40 229 521 481 227 480 213 +321 11 2 1 1 270 275 24 16 294 522 488 292 118 279 +322 11 2 1 1 275 24 202 270 522 208 517 294 523 488 +323 11 2 1 1 268 197 264 67 519 502 314 312 310 511 +324 11 2 1 1 266 193 267 263 507 524 316 300 320 510 +325 11 2 1 1 32 200 263 272 520 521 298 286 301 515 +326 11 2 1 1 191 195 203 271 221 249 252 525 526 527 +327 11 2 1 1 269 262 53 196 307 306 302 518 495 496 +328 11 2 1 1 66 197 59 169 501 238 180 181 178 461 +329 11 2 1 1 203 271 269 267 526 328 528 529 319 325 +330 11 2 1 1 198 191 203 271 255 252 256 505 526 525 +331 11 2 1 1 199 38 192 266 211 223 225 530 490 489 +332 11 2 1 1 38 274 30 266 531 282 134 489 296 297 +333 11 2 1 1 24 191 202 270 219 222 208 488 523 492 +334 11 2 1 1 38 199 274 266 211 514 531 489 297 530 +335 11 2 1 1 268 264 197 194 314 502 519 509 242 532 +336 11 2 1 1 66 275 197 264 281 533 501 309 502 311 +337 11 2 1 1 272 268 68 200 315 313 287 515 534 535 +338 11 2 1 1 269 190 203 196 499 250 528 518 251 235 +339 11 2 1 1 200 268 68 61 535 313 534 215 188 503 +340 11 2 1 1 268 198 264 194 508 504 314 509 532 260 +341 11 2 1 1 52 190 273 269 500 536 278 303 304 499 +342 11 2 1 1 54 199 262 274 537 538 305 284 308 514 +343 11 2 1 1 190 273 201 45 536 513 232 230 206 539 +344 11 2 1 1 273 14 265 195 277 288 290 540 485 516 +345 11 2 1 1 274 121 38 30 458 126 531 282 134 127 +346 11 2 1 1 201 99 14 22 465 104 541 204 111 103 +347 11 2 1 1 199 54 262 47 537 305 538 212 512 164 +348 11 2 1 1 200 120 32 40 462 130 520 213 141 131 +349 11 2 1 1 14 201 22 195 541 204 111 516 217 218 +350 11 2 1 1 61 200 268 194 215 535 503 240 509 243 +351 11 2 1 1 197 202 275 66 239 517 533 501 281 542 +352 11 2 1 1 263 268 200 189 321 535 521 481 229 543 +353 11 2 1 1 273 201 195 190 513 218 540 536 248 232 +354 11 2 1 1 143 199 54 274 470 537 154 457 284 514 +355 11 2 1 1 168 272 68 200 460 287 176 472 534 515 +356 11 2 1 1 99 14 273 201 104 277 456 465 513 541 +357 11 2 1 1 273 144 201 45 475 464 513 539 206 149 +358 11 2 1 1 120 32 272 200 130 286 459 462 515 520 +359 11 2 1 1 38 199 121 274 211 463 126 531 458 514 +360 11 2 1 1 14 273 201 195 277 513 541 516 218 540 +361 11 2 1 1 47 196 262 199 234 496 512 212 538 236 +362 11 2 1 1 191 198 264 271 255 504 544 525 324 505 +363 11 2 1 1 54 47 199 143 164 212 537 154 470 153 +364 11 2 1 1 45 190 273 52 230 536 539 157 278 500 +365 11 2 1 1 270 191 264 271 492 544 327 326 324 525 +366 11 2 1 1 194 200 268 189 243 535 509 247 543 229 +367 11 2 1 1 200 68 168 61 534 176 472 215 177 188 +368 11 2 1 1 202 197 59 66 239 238 209 542 180 501 +369 11 2 1 1 198 197 264 194 254 502 504 260 532 242 +370 11 2 1 1 263 268 272 200 321 315 301 521 515 535 +371 11 2 1 1 202 167 275 66 466 474 517 542 281 172 +372 11 2 1 1 265 273 195 269 290 540 485 330 545 304 +373 11 2 1 1 273 144 45 52 475 149 539 278 157 150 +374 11 2 1 1 190 195 273 269 248 540 536 499 304 545 +375 11 2 1 1 271 265 203 269 329 546 526 328 528 330 +376 11 2 1 1 167 202 59 66 466 209 173 172 180 542 +377 11 2 1 1 191 197 202 264 253 239 222 544 547 502 +378 11 2 1 1 192 262 196 199 498 496 257 225 236 538 +379 11 2 1 1 97 24 202 275 108 208 469 455 517 522 +380 11 2 1 1 275 97 24 16 455 108 522 279 118 107 +381 11 2 1 1 191 198 197 264 255 254 253 544 502 504 +382 11 2 1 1 203 265 195 269 546 485 249 528 545 330 +383 11 2 1 1 271 203 198 267 526 256 505 325 506 529 +384 11 2 1 1 190 203 195 269 250 249 248 499 545 528 +385 11 2 1 1 203 195 265 271 249 485 546 526 329 527 +386 11 2 1 1 275 202 197 264 517 239 533 311 502 547 +387 11 2 1 1 267 203 198 193 529 256 506 524 259 261 +388 11 2 1 1 167 3 4 97 170 21 171 479 105 106 +389 11 2 1 1 7 8 120 168 37 129 128 175 476 174 +390 11 2 1 1 121 5 6 143 124 29 125 478 151 152 +391 11 2 1 1 144 1 2 99 147 13 148 477 101 102 +392 11 2 1 1 270 195 271 265 486 527 326 293 329 485 +393 11 2 1 1 271 195 270 191 527 486 326 525 492 221 +394 11 2 1 1 262 193 203 267 497 261 548 317 529 524 +395 11 2 1 1 203 193 262 196 261 497 548 251 496 258 +396 11 2 1 1 203 269 262 267 528 307 548 529 317 319 +397 11 2 1 1 262 269 203 196 307 528 548 496 251 518 +398 11 2 1 1 193 266 262 192 507 318 497 244 498 490 +399 11 2 1 1 262 266 193 267 318 507 497 317 524 316 +400 11 2 1 1 202 270 264 275 523 327 547 517 311 294 +401 11 2 1 1 264 270 202 191 327 523 547 544 222 492 +402 11 2 1 1 189 268 193 263 543 549 245 481 510 321 +403 11 2 1 1 189 193 268 194 245 549 543 247 509 246 +404 11 2 1 1 267 193 268 263 524 549 322 320 321 510 +405 11 2 1 1 199 266 262 274 530 318 538 514 308 297 +406 11 2 1 1 262 266 199 192 318 530 538 498 225 490 +407 11 2 1 1 193 268 198 267 549 508 259 524 506 322 +408 11 2 1 1 193 198 268 194 259 508 549 246 509 260 +409 11 2 2 2 194 193 198 423 246 259 260 550 551 552 +410 11 2 2 2 331 426 191 195 553 554 555 556 221 557 +411 11 2 2 2 194 426 400 401 558 559 560 561 419 562 +412 11 2 2 2 189 193 194 423 245 246 247 563 550 552 +413 11 2 2 2 426 198 203 193 564 256 565 566 261 259 +414 11 2 2 2 354 189 355 424 567 568 373 569 570 571 +415 11 2 2 2 331 426 332 191 553 572 350 555 573 554 +416 11 2 2 2 192 189 354 424 228 567 574 575 569 571 +417 11 2 2 2 377 190 196 424 576 235 577 578 579 580 +418 11 2 2 2 426 400 197 194 559 581 582 558 242 560 +419 11 2 2 2 195 426 191 203 557 554 221 249 252 565 +420 11 2 2 2 197 198 426 194 254 564 582 242 558 260 +421 11 2 2 2 196 377 424 378 577 578 579 583 584 396 +422 11 2 2 2 193 426 198 423 566 564 259 552 551 442 +423 11 2 2 2 194 198 426 423 260 564 558 550 442 551 +424 11 2 2 2 196 190 203 424 235 250 251 579 585 580 +425 11 2 2 2 426 331 425 195 553 586 439 557 587 556 +426 11 2 2 2 189 192 193 424 228 244 245 571 588 575 +427 11 2 2 2 198 191 426 203 255 554 564 256 565 252 +428 11 2 2 2 401 194 426 423 561 558 562 589 442 550 +429 11 2 2 2 190 377 425 424 576 590 591 580 441 578 +430 11 2 2 2 203 195 425 190 249 587 592 250 591 248 +431 11 2 2 2 425 426 195 203 439 557 587 592 249 565 +432 11 2 2 2 199 196 192 424 236 257 225 593 575 579 +433 11 2 2 2 195 201 425 190 218 594 587 248 591 232 +434 11 2 2 2 423 355 189 424 595 568 563 440 571 570 +435 11 2 2 2 189 194 200 423 247 243 229 563 596 550 +436 11 2 2 2 426 191 197 202 554 253 582 597 239 222 +437 11 2 2 2 39 192 189 354 224 228 226 369 567 574 +438 11 2 2 2 424 192 199 356 575 225 593 598 599 600 +439 11 2 2 2 424 354 192 356 569 574 575 598 600 370 +440 11 2 2 2 198 426 191 197 564 554 255 254 253 582 +441 11 2 2 2 425 203 190 424 592 250 591 441 580 585 +442 11 2 2 2 23 331 332 191 345 350 349 220 573 555 +443 11 2 2 2 423 189 193 424 563 245 552 440 588 571 +444 11 2 2 2 355 39 189 354 371 226 568 373 567 369 +445 11 2 2 2 191 23 331 195 220 345 555 221 556 216 +446 11 2 2 2 60 197 400 194 237 581 414 241 560 242 +447 11 2 2 2 424 199 196 380 593 236 579 601 602 603 +448 11 2 2 2 426 94 400 401 432 422 559 562 419 421 +449 11 2 2 2 197 402 426 202 604 605 582 239 597 606 +450 11 2 2 2 400 402 426 197 416 605 559 581 582 604 +451 11 2 2 2 400 60 194 401 414 241 560 419 561 418 +452 11 2 2 2 190 377 196 46 576 577 235 231 233 392 +453 11 2 2 2 378 424 196 380 584 579 583 397 602 601 +454 11 2 2 2 46 377 196 378 392 577 233 394 583 396 +455 11 2 2 2 379 377 425 190 393 590 607 608 591 576 +456 11 2 2 2 193 196 203 424 258 251 261 588 585 579 +457 11 2 2 2 333 425 331 195 609 586 347 610 556 587 +458 11 2 2 2 426 79 331 332 431 352 553 572 350 353 +459 11 2 2 2 355 88 354 424 375 376 373 570 569 434 +460 11 2 2 2 201 379 425 190 611 607 594 232 591 608 +461 11 2 2 2 196 193 192 424 258 244 257 579 575 588 +462 11 2 2 2 425 333 201 195 609 612 594 587 218 610 +463 11 2 2 2 357 355 189 423 374 568 613 614 563 595 +464 11 2 2 2 194 401 403 423 561 420 615 550 616 589 +465 11 2 2 2 357 189 200 423 613 229 617 614 596 563 +466 11 2 2 2 380 424 199 356 601 593 603 618 599 598 +467 11 2 2 2 91 424 377 378 435 578 398 399 396 584 +468 11 2 2 2 403 200 194 423 619 243 615 616 550 596 +469 11 2 2 2 193 203 426 424 261 565 566 588 443 585 +470 11 2 2 2 332 24 191 202 348 219 573 620 222 208 +471 11 2 2 2 402 334 426 202 621 622 605 606 597 623 +472 11 2 2 2 423 193 426 424 552 566 442 440 443 588 +473 11 2 2 2 426 94 401 423 432 421 562 442 589 438 +474 11 2 2 2 79 331 425 426 352 586 427 431 439 553 +475 11 2 2 2 38 192 354 356 223 574 368 359 370 600 +476 11 2 2 2 192 38 199 356 223 211 225 600 599 359 +477 11 2 2 2 355 423 88 424 595 436 375 570 434 440 +478 11 2 2 2 425 426 203 424 439 565 592 441 585 443 +479 11 2 2 2 380 11 424 356 389 433 601 618 598 364 +480 11 2 2 2 377 425 424 91 590 441 578 398 435 429 +481 11 2 2 2 425 333 379 201 609 624 607 594 611 612 +482 11 2 2 2 424 11 88 356 433 89 434 598 365 364 +483 11 2 2 2 91 11 424 380 93 433 435 390 601 389 +484 11 2 2 2 24 332 334 202 348 351 339 208 623 620 +485 11 2 2 2 94 10 426 402 95 430 432 411 605 410 +486 11 2 2 2 426 10 79 334 430 81 431 622 344 343 +487 11 2 2 2 39 38 192 354 42 223 224 369 574 368 +488 11 2 2 2 88 354 424 356 376 569 434 365 598 370 +489 11 2 2 2 425 9 333 79 428 341 609 427 342 80 +490 11 2 2 2 379 9 425 91 387 428 607 388 429 92 +491 11 2 2 2 378 91 424 380 399 435 584 397 601 390 +492 11 2 2 2 379 377 190 45 393 576 608 382 230 391 +493 11 2 2 2 60 61 194 401 64 240 241 418 561 417 +494 11 2 2 2 40 39 189 355 43 226 227 372 568 371 +495 11 2 2 2 423 12 357 88 437 366 614 436 367 90 +496 11 2 2 2 403 94 12 423 413 96 412 616 437 438 +497 11 2 2 2 334 426 10 402 622 430 343 621 410 605 +498 11 2 2 2 23 24 191 332 27 219 220 349 573 348 +499 11 2 2 2 426 94 402 400 432 411 605 559 416 422 +500 11 2 2 2 46 45 190 377 49 230 231 392 576 391 +501 11 2 2 2 59 402 400 197 404 416 415 238 581 604 +502 11 2 2 2 22 333 331 195 335 347 346 217 556 610 +503 11 2 2 2 379 73 1 201 383 75 381 611 205 625 +504 11 2 2 2 73 333 1 201 337 336 75 625 205 612 +505 11 2 2 2 82 199 5 356 626 210 84 360 358 599 +506 11 2 2 2 47 46 196 378 50 233 234 395 583 394 +507 11 2 2 2 8 200 85 357 214 627 87 362 363 617 +508 11 2 2 2 403 8 200 85 407 214 619 409 627 87 +509 11 2 2 2 380 199 5 82 603 210 385 386 84 626 +510 11 2 2 2 79 426 334 332 431 622 344 353 351 572 +511 11 2 2 2 402 4 76 202 405 78 406 606 628 207 +512 11 2 2 2 190 201 379 45 232 611 608 230 382 206 +513 11 2 2 2 4 334 76 202 338 340 78 207 628 623 +514 11 2 2 2 197 59 402 202 238 404 604 239 606 209 +515 11 2 2 2 200 403 357 423 619 629 617 596 614 616 +516 11 2 2 2 22 23 195 331 26 216 217 346 556 345 +517 11 2 2 2 22 201 333 195 204 612 335 217 610 218 +518 11 2 2 2 331 425 333 79 586 609 347 352 342 427 +519 11 2 2 2 425 377 379 91 590 393 607 429 388 398 +520 11 2 2 2 60 59 400 197 63 415 414 237 581 238 +521 11 2 2 2 45 1 201 379 48 205 206 382 611 381 +522 11 2 2 2 5 38 356 199 41 359 358 210 599 211 +523 11 2 2 2 1 22 201 333 25 204 205 336 612 335 +524 11 2 2 2 4 59 202 402 62 209 207 405 606 404 +525 11 2 2 2 423 357 355 88 614 374 595 436 375 367 +526 11 2 2 2 403 401 94 423 420 421 413 616 438 589 +527 11 2 2 2 8 40 200 357 44 213 214 362 617 361 +528 11 2 2 2 333 73 379 201 337 383 624 612 611 625 +529 11 2 2 2 82 380 199 356 386 603 626 360 599 618 +530 11 2 2 2 402 76 334 202 406 340 621 606 623 628 +531 11 2 2 2 85 200 403 357 627 619 409 363 629 617 +532 11 2 2 2 24 4 202 334 28 207 208 339 623 338 +533 11 2 2 2 5 47 199 380 51 212 210 385 603 384 +534 11 2 2 2 61 8 200 403 65 214 215 408 619 407 +535 11 2 2 2 379 9 333 425 387 341 624 607 609 428 +536 11 2 2 2 73 9 333 379 74 341 337 383 624 387 +537 11 2 2 2 357 403 12 423 629 412 366 614 437 616 +538 11 2 2 2 76 334 10 402 340 343 77 406 410 621 +539 11 2 2 2 85 403 12 357 409 412 86 363 366 629 +540 11 2 2 2 82 11 380 356 83 389 386 360 618 364 +541 11 2 2 2 194 403 61 200 615 408 240 243 215 619 +542 11 2 2 2 61 403 194 401 408 615 240 417 561 420 +543 11 2 2 2 196 380 47 378 602 384 234 583 395 397 +544 11 2 2 2 47 380 196 199 384 602 234 212 236 603 +545 11 2 2 2 202 332 426 191 620 572 597 222 554 573 +546 11 2 2 2 202 426 332 334 597 572 620 623 351 622 +547 11 2 2 2 189 357 40 355 613 361 227 568 372 374 +548 11 2 2 2 40 357 189 200 361 613 227 213 229 617 +$EndElements diff --git a/tests/fixtures/coarse-o2-v41.msh b/tests/fixtures/coarse-o2-v41.msh new file mode 100644 index 0000000..a588443 --- /dev/null +++ b/tests/fixtures/coarse-o2-v41.msh @@ -0,0 +1,1921 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +12 20 11 2 +1 0 0 -0.25 0 +2 0 0 0 0 +3 0 2 0 0 +4 0 2 -0.25 0 +5 2 0 -0.25 0 +6 2 0 0 0 +7 2 2 0 0 +8 2 2 -0.25 0 +9 0 0 -1 0 +10 0 2 -1 0 +11 2 0 -1 0 +12 2 2 -1 0 +1 -1e-07 -1e-07 -0.2500001 1e-07 1e-07 1.000000000028756e-07 0 2 1 -2 +2 -1e-07 -9.999999994736442e-08 -1e-07 1e-07 2.0000001 1e-07 0 2 2 -3 +3 -1e-07 1.9999999 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 0 2 4 -3 +4 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 -0.2499999 0 2 1 -4 +5 1.9999999 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 0 2 5 -6 +6 1.9999999 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 0 2 6 -7 +7 1.9999999 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 0 2 8 -7 +8 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 5 -8 +9 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 -0.2499999 0 2 1 -5 +10 -9.999999994736442e-08 -1e-07 -1e-07 2.0000001 1e-07 1e-07 0 2 2 -6 +11 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 4 -8 +12 -9.999999994736442e-08 1.9999999 -1e-07 2.0000001 2.0000001 1e-07 0 2 3 -7 +13 -1e-07 -1e-07 -1.0000001 1e-07 1e-07 -0.2499999 0 2 9 -1 +14 -1e-07 1.9999999 -1.0000001 1e-07 2.0000001 -0.2499999 0 2 10 -4 +15 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.9999999000000001 0 2 9 -10 +16 1.9999999 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 0 2 11 -5 +17 1.9999999 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 0 2 12 -8 +18 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 11 -12 +19 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.9999999000000001 0 2 9 -11 +20 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 10 -12 +1 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 1 105 4 -1 4 3 -2 +2 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -5 8 7 -6 +3 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 1 105 4 -9 1 10 -5 +4 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -11 3 12 -7 +5 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 1 103 4 -4 9 8 -11 +6 -9.999999994736442e-08 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 1 101 4 -2 10 6 -12 +7 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.2499999 1 105 4 -13 15 14 -4 +8 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -16 18 17 -8 +9 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 1 105 4 -19 13 9 -16 +10 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -20 14 11 -17 +11 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 1 105 4 -15 19 18 -20 +1 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 1 6 -1 2 -3 4 -5 6 +2 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 2 6 -7 8 -9 10 -11 5 +$EndEntities +$Nodes +45 629 1 629 +0 1 0 1 +1 +0 0 -0.25 +0 2 0 1 +2 +0 0 0 +0 3 0 1 +3 +0 2 0 +0 4 0 1 +4 +0 2 -0.25 +0 5 0 1 +5 +2 0 -0.25 +0 6 0 1 +6 +2 0 0 +0 7 0 1 +7 +2 2 0 +0 8 0 1 +8 +2 2 -0.25 +0 9 0 1 +9 +0 0 -1 +0 10 0 1 +10 +0 2 -1 +0 11 0 1 +11 +2 0 -1 +0 12 0 1 +12 +2 2 -1 +1 1 0 1 +13 +0 0 -0.125 +1 2 0 7 +14 +15 +16 +17 +18 +19 +20 +0 0.5 0 +0 1 0 +0 1.5 0 +0 0.25 0 +0 0.75 0 +0 1.25 0 +0 1.75 0 +1 3 0 1 +21 +0 2 -0.125 +1 4 0 7 +22 +23 +24 +25 +26 +27 +28 +0 0.5 -0.25 +0 1 -0.25 +0 1.5 -0.25 +0 0.25 -0.25 +0 0.75 -0.25 +0 1.25 -0.25 +0 1.75 -0.25 +1 5 0 1 +29 +2 0 -0.125 +1 6 0 7 +30 +31 +32 +33 +34 +35 +36 +2 0.5 0 +2 1 0 +2 1.5 0 +2 0.25 0 +2 0.75 0 +2 1.25 0 +2 1.75 0 +1 7 0 1 +37 +2 2 -0.125 +1 8 0 7 +38 +39 +40 +41 +42 +43 +44 +2 0.5 -0.25 +2 1 -0.25 +2 1.5 -0.25 +2 0.25 -0.25 +2 0.75 -0.25 +2 1.25 -0.25 +2 1.75 -0.25 +1 9 0 7 +45 +46 +47 +48 +49 +50 +51 +0.5 0 -0.25 +1 0 -0.25 +1.5 0 -0.25 +0.25 0 -0.25 +0.75 0 -0.25 +1.25 0 -0.25 +1.75 0 -0.25 +1 10 0 7 +52 +53 +54 +55 +56 +57 +58 +0.5 0 0 +1 0 0 +1.5 0 0 +0.25 0 0 +0.75 0 0 +1.25 0 0 +1.75 0 0 +1 11 0 7 +59 +60 +61 +62 +63 +64 +65 +0.5 2 -0.25 +1 2 -0.25 +1.5 2 -0.25 +0.25 2 -0.25 +0.75 2 -0.25 +1.25 2 -0.25 +1.75 2 -0.25 +1 12 0 7 +66 +67 +68 +69 +70 +71 +72 +0.5 2 0 +1 2 0 +1.5 2 0 +0.25 2 0 +0.75 2 0 +1.25 2 0 +1.75 2 0 +1 13 0 3 +73 +74 +75 +0 0 -0.5863498029562912 +0 0 -0.7931749014781456 +0 0 -0.4181749014781456 +1 14 0 3 +76 +77 +78 +0 2 -0.5863498029562912 +0 2 -0.7931749014781456 +0 2 -0.4181749014781456 +1 15 0 3 +79 +80 +81 +0 1 -1 +0 0.4999999999999999 -1 +0 1.5 -1 +1 16 0 3 +82 +83 +84 +2 0 -0.5863498029562912 +2 0 -0.7931749014781456 +2 0 -0.4181749014781456 +1 17 0 3 +85 +86 +87 +2 2 -0.5863498029562912 +2 2 -0.7931749014781456 +2 2 -0.4181749014781456 +1 18 0 3 +88 +89 +90 +2 1 -1 +2 0.4999999999999999 -1 +2 1.5 -1 +1 19 0 3 +91 +92 +93 +1 0 -1 +0.4999999999999999 0 -1 +1.5 0 -1 +1 20 0 3 +94 +95 +96 +1 2 -1 +0.4999999999999999 2 -1 +1.5 2 -1 +2 1 0 23 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +0 1.75 -0.125 +0 1.25 -0.125 +0 0.25 -0.125 +0 0.75 -0.125 +0 0.125 -0.0625 +0 0.125 -0.1875 +0 0.375 -0.1875 +0 0.375 -0.0625 +0 1.875 -0.1875 +0 1.875 -0.0625 +0 1.625 -0.0625 +0 1.625 -0.1875 +0 0.875 -0.0625 +0 0.625 -0.0625 +0 0.5 -0.125 +0 0.625 -0.1875 +0 1.375 -0.0625 +0 1.125 -0.0625 +0 1 -0.125 +0 1.125 -0.1875 +0 0.875 -0.1875 +0 1.5 -0.125 +0 1.375 -0.1875 +2 2 0 23 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +2 1.75 -0.125 +2 0.25 -0.125 +2 0.75 -0.125 +2 1.25 -0.125 +2 0.125 -0.1875 +2 0.125 -0.0625 +2 0.375 -0.1875 +2 0.375 -0.0625 +2 1.875 -0.0625 +2 1.875 -0.1875 +2 1.625 -0.0625 +2 1.625 -0.1875 +2 0.625 -0.0625 +2 0.875 -0.0625 +2 0.5 -0.125 +2 0.625 -0.1875 +2 1.125 -0.0625 +2 1.375 -0.0625 +2 0.875 -0.1875 +2 1 -0.125 +2 1.125 -0.1875 +2 1.5 -0.125 +2 1.375 -0.1875 +2 3 0 23 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +1.75 0 -0.125 +0.25 0 -0.125 +0.75 0 -0.125 +1.25 0 -0.125 +0.125 0 -0.1875 +0.125 0 -0.0625 +0.375 0 -0.1875 +0.375 0 -0.0625 +1.875 0 -0.0625 +1.875 0 -0.1875 +1.625 0 -0.1875 +1.625 0 -0.0625 +0.875 0 -0.1875 +0.625 0 -0.1875 +0.5 0 -0.125 +0.625 0 -0.0625 +1.375 0 -0.1875 +1.125 0 -0.1875 +1 0 -0.125 +0.875 0 -0.0625 +1.125 0 -0.0625 +1.5 0 -0.125 +1.375 0 -0.0625 +2 4 0 23 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +1.25 2 -0.125 +0.25 2 -0.125 +1.75 2 -0.125 +0.75 2 -0.125 +0.125 2 -0.0625 +0.125 2 -0.1875 +0.375 2 -0.0625 +0.375 2 -0.1875 +1.875 2 -0.1875 +1.875 2 -0.0625 +1.625 2 -0.0625 +1.625 2 -0.1875 +0.625 2 -0.1875 +0.875 2 -0.1875 +0.5 2 -0.125 +0.625 2 -0.0625 +1.125 2 -0.1875 +1.375 2 -0.1875 +1.125 2 -0.0625 +1 2 -0.125 +0.875 2 -0.0625 +1.375 2 -0.0625 +1.5 2 -0.125 +2 5 0 73 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +1.578151847792411 1.230662432702593 -0.25 +0.7183803468335941 0.3953531183916653 -0.25 +0.4514095486548143 1.234183475777031 -0.25 +1.577292096379071 0.7509441482399639 -0.25 +1.19048230709083 0.9938634724449638 -0.25 +1.211526285167907 1.539080572974888 -0.25 +0.389289068198761 0.7180466052574374 -0.25 +1.213941721673849 0.4470568705367829 -0.25 +0.7375873251999121 1.636990986207155 -0.25 +0.8638678665980398 1.245262305856504 -0.25 +1.633974596215561 0.3660254037844386 -0.25 +1.633974596215561 1.633974596215561 -0.25 +0.3660254037844386 0.3660254037844387 -0.25 +0.3630158654516971 1.636984134548303 -0.25 +0.799919109919719 0.8127176335914889 -0.25 +0.1830127018922193 0.4330127018922194 -0.25 +0.1830127018922193 0.1830127018922194 -0.25 +0.4330127018922193 0.1830127018922194 -0.25 +0.1815079327258486 1.818492067274152 -0.25 +0.1815079327258486 1.568492067274152 -0.25 +0.4315079327258485 1.818492067274152 -0.25 +1.816987298107781 0.1830127018922193 -0.25 +1.816987298107781 0.4330127018922193 -0.25 +1.566987298107781 0.1830127018922193 -0.25 +1.816987298107781 1.566987298107781 -0.25 +1.816987298107781 1.816987298107781 -0.25 +1.566987298107781 1.816987298107781 -0.25 +0.1946445340993805 0.8590233026287186 -0.25 +0.1946445340993805 0.6090233026287186 -0.25 +0.3776572359915998 0.542036004520938 -0.25 +0.2257047743274071 1.367091737888515 -0.25 +0.2257047743274071 1.117091737888515 -0.25 +0.4203493084267876 0.9761150405172343 -0.25 +0.4072127070532557 1.435583805162667 -0.25 +1.788646048189535 0.625472074119982 -0.25 +1.788646048189535 0.875472074119982 -0.25 +1.605633346297316 0.5584847760122013 -0.25 +1.789075923896205 1.115331216351297 -0.25 +1.789075923896205 1.365331216351297 -0.25 +1.577721972085741 0.9908032904712787 -0.25 +1.606063222003986 1.432318514459077 -0.25 +0.609190173416797 0.1976765591958326 -0.25 +0.859190173416797 0.1976765591958326 -0.25 +0.5422028753090163 0.380689261088052 -0.25 +1.106970860836924 0.2235284352683914 -0.25 +1.356970860836924 0.2235284352683914 -0.25 +0.9661610342537215 0.4212049944642241 -0.25 +1.423958158944705 0.4065411371606107 -0.25 +0.8687936625999561 1.818495493103577 -0.25 +0.6187936625999561 1.818495493103577 -0.25 +0.5503015953258046 1.636987560377729 -0.25 +1.355763142583954 1.769540286487444 -0.25 +1.105763142583954 1.769540286487444 -0.25 +0.9745568051839095 1.588035779591021 -0.25 +1.422750440691734 1.586527584595224 -0.25 +1.38388720173495 0.8724038103424638 -0.25 +1.38431707744162 1.112262952573779 -0.25 +1.201004296129369 1.266472022709926 -0.25 +1.394839066480159 1.384871502838741 -0.25 +0.5538347075161776 0.5566998618245513 -0.25 +0.59460408905924 0.7653821194244631 -0.25 +0.7591497283766566 0.6040353759915771 -0.25 +1.006930415796784 0.6298872520641359 -0.25 +0.6256643292872667 1.02345055468426 -0.25 +0.5944984369273631 1.435587230992093 -0.25 +0.8007275958989759 1.441126646031829 -0.25 +0.6576387076264271 1.239722890816767 -0.25 +0.8318934882588794 1.028989969723996 -0.25 +1.39561690902646 0.5990005093883735 -0.25 +1.20221201438234 0.7204601714908734 -0.25 +1.027175086844435 1.119562889150734 -0.25 +1.037697075882973 1.392171439415696 -0.25 +0.9952007085052745 0.9032905530182262 -0.25 +2 6 0 69 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +1.271813574233536 0.4264624335545749 0 +1.57791535636081 1.249332516611218 0 +0.7707992533266995 1.491026004178691 0 +0.3838980770800826 0.7512620920870399 0 +1.604157990548827 0.756865646419863 0 +1.138381937170266 0.9886994546006772 0 +1.25740778765016 1.566401986104808 0 +0.7720151968557594 0.504236995066732 0 +0.3774516866522022 1.228912045653614 0 +0.6885092302170019 0.9928273183173509 0 +1.633974596215561 1.633974596215561 0 +0.3660254037844387 0.3660254037844386 0 +1.633974596215561 0.3660254037844387 0 +0.3296501879957803 1.643987609966461 0 +0.1830127018922194 0.1830127018922193 0 +0.1830127018922194 0.4330127018922193 0 +0.4330127018922194 0.1830127018922193 0 +0.1648250939978902 1.57199380498323 0 +0.1648250939978902 1.82199380498323 0 +0.4148250939978901 1.82199380498323 0 +1.816987298107781 0.4330127018922194 0 +1.816987298107781 0.1830127018922194 0 +1.566987298107781 0.1830127018922194 0 +1.816987298107781 1.816987298107781 0 +1.816987298107781 1.566987298107781 0 +1.566987298107781 1.816987298107781 0 +0.1919490385400413 0.6256310460435199 0 +0.1919490385400413 0.8756310460435199 0 +0.3749617404322607 0.5586437479357392 0 +0.1887258433261011 1.114456022826807 0 +0.1887258433261011 1.364456022826807 0 +0.3806748818661424 0.9900870688703272 0 +0.3535509373239912 1.436449827810038 0 +1.802078995274414 0.8784328232099314 0 +1.802078995274414 0.6284328232099314 0 +1.619066293382194 0.5614455251021508 0 +1.788957678180405 1.374666258305609 0 +1.788957678180405 1.124666258305609 0 +1.591036673454818 1.003099081515541 0 +1.605944976288185 1.44165355641339 0 +0.8860075984278797 0.252118497533366 0 +0.6360075984278797 0.252118497533366 0 +0.5690203003200991 0.4351311994255853 0 +1.385906787116768 0.2132312167772875 0 +1.135906787116768 0.2132312167772875 0 +1.021914385544648 0.4653497143106535 0 +1.452894085224548 0.3962439186695068 0 +0.6353996266633497 1.745513002089345 0 +0.8853996266633497 1.745513002089345 0 +0.55022472066124 1.567506807072576 0 +1.12870389382508 1.783200993052404 0 +1.37870389382508 1.783200993052404 0 +1.01410352048843 1.528713995141749 0 +1.445691191932861 1.600188291160184 0 +1.371269963859547 0.8727825505102701 0 +1.205097755701901 0.7075809440776261 0 +1.437985782391181 0.5916640399872189 0 +0.9551985670130129 0.7464682248337047 0 +1.358148646765538 1.119015985605948 0 +1.417661572005485 1.407867251358013 0 +1.197894862410213 1.277550720352742 0 +0.954590595248483 1.239862729389684 0 +0.7296542417718508 1.241926661248021 0 +0.9134455836936342 0.9907633864590141 0 +0.5329804584346021 1.110869681985483 0 +0.5741254699894509 1.359969024916153 0 +0.7302622135363807 0.7485321566920415 0 +0.5362036536485423 0.8720447052021953 0 +0.577956636967921 0.6277495435768859 0 +2 7 0 23 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +0 0.8250160980116606 -0.5458226013255596 +0 1.189987121590671 -0.5299871215906714 +0 0.3875026830019433 -0.6053620673803085 +0 1.614997853598445 -0.6027228207578272 +0 0.4437513415009717 -0.4276810336901542 +0 0.1937513415009717 -0.4276810336901542 +0 0.1937513415009717 -0.5958559351682999 +0 1.807498926799223 -0.4263614103789136 +0 1.557498926799223 -0.4263614103789136 +0 1.807498926799223 -0.5945363118570592 +0 0.1937513415009717 -0.8026810336901542 +0 0.6937513415009717 -0.8026810336901542 +0 1.807498926799223 -0.8013614103789136 +0 1.307498926799223 -0.8013614103789136 +0 0.9125080490058304 -0.3979113006627798 +0 0.6625080490058304 -0.3979113006627798 +0 0.606259390506802 -0.575592334352934 +0 1.344993560795336 -0.3899935607953358 +0 1.094993560795336 -0.3899935607953358 +0 1.007501609801166 -0.5379048614581154 +0 1.402492487594558 -0.5663549711742493 +0 0.9125080490058304 -0.7729113006627798 +0 1.094993560795336 -0.7649935607953358 +2 8 0 23 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +2 0.8250160980116606 -0.5458226013255596 +2 1.189987121590671 -0.5299871215906714 +2 0.3875026830019433 -0.6053620673803085 +2 1.614997853598445 -0.6027228207578272 +2 0.1937513415009717 -0.4276810336901542 +2 0.4437513415009717 -0.4276810336901542 +2 0.1937513415009717 -0.5958559351682999 +2 1.557498926799223 -0.4263614103789136 +2 1.807498926799223 -0.4263614103789136 +2 1.807498926799223 -0.5945363118570592 +2 0.1937513415009717 -0.8026810336901542 +2 0.6937513415009717 -0.8026810336901542 +2 1.807498926799223 -0.8013614103789136 +2 1.307498926799223 -0.8013614103789136 +2 0.6625080490058304 -0.3979113006627798 +2 0.9125080490058304 -0.3979113006627798 +2 0.606259390506802 -0.575592334352934 +2 1.094993560795336 -0.3899935607953358 +2 1.344993560795336 -0.3899935607953358 +2 1.007501609801166 -0.5379048614581154 +2 1.402492487594558 -0.5663549711742493 +2 1.094993560795336 -0.7649935607953358 +2 0.9125080490058304 -0.7729113006627798 +2 9 0 23 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +0.8250160980116606 0 -0.5458226013255596 +1.189987121590671 0 -0.5299871215906714 +0.3875026830019435 0 -0.6053620673803086 +1.614997853598445 0 -0.6027228207578272 +0.1937513415009717 0 -0.4276810336901543 +0.4437513415009717 0 -0.4276810336901543 +0.1937513415009717 0 -0.5958559351682999 +1.557498926799223 0 -0.4263614103789136 +1.807498926799223 0 -0.4263614103789136 +1.807498926799223 0 -0.5945363118570592 +0.1937513415009717 0 -0.8026810336901543 +0.6937513415009717 0 -0.8026810336901543 +1.807498926799223 0 -0.8013614103789136 +1.307498926799223 0 -0.8013614103789136 +0.6625080490058304 0 -0.3979113006627798 +0.9125080490058304 0 -0.3979113006627798 +0.6062593905068021 0 -0.5755923343529341 +1.094993560795336 0 -0.3899935607953358 +1.344993560795336 0 -0.3899935607953358 +1.007501609801166 0 -0.5379048614581154 +1.402492487594558 0 -0.5663549711742493 +0.9125080490058304 0 -0.7729113006627798 +1.094993560795336 0 -0.7649935607953358 +2 10 0 23 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +0.8250160980116606 2 -0.5458226013255596 +1.189987121590671 2 -0.5299871215906714 +0.3875026830019435 2 -0.6053620673803086 +1.614997853598445 2 -0.6027228207578272 +0.4437513415009717 2 -0.4276810336901543 +0.1937513415009717 2 -0.4276810336901543 +0.1937513415009717 2 -0.5958559351682999 +1.807498926799223 2 -0.4263614103789136 +1.557498926799223 2 -0.4263614103789136 +1.807498926799223 2 -0.5945363118570592 +0.1937513415009717 2 -0.8026810336901543 +0.6937513415009717 2 -0.8026810336901543 +1.807498926799223 2 -0.8013614103789136 +1.307498926799223 2 -0.8013614103789136 +0.9125080490058304 2 -0.3979113006627798 +0.6625080490058304 2 -0.3979113006627798 +0.6062593905068021 2 -0.5755923343529341 +1.344993560795336 2 -0.3899935607953358 +1.094993560795336 2 -0.3899935607953358 +1.007501609801166 2 -0.5379048614581154 +1.402492487594558 2 -0.5663549711742493 +1.094993560795336 2 -0.7649935607953358 +0.9125080490058304 2 -0.7729113006627798 +2 11 0 21 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +1.5 1.5 -1 +1.25 0.7499999999999999 -1 +0.5625 0.5625000000000002 -1 +0.6914062499999998 1.30859375 -1 +0.28125 0.7812500000000001 -1 +0.28125 0.2812500000000001 -1 +0.78125 0.2812500000000001 -1 +0.3457031249999999 1.654296875 -1 +0.3457031249999999 1.154296875 -1 +0.8457031249999999 1.654296875 -1 +1.625 0.3749999999999999 -1 +1.625 0.875 -1 +1.125 0.3749999999999999 -1 +1.75 1.25 -1 +1.75 1.75 -1 +1.25 1.75 -1 +0.6269531249999999 0.9355468750000002 -1 +1.375 1.125 -1 +0.90625 0.65625 -1 +1.095703125 1.404296875 -1 +0.9707031249999999 1.029296875 -1 +3 1 0 106 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +1.788646048189535 0.750472074119982 -0.1875 +1.789075923896205 1.240331216351297 -0.1875 +1.788957678180405 1.249666258305609 -0.0625 +0.1919490385400413 0.7506310460435199 -0.0625 +0.734190173416797 0.1976765591958326 -0.1875 +1.230763142583954 1.769540286487444 -0.1875 +0.2257047743274071 1.242091737888515 -0.1875 +0.1887258433261011 1.239456022826807 -0.0625 +1.231970860836924 0.2235284352683914 -0.1875 +1.802078995274414 0.7534328232099314 -0.0625 +0.1946445340993805 0.7340233026287186 -0.1875 +0.1648250939978902 1.69699380498323 -0.0625 +0.1830127018922194 0.3080127018922193 -0.0625 +1.691987298107781 0.1830127018922194 -0.0625 +1.816987298107781 0.3080127018922194 -0.0625 +1.816987298107781 1.691987298107781 -0.0625 +1.691987298107781 1.816987298107781 -0.0625 +0.7437936625999561 1.818495493103577 -0.1875 +1.816987298107781 1.691987298107781 -0.1875 +1.816987298107781 0.3080127018922193 -0.1875 +0.3080127018922193 0.1830127018922194 -0.1875 +0.1830127018922193 0.3080127018922194 -0.1875 +0.3065079327258485 1.818492067274152 -0.1875 +0.7610075984278797 0.252118497533366 -0.0625 +0.7603996266633497 1.745513002089345 -0.0625 +0.1815079327258486 1.693492067274152 -0.1875 +1.691987298107781 0.1830127018922193 -0.1875 +1.260906787116768 0.2132312167772875 -0.0625 +1.691987298107781 1.816987298107781 -0.1875 +1.25370389382508 1.783200993052404 -0.0625 +0.2898250939978901 1.82199380498323 -0.0625 +0.3080127018922194 0.1830127018922193 -0.0625 +1.875 1.875 -0.125 +0.125 0.125 -0.125 +1.875 0.125 -0.125 +0.125 1.875 -0.125 +1.788957678180405 1.374666258305609 -0.125 +1.57803360207661 1.239997474656906 -0.125 +1.789075923896205 1.115331216351297 -0.125 +0.1919490385400413 0.8756310460435199 -0.125 +0.1887258433261011 1.114456022826807 -0.125 +0.3865935726394218 0.7346543486722386 -0.125 +0.3833703774254816 0.9734793254555258 -0.125 +0.859190173416797 0.1976765591958326 -0.125 +0.1887258433261011 1.364456022826807 -0.125 +1.802078995274414 0.6284328232099314 -0.125 +1.590725043463949 0.7539048973299134 -0.125 +1.105763142583954 1.769540286487444 -0.125 +0.4144306176535082 1.231547760715323 -0.125 +1.802078995274414 0.8784328232099314 -0.125 +1.591154919170619 0.9937640395612282 -0.125 +1.106970860836924 0.2235284352683914 -0.125 +1.242877647953692 0.4367596520456789 -0.125 +1.231147940662183 0.7101629529997693 -0.125 +1.424552835306303 0.5887032908972694 -0.125 +0.7451977718446767 0.4497950567291986 -0.125 +0.609190173416797 0.1976765591958326 -0.125 +0.6187936625999561 1.818495493103577 -0.125 +0.7541932892633059 1.564008495192923 -0.125 +1.37870389382508 1.783200993052404 -0.125 +0.8173335599623697 1.368144155017597 -0.125 +0.7761885484075208 1.119044812086927 -0.125 +1.001124901884153 1.116980880228591 -0.125 +1.397320148819829 0.8753645594324133 -0.125 +1.0606378271241 1.405832145980656 -0.125 +1.234467036409034 1.552741279539848 -0.125 +1.38419883172582 1.121597994528091 -0.125 +0.8687936625999561 1.818495493103577 -0.125 +1.385906787116768 0.2132312167772875 -0.125 +0.3660254037844387 0.3660254037844387 -0.125 +1.633974596215561 0.3660254037844387 -0.125 +1.633974596215561 1.633974596215561 -0.125 +0.1946445340993805 0.6090233026287186 -0.125 +0.3463330267237387 1.640485872257382 -0.125 +0.9929784592648041 0.4756469328017575 -0.125 +0.9974975564250362 1.601696486155981 -0.125 +1.816987298107781 1.566987298107781 -0.125 +1.605944976288185 1.44165355641339 -0.125 +0.1648250939978902 1.57199380498323 -0.125 +0.3702337760519496 1.432948090100959 -0.125 +1.164432122130548 0.9912814635228204 -0.125 +0.5699593894359081 1.113505397047191 -0.125 +0.7442141700683604 0.9027724759544199 -0.125 +0.5388991492078814 0.8554369617873941 -0.125 +0.7859671533877393 0.6584773143291105 -0.125 +0.9691505235449926 0.900708544096083 -0.125 +1.619066293382194 0.5614455251021508 -0.125 +1.816987298107781 0.4330127018922194 -0.125 +0.9911627692473033 1.515053288576789 -0.125 +0.5336187565978462 1.640489298086808 -0.125 +1.566987298107781 1.816987298107781 -0.125 +1.445691191932861 1.600188291160184 -0.125 +0.5422028753090165 0.3806892610880519 -0.125 +1.566987298107781 0.1830127018922193 -0.125 +1.452894085224548 0.3962439186695068 -0.125 +0.4330127018922194 0.1830127018922193 -0.125 +0.3776572359915998 0.542036004520938 -0.125 +0.1830127018922193 0.4330127018922194 -0.125 +0.4315079327258485 1.818492067274152 -0.125 +1.417779817721286 1.3985322094037 -0.125 +0.6111044009907569 1.362604739977861 -0.125 +0.5806521325272602 0.6111418001620847 -0.125 +0.5919085934999009 0.7819898628392643 -0.125 +0.5669075593891983 1.564005069363497 -0.125 +1.035866342076627 0.6195900335730319 -0.125 +1.223945047370495 1.280132729274886 -0.125 +3 2 0 80 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +1.355763142583954 1.519540286487444 -0.625 +1.18193393329902 1.372631152928252 -0.625 +1.345241153545415 1.246931736222482 -0.625 +0.3457031249999999 1.06680492400583 -0.7729113006627798 +0.571407899327407 1.271388612888516 -0.625 +0.2257047743274071 1.029599786894346 -0.3979113006627798 +0.1946445340993805 0.771531351634549 -0.3979113006627798 +0.5403476590993803 1.013320177628719 -0.625 +0.9514662675839534 1.423837161487444 -0.625 +0.7582111740058302 1.654296875 -0.7729113006627798 +1.018271191589784 1.769540286487444 -0.3979113006627798 +1.200756703379289 1.769540286487444 -0.3899935607953357 +0.9406966857953356 1.654296875 -0.7649935607953358 +1.539075923896205 1.365331216351297 -0.625 +0.7776370582990197 1.276928027928252 -0.625 +0.7456626799598594 1.060655691795745 -0.625 +0.940944278545415 1.151228611222482 -0.625 +1.789075923896205 1.027839265357127 -0.3979113006627798 +1.789075923896205 1.210324777146632 -0.3899935607953357 +1.625 0.7875080490058303 -0.7729113006627798 +1.625 0.9699935607953356 -0.7649935607953358 +1.414075923896205 0.9903312163512967 -0.625 +0.3457031249999999 1.249290435795336 -0.7649935607953358 +0.2257047743274071 1.212085298683851 -0.3899935607953357 +1.788646048189535 0.7879801231258123 -0.3979113006627798 +1.413646048189535 0.750472074119982 -0.625 +0.7716982224226274 0.1976765591958326 -0.3979113006627798 +1.019478909842755 0.2235284352683914 -0.3979113006627798 +1.03750804900583 0.3749999999999999 -0.7729113006627798 +1.231970860836924 0.5985284352683914 -0.625 +0.984190173416797 0.5726765591958326 -0.625 +0.7813017116057863 1.818495493103577 -0.3979113006627798 +0.7144967875999559 1.472792368103577 -0.625 +1.20196442163226 0.2235284352683914 -0.3899935607953357 +1.219993560795336 0.3749999999999999 -0.7649935607953358 +1.02495955495986 0.7813588167957444 -0.625 +0.28125 0.6937580490058304 -0.7729113006627798 +0.4758945340993805 0.6402733026287188 -0.625 +1.220241153545415 0.8719317362224819 -0.625 +1.344993560795336 1.75 -0.7649935607953358 +0.6937580490058304 0.2812500000000001 -0.7729113006627798 +0.640440173416797 0.4789265591958327 -0.625 +0.6812095549598596 0.6876088167957446 -0.625 +1.441987298107781 0.5580127018922192 -0.625 +0.4642627018922193 0.4642627018922195 -0.625 +1.75 1.344993560795336 -0.7649935607953358 +1.566987298107781 1.566987298107781 -0.625 +0.5272110577258484 1.472788942274152 -0.625 +1.625 0.5687513415009716 -0.8026810336901542 +1.816987298107781 0.376764043393191 -0.4276810336901543 +1.788646048189535 0.5692234156209537 -0.4276810336901543 +1.432498926799223 0.3749999999999999 -0.8013614103789136 +1.414469787636147 0.2235284352683914 -0.4263614103789136 +1.624486224907003 0.1830127018922193 -0.4263614103789136 +0.5625450041009278 1.818495493103577 -0.4276810336901543 +0.5394544665009716 1.654296875 -0.8026810336901543 +0.3752592742268203 1.818492067274152 -0.4276810336901543 +0.4750013415009717 0.2812500000000001 -0.8026810336901543 +0.5529415149177688 0.1976765591958326 -0.4276810336901543 +0.28125 0.4750013415009718 -0.8026810336901542 +0.1946445340993805 0.5527746441296904 -0.4276810336901543 +0.376764043393191 0.1830127018922194 -0.4276810336901543 +0.1830127018922193 0.376764043393191 -0.4276810336901543 +1.789075923896205 1.422830143150519 -0.4263614103789136 +1.75 1.557498926799223 -0.8013614103789136 +1.413262069383176 1.769540286487444 -0.4263614103789136 +1.557498926799223 1.75 -0.8013614103789136 +1.816987298107781 1.624486224907003 -0.4263614103789136 +1.807498926799223 0.1937513415009717 -0.6040424440690679 +1.624486224907003 1.816987298107781 -0.4263614103789136 +0.1815079327258486 1.413485628069487 -0.3899935607953357 +0.1937513415009717 1.807498926799223 -0.6040424440690679 +0.3457031249999999 1.461795801799223 -0.8013614103789136 +0.1815079327258486 1.625990994073374 -0.4263614103789136 +0.1937513415009717 0.1937513415009717 -0.6053620673803086 +0.1830127018922193 0.1830127018922194 -0.4181749014781456 +1.816987298107781 0.1830127018922193 -0.4181749014781456 +1.816987298107781 1.816987298107781 -0.4181749014781456 +0.1815079327258486 1.818492067274152 -0.4181749014781456 +1.807498926799223 1.807498926799223 -0.6027228207578272 +$EndNodes +$Elements +13 548 1 548 +2 1 9 16 +1 1 2 99 13 101 102 +2 22 1 99 25 102 103 +3 2 14 99 17 104 101 +4 3 4 97 21 105 106 +5 16 3 97 20 106 107 +6 4 24 97 28 108 105 +7 14 15 100 18 109 110 +8 14 22 99 111 103 104 +9 22 14 100 111 110 112 +10 15 16 98 19 113 114 +11 23 15 98 115 114 116 +12 15 23 100 115 117 109 +13 24 16 97 118 107 108 +14 16 24 98 118 119 113 +15 23 22 100 26 112 117 +16 24 23 98 27 116 119 +2 2 9 16 +17 5 121 6 124 125 29 +18 38 121 5 126 124 41 +19 6 121 30 125 127 33 +20 7 120 8 128 129 37 +21 32 120 7 130 128 36 +22 8 120 40 129 131 44 +23 30 122 31 132 133 34 +24 30 121 38 127 126 134 +25 38 122 30 135 132 134 +26 31 123 32 136 137 35 +27 31 122 39 133 138 139 +28 39 123 31 140 136 139 +29 40 120 32 131 130 141 +30 32 123 40 137 142 141 +31 39 122 38 138 135 42 +32 40 123 39 142 140 43 +2 3 9 16 +33 2 1 144 13 147 148 +34 1 45 144 48 149 147 +35 52 2 144 55 148 150 +36 5 6 143 29 151 152 +37 47 5 143 51 152 153 +38 6 54 143 58 154 151 +39 45 46 145 49 155 156 +40 45 52 144 157 150 149 +41 52 45 145 157 156 158 +42 46 47 146 50 159 160 +43 46 53 145 161 162 155 +44 53 46 146 161 160 163 +45 54 47 143 164 153 154 +46 47 54 146 164 165 159 +47 53 52 145 56 158 162 +48 54 53 146 57 163 165 +2 4 9 16 +49 3 167 4 170 171 21 +50 66 167 3 172 170 69 +51 4 167 59 171 173 62 +52 8 168 7 174 175 37 +53 7 168 68 175 176 72 +54 61 168 8 177 174 65 +55 59 169 60 178 179 63 +56 59 167 66 173 172 180 +57 66 169 59 181 178 180 +58 60 166 61 182 183 64 +59 67 166 60 184 182 185 +60 60 169 67 179 186 185 +61 61 166 68 183 187 188 +62 68 168 61 176 177 188 +63 67 169 66 186 181 70 +64 68 166 67 187 184 71 +2 5 9 44 +65 1 22 201 25 204 205 +66 45 1 201 48 205 206 +67 24 4 202 28 207 208 +68 4 59 202 62 209 207 +69 38 5 199 41 210 211 +70 5 47 199 51 212 210 +71 8 40 200 44 213 214 +72 61 8 200 65 214 215 +73 22 23 195 26 216 217 +74 22 195 201 217 218 204 +75 23 24 191 27 219 220 +76 23 191 195 220 221 216 +77 191 24 202 219 208 222 +78 39 38 192 42 223 224 +79 192 38 199 223 211 225 +80 40 39 189 43 226 227 +81 189 39 192 226 224 228 +82 40 189 200 227 229 213 +83 46 45 190 49 230 231 +84 190 45 201 230 206 232 +85 47 46 196 50 233 234 +86 46 190 196 231 235 233 +87 47 196 199 234 236 212 +88 59 60 197 63 237 238 +89 59 197 202 238 239 209 +90 60 61 194 64 240 241 +91 60 194 197 241 242 237 +92 194 61 200 240 215 243 +93 189 192 193 228 244 245 +94 189 193 194 245 246 247 +95 189 194 200 247 243 229 +96 195 190 201 248 232 218 +97 190 195 203 248 249 250 +98 196 190 203 235 250 251 +99 195 191 203 221 252 249 +100 191 197 198 253 254 255 +101 197 191 202 253 222 239 +102 191 198 203 255 256 252 +103 193 192 196 244 257 258 +104 196 192 199 257 225 236 +105 194 193 198 246 259 260 +106 193 196 203 258 251 261 +107 198 193 203 259 261 256 +108 197 194 198 242 260 254 +2 6 9 42 +109 2 273 14 276 277 17 +110 52 273 2 278 276 55 +111 16 275 3 279 280 20 +112 3 275 66 280 281 69 +113 30 274 6 282 283 33 +114 6 274 54 283 284 58 +115 7 272 32 285 286 36 +116 68 272 7 287 285 72 +117 14 265 15 288 289 18 +118 14 273 265 277 290 288 +119 15 270 16 291 292 19 +120 265 270 15 293 291 289 +121 270 275 16 294 279 292 +122 31 266 30 295 296 34 +123 266 274 30 297 282 296 +124 32 263 31 298 299 35 +125 263 266 31 300 295 299 +126 32 272 263 286 301 298 +127 53 269 52 302 303 56 +128 269 273 52 304 278 303 +129 54 262 53 305 306 57 +130 262 269 53 307 302 306 +131 54 274 262 284 308 305 +132 66 264 67 309 310 70 +133 66 275 264 281 311 309 +134 67 268 68 312 313 71 +135 264 268 67 314 312 310 +136 268 272 68 315 287 313 +137 266 267 262 316 317 318 +138 262 274 266 308 297 318 +139 267 269 262 319 307 317 +140 263 267 266 320 316 300 +141 263 268 267 321 322 320 +142 263 272 268 301 315 321 +143 267 268 264 322 314 323 +144 264 271 267 324 325 323 +145 270 271 264 326 324 327 +146 264 275 270 311 294 327 +147 269 271 265 328 329 330 +148 265 273 269 290 304 330 +149 265 271 270 329 326 293 +150 267 271 269 325 328 319 +2 7 9 16 +151 1 22 333 25 335 336 +152 73 1 333 75 336 337 +153 24 4 334 28 338 339 +154 4 76 334 78 340 338 +155 9 73 333 74 337 341 +156 79 9 333 80 341 342 +157 76 10 334 77 343 340 +158 10 79 334 81 344 343 +159 22 23 331 26 345 346 +160 22 331 333 346 347 335 +161 23 24 332 27 348 349 +162 331 23 332 345 349 350 +163 332 24 334 348 339 351 +164 79 331 332 352 350 353 +165 331 79 333 352 342 347 +166 79 332 334 353 351 344 +2 8 9 16 +167 5 356 38 358 359 41 +168 82 356 5 360 358 84 +169 40 357 8 361 362 44 +170 8 357 85 362 363 87 +171 11 356 82 364 360 83 +172 88 356 11 365 364 89 +173 85 357 12 363 366 86 +174 12 357 88 366 367 90 +175 38 354 39 368 369 42 +176 38 356 354 359 370 368 +177 39 355 40 371 372 43 +178 354 355 39 373 371 369 +179 355 357 40 374 361 372 +180 88 355 354 375 373 376 +181 354 356 88 370 365 376 +182 88 357 355 367 374 375 +2 9 9 16 +183 45 1 379 48 381 382 +184 1 73 379 75 383 381 +185 5 47 380 51 384 385 +186 82 5 380 84 385 386 +187 73 9 379 74 387 383 +188 9 91 379 92 388 387 +189 11 82 380 83 386 389 +190 91 11 380 93 389 390 +191 46 45 377 49 391 392 +192 377 45 379 391 382 393 +193 47 46 378 50 394 395 +194 46 377 378 392 396 394 +195 47 378 380 395 397 384 +196 377 91 378 398 399 396 +197 91 377 379 398 393 388 +198 378 91 380 399 390 397 +2 10 9 16 +199 59 402 4 404 405 62 +200 4 402 76 405 406 78 +201 8 403 61 407 408 65 +202 85 403 8 409 407 87 +203 76 402 10 406 410 77 +204 10 402 94 410 411 95 +205 12 403 85 412 409 86 +206 94 403 12 413 412 96 +207 60 400 59 414 415 63 +208 400 402 59 416 404 415 +209 61 401 60 417 418 64 +210 60 401 400 418 419 414 +211 61 403 401 408 420 417 +212 400 401 94 419 421 422 +213 94 402 400 411 416 422 +214 401 403 94 420 413 421 +2 11 9 14 +215 9 79 425 80 427 428 +216 91 9 425 92 428 429 +217 79 10 426 81 430 431 +218 10 94 426 95 432 430 +219 88 11 424 89 433 434 +220 11 91 424 93 435 433 +221 12 88 423 90 436 437 +222 94 12 423 96 437 438 +223 425 79 426 427 431 439 +224 423 88 424 436 434 440 +225 424 91 425 435 429 441 +226 94 423 426 438 442 432 +227 423 424 426 440 443 442 +228 424 425 426 441 439 443 +3 1 11 180 +229 38 39 192 122 42 224 223 135 444 138 +230 39 40 189 123 43 227 226 140 445 142 +231 31 32 123 263 35 137 136 299 446 298 +232 100 15 14 265 109 18 110 447 288 289 +233 46 45 145 190 49 156 155 231 448 230 +234 61 60 194 166 64 241 240 183 449 182 +235 24 23 191 98 27 220 219 119 450 116 +236 16 98 270 15 113 451 292 19 291 114 +237 46 47 196 146 50 234 233 160 452 159 +238 122 266 30 31 453 296 132 133 34 295 +239 23 22 195 100 26 217 216 117 454 112 +240 3 97 275 16 106 455 280 20 279 107 +241 2 14 273 99 17 277 276 101 456 104 +242 54 6 143 274 58 151 154 284 457 283 +243 6 121 274 30 125 458 283 33 282 127 +244 272 7 32 120 285 36 286 459 130 128 +245 272 68 7 168 287 72 285 460 175 176 +246 60 59 197 169 63 238 237 179 461 178 +247 40 8 200 120 44 214 213 131 462 129 +248 5 38 199 121 41 211 210 124 463 126 +249 45 1 144 201 48 147 149 206 464 205 +250 1 22 99 201 25 103 102 205 465 204 +251 59 4 202 167 62 207 209 173 466 171 +252 269 52 145 53 303 158 467 302 162 56 +253 264 169 66 67 468 181 309 310 70 186 +254 4 24 202 97 28 208 207 105 469 108 +255 5 47 143 199 51 153 152 210 470 212 +256 262 54 53 146 305 57 306 471 163 165 +257 8 61 200 168 65 215 214 174 472 177 +258 166 67 268 68 184 312 473 187 313 71 +259 275 167 3 66 474 170 280 281 69 172 +260 144 273 2 52 475 276 148 150 55 278 +261 120 272 7 168 459 285 128 476 175 460 +262 144 2 273 99 148 276 475 477 456 101 +263 143 6 121 274 151 125 478 457 458 283 +264 168 8 120 200 174 129 476 472 462 214 +265 99 144 1 201 477 147 102 465 205 464 +266 121 199 5 143 463 210 124 478 152 470 +267 167 4 202 97 171 207 466 479 469 105 +268 97 167 3 275 479 170 106 455 280 474 +269 32 123 263 40 137 446 298 141 480 142 +270 263 123 189 40 446 445 481 480 227 142 +271 189 123 31 39 445 136 482 226 139 140 +272 265 23 270 195 483 484 293 485 486 216 +273 53 145 190 46 162 448 487 161 231 155 +274 263 123 31 189 446 136 299 481 482 445 +275 98 270 24 16 451 488 119 113 118 292 +276 266 38 192 122 489 223 490 453 444 135 +277 266 30 38 122 296 134 489 453 135 132 +278 60 166 67 194 182 184 185 241 491 449 +279 23 270 15 265 484 291 115 483 289 293 +280 270 24 191 98 488 219 492 451 450 119 +281 100 23 15 265 117 115 109 447 289 483 +282 122 192 266 39 444 490 453 138 493 224 +283 189 31 266 39 482 295 494 226 493 139 +284 266 192 189 39 490 228 494 493 226 224 +285 265 100 23 195 447 117 483 485 216 454 +286 196 53 190 46 495 487 235 233 231 161 +287 31 122 266 39 133 453 295 139 493 138 +288 196 146 53 46 452 163 495 233 161 160 +289 23 191 270 195 220 492 484 216 486 221 +290 263 31 266 189 299 295 300 481 494 482 +291 262 196 193 192 496 258 497 498 244 257 +292 269 145 190 53 467 448 499 302 487 162 +293 52 145 190 269 158 448 500 303 499 467 +294 169 66 197 264 181 501 461 468 502 309 +295 268 166 68 61 473 187 313 503 188 183 +296 198 264 271 267 504 324 505 506 325 323 +297 266 192 193 189 490 244 507 494 245 228 +298 198 264 267 268 504 323 506 508 322 314 +299 268 166 61 194 473 183 503 509 240 449 +300 270 191 23 98 492 220 484 451 116 450 +301 263 266 193 189 300 507 510 481 245 494 +302 60 67 197 194 185 511 237 241 242 491 +303 262 54 146 47 305 165 471 512 159 164 +304 273 144 99 201 475 477 456 513 465 464 +305 143 121 199 274 478 463 470 457 514 458 +306 168 120 272 200 476 459 460 472 515 462 +307 14 100 265 195 110 447 288 516 485 454 +308 202 167 97 275 466 479 469 517 455 474 +309 197 169 264 67 461 468 502 511 310 186 +310 23 270 98 15 484 451 116 115 114 291 +311 268 67 166 194 312 184 473 509 449 491 +312 196 262 146 47 496 471 452 234 159 512 +313 100 14 22 195 110 111 112 454 217 516 +314 53 190 269 196 487 499 302 495 518 235 +315 146 53 262 196 163 306 471 452 496 495 +316 197 169 67 60 461 186 511 237 185 179 +317 67 268 197 194 312 519 511 491 242 509 +318 190 45 145 52 230 156 448 500 158 157 +319 200 32 263 40 520 298 521 213 480 141 +320 189 200 263 40 229 521 481 227 480 213 +321 270 275 24 16 294 522 488 292 118 279 +322 275 24 202 270 522 208 517 294 523 488 +323 268 197 264 67 519 502 314 312 310 511 +324 266 193 267 263 507 524 316 300 320 510 +325 32 200 263 272 520 521 298 286 301 515 +326 191 195 203 271 221 249 252 525 526 527 +327 269 262 53 196 307 306 302 518 495 496 +328 66 197 59 169 501 238 180 181 178 461 +329 203 271 269 267 526 328 528 529 319 325 +330 198 191 203 271 255 252 256 505 526 525 +331 199 38 192 266 211 223 225 530 490 489 +332 38 274 30 266 531 282 134 489 296 297 +333 24 191 202 270 219 222 208 488 523 492 +334 38 199 274 266 211 514 531 489 297 530 +335 268 264 197 194 314 502 519 509 242 532 +336 66 275 197 264 281 533 501 309 502 311 +337 272 268 68 200 315 313 287 515 534 535 +338 269 190 203 196 499 250 528 518 251 235 +339 200 268 68 61 535 313 534 215 188 503 +340 268 198 264 194 508 504 314 509 532 260 +341 52 190 273 269 500 536 278 303 304 499 +342 54 199 262 274 537 538 305 284 308 514 +343 190 273 201 45 536 513 232 230 206 539 +344 273 14 265 195 277 288 290 540 485 516 +345 274 121 38 30 458 126 531 282 134 127 +346 201 99 14 22 465 104 541 204 111 103 +347 199 54 262 47 537 305 538 212 512 164 +348 200 120 32 40 462 130 520 213 141 131 +349 14 201 22 195 541 204 111 516 217 218 +350 61 200 268 194 215 535 503 240 509 243 +351 197 202 275 66 239 517 533 501 281 542 +352 263 268 200 189 321 535 521 481 229 543 +353 273 201 195 190 513 218 540 536 248 232 +354 143 199 54 274 470 537 154 457 284 514 +355 168 272 68 200 460 287 176 472 534 515 +356 99 14 273 201 104 277 456 465 513 541 +357 273 144 201 45 475 464 513 539 206 149 +358 120 32 272 200 130 286 459 462 515 520 +359 38 199 121 274 211 463 126 531 458 514 +360 14 273 201 195 277 513 541 516 218 540 +361 47 196 262 199 234 496 512 212 538 236 +362 191 198 264 271 255 504 544 525 324 505 +363 54 47 199 143 164 212 537 154 470 153 +364 45 190 273 52 230 536 539 157 278 500 +365 270 191 264 271 492 544 327 326 324 525 +366 194 200 268 189 243 535 509 247 543 229 +367 200 68 168 61 534 176 472 215 177 188 +368 202 197 59 66 239 238 209 542 180 501 +369 198 197 264 194 254 502 504 260 532 242 +370 263 268 272 200 321 315 301 521 515 535 +371 202 167 275 66 466 474 517 542 281 172 +372 265 273 195 269 290 540 485 330 545 304 +373 273 144 45 52 475 149 539 278 157 150 +374 190 195 273 269 248 540 536 499 304 545 +375 271 265 203 269 329 546 526 328 528 330 +376 167 202 59 66 466 209 173 172 180 542 +377 191 197 202 264 253 239 222 544 547 502 +378 192 262 196 199 498 496 257 225 236 538 +379 97 24 202 275 108 208 469 455 517 522 +380 275 97 24 16 455 108 522 279 118 107 +381 191 198 197 264 255 254 253 544 502 504 +382 203 265 195 269 546 485 249 528 545 330 +383 271 203 198 267 526 256 505 325 506 529 +384 190 203 195 269 250 249 248 499 545 528 +385 203 195 265 271 249 485 546 526 329 527 +386 275 202 197 264 517 239 533 311 502 547 +387 267 203 198 193 529 256 506 524 259 261 +388 167 3 4 97 170 21 171 479 105 106 +389 7 8 120 168 37 129 128 175 476 174 +390 121 5 6 143 124 29 125 478 151 152 +391 144 1 2 99 147 13 148 477 101 102 +392 270 195 271 265 486 527 326 293 329 485 +393 271 195 270 191 527 486 326 525 492 221 +394 262 193 203 267 497 261 548 317 529 524 +395 203 193 262 196 261 497 548 251 496 258 +396 203 269 262 267 528 307 548 529 317 319 +397 262 269 203 196 307 528 548 496 251 518 +398 193 266 262 192 507 318 497 244 498 490 +399 262 266 193 267 318 507 497 317 524 316 +400 202 270 264 275 523 327 547 517 311 294 +401 264 270 202 191 327 523 547 544 222 492 +402 189 268 193 263 543 549 245 481 510 321 +403 189 193 268 194 245 549 543 247 509 246 +404 267 193 268 263 524 549 322 320 321 510 +405 199 266 262 274 530 318 538 514 308 297 +406 262 266 199 192 318 530 538 498 225 490 +407 193 268 198 267 549 508 259 524 506 322 +408 193 198 268 194 259 508 549 246 509 260 +3 2 11 140 +409 194 193 198 423 246 259 260 550 551 552 +410 331 426 191 195 553 554 555 556 221 557 +411 194 426 400 401 558 559 560 561 419 562 +412 189 193 194 423 245 246 247 563 550 552 +413 426 198 203 193 564 256 565 566 261 259 +414 354 189 355 424 567 568 373 569 570 571 +415 331 426 332 191 553 572 350 555 573 554 +416 192 189 354 424 228 567 574 575 569 571 +417 377 190 196 424 576 235 577 578 579 580 +418 426 400 197 194 559 581 582 558 242 560 +419 195 426 191 203 557 554 221 249 252 565 +420 197 198 426 194 254 564 582 242 558 260 +421 196 377 424 378 577 578 579 583 584 396 +422 193 426 198 423 566 564 259 552 551 442 +423 194 198 426 423 260 564 558 550 442 551 +424 196 190 203 424 235 250 251 579 585 580 +425 426 331 425 195 553 586 439 557 587 556 +426 189 192 193 424 228 244 245 571 588 575 +427 198 191 426 203 255 554 564 256 565 252 +428 401 194 426 423 561 558 562 589 442 550 +429 190 377 425 424 576 590 591 580 441 578 +430 203 195 425 190 249 587 592 250 591 248 +431 425 426 195 203 439 557 587 592 249 565 +432 199 196 192 424 236 257 225 593 575 579 +433 195 201 425 190 218 594 587 248 591 232 +434 423 355 189 424 595 568 563 440 571 570 +435 189 194 200 423 247 243 229 563 596 550 +436 426 191 197 202 554 253 582 597 239 222 +437 39 192 189 354 224 228 226 369 567 574 +438 424 192 199 356 575 225 593 598 599 600 +439 424 354 192 356 569 574 575 598 600 370 +440 198 426 191 197 564 554 255 254 253 582 +441 425 203 190 424 592 250 591 441 580 585 +442 23 331 332 191 345 350 349 220 573 555 +443 423 189 193 424 563 245 552 440 588 571 +444 355 39 189 354 371 226 568 373 567 369 +445 191 23 331 195 220 345 555 221 556 216 +446 60 197 400 194 237 581 414 241 560 242 +447 424 199 196 380 593 236 579 601 602 603 +448 426 94 400 401 432 422 559 562 419 421 +449 197 402 426 202 604 605 582 239 597 606 +450 400 402 426 197 416 605 559 581 582 604 +451 400 60 194 401 414 241 560 419 561 418 +452 190 377 196 46 576 577 235 231 233 392 +453 378 424 196 380 584 579 583 397 602 601 +454 46 377 196 378 392 577 233 394 583 396 +455 379 377 425 190 393 590 607 608 591 576 +456 193 196 203 424 258 251 261 588 585 579 +457 333 425 331 195 609 586 347 610 556 587 +458 426 79 331 332 431 352 553 572 350 353 +459 355 88 354 424 375 376 373 570 569 434 +460 201 379 425 190 611 607 594 232 591 608 +461 196 193 192 424 258 244 257 579 575 588 +462 425 333 201 195 609 612 594 587 218 610 +463 357 355 189 423 374 568 613 614 563 595 +464 194 401 403 423 561 420 615 550 616 589 +465 357 189 200 423 613 229 617 614 596 563 +466 380 424 199 356 601 593 603 618 599 598 +467 91 424 377 378 435 578 398 399 396 584 +468 403 200 194 423 619 243 615 616 550 596 +469 193 203 426 424 261 565 566 588 443 585 +470 332 24 191 202 348 219 573 620 222 208 +471 402 334 426 202 621 622 605 606 597 623 +472 423 193 426 424 552 566 442 440 443 588 +473 426 94 401 423 432 421 562 442 589 438 +474 79 331 425 426 352 586 427 431 439 553 +475 38 192 354 356 223 574 368 359 370 600 +476 192 38 199 356 223 211 225 600 599 359 +477 355 423 88 424 595 436 375 570 434 440 +478 425 426 203 424 439 565 592 441 585 443 +479 380 11 424 356 389 433 601 618 598 364 +480 377 425 424 91 590 441 578 398 435 429 +481 425 333 379 201 609 624 607 594 611 612 +482 424 11 88 356 433 89 434 598 365 364 +483 91 11 424 380 93 433 435 390 601 389 +484 24 332 334 202 348 351 339 208 623 620 +485 94 10 426 402 95 430 432 411 605 410 +486 426 10 79 334 430 81 431 622 344 343 +487 39 38 192 354 42 223 224 369 574 368 +488 88 354 424 356 376 569 434 365 598 370 +489 425 9 333 79 428 341 609 427 342 80 +490 379 9 425 91 387 428 607 388 429 92 +491 378 91 424 380 399 435 584 397 601 390 +492 379 377 190 45 393 576 608 382 230 391 +493 60 61 194 401 64 240 241 418 561 417 +494 40 39 189 355 43 226 227 372 568 371 +495 423 12 357 88 437 366 614 436 367 90 +496 403 94 12 423 413 96 412 616 437 438 +497 334 426 10 402 622 430 343 621 410 605 +498 23 24 191 332 27 219 220 349 573 348 +499 426 94 402 400 432 411 605 559 416 422 +500 46 45 190 377 49 230 231 392 576 391 +501 59 402 400 197 404 416 415 238 581 604 +502 22 333 331 195 335 347 346 217 556 610 +503 379 73 1 201 383 75 381 611 205 625 +504 73 333 1 201 337 336 75 625 205 612 +505 82 199 5 356 626 210 84 360 358 599 +506 47 46 196 378 50 233 234 395 583 394 +507 8 200 85 357 214 627 87 362 363 617 +508 403 8 200 85 407 214 619 409 627 87 +509 380 199 5 82 603 210 385 386 84 626 +510 79 426 334 332 431 622 344 353 351 572 +511 402 4 76 202 405 78 406 606 628 207 +512 190 201 379 45 232 611 608 230 382 206 +513 4 334 76 202 338 340 78 207 628 623 +514 197 59 402 202 238 404 604 239 606 209 +515 200 403 357 423 619 629 617 596 614 616 +516 22 23 195 331 26 216 217 346 556 345 +517 22 201 333 195 204 612 335 217 610 218 +518 331 425 333 79 586 609 347 352 342 427 +519 425 377 379 91 590 393 607 429 388 398 +520 60 59 400 197 63 415 414 237 581 238 +521 45 1 201 379 48 205 206 382 611 381 +522 5 38 356 199 41 359 358 210 599 211 +523 1 22 201 333 25 204 205 336 612 335 +524 4 59 202 402 62 209 207 405 606 404 +525 423 357 355 88 614 374 595 436 375 367 +526 403 401 94 423 420 421 413 616 438 589 +527 8 40 200 357 44 213 214 362 617 361 +528 333 73 379 201 337 383 624 612 611 625 +529 82 380 199 356 386 603 626 360 599 618 +530 402 76 334 202 406 340 621 606 623 628 +531 85 200 403 357 627 619 409 363 629 617 +532 24 4 202 334 28 207 208 339 623 338 +533 5 47 199 380 51 212 210 385 603 384 +534 61 8 200 403 65 214 215 408 619 407 +535 379 9 333 425 387 341 624 607 609 428 +536 73 9 333 379 74 341 337 383 624 387 +537 357 403 12 423 629 412 366 614 437 616 +538 76 334 10 402 340 343 77 406 410 621 +539 85 403 12 357 409 412 86 363 366 629 +540 82 11 380 356 83 389 386 360 618 364 +541 194 403 61 200 615 408 240 243 215 619 +542 61 403 194 401 408 615 240 417 561 420 +543 196 380 47 378 602 384 234 583 395 397 +544 47 380 196 199 384 602 234 212 236 603 +545 202 332 426 191 620 572 597 222 554 573 +546 202 426 332 334 597 572 620 623 351 622 +547 189 357 40 355 613 361 227 568 372 374 +548 40 357 189 200 361 613 227 213 229 617 +$EndElements diff --git a/tests/fixtures/coarse-o2.puml.h5 b/tests/fixtures/coarse-o2.puml.h5 new file mode 100644 index 0000000..033f551 Binary files /dev/null and b/tests/fixtures/coarse-o2.puml.h5 differ diff --git a/tests/fixtures/coarse-offset-v41.msh b/tests/fixtures/coarse-offset-v41.msh new file mode 100644 index 0000000..29d0fb3 --- /dev/null +++ b/tests/fixtures/coarse-offset-v41.msh @@ -0,0 +1,877 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +12 20 11 2 +1 0 0 -0.25 0 +2 0 0 0 0 +3 0 2 0 0 +4 0 2 -0.25 0 +5 2 0 -0.25 0 +6 2 0 0 0 +7 2 2 0 0 +8 2 2 -0.25 0 +9 0 0 -1 0 +10 0 2 -1 0 +11 2 0 -1 0 +12 2 2 -1 0 +1 -1e-07 -1e-07 -0.2500001 1e-07 1e-07 1.000000000028756e-07 0 2 1 -2 +2 -1e-07 -9.999999994736442e-08 -1e-07 1e-07 2.0000001 1e-07 0 2 2 -3 +3 -1e-07 1.9999999 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 0 2 4 -3 +4 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 -0.2499999 0 2 1 -4 +5 1.9999999 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 0 2 5 -6 +6 1.9999999 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 0 2 6 -7 +7 1.9999999 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 0 2 8 -7 +8 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 5 -8 +9 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 -0.2499999 0 2 1 -5 +10 -9.999999994736442e-08 -1e-07 -1e-07 2.0000001 1e-07 1e-07 0 2 2 -6 +11 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 4 -8 +12 -9.999999994736442e-08 1.9999999 -1e-07 2.0000001 2.0000001 1e-07 0 2 3 -7 +13 -1e-07 -1e-07 -1.0000001 1e-07 1e-07 -0.2499999 0 2 9 -1 +14 -1e-07 1.9999999 -1.0000001 1e-07 2.0000001 -0.2499999 0 2 10 -4 +15 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.9999999000000001 0 2 9 -10 +16 1.9999999 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 0 2 11 -5 +17 1.9999999 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 0 2 12 -8 +18 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 11 -12 +19 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.9999999000000001 0 2 9 -11 +20 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 10 -12 +1 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 1 105 4 -1 4 3 -2 +2 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -5 8 7 -6 +3 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 1 105 4 -9 1 10 -5 +4 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -11 3 12 -7 +5 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 1 103 4 -4 9 8 -11 +6 -9.999999994736442e-08 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 1 101 4 -2 10 6 -12 +7 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.2499999 1 105 4 -13 15 14 -4 +8 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -16 18 17 -8 +9 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 1 105 4 -19 13 9 -16 +10 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -20 14 11 -17 +11 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 1 105 4 -15 19 18 -20 +1 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 1 6 -1 2 -3 4 -5 6 +2 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 2 6 -7 8 -9 10 -11 5 +$EndEntities +$Nodes +41 109 1001 1109 +0 1 0 1 +1001 +0 0 -0.25 +0 2 0 1 +1002 +0 0 0 +0 3 0 1 +1003 +0 2 0 +0 4 0 1 +1004 +0 2 -0.25 +0 5 0 1 +1005 +2 0 -0.25 +0 6 0 1 +1006 +2 0 0 +0 7 0 1 +1007 +2 2 0 +0 8 0 1 +1008 +2 2 -0.25 +0 9 0 1 +1009 +0 0 -1 +0 10 0 1 +1010 +0 2 -1 +0 11 0 1 +1011 +2 0 -1 +0 12 0 1 +1012 +2 2 -1 +1 2 0 3 +1013 +1014 +1015 +0 0.5 0 +0 1 0 +0 1.5 0 +1 4 0 3 +1016 +1017 +1018 +0 0.5 -0.25 +0 1 -0.25 +0 1.5 -0.25 +1 6 0 3 +1019 +1020 +1021 +2 0.5 0 +2 1 0 +2 1.5 0 +1 8 0 3 +1022 +1023 +1024 +2 0.5 -0.25 +2 1 -0.25 +2 1.5 -0.25 +1 9 0 3 +1025 +1026 +1027 +0.5 0 -0.25 +1 0 -0.25 +1.5 0 -0.25 +1 10 0 3 +1028 +1029 +1030 +0.5 0 0 +1 0 0 +1.5 0 0 +1 11 0 3 +1031 +1032 +1033 +0.5 2 -0.25 +1 2 -0.25 +1.5 2 -0.25 +1 12 0 3 +1034 +1035 +1036 +0.5 2 0 +1 2 0 +1.5 2 0 +1 13 0 1 +1037 +0 0 -0.5863498029562912 +1 14 0 1 +1038 +0 2 -0.5863498029562912 +1 15 0 1 +1039 +0 1 -1 +1 16 0 1 +1040 +2 0 -0.5863498029562912 +1 17 0 1 +1041 +2 2 -0.5863498029562912 +1 18 0 1 +1042 +2 1 -1 +1 19 0 1 +1043 +1 0 -1 +1 20 0 1 +1044 +1 2 -1 +2 1 0 4 +1045 +1046 +1047 +1048 +0 1.75 -0.125 +0 1.25 -0.125 +0 0.25 -0.125 +0 0.75 -0.125 +2 2 0 4 +1049 +1050 +1051 +1052 +2 1.75 -0.125 +2 0.25 -0.125 +2 0.75 -0.125 +2 1.25 -0.125 +2 3 0 4 +1053 +1054 +1055 +1056 +1.75 0 -0.125 +0.25 0 -0.125 +0.75 0 -0.125 +1.25 0 -0.125 +2 4 0 4 +1057 +1058 +1059 +1060 +1.25 2 -0.125 +0.25 2 -0.125 +1.75 2 -0.125 +0.75 2 -0.125 +2 5 0 15 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1.578151847792411 1.230662432702593 -0.25 +0.7183803468335941 0.3953531183916653 -0.25 +0.4514095486548143 1.234183475777031 -0.25 +1.577292096379071 0.7509441482399639 -0.25 +1.19048230709083 0.9938634724449638 -0.25 +1.211526285167907 1.539080572974888 -0.25 +0.389289068198761 0.7180466052574374 -0.25 +1.213941721673849 0.4470568705367829 -0.25 +0.7375873251999121 1.636990986207155 -0.25 +0.8638678665980398 1.245262305856504 -0.25 +1.633974596215561 0.3660254037844386 -0.25 +1.633974596215561 1.633974596215561 -0.25 +0.3660254037844386 0.3660254037844387 -0.25 +0.3630158654516971 1.636984134548303 -0.25 +0.799919109919719 0.8127176335914889 -0.25 +2 6 0 14 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1.271813574233536 0.4264624335545749 0 +1.57791535636081 1.249332516611218 0 +0.7707992533266995 1.491026004178691 0 +0.3838980770800826 0.7512620920870399 0 +1.604157990548827 0.756865646419863 0 +1.138381937170266 0.9886994546006772 0 +1.25740778765016 1.566401986104808 0 +0.7720151968557594 0.504236995066732 0 +0.3774516866522022 1.228912045653614 0 +0.6885092302170019 0.9928273183173509 0 +1.633974596215561 1.633974596215561 0 +0.3660254037844387 0.3660254037844386 0 +1.633974596215561 0.3660254037844387 0 +0.3296501879957803 1.643987609966461 0 +2 7 0 4 +1090 +1091 +1092 +1093 +0 0.8250160980116606 -0.5458226013255596 +0 1.189987121590671 -0.5299871215906714 +0 0.3875026830019433 -0.6053620673803085 +0 1.614997853598445 -0.6027228207578272 +2 8 0 4 +1094 +1095 +1096 +1097 +2 0.8250160980116606 -0.5458226013255596 +2 1.189987121590671 -0.5299871215906714 +2 0.3875026830019433 -0.6053620673803085 +2 1.614997853598445 -0.6027228207578272 +2 9 0 4 +1098 +1099 +1100 +1101 +0.8250160980116606 0 -0.5458226013255596 +1.189987121590671 0 -0.5299871215906714 +0.3875026830019435 0 -0.6053620673803086 +1.614997853598445 0 -0.6027228207578272 +2 10 0 4 +1102 +1103 +1104 +1105 +0.8250160980116606 2 -0.5458226013255596 +1.189987121590671 2 -0.5299871215906714 +0.3875026830019435 2 -0.6053620673803086 +1.614997853598445 2 -0.6027228207578272 +2 11 0 4 +1106 +1107 +1108 +1109 +1.5 1.5 -1 +1.25 0.7499999999999999 -1 +0.5625 0.5625000000000002 -1 +0.6914062499999998 1.30859375 -1 +3 1 0 0 +3 2 0 0 +$EndNodes +$Elements +13 548 1 548 +2 1 2 16 +1 1001 1002 1047 +2 1016 1001 1047 +3 1002 1013 1047 +4 1003 1004 1045 +5 1015 1003 1045 +6 1004 1018 1045 +7 1013 1014 1048 +8 1013 1016 1047 +9 1016 1013 1048 +10 1014 1015 1046 +11 1017 1014 1046 +12 1014 1017 1048 +13 1018 1015 1045 +14 1015 1018 1046 +15 1017 1016 1048 +16 1018 1017 1046 +2 2 2 16 +17 1005 1050 1006 +18 1022 1050 1005 +19 1006 1050 1019 +20 1007 1049 1008 +21 1021 1049 1007 +22 1008 1049 1024 +23 1019 1051 1020 +24 1019 1050 1022 +25 1022 1051 1019 +26 1020 1052 1021 +27 1020 1051 1023 +28 1023 1052 1020 +29 1024 1049 1021 +30 1021 1052 1024 +31 1023 1051 1022 +32 1024 1052 1023 +2 3 2 16 +33 1002 1001 1054 +34 1001 1025 1054 +35 1028 1002 1054 +36 1005 1006 1053 +37 1027 1005 1053 +38 1006 1030 1053 +39 1025 1026 1055 +40 1025 1028 1054 +41 1028 1025 1055 +42 1026 1027 1056 +43 1026 1029 1055 +44 1029 1026 1056 +45 1030 1027 1053 +46 1027 1030 1056 +47 1029 1028 1055 +48 1030 1029 1056 +2 4 2 16 +49 1003 1058 1004 +50 1034 1058 1003 +51 1004 1058 1031 +52 1008 1059 1007 +53 1007 1059 1036 +54 1033 1059 1008 +55 1031 1060 1032 +56 1031 1058 1034 +57 1034 1060 1031 +58 1032 1057 1033 +59 1035 1057 1032 +60 1032 1060 1035 +61 1033 1057 1036 +62 1036 1059 1033 +63 1035 1060 1034 +64 1036 1057 1035 +2 5 2 44 +65 1001 1016 1073 +66 1025 1001 1073 +67 1018 1004 1074 +68 1004 1031 1074 +69 1022 1005 1071 +70 1005 1027 1071 +71 1008 1024 1072 +72 1033 1008 1072 +73 1016 1017 1067 +74 1016 1067 1073 +75 1017 1018 1063 +76 1017 1063 1067 +77 1063 1018 1074 +78 1023 1022 1064 +79 1064 1022 1071 +80 1024 1023 1061 +81 1061 1023 1064 +82 1024 1061 1072 +83 1026 1025 1062 +84 1062 1025 1073 +85 1027 1026 1068 +86 1026 1062 1068 +87 1027 1068 1071 +88 1031 1032 1069 +89 1031 1069 1074 +90 1032 1033 1066 +91 1032 1066 1069 +92 1066 1033 1072 +93 1061 1064 1065 +94 1061 1065 1066 +95 1061 1066 1072 +96 1067 1062 1073 +97 1062 1067 1075 +98 1068 1062 1075 +99 1067 1063 1075 +100 1063 1069 1070 +101 1069 1063 1074 +102 1063 1070 1075 +103 1065 1064 1068 +104 1068 1064 1071 +105 1066 1065 1070 +106 1065 1068 1075 +107 1070 1065 1075 +108 1069 1066 1070 +2 6 2 42 +109 1002 1087 1013 +110 1028 1087 1002 +111 1015 1089 1003 +112 1003 1089 1034 +113 1019 1088 1006 +114 1006 1088 1030 +115 1007 1086 1021 +116 1036 1086 1007 +117 1013 1079 1014 +118 1013 1087 1079 +119 1014 1084 1015 +120 1079 1084 1014 +121 1084 1089 1015 +122 1020 1080 1019 +123 1080 1088 1019 +124 1021 1077 1020 +125 1077 1080 1020 +126 1021 1086 1077 +127 1029 1083 1028 +128 1083 1087 1028 +129 1030 1076 1029 +130 1076 1083 1029 +131 1030 1088 1076 +132 1034 1078 1035 +133 1034 1089 1078 +134 1035 1082 1036 +135 1078 1082 1035 +136 1082 1086 1036 +137 1080 1081 1076 +138 1076 1088 1080 +139 1081 1083 1076 +140 1077 1081 1080 +141 1077 1082 1081 +142 1077 1086 1082 +143 1081 1082 1078 +144 1078 1085 1081 +145 1084 1085 1078 +146 1078 1089 1084 +147 1083 1085 1079 +148 1079 1087 1083 +149 1079 1085 1084 +150 1081 1085 1083 +2 7 2 16 +151 1001 1016 1092 +152 1037 1001 1092 +153 1018 1004 1093 +154 1004 1038 1093 +155 1009 1037 1092 +156 1039 1009 1092 +157 1038 1010 1093 +158 1010 1039 1093 +159 1016 1017 1090 +160 1016 1090 1092 +161 1017 1018 1091 +162 1090 1017 1091 +163 1091 1018 1093 +164 1039 1090 1091 +165 1090 1039 1092 +166 1039 1091 1093 +2 8 2 16 +167 1005 1096 1022 +168 1040 1096 1005 +169 1024 1097 1008 +170 1008 1097 1041 +171 1011 1096 1040 +172 1042 1096 1011 +173 1041 1097 1012 +174 1012 1097 1042 +175 1022 1094 1023 +176 1022 1096 1094 +177 1023 1095 1024 +178 1094 1095 1023 +179 1095 1097 1024 +180 1042 1095 1094 +181 1094 1096 1042 +182 1042 1097 1095 +2 9 2 16 +183 1025 1001 1100 +184 1001 1037 1100 +185 1005 1027 1101 +186 1040 1005 1101 +187 1037 1009 1100 +188 1009 1043 1100 +189 1011 1040 1101 +190 1043 1011 1101 +191 1026 1025 1098 +192 1098 1025 1100 +193 1027 1026 1099 +194 1026 1098 1099 +195 1027 1099 1101 +196 1098 1043 1099 +197 1043 1098 1100 +198 1099 1043 1101 +2 10 2 16 +199 1031 1104 1004 +200 1004 1104 1038 +201 1008 1105 1033 +202 1041 1105 1008 +203 1038 1104 1010 +204 1010 1104 1044 +205 1012 1105 1041 +206 1044 1105 1012 +207 1032 1102 1031 +208 1102 1104 1031 +209 1033 1103 1032 +210 1032 1103 1102 +211 1033 1105 1103 +212 1102 1103 1044 +213 1044 1104 1102 +214 1103 1105 1044 +2 11 2 14 +215 1009 1039 1108 +216 1043 1009 1108 +217 1039 1010 1109 +218 1010 1044 1109 +219 1042 1011 1107 +220 1011 1043 1107 +221 1012 1042 1106 +222 1044 1012 1106 +223 1108 1039 1109 +224 1106 1042 1107 +225 1107 1043 1108 +226 1044 1106 1109 +227 1106 1107 1109 +228 1107 1108 1109 +3 1 4 180 +229 1022 1023 1064 1051 +230 1023 1024 1061 1052 +231 1020 1021 1052 1077 +232 1048 1014 1013 1079 +233 1026 1025 1055 1062 +234 1033 1032 1066 1057 +235 1018 1017 1063 1046 +236 1015 1046 1084 1014 +237 1026 1027 1068 1056 +238 1051 1080 1019 1020 +239 1017 1016 1067 1048 +240 1003 1045 1089 1015 +241 1002 1013 1087 1047 +242 1030 1006 1053 1088 +243 1006 1050 1088 1019 +244 1086 1007 1021 1049 +245 1086 1036 1007 1059 +246 1032 1031 1069 1060 +247 1024 1008 1072 1049 +248 1005 1022 1071 1050 +249 1025 1001 1054 1073 +250 1001 1016 1047 1073 +251 1031 1004 1074 1058 +252 1083 1028 1055 1029 +253 1078 1060 1034 1035 +254 1004 1018 1074 1045 +255 1005 1027 1053 1071 +256 1076 1030 1029 1056 +257 1008 1033 1072 1059 +258 1057 1035 1082 1036 +259 1089 1058 1003 1034 +260 1054 1087 1002 1028 +261 1049 1086 1007 1059 +262 1054 1002 1087 1047 +263 1053 1006 1050 1088 +264 1059 1008 1049 1072 +265 1047 1054 1001 1073 +266 1050 1071 1005 1053 +267 1058 1004 1074 1045 +268 1045 1058 1003 1089 +269 1021 1052 1077 1024 +270 1077 1052 1061 1024 +271 1061 1052 1020 1023 +272 1079 1017 1084 1067 +273 1029 1055 1062 1026 +274 1077 1052 1020 1061 +275 1046 1084 1018 1015 +276 1080 1022 1064 1051 +277 1080 1019 1022 1051 +278 1032 1057 1035 1066 +279 1017 1084 1014 1079 +280 1084 1018 1063 1046 +281 1048 1017 1014 1079 +282 1051 1064 1080 1023 +283 1061 1020 1080 1023 +284 1080 1064 1061 1023 +285 1079 1048 1017 1067 +286 1068 1029 1062 1026 +287 1020 1051 1080 1023 +288 1068 1056 1029 1026 +289 1017 1063 1084 1067 +290 1077 1020 1080 1061 +291 1076 1068 1065 1064 +292 1083 1055 1062 1029 +293 1028 1055 1062 1083 +294 1060 1034 1069 1078 +295 1082 1057 1036 1033 +296 1070 1078 1085 1081 +297 1080 1064 1065 1061 +298 1070 1078 1081 1082 +299 1082 1057 1033 1066 +300 1084 1063 1017 1046 +301 1077 1080 1065 1061 +302 1032 1035 1069 1066 +303 1076 1030 1056 1027 +304 1087 1054 1047 1073 +305 1053 1050 1071 1088 +306 1059 1049 1086 1072 +307 1013 1048 1079 1067 +308 1074 1058 1045 1089 +309 1069 1060 1078 1035 +310 1017 1084 1046 1014 +311 1082 1035 1057 1066 +312 1068 1076 1056 1027 +313 1048 1013 1016 1067 +314 1029 1062 1083 1068 +315 1056 1029 1076 1068 +316 1069 1060 1035 1032 +317 1035 1082 1069 1066 +318 1062 1025 1055 1028 +319 1072 1021 1077 1024 +320 1061 1072 1077 1024 +321 1084 1089 1018 1015 +322 1089 1018 1074 1084 +323 1082 1069 1078 1035 +324 1080 1065 1081 1077 +325 1021 1072 1077 1086 +326 1063 1067 1075 1085 +327 1083 1076 1029 1068 +328 1034 1069 1031 1060 +329 1075 1085 1083 1081 +330 1070 1063 1075 1085 +331 1071 1022 1064 1080 +332 1022 1088 1019 1080 +333 1018 1063 1074 1084 +334 1022 1071 1088 1080 +335 1082 1078 1069 1066 +336 1034 1089 1069 1078 +337 1086 1082 1036 1072 +338 1083 1062 1075 1068 +339 1072 1082 1036 1033 +340 1082 1070 1078 1066 +341 1028 1062 1087 1083 +342 1030 1071 1076 1088 +343 1062 1087 1073 1025 +344 1087 1013 1079 1067 +345 1088 1050 1022 1019 +346 1073 1047 1013 1016 +347 1071 1030 1076 1027 +348 1072 1049 1021 1024 +349 1013 1073 1016 1067 +350 1033 1072 1082 1066 +351 1069 1074 1089 1034 +352 1077 1082 1072 1061 +353 1087 1073 1067 1062 +354 1053 1071 1030 1088 +355 1059 1086 1036 1072 +356 1047 1013 1087 1073 +357 1087 1054 1073 1025 +358 1049 1021 1086 1072 +359 1022 1071 1050 1088 +360 1013 1087 1073 1067 +361 1027 1068 1076 1071 +362 1063 1070 1078 1085 +363 1030 1027 1071 1053 +364 1025 1062 1087 1028 +365 1084 1063 1078 1085 +366 1066 1072 1082 1061 +367 1072 1036 1059 1033 +368 1074 1069 1031 1034 +369 1070 1069 1078 1066 +370 1077 1082 1086 1072 +371 1074 1058 1089 1034 +372 1079 1087 1067 1083 +373 1087 1054 1025 1028 +374 1062 1067 1087 1083 +375 1085 1079 1075 1083 +376 1058 1074 1031 1034 +377 1063 1069 1074 1078 +378 1064 1076 1068 1071 +379 1045 1018 1074 1089 +380 1089 1045 1018 1015 +381 1063 1070 1069 1078 +382 1075 1079 1067 1083 +383 1085 1075 1070 1081 +384 1062 1075 1067 1083 +385 1075 1067 1079 1085 +386 1089 1074 1069 1078 +387 1081 1075 1070 1065 +388 1058 1003 1004 1045 +389 1007 1008 1049 1059 +390 1050 1005 1006 1053 +391 1054 1001 1002 1047 +392 1084 1067 1085 1079 +393 1085 1067 1084 1063 +394 1076 1065 1075 1081 +395 1075 1065 1076 1068 +396 1075 1083 1076 1081 +397 1076 1083 1075 1068 +398 1065 1080 1076 1064 +399 1076 1080 1065 1081 +400 1074 1084 1078 1089 +401 1078 1084 1074 1063 +402 1061 1082 1065 1077 +403 1061 1065 1082 1066 +404 1081 1065 1082 1077 +405 1071 1080 1076 1088 +406 1076 1080 1071 1064 +407 1065 1082 1070 1081 +408 1065 1070 1082 1066 +3 2 4 140 +409 1066 1065 1070 1106 +410 1090 1109 1063 1067 +411 1066 1109 1102 1103 +412 1061 1065 1066 1106 +413 1109 1070 1075 1065 +414 1094 1061 1095 1107 +415 1090 1109 1091 1063 +416 1064 1061 1094 1107 +417 1098 1062 1068 1107 +418 1109 1102 1069 1066 +419 1067 1109 1063 1075 +420 1069 1070 1109 1066 +421 1068 1098 1107 1099 +422 1065 1109 1070 1106 +423 1066 1070 1109 1106 +424 1068 1062 1075 1107 +425 1109 1090 1108 1067 +426 1061 1064 1065 1107 +427 1070 1063 1109 1075 +428 1103 1066 1109 1106 +429 1062 1098 1108 1107 +430 1075 1067 1108 1062 +431 1108 1109 1067 1075 +432 1071 1068 1064 1107 +433 1067 1073 1108 1062 +434 1106 1095 1061 1107 +435 1061 1066 1072 1106 +436 1109 1063 1069 1074 +437 1023 1064 1061 1094 +438 1107 1064 1071 1096 +439 1107 1094 1064 1096 +440 1070 1109 1063 1069 +441 1108 1075 1062 1107 +442 1017 1090 1091 1063 +443 1106 1061 1065 1107 +444 1095 1023 1061 1094 +445 1063 1017 1090 1067 +446 1032 1069 1102 1066 +447 1107 1071 1068 1101 +448 1109 1044 1102 1103 +449 1069 1104 1109 1074 +450 1102 1104 1109 1069 +451 1102 1032 1066 1103 +452 1062 1098 1068 1026 +453 1099 1107 1068 1101 +454 1026 1098 1068 1099 +455 1100 1098 1108 1062 +456 1065 1068 1075 1107 +457 1092 1108 1090 1067 +458 1109 1039 1090 1091 +459 1095 1042 1094 1107 +460 1073 1100 1108 1062 +461 1068 1065 1064 1107 +462 1108 1092 1073 1067 +463 1097 1095 1061 1106 +464 1066 1103 1105 1106 +465 1097 1061 1072 1106 +466 1101 1107 1071 1096 +467 1043 1107 1098 1099 +468 1105 1072 1066 1106 +469 1065 1075 1109 1107 +470 1091 1018 1063 1074 +471 1104 1093 1109 1074 +472 1106 1065 1109 1107 +473 1109 1044 1103 1106 +474 1039 1090 1108 1109 +475 1022 1064 1094 1096 +476 1064 1022 1071 1096 +477 1095 1106 1042 1107 +478 1108 1109 1075 1107 +479 1101 1011 1107 1096 +480 1098 1108 1107 1043 +481 1108 1092 1100 1073 +482 1107 1011 1042 1096 +483 1043 1011 1107 1101 +484 1018 1091 1093 1074 +485 1044 1010 1109 1104 +486 1109 1010 1039 1093 +487 1023 1022 1064 1094 +488 1042 1094 1107 1096 +489 1108 1009 1092 1039 +490 1100 1009 1108 1043 +491 1099 1043 1107 1101 +492 1100 1098 1062 1025 +493 1032 1033 1066 1103 +494 1024 1023 1061 1095 +495 1106 1012 1097 1042 +496 1105 1044 1012 1106 +497 1093 1109 1010 1104 +498 1017 1018 1063 1091 +499 1109 1044 1104 1102 +500 1026 1025 1062 1098 +501 1031 1104 1102 1069 +502 1016 1092 1090 1067 +503 1100 1037 1001 1073 +504 1037 1092 1001 1073 +505 1040 1071 1005 1096 +506 1027 1026 1068 1099 +507 1008 1072 1041 1097 +508 1105 1008 1072 1041 +509 1101 1071 1005 1040 +510 1039 1109 1093 1091 +511 1104 1004 1038 1074 +512 1062 1073 1100 1025 +513 1004 1093 1038 1074 +514 1069 1031 1104 1074 +515 1072 1105 1097 1106 +516 1016 1017 1067 1090 +517 1016 1073 1092 1067 +518 1090 1108 1092 1039 +519 1108 1098 1100 1043 +520 1032 1031 1102 1069 +521 1025 1001 1073 1100 +522 1005 1022 1096 1071 +523 1001 1016 1073 1092 +524 1004 1031 1074 1104 +525 1106 1097 1095 1042 +526 1105 1103 1044 1106 +527 1008 1024 1072 1097 +528 1092 1037 1100 1073 +529 1040 1101 1071 1096 +530 1104 1038 1093 1074 +531 1041 1072 1105 1097 +532 1018 1004 1074 1093 +533 1005 1027 1071 1101 +534 1033 1008 1072 1105 +535 1100 1009 1092 1108 +536 1037 1009 1092 1100 +537 1097 1105 1012 1106 +538 1038 1093 1010 1104 +539 1041 1105 1012 1097 +540 1040 1011 1101 1096 +541 1066 1105 1033 1072 +542 1033 1105 1066 1103 +543 1068 1101 1027 1099 +544 1027 1101 1068 1071 +545 1074 1091 1109 1063 +546 1074 1109 1091 1093 +547 1061 1097 1024 1095 +548 1024 1097 1061 1072 +$EndElements diff --git a/tests/fixtures/coarse-partitioned-v41.msh b/tests/fixtures/coarse-partitioned-v41.msh new file mode 100644 index 0000000..72e68b4 --- /dev/null +++ b/tests/fixtures/coarse-partitioned-v41.msh @@ -0,0 +1,1000 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +12 20 11 2 +1 0 0 -0.25 0 +2 0 0 0 0 +3 0 2 0 0 +4 0 2 -0.25 0 +5 2 0 -0.25 0 +6 2 0 0 0 +7 2 2 0 0 +8 2 2 -0.25 0 +9 0 0 -1 0 +10 0 2 -1 0 +11 2 0 -1 0 +12 2 2 -1 0 +1 -1e-07 -1e-07 -0.2500001 1e-07 1e-07 1.000000000028756e-07 0 2 1 -2 +2 -1e-07 -9.999999994736442e-08 -1e-07 1e-07 2.0000001 1e-07 0 2 2 -3 +3 -1e-07 1.9999999 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 0 2 4 -3 +4 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 -0.2499999 0 2 1 -4 +5 1.9999999 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 0 2 5 -6 +6 1.9999999 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 0 2 6 -7 +7 1.9999999 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 0 2 8 -7 +8 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 5 -8 +9 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 -0.2499999 0 2 1 -5 +10 -9.999999994736442e-08 -1e-07 -1e-07 2.0000001 1e-07 1e-07 0 2 2 -6 +11 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 4 -8 +12 -9.999999994736442e-08 1.9999999 -1e-07 2.0000001 2.0000001 1e-07 0 2 3 -7 +13 -1e-07 -1e-07 -1.0000001 1e-07 1e-07 -0.2499999 0 2 9 -1 +14 -1e-07 1.9999999 -1.0000001 1e-07 2.0000001 -0.2499999 0 2 10 -4 +15 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.9999999000000001 0 2 9 -10 +16 1.9999999 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 0 2 11 -5 +17 1.9999999 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 0 2 12 -8 +18 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 11 -12 +19 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.9999999000000001 0 2 9 -11 +20 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 10 -12 +1 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 1 105 4 -1 4 3 -2 +2 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -5 8 7 -6 +3 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 1 105 4 -9 1 10 -5 +4 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -11 3 12 -7 +5 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 1 103 4 -4 9 8 -11 +6 -9.999999994736442e-08 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 1 101 4 -2 10 6 -12 +7 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.2499999 1 105 4 -13 15 14 -4 +8 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -16 18 17 -8 +9 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 1 105 4 -19 13 9 -16 +10 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -20 14 11 -17 +11 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 1 105 4 -15 19 18 -20 +1 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 1 6 -1 2 -3 4 -5 6 +2 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 2 6 -7 8 -9 10 -11 5 +$EndEntities +$PartitionedEntities +2 +0 +18 33 20 4 +13 0 1 1 2 0 0 -0.25 0 +14 0 2 1 2 0 0 0 0 +15 0 3 1 2 0 2 0 0 +16 0 4 1 2 0 2 -0.25 0 +17 0 5 1 1 2 0 -0.25 0 +18 0 6 1 1 2 0 0 0 +19 0 7 1 1 2 2 0 0 +20 0 8 1 1 2 2 -0.25 0 +21 0 9 1 2 0 0 -1 0 +22 0 10 1 2 0 2 -1 0 +23 0 11 1 1 2 0 -1 0 +24 0 12 1 1 2 2 -1 0 +25 1 20 2 1 2 1 2 -1 0 +26 1 19 2 1 2 1 0 -1 0 +27 1 12 2 1 2 1.5 2 0 0 +28 1 11 2 1 2 1.5 2 -0.25 0 +29 1 9 2 1 2 0.5 0 -0.25 0 +30 1 10 2 1 2 0.5 0 0 0 +21 1 1 1 2 0 0 -0.25 0 0 0 0 2 13 -14 +22 1 2 1 2 0 0 0 0 2 0 0 2 14 -15 +23 1 3 1 2 0 2 -0.25 0 2 0 0 2 16 -15 +24 1 4 1 2 0 0 -0.25 0 2 -0.25 0 2 13 -16 +25 1 5 1 1 2 0 -0.25 2 0 0 0 2 17 -18 +26 1 6 1 1 2 0 0 2 2 0 0 2 18 -19 +27 1 7 1 1 2 2 -0.25 2 2 0 0 2 20 -19 +28 1 8 1 1 2 0 -0.25 2 2 -0.25 0 2 17 -20 +29 1 9 1 2 0 0 -0.25 0.5 0 -0.25 0 2 13 -29 +30 1 9 1 1 0.5 0 -0.25 2 0 -0.25 0 2 29 -17 +31 1 10 1 2 0 0 0 0.5 0 0 0 2 14 -30 +32 1 10 1 1 0.5 0 0 2 0 0 0 2 30 -18 +33 1 11 1 2 0 2 -0.25 1.5 2 -0.25 0 2 16 -28 +34 1 11 1 1 1.5 2 -0.25 2 2 -0.25 0 2 28 -20 +35 1 12 1 2 0 2 0 1.5 2 0 0 2 15 -27 +36 1 12 1 1 1.5 2 0 2 2 0 0 2 27 -19 +37 1 13 1 2 0 0 -1 0 0 -0.25 0 2 21 -13 +38 1 14 1 2 0 2 -1 0 2 -0.25 0 2 22 -16 +39 1 15 1 2 0 0 -1 0 2 -1 0 2 21 -22 +40 1 16 1 1 2 0 -1 2 0 -0.25 0 2 23 -17 +41 1 17 1 1 2 2 -1 2 2 -0.25 0 2 24 -20 +42 1 18 1 1 2 0 -1 2 2 -1 0 2 23 -24 +43 1 19 1 2 0 0 -1 1 0 -1 0 2 21 -26 +44 1 19 1 1 1 0 -1 2 0 -1 0 2 26 -23 +45 1 20 1 2 0 2 -1 1 2 -1 0 2 22 -25 +46 1 20 1 1 1 2 -1 2 2 -1 0 2 25 -24 +47 2 11 2 1 2 0.5625 0 -1 1.5 2 -1 0 2 26 -25 +48 2 5 2 1 2 0.5 0 -0.25 1.5 2 -0.25 0 2 29 -28 +49 2 9 2 1 2 0.3875026830019435 0 -1 1 0 -0.25 0 2 29 -26 +50 2 4 2 1 2 1.5 2 -0.25 1.5 2 0 0 2 28 -27 +51 2 3 2 1 2 0.5 0 -0.25 0.5 0 0 0 2 30 -29 +52 2 6 2 1 2 0.5 0 0 1.5 2 0 0 2 27 -30 +53 2 10 2 1 2 1 2 -1 1.5 2 -0.25 0 2 25 -28 +12 2 1 1 2 0 0 -0.25 0 2 0 1 105 4 -23 -24 21 22 +13 2 2 1 1 2 0 -0.25 2 2 0 1 105 4 -25 -26 27 28 +14 2 3 1 2 0 0 -0.25 0.5 0 0 1 105 4 -21 -31 29 -51 +15 2 3 1 1 0.5 0 -0.25 2 0 0 1 105 4 -32 25 30 51 +16 2 4 1 2 0 2 -0.25 1.5 2 0 1 105 4 -33 23 35 -50 +17 2 4 1 1 1.5 2 -0.25 2 2 0 1 105 4 -27 -34 36 50 +18 2 5 1 2 0 0 -0.25 1.5 2 -0.25 1 103 4 -29 24 33 -48 +19 2 5 1 1 0.5 0 -0.25 2 2 -0.25 1 103 4 -28 -30 34 48 +20 2 6 1 2 0 0 0 1.5 2 0 1 101 4 -22 -35 31 -52 +21 2 6 1 1 0.5 0 0 2 2 0 1 101 4 -36 26 32 52 +22 2 7 1 2 0 0 -1 0 2 -0.25 1 105 4 -38 -39 24 37 +23 2 8 1 1 2 0 -1 2 2 -0.25 1 105 4 -28 -40 41 42 +24 2 9 1 2 0 0 -1 1 0 -0.25 1 105 4 -29 -37 43 -49 +25 2 9 1 1 0.3875026830019435 0 -1 2 0 -0.25 1 105 4 -30 40 44 49 +26 2 10 1 2 0 2 -1 1.5 2 -0.25 1 105 4 -45 33 38 -53 +27 2 10 1 1 1 2 -1 2 2 -0.25 1 105 4 -41 -46 34 53 +28 2 11 1 2 0 0 -1 1 2 -1 1 105 3 -43 39 45 +29 2 11 1 1 0.5625 0 -1 2 2 -1 1 105 4 -42 -44 46 47 +30 3 2 2 1 2 0.3875026830019435 0 -1 1.5 2 -0.25 0 4 -47 48 -49 -53 +31 3 1 2 1 2 0.5 0 -0.25 1.5 2 0 0 4 -48 -50 -51 -52 +3 3 1 1 1 0.5 0 -0.25 2 2 0 1 1 6 -13 -15 -17 -19 -21 31 +4 3 1 1 2 0 0 -0.25 1.5 2 0 1 1 6 -12 -14 -16 -18 -20 -31 +5 3 2 1 2 0 0 -1 1.5 2 -0.25 1 2 6 -18 -22 -24 -26 -28 -30 +6 3 2 1 1 0.3875026830019435 0 -1 2 2 -0.25 1 2 6 -19 -23 -27 25 29 30 +$EndPartitionedEntities +$Nodes +75 109 1 109 +0 13 0 1 +1 +0 0 -0.25 +0 14 0 1 +2 +0 0 0 +0 15 0 1 +3 +0 2 0 +0 16 0 1 +4 +0 2 -0.25 +0 17 0 1 +5 +2 0 -0.25 +0 18 0 1 +6 +2 0 0 +0 19 0 1 +7 +2 2 0 +0 20 0 1 +8 +2 2 -0.25 +0 21 0 1 +9 +0 0 -1 +0 22 0 1 +10 +0 2 -1 +0 23 0 1 +11 +2 0 -1 +0 24 0 1 +12 +2 2 -1 +0 25 0 1 +44 +1 2 -1 +0 26 0 1 +43 +1 0 -1 +0 27 0 1 +36 +1.5 2 0 +0 28 0 1 +33 +1.5 2 -0.25 +0 29 0 1 +25 +0.5 0 -0.25 +0 30 0 1 +28 +0.5 0 0 +1 21 0 0 +1 22 0 3 +13 +14 +15 +0 0.5 0 +0 1 0 +0 1.5 0 +1 23 0 0 +1 24 0 3 +16 +17 +18 +0 0.5 -0.25 +0 1 -0.25 +0 1.5 -0.25 +1 25 0 0 +1 26 0 3 +19 +20 +21 +2 0.5 0 +2 1 0 +2 1.5 0 +1 27 0 0 +1 28 0 3 +22 +23 +24 +2 0.5 -0.25 +2 1 -0.25 +2 1.5 -0.25 +1 29 0 0 +1 30 0 2 +26 +27 +1 0 -0.25 +1.5 0 -0.25 +1 31 0 0 +1 32 0 2 +29 +30 +1 0 0 +1.5 0 0 +1 33 0 2 +31 +32 +0.5 2 -0.25 +1 2 -0.25 +1 34 0 0 +1 35 0 2 +34 +35 +0.5 2 0 +1 2 0 +1 36 0 0 +1 37 0 1 +37 +0 0 -0.5863498029562912 +1 38 0 1 +38 +0 2 -0.5863498029562912 +1 39 0 1 +39 +0 1 -1 +1 40 0 1 +40 +2 0 -0.5863498029562912 +1 41 0 1 +41 +2 2 -0.5863498029562912 +1 42 0 1 +42 +2 1 -1 +1 43 0 0 +1 44 0 0 +1 45 0 0 +1 46 0 0 +1 47 0 3 +106 +109 +108 +1.5 1.5 -1 +0.6914062499999998 1.30859375 -1 +0.5625 0.5625000000000002 -1 +1 48 0 4 +75 +65 +66 +62 +0.799919109919719 0.8127176335914889 -0.25 +1.19048230709083 0.9938634724449638 -0.25 +1.211526285167907 1.539080572974888 -0.25 +0.7183803468335941 0.3953531183916653 -0.25 +1 49 0 1 +100 +0.3875026830019435 0 -0.6053620673803086 +1 50 0 0 +1 51 0 0 +1 52 0 3 +83 +82 +81 +0.7720151968557594 0.504236995066732 0 +1.25740778765016 1.566401986104808 0 +1.138381937170266 0.9886994546006772 0 +1 53 0 1 +103 +1.189987121590671 2 -0.5299871215906714 +2 12 0 4 +47 +45 +48 +46 +0 0.25 -0.125 +0 1.75 -0.125 +0 0.75 -0.125 +0 1.25 -0.125 +2 13 0 4 +50 +49 +51 +52 +2 0.25 -0.125 +2 1.75 -0.125 +2 0.75 -0.125 +2 1.25 -0.125 +2 14 0 1 +54 +0.25 0 -0.125 +2 15 0 3 +53 +55 +56 +1.75 0 -0.125 +0.75 0 -0.125 +1.25 0 -0.125 +2 16 0 3 +58 +60 +57 +0.25 2 -0.125 +0.75 2 -0.125 +1.25 2 -0.125 +2 17 0 1 +59 +1.75 2 -0.125 +2 18 0 6 +73 +74 +67 +63 +69 +70 +0.3660254037844386 0.3660254037844387 -0.25 +0.3630158654516971 1.636984134548303 -0.25 +0.389289068198761 0.7180466052574374 -0.25 +0.4514095486548143 1.234183475777031 -0.25 +0.7375873251999121 1.636990986207155 -0.25 +0.8638678665980398 1.245262305856504 -0.25 +2 19 0 5 +71 +72 +64 +61 +68 +1.633974596215561 0.3660254037844386 -0.25 +1.633974596215561 1.633974596215561 -0.25 +1.577292096379071 0.7509441482399639 -0.25 +1.578151847792411 1.230662432702593 -0.25 +1.213941721673849 0.4470568705367829 -0.25 +2 20 0 6 +87 +89 +79 +84 +78 +85 +0.3660254037844387 0.3660254037844386 0 +0.3296501879957803 1.643987609966461 0 +0.3838980770800826 0.7512620920870399 0 +0.3774516866522022 1.228912045653614 0 +0.7707992533266995 1.491026004178691 0 +0.6885092302170019 0.9928273183173509 0 +2 21 0 5 +88 +86 +80 +77 +76 +1.633974596215561 0.3660254037844387 0 +1.633974596215561 1.633974596215561 0 +1.604157990548827 0.756865646419863 0 +1.57791535636081 1.249332516611218 0 +1.271813574233536 0.4264624335545749 0 +2 22 0 4 +92 +93 +90 +91 +0 0.3875026830019433 -0.6053620673803085 +0 1.614997853598445 -0.6027228207578272 +0 0.8250160980116606 -0.5458226013255596 +0 1.189987121590671 -0.5299871215906714 +2 23 0 4 +96 +97 +94 +95 +2 0.3875026830019433 -0.6053620673803085 +2 1.614997853598445 -0.6027228207578272 +2 0.8250160980116606 -0.5458226013255596 +2 1.189987121590671 -0.5299871215906714 +2 24 0 0 +2 25 0 3 +101 +98 +99 +1.614997853598445 0 -0.6027228207578272 +0.8250160980116606 0 -0.5458226013255596 +1.189987121590671 0 -0.5299871215906714 +2 26 0 2 +104 +102 +0.3875026830019435 2 -0.6053620673803086 +0.8250160980116606 2 -0.5458226013255596 +2 27 0 1 +105 +1.614997853598445 2 -0.6027228207578272 +2 28 0 0 +2 29 0 1 +107 +1.25 0.7499999999999999 -1 +2 30 0 0 +2 31 0 0 +3 3 0 0 +3 4 0 0 +3 5 0 0 +3 6 0 0 +$EndNodes +$Elements +22 548 1 548 +2 12 2 16 +1 1 2 47 +2 16 1 47 +3 2 13 47 +4 3 4 45 +5 15 3 45 +6 4 18 45 +7 13 14 48 +8 13 16 47 +9 16 13 48 +10 14 15 46 +11 17 14 46 +12 14 17 48 +13 18 15 45 +14 15 18 46 +15 17 16 48 +16 18 17 46 +2 13 2 16 +17 5 50 6 +18 22 50 5 +19 6 50 19 +20 7 49 8 +21 21 49 7 +22 8 49 24 +23 19 51 20 +24 19 50 22 +25 22 51 19 +26 20 52 21 +27 20 51 23 +28 23 52 20 +29 24 49 21 +30 21 52 24 +31 23 51 22 +32 24 52 23 +2 14 2 4 +33 2 1 54 +34 1 25 54 +35 28 2 54 +40 25 28 54 +2 15 2 12 +36 5 6 53 +37 27 5 53 +38 6 30 53 +39 25 26 55 +41 28 25 55 +42 26 27 56 +43 26 29 55 +44 29 26 56 +45 30 27 53 +46 27 30 56 +47 29 28 55 +48 30 29 56 +2 16 2 12 +49 3 58 4 +50 34 58 3 +51 4 58 31 +55 31 60 32 +56 31 58 34 +57 34 60 31 +58 32 57 33 +59 35 57 32 +60 32 60 35 +61 33 57 36 +63 35 60 34 +64 36 57 35 +2 17 2 4 +52 8 59 7 +53 7 59 36 +54 33 59 8 +62 36 59 33 +2 18 2 23 +65 1 16 73 +66 25 1 73 +67 18 4 74 +68 4 31 74 +73 16 17 67 +74 16 67 73 +75 17 18 63 +76 17 63 67 +77 63 18 74 +84 62 25 73 +88 31 32 69 +89 31 69 74 +90 32 33 66 +91 32 66 69 +96 67 62 73 +97 62 67 75 +99 67 63 75 +100 63 69 70 +101 69 63 74 +102 63 70 75 +105 66 65 70 +107 70 65 75 +108 69 66 70 +2 19 2 21 +69 22 5 71 +70 5 27 71 +71 8 24 72 +72 33 8 72 +78 23 22 64 +79 64 22 71 +80 24 23 61 +81 61 23 64 +82 24 61 72 +83 26 25 62 +85 27 26 68 +86 26 62 68 +87 27 68 71 +92 66 33 72 +93 61 64 65 +94 61 65 66 +95 61 66 72 +98 68 62 75 +103 65 64 68 +104 68 64 71 +106 65 68 75 +2 20 2 22 +109 2 87 13 +110 28 87 2 +111 15 89 3 +112 3 89 34 +117 13 79 14 +118 13 87 79 +119 14 84 15 +120 79 84 14 +121 84 89 15 +128 83 87 28 +132 34 78 35 +133 34 89 78 +134 35 82 36 +135 78 82 35 +143 81 82 78 +144 78 85 81 +145 84 85 78 +146 78 89 84 +147 83 85 79 +148 79 87 83 +149 79 85 84 +150 81 85 83 +2 21 2 20 +113 19 88 6 +114 6 88 30 +115 7 86 21 +116 36 86 7 +122 20 80 19 +123 80 88 19 +124 21 77 20 +125 77 80 20 +126 21 86 77 +127 29 83 28 +129 30 76 29 +130 76 83 29 +131 30 88 76 +136 82 86 36 +137 80 81 76 +138 76 88 80 +139 81 83 76 +140 77 81 80 +141 77 82 81 +142 77 86 82 +2 22 2 16 +151 1 16 92 +152 37 1 92 +153 18 4 93 +154 4 38 93 +155 9 37 92 +156 39 9 92 +157 38 10 93 +158 10 39 93 +159 16 17 90 +160 16 90 92 +161 17 18 91 +162 90 17 91 +163 91 18 93 +164 39 90 91 +165 90 39 92 +166 39 91 93 +2 23 2 16 +167 5 96 22 +168 40 96 5 +169 24 97 8 +170 8 97 41 +171 11 96 40 +172 42 96 11 +173 41 97 12 +174 12 97 42 +175 22 94 23 +176 22 96 94 +177 23 95 24 +178 94 95 23 +179 95 97 24 +180 42 95 94 +181 94 96 42 +182 42 97 95 +2 24 2 4 +183 25 1 100 +184 1 37 100 +187 37 9 100 +188 9 43 100 +2 25 2 12 +185 5 27 101 +186 40 5 101 +189 11 40 101 +190 43 11 101 +191 26 25 98 +192 98 25 100 +193 27 26 99 +194 26 98 99 +195 27 99 101 +196 98 43 99 +197 43 98 100 +198 99 43 101 +2 26 2 10 +199 31 104 4 +200 4 104 38 +203 38 104 10 +204 10 104 44 +207 32 102 31 +208 102 104 31 +209 33 103 32 +210 32 103 102 +212 102 103 44 +213 44 104 102 +2 27 2 6 +201 8 105 33 +202 41 105 8 +205 12 105 41 +206 44 105 12 +211 33 105 103 +214 103 105 44 +2 28 2 5 +215 9 39 108 +216 43 9 108 +217 39 10 109 +218 10 44 109 +223 108 39 109 +2 29 2 9 +219 42 11 107 +220 11 43 107 +221 12 42 106 +222 44 12 106 +224 106 42 107 +225 107 43 108 +226 44 106 109 +227 106 107 109 +228 107 108 109 +3 3 4 86 +229 22 23 64 51 +230 23 24 61 52 +231 20 21 52 77 +233 26 25 55 62 +237 26 27 68 56 +238 51 80 19 20 +242 30 6 53 88 +243 6 50 88 19 +244 86 7 21 49 +245 86 36 7 59 +247 24 8 72 49 +248 5 22 71 50 +252 83 28 55 29 +255 5 27 53 71 +256 76 30 29 56 +257 8 33 72 59 +261 49 86 7 59 +263 53 6 50 88 +264 59 8 49 72 +266 50 71 5 53 +269 21 52 77 24 +270 77 52 61 24 +271 61 52 20 23 +273 29 55 62 26 +274 77 52 20 61 +276 80 22 64 51 +277 80 19 22 51 +282 51 64 80 23 +283 61 20 80 23 +284 80 64 61 23 +286 68 29 62 26 +287 20 51 80 23 +288 68 56 29 26 +290 77 20 80 61 +291 76 68 65 64 +292 83 55 62 29 +293 28 55 62 83 +297 80 64 65 61 +301 77 80 65 61 +303 76 30 56 27 +305 53 50 71 88 +306 59 49 86 72 +312 68 76 56 27 +314 29 62 83 68 +315 56 29 76 68 +318 62 25 55 28 +319 72 21 77 24 +320 61 72 77 24 +324 80 65 81 77 +325 21 72 77 86 +327 83 76 29 68 +331 71 22 64 80 +332 22 88 19 80 +334 22 71 88 80 +337 86 82 36 72 +338 83 62 75 68 +339 72 82 36 33 +342 30 71 76 88 +345 88 50 22 19 +347 71 30 76 27 +348 72 49 21 24 +350 33 72 82 66 +352 77 82 72 61 +354 53 71 30 88 +355 59 86 36 72 +358 49 21 86 72 +359 22 71 50 88 +361 27 68 76 71 +363 30 27 71 53 +366 66 72 82 61 +367 72 36 59 33 +370 77 82 86 72 +378 64 76 68 71 +389 7 8 49 59 +390 50 5 6 53 +394 76 65 75 81 +395 75 65 76 68 +396 75 83 76 81 +397 76 83 75 68 +398 65 80 76 64 +399 76 80 65 81 +402 61 82 65 77 +403 61 65 82 66 +404 81 65 82 77 +405 71 80 76 88 +406 76 80 71 64 +3 4 4 94 +232 48 14 13 79 +234 33 32 66 57 +235 18 17 63 46 +236 15 46 84 14 +239 17 16 67 48 +240 3 45 89 15 +241 2 13 87 47 +246 32 31 69 60 +249 25 1 54 73 +250 1 16 47 73 +251 31 4 74 58 +253 78 60 34 35 +254 4 18 74 45 +258 57 35 82 36 +259 89 58 3 34 +260 54 87 2 28 +262 54 2 87 47 +265 47 54 1 73 +267 58 4 74 45 +268 45 58 3 89 +272 79 17 84 67 +275 46 84 18 15 +278 32 57 35 66 +279 17 84 14 79 +280 84 18 63 46 +281 48 17 14 79 +285 79 48 17 67 +289 17 63 84 67 +294 60 34 69 78 +295 82 57 36 33 +296 70 78 85 81 +298 70 78 81 82 +299 82 57 33 66 +300 84 63 17 46 +302 32 35 69 66 +304 87 54 47 73 +307 13 48 79 67 +308 74 58 45 89 +309 69 60 78 35 +310 17 84 46 14 +311 82 35 57 66 +313 48 13 16 67 +316 69 60 35 32 +317 35 82 69 66 +321 84 89 18 15 +322 89 18 74 84 +323 82 69 78 35 +326 63 67 75 85 +328 34 69 31 60 +329 75 85 83 81 +330 70 63 75 85 +333 18 63 74 84 +335 82 78 69 66 +336 34 89 69 78 +340 82 70 78 66 +341 28 62 87 83 +343 62 87 73 25 +344 87 13 79 67 +346 73 47 13 16 +349 13 73 16 67 +351 69 74 89 34 +353 87 73 67 62 +356 47 13 87 73 +357 87 54 73 25 +360 13 87 73 67 +362 63 70 78 85 +364 25 62 87 28 +365 84 63 78 85 +368 74 69 31 34 +369 70 69 78 66 +371 74 58 89 34 +372 79 87 67 83 +373 87 54 25 28 +374 62 67 87 83 +375 85 79 75 83 +376 58 74 31 34 +377 63 69 74 78 +379 45 18 74 89 +380 89 45 18 15 +381 63 70 69 78 +382 75 79 67 83 +383 85 75 70 81 +384 62 75 67 83 +385 75 67 79 85 +386 89 74 69 78 +387 81 75 70 65 +388 58 3 4 45 +391 54 1 2 47 +392 84 67 85 79 +393 85 67 84 63 +400 74 84 78 89 +401 78 84 74 63 +407 65 82 70 81 +408 65 70 82 66 +3 5 4 66 +409 66 65 70 106 +410 90 109 63 67 +411 66 109 102 103 +413 109 70 75 65 +415 90 109 91 63 +418 109 102 69 66 +419 67 109 63 75 +420 69 70 109 66 +422 65 109 70 106 +423 66 70 109 106 +425 109 90 108 67 +427 70 63 109 75 +428 103 66 109 106 +430 75 67 108 62 +431 108 109 67 75 +433 67 73 108 62 +436 109 63 69 74 +440 70 109 63 69 +442 17 90 91 63 +445 63 17 90 67 +446 32 69 102 66 +448 109 44 102 103 +449 69 104 109 74 +450 102 104 109 69 +451 102 32 66 103 +457 92 108 90 67 +458 109 39 90 91 +460 73 100 108 62 +462 108 92 73 67 +470 91 18 63 74 +471 104 93 109 74 +474 39 90 108 109 +481 108 92 100 73 +484 18 91 93 74 +485 44 10 109 104 +486 109 10 39 93 +489 108 9 92 39 +490 100 9 108 43 +493 32 33 66 103 +497 93 109 10 104 +498 17 18 63 91 +499 109 44 104 102 +501 31 104 102 69 +502 16 92 90 67 +503 100 37 1 73 +504 37 92 1 73 +510 39 109 93 91 +511 104 4 38 74 +512 62 73 100 25 +513 4 93 38 74 +514 69 31 104 74 +516 16 17 67 90 +517 16 73 92 67 +518 90 108 92 39 +520 32 31 102 69 +521 25 1 73 100 +523 1 16 73 92 +524 4 31 74 104 +528 92 37 100 73 +530 104 38 93 74 +532 18 4 74 93 +535 100 9 92 108 +536 37 9 92 100 +538 38 93 10 104 +545 74 91 109 63 +546 74 109 91 93 +3 6 4 74 +412 61 65 66 106 +414 94 61 95 107 +416 64 61 94 107 +417 98 62 68 107 +421 68 98 107 99 +424 68 62 75 107 +426 61 64 65 107 +429 62 98 108 107 +432 71 68 64 107 +434 106 95 61 107 +435 61 66 72 106 +437 23 64 61 94 +438 107 64 71 96 +439 107 94 64 96 +441 108 75 62 107 +443 106 61 65 107 +444 95 23 61 94 +447 107 71 68 101 +452 62 98 68 26 +453 99 107 68 101 +454 26 98 68 99 +455 100 98 108 62 +456 65 68 75 107 +459 95 42 94 107 +461 68 65 64 107 +463 97 95 61 106 +464 66 103 105 106 +465 97 61 72 106 +466 101 107 71 96 +467 43 107 98 99 +468 105 72 66 106 +469 65 75 109 107 +472 106 65 109 107 +473 109 44 103 106 +475 22 64 94 96 +476 64 22 71 96 +477 95 106 42 107 +478 108 109 75 107 +479 101 11 107 96 +480 98 108 107 43 +482 107 11 42 96 +483 43 11 107 101 +487 23 22 64 94 +488 42 94 107 96 +491 99 43 107 101 +492 100 98 62 25 +494 24 23 61 95 +495 106 12 97 42 +496 105 44 12 106 +500 26 25 62 98 +505 40 71 5 96 +506 27 26 68 99 +507 8 72 41 97 +508 105 8 72 41 +509 101 71 5 40 +515 72 105 97 106 +519 108 98 100 43 +522 5 22 96 71 +525 106 97 95 42 +526 105 103 44 106 +527 8 24 72 97 +529 40 101 71 96 +531 41 72 105 97 +533 5 27 71 101 +534 33 8 72 105 +537 97 105 12 106 +539 41 105 12 97 +540 40 11 101 96 +541 66 105 33 72 +542 33 105 66 103 +543 68 101 27 99 +544 27 101 68 71 +547 61 97 24 95 +548 24 97 61 72 +$EndElements diff --git a/tests/fixtures/coarse-v41.msh b/tests/fixtures/coarse-v41.msh new file mode 100644 index 0000000..01930be --- /dev/null +++ b/tests/fixtures/coarse-v41.msh @@ -0,0 +1,877 @@ +$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +12 20 11 2 +1 0 0 -0.25 0 +2 0 0 0 0 +3 0 2 0 0 +4 0 2 -0.25 0 +5 2 0 -0.25 0 +6 2 0 0 0 +7 2 2 0 0 +8 2 2 -0.25 0 +9 0 0 -1 0 +10 0 2 -1 0 +11 2 0 -1 0 +12 2 2 -1 0 +1 -1e-07 -1e-07 -0.2500001 1e-07 1e-07 1.000000000028756e-07 0 2 1 -2 +2 -1e-07 -9.999999994736442e-08 -1e-07 1e-07 2.0000001 1e-07 0 2 2 -3 +3 -1e-07 1.9999999 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 0 2 4 -3 +4 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 -0.2499999 0 2 1 -4 +5 1.9999999 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 0 2 5 -6 +6 1.9999999 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 0 2 6 -7 +7 1.9999999 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 0 2 8 -7 +8 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 5 -8 +9 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 -0.2499999 0 2 1 -5 +10 -9.999999994736442e-08 -1e-07 -1e-07 2.0000001 1e-07 1e-07 0 2 2 -6 +11 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 -0.2499999 0 2 4 -8 +12 -9.999999994736442e-08 1.9999999 -1e-07 2.0000001 2.0000001 1e-07 0 2 3 -7 +13 -1e-07 -1e-07 -1.0000001 1e-07 1e-07 -0.2499999 0 2 9 -1 +14 -1e-07 1.9999999 -1.0000001 1e-07 2.0000001 -0.2499999 0 2 10 -4 +15 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.9999999000000001 0 2 9 -10 +16 1.9999999 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 0 2 11 -5 +17 1.9999999 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 0 2 12 -8 +18 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 11 -12 +19 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.9999999000000001 0 2 9 -11 +20 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 0 2 10 -12 +1 -1e-07 -9.999999994736442e-08 -0.2500001 1e-07 2.0000001 1.000000000028756e-07 1 105 4 -1 4 3 -2 +2 1.9999999 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -5 8 7 -6 +3 -9.999999994736442e-08 -1e-07 -0.2500001 2.0000001 1e-07 1.000000000028756e-07 1 105 4 -9 1 10 -5 +4 -9.999999994736442e-08 1.9999999 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 105 4 -11 3 12 -7 +5 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 -0.2499999 1 103 4 -4 9 8 -11 +6 -9.999999994736442e-08 -9.999999994736442e-08 -1e-07 2.0000001 2.0000001 1e-07 1 101 4 -2 10 6 -12 +7 -1e-07 -9.999999994736442e-08 -1.0000001 1e-07 2.0000001 -0.2499999 1 105 4 -13 15 14 -4 +8 1.9999999 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -16 18 17 -8 +9 -9.999999994736442e-08 -1e-07 -1.0000001 2.0000001 1e-07 -0.2499999 1 105 4 -19 13 9 -16 +10 -9.999999994736442e-08 1.9999999 -1.0000001 2.0000001 2.0000001 -0.2499999 1 105 4 -20 14 11 -17 +11 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.9999999000000001 1 105 4 -15 19 18 -20 +1 -9.999999994736442e-08 -9.999999994736442e-08 -0.2500001 2.0000001 2.0000001 1.000000000028756e-07 1 1 6 -1 2 -3 4 -5 6 +2 -9.999999994736442e-08 -9.999999994736442e-08 -1.0000001 2.0000001 2.0000001 -0.2499999 1 2 6 -7 8 -9 10 -11 5 +$EndEntities +$Nodes +41 109 1 109 +0 1 0 1 +1 +0 0 -0.25 +0 2 0 1 +2 +0 0 0 +0 3 0 1 +3 +0 2 0 +0 4 0 1 +4 +0 2 -0.25 +0 5 0 1 +5 +2 0 -0.25 +0 6 0 1 +6 +2 0 0 +0 7 0 1 +7 +2 2 0 +0 8 0 1 +8 +2 2 -0.25 +0 9 0 1 +9 +0 0 -1 +0 10 0 1 +10 +0 2 -1 +0 11 0 1 +11 +2 0 -1 +0 12 0 1 +12 +2 2 -1 +1 2 0 3 +13 +14 +15 +0 0.5 0 +0 1 0 +0 1.5 0 +1 4 0 3 +16 +17 +18 +0 0.5 -0.25 +0 1 -0.25 +0 1.5 -0.25 +1 6 0 3 +19 +20 +21 +2 0.5 0 +2 1 0 +2 1.5 0 +1 8 0 3 +22 +23 +24 +2 0.5 -0.25 +2 1 -0.25 +2 1.5 -0.25 +1 9 0 3 +25 +26 +27 +0.5 0 -0.25 +1 0 -0.25 +1.5 0 -0.25 +1 10 0 3 +28 +29 +30 +0.5 0 0 +1 0 0 +1.5 0 0 +1 11 0 3 +31 +32 +33 +0.5 2 -0.25 +1 2 -0.25 +1.5 2 -0.25 +1 12 0 3 +34 +35 +36 +0.5 2 0 +1 2 0 +1.5 2 0 +1 13 0 1 +37 +0 0 -0.5863498029562912 +1 14 0 1 +38 +0 2 -0.5863498029562912 +1 15 0 1 +39 +0 1 -1 +1 16 0 1 +40 +2 0 -0.5863498029562912 +1 17 0 1 +41 +2 2 -0.5863498029562912 +1 18 0 1 +42 +2 1 -1 +1 19 0 1 +43 +1 0 -1 +1 20 0 1 +44 +1 2 -1 +2 1 0 4 +45 +46 +47 +48 +0 1.75 -0.125 +0 1.25 -0.125 +0 0.25 -0.125 +0 0.75 -0.125 +2 2 0 4 +49 +50 +51 +52 +2 1.75 -0.125 +2 0.25 -0.125 +2 0.75 -0.125 +2 1.25 -0.125 +2 3 0 4 +53 +54 +55 +56 +1.75 0 -0.125 +0.25 0 -0.125 +0.75 0 -0.125 +1.25 0 -0.125 +2 4 0 4 +57 +58 +59 +60 +1.25 2 -0.125 +0.25 2 -0.125 +1.75 2 -0.125 +0.75 2 -0.125 +2 5 0 15 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +1.578151847792411 1.230662432702593 -0.25 +0.7183803468335941 0.3953531183916653 -0.25 +0.4514095486548143 1.234183475777031 -0.25 +1.577292096379071 0.7509441482399639 -0.25 +1.19048230709083 0.9938634724449638 -0.25 +1.211526285167907 1.539080572974888 -0.25 +0.389289068198761 0.7180466052574374 -0.25 +1.213941721673849 0.4470568705367829 -0.25 +0.7375873251999121 1.636990986207155 -0.25 +0.8638678665980398 1.245262305856504 -0.25 +1.633974596215561 0.3660254037844386 -0.25 +1.633974596215561 1.633974596215561 -0.25 +0.3660254037844386 0.3660254037844387 -0.25 +0.3630158654516971 1.636984134548303 -0.25 +0.799919109919719 0.8127176335914889 -0.25 +2 6 0 14 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +1.271813574233536 0.4264624335545749 0 +1.57791535636081 1.249332516611218 0 +0.7707992533266995 1.491026004178691 0 +0.3838980770800826 0.7512620920870399 0 +1.604157990548827 0.756865646419863 0 +1.138381937170266 0.9886994546006772 0 +1.25740778765016 1.566401986104808 0 +0.7720151968557594 0.504236995066732 0 +0.3774516866522022 1.228912045653614 0 +0.6885092302170019 0.9928273183173509 0 +1.633974596215561 1.633974596215561 0 +0.3660254037844387 0.3660254037844386 0 +1.633974596215561 0.3660254037844387 0 +0.3296501879957803 1.643987609966461 0 +2 7 0 4 +90 +91 +92 +93 +0 0.8250160980116606 -0.5458226013255596 +0 1.189987121590671 -0.5299871215906714 +0 0.3875026830019433 -0.6053620673803085 +0 1.614997853598445 -0.6027228207578272 +2 8 0 4 +94 +95 +96 +97 +2 0.8250160980116606 -0.5458226013255596 +2 1.189987121590671 -0.5299871215906714 +2 0.3875026830019433 -0.6053620673803085 +2 1.614997853598445 -0.6027228207578272 +2 9 0 4 +98 +99 +100 +101 +0.8250160980116606 0 -0.5458226013255596 +1.189987121590671 0 -0.5299871215906714 +0.3875026830019435 0 -0.6053620673803086 +1.614997853598445 0 -0.6027228207578272 +2 10 0 4 +102 +103 +104 +105 +0.8250160980116606 2 -0.5458226013255596 +1.189987121590671 2 -0.5299871215906714 +0.3875026830019435 2 -0.6053620673803086 +1.614997853598445 2 -0.6027228207578272 +2 11 0 4 +106 +107 +108 +109 +1.5 1.5 -1 +1.25 0.7499999999999999 -1 +0.5625 0.5625000000000002 -1 +0.6914062499999998 1.30859375 -1 +3 1 0 0 +3 2 0 0 +$EndNodes +$Elements +13 548 1 548 +2 1 2 16 +1 1 2 47 +2 16 1 47 +3 2 13 47 +4 3 4 45 +5 15 3 45 +6 4 18 45 +7 13 14 48 +8 13 16 47 +9 16 13 48 +10 14 15 46 +11 17 14 46 +12 14 17 48 +13 18 15 45 +14 15 18 46 +15 17 16 48 +16 18 17 46 +2 2 2 16 +17 5 50 6 +18 22 50 5 +19 6 50 19 +20 7 49 8 +21 21 49 7 +22 8 49 24 +23 19 51 20 +24 19 50 22 +25 22 51 19 +26 20 52 21 +27 20 51 23 +28 23 52 20 +29 24 49 21 +30 21 52 24 +31 23 51 22 +32 24 52 23 +2 3 2 16 +33 2 1 54 +34 1 25 54 +35 28 2 54 +36 5 6 53 +37 27 5 53 +38 6 30 53 +39 25 26 55 +40 25 28 54 +41 28 25 55 +42 26 27 56 +43 26 29 55 +44 29 26 56 +45 30 27 53 +46 27 30 56 +47 29 28 55 +48 30 29 56 +2 4 2 16 +49 3 58 4 +50 34 58 3 +51 4 58 31 +52 8 59 7 +53 7 59 36 +54 33 59 8 +55 31 60 32 +56 31 58 34 +57 34 60 31 +58 32 57 33 +59 35 57 32 +60 32 60 35 +61 33 57 36 +62 36 59 33 +63 35 60 34 +64 36 57 35 +2 5 2 44 +65 1 16 73 +66 25 1 73 +67 18 4 74 +68 4 31 74 +69 22 5 71 +70 5 27 71 +71 8 24 72 +72 33 8 72 +73 16 17 67 +74 16 67 73 +75 17 18 63 +76 17 63 67 +77 63 18 74 +78 23 22 64 +79 64 22 71 +80 24 23 61 +81 61 23 64 +82 24 61 72 +83 26 25 62 +84 62 25 73 +85 27 26 68 +86 26 62 68 +87 27 68 71 +88 31 32 69 +89 31 69 74 +90 32 33 66 +91 32 66 69 +92 66 33 72 +93 61 64 65 +94 61 65 66 +95 61 66 72 +96 67 62 73 +97 62 67 75 +98 68 62 75 +99 67 63 75 +100 63 69 70 +101 69 63 74 +102 63 70 75 +103 65 64 68 +104 68 64 71 +105 66 65 70 +106 65 68 75 +107 70 65 75 +108 69 66 70 +2 6 2 42 +109 2 87 13 +110 28 87 2 +111 15 89 3 +112 3 89 34 +113 19 88 6 +114 6 88 30 +115 7 86 21 +116 36 86 7 +117 13 79 14 +118 13 87 79 +119 14 84 15 +120 79 84 14 +121 84 89 15 +122 20 80 19 +123 80 88 19 +124 21 77 20 +125 77 80 20 +126 21 86 77 +127 29 83 28 +128 83 87 28 +129 30 76 29 +130 76 83 29 +131 30 88 76 +132 34 78 35 +133 34 89 78 +134 35 82 36 +135 78 82 35 +136 82 86 36 +137 80 81 76 +138 76 88 80 +139 81 83 76 +140 77 81 80 +141 77 82 81 +142 77 86 82 +143 81 82 78 +144 78 85 81 +145 84 85 78 +146 78 89 84 +147 83 85 79 +148 79 87 83 +149 79 85 84 +150 81 85 83 +2 7 2 16 +151 1 16 92 +152 37 1 92 +153 18 4 93 +154 4 38 93 +155 9 37 92 +156 39 9 92 +157 38 10 93 +158 10 39 93 +159 16 17 90 +160 16 90 92 +161 17 18 91 +162 90 17 91 +163 91 18 93 +164 39 90 91 +165 90 39 92 +166 39 91 93 +2 8 2 16 +167 5 96 22 +168 40 96 5 +169 24 97 8 +170 8 97 41 +171 11 96 40 +172 42 96 11 +173 41 97 12 +174 12 97 42 +175 22 94 23 +176 22 96 94 +177 23 95 24 +178 94 95 23 +179 95 97 24 +180 42 95 94 +181 94 96 42 +182 42 97 95 +2 9 2 16 +183 25 1 100 +184 1 37 100 +185 5 27 101 +186 40 5 101 +187 37 9 100 +188 9 43 100 +189 11 40 101 +190 43 11 101 +191 26 25 98 +192 98 25 100 +193 27 26 99 +194 26 98 99 +195 27 99 101 +196 98 43 99 +197 43 98 100 +198 99 43 101 +2 10 2 16 +199 31 104 4 +200 4 104 38 +201 8 105 33 +202 41 105 8 +203 38 104 10 +204 10 104 44 +205 12 105 41 +206 44 105 12 +207 32 102 31 +208 102 104 31 +209 33 103 32 +210 32 103 102 +211 33 105 103 +212 102 103 44 +213 44 104 102 +214 103 105 44 +2 11 2 14 +215 9 39 108 +216 43 9 108 +217 39 10 109 +218 10 44 109 +219 42 11 107 +220 11 43 107 +221 12 42 106 +222 44 12 106 +223 108 39 109 +224 106 42 107 +225 107 43 108 +226 44 106 109 +227 106 107 109 +228 107 108 109 +3 1 4 180 +229 22 23 64 51 +230 23 24 61 52 +231 20 21 52 77 +232 48 14 13 79 +233 26 25 55 62 +234 33 32 66 57 +235 18 17 63 46 +236 15 46 84 14 +237 26 27 68 56 +238 51 80 19 20 +239 17 16 67 48 +240 3 45 89 15 +241 2 13 87 47 +242 30 6 53 88 +243 6 50 88 19 +244 86 7 21 49 +245 86 36 7 59 +246 32 31 69 60 +247 24 8 72 49 +248 5 22 71 50 +249 25 1 54 73 +250 1 16 47 73 +251 31 4 74 58 +252 83 28 55 29 +253 78 60 34 35 +254 4 18 74 45 +255 5 27 53 71 +256 76 30 29 56 +257 8 33 72 59 +258 57 35 82 36 +259 89 58 3 34 +260 54 87 2 28 +261 49 86 7 59 +262 54 2 87 47 +263 53 6 50 88 +264 59 8 49 72 +265 47 54 1 73 +266 50 71 5 53 +267 58 4 74 45 +268 45 58 3 89 +269 21 52 77 24 +270 77 52 61 24 +271 61 52 20 23 +272 79 17 84 67 +273 29 55 62 26 +274 77 52 20 61 +275 46 84 18 15 +276 80 22 64 51 +277 80 19 22 51 +278 32 57 35 66 +279 17 84 14 79 +280 84 18 63 46 +281 48 17 14 79 +282 51 64 80 23 +283 61 20 80 23 +284 80 64 61 23 +285 79 48 17 67 +286 68 29 62 26 +287 20 51 80 23 +288 68 56 29 26 +289 17 63 84 67 +290 77 20 80 61 +291 76 68 65 64 +292 83 55 62 29 +293 28 55 62 83 +294 60 34 69 78 +295 82 57 36 33 +296 70 78 85 81 +297 80 64 65 61 +298 70 78 81 82 +299 82 57 33 66 +300 84 63 17 46 +301 77 80 65 61 +302 32 35 69 66 +303 76 30 56 27 +304 87 54 47 73 +305 53 50 71 88 +306 59 49 86 72 +307 13 48 79 67 +308 74 58 45 89 +309 69 60 78 35 +310 17 84 46 14 +311 82 35 57 66 +312 68 76 56 27 +313 48 13 16 67 +314 29 62 83 68 +315 56 29 76 68 +316 69 60 35 32 +317 35 82 69 66 +318 62 25 55 28 +319 72 21 77 24 +320 61 72 77 24 +321 84 89 18 15 +322 89 18 74 84 +323 82 69 78 35 +324 80 65 81 77 +325 21 72 77 86 +326 63 67 75 85 +327 83 76 29 68 +328 34 69 31 60 +329 75 85 83 81 +330 70 63 75 85 +331 71 22 64 80 +332 22 88 19 80 +333 18 63 74 84 +334 22 71 88 80 +335 82 78 69 66 +336 34 89 69 78 +337 86 82 36 72 +338 83 62 75 68 +339 72 82 36 33 +340 82 70 78 66 +341 28 62 87 83 +342 30 71 76 88 +343 62 87 73 25 +344 87 13 79 67 +345 88 50 22 19 +346 73 47 13 16 +347 71 30 76 27 +348 72 49 21 24 +349 13 73 16 67 +350 33 72 82 66 +351 69 74 89 34 +352 77 82 72 61 +353 87 73 67 62 +354 53 71 30 88 +355 59 86 36 72 +356 47 13 87 73 +357 87 54 73 25 +358 49 21 86 72 +359 22 71 50 88 +360 13 87 73 67 +361 27 68 76 71 +362 63 70 78 85 +363 30 27 71 53 +364 25 62 87 28 +365 84 63 78 85 +366 66 72 82 61 +367 72 36 59 33 +368 74 69 31 34 +369 70 69 78 66 +370 77 82 86 72 +371 74 58 89 34 +372 79 87 67 83 +373 87 54 25 28 +374 62 67 87 83 +375 85 79 75 83 +376 58 74 31 34 +377 63 69 74 78 +378 64 76 68 71 +379 45 18 74 89 +380 89 45 18 15 +381 63 70 69 78 +382 75 79 67 83 +383 85 75 70 81 +384 62 75 67 83 +385 75 67 79 85 +386 89 74 69 78 +387 81 75 70 65 +388 58 3 4 45 +389 7 8 49 59 +390 50 5 6 53 +391 54 1 2 47 +392 84 67 85 79 +393 85 67 84 63 +394 76 65 75 81 +395 75 65 76 68 +396 75 83 76 81 +397 76 83 75 68 +398 65 80 76 64 +399 76 80 65 81 +400 74 84 78 89 +401 78 84 74 63 +402 61 82 65 77 +403 61 65 82 66 +404 81 65 82 77 +405 71 80 76 88 +406 76 80 71 64 +407 65 82 70 81 +408 65 70 82 66 +3 2 4 140 +409 66 65 70 106 +410 90 109 63 67 +411 66 109 102 103 +412 61 65 66 106 +413 109 70 75 65 +414 94 61 95 107 +415 90 109 91 63 +416 64 61 94 107 +417 98 62 68 107 +418 109 102 69 66 +419 67 109 63 75 +420 69 70 109 66 +421 68 98 107 99 +422 65 109 70 106 +423 66 70 109 106 +424 68 62 75 107 +425 109 90 108 67 +426 61 64 65 107 +427 70 63 109 75 +428 103 66 109 106 +429 62 98 108 107 +430 75 67 108 62 +431 108 109 67 75 +432 71 68 64 107 +433 67 73 108 62 +434 106 95 61 107 +435 61 66 72 106 +436 109 63 69 74 +437 23 64 61 94 +438 107 64 71 96 +439 107 94 64 96 +440 70 109 63 69 +441 108 75 62 107 +442 17 90 91 63 +443 106 61 65 107 +444 95 23 61 94 +445 63 17 90 67 +446 32 69 102 66 +447 107 71 68 101 +448 109 44 102 103 +449 69 104 109 74 +450 102 104 109 69 +451 102 32 66 103 +452 62 98 68 26 +453 99 107 68 101 +454 26 98 68 99 +455 100 98 108 62 +456 65 68 75 107 +457 92 108 90 67 +458 109 39 90 91 +459 95 42 94 107 +460 73 100 108 62 +461 68 65 64 107 +462 108 92 73 67 +463 97 95 61 106 +464 66 103 105 106 +465 97 61 72 106 +466 101 107 71 96 +467 43 107 98 99 +468 105 72 66 106 +469 65 75 109 107 +470 91 18 63 74 +471 104 93 109 74 +472 106 65 109 107 +473 109 44 103 106 +474 39 90 108 109 +475 22 64 94 96 +476 64 22 71 96 +477 95 106 42 107 +478 108 109 75 107 +479 101 11 107 96 +480 98 108 107 43 +481 108 92 100 73 +482 107 11 42 96 +483 43 11 107 101 +484 18 91 93 74 +485 44 10 109 104 +486 109 10 39 93 +487 23 22 64 94 +488 42 94 107 96 +489 108 9 92 39 +490 100 9 108 43 +491 99 43 107 101 +492 100 98 62 25 +493 32 33 66 103 +494 24 23 61 95 +495 106 12 97 42 +496 105 44 12 106 +497 93 109 10 104 +498 17 18 63 91 +499 109 44 104 102 +500 26 25 62 98 +501 31 104 102 69 +502 16 92 90 67 +503 100 37 1 73 +504 37 92 1 73 +505 40 71 5 96 +506 27 26 68 99 +507 8 72 41 97 +508 105 8 72 41 +509 101 71 5 40 +510 39 109 93 91 +511 104 4 38 74 +512 62 73 100 25 +513 4 93 38 74 +514 69 31 104 74 +515 72 105 97 106 +516 16 17 67 90 +517 16 73 92 67 +518 90 108 92 39 +519 108 98 100 43 +520 32 31 102 69 +521 25 1 73 100 +522 5 22 96 71 +523 1 16 73 92 +524 4 31 74 104 +525 106 97 95 42 +526 105 103 44 106 +527 8 24 72 97 +528 92 37 100 73 +529 40 101 71 96 +530 104 38 93 74 +531 41 72 105 97 +532 18 4 74 93 +533 5 27 71 101 +534 33 8 72 105 +535 100 9 92 108 +536 37 9 92 100 +537 97 105 12 106 +538 38 93 10 104 +539 41 105 12 97 +540 40 11 101 96 +541 66 105 33 72 +542 33 105 66 103 +543 68 101 27 99 +544 27 101 68 71 +545 74 91 109 63 +546 74 109 91 93 +547 61 97 24 95 +548 24 97 61 72 +$EndElements diff --git a/tests/fixtures/coarse.puml.h5 b/tests/fixtures/coarse.puml.h5 new file mode 100644 index 0000000..fb3cf15 Binary files /dev/null and b/tests/fixtures/coarse.puml.h5 differ diff --git a/tests/fixtures/generate.py b/tests/fixtures/generate.py new file mode 100644 index 0000000..227fe8c --- /dev/null +++ b/tests/fixtures/generate.py @@ -0,0 +1,414 @@ +# SPDX-FileCopyrightText: 2026 SeisSol Group +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate the mesh fixtures and the reference PUML files for the regression tests. + +Run from this directory: python3 generate.py +Requires the gmsh Python API, numpy and h5py. The reference files are computed +from the gmsh model directly, i.e. independently of PUMGen. +""" + +import itertools +import os +import struct + +import gmsh +import h5py +import numpy as np + +# local face numbering of a tetrahedron as used by SeisSol +FACE_NODES = [(1, 0, 2), (0, 1, 3), (1, 2, 3), (2, 0, 3)] + +WIDTH, DEPTH, LAYER = 2.0, 1.0, 0.25 +# OpenCASCADE enlarges bounding boxes slightly, so classify with a generous tolerance +TOL = 1e-3 +FREE_SURFACE, INTERFACE, ABSORBING = 101, 103, 105 + + +def boundary_offset(tag): + return tag - 100 if tag >= 100 else tag + + +def build_model(size_top, size_bottom): + gmsh.model.add("layered") + top = gmsh.model.occ.addBox(0, 0, -LAYER, WIDTH, WIDTH, LAYER) + bottom = gmsh.model.occ.addBox(0, 0, -DEPTH, WIDTH, WIDTH, DEPTH - LAYER) + gmsh.model.occ.fragment([(3, top)], [(3, bottom)]) + gmsh.model.occ.synchronize() + + for dim, tag in gmsh.model.getEntities(3): + zmin = gmsh.model.getBoundingBox(dim, tag)[2] + gmsh.model.addPhysicalGroup(3, [tag], 1 if zmin > -LAYER - TOL else 2) + + groups = {FREE_SURFACE: [], INTERFACE: [], ABSORBING: []} + for dim, tag in gmsh.model.getEntities(2): + _, _, zmin, _, _, zmax = gmsh.model.getBoundingBox(dim, tag) + if zmin > -TOL: + groups[FREE_SURFACE].append(tag) + elif abs(zmin + LAYER) < TOL and abs(zmax + LAYER) < TOL: + groups[INTERFACE].append(tag) + else: + groups[ABSORBING].append(tag) + for physical, tags in groups.items(): + gmsh.model.addPhysicalGroup(2, tags, physical) + + for dim, tag in gmsh.model.getEntities(0): + z = gmsh.model.getValue(dim, tag, [])[2] + gmsh.model.mesh.setSize([(dim, tag)], size_bottom if z < -LAYER - TOL else size_top) + + gmsh.model.mesh.generate(3) + + +def physical_of(dim, tag): + physicals = gmsh.model.getPhysicalGroupsForEntity(dim, tag) + return int(physicals[0]) if len(physicals) > 0 else 0 + + +def reference_data(): + """Mesh data in the layout PUMGen writes: vertices by node tag, cells in file order.""" + node_tags, coords, _ = gmsh.model.mesh.getNodes() + order = np.argsort(node_tags) + node_tags = node_tags[order] + assert np.array_equal(node_tags, np.arange(1, len(node_tags) + 1)), "expected dense node tags" + vertices = coords.reshape(-1, 3)[order] + + face_bc = {} + for dim, tag in gmsh.model.getEntities(2): + physical = physical_of(dim, tag) + if physical == 0: + continue + types, _, nodes = gmsh.model.mesh.getElements(dim, tag) + for element_type, element_nodes in zip(types, nodes): + _, _, _, num_nodes, _, _ = gmsh.model.mesh.getElementProperties(element_type) + for tri in np.asarray(element_nodes).reshape(-1, num_nodes): + face_bc[frozenset(int(v) for v in tri[:3])] = boundary_offset(physical) + + cells, groups, boundary = [], [], [] + for dim, tag in gmsh.model.getEntities(3): + physical = physical_of(dim, tag) + types, _, nodes = gmsh.model.mesh.getElements(dim, tag) + for element_type, element_nodes in zip(types, nodes): + _, _, _, num_nodes, _, _ = gmsh.model.mesh.getElementProperties(element_type) + for tet in np.asarray(element_nodes).reshape(-1, num_nodes): + code = 0 + for face, local in enumerate(FACE_NODES): + bc = face_bc.get(frozenset(int(tet[i]) for i in local), 0) + code |= bc << (8 * face) + cells.append(tet - 1) + groups.append(physical) + boundary.append(code) + return vertices, np.asarray(cells), np.asarray(groups), np.asarray(boundary) + + +def periodic_identification(num_vertices): + """Each vertex is identified with the smallest vertex index of its periodic class.""" + parent = list(range(num_vertices)) + + def find(v): + while parent[v] != v: + parent[v] = parent[parent[v]] + v = parent[v] + return v + + for dim in range(3): + for _, tag in gmsh.model.getEntities(dim): + master, nodes, master_nodes, _ = gmsh.model.mesh.getPeriodicNodes(dim, tag) + if master == tag: + continue + for node, master_node in zip(nodes, master_nodes): + a, b = find(int(node) - 1), find(int(master_node) - 1) + parent[max(a, b)] = min(a, b) + return np.array([find(v) for v in range(num_vertices)]) + + +def write_reference(path, mesh_file, periodic=False): + """Reference PUML data from a written mesh file, so coordinates match their text form.""" + gmsh.clear() + gmsh.open(mesh_file) + vertices, cells, groups, boundary = reference_data() + with h5py.File(path, "w") as out: + out.create_dataset("connect", data=cells.astype("") and size of size_t (4 or 8).""" + with open(source, "rb") as f: + data = f.read() + pos = 0 + out = bytearray() + size_format = "Q" if size_bytes == 8 else "I" + + def line(): + nonlocal pos + end = data.index(b"\n", pos) + text = data[pos:end] + pos = end + 1 + return text + + def get(fmt, count=1): + nonlocal pos + fmt = f"<{count}{fmt}" + values = struct.unpack_from(fmt, data, pos) + pos += struct.calcsize(fmt) + out.extend(struct.pack(byte_order + fmt[1:].replace("Q", size_format), *values)) + return values + + def sizes(count=1): + return get("Q", count) + + assert line() == b"$MeshFormat" and line() == b"4.1 1 8" + out.extend(f"$MeshFormat\n4.1 1 {size_bytes}\n".encode()) + assert get("i")[0] == 1 + assert line() == b"" + out.extend(b"\n") + while pos < len(data): + text = line() + out.extend(text + b"\n") + if text == b"$MeshFormat": + continue + if text == b"$Entities": + counts = sizes(4) + for kind, count in enumerate(counts): + for _ in range(count): + get("i") + get("d", 3 if kind == 0 else 6) + get("i", sizes()[0]) + if kind > 0: + get("i", sizes()[0]) + elif text == b"$Nodes": + num_blocks = sizes(4)[0] + for _ in range(num_blocks): + dim, _, parametric = get("i", 3) + count = sizes()[0] + sizes(count) + get("d", count * (3 + (dim if parametric else 0))) + elif text == b"$Elements": + num_blocks = sizes(4)[0] + for _ in range(num_blocks): + _, _, element_type = get("i", 3) + count = sizes()[0] + sizes(count * (1 + ELEMENT_NODES[element_type])) + elif text == b"$Periodic": + for _ in range(sizes()[0]): + get("i", 3) + get("d", sizes()[0]) + sizes(2 * sizes()[0]) + elif text.startswith(b"$End"): + continue + else: + raise ValueError(f"unexpected section {text}") + assert line() == b"" + end = line() + assert end == b"$End" + text[1:] + out.extend(b"\n" + end + b"\n") + with open(target, "wb") as f: + f.write(out) + + +def counts_exercise_chunking(count): + # the element/vertex counts are chosen such that the default chunk distribution differs + # from a ceil-based one for 3 and for 4 ranks + return count % 3 == 1 and count % 4 in (1, 2) + + +def generate_layered(): + for size_top in itertools.count(0.2, 0.0025): + gmsh.clear() + build_model(size_top, 2.0 * size_top) + vertices, cells, _, _ = reference_data() + if counts_exercise_chunking(len(cells)) and counts_exercise_chunking(len(vertices)): + break + print(f"layered: {len(cells)} cells, {len(vertices)} vertices, size {size_top:.4f}") + + write_mesh("layered-v41.msh", 4.1) + write_mesh("layered-binary-v41.msh", 4.1, binary=True) + write_mesh("layered-v22.msh", 2.2) + gmsh.write("layered.neu") + write_reference("layered.puml.h5", "layered-v41.msh") + + +def generate_coarse(): + gmsh.clear() + build_model(0.5, 1.0) + vertices, cells, _, _ = reference_data() + print(f"coarse: {len(cells)} cells, {len(vertices)} vertices") + + write_mesh("coarse-v41.msh", 4.1) + write_mesh("coarse-binary-v41.msh", 4.1, binary=True) + + # dense node tags that do not start at 1 + node_tags, _, _ = gmsh.model.mesh.getNodes() + gmsh.model.mesh.renumberNodes(node_tags, node_tags + 1000) + write_mesh("coarse-offset-v41.msh", 4.1) + gmsh.model.mesh.renumberNodes(node_tags + 1000, node_tags) + + gmsh.model.mesh.setOrder(2) + write_mesh("coarse-o2-v41.msh", 4.1) + write_mesh("coarse-o2-binary-v41.msh", 4.1, binary=True) + write_mesh("coarse-o2-v22.msh", 2.2) + gmsh.model.mesh.setOrder(1) + + gmsh.model.mesh.partition(2) + write_mesh("coarse-partitioned-v41.msh", 4.1) + + # the converter reproduces gmsh's layout exactly before it is used for other layouts + convert_binary("coarse-binary-v41.msh", "coarse-binary-copy.msh", "<", 8) + with open("coarse-binary-v41.msh", "rb") as a, open("coarse-binary-copy.msh", "rb") as b: + assert a.read() == b.read() + os.remove("coarse-binary-copy.msh") + convert_binary("coarse-binary-v41.msh", "coarse-binary-bigendian-v41.msh", ">", 8) + convert_binary("coarse-binary-v41.msh", "coarse-binary-size4-v41.msh", "<", 4) + with open("coarse-binary-v41.msh", "rb") as f: + data = f.read() + with open("coarse-binary-truncated-v41.msh", "wb") as f: + f.write(data[: data.index(b"$Elements") + 400]) + + write_reference("coarse.puml.h5", "coarse-v41.msh") + write_reference("coarse-o2.puml.h5", "coarse-o2-v41.msh") + + +def generate_periodic(): + gmsh.clear() + gmsh.model.add("periodic") + gmsh.model.occ.addBox(0, 0, 0, 1, 1, 1) + gmsh.model.occ.synchronize() + translation = [1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + left = [tag for _, tag in gmsh.model.getEntitiesInBoundingBox(-TOL, -TOL, -TOL, TOL, 1 + TOL, 1 + TOL, 2)] + right = [tag for _, tag in gmsh.model.getEntitiesInBoundingBox(1 - TOL, -TOL, -TOL, 1 + TOL, 1 + TOL, 1 + TOL, 2)] + gmsh.model.mesh.setPeriodic(2, right, left, translation) + gmsh.model.addPhysicalGroup(3, [1], 1) + others = [tag for _, tag in gmsh.model.getEntities(2) if tag not in left + right] + gmsh.model.addPhysicalGroup(2, others, FREE_SURFACE) + gmsh.option.setNumber("Mesh.MeshSizeMax", 0.4) + gmsh.model.mesh.generate(3) + write_mesh("periodic-v41.msh", 4.1) + write_mesh("periodic-binary-v41.msh", 4.1, binary=True) + gmsh.option.setNumber("Mesh.SaveParametric", 1) + write_mesh("periodic-parametric-v41.msh", 4.1) + gmsh.option.setNumber("Mesh.SaveParametric", 0) + write_reference("periodic.puml.h5", "periodic-v41.msh", periodic=True) + + +TINY_V41 = """$MeshFormat +4.1 0 8 +$EndMeshFormat +$Entities +0 0 0 1 +1 0 0 0 1 1 1 1 7 0 +$EndEntities +$Nodes +1 4 1 4 +3 1 0 4 +1 +2 +3 +4 +0 0 0 +1 0 0 +0 1 0 +0 0 1 +$EndNodes +$Elements +1 1 1 1 +3 1 4 1 +1 1 2 3 4 +$EndElements +""" + +TINY_V22 = """$MeshFormat +2.2 0 8 +$EndMeshFormat +$Nodes +4 +1 0 0 0 +2 1 0 0 +3 0 1 0 +4 0 0 1 +$EndNodes +$Elements +1 +1 4 2 7 1 1 2 3 4 +$EndElements +""" + + +def tiny_binary(node_tags, element_nodes, node_header=(1, 4, 1, 4)): + """The single tetrahedron of TINY_V41 as binary MSH 4.1 file.""" + data = b"$MeshFormat\n4.1 1 8\n" + struct.pack(" + + + + + + diff --git a/tests/fixtures/velocity-sizefield-0.bin b/tests/fixtures/velocity-sizefield-0.bin new file mode 100644 index 0000000..8b7bbbf Binary files /dev/null and b/tests/fixtures/velocity-sizefield-0.bin differ diff --git a/tests/fixtures/velocity-sizefield-1.bin b/tests/fixtures/velocity-sizefield-1.bin new file mode 100644 index 0000000..f8a5dda Binary files /dev/null and b/tests/fixtures/velocity-sizefield-1.bin differ diff --git a/tests/fixtures/velocity-sizefield.xml.in b/tests/fixtures/velocity-sizefield.xml.in new file mode 100644 index 0000000..4ce36ee --- /dev/null +++ b/tests/fixtures/velocity-sizefield.xml.in @@ -0,0 +1,16 @@ + + + + + + +