|
Mundy: Multibody Nonlocal Dynamics Version of the Day
|
Centralized reusable utilities.
See the MundyUtils directory reference.
This subpackage contains small Kokkos-friendly utilities that fill the gaps left by the standard library and by host-only APIs. The core functionality includes
The overall style is the same as the rest of Mundy: small focused types, explicit free functions, and APIs that work in host/device code without hiding ownership or synchronization.
These are the lowest-level tools in the package. They are mostly about writing portable code without giving up readable errors, compile-time strings, or simple metaprogramming.
MUNDY_THROW_REQUIRE and MUNDY_THROW_ASSERT are the main assertion macros.
Their behavior is:
| Macro | Behavior |
|---|---|
| MUNDY_THROW_REQUIRE(...) | Always checks the assertion. On host it throws the requested exception; on device it aborts. |
| MUNDY_THROW_ASSERT(...) | Same behavior in debug builds; compiled out in release builds except for minimal type use. |
The message can be a string literal, StringLiteral, std::string, or a sink expression such as sink() << .... Only compile-time-printable messages are guaranteed to appear in device abort output.
Use MUNDY_THROW_REQUIRE for contract-like checks that must always run. Use MUNDY_THROW_ASSERT for debug-only internal invariants.
StringLiteral<N> wraps a string literal as a compile-time value. This is useful when a string participates in a type, a policy, or a compile-time comparison.
StringLiterals can be concatenated at compile time.
sink() starts a <<-style message builder. Literal-only pipelines remain compile-time objects; once you stream a runtime value, the pipeline becomes a chunked runtime sink.
The two important sink families are:
| Type | When you get it | Main use |
|---|---|---|
| StringLiteralSink | Every streamed chunk is compile-time text. | constexpr strings and device-printable assertion messages. |
| StringSink | At least one streamed chunk is a runtime value. | Lazy runtime message construction with to_string(). |
This is why the same sink() << ... syntax works for both compile-time and runtime diagnostics.
Mundy offers a few intentionally small type-pack traits.
| Trait | Meaning |
|---|---|
| contains_type_v<T, Ts...> | true if T appears in Ts.... |
| count_type_v<T, Ts...> | Number of times T appears in Ts.... |
| index_finder_v<T, Ts...> | Index of a unique T in Ts.... |
| type_at_index_t<I, Ts...> | The Ith type in Ts.... |
These traits are mostly there to support tuple, variant, and the aggregate types.
make_philox(seed, counter) constructs an openrand::Philox generator from size_t inputs.
This helper exists because Philox expects a uint64_t seed and uint32_t counter, while Mundy often works with size_t. The helper performs the conversion and debug-checks that the values fit.
| Helper | Use |
|---|---|
| MUNDY_ATTRIBUTE_UNUSED | Mark intentionally unused declarations. |
| MUNDY_SUPPRESS_* macros | Locally silence compiler warnings around unavoidable patterns. |
| do_not_optimize_away(x) | Keep values live in benchmarks or micro-tests. |
| make_string_array(...) | Build a Teuchos::Array<std::string> from a parameter pack. |
These utilities mirror familiar standard-library ideas while staying friendly to Kokkos kernels and constexpr use.
mundy::tuple<Ts...> is a small heterogeneous value container. The core interface is:
The main supported operations are:
| Operation | Meaning |
|---|---|
| get<I>(t) | Access by index. |
| get<T>(t) | Access by unique type. |
| tuple_size_v<T> | Number of elements. |
| tuple_element_t<I, T> | Type of the Ith element. |
| make_tuple(...) | Build a tuple by value. |
| tuple_cat(a, b) | Concatenate two mundy::tuples. |
Compared with std::tuple, this is smaller out of time constraints. If you find there is a missing operation you need or would make your life easier, please open an issue or submit a PR. The main differences are:
| std::tuple | mundy::tuple |
|---|---|
| Supports the full standard tuple protocol. | Supports only the tuple operations Mundy uses. |
| Has tie, forward_as_tuple, apply, variadic tuple_cat, and richer conversions. | Omits those helpers. |
| get preserves full value category. | get returns mutable or const lvalue references. |
| tuple_cat works on tuple-like inputs. | tuple_cat joins two mundy::tuples and copies values into the result. |
mundy::variant<Ts...> is a Kokkos-compatible std::variant-like class with one active alternative at a time.
The main API is:
| Operation | Meaning |
|---|---|
| v.index() | Active alternative index. |
| holds_alternative<T>(v) | Check the active type. |
| get<T>(v) / get<I>(v) | Access the active alternative. |
| visit(visitor, v) | Dispatch on the active alternative. |
| variant_size_v<Variant> | Number of alternatives. |
| variant_alternative_t<I, Variant> | Ith alternative type. |
Relative to std::variant, Mundy's variant is more restrictive: all alternatives must be default constructible and copy assignable, wrong-type access is debug-checked, and visit requires a homogeneous return type across all alternatives.
reference_wrapper<T> is a Kokkos-compatible std::reference_wrapper-like class.
Core operations are:
| Operation | Meaning |
|---|---|
| ref(x) | Wrap a mutable lvalue reference. |
| cref(x) | Wrap a const lvalue reference. |
| r.get() | Recover the wrapped reference. |
| implicit conversion to T& | Use it where a reference is expected. |
| r(args...) / r[i] | Forward call or subscript when the wrapped object supports it. |
This makes it especially useful for accessors and functors that need reference-like semantics inside device-friendly objects.
storage<T> normalizes owning and non-owning storage.
The store(...) helper follows the rule Mundy usually wants:
| Input | Stored as |
|---|---|
| lvalue | reference_wrapper<T> |
| rvalue | owning value |
| pointer | pointer |
| existing storage<U> | unwrapped stored policy |
This is a small but useful way to write APIs that accept either borrowed or owned data without manual overload sets.
The aggregate family gives Mundy a lightweight, Kokkos-compatible alternative to larger metaprogramming libraries.
aggregate is a tagged bag of values where tags are types. It is similar in spirit to boost::hana::Map. Tags may be any type (complete or incomplete). In most cases, we use empty incomplete structs as tags, but there is no requirement to do so.
The core operations are:
| Operation | Meaning |
|---|---|
| aggregate() | Start an empty aggregate. |
| .append<Tag>(value) | Return a new aggregate with one more tagged value. |
| get<Tag>(agg) / agg.get<Tag>() | Access by tag. |
| get<I>(agg) / agg.get<I>() | Access by index. |
| has<Tag>(agg) | Check whether a tag is present. |
| project<Tags...>(agg) | Copy out a smaller aggregate in the requested tag order. |
If a stored value is callable, get<Tag>(args...) is syntactic sugar for calling that stored callable directly.
variant_aggregate<VariantType, Tags...> stores one VariantType per compile-time tag.
Use this when the tags are known at compile time but each tagged slot needs variant-valued runtime content.
runtime_aggregate<VariantType> stores variant values behind runtime string tags.
This is the runtime-tagged counterpart to variant_aggregate.
These utilities build on Kokkos views and dual views while using Mundy's sync_to_* and modify_on_* naming.
NgpView is Mundy's default wrapper around Kokkos::DualView. The synchronization rule is the one you already expect: sync before reading in a memory space, mark modified after writing in that memory space.
The important helpers are:
| Operation | Meaning |
|---|---|
| view_host() / view_device() | Access the host or device view. |
| modify_on_host() / modify_on_device() | Mark which side was written. |
| sync_to_host() / sync_to_device() | Synchronize stale data. |
| need_sync_to_host() / need_sync_to_device() | Check whether a sync is needed. |
NgpPool is a host/device pool of default-constructible objects built on top of NgpView.
Single-item and batch APIs are both available.
| Operation | Meaning |
|---|---|
| add(x) / add_host(x) | Push one item on device or host. |
| acquire() / acquire_host() | Pop one item on device or host. |
| batch_add(view) / batch_add_host(vector) | Push many items. |
| batch_acquire(n) / batch_acquire_host(n) | Pop many items. |
| reserve(n) | Increase capacity. |
| size() / capacity() | Device-side size and capacity accessors. |
| size_host() / capacity_host() | Host-side size and capacity accessors. |
Just like NgpView, you still use modify_on_*, sync_to_*, and need_sync_to_* to manage host/device state.