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
+## 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):
+
+
+## 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