deal.II version GIT relicensing-2076-g6b43d56a25 2024-11-02 12:30:00+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
Public Types | Public Member Functions | Static Public Member Functions | Protected Member Functions | Protected Attributes | Private Types | Private Member Functions | Private Attributes | Static Private Attributes | Friends | List of all members
TableBase< N, T > Class Template Reference

#include <deal.II/base/table.h>

Inheritance diagram for TableBase< N, T >:
Inheritance graph
[legend]

Public Types

using value_type = T
 
using size_type = typename AlignedVector< T >::size_type
 

Public Member Functions

 TableBase ()=default
 
 TableBase (const TableIndices< N > &sizes)
 
template<typename InputIterator >
 TableBase (const TableIndices< N > &sizes, InputIterator entries, const bool C_style_indexing=true)
 
 TableBase (const TableBase< N, T > &src)
 
template<typename T2 >
 TableBase (const TableBase< N, T2 > &src)
 
 TableBase (TableBase< N, T > &&src) noexcept
 
 ~TableBase () override=default
 
TableBase< N, T > & operator= (const TableBase< N, T > &src)
 
template<typename T2 >
TableBase< N, T > & operator= (const TableBase< N, T2 > &src)
 
TableBase< N, T > & operator= (TableBase< N, T > &&src) noexcept
 
bool operator== (const TableBase< N, T > &T2) const
 
void reset_values ()
 
void reinit (const TableIndices< N > &new_size, const bool omit_default_initialization=false)
 
void clear ()
 
size_type size (const unsigned int i) const
 
const TableIndices< N > & size () const
 
size_type n_elements () const
 
bool empty () const
 
template<typename InputIterator >
void fill (InputIterator entries, const bool C_style_indexing=true)
 
void fill (const T &value)
 
AlignedVector< T >::reference operator() (const TableIndices< N > &indices)
 
AlignedVector< T >::const_reference operator() (const TableIndices< N > &indices) const
 
void replicate_across_communicator (const MPI_Comm communicator, const unsigned int root_process)
 
void swap (TableBase< N, T > &v) noexcept
 
std::size_t memory_consumption () const
 
template<class Archive >
void serialize (Archive &ar, const unsigned int version)
 
EnableObserverPointer functionality

Classes derived from EnableObserverPointer provide a facility to subscribe to this object. This is mostly used by the ObserverPointer class.

void subscribe (std::atomic< bool > *const validity, const std::string &identifier="") const
 
void unsubscribe (std::atomic< bool > *const validity, const std::string &identifier="") const
 
unsigned int n_subscriptions () const
 
template<typename StreamType >
void list_subscribers (StreamType &stream) const
 
void list_subscribers () const
 

Static Public Member Functions

static ::ExceptionBaseExcInUse (int arg1, std::string arg2, std::string arg3)
 
static ::ExceptionBaseExcNoSubscriber (std::string arg1, std::string arg2)
 

Protected Member Functions

size_type position (const TableIndices< N > &indices) const
 
AlignedVector< T >::reference el (const TableIndices< N > &indices)
 
AlignedVector< T >::const_reference el (const TableIndices< N > &indices) const
 

Protected Attributes

AlignedVector< T > values
 
TableIndices< N > table_size
 

Private Types

using map_value_type = decltype(counter_map)::value_type
 
using map_iterator = decltype(counter_map)::iterator
 

Private Member Functions

void check_no_subscribers () const noexcept
 

Private Attributes

std::atomic< unsigned intcounter
 
std::map< std::string, unsigned intcounter_map
 
std::vector< std::atomic< bool > * > validity_pointers
 
const std::type_info * object_info
 

Static Private Attributes

static std::mutex mutex
 

Friends

template<int , typename >
class TableBase
 

Detailed Description

template<int N, typename T>
class TableBase< N, T >

A class holding a multi-dimensional array of objects of templated type. If the template parameter indicating the number of dimensions is one, then this class more or less represents a vector; if it is two then it is a matrix; and so on.

This class specifically replaces attempts at higher-dimensional arrays like std::vector<std::vector<T>>, or even higher nested constructs. These constructs have the disadvantage that they are hard to initialize, and most importantly that they are very inefficient if all rows of a matrix or higher-dimensional table have the same size (which is the usual case), since then the memory for each row is allocated independently, both wasting time and memory. This can be made more efficient by allocating only one chunk of memory for the entire object, which is what the current class does.

Comparison with the Tensor class

In some way, this class is similar to the Tensor class, in that it templatizes on the number of dimensions. However, there are two major differences. The first is that the Tensor class stores only numeric values (as doubles), while the Table class stores arbitrary objects. The second is that the Tensor class has fixed sizes in each dimension, also given as a template argument, while this class can handle arbitrary and different sizes in each dimension.

This has two consequences. First, since the size is not known at compile time, it has to do explicit memory allocation. Second, the layout of individual elements is not known at compile time, so access is slower than for the Tensor class where the number of elements are their location is known at compile time and the compiler can optimize with this knowledge (for example when unrolling loops). On the other hand, this class is of course more flexible, for example when you want a two-dimensional table with the number of rows equal to the number of degrees of freedom on a cell, and the number of columns equal to the number of quadrature points. Both numbers may only be known at run-time, so a flexible table is needed here. Furthermore, you may want to store, say, the gradients of shape functions, so the data type is not a single scalar value, but a tensor itself.

Dealing with large data sets

The Table classes (derived from this class) are frequently used to store large data tables. A modest example is given in step-53 where we store a \(380 \times 220\) table of geographic elevation data for a region of Africa, and this data requires about 670 kB if memory; however, tables that store three- or more-dimensional data (say, information about the density, pressure, and temperature in the earth interior on a regular grid of (latitude, longitude, depth) points) can easily run into hundreds of megabytes or more. These tables are then often provided to classes such as InterpolatedTensorProductGridData or InterpolatedUniformGridData.

If you need to load such tables on single-processor (or multi-threaded) jobs, then there is nothing you can do about the size of these tables: The table just has to fit into memory. But, if your program is parallelized via MPI, then a typical first implementation would create a table object on every process and fill it on every MPI process by reading the data from a file. This is inefficient from two perspectives:

Both of these use cases are enabled by the TableBase::replicate_across_communicator() function that is internally based on AlignedVector::replicate_across_communicator(). This function allows for workflows like the following where we put that MPI process with rank zero in charge of reading the data (but it could have been any other "root rank" as well):

const unsigned int N=..., M=...; // table sizes, assumed known
const unsigned int root_rank = 0;
{
data_table.resize (N,M);
std::ifstream input_file ("data_file.dat");
...; // read the data from the file
}
// Now distribute to all processes
data_table.replicate_across_communicator (mpi_communicator, root_rank);
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:107

The last call in this code snippet makes sure that the data is made available on all non-root processes, either by re-creating a copy of the table in the other processes' memory space or, if possible, by creating copies in shared memory once for all processes located on each of the machines used by the MPI job.

Definition at line 440 of file table.h.

Member Typedef Documentation

◆ value_type

template<int N, typename T >
using TableBase< N, T >::value_type = T

Definition at line 443 of file table.h.

◆ size_type

template<int N, typename T >
using TableBase< N, T >::size_type = typename AlignedVector<T>::size_type

Integer type used to count the number of elements in this container.

Definition at line 448 of file table.h.

◆ map_value_type

The data type used in counter_map.

Definition at line 238 of file enable_observer_pointer.h.

◆ map_iterator

The iterator type used in counter_map.

Definition at line 243 of file enable_observer_pointer.h.

Constructor & Destructor Documentation

◆ TableBase() [1/6]

template<int N, typename T >
TableBase< N, T >::TableBase ( )
default

Default constructor. Set all dimensions to zero.

◆ TableBase() [2/6]

template<int N, typename T >
TableBase< N, T >::TableBase ( const TableIndices< N > &  sizes)
explicit

Constructor. Initialize the array with the given dimensions in each index component.

◆ TableBase() [3/6]

template<int N, typename T >
template<typename InputIterator >
TableBase< N, T >::TableBase ( const TableIndices< N > &  sizes,
InputIterator  entries,
const bool  C_style_indexing = true 
)

Constructor. Initialize the array with the given dimensions in each index component, and then initialize the elements of the table using the second and third argument by calling fill(entries,C_style_indexing).

◆ TableBase() [4/6]

template<int N, typename T >
TableBase< N, T >::TableBase ( const TableBase< N, T > &  src)

Copy constructor. Performs a deep copy.

◆ TableBase() [5/6]

template<int N, typename T >
template<typename T2 >
TableBase< N, T >::TableBase ( const TableBase< N, T2 > &  src)

Copy constructor. Performs a deep copy from a table object storing some other data type.

◆ TableBase() [6/6]

template<int N, typename T >
TableBase< N, T >::TableBase ( TableBase< N, T > &&  src)
noexcept

Move constructor. Transfers the contents of another Table.

◆ ~TableBase()

template<int N, typename T >
TableBase< N, T >::~TableBase ( )
overridedefault

Destructor. Free allocated memory.

Member Function Documentation

◆ operator=() [1/3]

template<int N, typename T >
TableBase< N, T > & TableBase< N, T >::operator= ( const TableBase< N, T > &  src)

Assignment operator. Copy all elements of src into the matrix. The size is adjusted if needed.

We can't use the other, templatized version since if we don't declare this one, the compiler will happily generate a predefined copy operator which is not what we want.

◆ operator=() [2/3]

template<int N, typename T >
template<typename T2 >
TableBase< N, T > & TableBase< N, T >::operator= ( const TableBase< N, T2 > &  src)

Copy operator. Copy all elements of src into the array. The size is adjusted if needed.

This function requires that the type T2 is convertible to T.

◆ operator=() [3/3]

template<int N, typename T >
TableBase< N, T > & TableBase< N, T >::operator= ( TableBase< N, T > &&  src)
noexcept

Move assignment operator. Transfer all elements of src into the table.

◆ operator==()

template<int N, typename T >
bool TableBase< N, T >::operator== ( const TableBase< N, T > &  T2) const

Test for equality of two tables.

◆ reset_values()

template<int N, typename T >
void TableBase< N, T >::reset_values ( )

Set all entries to their default value (i.e. copy them over with default constructed objects). Do not change the size of the table, though.

◆ reinit()

template<int N, typename T >
void TableBase< N, T >::reinit ( const TableIndices< N > &  new_size,
const bool  omit_default_initialization = false 
)

Set the dimensions of this object to the sizes given in the first argument, and allocate the required memory for table entries to accommodate these sizes. If omit_default_initialization is set to false, all elements of the table are set to a default constructed object for the element type. Otherwise the memory is left in an uninitialized or otherwise undefined state.

◆ clear()

template<int N, typename T >
void TableBase< N, T >::clear ( )

Set all dimensions to zero.

◆ size() [1/2]

template<int N, typename T >
size_type TableBase< N, T >::size ( const unsigned int  i) const

Size of the table in direction i.

◆ size() [2/2]

template<int N, typename T >
const TableIndices< N > & TableBase< N, T >::size ( ) const

Return the sizes of this object in each direction.

◆ n_elements()

template<int N, typename T >
size_type TableBase< N, T >::n_elements ( ) const

Return the number of elements stored in this object, which is the product of the extensions in each dimension.

◆ empty()

template<int N, typename T >
bool TableBase< N, T >::empty ( ) const

Return whether the object is empty, i.e. one of the directions is zero. This is equivalent to n_elements()==0.

◆ fill() [1/2]

template<int N, typename T >
template<typename InputIterator >
void TableBase< N, T >::fill ( InputIterator  entries,
const bool  C_style_indexing = true 
)

Fill this table (which is assumed to already have the correct size) from a source given by dereferencing the given forward iterator (which could, for example, be a pointer to the first element of an array, or an inserting std::istream_iterator). The second argument denotes whether the elements pointed to are arranged in a way that corresponds to the last index running fastest or slowest. The default is to use C-style indexing where the last index runs fastest (as opposed to Fortran-style where the first index runs fastest when traversing multidimensional arrays. For example, if you try to fill an object of type Table<2,T>, then calling this function with the default value for the second argument will result in the equivalent of doing

for (unsigned int i=0; i<t.size(0); ++i)
for (unsigned int j=0; j<t.size(1); ++j)
t[i][j] = *entries++;

On the other hand, if the second argument to this function is false, then this would result in code of the following form:

for (unsigned int j=0; j<t.size(1); ++j)
for (unsigned int i=0; i<t.size(0); ++i)
t[i][j] = *entries++;

Note the switched order in which we fill the table elements by traversing the given set of iterators.

Parameters
entriesAn iterator to a set of elements from which to initialize this table. It is assumed that iterator can be incremented and dereferenced a sufficient number of times to fill this table.
C_style_indexingIf true, run over elements of the table with the last index changing fastest as we dereference subsequent elements of the input range. If false, change the first index fastest.

◆ fill() [2/2]

template<int N, typename T >
void TableBase< N, T >::fill ( const T &  value)

Fill all table entries with the same value.

◆ operator()() [1/2]

template<int N, typename T >
AlignedVector< T >::reference TableBase< N, T >::operator() ( const TableIndices< N > &  indices)

Return a read-write reference to the indicated element.

◆ operator()() [2/2]

template<int N, typename T >
AlignedVector< T >::const_reference TableBase< N, T >::operator() ( const TableIndices< N > &  indices) const

Return the value of the indicated element as a read-only reference.

We return the requested value as a constant reference rather than by value since this object may hold data types that may be large, and we don't know here whether copying is expensive or not.

◆ replicate_across_communicator()

template<int N, typename T >
void TableBase< N, T >::replicate_across_communicator ( const MPI_Comm  communicator,
const unsigned int  root_process 
)

This function replicates the state found on the process indicated by root_process across all processes of the MPI communicator. The current state found on any of the processes other than root_process is lost in this process. One can imagine this operation to act like a call to Utilities::MPI::broadcast() from the root process to all other processes, though in practice the function may try to move the data into shared memory regions on each of the machines that host MPI processes and let all MPI processes on this machine then access this shared memory region instead of keeping their own copy. See the general documentation of this class for a code example.

The intent of this function is to quickly exchange large arrays from one process to others, rather than having to compute or create it on all processes. This is specifically the case for data loaded from disk – say, large data tables – that are more easily dealt with by reading once and then distributing across all processes in an MPI universe, than letting each process read the data from disk itself. Specifically, the use of shared memory regions allows for replicating the data only once per multicore machine in the MPI universe, rather than replicating data once for each MPI process. This results in large memory savings if the data is large on today's machines that can easily house several dozen MPI processes per shared memory space.

This function does not imply a model of keeping data on different processes in sync, as LinearAlgebra::distributed::Vector and other vector classes do where there exists a notion of certain elements of the vector owned by each process and possibly ghost elements that are mirrored from its owning process to other processes. Rather, the elements of the current object are simply copied to the other processes, and it is useful to think of this operation as creating a set of const TableBase objects on all processes that should not be changed any more after the replication operation, as this is the only way to ensure that the vectors remain the same on all processes. This is particularly true because of the use of shared memory regions where any modification of a vector element on one MPI process may also result in a modification of elements visible on other processes, assuming they are located within one shared memory node.

Note
The use of shared memory between MPI processes requires that the detected MPI installation supports the necessary operations. This is the case for MPI 3.0 and higher.
This function is not cheap. It needs to create sub-communicators of the provided communicator object, which is generally an expensive operation. Likewise, the generation of shared memory spaces is not a cheap operation. As a consequence, this function primarily makes sense when the goal is to share large read-only data tables among processes; examples are data tables that are loaded at start-up time and then used over the course of the run time of the program. In such cases, the start-up cost of running this function can be amortized over time, and the potential memory savings from not having to store the table on each process may be substantial on machines with large core counts on which many MPI processes run on the same machine.
This function only makes sense if the data type T is "self-contained", i.e., all of its information is stored in its member variables, and if none of the member variables are pointers to other parts of the memory. This is because if a type T does have pointers to other parts of memory, then moving T into a shared memory space does not result in the other processes having access to data that the object points to with its member variable pointers: These continue to live only on one process, and are typically in memory areas not accessible to the other processes. As a consequence, the usual use case for this function is to share arrays of simple objects such as doubles or ints.
After calling this function, objects on different MPI processes share a common state. That means that certain operations become "collective", i.e., they must be called on all participating processors at the same time. In particular, you can no longer call resize(), reserve(), or clear() on one MPI process – you have to do so on all processes at the same time, because they have to communicate for these operations. If you do not do so, you will likely get a deadlock that may be difficult to debug. By extension, this rule of only collectively resizing extends to this function itself: You can not call it twice in a row because that implies that first all but the root_process throw away their data, which is not a collective operation. Generally, these restrictions on what can and can not be done hint at the correctness of the comments above: You should treat an AlignedVector on which the current function has been called as const, on which no further operations can be performed until the destructor is called.

◆ swap()

template<int N, typename T >
void TableBase< N, T >::swap ( TableBase< N, T > &  v)
noexcept

Swap the contents of this table and the other table v. One could do this operation with a temporary variable and copying over the data elements, but this function is significantly more efficient since it only swaps the pointers to the data of the two vectors and therefore does not need to allocate temporary storage and move data around.

This function is analogous to the swap function of all C++ standard containers. Also, there is a global function swap(u,v) that simply calls u.swap(v), again in analogy to standard functions.

◆ memory_consumption()

template<int N, typename T >
std::size_t TableBase< N, T >::memory_consumption ( ) const

Determine an estimate for the memory consumption (in bytes) of this object.

◆ serialize()

template<int N, typename T >
template<class Archive >
void TableBase< N, T >::serialize ( Archive ar,
const unsigned int  version 
)

Write or read the data of this object to or from a stream for the purpose of serialization using the BOOST serialization library.

◆ position()

template<int N, typename T >
size_type TableBase< N, T >::position ( const TableIndices< N > &  indices) const
protected

Return the position of the indicated element within the array of elements stored one after the other. This function does no index checking.

◆ el() [1/2]

template<int N, typename T >
AlignedVector< T >::reference TableBase< N, T >::el ( const TableIndices< N > &  indices)
protected

Return a read-write reference to the indicated element.

This function does no bounds checking and is only to be used internally and in functions already checked.

◆ el() [2/2]

template<int N, typename T >
AlignedVector< T >::const_reference TableBase< N, T >::el ( const TableIndices< N > &  indices) const
protected

Return the value of the indicated element as a read-only reference.

This function does no bounds checking and is only to be used internally and in functions already checked.

We return the requested value as a constant reference rather than by value since this object may hold data types that may be large, and we don't know here whether copying is expensive or not.

◆ subscribe()

void EnableObserverPointer::subscribe ( std::atomic< bool > *const  validity,
const std::string &  identifier = "" 
) const
inherited

Subscribes a user of the object by storing the pointer validity. The subscriber may be identified by text supplied as identifier.

Definition at line 131 of file enable_observer_pointer.cc.

◆ unsubscribe()

void EnableObserverPointer::unsubscribe ( std::atomic< bool > *const  validity,
const std::string &  identifier = "" 
) const
inherited

Unsubscribes a user from the object.

Note
The identifier and the validity pointer must be the same as the one supplied to subscribe().

Definition at line 151 of file enable_observer_pointer.cc.

◆ n_subscriptions()

unsigned int EnableObserverPointer::n_subscriptions ( ) const
inlineinherited

Return the present number of subscriptions to this object. This allows to use this class for reference counted lifetime determination where the last one to unsubscribe also deletes the object.

Definition at line 322 of file enable_observer_pointer.h.

◆ list_subscribers() [1/2]

template<typename StreamType >
void EnableObserverPointer::list_subscribers ( StreamType stream) const
inlineinherited

List the subscribers to the input stream.

Definition at line 339 of file enable_observer_pointer.h.

◆ list_subscribers() [2/2]

void EnableObserverPointer::list_subscribers ( ) const
inherited

List the subscribers to deallog.

Definition at line 199 of file enable_observer_pointer.cc.

◆ check_no_subscribers()

void EnableObserverPointer::check_no_subscribers ( ) const
privatenoexceptinherited

Check that there are no objects subscribing to this object. If this check passes then it is safe to destroy the current object. It this check fails then this function will either abort or print an error message to deallog (by using the AssertNothrow mechanism), but will not throw an exception.

Note
Since this function is just a consistency check it does nothing in release mode.
If this function is called when there is an uncaught exception then, rather than aborting, this function prints an error message to the standard error stream and returns.

Definition at line 53 of file enable_observer_pointer.cc.

Friends And Related Symbol Documentation

◆ TableBase

template<int N, typename T >
template<int , typename >
friend class TableBase
friend

Definition at line 804 of file table.h.

Member Data Documentation

◆ values

template<int N, typename T >
AlignedVector<T> TableBase< N, T >::values
protected

Component-array.

Definition at line 795 of file table.h.

◆ table_size

template<int N, typename T >
TableIndices<N> TableBase< N, T >::table_size
protected

Size in each direction of the table.

Definition at line 800 of file table.h.

◆ counter

std::atomic<unsigned int> EnableObserverPointer::counter
mutableprivateinherited

Store the number of objects which subscribed to this object. Initially, this number is zero, and upon destruction it shall be zero again (i.e. all objects which subscribed should have unsubscribed again).

The creator (and owner) of an object is counted in the map below if HE manages to supply identification.

We use the mutable keyword in order to allow subscription to constant objects also.

This counter may be read from and written to concurrently in multithreaded code: hence we use the std::atomic class template.

Definition at line 227 of file enable_observer_pointer.h.

◆ counter_map

std::map<std::string, unsigned int> EnableObserverPointer::counter_map
mutableprivateinherited

In this map, we count subscriptions for each different identification string supplied to subscribe().

Definition at line 233 of file enable_observer_pointer.h.

◆ validity_pointers

std::vector<std::atomic<bool> *> EnableObserverPointer::validity_pointers
mutableprivateinherited

In this vector, we store pointers to the validity bool in the ObserverPointer objects that subscribe to this class.

Definition at line 249 of file enable_observer_pointer.h.

◆ object_info

const std::type_info* EnableObserverPointer::object_info
mutableprivateinherited

Pointer to the typeinfo object of this object, from which we can later deduce the class name. Since this information on the derived class is neither available in the destructor, nor in the constructor, we obtain it in between and store it here.

Definition at line 257 of file enable_observer_pointer.h.

◆ mutex

std::mutex EnableObserverPointer::mutex
staticprivateinherited

A mutex used to ensure data consistency when accessing the mutable members of this class. This lock is used in the subscribe() and unsubscribe() functions, as well as in list_subscribers().

Definition at line 280 of file enable_observer_pointer.h.


The documentation for this class was generated from the following file: