diff --git a/.gitignore b/.gitignore index db26c12..0dfb496 100644 --- a/.gitignore +++ b/.gitignore @@ -74,12 +74,15 @@ subprojects/libconfig/ subprojects/libcomposition/ subprojects/GridFire/ subprojects/tomlplusplus-*/ +subprojects/CLI11-*/ + qhull.wrap quill.wrap yaml-cpp.wrap cppad.wrap tomlplusplus.wrap +gtest.wrap subprojects/quill.wrap diff --git a/assets/imgs/ExampleMesh.png b/assets/imgs/ExampleMesh.png new file mode 100644 index 0000000..7460e37 Binary files /dev/null and b/assets/imgs/ExampleMesh.png differ diff --git a/assets/logo/Logo.png b/assets/logo/Logo.png new file mode 100644 index 0000000..d4b94f0 Binary files /dev/null and b/assets/logo/Logo.png differ diff --git a/assets/logo/Logo.svg b/assets/logo/Logo.svg new file mode 100644 index 0000000..fb5ba32 --- /dev/null +++ b/assets/logo/Logo.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build-config/CLI11/meson.build b/build-config/CLI11/meson.build new file mode 100644 index 0000000..a8549f3 --- /dev/null +++ b/build-config/CLI11/meson.build @@ -0,0 +1,2 @@ +cli11_proj = subproject('cli11') +cli11_dep = cli11_proj.get_variable('CLI11_dep') \ No newline at end of file diff --git a/build-config/meson.build b/build-config/meson.build index 2965f69..0557676 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,2 +1,3 @@ subdir('mfem') -subdir('libconfig') \ No newline at end of file +subdir('libconfig') +subdir('CLI11') \ No newline at end of file diff --git a/configs/default_config.toml b/configs/default_config.toml new file mode 100644 index 0000000..110ff95 --- /dev/null +++ b/configs/default_config.toml @@ -0,0 +1,10 @@ +[main] +core_steepness = 1.0 +flattening = 0.0 +include_external_domain = false +order = 3 +r_core = 1.5 +r_infinity = 6.0 +r_instability = 1e-14 +r_star = 5.0 +refinement_levels = 4 \ No newline at end of file diff --git a/configs/test_external_domain.toml b/configs/test_external_domain.toml new file mode 100644 index 0000000..90b89bb --- /dev/null +++ b/configs/test_external_domain.toml @@ -0,0 +1,10 @@ +[main] +core_steepness = 1.0 +flattening = 0.0 +include_external_domain = true +order = 3 +r_core = 1.5 +r_infinity = 6.0 +r_instability = 1e-14 +r_star = 5.0 +refinement_levels = 4 diff --git a/configs/test_flattening.toml b/configs/test_flattening.toml new file mode 100644 index 0000000..e3af871 --- /dev/null +++ b/configs/test_flattening.toml @@ -0,0 +1,10 @@ +[main] +core_steepness = 1.0 +flattening = 0.2 +include_external_domain = false +order = 3 +r_core = 1.5 +r_infinity = 6.0 +r_instability = 1e-14 +r_star = 5.0 +refinement_levels = 4 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..e69de29 diff --git a/meson.build b/meson.build index 831ddbe..6669d1a 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,27 @@ -project('stroid', 'cpp', meson_version : '>= 1.3.0', version : 'v0.1.0a0.1', default_options : ['cpp_std=c++23']) +project('stroid', 'cpp', meson_version : '>= 1.3.0', version : 'v0.1.0', default_options : ['cpp_std=c++23']) subdir('build-config') subdir('src') -subdir('tests') \ No newline at end of file + +if get_option('build_tests') + subdir('tests') +endif + +if get_option('build_tools') + subdir('tools') +endif + +if get_option('pkg_config') + pkg = import('pkgconfig') + pkg.generate( + name: 'stroid', + description: 'Stroid multi-block curvilinear mesh generation library', + version: meson.project_version(), + libraries: [ + stroid_lib + ], + subdirs: ['stroid'], + filebase: 'stroid', + install_dir: join_paths(get_option('libdir'), 'pkgconfig') + ) +endif \ No newline at end of file diff --git a/meson_options.txt b/meson_options.txt index fcb4c83..2e318cc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,3 @@ option('pkg_config', type: 'boolean', value: false, description: 'generate pkg-config file for stroid') -option('build_tests', type: 'boolean', value: false, description: 'compile subproject tests') -option('build_python', type: 'boolean', value: true, description: 'build the python bindings so you can use stroid from python.') \ No newline at end of file +option('build_tests', type: 'boolean', value: true, description: 'compile subproject tests') +option('build_tools', type: 'boolean', value: true, description: 'compile stroid command line tools') \ No newline at end of file diff --git a/readme.md b/readme.md index fc9d34e..ca4f0d5 100644 --- a/readme.md +++ b/readme.md @@ -1,2 +1,120 @@ -# stroid -A focused mesh generation suite, using an O-grid approach to generate high-quality stellar structure meshes for astrophysical simulations. \ No newline at end of file +![stroid logo](assets/logo/Logo.png) +# Stroid +## A multi-block mesh generation tool for stellar modeling + +Stroid is a simple multi-block mesh generation tool designed to generate multi-domain +meshes for 3D finite element modeling of stellar physics. It uses the MFEM library for +mesh generation and manipulation and is capable of generating high-order curvilinear +and non-singular meshes. + +> Note: Stroid is under active development and is not yet stable. Features and interfaces may change in future releases. + +## Building and Installing +Stroid uses meson as its build system, specifically we require version 1.3.0 or higher. Further, +stroid depends on C++23 standard library features, so both a compatible compiler and standard template +library are required. All other dependencies are handled by meson and will be downloaded and built +automatically. + +### Building +```bash +git clone https://github.com/4D-STAR/stroid.git +cd stroid +meson setup build +meson compile -C build +meson test -C build +meson install -C build +``` + +### Running +Stroid can be used either from the command line or from C++. The command line interface is +the simplest way to get started. After installation, the `stroid generate` command should be available in your terminal. + +```bash +stroid generate --help +``` + +The main way to interface with this is through the subcommands (currently only `generate` and `info` are available): + +```bash +stroid generate -c +``` + +```bash +stroid info -d +``` + +to save the default configuration to a file named ``default.toml`` + +### Configuration File +Stroid uses a TOML configuration file to specify the parameters for mesh generation. An example configuration +file is found below + +```toml +[main] +core_steepness = 1.0 +flattening = 0.0 +include_external_domain = false +order = 3 +r_core = 1.5 +r_infinity = 6.0 +r_instability = 1e-14 +r_star = 5.0 +refinement_levels = 4 +``` + + +| Parameter | Description | Default | +|-------------------------|-----------------------------------------------------------------------------------------------------|---------| +| refinement_levels | Number of uniform refinement levels to apply to the mesh after generation | 4 | +| order | The polynomial order of the finite elements in the mesh | 3 | +| include_external_domain | Whether to include an external domain extending to r_infinity | false | +| r_core | The radius of the core region of the star | 1.5 | +| r_star | The radius of the star | 5.0 | +| flattening | The flattening factor of the star (0 for spherical, >0 for oblate) | 0 | +| r_infinity | The outer radius of the external domain (if included) | 6.0 | +| r_instability | The radius at which no transformations are applied to the initial topology (to avoid singularities) | 1e-14 | +| core_steepness | The steepness of the transition between the core and envelope regions of the star | 1.0 | + + +If no configuration file is provided, stroid will use the default parameters listed above. Further, configuration files +need only include parameters that differ from the defaults, any parameters not specified will use the default values. + +### C++ Interface +Stroid can be used as a library in C++ projects. After installation, include the stroid header and link against the stroid library. + +A basic example of using stroid in C++ is shown below (note that you will need a glvis instance running on localhost:19916 to visualize the mesh): +```c++ +#include +#include "mfem.hpp" + +#include "stroid/config/config.h" +#include "stroid/IO/mesh.h" +#include "stroid/topology/curvilinear.h" +#include "stroid/topology/topology.h" + +#include "fourdst/config/config.h" + +int main() { + const fourdst::config::Config cfg; + + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + stroid::topology::PromoteToHighOrder(*mesh, cfg); + stroid::topology::ProjectMesh(*mesh, cfg); + + + stroid::IO::ViewMesh(*mesh, "Spheroidal Mesh", stroid::IO::VISUALIZATION_MODE::BOUNDARY_ELEMENT_ID); +} +``` + +## Example Meshes +An example mesh with the default configuration parameters is shown below (coloration indicates attribute IDs of different regions): +![Example Mesh](assets/imgs/ExampleMesh.png) + +## Funding +Stroid is developed as part of the 4D-STAR project. + +4D-STAR is funded by European Research Council (ERC) under the Horizon Europe programme (Synergy Grant agreement No. +101071505: 4D-STAR) +Work for this project is funded by the European Union. Views and opinions expressed are however those of the author(s) +only and do not necessarily reflect those of the European Union or the European Research Council. \ No newline at end of file diff --git a/src/include/stroid/IO/mesh.h b/src/include/stroid/IO/mesh.h index 7707a8c..2c1c6cc 100644 --- a/src/include/stroid/IO/mesh.h +++ b/src/include/stroid/IO/mesh.h @@ -3,14 +3,42 @@ #include "mfem.hpp" namespace stroid::IO { + /** + * @brief Visualization modes for GLVis display. + */ enum class VISUALIZATION_MODE : uint8_t { + /** @brief No attribute visualization (default rendering). */ NONE, + /** @brief Color elements by their element attribute/ID. */ ELEMENT_ID, + /** @brief Color boundary-adjacent elements by boundary attribute/ID. */ BOUNDARY_ELEMENT_ID }; + /** + * @brief Save a mesh to MFEM's native `.mesh` format. + * @param mesh Mesh to serialize. + * @param filename Output path (including extension). + */ void SaveMesh(const mfem::Mesh& mesh, const std::string& filename); + /** + * @brief Save a mesh as a ParaView VTU dataset. + * @param mesh Mesh to export. + * @param exportName Output base name (ParaView will add extensions). + */ void SaveVTU(mfem::Mesh& mesh, const std::string& exportName); - void ViewMesh(mfem::Mesh &mesh, const std::string& title, VISUALIZATION_MODE mode); + /** + * @brief Stream a mesh to a running GLVis server for interactive viewing. + * @param mesh Mesh to display. + * @param title Window title shown in GLVis. + * @param mode Attribute visualization mode. + * @param vishost GLVis server host. + * @param visport GLVis server port. + */ + void ViewMesh(mfem::Mesh &mesh, const std::string& title, VISUALIZATION_MODE mode, const std::string &vishost, int visport); + /** + * @brief Visualize boundary face valence (1=surface, 2=internal). + * @param mesh Mesh whose boundary faces are inspected. + */ void VisualizeFaceValence(mfem::Mesh& mesh); } \ No newline at end of file diff --git a/src/include/stroid/config/config.h b/src/include/stroid/config/config.h index ea1f562..083a331 100644 --- a/src/include/stroid/config/config.h +++ b/src/include/stroid/config/config.h @@ -1,18 +1,63 @@ #pragma once namespace stroid::config { + /** + * @brief Configuration parameters for stroid mesh generation. + * + * These values are typically loaded via + * `fourdst::config::Config` from a TOML file. + * The README shows the expected TOML layout under the `[main]` table. + * Unspecified keys use the defaults defined here. + */ struct MeshConfig { - int refinement_levels = 3; - int order = 2; + /** + * @brief Number of uniform refinement passes applied after topology creation. + * @toml [main].refinement_levels + */ + int refinement_levels = 4; + /** + * @brief Polynomial order for high-order elements. + * @toml [main].order + */ + int order = 3; + /** + * @brief Whether to include an external domain extending to `r_infinity`. + * @details Currently this flag does not affect mesh generation. + * @toml [main].include_external_domain + */ bool include_external_domain = false; - double r_core = 2.5; + /** + * @brief Radius of the stellar core region. + * @toml [main].r_core + */ + double r_core = 1.5; + /** + * @brief Radius of the stellar surface. + * @toml [main].r_star + */ double r_star = 5.0; + /** + * @brief Flattening factor for spheroidal shaping (0 = spherical, >0 = oblate). + * @toml [main].flattening + */ double flattening = 0; + /** + * @brief Outer radius of the external domain when enabled. + * @toml [main].r_infinity + */ double r_infinity = 6.0; + /** + * @brief Radius inside which transformations are skipped to avoid singularities. + * @toml [main].r_instability + */ double r_instability = 1e-14; + /** + * @brief Controls the smoothness/steepness of the core-to-envelope transition. + * @toml [main].core_steepness + */ double core_steepness = 1.0; }; } diff --git a/src/include/stroid/meson.build b/src/include/stroid/meson.build new file mode 100644 index 0000000..540199d --- /dev/null +++ b/src/include/stroid/meson.build @@ -0,0 +1,27 @@ +python_exe = import('python').find_installation('python3') + +version_parser = ''' +import sys, re +ver = sys.argv[1] +if ver.startswith("v"): ver = ver[1:] +m = re.match(r"^(\d+)\.(\d+)\.(\d+)(.*)$", ver) +if m: + print(f"{m.group(1)};{m.group(2)};{m.group(3)};{m.group(4)}") +else: + print("0;0;0;unknown") +''' + +ver_res = run_command(python_exe, '-c', version_parser, meson.project_version(), check: true) +ver_parts = ver_res.stdout().strip().split(';') + +config = configuration_data() +config.set('STROID_VERSION_MAJOR', ver_parts[0]) +config.set('STROID_VERSION_MINOR', ver_parts[1]) +config.set('STROID_VERSION_PATCH', ver_parts[2]) +config.set('STROID_VERSION_TAG', ver_parts[3]) + +configure_file( + input : 'stroid.h.in', + output : 'stroid.h', + configuration : config , +) diff --git a/src/include/stroid/stroid.h.in b/src/include/stroid/stroid.h.in new file mode 100644 index 0000000..b596747 --- /dev/null +++ b/src/include/stroid/stroid.h.in @@ -0,0 +1,110 @@ +#pragma once + +#include "stroid/config/config.h" +#include "stroid/topology/topology.h" +#include "stroid/topology/mapping.h" +#include "stroid/topology/curvilinear.h" +#include "stroid/utils/mesh_utils.h" +#include "stroid/IO/mesh.h" + +/** + * @namespace stroid + * @brief Public API surface for the stroid mesh generation library. + * + * This namespace aggregates configuration, topology construction, mapping, and + * mesh utilities for building curvilinear multi-block meshes. + * + * @par Example: build a mesh programmatically + * @code + * #include + * #include "mfem.hpp" + * #include "fourdst/config/config.h" + * #include "stroid/stroid.h" + * + * int main() { + * fourdst::config::Config cfg; + * auto mesh = stroid::topology::BuildSkeleton(cfg); + * stroid::topology::Finalize(*mesh, cfg); + * stroid::topology::PromoteToHighOrder(*mesh, cfg); + * stroid::topology::ProjectMesh(*mesh, cfg); + * stroid::IO::SaveVTU(*mesh, "stroid"); + * } + * @endcode + * + * @par Example: tweak config then visualize + * @code + * fourdst::config::Config cfg; + * cfg->order = 4; + * cfg->flattening = 0.1; + * auto mesh = stroid::topology::BuildSkeleton(cfg); + * stroid::topology::Finalize(*mesh, cfg); + * stroid::topology::PromoteToHighOrder(*mesh, cfg); + * stroid::topology::ProjectMesh(*mesh, cfg); + * stroid::IO::ViewMesh(*mesh, "Stroid Mesh", stroid::IO::VISUALIZATION_MODE::ELEMENT_ID, "localhost", 19916); + * @endcode + */ +namespace stroid { + /** + * @brief Version helpers for the stroid library. + */ + struct version { + static constexpr int major = @STROID_VERSION_MAJOR@; + static constexpr int minor = @STROID_VERSION_MINOR@; + static constexpr int patch = @STROID_VERSION_PATCH@; + static constexpr const char* tag = "@STROID_VERSION_TAG@"; + + static std::string toString() { + std::string versionStr = std::to_string(major) + "." + + std::to_string(minor) + "." + + std::to_string(patch); + if (std::string(tag) != "") { + versionStr += "-" + std::string(tag); + } + return versionStr; + } + + friend std::ostream& operator<<(std::ostream& os, const version&) { + os << toString(); + return os; + } + }; +} + +/** + * @namespace std + * @brief Standard library extensions used by stroid. + * + * Provides a `std::formatter` specialization for `stroid::version` so it can + * be used with `std::format` and related APIs. + */ +// Overload format struct +template <> +struct std::formatter : std::formatter { + auto format(const stroid::version& v, auto& ctx) { + return std::formatter::format(stroid::version::toString(), ctx); + } +}; + +/** + * @namespace stroid::config + * @brief Configuration types and defaults for mesh generation. + */ +namespace stroid::config {} + +/** + * @namespace stroid::topology + * @brief Topology construction and curvilinear mapping utilities. + */ +namespace stroid::topology {} + +/** + * @namespace stroid::IO + * @brief Mesh serialization and visualization helpers. + */ +namespace stroid::IO {} + +/** + * @namespace stroid::utils + * @brief Mesh inspection and validation utilities. + */ +namespace stroid::utils {} diff --git a/src/include/stroid/topology/curvilinear.h b/src/include/stroid/topology/curvilinear.h index 36d8165..3b8a7da 100644 --- a/src/include/stroid/topology/curvilinear.h +++ b/src/include/stroid/topology/curvilinear.h @@ -5,6 +5,17 @@ #include "fourdst/config/config.h" namespace stroid::topology { + /** + * @brief Promote a mesh to high-order by attaching an H1 nodal finite element space. + * @param mesh Mesh to update in-place. + * @param config Mesh configuration (uses `order`). + */ void PromoteToHighOrder(mfem::Mesh& mesh, const fourdst::config::Config &config); + /** + * @brief Project high-order mesh nodes using the configured curvilinear mapping. + * @details Requires nodes to be present (call PromoteToHighOrder first). + * @param mesh Mesh to update in-place. + * @param config Mesh configuration (uses radii, flattening, and mapping parameters). + */ void ProjectMesh(mfem::Mesh& mesh, const fourdst::config::Config &config); } \ No newline at end of file diff --git a/src/include/stroid/topology/mapping.h b/src/include/stroid/topology/mapping.h index a2c6f5c..e1f53b6 100644 --- a/src/include/stroid/topology/mapping.h +++ b/src/include/stroid/topology/mapping.h @@ -5,11 +5,31 @@ #include "fourdst/config/config.h" namespace stroid::topology { + /** + * @brief Apply an equiangular (gnomonic) projection to a point on a cube. + * @param pos Position vector updated in-place. + */ void ApplyEquiangular(mfem::Vector& pos); + /** + * @brief Apply spheroidal flattening along the Z axis. + * @param pos Position vector updated in-place. + * @param config Mesh configuration (uses `flattening`). + */ void ApplySpheroidal(mfem::Vector& pos, const fourdst::config::Config &config); + /** + * @brief Apply Kelvin transform outside the stellar radius. + * @param pos Position vector updated in-place. + * @param config Mesh configuration (uses `r_star` and `r_infinity`). + */ void ApplyKelvin(mfem::Vector& pos, const fourdst::config::Config &config); + /** + * @brief Map a point from the initial block topology to the curvilinear domain. + * @param pos Position vector updated in-place. + * @param config Mesh configuration (uses radii, flattening, instability radius, and core steepness). + * @param attribute_id Element attribute ID (currently unused). + */ void TransformPoint(mfem::Vector& pos, const fourdst::config::Config &config, int attribute_id); } \ No newline at end of file diff --git a/src/include/stroid/topology/topology.h b/src/include/stroid/topology/topology.h index 5832c91..c63c887 100644 --- a/src/include/stroid/topology/topology.h +++ b/src/include/stroid/topology/topology.h @@ -6,6 +6,16 @@ #include namespace stroid::topology { + /** + * @brief Build the initial multi-block mesh topology for the star model. + * @param config Mesh configuration (uses radii and domain flags). + * @return Newly allocated mesh skeleton (not yet refined or curved). + */ std::unique_ptr BuildSkeleton(const fourdst::config::Config & config); + /** + * @brief Finalize topology, validate orientation, and apply uniform refinement. + * @param mesh Mesh to finalize in-place. + * @param config Mesh configuration (uses `refinement_levels`). + */ void Finalize(mfem::Mesh& mesh, const fourdst::config::Config &config); } diff --git a/src/include/stroid/utils/mesh_utils.h b/src/include/stroid/utils/mesh_utils.h index 8e55e09..6de6c53 100644 --- a/src/include/stroid/utils/mesh_utils.h +++ b/src/include/stroid/utils/mesh_utils.h @@ -3,6 +3,16 @@ #include "mfem.hpp" namespace stroid::utils { + /** + * @brief Mark elements with negative Jacobian determinant. + * @details Elements detected as flipped are assigned attribute 999. + * @param mesh Mesh to scan and update in-place. + */ void MarkFlippedElements(mfem::Mesh& mesh); + /** + * @brief Mark boundary elements whose outward normal orientation is flipped. + * @details Boundary elements detected as flipped are assigned attribute 500. + * @param mesh Mesh to scan and update in-place. + */ void MarkFlippedBoundaryElements(mfem::Mesh& mesh); } \ No newline at end of file diff --git a/src/lib/IO/mesh.cpp b/src/lib/IO/mesh.cpp index c761b5e..434aaa6 100644 --- a/src/lib/IO/mesh.cpp +++ b/src/lib/IO/mesh.cpp @@ -17,14 +17,13 @@ namespace stroid::IO { void SaveVTU(mfem::Mesh &mesh, const std::string &exportName) { mfem::ParaViewDataCollection pd(exportName, &mesh); + pd.SetDataFormat(mfem::VTKFormat::BINARY); pd.SetHighOrderOutput(true); pd.Save(); } - void ViewMesh(mfem::Mesh &mesh, const std::string& title, const VISUALIZATION_MODE mode) { - char vishost[] = "localhost"; - int visport = 19916; - mfem::socketstream sol_sock(vishost, visport); + void ViewMesh(mfem::Mesh &mesh, const std::string& title, const VISUALIZATION_MODE mode, const std::string &vishost, int visport) { + mfem::socketstream sol_sock(vishost.c_str(), visport); if (!sol_sock.is_open()) { std::cerr << "Unable to connect to GLVis server at " << vishost << ':' << visport << std::endl; diff --git a/src/meson.build b/src/meson.build index 7739e15..a4385f8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,8 +1,10 @@ dependencies = [ mfem_dep, - config_dep + config_dep, ] +subdir('include/stroid') + stroid_include_files = include_directories('include') stroid_sources = files( 'lib/topology/curvilinear.cpp', @@ -13,7 +15,7 @@ stroid_sources = files( ) stroid_lib = static_library( - 'stroid', + 'libstroid', stroid_sources, include_directories: stroid_include_files, dependencies: dependencies, diff --git a/subprojects/cli11.wrap b/subprojects/cli11.wrap new file mode 100644 index 0000000..0072590 --- /dev/null +++ b/subprojects/cli11.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = CLI11-2.6.1 +source_url = https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.6.1.tar.gz +source_filename = CLI11-2.6.1.tar.gz +source_hash = 377691f3fac2b340f12a2f79f523c780564578ba3d6eaf5238e9f35895d5ba95 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cli11_2.6.1-1/CLI11-2.6.1.tar.gz +wrapdb_version = 2.6.1-1 + +[provide] +dependency_names = CLI11 \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index ef4af0f..b5a6dd5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1 +1,27 @@ -subdir('stroid_sandbox') \ No newline at end of file +gtest_dep = dependency('gtest', main: true, required : true) +gtest_main = dependency('gtest_main', required: true) +gtest_nomain_dep = dependency('gtest', main: false, required : true) + +# Test files for const +test_sources = [ + 'stroidTest.cpp' +] + +foreach test_file : test_sources + exe_name = test_file.split('.')[0] + + test_exe = executable( + exe_name, + test_file, + dependencies: [ + stroid_dep, + gtest_dep, + gtest_main + ], + ) + + test( + exe_name, + test_exe, + env: ['MESON_SOURCE_ROOT=' + meson.project_source_root(), 'MESON_BUILD_ROOT=' + meson.project_build_root()]) +endforeach diff --git a/tests/stroidTest.cpp b/tests/stroidTest.cpp new file mode 100644 index 0000000..c0a11f1 --- /dev/null +++ b/tests/stroidTest.cpp @@ -0,0 +1,220 @@ +#include + +#include "fourdst/config/config.h" +#include "stroid/config/config.h" +#include "stroid/IO/mesh.h" +#include "stroid/topology/curvilinear.h" +#include "stroid/topology/mapping.h" +#include "stroid/topology/topology.h" + +#include +#include +#include +#include + +namespace { + +constexpr double kPi = 3.14159265358979323846; + +using Config = fourdst::config::Config; + + +std::filesystem::path GetSourceRoot() { + if (const char* env = std::getenv("MESON_SOURCE_ROOT")) { + return std::filesystem::path(env); + } + return std::filesystem::current_path(); +} + +Config LoadConfigFromRepo(const std::filesystem::path& relative_path) { + Config cfg; + cfg.load((GetSourceRoot() / relative_path).string()); + return cfg; +} + + +bool IsFiniteMeshNodes(const mfem::Mesh& mesh) { + const mfem::GridFunction* nodes = mesh.GetNodes(); + if (!nodes) { + return false; + } + const int vdim = nodes->FESpace()->GetVDim(); + const int ndofs = nodes->FESpace()->GetNDofs(); + for (int i = 0; i < ndofs; ++i) { + for (int d = 0; d < vdim; ++d) { + const double val = (*nodes)(nodes->FESpace()->DofToVDof(i, d)); + if (!std::isfinite(val)) { + return false; + } + } + } + return true; +} + +} // namespace + +/** + * @brief Test suite for the Stroid library + */ +class stroidTest : public ::testing::Test {}; + +TEST_F(stroidTest, BuildSkeleton_DefaultCounts) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + + ASSERT_NE(mesh, nullptr); + EXPECT_EQ(mesh->Dimension(), 3); + EXPECT_EQ(mesh->GetNV(), 16); + EXPECT_EQ(mesh->GetNE(), 7); + EXPECT_EQ(mesh->GetNBE(), 6); +} + + +TEST_F(stroidTest, Finalize_RefinementIncreasesElements) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + const int initial_elements = mesh->GetNE(); + + stroid::topology::Finalize(*mesh, cfg); + + EXPECT_GT(mesh->GetNE(), initial_elements); + EXPECT_TRUE(mesh->Conforming()); +} + +TEST_F(stroidTest, PromoteToHighOrder_SetsNodes) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + + stroid::topology::PromoteToHighOrder(*mesh, cfg); + + EXPECT_NE(mesh->GetNodes(), nullptr); + EXPECT_TRUE(IsFiniteMeshNodes(*mesh)); +} + +TEST_F(stroidTest, ProjectMesh_ProducesFiniteNodes) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + stroid::topology::PromoteToHighOrder(*mesh, cfg); + + stroid::topology::ProjectMesh(*mesh, cfg); + + EXPECT_TRUE(IsFiniteMeshNodes(*mesh)); +} + +TEST_F(stroidTest, ApplyEquiangular_BasicTransform) { + mfem::Vector pos(3); + pos(0) = 1.0; + pos(1) = 0.5; + pos(2) = -0.25; + + stroid::topology::ApplyEquiangular(pos); + + const double expected_y = 1.0 * std::tan(kPi / 4.0 * (0.5 / 1.0)); + const double expected_z = 1.0 * std::tan(kPi / 4.0 * (-0.25 / 1.0)); + EXPECT_NEAR(pos(1), expected_y, 1e-12); + EXPECT_NEAR(pos(2), expected_z, 1e-12); +} + +TEST_F(stroidTest, ApplySpheroidal_FlattensZ) { + const Config cfg = LoadConfigFromRepo("configs/test_flattening.toml"); + + mfem::Vector pos(3); + pos(0) = 0.0; + pos(1) = 0.0; + pos(2) = 10.0; + + stroid::topology::ApplySpheroidal(pos, cfg); + + EXPECT_NEAR(pos(2), 8.0, 1e-12); +} + +TEST_F(stroidTest, ApplyKelvin_ExpandsOutsideStar) { + const Config cfg; + + mfem::Vector pos(3); + pos(0) = 5.5; + pos(1) = 0.0; + pos(2) = 0.0; + + stroid::topology::ApplyKelvin(pos, cfg); + + EXPECT_NEAR(pos(0), 6.0, 1e-12); + EXPECT_NEAR(pos(1), 0.0, 1e-12); + EXPECT_NEAR(pos(2), 0.0, 1e-12); +} + +TEST_F(stroidTest, TransformPoint_AxisInsideCore_NoChange) { + const Config cfg; + + mfem::Vector pos(3); + pos(0) = 1.0; + pos(1) = 0.0; + pos(2) = 0.0; + + stroid::topology::TransformPoint(pos, cfg, 0); + + EXPECT_NEAR(pos(0), 1.0, 1e-12); + EXPECT_NEAR(pos(1), 0.0, 1e-12); + EXPECT_NEAR(pos(2), 0.0, 1e-12); +} + +TEST_F(stroidTest, TransformPoint_AxisEnvelope_NoChange) { + const Config cfg; + + mfem::Vector pos(3); + pos(0) = 3.0; + pos(1) = 0.0; + pos(2) = 0.0; + + stroid::topology::TransformPoint(pos, cfg, 0); + + EXPECT_NEAR(pos(0), 3.0, 1e-12); + EXPECT_NEAR(pos(1), 0.0, 1e-12); + EXPECT_NEAR(pos(2), 0.0, 1e-12); +} + +TEST_F(stroidTest, TransformPoint_AxisOutsideStar_KelvinExpands) { + const Config cfg; + + mfem::Vector pos(3); + pos(0) = 5.5; + pos(1) = 0.0; + pos(2) = 0.0; + + stroid::topology::TransformPoint(pos, cfg, 0); + + EXPECT_NEAR(pos(0), 6.0, 1e-12); + EXPECT_NEAR(pos(1), 0.0, 1e-12); + EXPECT_NEAR(pos(2), 0.0, 1e-12); +} + +TEST_F(stroidTest, SaveMesh_WritesFile) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + + const std::filesystem::path tmp_dir = std::filesystem::temp_directory_path(); + const std::filesystem::path mesh_path = tmp_dir / "stroid_test_mesh.mesh"; + + stroid::IO::SaveMesh(*mesh, mesh_path.string()); + + ASSERT_TRUE(std::filesystem::exists(mesh_path)); + EXPECT_GT(std::filesystem::file_size(mesh_path), 0u); + + std::error_code ec; + std::filesystem::remove(mesh_path, ec); +} + +TEST_F(stroidTest, EndToEnd_BuildFinalizePromoteProject) { + const Config cfg; + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + stroid::topology::PromoteToHighOrder(*mesh, cfg); + stroid::topology::ProjectMesh(*mesh, cfg); + + EXPECT_GT(mesh->GetNE(), 0); + EXPECT_NE(mesh->GetNodes(), nullptr); + EXPECT_TRUE(IsFiniteMeshNodes(*mesh)); +} diff --git a/tests/stroid_sandbox/main.cpp b/tests/stroid_sandbox/main.cpp deleted file mode 100644 index bec09c2..0000000 --- a/tests/stroid_sandbox/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include "stroid/topology/topology.h" -#include "stroid/config/config.h" -#include "stroid/IO/mesh.h" -#include -#include "mfem.hpp" -#include "stroid/topology/curvilinear.h" -#include "stroid/utils/mesh_utils.h" - -#include "fourdst/config/config.h" - -int main() { - const fourdst::config::Config cfg; - - const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); - stroid::topology::Finalize(*mesh, cfg); - stroid::topology::PromoteToHighOrder(*mesh, cfg); - stroid::topology::ProjectMesh(*mesh, cfg); - // - // stroid::utils::MarkFlippedElements(*mesh); - // stroid::utils::MarkFlippedBoundaryElements(*mesh); - - - stroid::IO::ViewMesh(*mesh, "Spheroidal Mesh", stroid::IO::VISUALIZATION_MODE::BOUNDARY_ELEMENT_ID); - // stroid::IO::VisualizeFaceValence(*mesh); - // stroid::IO::SaveVTU(*mesh, "SpheroidalMesh"); -} diff --git a/tests/stroid_sandbox/meson.build b/tests/stroid_sandbox/meson.build deleted file mode 100644 index 1d9c6fd..0000000 --- a/tests/stroid_sandbox/meson.build +++ /dev/null @@ -1 +0,0 @@ -executable('stroid_sandbox', 'main.cpp', dependencies: stroid_dep) \ No newline at end of file diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000..6fee0d3 --- /dev/null +++ b/tools/meson.build @@ -0,0 +1 @@ +executable('stroid', 'stroid.cpp', dependencies: [stroid_dep, cli11_dep], install: true) diff --git a/tools/stroid.cpp b/tools/stroid.cpp new file mode 100644 index 0000000..5e71ff4 --- /dev/null +++ b/tools/stroid.cpp @@ -0,0 +1,84 @@ +#include +#include "mfem.hpp" + +#include "stroid/config/config.h" +#include "stroid/IO/mesh.h" +#include "stroid/topology/curvilinear.h" +#include "stroid/topology/topology.h" + +#include "stroid/stroid.h" + +#include "fourdst/config/config.h" + +#include "CLI/CLI.hpp" +#include + +enum INFO_MODE { + VERSION, + DEFAULT_CONFIG +}; + +int main(int argc, char** argv) { + fourdst::config::Config cfg; + + CLI::App app{"stroid - A tool for generating multi-block meshes for stellar modeling"}; + auto* generate = app.add_subcommand("generate", "Generate a multi-block mesh for stellar modeling"); + auto* info = app.add_subcommand("info", "Access information about the program stroid"); + + std::optional config_filename; + std::string output_filename; + bool view_mesh; + bool no_save; + std::optional glvis_host; + std::optional glvis_port; + + generate->add_option("-c,--config", config_filename, "Path to configuration file")->check(CLI::ExistingFile); + generate->add_flag("-v,--view", view_mesh, "View the generated mesh using GLVis"); + generate->add_flag("-n,--nosave", no_save, "Do not save the generated mesh to a file"); + generate->add_option("--glvis-host", glvis_host, "GLVis server host (default: localhost)")->default_val("localhost"); + generate->add_option("--glvis-port", glvis_port, "GLVis server port (default: 19916)")->default_val(19916); + generate->add_option("-o,--output", output_filename, "Output filename for the generated mesh")->default_val("stroid"); + + + info->add_flag_callback("-v,--version", []() { + std::println("Stroid Version {}", stroid::version::toString()); + return 0; + }, "Display stroid version information"); + + info->add_flag_callback("-d,--default", [&cfg]() { + cfg.save("default.toml"); + }, "Save the default configuration to a file default.toml"); + + + CLI11_PARSE(app, argc, argv); + + // Ensure that if view is requested, host and port are set + if (view_mesh) { + if (!glvis_host.has_value()) { + glvis_host = "localhost"; + } + if (!glvis_port.has_value()) { + glvis_port = 19916; + } + } + + // If config filename is provided, load configuration from file + if (config_filename.has_value()) { + cfg.load(config_filename.value()); + } + + + const std::unique_ptr mesh = stroid::topology::BuildSkeleton(cfg); + stroid::topology::Finalize(*mesh, cfg); + stroid::topology::PromoteToHighOrder(*mesh, cfg); + stroid::topology::ProjectMesh(*mesh, cfg); + + + if (!output_filename.empty() && !no_save) { + stroid::IO::SaveVTU(*mesh, output_filename); + } + + if (view_mesh) { + stroid::IO::ViewMesh(*mesh, "Spheroidal Mesh - Colored by Element ID", stroid::IO::VISUALIZATION_MODE::ELEMENT_ID, glvis_host.value(), glvis_port.value()); + } +} \ No newline at end of file