C++ API¶
µGrid is the lowest-level component of the µSpectre project and is written in
C++. The core lives in src/libmugrid/ under the namespace muGrid. Common
type aliases and the data structures used throughout the project are defined in
grid_common.hh;
it is worth reading that header for details.
This page is a hand-written tour of the main classes and how they fit together. Full member-level documentation (signatures, every method) is generated by Doxygen and lives in the Doxygen reference; the lists below name the relevant classes and describe what each is for.
See installation for build instructions and examples for worked code. The Python bindings are documented in python.md.
Common type aliases¶
All mathematical calculations should use the project's type aliases rather than raw C++ types. These are the types for which every data structure is tested and that other µSpectre developers expect.
muGrid::Real,muGrid::Complex,muGrid::Int,muGrid::Uint— the scalar types fields may store.muGrid::Index_t— index type used for sizes and offsets.muGrid::Dim_t— a signed integer used to count dimensions. It is signed because Eigen uses-1to signify a dynamic number of dimensions.muGrid::Rcoord_tandmuGrid::Ccoord_t— real-valued coordinates and integer-valued coordinates (pixel/voxel coordinates), respectively. These are also used to expressnb_grid_ptsand spatial lengths of computational domains.muGrid::IterUnitandmuGrid::StorageOrder— enumerations controlling iteration units and in-memory storage order.
Note
While other C++ types can be used in principle, only the aliases above are tested and supported.
The field abstraction¶
The most important concept in µGrid is how it handles field data. A field is the discretisation of a mathematical field on grid points: numerical data associated with all pixels/voxels of a grid (or a subset thereof). Field values can be scalar, vectorial, matricial, tensorial or a generic array of integer, real or complex values per pixel.
Fields defined on every pixel/voxel are global fields; fields defined on a subset of pixels/voxels are local fields. For example, the strain field is global (it exists in the whole domain), whereas an internal state variable of a material in a composite is local (only defined on the pixels of that material).
The same field is often viewed in different ways by different parts of a problem. Consider a global strain field in a 3D finite-strain problem with \(255 \times 255 \times 255\) voxels: the solver treats it as a long vector of length \(3^2 \cdot 255^3\), the FFT sees a four-dimensional array of shape \(255 \times 255 \times 255 \times 3^2\), and the constitutive laws see a sequence of second-rank tensors (shape \(255^3 \times 3 \times 3\)). To reconcile these interpretations without copying data, µGrid splits a field into three components:
- Storage — the field. Manages the actual memory holding the data. It knows the scalar type, the number of pixels/voxels, and the number of scalar components per pixel/voxel (e.g. 9 for an asymmetric second-rank tensor in 3D).
- Representation — the field map. Defines how to interpret per-pixel data (vector, matrix, ...), which mathematical operations are available, and allows iterating pixel by pixel.
- Access / management — the field collection. Maps a pixel/voxel coordinate or index to the position of the associated data. Because this mapping is identical for every field on the same domain, it is centralised in a manager.
Field collections¶
Field collections manage groups of fields on a structured grid; all fields in a collection share the same spatial discretisation. They are iterable, yielding the coordinates of the pixels/voxels on which the collection's fields are defined.
Two flavours, both templated by the spatial dimension and sharing the interface of their common base class:
muGrid::FieldCollection— common base class.muGrid::GlobalFieldCollection— defined on the whole grid; must be given the problem'snb_grid_pts(the grid size) at initialisation.muGrid::LocalFieldCollection— defined on a subset; filled with pixels/voxels through repeated calls toadd_pixel(pixel), and derives its size from the number of pixels added.
Fields (and state fields) are identified by a unique name and retrieved from the
collection with get_field(name) and get_state_field(name).
#include "libmugrid/field_collection_global.hh"
using muGrid::GlobalFieldCollection;
// A 2D collection on a 255 x 255 grid
GlobalFieldCollection collection{muGrid::Ccoord_t<2>{255, 255}};
Full member documentation: muGrid::FieldCollection,
muGrid::GlobalFieldCollection, muGrid::LocalFieldCollection in the Doxygen
reference.
Fields¶
Fields are where data is stored, so they are distinguished mainly by the scalar
type they hold (Int, Real or Complex) and by their number of components
(statically fixed, or dynamic).
Fields have a protected constructor — you cannot build one directly. Instead
use the factory function make_field<Field_t>(name, collection) (or
make_statefield<Field_t>(name, collection) for a state field). The factory
returns a reference; the field itself is held in a std::unique_ptr registered
in and managed by a field collection. This ensures fields are never copied or
freed out from under the field maps that reference them.
Fields expose their bulk memory as an Eigen::Map, which is convenient for
accessing global strain, stress and tangent-moduli fields in a solver.
The classes in the Doxygen reference:
muGrid::Field— abstract base for all fields.muGrid::TypedFieldBase— base for fields of a concrete scalar type.muGrid::StateField— a field holding a current value plus one or more old values (see below).
#include "libmugrid/field_typed.hh"
// Build a real-valued field with 9 components per pixel (a 3x3 tensor)
auto & strain{muGrid::make_field<muGrid::TypedField<muGrid::Real>>(
"strain", collection, /*nb_components=*/9)};
Field maps¶
Field maps are light-weight resource handles (cheap to create and destroy) that
are iterable and give direct per-pixel/voxel access to a mapped field's data.
The choice of map defines the reference type you get when dereferencing an
iterator or using operator[].
Common fixed-size maps (size known at compile time, so iterates support fast linear algebra):
muGrid::ScalarFieldMapmuGrid::ArrayFieldMapmuGrid::MatrixFieldMapmuGrid::T4MatrixFieldMap— fourth-rank tensor map.
There is also a dynamically sized muGrid::TypedFieldMap, useful for debugging
and the Python bindings. It supports the same features as the fixed-size maps,
but linear algebra on its iterates is slower because it cannot be effectively
vectorised.
muGrid::MatrixFieldMap<muGrid::Real, 3, 3> strain_map{strain};
for (auto && tensor : strain_map) {
// `tensor` is an Eigen 3x3 block referencing this pixel's data
}
State fields¶
Some fields hold state or history variables — a current value plus one or more old values — which is common for internal variables of inelastic materials (damage, plastic flow, etc.). Managing this with separate fields and manual copying is inefficient and error-prone.
A state field encapsulates a container of fields in one variable. It gives read/write access to the current values and read-only access to the old values. Per-pixel iteration is handled through state field maps, whose iterates give access to the current value at a pixel and read-only access to the old values.
State fields are created with make_statefield<Field_t>(name, collection).
Mapped fields¶
When a field is only ever used by one entity (e.g. a material's internal variable), the full field / field-collection / field-map machinery is more flexibility than needed. Mapped fields bundle a field and its corresponding map into a single object, drastically reducing boilerplate.
Domain decomposition and ghosts¶
Classes for parallel (MPI) domain decomposition and ghost-region communication:
muGrid::Decomposition— abstract base for a domain decomposition.muGrid::CartesianDecomposition— Cartesian (regular grid) decomposition that splits the global grid across MPI ranks and manages ghost buffers.muGrid::Communicator— thin wrapper over the MPI communicator used for collective and point-to-point operations; degrades gracefully to a serial no-op communicator when built without MPI.
See the Doxygen reference for the member-level API.
FFT engine¶
Distributed FFT operations use an auto-selected slab or pencil decomposition. See fft.md for the conceptual overview and linalg.md for the linear-algebra context.
muGrid::FFTEngineBase— abstract interface to an FFT engine.muGrid::FFTEngine— concrete engine performing forward/inverse transforms on fields.
Low-level frequency helpers are also available (prefer the fftfreq /
ifftfreq properties on the engine from Python):
muGrid::fft_freq, muGrid::fft_freqind, muGrid::rfft_freq,
muGrid::rfft_freqind, muGrid::fft_normalization,
muGrid::get_hermitian_grid_pts.
Operators¶
Discrete operators for stencil-based computations such as gradients and the Laplacian. See operators.md for usage and theory.
muGrid::ConvolutionOperatorBase— abstract base for convolution-style stencil operators.muGrid::ConvolutionOperator— generic convolution operator applying a stencil to a field.muGrid::LaplaceOperator— discrete Laplacian.muGrid::FEMGradientOperator— finite-element gradient operator.
File I/O¶
Classes for reading and writing fields to disk in NetCDF format:
muGrid::FileIOBase— abstract base for file I/O backends.muGrid::FileIONetCDF— NetCDF reader/writer for fields and field collections.
Grid utilities¶
Helpers for converting between grid coordinates and linear indices:
muGrid::get_domain_ccoord— linear index → pixel/voxel coordinate.muGrid::get_domain_index— pixel/voxel coordinate → linear index.
Device / memory spaces¶
µGrid supports execution on GPUs; see gpu.md for the build flags and the device-side workflow.