Mundy: Multibody Nonlocal Dynamics Version of the Day
Loading...
Searching...
No Matches
mundy::mesh::Aggregate< Components > Class Template Reference

An aggregator of components. More...

#include <Aggregate.hpp>

Public Types

using ComponentsTuple = tuple<Components...>

Public Member Functions

Constructors
 Aggregate (const stk::mesh::BulkData &bulk_data, stk::mesh::Selector selector)
 Construct an Aggregate that has no components.
 Aggregate (const stk::mesh::BulkData &bulk_data, stk::mesh::Selector selector, ComponentsTuple components)
 Construct an Aggregate that has the given components.
 Aggregate (const Aggregate &)=default
 Default copy/move/assign constructors.
 Aggregate (Aggregate &&)=default
Aggregateoperator= (const Aggregate &)=default
Aggregateoperator= (Aggregate &&)=default

Accessors

const stk::mesh::BulkDatabulk_data () const
const stk::mesh::MetaDatamesh_meta_data () const
const stk::mesh::Selector & selector () const
template<typename Tag, typename NewComponent>
auto add_component (NewComponent new_component) const
 Add a component with the given tag (fluent interface):
template<typename Tag, typename NewComponent>
void add_component (NewComponent) const
template<typename NewTaggedComponent>
auto add_component (NewTaggedComponent new_component) const
 Add a component that already has a tag.
template<typename NewTaggedComponent>
void add_component (NewTaggedComponent) const
template<typename Tag>
const auto & get_component () const
 Fetch the component corresponding to the given Tag.
template<typename Tag>
const void get_component () const
template<typename Tag>
auto & get_component ()
 Fetch the component corresponding to the given Tag.
template<typename Tag>
void get_component ()
template<typename... TagsToSync>
void sync_to_device ()
 Synchronize the components marked by the given tags to the device.
template<typename... TagsToSync>
void sync_to_host ()
 Synchronize the components marked by the given tags to the host.
template<typename... TagsToModify>
void modify_on_device ()
 Mark the components marked by the given tags as modified on the device.
template<typename... TagsToModify>
void modify_on_host ()
 Mark the components marked by the given tags as modified on the host.
template<typename Tag>
decltype(auto) get (stk::mesh::Entity entity)
 Get the data tagged by the given tag for the given entity.
template<typename Tag>
void get (stk::mesh::Entity)
template<typename Tag, class EntityExpr>
decltype(auto) get (const impl::EntityExprBase< EntityExpr > &entity_expr)
 Get an expression for the data tagged by the given tag for the given entity expression.
template<typename Tag, class EntityExpr>
void get (const impl::EntityExprBase< EntityExpr > &)
template<typename Tag>
decltype(auto) get (stk::mesh::Entity entity) const
 Get the data tagged by the given tag for the given entity.
template<typename Tag>
void get (stk::mesh::Entity) const
template<typename Tag, class EntityExpr>
decltype(auto) get (const impl::EntityExprBase< EntityExpr > &entity_expr) const
 Get an expression for the data tagged by the given tag for the given entity expression.
template<typename Tag, class EntityExpr>
void get (const impl::EntityExprBase< EntityExpr > &) const
template<typename Tag>
static constexpr bool has ()
 Check if we have a component with the given Tag.

Detailed Description

template<typename... Components>
class mundy::mesh::Aggregate< Components >

ECS Overview

This class is the main entry point for the user to interact with that we refer to as components, in accordance with the Entity Component System (ECS). If you aren't familiar with this pattern, ECS is an architectural pattern designed to decouple data from behavior, enabling flexibility, performance, and scalability in software systems. ECS's has increased in popularity since the late 2000s and is now gaining widespread use within the gaming industry, with addoption by Minecraft (via the elegant EnTT library) and engines like Unity ECS and Unreal ECS.

Unity states: "ECS (Entity Component System) is a data-oriented framework [that] scales processing performance, enabling experienced creators to build more ambitious games with an unprecedented level of control and determinism."

ECS Core Concepts

Entities are simple, unique identifiers—purely conceptual representations of "things" in your application. They have no data or behavior themselves and are rather lightweight, often consisting of nothing more than a unique ID. This makes getting and manipulating lists of entities far faster then getting and manipulating lists of objects.

Components are data-only structures that can be assigned to entities. They contain no behavior, only data. They typically represent a single aspect of an entity's state, such as position, velocity, mass, acceleration. As with most ECS designs, components may be added or removed from entities at runtime.

Importantly, ECS typically discourages hard-coding collections of component at compile-time. Unlike polymorphism, we do not offer a RigidBody class or Sphere class; rather, any entity that ~looks~ like a sphere (i.e. has a component that ~looks~ like a radius and a component that ~looks~ like a center) ~is~ a sphere. This does not require an explicit Sphere class and attempting to create one would be counter to the ECS design. This gives a level of flexibility that is difficult to achieve with traditional object-oriented programming and allows for more optimized memory access patterns and cache coherency.

Systems are functions or processes that operate on entities possessing specific sets of components. They are typically "free" functions that are outside of any class hierarchy or inheritance chain. Unlike functions in a class, users can add "free" functions meant to operate on entities with specific components without needing to modify some hard-coded class definition. This is more flexible to extension, as users can add new free functions without needing to modify existing classes. In many ways, we like to think of ECS as a runtime-extensible deconstructed class hierarchy.

STK's flavor of ECS

STK's domain model can be seen as an extension of ECS, adding to it the concept of connections between entities of different ranks and the ability for entities to possess a graph topology, statically defining its connectivity. This concept of rank and topology is common in mesh-based or molecular dynamics simulations, where we have nodes, edges, faces, and elements that connect to each other in a hierarchical manner. Unlike simplistic ECS systems, like EnTT, adding ranks and topologies complicates the design, necessitating additional features and care.

STK introduces the concept of Parts, Fields, and Selectors. data.

Parts are collections of entities that share the same properties. They may possess a topology, requiring that all entities in that part of the same rank as the topology, have said topology. They may instead possess only a rank, allowing them to hold any entities of that rank. Or they may possess neither, allowing them to be used as Assemblies of any entities. Importantly, Parts may be subsets of other Parts, allowing for hierarchical organization at runtime. Parts (by default) have inherited part membership, meaning that if an entity within the part of its primary rank connects to an entity of lower rank, that entity is also considered to be within the part.

Fields are collections of ranked data that can be assigned to any number of Parts. These Fields can be seen as one type of component. They have a rank, a name, and a type. If a Part has a Field, then all entities in that Part of the same rank as the Field pickup that Field.

Selectors (also called groups or views in other ECS systems) are a means of identifying a subset of entities. In STK, Selectors are formed using set arithmetic applied to the Parts and Fields. For example, a Selector might abstractly represent "all entities of Part A that have Field B but are not in Part C". Selectors are used to define the scope of Systems such as "for all entities in Selector X, do Y". In practice, the smallest unit of work in STK is defined by a Selector (Parts themselves may act as Selectors). If there is ever a need to iterate over a specific subset set of entities that cannot be fetched by set arithmetic applied to your current Parts and Fields, you likely need to create a new Part. That said, sometimes it's more efficient to iterate over a larger set of entities and use a conditional to filter out the entities you don't want. If the subset of entities you wish to iterate over is large, then using a new Part is likely more efficient.

Accessors

We extend STK's domain model to include the concept of Aggregates and Accessors, as an organizational layer above Parts, Fields, and Selectors, meant to abstract away access patterns into an entity's data while reducing boilerplate code. Similar to Field and Parts, we ~assemble~ Aggregates at runtime.

Notably, Accessors overcome the following limitation of STK's domain model: It's common to have a collection of entities within some set of Parts for which we want to store a shared value. This might include a collection of spheres with the same radius and material properties. Similarly, one might want to have a single shared material per part. Simply because a collection of spheres share a radius rather than having a radius stored within a Field shouldn't impact the design of systems meant to operate on spheres, and yet STK offers no means to abstract away shared vs non-shared data. This is a direct consequence of the lack of separation of concerns with regard to data storage and access patterns.

Accessors provide an interface through which data beyond just Fields may be treated as components and accessed in a unified manner. If a user starts with an aggregate for spheres that have a shared radius and then decides later to switch to using a Field of radii, they need only update the aggregate's definition. The systems that act on the aggregate will remain unchanged, as how the data is accesses does not concern them. Notably, accessors are ~views~ into data, i.e. they should not ~hold~ the data they access. They are meant to be cheap to construct and trivial to copy (just like Kokkos::Views). Interface-wise, Accessors must provide a sync_to_host, sync_to_device, modify_on_host, and modify_on_device method the same as STK's NgpField. When called, these methods should synchronize the data to the appropriate space/mark the data as modified. Synchronization should be a no-op if the data is up-to-date on the requested space.

Our Accessors, the data they access, and the return type of the Accessor's get_view method are as follows: Component Name : Data it accesses -> Return Type ScalarFieldComponent : Field<value_type> -> ScalarView<value_type> VectorNFieldComponent : Field<value_type> -> VectorView<value_type, N> Matrix3FieldComponent : Field<value_type> -> Matrix3View<value_type> QuaternionFieldComponent : Field<value_type> -> QuaternionView<value_type> AABBFieldComponent : Field<value_type> -> AABBView<value_type> SharedComponent<SharedType> : SharedType -> SharedType& PartMappedComponent<OtherComponent> : Kokkos::Map<PartOrdinal, OtherComponent> -> OtherComponent's return type

Note
SharedComponent may either alias a rank-1 Kokkos::View in HostSpace of extent 1 or copy a raw value into owned HostSpace storage.

Aggregates

Aggregates are just collections of accessors(components) that can be accessed via a unified tag-based interface. The components are "tagged". That is, they are associated with some type that is used to fetch the data. That type is often nothing more than an emtpy struct, but it can be used to differentiate between components that have the same underlying type. This is opposed to creating a SphereAggregate which stores a radius_field and a center_field. Instead, we can access the radius and center accessors via their tags.

The Aggregate class can be constructed directly as "Aggregate<>(bulk_data, selector)", but we also offer a non-member helper function to streamline this process.

  • Use "Aggregate(bulk_data, selector)" to create an empty aggregate.

Adding components to an aggregate is done via the fluent interface "add_component<Tag>(accessor)", which returns a new aggregate with the added component. This allows for easy chaining of components, as seen in the below example.

Aggregates can then be used to fetch tagged data directly from entities or entity indices via get<Tag>(entity). Notably, connectivity is no longer hidden inside the aggregate access pattern. If you need the center node of a sphere, fetch that connected node explicitly via BulkData or NgpMesh and then pass the node entity/index into get<CENTER>(...). To apply functors, iterate directly on BulkData or NgpMesh with an explicit rank and selector.

Note
Accessors and aggregates are not a replacement for STK's Field and Part system. They are an abstraction layer that sits above and beside it. We choose to use Aggregates to organize our data and Accessors to access it, but you may also directly act on accessors or directly on fields and parts. The choice is yours.

Example Usage

// We'll assume that there exists an elem1 within a Spheres part of PARTICLE topology connected to a node1
// with a NODE_RANK center_field and a shared ELEM_RANK radius. Both have double type.
// Create the accessors
auto center_accessor = ScalarFieldComponent(center_field);
auto radius_accessor = SharedScalarComponent(radius); // Copies radius into owned HostSpace storage
// Fetch the data for the entity via the accessor's operator()
Vector3View<double> center = center_accessor(elem1);
double& radius = radius_accessor(node1);
// Create an aggregate for the spheres
auto collision_sphere_data = Aggregate(bulk_data, selector)
.add_component<CENTER>(center_accessor)
// Sync the data to the device and mark it as modified
collision_sphere_data.sync_to_device<CENTER, COLLISION_RADIUS>();
collision_sphere_data.modify_on_device<CENTER, COLLISION_RADIUS>();
// Do the same directly via the accessors
center_accessor.sync_to_device();
radius_accessor.modify_on_device();
collision_sphere_data.get_component<CENTER>().sync_to_device();
collision_sphere_data.get_component<COLLISION_RADIUS>().modify_on_device();
// Fetch data directly through the aggregate
Vector3View<double> also_center = collision_sphere_data.get<CENTER>(node1);
double& also_radius = collision_sphere_data.get<COLLISION_RADIUS>(entity1);
// Apply a functor to all entities in the aggregate
mundy::mesh::for_each_entity_run(bulk_data, stk::topology::ELEM_RANK, collision_sphere_data.selector(),
[&](stk::mesh::Entity sphere) {
stk::mesh::Entity center_node =
collision_sphere_data.bulk_data().begin(sphere, stk::topology::NODE_RANK)[0];
Vector3View<double> c = collision_sphere_data.get<CENTER>(center_node);
double& r = collision_sphere_data.get<COLLISION_RADIUS>(sphere);
std::cout << "Center = " << c << ", Radius = " << r << std::endl;
});
// Directly use accessors without an aggregate
stk::mesh::for_each_entity_run(bulk_data, stk::topology::ELEM_RANK, selector,
[center_accessor, radius_accessor](const stk::mesh::BulkData &bulk_data, const stk::mesh::Entity &entity) {
Vector3View<double> c = center_accessor(entity);
double& r = radius_accessor(entity);
std::cout << "Center = " << c << ", Radius = " << r << std::endl;
});
// Directly pass accessors to a free-function move_spheres2 templated by the accessor types
move_spheres2(center_accessor, radius_accessor);
Aggregate(const stk::mesh::BulkData &bulk_data, stk::mesh::Selector selector)
Construct an Aggregate that has no components.
Definition Aggregate.hpp:255
auto add_component(NewComponent new_component) const
Add a component with the given tag (fluent interface):
Definition Aggregate.hpp:289
const stk::mesh::Selector & selector() const
Definition Aggregate.hpp:281
void sync_to_device()
Synchronize the components marked by the given tags to the device.
Definition Aggregate.hpp:353
void modify_on_device()
Mark the components marked by the given tags as modified on the device.
Definition Aggregate.hpp:365
const stk::mesh::BulkData & bulk_data() const
Definition Aggregate.hpp:275
Definition FieldComponent.hpp:378
Definition SharedComponent.hpp:164
void for_each_entity_run(Mesh &mesh, stk::topology::rank_t rank, const stk::mesh::Selector &selector, const AlgorithmPerEntity &functor)
Definition ForEachEntity.hpp:53

Member Typedef Documentation

◆ ComponentsTuple

template<typename... Components>
using mundy::mesh::Aggregate< Components >::ComponentsTuple = tuple<Components...>

Constructor & Destructor Documentation

◆ Aggregate() [1/4]

template<typename... Components>
mundy::mesh::Aggregate< Components >::Aggregate ( const stk::mesh::BulkData & bulk_data,
stk::mesh::Selector selector )
inline

◆ Aggregate() [2/4]

template<typename... Components>
mundy::mesh::Aggregate< Components >::Aggregate ( const stk::mesh::BulkData & bulk_data,
stk::mesh::Selector selector,
ComponentsTuple components )
inline

◆ Aggregate() [3/4]

template<typename... Components>
mundy::mesh::Aggregate< Components >::Aggregate ( const Aggregate< Components > & )
default

◆ Aggregate() [4/4]

template<typename... Components>
mundy::mesh::Aggregate< Components >::Aggregate ( Aggregate< Components > && )
default

Member Function Documentation

◆ operator=() [1/2]

template<typename... Components>
Aggregate & mundy::mesh::Aggregate< Components >::operator= ( const Aggregate< Components > & )
default

◆ operator=() [2/2]

template<typename... Components>
Aggregate & mundy::mesh::Aggregate< Components >::operator= ( Aggregate< Components > && )
default

◆ bulk_data()

template<typename... Components>
const stk::mesh::BulkData & mundy::mesh::Aggregate< Components >::bulk_data ( ) const
inline

◆ mesh_meta_data()

template<typename... Components>
const stk::mesh::MetaData & mundy::mesh::Aggregate< Components >::mesh_meta_data ( ) const
inline

◆ selector()

template<typename... Components>
const stk::mesh::Selector & mundy::mesh::Aggregate< Components >::selector ( ) const
inline

◆ add_component() [1/4]

template<typename... Components>
template<typename Tag, typename NewComponent>
auto mundy::mesh::Aggregate< Components >::add_component ( NewComponent new_component) const
inline

◆ add_component() [2/4]

template<typename... Components>
template<typename Tag, typename NewComponent>
void mundy::mesh::Aggregate< Components >::add_component ( NewComponent ) const
inline

◆ add_component() [3/4]

template<typename... Components>
template<typename NewTaggedComponent>
auto mundy::mesh::Aggregate< Components >::add_component ( NewTaggedComponent new_component) const
inline

◆ add_component() [4/4]

template<typename... Components>
template<typename NewTaggedComponent>
void mundy::mesh::Aggregate< Components >::add_component ( NewTaggedComponent ) const
inline

◆ get_component() [1/4]

template<typename... Components>
template<typename Tag>
const auto & mundy::mesh::Aggregate< Components >::get_component ( ) const
inline

◆ get_component() [2/4]

template<typename... Components>
template<typename Tag>
const void mundy::mesh::Aggregate< Components >::get_component ( ) const
inline

◆ get_component() [3/4]

template<typename... Components>
template<typename Tag>
auto & mundy::mesh::Aggregate< Components >::get_component ( )
inline

◆ get_component() [4/4]

template<typename... Components>
template<typename Tag>
void mundy::mesh::Aggregate< Components >::get_component ( )
inline

◆ sync_to_device()

template<typename... Components>
template<typename... TagsToSync>
void mundy::mesh::Aggregate< Components >::sync_to_device ( )
inline

◆ sync_to_host()

template<typename... Components>
template<typename... TagsToSync>
void mundy::mesh::Aggregate< Components >::sync_to_host ( )
inline

◆ modify_on_device()

template<typename... Components>
template<typename... TagsToModify>
void mundy::mesh::Aggregate< Components >::modify_on_device ( )
inline

◆ modify_on_host()

template<typename... Components>
template<typename... TagsToModify>
void mundy::mesh::Aggregate< Components >::modify_on_host ( )
inline

◆ get() [1/8]

template<typename... Components>
template<typename Tag>
decltype(auto) mundy::mesh::Aggregate< Components >::get ( stk::mesh::Entity entity)
inline

◆ get() [2/8]

template<typename... Components>
template<typename Tag>
void mundy::mesh::Aggregate< Components >::get ( stk::mesh::Entity )
inline

◆ get() [3/8]

template<typename... Components>
template<typename Tag, class EntityExpr>
decltype(auto) mundy::mesh::Aggregate< Components >::get ( const impl::EntityExprBase< EntityExpr > & entity_expr)
inline

◆ get() [4/8]

template<typename... Components>
template<typename Tag, class EntityExpr>
void mundy::mesh::Aggregate< Components >::get ( const impl::EntityExprBase< EntityExpr > & )
inline

◆ get() [5/8]

template<typename... Components>
template<typename Tag>
decltype(auto) mundy::mesh::Aggregate< Components >::get ( stk::mesh::Entity entity) const
inline

◆ get() [6/8]

template<typename... Components>
template<typename Tag>
void mundy::mesh::Aggregate< Components >::get ( stk::mesh::Entity ) const
inline

◆ get() [7/8]

template<typename... Components>
template<typename Tag, class EntityExpr>
decltype(auto) mundy::mesh::Aggregate< Components >::get ( const impl::EntityExprBase< EntityExpr > & entity_expr) const
inline

◆ get() [8/8]

template<typename... Components>
template<typename Tag, class EntityExpr>
void mundy::mesh::Aggregate< Components >::get ( const impl::EntityExprBase< EntityExpr > & ) const
inline

◆ has()

template<typename... Components>
template<typename Tag>
constexpr bool mundy::mesh::Aggregate< Components >::has ( )
inlinestaticconstexpr