diff --git a/CMakeLists.txt b/CMakeLists.txt index d7c7318458823c05c9f385c1b5c50118534f6a76..93eb3fa005eda92b4884d61cfa8963fc237b5354 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -706,6 +706,12 @@ else() message(" gnuplot: not found!") endif() +if (GMSH) + message(" gmsh: ${GMSH}") +else() + message(" gmsh: not found!") +endif() + if (PYGMENTIZE) message(" pygmentize: ${PYGMENTIZE}") else() @@ -718,7 +724,7 @@ else() message(" pdflatex: not found!") endif() -if (NOT EMACS OR NOT GNUPLOT_FOUND) +if (NOT EMACS OR NOT GNUPLOT_FOUND OR NOT GMSH) message(" ** Cannot build documentation: missing ") elseif(NOT LATEX_PDFLATEX_FOUND OR NOT PYGMENTIZE) message(" ** Cannot build pdf documentation: missing") @@ -729,6 +735,9 @@ endif() if (NOT GNUPLOT_FOUND) message(" - gnuplot") endif() +if (NOT GMSH) + message(" - gmsh") +endif() if (NOT LATEX_PDFLATEX_FOUND) message(" - pdflatex") endif() diff --git a/README.md b/README.md index 62d6b43773cb0164a29e8a3716a818c5ec219c7d..36e460a851a53d40b4ab442460594091fa22b20f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ apt install slepc-dev ### User documentation -To build documentation one requires `emacs` and `gnuplot`, +To build documentation one requires `emacs`, `gmsh` and `gnuplot`, additionally since examples results are generated, the documentation can only be produced after the compilation of `pugs` itself. @@ -94,6 +94,10 @@ To install `emacs` on Debian-like systems ```shell apt install emacs ``` +To install `gmsh` on Debian-like systems +```shell +apt install gmsh +``` To install `gnuplot` one can either use ```shell apt install gnuplot-nox @@ -106,6 +110,7 @@ apt install gnuplot-x11 > When building the documentation for the first time, a local `emacs` > configuration is generated. *This requires an internet connection.* + These packages are enough to build the html documentation. To build the pdf documentation one requires a few more packages: `pdflatex` (actually a fresh texlive installation is probably necessary) and `pygmentize` diff --git a/cmake/PugsDoc.cmake b/cmake/PugsDoc.cmake index 1f8701ccc20f6416b529908f3893d971e86c7452..b067a5cc828ec0c245e8d51c0bda46a96dcce702 100644 --- a/cmake/PugsDoc.cmake +++ b/cmake/PugsDoc.cmake @@ -12,10 +12,13 @@ find_program(PYGMENTIZE pygmentize) # check for gnuplot find_package(Gnuplot) +# check for gmsh +find_program(GMSH NAMES gmsh) + add_custom_target(userdoc) add_custom_target(doc DEPENDS userdoc) -if (EMACS AND GNUPLOT_FOUND) +if (EMACS AND GNUPLOT_FOUND AND GMSH) add_custom_command( OUTPUT "${PUGS_BINARY_DIR}/doc" @@ -129,6 +132,13 @@ else() COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "gnuplot missing") add_dependencies(userdoc userdoc-missing-gnuplot) endif() + + if (NOT GMSH) + add_custom_target(userdoc-missing-gmsh + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --no-newline "Cannot build documentation: " + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "gmsh missing") + add_dependencies(userdoc userdoc-missing-gmsh) + endif() endif() add_dependencies(doc userdoc) diff --git a/src/language/utils/OFStream.cpp b/src/language/utils/OFStream.cpp index 39b7cc8ecedee3ff76e3d6d54790d25f90d01569..ca7c77fad8a69fd79ae2b512635acbb7f03277fd 100644 --- a/src/language/utils/OFStream.cpp +++ b/src/language/utils/OFStream.cpp @@ -1,10 +1,12 @@ #include <language/utils/OFStream.hpp> +#include <utils/Filesystem.hpp> #include <utils/Messenger.hpp> OFStream::OFStream(const std::string& filename) { if (parallel::rank() == 0) { + createDirectoryIfNeeded(filename); m_fstream.open(filename); if (m_fstream.is_open()) { m_ostream = &m_fstream; diff --git a/src/mesh/MeshFlatNodeBoundary.cpp b/src/mesh/MeshFlatNodeBoundary.cpp index b7de42715eff88510119b54507ed32323365c817..459a827c84ead76ed23acdf28cbd8525923694da 100644 --- a/src/mesh/MeshFlatNodeBoundary.cpp +++ b/src/mesh/MeshFlatNodeBoundary.cpp @@ -87,57 +87,108 @@ MeshFlatNodeBoundary<2>::_getNormal(const Mesh<Connectivity<2>>& mesh) template <> TinyVector<3, double> -MeshFlatNodeBoundary<3>::_getNormal(const Mesh<Connectivity<3>>& mesh) +MeshFlatNodeBoundary<3>::_getFarestNode(const Mesh<Connectivity<3>>& mesh, const Rd& x0, const Rd& x1) { - using R3 = TinyVector<3, double>; - - std::array<R3, 6> bounds = this->_getBounds(mesh); + const NodeValue<const Rd>& xr = mesh.xr(); + const auto node_number = mesh.connectivity().nodeNumber(); - const R3& xmin = bounds[0]; - const R3& ymin = bounds[1]; - const R3& zmin = bounds[2]; - const R3& xmax = bounds[3]; - const R3& ymax = bounds[4]; - const R3& zmax = bounds[5]; + using NodeNumberType = std::remove_const_t<typename decltype(node_number)::data_type>; - const R3 u = xmax - xmin; - const R3 v = ymax - ymin; - const R3 w = zmax - zmin; + Rd t = x1 - x0; + t *= 1. / l2Norm(t); - const R3 uv = crossProduct(u, v); - const double uv_l2 = dot(uv, uv); + double farest_distance = 0; + Rd farest_x = zero; + NodeNumberType farest_number = std::numeric_limits<NodeNumberType>::max(); - R3 normal = uv; - double normal_l2 = uv_l2; + auto node_list = this->m_ref_node_list.list(); - const R3 uw = crossProduct(u, w); - const double uw_l2 = dot(uw, uw); + for (size_t i_node = 0; i_node < node_list.size(); ++i_node) { + const NodeId& node_id = node_list[i_node]; + const Rd& x = xr[node_id]; + const double distance = l2Norm(crossProduct(t, x - x0)); - if (uw_l2 > uv_l2) { - normal = uw; - normal_l2 = uw_l2; + if ((distance > farest_distance) or ((distance == farest_distance) and (node_number[node_id] < farest_number))) { + farest_distance = distance; + farest_number = node_number[node_id]; + farest_x = x; + } } - const R3 vw = crossProduct(v, w); - const double vw_l2 = dot(vw, vw); + if (parallel::size()) { + Array<double> farest_distance_array = parallel::allGather(farest_distance); + Array<Rd> farest_x_array = parallel::allGather(farest_x); + Array<NodeNumberType> farest_number_array = parallel::allGather(farest_number); - if (vw_l2 > normal_l2) { - normal = vw; - normal_l2 = vw_l2; + Assert(farest_distance_array.size() == farest_x_array.size()); + Assert(farest_distance_array.size() == farest_number_array.size()); + + for (size_t i = 0; i < farest_distance_array.size(); ++i) { + if ((farest_distance_array[i] > farest_distance) or + ((farest_distance_array[i] == farest_distance) and (farest_number_array[i] < farest_number))) { + farest_distance = farest_distance_array[i]; + farest_number = farest_number_array[i]; + farest_x = farest_x_array[i]; + } + } } - if (normal_l2 == 0) { + return farest_x; +} + +template <> +TinyVector<3, double> +MeshFlatNodeBoundary<3>::_getNormal(const Mesh<Connectivity<3>>& mesh) +{ + using R3 = TinyVector<3, double>; + + std::array<R3, 2> diagonal = [](const std::array<R3, 6>& bounds) { + size_t max_i = 0; + size_t max_j = 0; + double max_length = 0; + + for (size_t i = 0; i < bounds.size(); ++i) { + for (size_t j = i + 1; j < bounds.size(); ++j) { + double length = l2Norm(bounds[i] - bounds[j]); + if (length > max_length) { + max_i = i; + max_j = j; + max_length = length; + } + } + } + + return std::array<R3, 2>{bounds[max_i], bounds[max_j]}; + }(this->_getBounds(mesh)); + + const R3& x0 = diagonal[0]; + const R3& x1 = diagonal[1]; + + if (x0 == x1) { std::ostringstream ost; ost << "invalid boundary \"" << rang::fgB::yellow << m_ref_node_list.refId() << rang::style::reset << "\": unable to compute normal"; throw NormalError(ost.str()); } - const double length = sqrt(normal_l2); + const R3 x2 = this->_getFarestNode(mesh, x0, x1); + + const R3 u = x1 - x0; + const R3 v = x2 - x0; + + R3 normal = crossProduct(u, v); + const double normal_norm = l2Norm(normal); + + if (normal_norm == 0) { + std::ostringstream ost; + ost << "invalid boundary \"" << rang::fgB::yellow << m_ref_node_list.refId() << rang::style::reset + << "\": unable to compute normal"; + throw NormalError(ost.str()); + } - normal *= 1. / length; + normal *= (1. / normal_norm); - this->_checkBoundaryIsFlat(normal, 1. / 6. * (xmin + xmax + ymin + ymax + zmin + zmax), length, mesh); + this->_checkBoundaryIsFlat(normal, 1. / 3. * (x0 + x1 + x2), normal_norm, mesh); return normal; } diff --git a/src/mesh/MeshFlatNodeBoundary.hpp b/src/mesh/MeshFlatNodeBoundary.hpp index 8486f02c7377fe2987560d29f2ee7cc45c1b44cf..54a10ae1c1412005440cd9e6cc49973c017265e0 100644 --- a/src/mesh/MeshFlatNodeBoundary.hpp +++ b/src/mesh/MeshFlatNodeBoundary.hpp @@ -13,21 +13,26 @@ class [[nodiscard]] MeshFlatNodeBoundary final private: const Rd m_outgoing_normal; + Rd _getFarestNode(const Mesh<Connectivity<Dimension>>& mesh, const Rd& x0, const Rd& x1); + Rd _getNormal(const Mesh<Connectivity<Dimension>>& mesh); - void _checkBoundaryIsFlat(const TinyVector<Dimension, double>& normal, const TinyVector<Dimension, double>& origin, - const double length, const Mesh<Connectivity<Dimension>>& mesh) const; + void _checkBoundaryIsFlat(const TinyVector<Dimension, double>& normal, + const TinyVector<Dimension, double>& origin, + const double length, + const Mesh<Connectivity<Dimension>>& mesh) const; Rd _getOutgoingNormal(const Mesh<Connectivity<Dimension>>& mesh); public: - const Rd& outgoingNormal() const + const Rd& + outgoingNormal() const { return m_outgoing_normal; } MeshFlatNodeBoundary& operator=(const MeshFlatNodeBoundary&) = default; - MeshFlatNodeBoundary& operator=(MeshFlatNodeBoundary&&) = default; + MeshFlatNodeBoundary& operator=(MeshFlatNodeBoundary&&) = default; template <size_t MeshDimension> friend MeshFlatNodeBoundary<MeshDimension> getMeshFlatNodeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh, @@ -47,7 +52,7 @@ class [[nodiscard]] MeshFlatNodeBoundary final public: MeshFlatNodeBoundary() = default; MeshFlatNodeBoundary(const MeshFlatNodeBoundary&) = default; - MeshFlatNodeBoundary(MeshFlatNodeBoundary &&) = default; + MeshFlatNodeBoundary(MeshFlatNodeBoundary&&) = default; ~MeshFlatNodeBoundary() = default; }; diff --git a/src/output/GnuplotWriter.cpp b/src/output/GnuplotWriter.cpp index 50969799eeb4250b28d21722e8476ab86a88d9eb..8ccf7767180b30524ba54f7c4f085c3ad35783d2 100644 --- a/src/output/GnuplotWriter.cpp +++ b/src/output/GnuplotWriter.cpp @@ -5,6 +5,7 @@ #include <mesh/Mesh.hpp> #include <mesh/MeshData.hpp> #include <mesh/MeshDataManager.hpp> +#include <utils/Filesystem.hpp> #include <utils/Messenger.hpp> #include <utils/PugsTraits.hpp> #include <utils/RevisionInfo.hpp> @@ -243,8 +244,16 @@ GnuplotWriter::_write(const MeshType& mesh, const OutputNamedItemDataSet& output_named_item_data_set, std::optional<double> time) const { + createDirectoryIfNeeded(_getFilename()); + if (parallel::rank() == 0) { std::ofstream fout{_getFilename()}; + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot create file \"" << rang::fgB::yellow << _getFilename() << rang::fg::reset << '"'; + throw NormalError(error_msg.str()); + } + fout.precision(15); fout.setf(std::ios_base::scientific); @@ -286,6 +295,12 @@ GnuplotWriter::_write(const MeshType& mesh, for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { if (i_rank == parallel::rank()) { std::ofstream fout(_getFilename(), std::ios_base::app); + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot open file \"" << rang::fgB::yellow << _getFilename() << rang::fg::reset << '"'; + throw NormalError(error_msg.str()); + } + fout.precision(15); fout.setf(std::ios_base::scientific); diff --git a/src/output/GnuplotWriter1D.cpp b/src/output/GnuplotWriter1D.cpp index a3156904843bdeb77be718de6a10614a3333afae..3e0740446e961505eb01e36eee5ba168e0f47dcc 100644 --- a/src/output/GnuplotWriter1D.cpp +++ b/src/output/GnuplotWriter1D.cpp @@ -5,6 +5,7 @@ #include <mesh/Mesh.hpp> #include <mesh/MeshData.hpp> #include <mesh/MeshDataManager.hpp> +#include <utils/Filesystem.hpp> #include <utils/Messenger.hpp> #include <utils/PugsTraits.hpp> #include <utils/RevisionInfo.hpp> @@ -322,6 +323,12 @@ GnuplotWriter1D::_write(const MeshType& mesh, if (parallel::rank() == 0) { fout.open(_getFilename()); + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot create file \"" << rang::fgB::yellow << _getFilename() << rang::fg::reset << '"'; + throw NormalError(error_msg.str()); + } + fout.precision(15); fout.setf(std::ios_base::scientific); fout << _getDateAndVersionComment(); diff --git a/src/output/VTKWriter.cpp b/src/output/VTKWriter.cpp index 0a40c7265e05a17d31e59bd41eeb10cb63935821..6ec6295e44b54bef9ea21c110120fdd873f71488 100644 --- a/src/output/VTKWriter.cpp +++ b/src/output/VTKWriter.cpp @@ -4,6 +4,7 @@ #include <mesh/Mesh.hpp> #include <mesh/MeshData.hpp> #include <mesh/MeshDataManager.hpp> +#include <utils/Filesystem.hpp> #include <utils/Messenger.hpp> #include <utils/RevisionInfo.hpp> #include <utils/Stringify.hpp> @@ -364,8 +365,16 @@ VTKWriter::_write(const MeshType& mesh, output_named_item_data_set.add(NamedItemData{"cell_number", mesh.connectivity().cellNumber()}); output_named_item_data_set.add(NamedItemData{"node_number", mesh.connectivity().nodeNumber()}); + createDirectoryIfNeeded(m_base_filename); + if (parallel::rank() == 0) { // write PVTK file std::ofstream fout(_getFilenamePVTU()); + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot create file \"" << rang::fgB::yellow << _getFilenamePVTU() << rang::fg::reset << '"'; + throw NormalError(error_msg.str()); + } + fout << "<?xml version=\"1.0\"?>\n"; fout << _getDateAndVersionComment(); fout << "<VTKFile type=\"PUnstructuredGrid\">\n"; @@ -424,7 +433,7 @@ VTKWriter::_write(const MeshType& mesh, fout << "</PCellData>\n"; for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) { - fout << "<Piece Source=\"" << _getFilenameVTU(i_rank) << "\"/>\n"; + fout << "<Piece Source=" << std::filesystem::path{_getFilenameVTU(i_rank)}.filename() << "/>\n"; } fout << "</PUnstructuredGrid>\n"; fout << "</VTKFile>\n"; @@ -435,6 +444,13 @@ VTKWriter::_write(const MeshType& mesh, // write VTK files std::ofstream fout(_getFilenameVTU(parallel::rank())); + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot create file \"" << rang::fgB::yellow << _getFilenameVTU(parallel::rank()) << rang::fg::reset + << '"'; + throw NormalError(error_msg.str()); + } + fout << "<?xml version=\"1.0\"?>\n"; fout << _getDateAndVersionComment(); fout << "<VTKFile type=\"UnstructuredGrid\""; @@ -632,7 +648,13 @@ VTKWriter::_write(const MeshType& mesh, } if (parallel::rank() == 0) { // write PVD file - std::ofstream fout(m_base_filename + ".pvd"); + const std::string pvd_filename = m_base_filename + ".pvd"; + std::ofstream fout(pvd_filename); + if (not fout) { + std::ostringstream error_msg; + error_msg << "cannot create file \"" << rang::fgB::yellow << pvd_filename << rang::fg::reset << '"'; + throw NormalError(error_msg.str()); + } fout << "<?xml version=\"1.0\"?>\n"; fout << _getDateAndVersionComment(); @@ -645,11 +667,13 @@ VTKWriter::_write(const MeshType& mesh, sout << m_base_filename; sout << '.' << std::setfill('0') << std::setw(4) << i_time << ".pvtu"; - fout << "<DataSet timestep=\"" << m_period_manager->savedTime(i_time) << "\" file=\"" << sout.str() << "\"/>\n"; + fout << "<DataSet timestep=\"" << m_period_manager->savedTime(i_time) + << "\" file=" << std::filesystem::path{sout.str()}.filename() << "/>\n"; } - fout << "<DataSet timestep=\"" << *time << "\" file=\"" << _getFilenamePVTU() << "\"/>\n"; + fout << "<DataSet timestep=\"" << *time << "\" file=" << std::filesystem::path{_getFilenamePVTU()}.filename() + << "/>\n"; } else { - fout << "<DataSet file=\"" << _getFilenamePVTU() << "\"/>\n"; + fout << "<DataSet file=" << std::filesystem::path{_getFilenamePVTU()}.filename() << "/>\n"; } fout << "</Collection>\n"; diff --git a/src/scheme/FluxingAdvectionSolver.cpp b/src/scheme/FluxingAdvectionSolver.cpp index 264ecef042956e6b77230e00f49cd321b50dcc6a..754c6e3ac6448a96e5e05c0a7f60967243b4798f 100644 --- a/src/scheme/FluxingAdvectionSolver.cpp +++ b/src/scheme/FluxingAdvectionSolver.cpp @@ -1,5 +1,8 @@ #include <scheme/FluxingAdvectionSolver.hpp> +#include <analysis/GaussQuadratureDescriptor.hpp> +#include <analysis/QuadratureManager.hpp> +#include <geometry/PrismTransformation.hpp> #include <language/utils/EvaluateAtPoints.hpp> #include <language/utils/InterpolateItemArray.hpp> #include <language/utils/InterpolateItemValue.hpp> @@ -322,6 +325,10 @@ template <> FaceValue<double> FluxingAdvectionSolver<3>::_computeAlgebraicFluxingVolume() { + // due to the z component of the jacobian determinant, degree 3 + // polynomials must be exactly integrated + const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(3)); + const auto face_to_node_matrix = m_old_mesh->connectivity().faceToNodeMatrix(); FaceValue<double> algebraic_fluxing_volume(m_new_mesh->connectivity()); auto old_xr = m_old_mesh->xr(); @@ -329,31 +336,10 @@ FluxingAdvectionSolver<3>::_computeAlgebraicFluxingVolume() parallel_for( m_new_mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) { const auto& face_to_node = face_to_node_matrix[face_id]; - if (face_to_node.size() == 4) { - const Rd& x0 = old_xr[face_to_node[0]]; - const Rd& x1 = old_xr[face_to_node[1]]; - const Rd& x2 = old_xr[face_to_node[2]]; - const Rd& x3 = old_xr[face_to_node[3]]; - - const Rd& x4 = new_xr[face_to_node[0]]; - const Rd& x5 = new_xr[face_to_node[1]]; - const Rd& x6 = new_xr[face_to_node[2]]; - const Rd& x7 = new_xr[face_to_node[3]]; - - const Rd& a1 = x6 - x1; - const Rd& a2 = x6 - x3; - const Rd& a3 = x6 - x4; - - const Rd& b1 = x7 - x0; - const Rd& b2 = x5 - x0; - const Rd& b3 = x2 - x0; - TinyMatrix<3> M1(a1 + b1, a2, a3); - TinyMatrix<3> M2(b1, a2 + b2, a3); - TinyMatrix<3> M3(a1, b2, a3 + b3); + const size_t face_nb_node = face_to_node.size(); - algebraic_fluxing_volume[face_id] = (det(M1) + det(M2) + det(M3)) / 12; - } else if (face_to_node.size() == 3) { + if (face_nb_node == 3) { const Rd& x0 = old_xr[face_to_node[0]]; const Rd& x1 = old_xr[face_to_node[1]]; const Rd& x2 = old_xr[face_to_node[2]]; @@ -362,21 +348,40 @@ FluxingAdvectionSolver<3>::_computeAlgebraicFluxingVolume() const Rd& x4 = new_xr[face_to_node[1]]; const Rd& x5 = new_xr[face_to_node[2]]; - const Rd& a1 = x5 - x1; - const Rd& a2 = x5 - x2; - const Rd& a3 = x5 - x3; + double volume = 0; - const Rd& b1 = x5 - x0; - const Rd& b2 = x4 - x0; - const Rd& b3 = x2 - x0; - - TinyMatrix<3> M1(a1 + b1, a2, a3); - TinyMatrix<3> M2(b1, a2 + b2, a3); - TinyMatrix<3> M3(a1, b2, a3 + b3); + PrismTransformation T(x0, x1, x2, x3, x4, x5); + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + volume += gauss.weight(i) * T.jacobianDeterminant(gauss.point(i)); + } - algebraic_fluxing_volume[face_id] = (det(M1) + det(M2) + det(M3)) / 12; + algebraic_fluxing_volume[face_id] = volume; } else { - throw NotImplementedError("Not implemented for non quad faces"); + Rd xg_old = zero; + for (size_t i_node = 0; i_node < face_nb_node; ++i_node) { + xg_old += old_xr[face_to_node[i_node]]; + } + xg_old *= 1. / face_nb_node; + Rd xg_new = zero; + for (size_t i_node = 0; i_node < face_nb_node; ++i_node) { + xg_new += new_xr[face_to_node[i_node]]; + } + xg_new *= 1. / face_nb_node; + + double volume = 0; + for (size_t i_node = 0; i_node < face_nb_node; ++i_node) { + PrismTransformation T(xg_old, // + old_xr[face_to_node[i_node]], // + old_xr[face_to_node[(i_node + 1) % face_nb_node]], // + xg_new, // + new_xr[face_to_node[i_node]], // + new_xr[face_to_node[(i_node + 1) % face_nb_node]]); + for (size_t i = 0; i < gauss.numberOfPoints(); ++i) { + volume += gauss.weight(i) * T.jacobianDeterminant(gauss.point(i)); + } + } + + algebraic_fluxing_volume[face_id] = volume; } }); diff --git a/src/utils/Filesystem.hpp b/src/utils/Filesystem.hpp new file mode 100644 index 0000000000000000000000000000000000000000..63af52b2e432f1baf55d2cbfd895485a408f7e10 --- /dev/null +++ b/src/utils/Filesystem.hpp @@ -0,0 +1,23 @@ +#ifndef FILESYSTEM_HPP +#define FILESYSTEM_HPP + +#include <utils/Exceptions.hpp> +#include <utils/PugsMacros.hpp> + +#include <filesystem> + +PUGS_INLINE void +createDirectoryIfNeeded(const std::string& filename) +{ + std::filesystem::path path = std::filesystem::path{filename}.parent_path(); + if (not path.empty()) { + try { + std::filesystem::create_directories(path); + } + catch (std::filesystem::filesystem_error& e) { + throw NormalError(e.what()); + } + } +} + +#endif // FILESYSTEM_HPP diff --git a/tests/test_MeshFlatNodeBoundary.cpp b/tests/test_MeshFlatNodeBoundary.cpp index 9b9169f73c4306f9591ba0a9c0f5c1c622f7ef69..760f5564226f77fa53ec8de15f3f85552f02cbc4 100644 --- a/tests/test_MeshFlatNodeBoundary.cpp +++ b/tests/test_MeshFlatNodeBoundary.cpp @@ -1133,6 +1133,211 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]") } } + SECTION("rotated diamond") + { + SECTION("2D") + { + static constexpr size_t Dimension = 2; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R2 = TinyVector<2>; + + auto T = [](const R2& x) -> R2 { return R2{x[0] + 0.1 * x[1], x[1] + 0.1 * x[0]}; }; + + SECTION("cartesian 2d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian2DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R2> rotated_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = T(xr[node_id]); }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {0, 1, 2, 3}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& node_boundary = getMeshFlatNodeBoundary(mesh, numbered_boundary_descriptor); + + auto node_list = get_node_list_from_tag(tag, connectivity); + REQUIRE(is_same(node_boundary.nodeList(), node_list)); + + R2 normal = zero; + + switch (tag) { + case 0: { + normal = 1. / std::sqrt(1.01) * R2{-1, 0.1}; + break; + } + case 1: { + normal = 1. / std::sqrt(1.01) * R2{1, -0.1}; + break; + } + case 2: { + normal = 1. / std::sqrt(1.01) * R2{0.1, -1}; + break; + } + case 3: { + normal = 1. / std::sqrt(1.01) * R2{-0.1, 1}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + REQUIRE(l2Norm(node_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"}; + + for (const auto& name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor); + + auto node_list = get_node_list_from_name(name, connectivity); + REQUIRE(is_same(node_boundary.nodeList(), node_list)); + + R2 normal = zero; + + if (name == "XMIN") { + normal = 1. / std::sqrt(1.01) * R2{-1, 0.1}; + } else if (name == "XMAX") { + normal = 1. / std::sqrt(1.01) * R2{1, -0.1}; + } else if (name == "YMIN") { + normal = 1. / std::sqrt(1.01) * R2{0.1, -1}; + } else if (name == "YMAX") { + normal = 1. / std::sqrt(1.01) * R2{-0.1, 1}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(node_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + + SECTION("3D") + { + static constexpr size_t Dimension = 3; + + using ConnectivityType = Connectivity<Dimension>; + using MeshType = Mesh<ConnectivityType>; + + using R3 = TinyVector<3>; + + auto T = [](const R3& x) -> R3 { + return R3{x[0] + 0.1 * x[1] + 0.2 * x[2], x[1] + 0.1 * x[0] + 0.1 * x[2], x[2] + 0.1 * x[0]}; + }; + + SECTION("cartesian 3d") + { + std::shared_ptr p_mesh = MeshDataBaseForTests::get().cartesian3DMesh(); + + const ConnectivityType& connectivity = p_mesh->connectivity(); + + auto xr = p_mesh->xr(); + + NodeValue<R3> rotated_xr{connectivity}; + + parallel_for( + connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { rotated_xr[node_id] = T(xr[node_id]); }); + + MeshType mesh{p_mesh->shared_connectivity(), rotated_xr}; + + { + const std::set<size_t> tag_set = {0, 1, 2, 3, 4, 5}; + + for (auto tag : tag_set) { + NumberedBoundaryDescriptor numbered_boundary_descriptor(tag); + const auto& node_boundary = getMeshFlatNodeBoundary(mesh, numbered_boundary_descriptor); + + auto node_list = get_node_list_from_tag(tag, connectivity); + REQUIRE(is_same(node_boundary.nodeList(), node_list)); + + R3 normal = zero; + + switch (tag) { + case 0: { + normal = R3{-0.977717523265611, 0.0977717523265611, 0.185766329420466}; + break; + } + case 1: { + normal = R3{0.977717523265611, -0.0977717523265612, -0.185766329420466}; + break; + } + case 2: { + normal = R3{0.0911512175788074, -0.992535480302569, 0.0810233045144955}; + break; + } + case 3: { + normal = R3{-0.0911512175788074, 0.992535480302569, -0.0810233045144955}; + break; + } + case 4: { + normal = R3{0.100493631166705, -0.0100493631166705, -0.994886948550377}; + break; + } + case 5: { + normal = R3{-0.100493631166705, 0.0100493631166705, 0.994886948550377}; + break; + } + default: { + FAIL("unexpected tag number"); + } + } + + REQUIRE(l2Norm(node_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + + { + const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"}; + + for (const auto& name : name_set) { + NamedBoundaryDescriptor named_boundary_descriptor(name); + const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor); + + auto node_list = get_node_list_from_name(name, connectivity); + + REQUIRE(is_same(node_boundary.nodeList(), node_list)); + + R3 normal = zero; + + if (name == "XMIN") { + normal = R3{-0.977717523265611, 0.0977717523265611, 0.185766329420466}; + } else if (name == "XMAX") { + normal = R3{0.977717523265611, -0.0977717523265612, -0.185766329420466}; + } else if (name == "YMIN") { + normal = R3{0.0911512175788074, -0.992535480302569, 0.0810233045144955}; + } else if (name == "YMAX") { + normal = R3{-0.0911512175788074, 0.992535480302569, -0.0810233045144955}; + } else if (name == "ZMIN") { + normal = R3{0.100493631166705, -0.0100493631166705, -0.994886948550377}; + } else if (name == "ZMAX") { + normal = R3{-0.100493631166705, 0.0100493631166705, 0.994886948550377}; + } else { + FAIL("unexpected name: " + name); + } + + REQUIRE(l2Norm(node_boundary.outgoingNormal() - normal) == Catch::Approx(0).margin(1E-13)); + } + } + } + } + } + SECTION("curved mesh") { SECTION("2D") diff --git a/tests/test_OFStream.cpp b/tests/test_OFStream.cpp index fbb5818dbc70451ff212c820edacfa595963e961..6ca7d98df0a6f36bf30add2b891a659beba757c2 100644 --- a/tests/test_OFStream.cpp +++ b/tests/test_OFStream.cpp @@ -13,7 +13,8 @@ TEST_CASE("OFStream", "[language]") { SECTION("ofstream") { - const std::string basename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("ofstream_"); + const std::string basename = + std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("ofstream_dir").append("ofstream_"); const std::string filename = basename + stringify(parallel::rank()); // Ensures that the file is closed after this line @@ -47,13 +48,4 @@ TEST_CASE("OFStream", "[language]") REQUIRE(not std::filesystem::exists(filename)); } - - SECTION("invalid filename") - { - if (parallel::rank() == 0) { - const std::string filename = "badpath/invalidpath/ofstream"; - - REQUIRE_THROWS_WITH(std::make_shared<OFStream>(filename), "error: cannot create file " + filename); - } - } }